feat: initial commit — storefront, convex backend, and shared packages

Completes the first milestone of The Pet Loft ecommerce platform:
- apps/storefront: full customer-facing Next.js app with HeroUI (cart,
  checkout, orders, wishlist, product detail, shop, search, auth)
- convex/: serverless backend with schema, queries, mutations, actions,
  HTTP routes, Stripe/Shippo integrations, and co-located tests
- packages/types, packages/utils, packages/convex: shared workspace packages

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 09:31:18 +03:00
commit cc15338ad9
361 changed files with 45005 additions and 0 deletions

218
packages/types/src/index.ts Normal file
View File

@@ -0,0 +1,218 @@
// ─── User & Auth ────────────────────────────────────────────────────────────
export type UserRole = "customer" | "admin" | "super_admin";
export interface User {
id: string;
email: string;
full_name: string | null;
avatar_url: string | null;
role: UserRole;
created_at: string;
updated_at: string;
}
export interface CustomerProfile {
id: string;
user_id: string;
phone: string | null;
date_of_birth: string | null;
created_at: string;
}
// ─── Address ─────────────────────────────────────────────────────────────────
export interface Address {
id: string;
user_id: string;
full_name: string;
phone: string;
address_line1: string;
address_line2: string | null;
city: string;
state: string;
postal_code: string;
country: string;
is_default: boolean;
created_at: string;
}
export type AddressInput = Omit<Address, "id" | "user_id" | "created_at">;
// ─── Product & Catalog ───────────────────────────────────────────────────────
export type ProductStatus = "active" | "draft" | "archived";
export interface Category {
id: string;
name: string;
slug: string;
description: string | null;
image_url: string | null;
parent_id: string | null;
created_at: string;
}
export interface Product {
id: string;
name: string;
slug: string;
description: string | null;
status: ProductStatus;
category_id: string;
category?: Category;
images: ProductImage[];
variants: ProductVariant[];
tags: string[];
created_at: string;
updated_at: string;
}
export interface ProductImage {
id: string;
product_id: string;
url: string;
alt: string | null;
position: number;
}
export interface ProductVariant {
id: string;
product_id: string;
name: string; // e.g. "Red / XL"
sku: string;
price: number; // stored in cents e.g. 1999 = $19.99
compare_at_price: number | null;
stock_quantity: number;
attributes: Record<string, string>; // { color: "Red", size: "XL" }
is_active: boolean;
}
export type ProductInput = Omit<Product, "id" | "images" | "variants" | "category" | "created_at" | "updated_at">;
/** Same shape as Product with optional highlights for future relevance snippets. */
export type ProductSearchResult = Product & {
highlights?: Record<string, string>;
};
// ─── Cart ─────────────────────────────────────────────────────────────────────
export interface CartItem {
id: string;
variant_id: string;
variant?: ProductVariant;
product?: Product;
quantity: number;
}
export interface Cart {
items: CartItem[];
subtotal: number;
total_items: number;
}
/** Enriched cart line returned by Convex carts.get (variantId, quantity, priceSnapshot, productName, variantName, imageUrl, stock) */
export interface CartLineEnriched {
variantId: string;
productId: string;
quantity: number;
priceSnapshot: number;
productName: string;
variantName: string;
imageUrl?: string;
stockQuantity?: number;
}
// ─── Orders ──────────────────────────────────────────────────────────────────
export type OrderStatus =
| "pending"
| "confirmed"
| "processing"
| "shipped"
| "delivered"
| "cancelled"
| "refunded";
export type PaymentStatus = "pending" | "paid" | "failed" | "refunded";
export interface Order {
id: string;
order_number: string;
user_id: string;
user?: User;
status: OrderStatus;
payment_status: PaymentStatus;
items: OrderItem[];
shipping_address: Address;
subtotal: number; // in cents
shipping_cost: number; // in cents
discount: number; // in cents
total: number; // in cents
notes: string | null;
created_at: string;
updated_at: string;
}
export interface OrderItem {
id: string;
order_id: string;
variant_id: string;
variant?: ProductVariant;
product_name: string; // snapshot at time of order
variant_name: string; // snapshot at time of order
quantity: number;
unit_price: number; // in cents, snapshot at time of order
total_price: number; // in cents
}
// ─── Pagination & API Responses ───────────────────────────────────────────────
export interface PaginationParams {
page: number;
limit: number;
}
export interface PaginatedResponse<T> {
data: T[];
total: number;
page: number;
limit: number;
total_pages: number;
}
export interface ApiResponse<T> {
data: T | null;
error: string | null;
}
// ─── Filters ─────────────────────────────────────────────────────────────────
export interface ProductFilters {
category_id?: string;
status?: ProductStatus;
min_price?: number;
max_price?: number;
search?: string;
tags?: string[];
}
export interface OrderFilters {
status?: OrderStatus;
payment_status?: PaymentStatus;
user_id?: string;
date_from?: string;
date_to?: string;
search?: string;
}
// ─── Dashboard / Analytics ───────────────────────────────────────────────────
export interface DashboardStats {
total_revenue: number;
total_orders: number;
total_customers: number;
total_products: number;
revenue_change_percent: number;
orders_change_percent: number;
}