@01.software/init 0.9.2 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/dist/ai-docs.d.ts +13 -0
  2. package/dist/browser-auth-CJDrpp5T.d.ts +11 -0
  3. package/dist/{chunk-UA7WNT2F.js → chunk-4LHYICUL.js} +1 -1
  4. package/dist/chunk-4LHYICUL.js.map +1 -0
  5. package/dist/{chunk-R4GGO33X.js → chunk-NJ4X7VNK.js} +1 -1
  6. package/dist/{chunk-R4GGO33X.js.map → chunk-NJ4X7VNK.js.map} +1 -1
  7. package/dist/chunk-STM4DKVZ.js +183 -0
  8. package/dist/chunk-STM4DKVZ.js.map +1 -0
  9. package/dist/{chunk-ENQSB4OF.js → chunk-WDWJ73KP.js} +40 -214
  10. package/dist/chunk-WDWJ73KP.js.map +1 -0
  11. package/dist/create-app-templates/ecommerce/AGENTS.md +88 -0
  12. package/dist/create-app-templates/ecommerce/CHANGELOG.md +48 -0
  13. package/dist/create-app-templates/ecommerce/CLAUDE.md +1 -0
  14. package/dist/create-app-templates/ecommerce/README.md +154 -0
  15. package/dist/create-app-templates/ecommerce/app/api/auth/login/route.ts +30 -0
  16. package/dist/create-app-templates/ecommerce/app/api/auth/logout/route.ts +18 -0
  17. package/dist/create-app-templates/ecommerce/app/api/auth/register/route.ts +41 -0
  18. package/dist/create-app-templates/ecommerce/app/api/cart/clear/route.ts +12 -0
  19. package/dist/create-app-templates/ecommerce/app/api/cart/items/route.ts +45 -0
  20. package/dist/create-app-templates/ecommerce/app/api/cart/route.ts +14 -0
  21. package/dist/create-app-templates/ecommerce/app/api/checkout/payment-return/route.ts +86 -0
  22. package/dist/create-app-templates/ecommerce/app/api/checkout/reconcile/route.ts +50 -0
  23. package/dist/create-app-templates/ecommerce/app/api/checkout/route.ts +41 -0
  24. package/dist/create-app-templates/ecommerce/app/cart/page.tsx +10 -0
  25. package/dist/create-app-templates/ecommerce/app/checkout/page.tsx +10 -0
  26. package/dist/create-app-templates/ecommerce/app/checkout/success/page.tsx +34 -0
  27. package/dist/create-app-templates/ecommerce/app/favicon.ico +0 -0
  28. package/dist/create-app-templates/ecommerce/app/globals.css +67 -0
  29. package/dist/create-app-templates/ecommerce/app/layout.tsx +23 -0
  30. package/dist/create-app-templates/ecommerce/app/login/page.tsx +11 -0
  31. package/dist/create-app-templates/ecommerce/app/page.tsx +5 -0
  32. package/dist/create-app-templates/ecommerce/app/products/[slug]/page.tsx +46 -0
  33. package/dist/create-app-templates/ecommerce/app/products/page.tsx +45 -0
  34. package/dist/create-app-templates/ecommerce/app/register/page.tsx +11 -0
  35. package/dist/create-app-templates/ecommerce/app/webhook/payment/route.ts +20 -0
  36. package/dist/create-app-templates/ecommerce/app-config.ts +54 -0
  37. package/dist/create-app-templates/ecommerce/components/auth/auth-form.tsx +109 -0
  38. package/dist/create-app-templates/ecommerce/components/cart/cart-content.tsx +129 -0
  39. package/dist/create-app-templates/ecommerce/components/checkout/checkout-form.tsx +307 -0
  40. package/dist/create-app-templates/ecommerce/components/checkout/checkout-reconcile.tsx +78 -0
  41. package/dist/create-app-templates/ecommerce/components/layout/account-nav.tsx +48 -0
  42. package/dist/create-app-templates/ecommerce/components/layout/account-slot.tsx +12 -0
  43. package/dist/create-app-templates/ecommerce/components/layout/cart-link.tsx +13 -0
  44. package/dist/create-app-templates/ecommerce/components/layout/page-shell.tsx +11 -0
  45. package/dist/create-app-templates/ecommerce/components/layout/site-header.tsx +22 -0
  46. package/dist/create-app-templates/ecommerce/components/product/add-to-cart.tsx +116 -0
  47. package/dist/create-app-templates/ecommerce/components/product/product-card.tsx +50 -0
  48. package/dist/create-app-templates/ecommerce/components/product/product-gallery.tsx +39 -0
  49. package/dist/create-app-templates/ecommerce/data/mock-catalog.json +173 -0
  50. package/dist/create-app-templates/ecommerce/eslint.config.mjs +18 -0
  51. package/dist/create-app-templates/ecommerce/lib/cart/cookie.ts +40 -0
  52. package/dist/create-app-templates/ecommerce/lib/cart/normalize.ts +32 -0
  53. package/dist/create-app-templates/ecommerce/lib/cart/parse-cart-request.ts +56 -0
  54. package/dist/create-app-templates/ecommerce/lib/cart/route-helpers.ts +17 -0
  55. package/dist/create-app-templates/ecommerce/lib/cart/select-provider.ts +44 -0
  56. package/dist/create-app-templates/ecommerce/lib/cart/server-cart.ts +135 -0
  57. package/dist/create-app-templates/ecommerce/lib/cart/sync-on-login.server.ts +34 -0
  58. package/dist/create-app-templates/ecommerce/lib/cart/use-cart.tsx +151 -0
  59. package/dist/create-app-templates/ecommerce/lib/checkout/checkout-errors.ts +22 -0
  60. package/dist/create-app-templates/ecommerce/lib/checkout/checkout-provider.ts +28 -0
  61. package/dist/create-app-templates/ecommerce/lib/checkout/parse-checkout-payload.ts +86 -0
  62. package/dist/create-app-templates/ecommerce/lib/checkout/start-checkout.ts +73 -0
  63. package/dist/create-app-templates/ecommerce/lib/checkout/types.ts +6 -0
  64. package/dist/create-app-templates/ecommerce/lib/commerce/adapters/mock.ts +346 -0
  65. package/dist/create-app-templates/ecommerce/lib/commerce/adapters/software-mappers.ts +312 -0
  66. package/dist/create-app-templates/ecommerce/lib/commerce/adapters/software.ts +930 -0
  67. package/dist/create-app-templates/ecommerce/lib/commerce/product-summary.ts +37 -0
  68. package/dist/create-app-templates/ecommerce/lib/commerce/provider.server.ts +60 -0
  69. package/dist/create-app-templates/ecommerce/lib/commerce/provider.ts +96 -0
  70. package/dist/create-app-templates/ecommerce/lib/commerce/stock.ts +37 -0
  71. package/dist/create-app-templates/ecommerce/lib/commerce/types.ts +208 -0
  72. package/dist/create-app-templates/ecommerce/lib/commerce/variant-selection.ts +23 -0
  73. package/dist/create-app-templates/ecommerce/lib/customer/auth-actions.ts +131 -0
  74. package/dist/create-app-templates/ecommerce/lib/customer/cart-sync.ts +44 -0
  75. package/dist/create-app-templates/ecommerce/lib/customer/client.server.ts +109 -0
  76. package/dist/create-app-templates/ecommerce/lib/customer/current-customer.ts +15 -0
  77. package/dist/create-app-templates/ecommerce/lib/customer/route-guard.ts +58 -0
  78. package/dist/create-app-templates/ecommerce/lib/customer/route-helpers.ts +75 -0
  79. package/dist/create-app-templates/ecommerce/lib/customer/session.ts +108 -0
  80. package/dist/create-app-templates/ecommerce/lib/format.ts +7 -0
  81. package/dist/create-app-templates/ecommerce/lib/payment/adapters/mock.ts +84 -0
  82. package/dist/create-app-templates/ecommerce/lib/payment/adapters/portone.ts +254 -0
  83. package/dist/create-app-templates/ecommerce/lib/payment/adapters/tosspayments.ts +287 -0
  84. package/dist/create-app-templates/ecommerce/lib/payment/amount-gate.ts +86 -0
  85. package/dist/create-app-templates/ecommerce/lib/payment/provider.server.ts +51 -0
  86. package/dist/create-app-templates/ecommerce/lib/payment/provider.ts +18 -0
  87. package/dist/create-app-templates/ecommerce/lib/payment/sync-order-payment.ts +96 -0
  88. package/dist/create-app-templates/ecommerce/lib/payment/types.ts +71 -0
  89. package/dist/create-app-templates/ecommerce/lib/server-only-guard.ts +20 -0
  90. package/dist/create-app-templates/ecommerce/next-env.d.ts +6 -0
  91. package/dist/create-app-templates/ecommerce/next.config.ts +16 -0
  92. package/dist/create-app-templates/ecommerce/package.json +33 -0
  93. package/dist/create-app-templates/ecommerce/postcss.config.mjs +7 -0
  94. package/dist/create-app-templates/ecommerce/tests/customer-auth.test.ts +263 -0
  95. package/dist/create-app-templates/ecommerce/tests/customer-cart.test.ts +401 -0
  96. package/dist/create-app-templates/ecommerce/tests/domain.test.ts +1632 -0
  97. package/dist/create-app-templates/ecommerce/tsconfig.json +35 -0
  98. package/dist/create-app-templates/registry.json +66 -0
  99. package/dist/create-app.d.ts +40 -0
  100. package/dist/create-app.js +652 -0
  101. package/dist/create-app.js.map +1 -0
  102. package/dist/detect-Bjxp9wcS.d.ts +13 -0
  103. package/dist/file-ops.d.ts +21 -0
  104. package/dist/file-ops.js +1 -1
  105. package/dist/index.d.ts +2 -0
  106. package/dist/index.js +4 -3
  107. package/dist/index.js.map +1 -1
  108. package/dist/init.d.ts +40 -0
  109. package/dist/init.js +4 -3
  110. package/dist/templates.d.ts +27 -0
  111. package/dist/templates.js +1 -1
  112. package/package.json +18 -3
  113. package/dist/chunk-ENQSB4OF.js.map +0 -1
  114. package/dist/chunk-UA7WNT2F.js.map +0 -1
@@ -0,0 +1,151 @@
1
+ "use client";
2
+
3
+ import {
4
+ createContext,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useState,
10
+ type ReactNode,
11
+ } from "react";
12
+ import type { CartItemRef, CartView } from "../commerce/types.ts";
13
+
14
+ type CartContextValue = {
15
+ cart: CartView | null;
16
+ loading: boolean;
17
+ error: string | null;
18
+ itemCount: number;
19
+ addItem: (item: CartItemRef) => Promise<boolean>;
20
+ updateItem: (input: {
21
+ cartItemId: string;
22
+ quantity: number;
23
+ }) => Promise<void>;
24
+ removeItem: (cartItemId: string) => Promise<void>;
25
+ clear: () => Promise<void>;
26
+ /** Drop the local cart view (e.g. after checkout consumed the server cart). */
27
+ reset: () => void;
28
+ };
29
+
30
+ const CartContext = createContext<CartContextValue | null>(null);
31
+
32
+ /**
33
+ * Client cart state backed by the server cart routes (`/api/cart*`). The cart
34
+ * lives in the Console (`commerce.cart.*`); this holds only the rendered view —
35
+ * the ownership `cartToken` stays in the HttpOnly cookie, never in JS.
36
+ */
37
+ export function CartProvider({ children }: { children: ReactNode }) {
38
+ const [cart, setCart] = useState<CartView | null>(null);
39
+ const [loading, setLoading] = useState(true);
40
+ const [error, setError] = useState<string | null>(null);
41
+
42
+ // Hydrate the current cart on mount. setState lives in the async resolution
43
+ // callbacks (external-system → React sync), not the synchronous effect body.
44
+ useEffect(() => {
45
+ let active = true;
46
+ fetch("/api/cart")
47
+ .then((response) => response.json() as Promise<{ cart?: CartView | null }>)
48
+ .then((data) => {
49
+ if (!active) return;
50
+ setCart(data.cart ?? null);
51
+ setLoading(false);
52
+ })
53
+ .catch(() => {
54
+ if (!active) return;
55
+ setCart(null);
56
+ setLoading(false);
57
+ });
58
+ return () => {
59
+ active = false;
60
+ };
61
+ }, []);
62
+
63
+ const mutate = useCallback(
64
+ async (url: string, method: string, body?: unknown): Promise<boolean> => {
65
+ setError(null);
66
+ try {
67
+ const response = await fetch(url, {
68
+ method,
69
+ headers: { "Content-Type": "application/json" },
70
+ body: body === undefined ? undefined : JSON.stringify(body),
71
+ });
72
+ const data = (await response.json().catch(() => ({}))) as {
73
+ cart?: CartView | null;
74
+ message?: string;
75
+ };
76
+ if (!response.ok) {
77
+ setError(data.message ?? "Cart update failed");
78
+ return false;
79
+ }
80
+ setCart(data.cart ?? null);
81
+ return true;
82
+ } catch {
83
+ setError("Cart update failed");
84
+ return false;
85
+ }
86
+ },
87
+ [],
88
+ );
89
+
90
+ const addItem = useCallback(
91
+ (item: CartItemRef) => mutate("/api/cart/items", "POST", item),
92
+ [mutate],
93
+ );
94
+ const updateItem = useCallback(
95
+ async (input: { cartItemId: string; quantity: number }) => {
96
+ await mutate("/api/cart/items", "PATCH", input);
97
+ },
98
+ [mutate],
99
+ );
100
+ const removeItem = useCallback(
101
+ async (cartItemId: string) => {
102
+ await mutate("/api/cart/items", "DELETE", { cartItemId });
103
+ },
104
+ [mutate],
105
+ );
106
+ const clear = useCallback(async () => {
107
+ await mutate("/api/cart/clear", "POST");
108
+ }, [mutate]);
109
+ const reset = useCallback(() => {
110
+ setCart(null);
111
+ setError(null);
112
+ }, []);
113
+
114
+ const itemCount =
115
+ cart?.items.reduce((sum, item) => sum + item.quantity, 0) ?? 0;
116
+
117
+ const value = useMemo<CartContextValue>(
118
+ () => ({
119
+ cart,
120
+ loading,
121
+ error,
122
+ itemCount,
123
+ addItem,
124
+ updateItem,
125
+ removeItem,
126
+ clear,
127
+ reset,
128
+ }),
129
+ [
130
+ cart,
131
+ loading,
132
+ error,
133
+ itemCount,
134
+ addItem,
135
+ updateItem,
136
+ removeItem,
137
+ clear,
138
+ reset,
139
+ ],
140
+ );
141
+
142
+ return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
143
+ }
144
+
145
+ export function useCart(): CartContextValue {
146
+ const context = useContext(CartContext);
147
+ if (!context) {
148
+ throw new Error("useCart must be used within a CartProvider");
149
+ }
150
+ return context;
151
+ }
@@ -0,0 +1,22 @@
1
+ const CHECKOUT_VALIDATION_PATTERN = /required|must be|valid email|payload|empty/;
2
+
3
+ export function getCheckoutErrorMessage(error: unknown): string {
4
+ return error instanceof Error ? error.message : "Checkout failed";
5
+ }
6
+
7
+ export function getCheckoutErrorStatus(error: unknown): number {
8
+ const status = readHttpStatus(error);
9
+ if (status !== null) return status;
10
+
11
+ return CHECKOUT_VALIDATION_PATTERN.test(getCheckoutErrorMessage(error))
12
+ ? 400
13
+ : 422;
14
+ }
15
+
16
+ function readHttpStatus(error: unknown): number | null {
17
+ if (typeof error !== "object" || error === null) return null;
18
+ const status = (error as { status?: unknown }).status;
19
+ return typeof status === "number" && status >= 400 && status < 500
20
+ ? status
21
+ : null;
22
+ }
@@ -0,0 +1,28 @@
1
+ import "../server-only-guard.ts";
2
+ import {
3
+ getCommerceProvider,
4
+ getCustomerCheckoutProvider,
5
+ } from "../commerce/provider.server.ts";
6
+ import { getSessionToken } from "../customer/session.ts";
7
+ import type { CheckoutCommerceProvider } from "./types.ts";
8
+
9
+ export type CheckoutProviderFactories = {
10
+ guest: () => CheckoutCommerceProvider;
11
+ customer: (token: string) => CheckoutCommerceProvider;
12
+ };
13
+
14
+ export function resolveCheckoutCommerceProvider(
15
+ sessionToken: string | null,
16
+ factories: CheckoutProviderFactories,
17
+ ): CheckoutCommerceProvider {
18
+ return sessionToken
19
+ ? factories.customer(sessionToken)
20
+ : factories.guest();
21
+ }
22
+
23
+ export async function getCheckoutCommerceProvider(): Promise<CheckoutCommerceProvider> {
24
+ return resolveCheckoutCommerceProvider(await getSessionToken(), {
25
+ guest: getCommerceProvider,
26
+ customer: getCustomerCheckoutProvider,
27
+ });
28
+ }
@@ -0,0 +1,86 @@
1
+ import type { CustomerSnapshot, ShippingAddress } from '../commerce/types.ts'
2
+
3
+ const MAX_TEXT_LENGTH = 160
4
+ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
5
+
6
+ /**
7
+ * Checkout collects only the buyer + shipping snapshot. The cart contents live
8
+ * server-side (addressed by the HttpOnly `cartToken`); there are no
9
+ * client-supplied lines or totals — the cart is the quote.
10
+ */
11
+ export type CheckoutPayload = {
12
+ customerSnapshot: CustomerSnapshot
13
+ shippingAddress?: ShippingAddress
14
+ }
15
+
16
+ export function parseCheckoutPayload(
17
+ input: unknown,
18
+ options: { requiresShipping?: boolean } = {},
19
+ ): CheckoutPayload {
20
+ if (!input || typeof input !== 'object') {
21
+ throw new Error('Checkout payload must be an object')
22
+ }
23
+
24
+ const body = input as {
25
+ customerSnapshot?: unknown
26
+ shippingAddress?: unknown
27
+ }
28
+ const customer = parseRecord(body.customerSnapshot, 'customerSnapshot')
29
+ const email = requiredString(customer.email, 'email')
30
+ if (!EMAIL_RE.test(email)) {
31
+ throw new Error('email must be a valid email address')
32
+ }
33
+
34
+ const customerSnapshot = {
35
+ name: requiredString(customer.name, 'customer name'),
36
+ email,
37
+ phone: requiredString(customer.phone, 'customer phone'),
38
+ }
39
+
40
+ if (options.requiresShipping === false) {
41
+ return { customerSnapshot }
42
+ }
43
+
44
+ const shipping = parseRecord(body.shippingAddress, 'shippingAddress')
45
+
46
+ return {
47
+ customerSnapshot,
48
+ shippingAddress: {
49
+ recipientName: requiredString(shipping.recipientName, 'recipient name'),
50
+ phone: requiredString(shipping.phone, 'shipping phone'),
51
+ postalCode: requiredString(shipping.postalCode, 'postal code'),
52
+ address: requiredString(shipping.address, 'address'),
53
+ detailAddress: requiredString(shipping.detailAddress, 'address detail'),
54
+ deliveryMessage: optionalString(
55
+ shipping.deliveryMessage,
56
+ 'shipping message',
57
+ ),
58
+ },
59
+ }
60
+ }
61
+
62
+ function parseRecord(input: unknown, field: string): Record<string, unknown> {
63
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
64
+ throw new Error(`${field} must be an object`)
65
+ }
66
+
67
+ return input as Record<string, unknown>
68
+ }
69
+
70
+ function requiredString(input: unknown, field: string): string {
71
+ const value = optionalString(input, field)
72
+ if (!value) throw new Error(`${field} is required`)
73
+ return value
74
+ }
75
+
76
+ function optionalString(input: unknown, field: string): string {
77
+ if (input === undefined || input === null) return ''
78
+ if (typeof input !== 'string') throw new Error(`${field} must be a string`)
79
+
80
+ const value = input.trim()
81
+ if (value.length > MAX_TEXT_LENGTH) {
82
+ throw new Error(`${field} must be ${MAX_TEXT_LENGTH} characters or fewer`)
83
+ }
84
+
85
+ return value
86
+ }
@@ -0,0 +1,73 @@
1
+ import '../server-only-guard.ts'
2
+ import type { CheckoutCommerceProvider } from './types.ts'
3
+ import type { ClientPaymentRequest } from '../payment/types.ts'
4
+ import type { PaymentProvider } from '../payment/provider.ts'
5
+ import {
6
+ parseCheckoutPayload,
7
+ type CheckoutPayload,
8
+ } from './parse-checkout-payload.ts'
9
+
10
+ export type StartCheckoutResult = {
11
+ orderNumber: string
12
+ paymentId: string
13
+ paymentName: string
14
+ amount: number
15
+ currency: string
16
+ redirectUrl?: string
17
+ clientPayment?: ClientPaymentRequest
18
+ }
19
+
20
+ /**
21
+ * Convert the server cart (addressed by `cartToken`) into an order via
22
+ * `orders.checkout({ cartId })` and open the PG payment window. The cart is the
23
+ * single payable quote — no client-supplied lines or totals.
24
+ */
25
+ export async function startCheckout(input: {
26
+ cartToken: string
27
+ payload: unknown
28
+ commerceProvider: CheckoutCommerceProvider
29
+ paymentProvider: PaymentProvider
30
+ }): Promise<StartCheckoutResult> {
31
+ const cart = await input.commerceProvider.getCart(input.cartToken)
32
+ if (!cart) throw new Error('Cart not found')
33
+
34
+ const requiresShipping = cart.items.some(
35
+ (item) => item.requiresShipping !== false,
36
+ )
37
+ const payload: CheckoutPayload = parseCheckoutPayload(input.payload, {
38
+ requiresShipping,
39
+ })
40
+ const pending = await input.commerceProvider.checkoutCart({
41
+ cartToken: input.cartToken,
42
+ customerSnapshot: payload.customerSnapshot,
43
+ ...(payload.shippingAddress
44
+ ? { shippingAddress: payload.shippingAddress }
45
+ : {}),
46
+ })
47
+
48
+ const payment = await input.paymentProvider.requestPayment({
49
+ paymentId: pending.paymentId,
50
+ orderNumber: pending.order.orderNumber,
51
+ orderName: pending.paymentName,
52
+ amount: pending.amount,
53
+ currency: pending.currency,
54
+ customer: pending.order.customerSnapshot,
55
+ })
56
+
57
+ if (!payment.ok) {
58
+ // Checkout created an OPEN Checkout but no Order/Transaction yet. A failed
59
+ // PG handoff leaves nothing durable to cancel; the abandoned Checkout (and
60
+ // its inventory hold) is released by the Console `expire-checkouts` cron.
61
+ throw new Error(payment.reason ?? 'payment_request_failed')
62
+ }
63
+
64
+ return {
65
+ orderNumber: pending.order.orderNumber,
66
+ paymentId: pending.paymentId,
67
+ paymentName: pending.paymentName,
68
+ amount: pending.amount,
69
+ currency: pending.currency,
70
+ redirectUrl: payment.redirectUrl,
71
+ clientPayment: payment.clientPayment,
72
+ }
73
+ }
@@ -0,0 +1,6 @@
1
+ import type { CommerceProvider } from '../commerce/provider.ts'
2
+
3
+ export type CheckoutCommerceProvider = Pick<
4
+ CommerceProvider,
5
+ 'checkoutCart' | 'getCart'
6
+ >