@ikas/code-components-mcp 1.4.0-beta.174 → 1.4.0-beta.176

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.
@@ -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": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-08-20T09:36:42.821Z",
2
+ "generatedAt": "2026-08-24T11:36:32.957Z",
3
3
  "functions": [
4
4
  {
5
5
  "name": "apiListProductBrand",
@@ -101,16 +101,16 @@
101
101
  },
102
102
  {
103
103
  "name": "apiListLoyaltyProgramPointHistory",
104
- "signature": "async function apiListLoyaltyProgramPointHistory(params: ListLoyaltyProgramPointHistoryQueryParams): Promise<APIResponse<IkasLoyaltyProgramPointHistory[]>>",
104
+ "signature": "async function apiListLoyaltyProgramPointHistory(params: ListLoyaltyProgramPointHistoryQueryParams): Promise<APIResponse<IkasLoyaltyProgramPointHistoryPaginationResponse>>",
105
105
  "description": "",
106
106
  "params": [],
107
- "returnType": "Promise<APIResponse<IkasLoyaltyProgramPointHistory[]>>",
107
+ "returnType": "Promise<APIResponse<IkasLoyaltyProgramPointHistoryPaginationResponse>>",
108
108
  "categories": [],
109
109
  "related": [],
110
110
  "isAsync": true,
111
111
  "isClass": false,
112
112
  "parameterTypes": [
113
- "IkasLoyaltyProgramPointHistory"
113
+ "IkasLoyaltyProgramPointHistoryPaginationResponse"
114
114
  ]
115
115
  },
116
116
  {
@@ -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,
@@ -14352,17 +14435,17 @@
14352
14435
  {
14353
14436
  "name": "cs_listLoyaltyProgramPointHistory",
14354
14437
  "displayName": "listLoyaltyProgramPointHistory",
14355
- "signature": "async function cs_listLoyaltyProgramPointHistory(customerStore: IkasCustomerStore, params: ListLoyaltyProgramPointHistoryQueryParams): Promise<IkasLoyaltyProgramPointHistory[] | undefined>",
14438
+ "signature": "async function cs_listLoyaltyProgramPointHistory(customerStore: IkasCustomerStore, params: ListLoyaltyProgramPointHistoryQueryParams): Promise<IkasLoyaltyProgramPointHistoryPaginationResponse | undefined>",
14356
14439
  "description": "",
14357
14440
  "params": [],
14358
- "returnType": "Promise<IkasLoyaltyProgramPointHistory[] | undefined>",
14441
+ "returnType": "Promise<IkasLoyaltyProgramPointHistoryPaginationResponse | undefined>",
14359
14442
  "categories": [],
14360
14443
  "related": [],
14361
14444
  "isAsync": true,
14362
14445
  "isClass": false,
14363
14446
  "parameterTypes": [
14364
14447
  "IkasCustomerStore",
14365
- "IkasLoyaltyProgramPointHistory"
14448
+ "IkasLoyaltyProgramPointHistoryPaginationResponse"
14366
14449
  ]
14367
14450
  },
14368
14451
  {
@@ -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-20T09:36:42.849Z",
2
+ "generatedAt": "2026-08-24T11:36:32.988Z",
3
3
  "types": [
4
4
  {
5
5
  "name": "TextElementTagTypes",
@@ -5370,6 +5370,16 @@
5370
5370
  "type": "number",
5371
5371
  "optional": false
5372
5372
  },
5373
+ {
5374
+ "name": "baseAmount",
5375
+ "type": "number | null",
5376
+ "optional": false
5377
+ },
5378
+ {
5379
+ "name": "baseQuantity",
5380
+ "type": "number | null",
5381
+ "optional": false
5382
+ },
5373
5383
  {
5374
5384
  "name": "createdAt",
5375
5385
  "type": "any | null",
@@ -5420,6 +5430,16 @@
5420
5430
  "type": "string | null",
5421
5431
  "optional": false
5422
5432
  },
5433
+ {
5434
+ "name": "refundTransactionId",
5435
+ "type": "string | null",
5436
+ "optional": false
5437
+ },
5438
+ {
5439
+ "name": "retroactiveCalculationId",
5440
+ "type": "string | null",
5441
+ "optional": false
5442
+ },
5423
5443
  {
5424
5444
  "name": "type",
5425
5445
  "type": "LoyaltyProgramPointHistoryTypeEnum",
@@ -5431,6 +5451,39 @@
5431
5451
  "optional": false
5432
5452
  }
5433
5453
  ],
5454
+ "filePath": "loyalty-program/point-history/index.ts"
5455
+ },
5456
+ {
5457
+ "name": "IkasLoyaltyProgramPointHistoryPaginationResponse",
5458
+ "kind": "type",
5459
+ "domain": "loyalty-program",
5460
+ "properties": [
5461
+ {
5462
+ "name": "count",
5463
+ "type": "number",
5464
+ "optional": false
5465
+ },
5466
+ {
5467
+ "name": "data",
5468
+ "type": "IkasLoyaltyProgramPointHistory[]",
5469
+ "optional": false
5470
+ },
5471
+ {
5472
+ "name": "hasNext",
5473
+ "type": "boolean",
5474
+ "optional": false
5475
+ },
5476
+ {
5477
+ "name": "limit",
5478
+ "type": "number",
5479
+ "optional": false
5480
+ },
5481
+ {
5482
+ "name": "page",
5483
+ "type": "number",
5484
+ "optional": false
5485
+ }
5486
+ ],
5434
5487
  "filePath": "loyalty-program/point-history/index.ts",
5435
5488
  "relatedFunctions": [
5436
5489
  "apiListLoyaltyProgramPointHistory",
@@ -13831,6 +13884,22 @@
13831
13884
  ],
13832
13885
  "filePath": "validator/customer-review/index.ts"
13833
13886
  },
13887
+ {
13888
+ "name": "DeactivateCustomerForm",
13889
+ "kind": "type",
13890
+ "domain": "validator",
13891
+ "properties": [
13892
+ {
13893
+ "name": "password",
13894
+ "type": "IkasFormItem",
13895
+ "optional": false
13896
+ }
13897
+ ],
13898
+ "extends": [
13899
+ "IkasForm"
13900
+ ],
13901
+ "filePath": "validator/deactivate-customer/index.ts"
13902
+ },
13834
13903
  {
13835
13904
  "name": "ForgotPasswordForm",
13836
13905
  "kind": "type",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ikas/code-components-mcp",
3
- "version": "1.4.0-beta.174",
3
+ "version": "1.4.0-beta.176",
4
4
  "description": "MCP server for ikas code components documentation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",