init
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { api } from '@/plugins/axios';
|
||||
|
||||
const CMS_AUTH_STORAGE_KEY = 'cms-auth';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const isSignedIn = ref(localStorage.getItem(CMS_AUTH_STORAGE_KEY) === '1');
|
||||
|
||||
const markSignedIn = () => {
|
||||
localStorage.setItem(CMS_AUTH_STORAGE_KEY, '1');
|
||||
isSignedIn.value = true;
|
||||
};
|
||||
|
||||
const clearSession = () => {
|
||||
localStorage.removeItem(CMS_AUTH_STORAGE_KEY);
|
||||
isSignedIn.value = false;
|
||||
};
|
||||
|
||||
const login = async (password: string) => {
|
||||
await api.post('/auth/login', { password });
|
||||
|
||||
markSignedIn();
|
||||
};
|
||||
|
||||
return {
|
||||
isSignedIn,
|
||||
login,
|
||||
clearSession
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { api } from '@/plugins/axios';
|
||||
import type { CreateOrUpdateCategoryPayload } from '@/types/category/CreateOrUpdateCategoryPayload';
|
||||
import type { Category } from '@/types/product/Category';
|
||||
|
||||
export const useCategoriesStore = defineStore('categories', () => {
|
||||
const categories = ref<Category[]>([]);
|
||||
|
||||
const upsertCategory = (data: Category): void => {
|
||||
const idx = categories.value.findIndex(c => c.id === data.id);
|
||||
|
||||
if (idx !== -1) {
|
||||
categories.value[idx] = data;
|
||||
} else {
|
||||
categories.value.push(data);
|
||||
}
|
||||
|
||||
categories.value.sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name));
|
||||
};
|
||||
|
||||
const fetchAll = async (): Promise<void> => {
|
||||
const { data } = await api.get<Category[]>('/categories');
|
||||
|
||||
categories.value = data;
|
||||
};
|
||||
|
||||
const createCategory = async (payload: CreateOrUpdateCategoryPayload): Promise<Category> => {
|
||||
const { data } = await api.post<Category>('/categories', payload);
|
||||
|
||||
upsertCategory(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateCategory = async (id: string, payload: CreateOrUpdateCategoryPayload): Promise<Category> => {
|
||||
const { data } = await api.patch<Category>(`/categories/${id}`, payload);
|
||||
|
||||
upsertCategory(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const removeCategory = async (id: string): Promise<void> => {
|
||||
await api.delete(`/categories/${id}`);
|
||||
|
||||
categories.value = categories.value.filter(c => c.id !== id);
|
||||
};
|
||||
|
||||
return {
|
||||
categories,
|
||||
fetchAll,
|
||||
createCategory,
|
||||
updateCategory,
|
||||
removeCategory
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const STORAGE_KEY = 'cms-color-mode';
|
||||
|
||||
const resolveInitialDark = (): boolean => {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
|
||||
if (saved === 'dark') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (saved === 'light') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const useColorSchemeStore = defineStore('colorScheme', () => {
|
||||
const isDark = ref(false);
|
||||
|
||||
const initColorScheme = (): void => {
|
||||
isDark.value = resolveInitialDark();
|
||||
|
||||
document.documentElement.classList.toggle('dark', isDark.value);
|
||||
};
|
||||
|
||||
const toggleDarkMode = (): void => {
|
||||
isDark.value = !isDark.value;
|
||||
|
||||
document.documentElement.classList.toggle('dark', isDark.value);
|
||||
|
||||
localStorage.setItem(STORAGE_KEY, isDark.value ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
return {
|
||||
isDark,
|
||||
initColorScheme,
|
||||
toggleDarkMode
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,148 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { api } from '@/plugins/axios';
|
||||
import type { PaginatedResponse } from '@/types/PaginatedResponse';
|
||||
import type { DigitalStockItem } from '@/types/product/DigitalStockItem';
|
||||
import type { DigitalStockListQuery } from '@/types/product/DigitalStockListQuery';
|
||||
import { useProductsStore } from './products';
|
||||
|
||||
export const useDigitalStockStore = defineStore('digitalStock', () => {
|
||||
const items = ref<DigitalStockItem[]>([]);
|
||||
|
||||
const resetList = (): void => {
|
||||
items.value = [];
|
||||
};
|
||||
|
||||
const upsertItemInList = (item: DigitalStockItem): void => {
|
||||
const idx = items.value.findIndex(row => row.id === item.id);
|
||||
|
||||
if (idx === -1) {
|
||||
items.value.push(item);
|
||||
} else {
|
||||
items.value[idx] = item;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchList = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
{ page, limit, hideSold }: DigitalStockListQuery
|
||||
): Promise<PaginatedResponse<DigitalStockItem>> => {
|
||||
const { data } = await api.get<PaginatedResponse<DigitalStockItem>>(
|
||||
`/products/${productId}/variants/${variantId}/digital-stock-items`,
|
||||
{ params: { page, limit, hideSold: hideSold ? 1 : 0 } }
|
||||
);
|
||||
|
||||
items.value = data.items;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const addItem = async (productId: string, variantId: string, content: string): Promise<DigitalStockItem> => {
|
||||
const { data } = await api.post<DigitalStockItem>(
|
||||
`/products/${productId}/variants/${variantId}/digital-stock-items`,
|
||||
{ content }
|
||||
);
|
||||
|
||||
const { adjustVariantStockAvailable } = useProductsStore();
|
||||
|
||||
adjustVariantStockAvailable(productId, variantId, 1);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateItem = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
itemId: string,
|
||||
content: string
|
||||
): Promise<DigitalStockItem> => {
|
||||
const { data } = await api.patch<DigitalStockItem>(
|
||||
`/products/${productId}/variants/${variantId}/digital-stock-items/${itemId}`,
|
||||
{ content }
|
||||
);
|
||||
|
||||
upsertItemInList(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const removeItem = async (productId: string, variantId: string, itemId: string, isSold: boolean): Promise<void> => {
|
||||
await api.delete(`/products/${productId}/variants/${variantId}/digital-stock-items/${itemId}`);
|
||||
|
||||
if (!isSold) {
|
||||
const { adjustVariantStockAvailable } = useProductsStore();
|
||||
|
||||
adjustVariantStockAvailable(productId, variantId, -1);
|
||||
}
|
||||
};
|
||||
|
||||
const uploadAttachment = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
itemId: string,
|
||||
file: File
|
||||
): Promise<DigitalStockItem> => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('file', file);
|
||||
|
||||
const { data } = await api.post<DigitalStockItem>(
|
||||
`/products/${productId}/variants/${variantId}/digital-stock-items/${itemId}/attachments`,
|
||||
formData
|
||||
);
|
||||
|
||||
upsertItemInList(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const removeAttachment = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
itemId: string,
|
||||
attachmentId: string
|
||||
): Promise<DigitalStockItem> => {
|
||||
const { data } = await api.delete<DigitalStockItem>(
|
||||
`/products/${productId}/variants/${variantId}/digital-stock-items/${itemId}/attachments/${attachmentId}`
|
||||
);
|
||||
|
||||
upsertItemInList(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const downloadAttachment = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
itemId: string,
|
||||
attachmentId: string,
|
||||
filename: string
|
||||
): Promise<void> => {
|
||||
const { data } = await api.get<Blob>(
|
||||
`/products/${productId}/variants/${variantId}/digital-stock-items/${itemId}/attachments/${attachmentId}/download`,
|
||||
{ responseType: 'blob' }
|
||||
);
|
||||
|
||||
const url = URL.createObjectURL(data);
|
||||
const anchor = document.createElement('a');
|
||||
|
||||
anchor.href = url;
|
||||
anchor.download = filename;
|
||||
anchor.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
resetList,
|
||||
fetchList,
|
||||
addItem,
|
||||
updateItem,
|
||||
removeItem,
|
||||
uploadAttachment,
|
||||
removeAttachment,
|
||||
downloadAttachment
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { api } from '@/plugins/axios';
|
||||
import type { CreateOrUpdateDiscountCodePayload } from '@/types/discountCode/CreateOrUpdateDiscountCodePayload';
|
||||
import type { DiscountCode } from '@/types/discountCode/DiscountCode';
|
||||
|
||||
export const useDiscountCodesStore = defineStore('discountCodes', () => {
|
||||
const discountCodes = ref<DiscountCode[]>([]);
|
||||
|
||||
const upsertDiscountCode = (data: DiscountCode): void => {
|
||||
const idx = discountCodes.value.findIndex(c => c.id === data.id);
|
||||
|
||||
if (idx !== -1) {
|
||||
discountCodes.value[idx] = data;
|
||||
} else {
|
||||
discountCodes.value.unshift(data);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchAll = async (): Promise<void> => {
|
||||
const { data } = await api.get<DiscountCode[]>('/discount-codes');
|
||||
|
||||
discountCodes.value = data;
|
||||
};
|
||||
|
||||
const fetchDiscountCodeById = async (id: string): Promise<DiscountCode> => {
|
||||
const { data } = await api.get<DiscountCode>(`/discount-codes/${id}`);
|
||||
|
||||
upsertDiscountCode(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const createDiscountCode = async (payload: CreateOrUpdateDiscountCodePayload): Promise<DiscountCode> => {
|
||||
const { data } = await api.post<DiscountCode>('/discount-codes', payload);
|
||||
|
||||
upsertDiscountCode(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateDiscountCode = async (
|
||||
id: string,
|
||||
payload: CreateOrUpdateDiscountCodePayload
|
||||
): Promise<DiscountCode> => {
|
||||
const { data } = await api.patch<DiscountCode>(`/discount-codes/${id}`, payload);
|
||||
|
||||
upsertDiscountCode(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const removeDiscountCode = async (id: string): Promise<void> => {
|
||||
await api.delete(`/discount-codes/${id}`);
|
||||
|
||||
discountCodes.value = discountCodes.value.filter(c => c.id !== id);
|
||||
};
|
||||
|
||||
return {
|
||||
discountCodes,
|
||||
fetchAll,
|
||||
fetchDiscountCodeById,
|
||||
createDiscountCode,
|
||||
updateDiscountCode,
|
||||
removeDiscountCode
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { api } from '@/plugins/axios';
|
||||
import type { MoneroWalletRevealSeedPayload } from '@/types/moneroWallet/MoneroWalletRevealSeedPayload';
|
||||
import type { MoneroWalletRevealSeedResult } from '@/types/moneroWallet/MoneroWalletRevealSeedResult';
|
||||
import type { MoneroWalletStatus } from '@/types/moneroWallet/MoneroWalletStatus';
|
||||
import type { MoneroWalletWithdrawPayload } from '@/types/moneroWallet/MoneroWalletWithdrawPayload';
|
||||
import type { MoneroWalletWithdrawResult } from '@/types/moneroWallet/MoneroWalletWithdrawResult';
|
||||
|
||||
export const useMoneroWalletStore = defineStore('moneroWallet', () => {
|
||||
const status = ref<MoneroWalletStatus | null>(null);
|
||||
|
||||
const fetchStatus = async (): Promise<MoneroWalletStatus> => {
|
||||
const { data } = await api.get<MoneroWalletStatus>('/monero-wallet');
|
||||
|
||||
status.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const withdrawAll = async (payload: MoneroWalletWithdrawPayload): Promise<MoneroWalletWithdrawResult> => {
|
||||
const { data } = await api.post<MoneroWalletWithdrawResult>('/monero-wallet/withdraw', payload);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const revealSeed = async (payload: MoneroWalletRevealSeedPayload): Promise<MoneroWalletRevealSeedResult> => {
|
||||
const { data } = await api.post<MoneroWalletRevealSeedResult>('/monero-wallet/reveal-seed', payload);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return {
|
||||
status,
|
||||
fetchStatus,
|
||||
withdrawAll,
|
||||
revealSeed
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ROUTE_NAMES } from '@/consts/routeNames';
|
||||
import { api } from '@/plugins/axios';
|
||||
import type { PaginatedResponse } from '@/types/PaginatedResponse';
|
||||
import type { OrderExtended } from '@/types/order/OrderExtended';
|
||||
import type { OrderListItem } from '@/types/order/OrderListItem';
|
||||
import type { OrderMessage } from '@/types/order/OrderMessage';
|
||||
import type { SetDeliveryCostPayload } from '@/types/order/SetDeliveryCostPayload';
|
||||
|
||||
export const useOrdersStore = defineStore('orders', () => {
|
||||
const route = useRoute();
|
||||
|
||||
const orderList = ref<OrderListItem[]>([]);
|
||||
const currentOrder = ref<OrderExtended | null>(null);
|
||||
|
||||
const currentOrderId = computed<string | null>(() => {
|
||||
if (route.name !== ROUTE_NAMES.OrderDetail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = route.params.id;
|
||||
|
||||
return typeof id === 'string' && id ? id : null;
|
||||
});
|
||||
|
||||
const fetchList = async ({
|
||||
page,
|
||||
limit
|
||||
}: {
|
||||
page: number;
|
||||
limit: number;
|
||||
}): Promise<PaginatedResponse<OrderListItem>> => {
|
||||
const { data } = await api.get<PaginatedResponse<OrderListItem>>('/orders', {
|
||||
params: { page, limit }
|
||||
});
|
||||
|
||||
orderList.value = data.items;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchById = async (id: string): Promise<OrderExtended> => {
|
||||
const { data } = await api.get<OrderExtended>(`/orders/${id}`);
|
||||
|
||||
currentOrder.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const markChatRead = async (id: string): Promise<void> => {
|
||||
await api.post(`/orders/${id}/messages/mark-read`);
|
||||
};
|
||||
|
||||
const sendMessage = async (id: string, body: string): Promise<OrderMessage[]> => {
|
||||
const { data } = await api.post<OrderMessage[]>(`/orders/${id}/messages`, { body });
|
||||
|
||||
if (currentOrder.value?.id === id) {
|
||||
currentOrder.value = { ...currentOrder.value, messages: data };
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const deleteMessage = async (orderId: string, messageId: string): Promise<OrderMessage[]> => {
|
||||
const { data } = await api.delete<OrderMessage[]>(`/orders/${orderId}/messages/${messageId}`);
|
||||
|
||||
if (currentOrder.value?.id === orderId) {
|
||||
currentOrder.value = { ...currentOrder.value, messages: data };
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const setDeliveryCost = async (id: string, payload: SetDeliveryCostPayload): Promise<OrderExtended> => {
|
||||
const { data } = await api.post<OrderExtended>(`/orders/${id}/delivery-cost`, payload);
|
||||
|
||||
currentOrder.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const fulfillManualLine = async (orderId: string, lineId: string): Promise<OrderExtended> => {
|
||||
const { data } = await api.post<OrderExtended>(`/orders/${orderId}/lines/${lineId}/fulfill`);
|
||||
|
||||
currentOrder.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return {
|
||||
orderList,
|
||||
currentOrder,
|
||||
currentOrderId,
|
||||
fetchList,
|
||||
fetchById,
|
||||
markChatRead,
|
||||
sendMessage,
|
||||
deleteMessage,
|
||||
setDeliveryCost,
|
||||
fulfillManualLine
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,335 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import type { FormRules } from 'element-plus';
|
||||
import { config } from '@/config';
|
||||
import { ROUTE_NAMES } from '@/consts/routeNames';
|
||||
import { api } from '@/plugins/axios';
|
||||
import type { PaginatedResponse } from '@/types/PaginatedResponse';
|
||||
import type { ProductWithVariantsExtended } from '@/types/product/ProductWithVariantsExtended';
|
||||
import { DeliveryMode } from '@/types/product/DeliveryMode';
|
||||
import type { UpdateProductPayload } from '@/types/product/UpdateProductPayload';
|
||||
import type { ProductVariantPayload } from '@/types/product/ProductVariantPayload';
|
||||
import type { ProductVariant } from '@/types/product/ProductVariant';
|
||||
import type { ProductVariantExtended } from '@/types/product/ProductVariantExtended';
|
||||
import { compareProductVariants } from '@/utils/product/compareProductVariants';
|
||||
import { formatDeliveryMode } from '@/utils/product/formatDeliveryMode';
|
||||
import { getProductTitle } from '@/utils/product/getProductTitle';
|
||||
|
||||
const {
|
||||
validation: { productTitleMaxLength: validationProductTitleMaxLength }
|
||||
} = config;
|
||||
|
||||
export const useProductsStore = defineStore('products', () => {
|
||||
const route = useRoute();
|
||||
const products = ref<ProductWithVariantsExtended[]>([]);
|
||||
|
||||
const currentProductId = computed<string | null>(() => {
|
||||
if (route.name === ROUTE_NAMES.ProductDetail) {
|
||||
const id = route.params.id;
|
||||
|
||||
return typeof id === 'string' && id ? id : null;
|
||||
}
|
||||
|
||||
if (route.name === ROUTE_NAMES.ProductVariantDetail) {
|
||||
const id = route.params.productId;
|
||||
|
||||
return typeof id === 'string' && id ? id : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
const currentVariantId = computed<string | null>(() => {
|
||||
if (route.name !== ROUTE_NAMES.ProductVariantDetail) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = route.params.variantId;
|
||||
|
||||
return typeof id === 'string' && id ? id : null;
|
||||
});
|
||||
|
||||
const currentProduct = computed<ProductWithVariantsExtended | null>(() => {
|
||||
if (!currentProductId.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return products.value.find(product => product.id === currentProductId.value) ?? null;
|
||||
});
|
||||
|
||||
const currentVariant = computed<ProductVariantExtended | null>(() => {
|
||||
if (!currentVariantId.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return currentProduct.value?.variants.find(variant => variant.id === currentVariantId.value) ?? null;
|
||||
});
|
||||
|
||||
const currentDeliveryModeLabel = computed(() =>
|
||||
currentProduct.value ? formatDeliveryMode(currentProduct.value.deliveryMode) : null
|
||||
);
|
||||
|
||||
const currentProductDisplayTitle = computed(() =>
|
||||
currentProduct.value ? getProductTitle(currentProduct.value.title) : null
|
||||
);
|
||||
|
||||
const currentProductIsManual = computed(
|
||||
() => currentProduct.value?.deliveryMode === DeliveryMode.Manual
|
||||
);
|
||||
|
||||
const currentProductIsAuto = computed(
|
||||
() => currentProduct.value?.deliveryMode === DeliveryMode.Auto
|
||||
);
|
||||
|
||||
const canDeleteCurrentVariant = computed(() => (currentProduct.value?.variants.length ?? 0) > 1);
|
||||
|
||||
const createOrEditCurrentProductVariantFormRules = computed<FormRules>(() => ({
|
||||
title: [
|
||||
{ required: true, message: 'Required', trigger: 'blur' },
|
||||
{
|
||||
max: validationProductTitleMaxLength,
|
||||
message: `At most ${validationProductTitleMaxLength} characters`,
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
price: [{ required: true, message: 'Required', trigger: 'change' }],
|
||||
...(currentProductIsManual.value
|
||||
? {
|
||||
stockQuantity: [
|
||||
{ required: true, message: 'Stock quantity is required', trigger: 'change' },
|
||||
{ type: 'integer', message: 'Stock quantity must be a whole number', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
: {}),
|
||||
sortOrder: [
|
||||
{ required: true, message: 'Sort order is required', trigger: 'change' },
|
||||
{ type: 'integer', message: 'Sort order must be a whole number', trigger: 'change' }
|
||||
]
|
||||
}));
|
||||
|
||||
const upsertProduct = (data: ProductWithVariantsExtended): void => {
|
||||
const idx = products.value.findIndex(r => r.id === data.id);
|
||||
|
||||
if (idx !== -1) {
|
||||
products.value[idx] = data;
|
||||
} else {
|
||||
products.value.unshift(data);
|
||||
}
|
||||
};
|
||||
|
||||
const upsertProductVariant = (productId: string, variant: ProductVariantExtended): void => {
|
||||
const product = products.value.find(p => p.id === productId);
|
||||
|
||||
if (!product) {
|
||||
return;
|
||||
}
|
||||
|
||||
const idx = product.variants.findIndex(v => v.id === variant.id);
|
||||
|
||||
if (idx === -1) {
|
||||
product.variants.push(variant);
|
||||
} else {
|
||||
product.variants[idx] = variant;
|
||||
}
|
||||
|
||||
product.variants.sort(compareProductVariants);
|
||||
};
|
||||
|
||||
const adjustVariantStockAvailable = (productId: string, variantId: string, delta: number): void => {
|
||||
const product = products.value.find(p => p.id === productId);
|
||||
const variant = product?.variants.find(v => v.id === variantId);
|
||||
|
||||
if (variant) {
|
||||
variant.stockAvailable = Math.max(0, variant.stockAvailable + delta);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchProducts = async ({
|
||||
page,
|
||||
limit,
|
||||
search = ''
|
||||
}: {
|
||||
page: number;
|
||||
limit: number;
|
||||
search?: string;
|
||||
}): Promise<PaginatedResponse<ProductWithVariantsExtended>> => {
|
||||
const { data } = await api.get<PaginatedResponse<ProductWithVariantsExtended>>('/products', {
|
||||
params: { page, limit, search }
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchProductVariants = async ({
|
||||
search = '',
|
||||
page = 1,
|
||||
limit = 20
|
||||
}: {
|
||||
search?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
} = {}): Promise<PaginatedResponse<ProductVariant>> => {
|
||||
const { data } = await api.get<PaginatedResponse<ProductVariant>>('/products/variants', {
|
||||
params: { search, page, limit }
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const fetchProductById = async (id: string): Promise<ProductWithVariantsExtended> => {
|
||||
const { data } = await api.get<ProductWithVariantsExtended>(`/products/${id}`);
|
||||
|
||||
upsertProduct(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const createDraftProduct = async (deliveryMode: DeliveryMode): Promise<ProductWithVariantsExtended> => {
|
||||
const { data } = await api.post<ProductWithVariantsExtended>('/products', { deliveryMode });
|
||||
|
||||
upsertProduct(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateProduct = async (id: string, payload: UpdateProductPayload): Promise<ProductWithVariantsExtended> => {
|
||||
const { data } = await api.patch<ProductWithVariantsExtended>(`/products/${id}`, payload);
|
||||
|
||||
upsertProduct(data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const createProductVariant = async (
|
||||
productId: string,
|
||||
payload: ProductVariantPayload
|
||||
): Promise<ProductVariantExtended> => {
|
||||
const { data } = await api.post<ProductVariantExtended>(`/products/${productId}/variants`, payload);
|
||||
|
||||
upsertProductVariant(productId, data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateProductVariant = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
payload: ProductVariantPayload
|
||||
): Promise<ProductVariantExtended> => {
|
||||
const { data } = await api.patch<ProductVariantExtended>(
|
||||
`/products/${productId}/variants/${variantId}`,
|
||||
payload
|
||||
);
|
||||
|
||||
upsertProductVariant(productId, data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const deleteProductVariant = async (productId: string, variantId: string): Promise<void> => {
|
||||
await api.delete(`/products/${productId}/variants/${variantId}`);
|
||||
|
||||
const product = products.value.find(p => p.id === productId);
|
||||
|
||||
if (product) {
|
||||
product.variants = product.variants.filter(v => v.id !== variantId);
|
||||
}
|
||||
};
|
||||
|
||||
const uploadVariantImage = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
file: File
|
||||
): Promise<ProductVariantExtended> => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('file', file);
|
||||
|
||||
const { data } = await api.post<ProductVariantExtended>(
|
||||
`/products/${productId}/variants/${variantId}/images`,
|
||||
formData
|
||||
);
|
||||
|
||||
upsertProductVariant(productId, data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const removeVariantImage = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
imageId: string
|
||||
): Promise<ProductVariantExtended> => {
|
||||
const { data } = await api.delete<ProductVariantExtended>(
|
||||
`/products/${productId}/variants/${variantId}/images/${imageId}`
|
||||
);
|
||||
|
||||
upsertProductVariant(productId, data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const setVariantImageThumbnail = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
imageId: string
|
||||
): Promise<ProductVariantExtended> => {
|
||||
const { data } = await api.patch<ProductVariantExtended>(
|
||||
`/products/${productId}/variants/${variantId}/images/${imageId}/set-thumbnail`
|
||||
);
|
||||
|
||||
upsertProductVariant(productId, data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const reorderVariantImages = async (
|
||||
productId: string,
|
||||
variantId: string,
|
||||
imageIds: string[]
|
||||
): Promise<ProductVariantExtended> => {
|
||||
const { data } = await api.patch<ProductVariantExtended>(
|
||||
`/products/${productId}/variants/${variantId}/images/reorder`,
|
||||
{ imageIds }
|
||||
);
|
||||
|
||||
upsertProductVariant(productId, data);
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const deleteProduct = async (id: string): Promise<void> => {
|
||||
await api.delete(`/products/${id}`);
|
||||
|
||||
products.value = products.value.filter(p => p.id !== id);
|
||||
};
|
||||
|
||||
return {
|
||||
products,
|
||||
currentProduct,
|
||||
currentVariant,
|
||||
currentProductId,
|
||||
currentVariantId,
|
||||
currentDeliveryModeLabel,
|
||||
currentProductDisplayTitle,
|
||||
currentProductIsManual,
|
||||
currentProductIsAuto,
|
||||
canDeleteCurrentVariant,
|
||||
createOrEditCurrentProductVariantFormRules,
|
||||
fetchProducts,
|
||||
fetchProductVariants,
|
||||
fetchProductById,
|
||||
adjustVariantStockAvailable,
|
||||
createDraftProduct,
|
||||
updateProduct,
|
||||
createProductVariant,
|
||||
updateProductVariant,
|
||||
deleteProductVariant,
|
||||
uploadVariantImage,
|
||||
removeVariantImage,
|
||||
setVariantImageThumbnail,
|
||||
reorderVariantImages,
|
||||
deleteProduct
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { api } from '@/plugins/axios';
|
||||
import type { ConnectSimplexNotificationsPayload } from '@/types/shopSettings/ConnectSimplexNotificationsPayload';
|
||||
import type { ShopSettings } from '@/types/shopSettings/ShopSettings';
|
||||
import type { UpdateNotificationsPayload } from '@/types/shopSettings/UpdateNotificationsPayload';
|
||||
import type { UpdateShippingNotePayload } from '@/types/shopSettings/UpdateShippingNotePayload';
|
||||
import type { UpdateSimplexLinkPayload } from '@/types/shopSettings/UpdateSimplexLinkPayload';
|
||||
|
||||
export const useShopSettingsStore = defineStore('shopSettings', () => {
|
||||
const settings = ref<ShopSettings | null>(null);
|
||||
|
||||
const fetchShopSettings = async (): Promise<ShopSettings> => {
|
||||
const { data } = await api.get<ShopSettings>('/shop-settings');
|
||||
|
||||
settings.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateSimplexLink = async (payload: UpdateSimplexLinkPayload): Promise<ShopSettings> => {
|
||||
const { data } = await api.patch<ShopSettings>('/shop-settings/simplex-link', payload);
|
||||
|
||||
settings.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateShippingNote = async (payload: UpdateShippingNotePayload): Promise<ShopSettings> => {
|
||||
const { data } = await api.patch<ShopSettings>('/shop-settings/shipping-note', payload);
|
||||
|
||||
settings.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const updateNotifications = async (payload: UpdateNotificationsPayload): Promise<ShopSettings> => {
|
||||
const { data } = await api.patch<ShopSettings>('/shop-settings/notifications', payload);
|
||||
|
||||
settings.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const connectSimplexNotifications = async (payload: ConnectSimplexNotificationsPayload): Promise<ShopSettings> => {
|
||||
const { data } = await api.post<ShopSettings>('/shop-settings/simplex-connect', payload);
|
||||
|
||||
settings.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const uploadShopLogo = async (file: File): Promise<ShopSettings> => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('file', file);
|
||||
|
||||
const { data } = await api.post<ShopSettings>('/shop-settings/logo', formData);
|
||||
|
||||
settings.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const uploadShopFavicon = async (file: File): Promise<ShopSettings> => {
|
||||
const formData = new FormData();
|
||||
|
||||
formData.append('file', file);
|
||||
|
||||
const { data } = await api.post<ShopSettings>('/shop-settings/favicon', formData);
|
||||
|
||||
settings.value = data;
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return {
|
||||
settings,
|
||||
fetchShopSettings,
|
||||
updateSimplexLink,
|
||||
updateShippingNote,
|
||||
updateNotifications,
|
||||
connectSimplexNotifications,
|
||||
uploadShopLogo,
|
||||
uploadShopFavicon
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user