@ikas/code-components-mcp 1.4.0-beta.174 → 1.4.0-beta.175
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.
- package/data/framework.json +10 -4
- package/data/storefront-api.json +137 -2
- package/data/storefront-types.json +17 -1
- package/package.json +1 -1
package/data/framework.json
CHANGED
|
@@ -243,14 +243,17 @@
|
|
|
243
243
|
"form-handling": {
|
|
244
244
|
"title": "Form Handling",
|
|
245
245
|
"description": "How to use the form model pattern for login, registration, address, and other forms",
|
|
246
|
-
"content": "## Form Handling Pattern\n\nikas storefront uses a consistent form model pattern across all form types: `init*Form()` → `set*FormField()` → `submit*Form()`.\n\n### Form Model Structure\n\nEach form field has:\n```typescript\n{\n value: string; // Current field value\n hasError: boolean; // Whether validation failed\n message?: string; // Error message to display\n label: string; // Display label\n placeholder: string; // Input placeholder\n}\n```\n\nEach form tracks:\n```typescript\n{\n isSubmitted: boolean; // Has submit been attempted\n isSubmitting: boolean; // Is submission in progress\n isSuccess?: boolean; // Did last submit succeed\n isFailure?: boolean; // Did last submit fail\n responseMessage?: string; // Server response message\n}\n```\n\n### Login Form Example\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n getLoginForm,\n initLoginForm,\n setLoginFormEmail,\n setLoginFormPassword,\n submitLoginForm,\n Router,\n} from \"@ikas/bp-storefront\";\n\n// Root export — no observer() needed, ikas runtime handles reactivity via autorun()\nexport function LoginForm() {\n // Forms are reached through get*Form(customerStore) accessors — the store\n // holds them in private fields (`_loginForm`), so `customerStore.loginForm`\n // does not compile.\n const loginForm = getLoginForm(customerStore);\n\n useEffect(() => {\n initLoginForm(loginForm);\n }, []);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitLoginForm(loginForm);\n if (success) {\n Router.navigate(\"/account\");\n }\n };\n\n return (\n <form onSubmit={handleSubmit}>\n {loginForm.isFailure && <div>{loginForm.responseMessage}</div>}\n\n <input\n type=\"email\"\n value={loginForm.email.value}\n onInput={(e) => setLoginFormEmail(loginForm, e.target.value)}\n />\n {loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n\n <input\n type=\"password\"\n value={loginForm.password.value}\n onInput={(e) => setLoginFormPassword(loginForm, e.target.value)}\n />\n {loginForm.password.hasError && <span>{loginForm.password.message}</span>}\n\n <button type=\"submit\" disabled={loginForm.isSubmitting}>\n {loginForm.isSubmitting ? \"Signing in...\" : \"Sign In\"}\n </button>\n </form>\n );\n}\n\nexport default LoginForm;\n```\n\n### Available Form Types\n\n| Form | Init | Setter Functions | Submit |\n|------|------|-----------------|--------|\n| Login | `initLoginForm(form)` | `setLoginFormEmail()`, `setLoginFormPassword()` | `submitLoginForm(form)` |\n| Register | `initRegisterForm(form)` | `setRegisterFormEmail()`, `setRegisterFormPassword()`, `setRegisterFormFirstName()`, `setRegisterFormLastName()` | `submitRegisterForm(form)` |\n| Forgot Password | `initForgotPasswordForm(form)` | `setForgotPasswordFormEmail()` | `submitForgotPasswordForm(form)` |\n| Address | `initAddressForm(form)` | `setAddressFormField()` for each field | `submitAddressForm(form)` |\n| Contact | `initContactForm(form)` | `setContactFormField()` for each field | `submitContactForm(form)` |\n| Account Info | `initAccountInfoForm(form)` | `setAccountInfoFormField()` for each field | `submitAccountInfoForm(form)` |\n| Newsletter | `initNewsletterSubscriptionForm(form)` | `setNewsletterEmail()` | `submitNewsletterSubscriptionForm(form)` |\n| Coupon Code | `getCouponCodeForm(customerStore)` → `initCouponCodeForm(form)` | `setCouponCodeFormCouponCode(form, value)` | `submitCouponCodeForm(form)` (remove with `removeCouponCodeForm(form)`) |\n\n**Address is the exception to this page.** It follows the same init/set/submit contract, but its\nfield set and row layout are resolved from the selected country at runtime via\n`addressForm.addressFormat` -- you cannot lay it out statically. Call\n`get_framework_guide({ topic: \"address-form-patterns\" })` before building any address form.\n\n### Key Rules\n\n1. **Always call init first** — `init*Form()` sets up default field values, labels, and placeholders\n2. **Setter functions auto-validate** — if `form.isSubmitted` is true, calling any setter re-validates the form automatically\n3. **Check field.hasError for display** — show error messages only when `field.hasError` is true\n4. **Root components are automatically reactive** — forms use MobX observables which are automatically tracked in root components via `autorun()`. Use `observer()` only if you extract form logic into a sub-component\n5. **Submit returns boolean** — `submit*Form()` returns `true` on success, `false` on validation error or server failure",
|
|
246
|
+
"content": "## Form Handling Pattern\n\nikas storefront uses a consistent form model pattern across all form types: `init*Form()` → `set*FormField()` → `submit*Form()`.\n\n### Form Model Structure\n\nEach form field has:\n```typescript\n{\n value: string; // Current field value\n hasError: boolean; // Whether validation failed\n message?: string; // Error message to display\n label: string; // Display label\n placeholder: string; // Input placeholder\n}\n```\n\nEach form tracks:\n```typescript\n{\n isSubmitted: boolean; // Has submit been attempted\n isSubmitting: boolean; // Is submission in progress\n isSuccess?: boolean; // Did last submit succeed\n isFailure?: boolean; // Did last submit fail\n responseMessage?: string; // Server response message\n}\n```\n\n### Login Form Example\n\n```tsx\nimport { useEffect } from \"preact/hooks\";\nimport {\n customerStore,\n getLoginForm,\n initLoginForm,\n setLoginFormEmail,\n setLoginFormPassword,\n submitLoginForm,\n Router,\n} from \"@ikas/bp-storefront\";\n\n// Root export — no observer() needed, ikas runtime handles reactivity via autorun()\nexport function LoginForm() {\n // Forms are reached through get*Form(customerStore) accessors — the store\n // holds them in private fields (`_loginForm`), so `customerStore.loginForm`\n // does not compile.\n const loginForm = getLoginForm(customerStore);\n\n useEffect(() => {\n initLoginForm(loginForm);\n }, []);\n\n const handleSubmit = async (e: Event) => {\n e.preventDefault();\n const success = await submitLoginForm(loginForm);\n if (success) {\n Router.navigate(\"/account\");\n }\n };\n\n return (\n <form onSubmit={handleSubmit}>\n {loginForm.isFailure && <div>{loginForm.responseMessage}</div>}\n\n <input\n type=\"email\"\n value={loginForm.email.value}\n onInput={(e) => setLoginFormEmail(loginForm, e.target.value)}\n />\n {loginForm.email.hasError && <span>{loginForm.email.message}</span>}\n\n <input\n type=\"password\"\n value={loginForm.password.value}\n onInput={(e) => setLoginFormPassword(loginForm, e.target.value)}\n />\n {loginForm.password.hasError && <span>{loginForm.password.message}</span>}\n\n <button type=\"submit\" disabled={loginForm.isSubmitting}>\n {loginForm.isSubmitting ? \"Signing in...\" : \"Sign In\"}\n </button>\n </form>\n );\n}\n\nexport default LoginForm;\n```\n\n### Available Form Types\n\n| Form | Init | Setter Functions | Submit |\n|------|------|-----------------|--------|\n| Login | `initLoginForm(form)` | `setLoginFormEmail()`, `setLoginFormPassword()` | `submitLoginForm(form)` |\n| Register | `initRegisterForm(form)` | `setRegisterFormEmail()`, `setRegisterFormPassword()`, `setRegisterFormFirstName()`, `setRegisterFormLastName()` | `submitRegisterForm(form)` |\n| Forgot Password | `initForgotPasswordForm(form)` | `setForgotPasswordFormEmail()` | `submitForgotPasswordForm(form)` |\n| Address | `initAddressForm(form)` | `setAddressFormField()` for each field | `submitAddressForm(form)` |\n| Contact | `initContactForm(form)` | `setContactFormField()` for each field | `submitContactForm(form)` |\n| Account Info | `initAccountInfoForm(form)` | `setAccountInfoFormField()` for each field | `submitAccountInfoForm(form)` |\n| Newsletter | `initNewsletterSubscriptionForm(form)` | `setNewsletterEmail()` | `submitNewsletterSubscriptionForm(form)` |\n| Coupon Code | `getCouponCodeForm(customerStore)` → `initCouponCodeForm(form)` | `setCouponCodeFormCouponCode(form, value)` | `submitCouponCodeForm(form)` (remove with `removeCouponCodeForm(form)`) |\n| Deactivate Customer | `getDeactivateCustomerForm(customerStore)` → `initDeactivateCustomerForm(form)` | `setDeactivateCustomerFormPassword(form, value)` | `submitDeactivateCustomerForm(form)` (call `logout(customerStore)` after success) |\n\n**Address is the exception to this page.** It follows the same init/set/submit contract, but its\nfield set and row layout are resolved from the selected country at runtime via\n`addressForm.addressFormat` -- you cannot lay it out statically. Call\n`get_framework_guide({ topic: \"address-form-patterns\" })` before building any address form.\n\n### Key Rules\n\n1. **Always call init first** — `init*Form()` sets up default field values, labels, and placeholders\n2. **Setter functions auto-validate** — if `form.isSubmitted` is true, calling any setter re-validates the form automatically\n3. **Check field.hasError for display** — show error messages only when `field.hasError` is true\n4. **Root components are automatically reactive** — forms use MobX observables which are automatically tracked in root components via `autorun()`. Use `observer()` only if you extract form logic into a sub-component\n5. **Submit returns boolean** — `submit*Form()` returns `true` on success, `false` on validation error or server failure",
|
|
247
247
|
"tags": [
|
|
248
248
|
"form",
|
|
249
249
|
"login",
|
|
250
250
|
"register",
|
|
251
251
|
"address",
|
|
252
252
|
"validation",
|
|
253
|
-
"input"
|
|
253
|
+
"input",
|
|
254
|
+
"password",
|
|
255
|
+
"account",
|
|
256
|
+
"deactivate"
|
|
254
257
|
]
|
|
255
258
|
},
|
|
256
259
|
"async-data-patterns": {
|
|
@@ -376,7 +379,7 @@
|
|
|
376
379
|
"account-patterns": {
|
|
377
380
|
"title": "Account & Dashboard Patterns",
|
|
378
381
|
"description": "Orders list, order detail, address management, favorites, and account info forms",
|
|
379
|
-
"content": "## Account & Dashboard Patterns\n\nAccount sections typically use a tab-based layout with orders, addresses, favorites, and account info.\n\n> **`customerStore` has exactly one public field: `customer`.** Everything else on the store is\n> underscore-prefixed internal state. Order and favorite lists are **not** fields on the store —\n> they come from the `Promise` that `getOrders()` / `getFavoriteProducts()` return. Reading\n> `customerStore.orders` or `customerStore.favoriteProducts` is a TypeScript error (TS2339 /\n> TS2551), not a silent empty list.\n>\n> This is the one place where copying data into `useState` is correct. The\n> \"don't mirror store data in `useState`\" rule in `async-data-patterns` §5 is about\n> store-backed observable fields; orders and favorites have no such field to observe.\n\n### Orders List\n\n```tsx\nimport { useEffect, useState } from \"preact/hooks\";\nimport {\n customerStore,\n getOrders,\n IkasOrder,\n getIkasOrderFormattedTotalFinalPrice,\n getIkasOrderTotalItemCount,\n getIkasOrderFormattedOrderedAt,\n getIkasOrderPackageStatusTranslation,\n getIkasOrderHref,\n} from \"@ikas/bp-storefront\";\n\n// getOrders() RESOLVES with the orders — it writes nothing to the store.\n// Keep the result in local state; there is no `customerStore.orders`.\nconst [orders, setOrders] = useState<IkasOrder[]>([]);\nconst [loading, setLoading] = useState(true);\n\nuseEffect(() => {\n let cancelled = false;\n getOrders(customerStore)\n .then((result) => {\n if (!cancelled) setOrders(result ?? []);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n}, []);\n\n{orders.map((order) => (\n <a key={order.id} href={getIkasOrderHref(order)} className=\"order-card\">\n <span>Order #{order.orderNumber}</span>\n <span>{getIkasOrderFormattedOrderedAt(order)}</span>\n <span>{getIkasOrderTotalItemCount(order)} items</span>\n <span>{getIkasOrderFormattedTotalFinalPrice(order)}</span>\n <span>{getIkasOrderPackageStatusTranslation(order)}</span>\n </a>\n))}\n```\n\n### Order Detail\n\n```tsx\nimport {\n getIkasOrderLineVariantMainImage,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// order.orderLineItems contains line items\n{order.orderLineItems.map((item) => {\n const image = item.variant\n ? getIkasOrderLineVariantMainImage(item.variant)\n : null;\n return (\n <div key={item.id}>\n {image && <img src={getDefaultSrc(image)} />}\n <span>{item.variant?.name}</span>\n <span>Qty: {item.quantity}</span>\n </div>\n );\n})}\n```\n\n### Address Management\n\nAddress forms are country-driven: the field set and the row layout come from\n`addressForm.addressFormat`, not from a list you write. Call\n`get_framework_guide({ topic: \"address-form-patterns\" })` before building one.\n\n```tsx\nimport {\n getEmptyAddressForm,\n getIkasCustomerAddressForm,\n initAddressForm,\n submitAddressForm,\n deleteCustomerAddress,\n customerStore,\n} from \"@ikas/bp-storefront\";\n\nconst addresses = customerStore.customer?.addresses ?? [];\n\n// Create: getEmptyAddressForm takes the STORE.\n// Edit: getIkasCustomerAddressForm takes the ADDRESS.\nconst addressForm = address ? getIkasCustomerAddressForm(address) : getEmptyAddressForm(customerStore);\n\n// init is async and loads the country format + location lists; gate render on it.\nawait initAddressForm(addressForm, address);\n\n// Render rows from addressForm.addressFormat, set values via setAddressForm*(addressForm, value),\n// then submit (it validates and returns a boolean):\nconst ok = await submitAddressForm(addressForm);\n\n// Delete takes the ADDRESS OBJECT, not an id:\nawait deleteCustomerAddress(customerStore, address);\n```\n\n### Favorites\n\n```tsx\nimport { useEffect, useState } from \"preact/hooks\";\nimport {\n customerStore,\n getFavoriteProducts,\n IkasProduct,\n} from \"@ikas/bp-storefront\";\n\n// Same rule as orders: getFavoriteProducts() resolves with the products.\n// There is no `customerStore.favoriteProducts` field.\nconst [favorites, setFavorites] = useState<IkasProduct[]>([]);\nconst [loading, setLoading] = useState(true);\n\nuseEffect(() => {\n let cancelled = false;\n getFavoriteProducts(customerStore)\n .then((result) => {\n if (!cancelled) setFavorites(result ?? []);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n}, []);\n```\n\nThe favorites **list** is a fetched snapshot, but the favorite **flag** is store-backed and\nreactive — `isFavoriteIkasProduct(product)` reads the store synchronously, so heart icons inside\nan `observer()` sub-component update on their own:\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport {\n isFavoriteIkasProduct,\n addIkasProductToFavorites,\n removeIkasProductFromFavorites,\n} from \"@ikas/bp-storefront\";\n\nconst FavoriteButton = observer(function FavoriteButton({ product }: { product: IkasProduct }) {\n const isFavorite = isFavoriteIkasProduct(product);\n return (\n <button\n onClick={() =>\n isFavorite\n ? removeIkasProductFromFavorites(product)\n : addIkasProductToFavorites(product)\n }\n >\n {isFavorite ? \"\\u2665\" : \"\\u2661\"}\n </button>\n );\n});\n```\n\nBecause the list is a snapshot, removing a favorite does **not** re-render it. Drop the item from\nyour local array as well:\n\n```tsx\nonFavoriteRemove={() => setFavorites((prev) => prev.filter((f) => f.id !== product.id))}\n```\n\nNote: `getFavoriteProducts` fetches at most 100 products in one call.\n\n### Account Info Form\n\n```tsx\nimport {\n getAccountInfoForm,\n initAccountInfoForm,\n setAccountInfoFormFirstName,\n setAccountInfoFormLastName,\n setAccountInfoFormPhone,\n submitAccountInfoForm,\n} from \"@ikas/bp-storefront\";\n\nconst accountForm = getAccountInfoForm(customerStore);\n\nuseEffect(() => {\n initAccountInfoForm(accountForm);\n}, []);\n\nconst handleSave = async (e: Event) => {\n e.preventDefault();\n await submitAccountInfoForm(accountForm);\n};\n```\n\n### Tab Navigation Pattern\n\nProduction themes use a tab-based layout:\n\n```tsx\nconst [activeTab, setActiveTab] = useState(\"orders\");\n\nconst tabs = [\n { id: \"orders\", label: \"Orders\" },\n { id: \"addresses\", label: \"Addresses\" },\n { id: \"favorites\", label: \"Favorites\" },\n { id: \"account\", label: \"Account Info\" },\n];\n\n<div className=\"account-tabs\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n className={activeTab === tab.id ? \"active\" : \"\"}\n onClick={() => setActiveTab(tab.id)}\n >\n {tab.label}\n </button>\n ))}\n</div>\n\n{activeTab === \"orders\" && <OrdersList />}\n{activeTab === \"addresses\" && <AddressList />}\n{activeTab === \"favorites\" && <FavoritesList />}\n{activeTab === \"account\" && <AccountInfoForm />}\n```",
|
|
382
|
+
"content": "## Account & Dashboard Patterns\n\nAccount sections typically use a tab-based layout with orders, addresses, favorites, and account info.\n\n> **`customerStore` has exactly one public field: `customer`.** Everything else on the store is\n> underscore-prefixed internal state. Order and favorite lists are **not** fields on the store —\n> they come from the `Promise` that `getOrders()` / `getFavoriteProducts()` return. Reading\n> `customerStore.orders` or `customerStore.favoriteProducts` is a TypeScript error (TS2339 /\n> TS2551), not a silent empty list.\n>\n> This is the one place where copying data into `useState` is correct. The\n> \"don't mirror store data in `useState`\" rule in `async-data-patterns` §5 is about\n> store-backed observable fields; orders and favorites have no such field to observe.\n\n### Orders List\n\n```tsx\nimport { useEffect, useState } from \"preact/hooks\";\nimport {\n customerStore,\n getOrders,\n IkasOrder,\n getIkasOrderFormattedTotalFinalPrice,\n getIkasOrderTotalItemCount,\n getIkasOrderFormattedOrderedAt,\n getIkasOrderPackageStatusTranslation,\n getIkasOrderHref,\n} from \"@ikas/bp-storefront\";\n\n// getOrders() RESOLVES with the orders — it writes nothing to the store.\n// Keep the result in local state; there is no `customerStore.orders`.\nconst [orders, setOrders] = useState<IkasOrder[]>([]);\nconst [loading, setLoading] = useState(true);\n\nuseEffect(() => {\n let cancelled = false;\n getOrders(customerStore)\n .then((result) => {\n if (!cancelled) setOrders(result ?? []);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n}, []);\n\n{orders.map((order) => (\n <a key={order.id} href={getIkasOrderHref(order)} className=\"order-card\">\n <span>Order #{order.orderNumber}</span>\n <span>{getIkasOrderFormattedOrderedAt(order)}</span>\n <span>{getIkasOrderTotalItemCount(order)} items</span>\n <span>{getIkasOrderFormattedTotalFinalPrice(order)}</span>\n <span>{getIkasOrderPackageStatusTranslation(order)}</span>\n </a>\n))}\n```\n\n### Order Detail\n\n```tsx\nimport {\n getIkasOrderLineVariantMainImage,\n getDefaultSrc,\n} from \"@ikas/bp-storefront\";\n\n// order.orderLineItems contains line items\n{order.orderLineItems.map((item) => {\n const image = item.variant\n ? getIkasOrderLineVariantMainImage(item.variant)\n : null;\n return (\n <div key={item.id}>\n {image && <img src={getDefaultSrc(image)} />}\n <span>{item.variant?.name}</span>\n <span>Qty: {item.quantity}</span>\n </div>\n );\n})}\n```\n\n### Address Management\n\nAddress forms are country-driven: the field set and the row layout come from\n`addressForm.addressFormat`, not from a list you write. Call\n`get_framework_guide({ topic: \"address-form-patterns\" })` before building one.\n\n```tsx\nimport {\n getEmptyAddressForm,\n getIkasCustomerAddressForm,\n initAddressForm,\n submitAddressForm,\n deleteCustomerAddress,\n customerStore,\n} from \"@ikas/bp-storefront\";\n\nconst addresses = customerStore.customer?.addresses ?? [];\n\n// Create: getEmptyAddressForm takes the STORE.\n// Edit: getIkasCustomerAddressForm takes the ADDRESS.\nconst addressForm = address ? getIkasCustomerAddressForm(address) : getEmptyAddressForm(customerStore);\n\n// init is async and loads the country format + location lists; gate render on it.\nawait initAddressForm(addressForm, address);\n\n// Render rows from addressForm.addressFormat, set values via setAddressForm*(addressForm, value),\n// then submit (it validates and returns a boolean):\nconst ok = await submitAddressForm(addressForm);\n\n// Delete takes the ADDRESS OBJECT, not an id:\nawait deleteCustomerAddress(customerStore, address);\n```\n\n### Favorites\n\n```tsx\nimport { useEffect, useState } from \"preact/hooks\";\nimport {\n customerStore,\n getFavoriteProducts,\n IkasProduct,\n} from \"@ikas/bp-storefront\";\n\n// Same rule as orders: getFavoriteProducts() resolves with the products.\n// There is no `customerStore.favoriteProducts` field.\nconst [favorites, setFavorites] = useState<IkasProduct[]>([]);\nconst [loading, setLoading] = useState(true);\n\nuseEffect(() => {\n let cancelled = false;\n getFavoriteProducts(customerStore)\n .then((result) => {\n if (!cancelled) setFavorites(result ?? []);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n return () => {\n cancelled = true;\n };\n}, []);\n```\n\nThe favorites **list** is a fetched snapshot, but the favorite **flag** is store-backed and\nreactive — `isFavoriteIkasProduct(product)` reads the store synchronously, so heart icons inside\nan `observer()` sub-component update on their own:\n\n```tsx\nimport { observer } from \"@ikas/component-utils\";\nimport {\n isFavoriteIkasProduct,\n addIkasProductToFavorites,\n removeIkasProductFromFavorites,\n} from \"@ikas/bp-storefront\";\n\nconst FavoriteButton = observer(function FavoriteButton({ product }: { product: IkasProduct }) {\n const isFavorite = isFavoriteIkasProduct(product);\n return (\n <button\n onClick={() =>\n isFavorite\n ? removeIkasProductFromFavorites(product)\n : addIkasProductToFavorites(product)\n }\n >\n {isFavorite ? \"\\u2665\" : \"\\u2661\"}\n </button>\n );\n});\n```\n\nBecause the list is a snapshot, removing a favorite does **not** re-render it. Drop the item from\nyour local array as well:\n\n```tsx\nonFavoriteRemove={() => setFavorites((prev) => prev.filter((f) => f.id !== product.id))}\n```\n\nNote: `getFavoriteProducts` fetches at most 100 products in one call.\n\n### Account Info Form\n\n```tsx\nimport {\n getAccountInfoForm,\n initAccountInfoForm,\n setAccountInfoFormFirstName,\n setAccountInfoFormLastName,\n setAccountInfoFormPhone,\n submitAccountInfoForm,\n} from \"@ikas/bp-storefront\";\n\nconst accountForm = getAccountInfoForm(customerStore);\n\nuseEffect(() => {\n initAccountInfoForm(accountForm);\n}, []);\n\nconst handleSave = async (e: Event) => {\n e.preventDefault();\n await submitAccountInfoForm(accountForm);\n};\n```\n\n### Delete Account (Deactivate Customer)\n\nAccount deletion is a form like any other — the customer confirms with their password. The form\ndoes **not** clear the session, so call `logout(customerStore)` yourself once it succeeds.\n\n```tsx\nimport {\n customerStore,\n getDeactivateCustomerForm,\n initDeactivateCustomerForm,\n setDeactivateCustomerFormPassword,\n submitDeactivateCustomerForm,\n logout,\n Router,\n} from \"@ikas/bp-storefront\";\n\nconst deactivateForm = getDeactivateCustomerForm(customerStore);\n\nuseEffect(() => {\n initDeactivateCustomerForm(deactivateForm);\n}, []);\n\nconst handleDelete = async (e: Event) => {\n e.preventDefault();\n const success = await submitDeactivateCustomerForm(deactivateForm);\n if (success) {\n await logout(customerStore);\n Router.navigateToPage(\"INDEX\");\n }\n};\n\n<form onSubmit={handleDelete}>\n {deactivateForm.isFailure && <div>{deactivateForm.responseMessage}</div>}\n\n <input\n type=\"password\"\n value={deactivateForm.password.value}\n onInput={(e) => setDeactivateCustomerFormPassword(deactivateForm, e.target.value)}\n />\n {deactivateForm.password.hasError && <span>{deactivateForm.password.message}</span>}\n\n <button type=\"submit\" disabled={deactivateForm.isSubmitting}>\n {deactivateForm.isSubmitting ? \"Deleting...\" : \"Delete Account\"}\n </button>\n</form>\n```\n\n`submit` already turns the API error for an existing deletion request into a translated\n`responseMessage` — render `responseMessage` rather than inspecting the API response yourself.\nDeletion is irreversible, so gate the form behind a confirmation step in your own UI.\n\n### Tab Navigation Pattern\n\nProduction themes use a tab-based layout:\n\n```tsx\nconst [activeTab, setActiveTab] = useState(\"orders\");\n\nconst tabs = [\n { id: \"orders\", label: \"Orders\" },\n { id: \"addresses\", label: \"Addresses\" },\n { id: \"favorites\", label: \"Favorites\" },\n { id: \"account\", label: \"Account Info\" },\n];\n\n<div className=\"account-tabs\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n className={activeTab === tab.id ? \"active\" : \"\"}\n onClick={() => setActiveTab(tab.id)}\n >\n {tab.label}\n </button>\n ))}\n</div>\n\n{activeTab === \"orders\" && <OrdersList />}\n{activeTab === \"addresses\" && <AddressList />}\n{activeTab === \"favorites\" && <FavoritesList />}\n{activeTab === \"account\" && <AccountInfoForm />}\n```",
|
|
380
383
|
"tags": [
|
|
381
384
|
"account",
|
|
382
385
|
"orders",
|
|
@@ -384,7 +387,10 @@
|
|
|
384
387
|
"favorites",
|
|
385
388
|
"dashboard",
|
|
386
389
|
"customer",
|
|
387
|
-
"tabs"
|
|
390
|
+
"tabs",
|
|
391
|
+
"deactivate",
|
|
392
|
+
"delete account",
|
|
393
|
+
"password"
|
|
388
394
|
]
|
|
389
395
|
},
|
|
390
396
|
"header-footer-patterns": {
|
package/data/storefront-api.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-08-
|
|
2
|
+
"generatedAt": "2026-08-24T11:34:54.069Z",
|
|
3
3
|
"functions": [
|
|
4
4
|
{
|
|
5
5
|
"name": "apiListProductBrand",
|
|
@@ -10746,6 +10746,88 @@
|
|
|
10746
10746
|
"isAsync": true,
|
|
10747
10747
|
"isClass": false
|
|
10748
10748
|
},
|
|
10749
|
+
{
|
|
10750
|
+
"name": "initDeactivateCustomerForm",
|
|
10751
|
+
"displayName": "init",
|
|
10752
|
+
"signature": "function initDeactivateCustomerForm(deactivateCustomerForm: DeactivateCustomerForm): void",
|
|
10753
|
+
"description": "Initializes the deactivate customer form with default password field values, labels, and placeholders.",
|
|
10754
|
+
"params": [
|
|
10755
|
+
{
|
|
10756
|
+
"name": "deactivateCustomerForm",
|
|
10757
|
+
"description": "- The deactivate customer form model to initialize"
|
|
10758
|
+
}
|
|
10759
|
+
],
|
|
10760
|
+
"returns": "void",
|
|
10761
|
+
"returnType": "void",
|
|
10762
|
+
"example": "```typescript\nimport { initDeactivateCustomerForm } from \"@ikas/bp-storefront\";\ninitDeactivateCustomerForm(deactivateCustomerForm);\n```",
|
|
10763
|
+
"categories": [
|
|
10764
|
+
"Validation",
|
|
10765
|
+
"DeactivateCustomer",
|
|
10766
|
+
"Form"
|
|
10767
|
+
],
|
|
10768
|
+
"related": [
|
|
10769
|
+
"setDeactivateCustomerFormPassword",
|
|
10770
|
+
"submitDeactivateCustomerForm"
|
|
10771
|
+
],
|
|
10772
|
+
"isAsync": false,
|
|
10773
|
+
"isClass": false
|
|
10774
|
+
},
|
|
10775
|
+
{
|
|
10776
|
+
"name": "setDeactivateCustomerFormPassword",
|
|
10777
|
+
"displayName": "setPassword",
|
|
10778
|
+
"signature": "function setDeactivateCustomerFormPassword(deactivateCustomerForm: DeactivateCustomerForm, value: string): void",
|
|
10779
|
+
"description": "Sets the password value on the deactivate customer form and re-validates if the form has already been submitted.",
|
|
10780
|
+
"params": [
|
|
10781
|
+
{
|
|
10782
|
+
"name": "deactivateCustomerForm",
|
|
10783
|
+
"description": "- The deactivate customer form model to update"
|
|
10784
|
+
},
|
|
10785
|
+
{
|
|
10786
|
+
"name": "value",
|
|
10787
|
+
"description": "- The new password string to set"
|
|
10788
|
+
}
|
|
10789
|
+
],
|
|
10790
|
+
"returns": "void",
|
|
10791
|
+
"returnType": "void",
|
|
10792
|
+
"example": "```typescript\nimport { setDeactivateCustomerFormPassword } from \"@ikas/bp-storefront\";\nsetDeactivateCustomerFormPassword(deactivateCustomerForm, \"mySecurePassword123\");\n```",
|
|
10793
|
+
"categories": [
|
|
10794
|
+
"Validation",
|
|
10795
|
+
"DeactivateCustomer",
|
|
10796
|
+
"Form"
|
|
10797
|
+
],
|
|
10798
|
+
"related": [
|
|
10799
|
+
"initDeactivateCustomerForm",
|
|
10800
|
+
"submitDeactivateCustomerForm"
|
|
10801
|
+
],
|
|
10802
|
+
"isAsync": false,
|
|
10803
|
+
"isClass": false
|
|
10804
|
+
},
|
|
10805
|
+
{
|
|
10806
|
+
"name": "submitDeactivateCustomerForm",
|
|
10807
|
+
"displayName": "submit",
|
|
10808
|
+
"signature": "async function submitDeactivateCustomerForm(deactivateCustomerForm: DeactivateCustomerForm): Promise<boolean>",
|
|
10809
|
+
"description": "Validates and submits the deactivate customer form, deleting the current customer's account after password verification.\nThe customer session is not cleared automatically, call `logout` after a successful submission.",
|
|
10810
|
+
"params": [
|
|
10811
|
+
{
|
|
10812
|
+
"name": "deactivateCustomerForm",
|
|
10813
|
+
"description": "- The deactivate customer form model to submit"
|
|
10814
|
+
}
|
|
10815
|
+
],
|
|
10816
|
+
"returns": "A promise that resolves to true if the account was deactivated successfully, or false on validation error or API failure",
|
|
10817
|
+
"returnType": "Promise<boolean>",
|
|
10818
|
+
"example": "```typescript\nimport { customerStore, logout, submitDeactivateCustomerForm } from \"@ikas/bp-storefront\";\nconst success = await submitDeactivateCustomerForm(deactivateCustomerForm);\nif (success) {\n await logout(customerStore);\n}\n```",
|
|
10819
|
+
"categories": [
|
|
10820
|
+
"Validation",
|
|
10821
|
+
"DeactivateCustomer",
|
|
10822
|
+
"Form"
|
|
10823
|
+
],
|
|
10824
|
+
"related": [
|
|
10825
|
+
"initDeactivateCustomerForm",
|
|
10826
|
+
"setDeactivateCustomerFormPassword"
|
|
10827
|
+
],
|
|
10828
|
+
"isAsync": true,
|
|
10829
|
+
"isClass": false
|
|
10830
|
+
},
|
|
10749
10831
|
{
|
|
10750
10832
|
"name": "initForgotPasswordForm",
|
|
10751
10833
|
"displayName": "init",
|
|
@@ -13986,7 +14068,8 @@
|
|
|
13986
14068
|
],
|
|
13987
14069
|
"related": [
|
|
13988
14070
|
"hasCustomer",
|
|
13989
|
-
"logout"
|
|
14071
|
+
"logout",
|
|
14072
|
+
"getDeactivateCustomerForm"
|
|
13990
14073
|
],
|
|
13991
14074
|
"isAsync": true,
|
|
13992
14075
|
"isClass": false,
|
|
@@ -14557,6 +14640,58 @@
|
|
|
14557
14640
|
"IkasCustomerStore"
|
|
14558
14641
|
]
|
|
14559
14642
|
},
|
|
14643
|
+
{
|
|
14644
|
+
"name": "getDeactivateCustomerForm",
|
|
14645
|
+
"signature": "function getDeactivateCustomerForm(customerStore: IkasCustomerStore): DeactivateCustomerForm",
|
|
14646
|
+
"description": "Get or initialize the deactivate customer form with validation for deleting the current customer's account.",
|
|
14647
|
+
"params": [
|
|
14648
|
+
{
|
|
14649
|
+
"name": "customerStore",
|
|
14650
|
+
"description": "- The customer store instance"
|
|
14651
|
+
}
|
|
14652
|
+
],
|
|
14653
|
+
"returns": "The deactivate customer form object with validated fields",
|
|
14654
|
+
"returnType": "DeactivateCustomerForm",
|
|
14655
|
+
"example": "```typescript\nimport {\n customerStore,\n getDeactivateCustomerForm,\n setDeactivateCustomerFormPassword,\n submitDeactivateCustomerForm,\n logout,\n} from \"@ikas/bp-storefront\";\n\nfunction DeleteAccountPage() {\n const form = getDeactivateCustomerForm(customerStore);\n\n const handleSubmit = async () => {\n const isSuccess = await submitDeactivateCustomerForm(form);\n if (isSuccess) {\n await logout(customerStore);\n Router.navigateToPage(\"INDEX\");\n }\n };\n\n return (\n <form>\n <input\n type=\"password\"\n value={form.password.value}\n onChange={e => setDeactivateCustomerFormPassword(form, e.target.value)}\n />\n <button onClick={handleSubmit}>Delete Account</button>\n </form>\n );\n}\n```",
|
|
14656
|
+
"categories": [
|
|
14657
|
+
"Customer",
|
|
14658
|
+
"Account"
|
|
14659
|
+
],
|
|
14660
|
+
"related": [
|
|
14661
|
+
"clearDeactivateCustomerForm",
|
|
14662
|
+
"deactivateCustomer"
|
|
14663
|
+
],
|
|
14664
|
+
"isAsync": false,
|
|
14665
|
+
"isClass": false,
|
|
14666
|
+
"parameterTypes": [
|
|
14667
|
+
"IkasCustomerStore"
|
|
14668
|
+
]
|
|
14669
|
+
},
|
|
14670
|
+
{
|
|
14671
|
+
"name": "clearDeactivateCustomerForm",
|
|
14672
|
+
"signature": "function clearDeactivateCustomerForm(customerStore: IkasCustomerStore): void",
|
|
14673
|
+
"description": "Clear the cached deactivate customer form, forcing a fresh form on next retrieval.",
|
|
14674
|
+
"params": [
|
|
14675
|
+
{
|
|
14676
|
+
"name": "customerStore",
|
|
14677
|
+
"description": "- The customer store instance"
|
|
14678
|
+
}
|
|
14679
|
+
],
|
|
14680
|
+
"returnType": "void",
|
|
14681
|
+
"example": "```typescript\nimport { customerStore, clearDeactivateCustomerForm } from \"@ikas/bp-storefront\";\n\nfunction handleDeactivateCustomerReset() {\n clearDeactivateCustomerForm(customerStore);\n}\n```",
|
|
14682
|
+
"categories": [
|
|
14683
|
+
"Customer",
|
|
14684
|
+
"Account"
|
|
14685
|
+
],
|
|
14686
|
+
"related": [
|
|
14687
|
+
"getDeactivateCustomerForm"
|
|
14688
|
+
],
|
|
14689
|
+
"isAsync": false,
|
|
14690
|
+
"isClass": false,
|
|
14691
|
+
"parameterTypes": [
|
|
14692
|
+
"IkasCustomerStore"
|
|
14693
|
+
]
|
|
14694
|
+
},
|
|
14560
14695
|
{
|
|
14561
14696
|
"name": "getForgotPasswordForm",
|
|
14562
14697
|
"signature": "function getForgotPasswordForm(customerStore: IkasCustomerStore): ForgotPasswordForm",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-08-
|
|
2
|
+
"generatedAt": "2026-08-24T11:34:54.098Z",
|
|
3
3
|
"types": [
|
|
4
4
|
{
|
|
5
5
|
"name": "TextElementTagTypes",
|
|
@@ -13831,6 +13831,22 @@
|
|
|
13831
13831
|
],
|
|
13832
13832
|
"filePath": "validator/customer-review/index.ts"
|
|
13833
13833
|
},
|
|
13834
|
+
{
|
|
13835
|
+
"name": "DeactivateCustomerForm",
|
|
13836
|
+
"kind": "type",
|
|
13837
|
+
"domain": "validator",
|
|
13838
|
+
"properties": [
|
|
13839
|
+
{
|
|
13840
|
+
"name": "password",
|
|
13841
|
+
"type": "IkasFormItem",
|
|
13842
|
+
"optional": false
|
|
13843
|
+
}
|
|
13844
|
+
],
|
|
13845
|
+
"extends": [
|
|
13846
|
+
"IkasForm"
|
|
13847
|
+
],
|
|
13848
|
+
"filePath": "validator/deactivate-customer/index.ts"
|
|
13849
|
+
},
|
|
13834
13850
|
{
|
|
13835
13851
|
"name": "ForgotPasswordForm",
|
|
13836
13852
|
"kind": "type",
|