fix shop
This commit is contained in:
@ -5,7 +5,7 @@
|
||||
<p class="text-lg xl:text-2xl font-extrabold text-black dark:text-white">
|
||||
{{ mainCategoryName }}
|
||||
</p>
|
||||
<span :class="['flex items-center justify-center',isOpen && 'rotate-180', 'transition-all']"><i
|
||||
<span :class="['flex items-center justify-center', isOpen && 'rotate-180', 'transition-all']"><i
|
||||
class="iconify i-lucide:chevron-down text-button shrink-0 size-6 ms-auto"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
@ -40,7 +40,7 @@ const toggle = () => {
|
||||
};
|
||||
|
||||
// Computed properties
|
||||
const hasMainCategory = computed(() => props.data.length > 0 && props.data[0].langs && props.data[0].langs.length > 0);
|
||||
const hasMainCategory = computed(() => props.data && props.data.length > 0 && props.data[0].langs && props.data[0].langs.length > 0);
|
||||
const mainCategoryName = computed(() => hasMainCategory.value ? props.data[0].langs[0].Name : '');
|
||||
const subcategories = computed(() => hasMainCategory.value && props.data[0].children ? props.data[0].children : []);
|
||||
</script>
|
@ -17,7 +17,7 @@
|
||||
</h1>
|
||||
<div class="flex flex-col gap-[25px]">
|
||||
<div>
|
||||
<div v-if="categoriesList.length < 1" class="animate-pulse">
|
||||
<div v-if="categoriesList && categoriesList.length < 1" class="animate-pulse">
|
||||
<div
|
||||
class="flex items-center justify-between mt-4 text-white rounded-lg cursor-pointer xl:pr-24">
|
||||
<div class="w-32 h-4 bg-gray-200 rounded"></div>
|
||||
@ -34,7 +34,8 @@
|
||||
|
||||
<div v-for="(item, itemIndex) in filters" :key="itemIndex"
|
||||
:class="['mb-[30px]', visibleFeatures[item.feature] && 'border-b border-block pb-2']">
|
||||
<span class="flex justify-between items-center font-bold cursor-pointer mb-[25px] text-base"
|
||||
<span
|
||||
class="flex justify-between items-center font-bold cursor-pointer mb-[25px] text-base"
|
||||
@click="toggleFeature(item.feature)">
|
||||
{{ item.feature }}
|
||||
<span :class="[visibleFeatures[item.feature] && 'rotate-180', 'transition-all']"><i
|
||||
@ -69,7 +70,7 @@
|
||||
<div class="flex flex-col gap-12">
|
||||
<div>
|
||||
|
||||
<div v-if="categoriesList.length < 1" class="animate-pulse">
|
||||
<div v-if="categoriesList && categoriesList.length < 1" class="animate-pulse">
|
||||
<div
|
||||
class="flex items-center justify-between mt-4 text-white rounded-lg cursor-pointer xl:pr-24">
|
||||
<div class="w-32 h-4 bg-gray-200 rounded"></div>
|
||||
@ -167,7 +168,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import Product from "./Product.vue";
|
||||
import type { Feature, ProductType } from "~/types";
|
||||
import type { Feature, GenericResponse, GenericResponseChildren, GenericResponseItems, ProductType } from "~/types";
|
||||
import CategoryTree from "./CategoryTree.vue";
|
||||
|
||||
defineProps<{ component: Component }>();
|
||||
@ -199,19 +200,21 @@ const maxElements = ref(0);
|
||||
const products = ref([] as ProductType[]);
|
||||
async function getProducts() {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`http://127.0.0.1:4000/api/public/products/category/${categoryId.value}?p=${page.value}&elems=${elems.value}`,
|
||||
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
|
||||
`/api/public/products/category/${categoryId.value}?p=${page.value}&elems=${elems.value}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => {
|
||||
throw new Error(`HTTP error: ${status}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
products.value = data.data.items;
|
||||
maxElements.value = data.data.items_count + 1;
|
||||
products.value = data.items;
|
||||
maxElements.value = data.items_count + 1;
|
||||
|
||||
} catch (error) {
|
||||
console.error("getProducts error:", error);
|
||||
@ -221,18 +224,20 @@ async function getProducts() {
|
||||
const filters = ref([] as Feature[]);
|
||||
async function getCategory() {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`http://127.0.0.1:4000/api/public/products/category/1/classification`,
|
||||
const { data } = await useMyFetch<GenericResponse<object>>(
|
||||
`/api/public/products/category/1/classification`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => {
|
||||
throw new Error(`HTTP error: ${status}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
filters.value = data.data as Feature[];
|
||||
filters.value = data as Feature[];
|
||||
filters.value.forEach((el) => {
|
||||
const parentId = el.feature_id;
|
||||
el.feature_values.forEach((el) => {
|
||||
@ -245,20 +250,22 @@ async function getCategory() {
|
||||
}
|
||||
}
|
||||
|
||||
const categoriesList = ref([]);
|
||||
const categoriesList = ref();
|
||||
async function getCategoryTree() {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`http://127.0.0.1:4000/api/public/categories/tree`,
|
||||
const { data } = await useMyFetch<GenericResponseChildren<ProductType[]>>(
|
||||
`/api/public/categories/tree`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => {
|
||||
throw new Error(`HTTP error: ${status}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const data = await res.json();
|
||||
categoriesList.value = data.data.children;
|
||||
categoriesList.value = data.children;
|
||||
|
||||
} catch (error) {
|
||||
console.error("getCategory error:", error);
|
||||
@ -325,20 +332,22 @@ async function loadMoreProducts() {
|
||||
qParams.append("features", selectedFilters.value.length > 0 ? selectedFilters.value : null);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`http://127.0.0.1:4000/api/public/products/category/${categoryId.value}?${qParams.toString()}`,
|
||||
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
|
||||
`/api/public/products/category/${categoryId.value}?${qParams.toString()}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => {
|
||||
throw new Error(`HTTP error: ${status}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const data = await res.json();
|
||||
maxElements.value = data.data.items_count;
|
||||
maxElements.value = data.items_count;
|
||||
|
||||
if (data.data.items) {
|
||||
products.value.push(...(data.data.items as ProductType[]));
|
||||
if (data.items) {
|
||||
products.value.push(...(data.items as ProductType[]));
|
||||
} else {
|
||||
reachedEnd.value = true;
|
||||
}
|
||||
@ -369,18 +378,20 @@ watch(selectedFilters, async (newQuestion) => {
|
||||
qParams.append("features", selectedFilters.value.length > 0 ? selectedFilters.value : null);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`http://127.0.0.1:4000/api/public/products/category/1?${qParams.toString()}`,
|
||||
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
|
||||
`/api/public/products/category/1?${qParams.toString()}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => {
|
||||
throw new Error(`HTTP error: ${status}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const data = await res.json();
|
||||
products.value = data.data.items;
|
||||
maxElements.value = data.data.items_count;
|
||||
products.value = data.items;
|
||||
maxElements.value = data.items_count;
|
||||
|
||||
} catch (error) {
|
||||
console.error("selectedFilters error:", error);
|
||||
@ -401,18 +412,20 @@ watch(categoryId, async (newQuestion) => {
|
||||
qParams.append("features", selectedFilters.value.length > 0 ? selectedFilters.value : null);
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`http://127.0.0.1:4000/api/public/products/category/${categoryId.value}?${qParams.toString()}`,
|
||||
const { data } = await useMyFetch<GenericResponseItems<ProductType[]>>(
|
||||
`api/public/products/category/${categoryId.value}?${qParams.toString()}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => {
|
||||
throw new Error(`HTTP error: ${status}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const data = await res.json();
|
||||
products.value = data.data.items;
|
||||
maxElements.value = data.data.items_count;
|
||||
products.value = data.items;
|
||||
maxElements.value = data.items_count;
|
||||
|
||||
} catch (error) {
|
||||
console.error("getCategory error:", error);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ofetch } from "ofetch";
|
||||
|
||||
export interface RequestOptions<T> extends RequestInit {
|
||||
onErrorOccured?: (error: Error, statusCode: number) => void;
|
||||
onErrorOccured?: (error: Error, statusCode: number) => Promise<void>;
|
||||
onSuccess?: (data: T, statusCode: number) => void;
|
||||
onStart?: () => void;
|
||||
}
|
||||
@ -21,7 +21,10 @@ export interface RequestOptions<T> extends RequestInit {
|
||||
* @example
|
||||
* const { data } = useMyFetch<{ name: string }>('/api/user')
|
||||
*/
|
||||
export const useMyFetch = async <T>(url: string, options?: RequestOptions<T>): Promise<T > => {
|
||||
export const useMyFetch = async <T>(
|
||||
url: string,
|
||||
options?: RequestOptions<T>
|
||||
): Promise<T> => {
|
||||
if (options?.onStart) options.onStart();
|
||||
let response = null;
|
||||
try {
|
||||
@ -32,7 +35,8 @@ export const useMyFetch = async <T>(url: string, options?: RequestOptions<T>): P
|
||||
options.credentials = "include";
|
||||
|
||||
if (import.meta.server) {
|
||||
const api_uri = event?.node.req.headers["api-uri"] || "http://localhost:4000";
|
||||
const api_uri =
|
||||
event?.node.req.headers["api-uri"] || "http://localhost:4000";
|
||||
url = api_uri + url;
|
||||
options.headers = event?.headers;
|
||||
}
|
||||
@ -52,7 +56,7 @@ export const useMyFetch = async <T>(url: string, options?: RequestOptions<T>): P
|
||||
|
||||
// handle success to be able clearly marked that request has finished
|
||||
if (response.ok && typeof options.onSuccess == "function") {
|
||||
options.onSuccess( response._data, response.status);
|
||||
options.onSuccess(response._data, response.status);
|
||||
}
|
||||
|
||||
return response._data as T;
|
||||
|
11
error.vue
Normal file
11
error.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="p-10 text-center">
|
||||
<h1 class="text-3xl font-bold">Error {{ error?.statusCode }}</h1>
|
||||
<p class="mt-4 text-gray-600">{{ error?.statusMessage }}</p>
|
||||
<NuxtLink to="/" class="mt-6 text-blue-500 underline">Go back home</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({ error: Object })
|
||||
</script>
|
@ -1,30 +1,31 @@
|
||||
import { NuxtErrorBoundary } from "#components";
|
||||
import { useMyFetch } from "#imports";
|
||||
import type { GenericResponse, UserCart } from "~/types";
|
||||
import type { GenericResponse, GenericResponseItems, UserCart } from "~/types";
|
||||
import type { Product } from "~/types/product";
|
||||
|
||||
export const useProductStore = defineStore("productStore", () => {
|
||||
const productList = ref();
|
||||
const productList = ref<Product>();
|
||||
const modules = ref();
|
||||
|
||||
async function getList(count: number, categoryId = 1) {
|
||||
try {
|
||||
const {data} = await useMyFetch<GenericResponse<object>>(
|
||||
const { data } = await useMyFetch<GenericResponseItems<Product>>(
|
||||
`/api/public/products/category/${categoryId}?p=1&elems=${count}`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => { throw new Error(`HTTP error: ${status}`) },
|
||||
onErrorOccured: async (_, status) => {
|
||||
// await navigateTo("/error", { replace: true });
|
||||
throw createError({
|
||||
statusCode: status,
|
||||
statusMessage: `HTTP error: ${status}`,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log(data);
|
||||
|
||||
// if (!res.ok) {
|
||||
// throw new Error(`HTTP error: ${res.status}`);
|
||||
// }
|
||||
|
||||
// const data = await res.json();
|
||||
// productList.value = data.data.items;
|
||||
productList.value = data.items;
|
||||
} catch (error) {
|
||||
console.error("getList error:", error);
|
||||
}
|
||||
@ -55,7 +56,7 @@ console.log(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function addToCart(product) {
|
||||
async function addToCart(product: Product) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`http://127.0.0.1:4000/api/public/user/cart/item/add/${product.id}/1`,
|
||||
@ -81,12 +82,17 @@ console.log(data);
|
||||
const cart = ref({} as UserCart);
|
||||
async function getCart() {
|
||||
try {
|
||||
const {data} = await useMyFetch<GenericResponse<UserCart>>(`/api/public/user/cart`, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => { throw new Error(`HTTP error: ${status}`) },
|
||||
});
|
||||
const { data } = await useMyFetch<GenericResponse<UserCart>>(
|
||||
`/api/public/user/cart`,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
onErrorOccured: (_, status) => {
|
||||
throw new Error(`HTTP error: ${status}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// if (!res.ok) {
|
||||
// throw new Error(`HTTP error: ${res.status}`);
|
||||
|
123
types/index.ts
123
types/index.ts
@ -44,7 +44,7 @@ export interface PBFooterItem {
|
||||
}>;
|
||||
data: Array<{
|
||||
title: string;
|
||||
items: Array<string>
|
||||
items: Array<string>;
|
||||
}>;
|
||||
company_info: Array<{
|
||||
data: string;
|
||||
@ -71,7 +71,6 @@ export interface PBPageItem {
|
||||
section_name: string;
|
||||
}
|
||||
|
||||
|
||||
export interface SectionLangData {
|
||||
title: string;
|
||||
description: string;
|
||||
@ -111,13 +110,13 @@ export type Countries = {
|
||||
currency_iso_code: string;
|
||||
iso_code: string;
|
||||
name: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type PartnersList = {
|
||||
country_iso: string;
|
||||
country_name: string;
|
||||
total: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type Currencies = {
|
||||
iso_code: string;
|
||||
@ -128,78 +127,88 @@ export type Currencies = {
|
||||
sign: string;
|
||||
active: boolean;
|
||||
suffix: boolean;
|
||||
}
|
||||
};
|
||||
|
||||
export type FeatureValue = {
|
||||
parent: number,
|
||||
products_with_value: number,
|
||||
value: string,
|
||||
value_id: number,
|
||||
}
|
||||
parent: number;
|
||||
products_with_value: number;
|
||||
value: string;
|
||||
value_id: number;
|
||||
};
|
||||
|
||||
export type Feature = {
|
||||
feature: string,
|
||||
feature_id: number,
|
||||
feature_values: FeatureValue[],
|
||||
products_with_feature: number,
|
||||
}
|
||||
feature: string;
|
||||
feature_id: number;
|
||||
feature_values: FeatureValue[];
|
||||
products_with_feature: number;
|
||||
};
|
||||
|
||||
export type ProductType = {
|
||||
applied_tax_rate: number,
|
||||
cover_picture_uuid: string,
|
||||
description: string,
|
||||
formatted_price: string,
|
||||
id: number,
|
||||
in_stock: number,
|
||||
is_sale_active: boolean,
|
||||
link_rewrite: string,
|
||||
name: string,
|
||||
price: number,
|
||||
tax_name: string,
|
||||
cart_item_id?: number
|
||||
product_id?: number
|
||||
}
|
||||
|
||||
applied_tax_rate: number;
|
||||
cover_picture_uuid: string;
|
||||
description: string;
|
||||
formatted_price: string;
|
||||
id: number;
|
||||
in_stock: number;
|
||||
is_sale_active: boolean;
|
||||
link_rewrite: string;
|
||||
name: string;
|
||||
price: number;
|
||||
tax_name: string;
|
||||
cart_item_id?: number;
|
||||
product_id?: number;
|
||||
};
|
||||
|
||||
export interface Country {
|
||||
iso_code: string
|
||||
currency_iso_code: string
|
||||
call_prefix: string
|
||||
need_postcode: boolean
|
||||
postcode_format: string
|
||||
is_default: boolean
|
||||
active: boolean
|
||||
name: string
|
||||
iso_code: string;
|
||||
currency_iso_code: string;
|
||||
call_prefix: string;
|
||||
need_postcode: boolean;
|
||||
postcode_format: string;
|
||||
is_default: boolean;
|
||||
active: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Currency {
|
||||
iso_code: string
|
||||
name: string
|
||||
UpdatedAt: string
|
||||
iso_code_num: number
|
||||
precision: number
|
||||
sign: string
|
||||
active: boolean
|
||||
suffix: boolean
|
||||
iso_code: string;
|
||||
name: string;
|
||||
UpdatedAt: string;
|
||||
iso_code_num: number;
|
||||
precision: number;
|
||||
sign: string;
|
||||
active: boolean;
|
||||
suffix: boolean;
|
||||
}
|
||||
|
||||
export interface UserCart {
|
||||
id: number
|
||||
checkout_in_progress: boolean
|
||||
total_value: number
|
||||
currency_iso: string
|
||||
id: number;
|
||||
checkout_in_progress: boolean;
|
||||
total_value: number;
|
||||
currency_iso: string;
|
||||
}
|
||||
|
||||
export interface GenericResponse<Data> {
|
||||
data: Data
|
||||
message?: string
|
||||
status: number
|
||||
data: Data;
|
||||
message?: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface GenericResponseItems<Data> {
|
||||
data: { items: Data , items_count: number }
|
||||
message?: string
|
||||
status: number
|
||||
data: { items: Data; items_count: number };
|
||||
message?: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export type { InvestmentPiece, PlanPrediction, PeriodToFirstPiece} from './PlanPrediction'
|
||||
export interface GenericResponseChildren<Data> {
|
||||
data: { children: Data; items_count: number };
|
||||
message?: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export type {
|
||||
InvestmentPiece,
|
||||
PlanPrediction,
|
||||
PeriodToFirstPiece,
|
||||
} from "./planPrediction";
|
||||
export type { Product } from "./product";
|
||||
|
13
types/product.ts
Normal file
13
types/product.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export interface Product {
|
||||
id: number;
|
||||
link_rewrite: string;
|
||||
name: string;
|
||||
cover_picture_uuid: string;
|
||||
description: string;
|
||||
price: number;
|
||||
formatted_price: string;
|
||||
in_stock: number;
|
||||
is_sale_active: boolean;
|
||||
applied_tax_rate: number;
|
||||
tax_name: string;
|
||||
}
|
Reference in New Issue
Block a user