178 lines
5.2 KiB
TypeScript
178 lines
5.2 KiB
TypeScript
import { defineStore } from 'pinia';
|
|
import type { Product } from '~~/types/product';
|
|
|
|
export interface CartItem {
|
|
product: Product;
|
|
quantity: number;
|
|
}
|
|
|
|
export type CouponType = 'percent' | 'fixed';
|
|
|
|
export interface CartCoupon {
|
|
code: string;
|
|
type: CouponType;
|
|
value: number;
|
|
description?: string;
|
|
minSubtotal?: number;
|
|
}
|
|
|
|
interface CartState {
|
|
items: CartItem[];
|
|
coupon: CartCoupon | null;
|
|
}
|
|
|
|
const CART_STORAGE_KEY = 'nuxt-shop-cart-v1';
|
|
|
|
const AVAILABLE_COUPONS: CartCoupon[] = [
|
|
{
|
|
code: 'SALE10',
|
|
type: 'percent',
|
|
value: 10,
|
|
description: '10% indirim',
|
|
},
|
|
{
|
|
code: 'WELCOME50',
|
|
type: 'fixed',
|
|
value: 50,
|
|
description: '50 TL indirim',
|
|
minSubtotal: 300,
|
|
},
|
|
];
|
|
|
|
export const useCartStore = defineStore('cart', {
|
|
state: (): CartState => ({
|
|
items: [],
|
|
coupon: null,
|
|
}),
|
|
getters: {
|
|
totalQuantity: (state): number =>
|
|
state.items.reduce((total, item) => total + item.quantity, 0),
|
|
subtotal: (state): number =>
|
|
state.items.reduce(
|
|
(total, item) => total + item.product.price * item.quantity,
|
|
0
|
|
),
|
|
discountAmount(): number {
|
|
if (!this.coupon) return 0;
|
|
if (this.coupon.minSubtotal && this.subtotal < this.coupon.minSubtotal) {
|
|
return 0;
|
|
}
|
|
const rawDiscount =
|
|
this.coupon.type === 'percent'
|
|
? (this.subtotal * this.coupon.value) / 100
|
|
: this.coupon.value;
|
|
return Math.min(rawDiscount, this.subtotal);
|
|
},
|
|
total(): number {
|
|
return Math.max(this.subtotal - this.discountAmount, 0);
|
|
},
|
|
totalPrice(): number {
|
|
return this.total;
|
|
},
|
|
getItem:
|
|
(state) =>
|
|
(slug: string): CartItem | undefined =>
|
|
state.items.find((item) => item.product.slug === slug),
|
|
},
|
|
actions: {
|
|
addItem(product: Product, quantity = 1): void {
|
|
if (!Number.isFinite(quantity) || quantity <= 0) {
|
|
return;
|
|
}
|
|
const existingItem = this.items.find(
|
|
(item) => item.product.slug === product.slug
|
|
);
|
|
|
|
if (existingItem) {
|
|
existingItem.quantity += quantity;
|
|
existingItem.product = product;
|
|
return;
|
|
}
|
|
|
|
this.items.push({ product, quantity });
|
|
},
|
|
setItemQuantity(slug: string, quantity: number): void {
|
|
if (!Number.isFinite(quantity)) {
|
|
return;
|
|
}
|
|
const item = this.items.find((entry) => entry.product.slug === slug);
|
|
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
if (quantity <= 0) {
|
|
this.items = this.items.filter(
|
|
(entry) => entry.product.slug !== slug
|
|
);
|
|
return;
|
|
}
|
|
|
|
item.quantity = Math.floor(quantity);
|
|
},
|
|
removeItem(slug: string): void {
|
|
this.items = this.items.filter(
|
|
(item) => item.product.slug !== slug
|
|
);
|
|
},
|
|
clearCart(): void {
|
|
this.items = [];
|
|
this.coupon = null;
|
|
},
|
|
setItems(items: CartItem[]): void {
|
|
this.items = items;
|
|
},
|
|
applyCouponCode(code: string): boolean {
|
|
const normalizedCode = code.trim().toUpperCase();
|
|
if (!normalizedCode) return false;
|
|
const coupon = AVAILABLE_COUPONS.find(
|
|
(entry) => entry.code === normalizedCode
|
|
);
|
|
if (!coupon) return false;
|
|
if (coupon.minSubtotal && this.subtotal < coupon.minSubtotal) {
|
|
return false;
|
|
}
|
|
this.coupon = coupon;
|
|
return true;
|
|
},
|
|
clearCoupon(): void {
|
|
this.coupon = null;
|
|
},
|
|
setCoupon(coupon: CartCoupon | null): void {
|
|
this.coupon = coupon;
|
|
},
|
|
hydrateFromStorage(payload: string | null): void {
|
|
if (!payload) return;
|
|
try {
|
|
const parsed = JSON.parse(payload) as {
|
|
items?: CartItem[];
|
|
coupon?: CartCoupon | null;
|
|
};
|
|
if (Array.isArray(parsed.items)) {
|
|
this.items = parsed.items.filter(
|
|
(entry) =>
|
|
entry &&
|
|
entry.product &&
|
|
typeof entry.product.slug === 'string' &&
|
|
typeof entry.quantity === 'number'
|
|
);
|
|
}
|
|
if (parsed.coupon && typeof parsed.coupon.code === 'string') {
|
|
this.coupon = parsed.coupon;
|
|
}
|
|
} catch {
|
|
// Ignore invalid storage payloads
|
|
}
|
|
},
|
|
toStoragePayload(): string {
|
|
return JSON.stringify({
|
|
items: this.items,
|
|
coupon: this.coupon,
|
|
});
|
|
},
|
|
getStorageKey(): string {
|
|
return CART_STORAGE_KEY;
|
|
},
|
|
},
|
|
});
|