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

View File

@@ -0,0 +1,88 @@
/**
* @vitest-environment happy-dom
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook } from "@testing-library/react";
import { useCartSession } from "./useCartSession";
const mockUseAuth = vi.fn();
vi.mock("@clerk/nextjs", () => ({ useAuth: () => mockUseAuth() }));
const mockGetGuestSessionId = vi.fn();
const mockSetGuestSessionCookie = vi.fn();
const mockGenerateGuestSessionId = vi.fn();
vi.mock("./cookie", () => ({
getGuestSessionId: () => mockGetGuestSessionId(),
setGuestSessionCookie: (id: string) => mockSetGuestSessionCookie(id),
generateGuestSessionId: () => mockGenerateGuestSessionId(),
}));
describe("useCartSession", () => {
beforeEach(() => {
vi.clearAllMocks();
mockGenerateGuestSessionId.mockReturnValue("generated-uuid-123");
});
it("returns authenticated session when loaded and signed in", () => {
mockUseAuth.mockReturnValue({ isLoaded: true, isSignedIn: true });
const { result } = renderHook(() => useCartSession());
expect(result.current).toEqual({ sessionId: undefined, isGuest: false });
expect(mockGetGuestSessionId).not.toHaveBeenCalled();
});
it("returns guest session with cookie value when not signed in and cookie present", () => {
mockUseAuth.mockReturnValue({ isLoaded: true, isSignedIn: false });
mockGetGuestSessionId.mockReturnValue("cookie-uuid-456");
const { result } = renderHook(() => useCartSession());
expect(result.current).toEqual({
sessionId: "cookie-uuid-456",
isGuest: true,
});
expect(mockGetGuestSessionId).toHaveBeenCalled();
expect(mockSetGuestSessionCookie).not.toHaveBeenCalled();
expect(mockGenerateGuestSessionId).not.toHaveBeenCalled();
});
it("returns guest session and sets cookie when not signed in and cookie missing", () => {
mockUseAuth.mockReturnValue({ isLoaded: true, isSignedIn: false });
mockGetGuestSessionId.mockReturnValue(null);
const { result } = renderHook(() => useCartSession());
expect(result.current).toEqual({
sessionId: "generated-uuid-123",
isGuest: true,
});
expect(mockGetGuestSessionId).toHaveBeenCalled();
expect(mockGenerateGuestSessionId).toHaveBeenCalled();
expect(mockSetGuestSessionCookie).toHaveBeenCalledWith("generated-uuid-123");
});
it("treats not-loaded auth as guest and uses cookie when present", () => {
mockUseAuth.mockReturnValue({ isLoaded: false, isSignedIn: false });
mockGetGuestSessionId.mockReturnValue("existing-guest-id");
const { result } = renderHook(() => useCartSession());
expect(result.current).toEqual({
sessionId: "existing-guest-id",
isGuest: true,
});
expect(mockSetGuestSessionCookie).not.toHaveBeenCalled();
});
it("treats not-loaded auth as guest and creates session when cookie missing", () => {
mockUseAuth.mockReturnValue({ isLoaded: false, isSignedIn: false });
mockGetGuestSessionId.mockReturnValue(null);
const { result } = renderHook(() => useCartSession());
expect(result.current).toEqual({
sessionId: "generated-uuid-123",
isGuest: true,
});
expect(mockSetGuestSessionCookie).toHaveBeenCalledWith("generated-uuid-123");
});
it("returns authenticated only when both isLoaded and isSignedIn are true", () => {
mockUseAuth.mockReturnValue({ isLoaded: true, isSignedIn: false });
mockGetGuestSessionId.mockReturnValue("guest-id");
const { result } = renderHook(() => useCartSession());
expect(result.current.isGuest).toBe(true);
expect(result.current.sessionId).toBe("guest-id");
});
});