@open-mercato/core 0.6.1-develop.3278.1.a4d26475e4 → 0.6.1-develop.3291.1.6fad645fd0

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 (58) hide show
  1. package/dist/helpers/integration/auth.js +1 -1
  2. package/dist/helpers/integration/auth.js.map +2 -2
  3. package/dist/helpers/integration/authUi.js +1 -1
  4. package/dist/helpers/integration/authUi.js.map +2 -2
  5. package/dist/modules/auth/frontend/login.js +4 -4
  6. package/dist/modules/auth/frontend/login.js.map +2 -2
  7. package/dist/modules/auth/frontend/reset/[token]/page.js +5 -5
  8. package/dist/modules/auth/frontend/reset/[token]/page.js.map +2 -2
  9. package/dist/modules/auth/frontend/reset.js +2 -2
  10. package/dist/modules/auth/frontend/reset.js.map +2 -2
  11. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +4 -3
  12. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  13. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +6 -5
  14. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  15. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +2 -2
  16. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  17. package/dist/modules/customers/components/formConfig.js +2 -2
  18. package/dist/modules/customers/components/formConfig.js.map +2 -2
  19. package/dist/modules/integrations/backend/integrations/[id]/page.js +2 -3
  20. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  21. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +4 -3
  22. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  23. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js +3 -3
  24. package/dist/modules/portal/frontend/[orgSlug]/portal/reset-password/page.js.map +2 -2
  25. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +4 -2
  26. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +2 -2
  27. package/dist/modules/sales/backend/sales/documents/[id]/page.js +3 -5
  28. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  29. package/dist/modules/sales/components/ProviderFieldInput.js +2 -2
  30. package/dist/modules/sales/components/ProviderFieldInput.js.map +2 -2
  31. package/dist/modules/sales/components/documents/SalesDocumentForm.js +3 -4
  32. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  33. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +3 -4
  34. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +2 -2
  35. package/dist/modules/workflows/backend/tasks/[id]/page.js +2 -2
  36. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  37. package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -2
  38. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  39. package/package.json +5 -5
  40. package/src/helpers/integration/auth.ts +1 -1
  41. package/src/helpers/integration/authUi.ts +1 -1
  42. package/src/modules/auth/frontend/login.tsx +4 -3
  43. package/src/modules/auth/frontend/reset/[token]/page.tsx +4 -4
  44. package/src/modules/auth/frontend/reset.tsx +2 -2
  45. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +3 -2
  46. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +5 -4
  47. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +2 -2
  48. package/src/modules/customers/components/formConfig.tsx +2 -2
  49. package/src/modules/integrations/backend/integrations/[id]/page.tsx +2 -2
  50. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +4 -3
  51. package/src/modules/portal/frontend/[orgSlug]/portal/reset-password/page.tsx +3 -3
  52. package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +4 -2
  53. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +3 -5
  54. package/src/modules/sales/components/ProviderFieldInput.tsx +2 -2
  55. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +2 -3
  56. package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +3 -4
  57. package/src/modules/workflows/backend/tasks/[id]/page.tsx +2 -2
  58. package/src/modules/workflows/frontend/checkout-demo/page.tsx +2 -2
@@ -186,7 +186,7 @@ async function login(page, role = "admin") {
186
186
  }
187
187
  }
188
188
  await page.getByLabel("Email").fill(creds.email);
189
- const passwordInput = page.getByLabel("Password").first();
189
+ const passwordInput = page.getByLabel("Password", { exact: true }).first();
190
190
  if (await passwordInput.isVisible().catch(() => false)) {
191
191
  await passwordInput.fill(creds.password);
192
192
  await passwordInput.press("Enter");
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/helpers/integration/auth.ts"],
4
- "sourcesContent": ["import { type Page } from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\n\nfunction loadEnvFileContent(): string | null {\n const candidatePaths = [\n resolve(process.cwd(), 'apps/mercato/.env'),\n resolve(process.cwd(), '.env'),\n ];\n\n for (const envPath of candidatePaths) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n if (content.trim().length > 0) {\n return content;\n }\n } catch {\n continue;\n }\n }\n\n return null;\n}\n\nfunction loadEnvValue(key: string): string | undefined {\n if (process.env[key]) return process.env[key];\n const content = loadEnvFileContent();\n if (!content) return undefined;\n const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));\n return match?.[1]?.trim();\n}\n\nexport const DEFAULT_CREDENTIALS: Record<string, { email: string; password: string }> = {\n superadmin: {\n email: loadEnvValue('OM_INIT_SUPERADMIN_EMAIL') || 'superadmin@acme.com',\n password: loadEnvValue('OM_INIT_SUPERADMIN_PASSWORD') || 'secret',\n },\n admin: { email: 'admin@acme.com', password: 'secret' },\n employee: { email: 'employee@acme.com', password: 'secret' },\n};\n\nexport type Role = 'superadmin' | 'admin' | 'employee';\n\nfunction decodeJwtClaims(token: string): { tenantId?: string; orgId?: string | null } | null {\n const parts = token.split('.');\n if (parts.length < 2) return null;\n try {\n const normalized = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');\n const payload = JSON.parse(Buffer.from(padded, 'base64').toString('utf8')) as {\n tenantId?: string;\n orgId?: string | null;\n };\n return payload;\n } catch {\n return null;\n }\n}\n\nasync function acknowledgeGlobalNotices(page: Page): Promise<void> {\n const baseUrl = process.env.BASE_URL || 'http://localhost:3000';\n await page.context().addCookies([\n {\n name: 'om_demo_notice_ack',\n value: 'ack',\n url: baseUrl,\n sameSite: 'Lax',\n },\n {\n name: 'om_cookie_notice_ack',\n value: 'ack',\n url: baseUrl,\n sameSite: 'Lax',\n },\n {\n name: 'om_feedback_suppress',\n value: '1',\n url: baseUrl,\n sameSite: 'Lax',\n },\n {\n name: 'om_feedback_shown',\n value: new Date().toISOString().slice(0, 10),\n url: baseUrl,\n sameSite: 'Lax',\n },\n ]);\n}\n\nasync function dismissGlobalNoticesIfPresent(page: Page): Promise<void> {\n const cookieAcceptButton = page.getByRole('button', { name: /accept cookies/i }).first();\n if (await cookieAcceptButton.isVisible().catch(() => false)) {\n await cookieAcceptButton.click();\n }\n\n const demoNotice = page.getByText(/this instance is provided for demo purposes only/i).first();\n if (await demoNotice.isVisible().catch(() => false)) {\n const noticeContainer = demoNotice.locator('xpath=ancestor::div[contains(@class,\"pointer-events-auto\")]').first();\n const dismissButton = noticeContainer.locator('button').first();\n if (await dismissButton.isVisible().catch(() => false)) {\n await dismissButton.click();\n }\n }\n\n const feedbackDialog = page.getByRole('dialog', { name: /Talk to Open Mercato team/i }).first();\n if (await feedbackDialog.isVisible().catch(() => false)) {\n const closeButton = feedbackDialog.getByRole('button', { name: /close/i }).first();\n if (await closeButton.isVisible().catch(() => false)) {\n await closeButton.click().catch(() => {});\n } else {\n await page.keyboard.press('Escape').catch(() => {});\n }\n }\n}\n\nasync function recoverClientSideErrorPageIfPresent(page: Page): Promise<void> {\n const clientErrorHeading = page\n .getByRole('heading', { name: /Application error: a client-side exception has occurred/i })\n .first();\n if (!(await clientErrorHeading.isVisible().catch(() => false))) return;\n await page.reload({ waitUntil: 'domcontentloaded' });\n await dismissGlobalNoticesIfPresent(page);\n}\n\nasync function recoverGenericErrorPageIfPresent(page: Page): Promise<void> {\n const errorHeading = page.getByRole('heading', { name: /^Something went wrong$/i }).first();\n if (!(await errorHeading.isVisible().catch(() => false))) return;\n const retryButton = page.getByRole('button', { name: /Try again/i }).first();\n if (await retryButton.isVisible().catch(() => false)) {\n await retryButton.click().catch(() => {});\n await page.waitForLoadState('domcontentloaded').catch(() => {});\n await page.waitForTimeout(500).catch(() => {});\n } else {\n await page.reload({ waitUntil: 'domcontentloaded' });\n }\n await dismissGlobalNoticesIfPresent(page);\n}\n\nexport async function login(page: Page, role: Role = 'admin'): Promise<void> {\n const creds = DEFAULT_CREDENTIALS[role];\n const loginReadySelector = 'form[data-auth-ready=\"1\"]';\n const hasBackendUrl = (): boolean => /\\/backend(?:\\/.*)?$/.test(page.url());\n const waitForBackend = async (timeout: number): Promise<boolean> => {\n try {\n await page.waitForURL(/\\/backend(?:\\/.*)?$/, { timeout });\n return true;\n } catch {\n return hasBackendUrl();\n }\n };\n\n await acknowledgeGlobalNotices(page);\n const apiLoginForm = new URLSearchParams();\n apiLoginForm.set('email', creds.email);\n apiLoginForm.set('password', creds.password);\n // Retry-on-429 against the auth rate limit (5/60s per email). Capped\n // exponential backoff: 1s, 2s, 4s \u2014 worst-case ~7s.\n let apiLoginResponse: Awaited<ReturnType<typeof page.request.post>> | null = null;\n for (let retry = 0; retry < 4; retry += 1) {\n apiLoginResponse = await page.request.post('/api/auth/login', {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n },\n data: apiLoginForm.toString(),\n }).catch(() => null);\n if (!apiLoginResponse || apiLoginResponse.status() !== 429) break;\n await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** retry));\n }\n if (apiLoginResponse?.ok()) {\n const apiLoginBody = (await apiLoginResponse.json().catch(() => null)) as { token?: string } | null;\n const claims = typeof apiLoginBody?.token === 'string' ? decodeJwtClaims(apiLoginBody.token) : null;\n const baseUrl = process.env.BASE_URL || 'http://localhost:3000';\n const cookies = [];\n if (claims?.tenantId) {\n cookies.push({\n name: 'om_selected_tenant',\n value: claims.tenantId,\n url: baseUrl,\n sameSite: 'Lax' as const,\n });\n }\n if (claims?.orgId) {\n cookies.push({\n name: 'om_selected_org',\n value: claims.orgId,\n url: baseUrl,\n sameSite: 'Lax' as const,\n });\n }\n if (cookies.length > 0) {\n await page.context().addCookies(cookies);\n }\n await page.goto('/backend', { waitUntil: 'domcontentloaded' });\n if (await waitForBackend(8_000)) return;\n }\n\n for (let attempt = 0; attempt < 4; attempt += 1) {\n await page.goto('/login', { waitUntil: 'domcontentloaded' });\n await dismissGlobalNoticesIfPresent(page);\n await recoverClientSideErrorPageIfPresent(page);\n await recoverGenericErrorPageIfPresent(page);\n await page.waitForSelector(loginReadySelector, { state: 'visible', timeout: 3_000 }).catch(() => null);\n if (await page.getByLabel('Email').isVisible().catch(() => false)) break;\n if (attempt === 3) {\n throw new Error(`Login form is unavailable for role: ${role}; current URL: ${page.url()}`);\n }\n }\n await page.getByLabel('Email').fill(creds.email);\n\n const passwordInput = page.getByLabel('Password').first();\n if (await passwordInput.isVisible().catch(() => false)) {\n await passwordInput.fill(creds.password);\n await passwordInput.press('Enter');\n } else {\n const submitButton = page.getByRole('button', { name: /login|sign in|continue with sso/i }).first();\n await submitButton.click();\n }\n\n if (await waitForBackend(7_000)) return;\n\n const loginForm = page.locator('form').first();\n if (await loginForm.isVisible().catch(() => false)) {\n await loginForm.evaluate((element) => {\n const form = element as HTMLFormElement\n form.requestSubmit()\n }).catch(() => {})\n }\n if (await waitForBackend(5_000)) return;\n\n const loginButton = page.getByRole('button', { name: /login|sign in|continue with sso/i }).first();\n if (await loginButton.isVisible().catch(() => false)) {\n await loginButton.click({ force: true });\n }\n if (await waitForBackend(8_000)) return;\n\n throw new Error(`Login did not reach backend for role: ${role}; current URL: ${page.url()}`);\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { type Page } from '@playwright/test';\nimport { readFileSync } from 'fs';\nimport { resolve } from 'path';\n\nfunction loadEnvFileContent(): string | null {\n const candidatePaths = [\n resolve(process.cwd(), 'apps/mercato/.env'),\n resolve(process.cwd(), '.env'),\n ];\n\n for (const envPath of candidatePaths) {\n try {\n const content = readFileSync(envPath, 'utf-8');\n if (content.trim().length > 0) {\n return content;\n }\n } catch {\n continue;\n }\n }\n\n return null;\n}\n\nfunction loadEnvValue(key: string): string | undefined {\n if (process.env[key]) return process.env[key];\n const content = loadEnvFileContent();\n if (!content) return undefined;\n const match = content.match(new RegExp(`^${key}=(.+)$`, 'm'));\n return match?.[1]?.trim();\n}\n\nexport const DEFAULT_CREDENTIALS: Record<string, { email: string; password: string }> = {\n superadmin: {\n email: loadEnvValue('OM_INIT_SUPERADMIN_EMAIL') || 'superadmin@acme.com',\n password: loadEnvValue('OM_INIT_SUPERADMIN_PASSWORD') || 'secret',\n },\n admin: { email: 'admin@acme.com', password: 'secret' },\n employee: { email: 'employee@acme.com', password: 'secret' },\n};\n\nexport type Role = 'superadmin' | 'admin' | 'employee';\n\nfunction decodeJwtClaims(token: string): { tenantId?: string; orgId?: string | null } | null {\n const parts = token.split('.');\n if (parts.length < 2) return null;\n try {\n const normalized = parts[1].replace(/-/g, '+').replace(/_/g, '/');\n const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, '=');\n const payload = JSON.parse(Buffer.from(padded, 'base64').toString('utf8')) as {\n tenantId?: string;\n orgId?: string | null;\n };\n return payload;\n } catch {\n return null;\n }\n}\n\nasync function acknowledgeGlobalNotices(page: Page): Promise<void> {\n const baseUrl = process.env.BASE_URL || 'http://localhost:3000';\n await page.context().addCookies([\n {\n name: 'om_demo_notice_ack',\n value: 'ack',\n url: baseUrl,\n sameSite: 'Lax',\n },\n {\n name: 'om_cookie_notice_ack',\n value: 'ack',\n url: baseUrl,\n sameSite: 'Lax',\n },\n {\n name: 'om_feedback_suppress',\n value: '1',\n url: baseUrl,\n sameSite: 'Lax',\n },\n {\n name: 'om_feedback_shown',\n value: new Date().toISOString().slice(0, 10),\n url: baseUrl,\n sameSite: 'Lax',\n },\n ]);\n}\n\nasync function dismissGlobalNoticesIfPresent(page: Page): Promise<void> {\n const cookieAcceptButton = page.getByRole('button', { name: /accept cookies/i }).first();\n if (await cookieAcceptButton.isVisible().catch(() => false)) {\n await cookieAcceptButton.click();\n }\n\n const demoNotice = page.getByText(/this instance is provided for demo purposes only/i).first();\n if (await demoNotice.isVisible().catch(() => false)) {\n const noticeContainer = demoNotice.locator('xpath=ancestor::div[contains(@class,\"pointer-events-auto\")]').first();\n const dismissButton = noticeContainer.locator('button').first();\n if (await dismissButton.isVisible().catch(() => false)) {\n await dismissButton.click();\n }\n }\n\n const feedbackDialog = page.getByRole('dialog', { name: /Talk to Open Mercato team/i }).first();\n if (await feedbackDialog.isVisible().catch(() => false)) {\n const closeButton = feedbackDialog.getByRole('button', { name: /close/i }).first();\n if (await closeButton.isVisible().catch(() => false)) {\n await closeButton.click().catch(() => {});\n } else {\n await page.keyboard.press('Escape').catch(() => {});\n }\n }\n}\n\nasync function recoverClientSideErrorPageIfPresent(page: Page): Promise<void> {\n const clientErrorHeading = page\n .getByRole('heading', { name: /Application error: a client-side exception has occurred/i })\n .first();\n if (!(await clientErrorHeading.isVisible().catch(() => false))) return;\n await page.reload({ waitUntil: 'domcontentloaded' });\n await dismissGlobalNoticesIfPresent(page);\n}\n\nasync function recoverGenericErrorPageIfPresent(page: Page): Promise<void> {\n const errorHeading = page.getByRole('heading', { name: /^Something went wrong$/i }).first();\n if (!(await errorHeading.isVisible().catch(() => false))) return;\n const retryButton = page.getByRole('button', { name: /Try again/i }).first();\n if (await retryButton.isVisible().catch(() => false)) {\n await retryButton.click().catch(() => {});\n await page.waitForLoadState('domcontentloaded').catch(() => {});\n await page.waitForTimeout(500).catch(() => {});\n } else {\n await page.reload({ waitUntil: 'domcontentloaded' });\n }\n await dismissGlobalNoticesIfPresent(page);\n}\n\nexport async function login(page: Page, role: Role = 'admin'): Promise<void> {\n const creds = DEFAULT_CREDENTIALS[role];\n const loginReadySelector = 'form[data-auth-ready=\"1\"]';\n const hasBackendUrl = (): boolean => /\\/backend(?:\\/.*)?$/.test(page.url());\n const waitForBackend = async (timeout: number): Promise<boolean> => {\n try {\n await page.waitForURL(/\\/backend(?:\\/.*)?$/, { timeout });\n return true;\n } catch {\n return hasBackendUrl();\n }\n };\n\n await acknowledgeGlobalNotices(page);\n const apiLoginForm = new URLSearchParams();\n apiLoginForm.set('email', creds.email);\n apiLoginForm.set('password', creds.password);\n // Retry-on-429 against the auth rate limit (5/60s per email). Capped\n // exponential backoff: 1s, 2s, 4s \u2014 worst-case ~7s.\n let apiLoginResponse: Awaited<ReturnType<typeof page.request.post>> | null = null;\n for (let retry = 0; retry < 4; retry += 1) {\n apiLoginResponse = await page.request.post('/api/auth/login', {\n headers: {\n 'content-type': 'application/x-www-form-urlencoded',\n },\n data: apiLoginForm.toString(),\n }).catch(() => null);\n if (!apiLoginResponse || apiLoginResponse.status() !== 429) break;\n await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** retry));\n }\n if (apiLoginResponse?.ok()) {\n const apiLoginBody = (await apiLoginResponse.json().catch(() => null)) as { token?: string } | null;\n const claims = typeof apiLoginBody?.token === 'string' ? decodeJwtClaims(apiLoginBody.token) : null;\n const baseUrl = process.env.BASE_URL || 'http://localhost:3000';\n const cookies = [];\n if (claims?.tenantId) {\n cookies.push({\n name: 'om_selected_tenant',\n value: claims.tenantId,\n url: baseUrl,\n sameSite: 'Lax' as const,\n });\n }\n if (claims?.orgId) {\n cookies.push({\n name: 'om_selected_org',\n value: claims.orgId,\n url: baseUrl,\n sameSite: 'Lax' as const,\n });\n }\n if (cookies.length > 0) {\n await page.context().addCookies(cookies);\n }\n await page.goto('/backend', { waitUntil: 'domcontentloaded' });\n if (await waitForBackend(8_000)) return;\n }\n\n for (let attempt = 0; attempt < 4; attempt += 1) {\n await page.goto('/login', { waitUntil: 'domcontentloaded' });\n await dismissGlobalNoticesIfPresent(page);\n await recoverClientSideErrorPageIfPresent(page);\n await recoverGenericErrorPageIfPresent(page);\n await page.waitForSelector(loginReadySelector, { state: 'visible', timeout: 3_000 }).catch(() => null);\n if (await page.getByLabel('Email').isVisible().catch(() => false)) break;\n if (attempt === 3) {\n throw new Error(`Login form is unavailable for role: ${role}; current URL: ${page.url()}`);\n }\n }\n await page.getByLabel('Email').fill(creds.email);\n\n const passwordInput = page.getByLabel('Password', { exact: true }).first();\n if (await passwordInput.isVisible().catch(() => false)) {\n await passwordInput.fill(creds.password);\n await passwordInput.press('Enter');\n } else {\n const submitButton = page.getByRole('button', { name: /login|sign in|continue with sso/i }).first();\n await submitButton.click();\n }\n\n if (await waitForBackend(7_000)) return;\n\n const loginForm = page.locator('form').first();\n if (await loginForm.isVisible().catch(() => false)) {\n await loginForm.evaluate((element) => {\n const form = element as HTMLFormElement\n form.requestSubmit()\n }).catch(() => {})\n }\n if (await waitForBackend(5_000)) return;\n\n const loginButton = page.getByRole('button', { name: /login|sign in|continue with sso/i }).first();\n if (await loginButton.isVisible().catch(() => false)) {\n await loginButton.click({ force: true });\n }\n if (await waitForBackend(8_000)) return;\n\n throw new Error(`Login did not reach backend for role: ${role}; current URL: ${page.url()}`);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["resolve"]
7
7
  }
@@ -30,7 +30,7 @@ async function createUserViaUi(page, input) {
30
30
  await rolesInput.press("Enter");
31
31
  await page.getByRole("button", { name: "Create" }).first().click();
32
32
  await expect(page).toHaveURL(/\/backend\/users(?:\?.*)?$/);
33
- await page.getByRole("textbox", { name: "Search", exact: true }).fill(input.email);
33
+ await page.getByRole("searchbox", { name: "Search", exact: true }).fill(input.email);
34
34
  await expect(page.getByRole("row", { name: new RegExp(input.email, "i") })).toBeVisible();
35
35
  }
36
36
  export {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/helpers/integration/authUi.ts"],
4
- "sourcesContent": ["import { expect, type Page } from '@playwright/test';\n\nexport async function createUserViaUi(page: Page, input: { email: string; password: string; role?: string }) {\n const role = input.role ?? 'employee';\n\n await page.goto('/backend/users/create');\n await expect(page.getByText('Create User')).toBeVisible();\n\n const emailInput = page.locator('[data-crud-field-id=\"email\"] input').first();\n const nameInput = page.locator('[data-crud-field-id=\"name\"] input').first();\n const passwordInput = page.locator('[data-crud-field-id=\"password\"] input').first();\n\n await expect(emailInput).toBeVisible();\n await emailInput.fill(input.email);\n if (await nameInput.count()) {\n await nameInput.fill('');\n }\n await expect(passwordInput).toBeVisible();\n await passwordInput.fill(input.password);\n\n const orgSelect = page.locator('main').locator('select').first();\n await expect(orgSelect).toBeEnabled();\n const orgValue = await orgSelect.evaluate((element) => {\n const select = element as HTMLSelectElement;\n for (const option of Array.from(select.options)) {\n if (option.value && option.value.trim().length > 0) return option.value;\n }\n return '';\n });\n if (orgValue) {\n await orgSelect.selectOption(orgValue);\n }\n\n const rolesInput = page.getByRole('textbox', { name: /add tag and press enter/i });\n await rolesInput.fill(role);\n await rolesInput.press('Enter');\n\n await page.getByRole('button', { name: 'Create' }).first().click();\n await expect(page).toHaveURL(/\\/backend\\/users(?:\\?.*)?$/);\n await page.getByRole('textbox', { name: 'Search', exact: true }).fill(input.email);\n await expect(page.getByRole('row', { name: new RegExp(input.email, 'i') })).toBeVisible();\n}\n"],
5
- "mappings": "AAAA,SAAS,cAAyB;AAElC,eAAsB,gBAAgB,MAAY,OAA2D;AAC3G,QAAM,OAAO,MAAM,QAAQ;AAE3B,QAAM,KAAK,KAAK,uBAAuB;AACvC,QAAM,OAAO,KAAK,UAAU,aAAa,CAAC,EAAE,YAAY;AAExD,QAAM,aAAa,KAAK,QAAQ,oCAAoC,EAAE,MAAM;AAC5E,QAAM,YAAY,KAAK,QAAQ,mCAAmC,EAAE,MAAM;AAC1E,QAAM,gBAAgB,KAAK,QAAQ,uCAAuC,EAAE,MAAM;AAElF,QAAM,OAAO,UAAU,EAAE,YAAY;AACrC,QAAM,WAAW,KAAK,MAAM,KAAK;AACjC,MAAI,MAAM,UAAU,MAAM,GAAG;AAC3B,UAAM,UAAU,KAAK,EAAE;AAAA,EACzB;AACA,QAAM,OAAO,aAAa,EAAE,YAAY;AACxC,QAAM,cAAc,KAAK,MAAM,QAAQ;AAEvC,QAAM,YAAY,KAAK,QAAQ,MAAM,EAAE,QAAQ,QAAQ,EAAE,MAAM;AAC/D,QAAM,OAAO,SAAS,EAAE,YAAY;AACpC,QAAM,WAAW,MAAM,UAAU,SAAS,CAAC,YAAY;AACrD,UAAM,SAAS;AACf,eAAW,UAAU,MAAM,KAAK,OAAO,OAAO,GAAG;AAC/C,UAAI,OAAO,SAAS,OAAO,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,OAAO;AAAA,IACpE;AACA,WAAO;AAAA,EACT,CAAC;AACD,MAAI,UAAU;AACZ,UAAM,UAAU,aAAa,QAAQ;AAAA,EACvC;AAEA,QAAM,aAAa,KAAK,UAAU,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACjF,QAAM,WAAW,KAAK,IAAI;AAC1B,QAAM,WAAW,MAAM,OAAO;AAE9B,QAAM,KAAK,UAAU,UAAU,EAAE,MAAM,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM;AACjE,QAAM,OAAO,IAAI,EAAE,UAAU,4BAA4B;AACzD,QAAM,KAAK,UAAU,WAAW,EAAE,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE,KAAK,MAAM,KAAK;AACjF,QAAM,OAAO,KAAK,UAAU,OAAO,EAAE,MAAM,IAAI,OAAO,MAAM,OAAO,GAAG,EAAE,CAAC,CAAC,EAAE,YAAY;AAC1F;",
4
+ "sourcesContent": ["import { expect, type Page } from '@playwright/test';\n\nexport async function createUserViaUi(page: Page, input: { email: string; password: string; role?: string }) {\n const role = input.role ?? 'employee';\n\n await page.goto('/backend/users/create');\n await expect(page.getByText('Create User')).toBeVisible();\n\n const emailInput = page.locator('[data-crud-field-id=\"email\"] input').first();\n const nameInput = page.locator('[data-crud-field-id=\"name\"] input').first();\n const passwordInput = page.locator('[data-crud-field-id=\"password\"] input').first();\n\n await expect(emailInput).toBeVisible();\n await emailInput.fill(input.email);\n if (await nameInput.count()) {\n await nameInput.fill('');\n }\n await expect(passwordInput).toBeVisible();\n await passwordInput.fill(input.password);\n\n const orgSelect = page.locator('main').locator('select').first();\n await expect(orgSelect).toBeEnabled();\n const orgValue = await orgSelect.evaluate((element) => {\n const select = element as HTMLSelectElement;\n for (const option of Array.from(select.options)) {\n if (option.value && option.value.trim().length > 0) return option.value;\n }\n return '';\n });\n if (orgValue) {\n await orgSelect.selectOption(orgValue);\n }\n\n const rolesInput = page.getByRole('textbox', { name: /add tag and press enter/i });\n await rolesInput.fill(role);\n await rolesInput.press('Enter');\n\n await page.getByRole('button', { name: 'Create' }).first().click();\n await expect(page).toHaveURL(/\\/backend\\/users(?:\\?.*)?$/);\n await page.getByRole('searchbox', { name: 'Search', exact: true }).fill(input.email);\n await expect(page.getByRole('row', { name: new RegExp(input.email, 'i') })).toBeVisible();\n}\n"],
5
+ "mappings": "AAAA,SAAS,cAAyB;AAElC,eAAsB,gBAAgB,MAAY,OAA2D;AAC3G,QAAM,OAAO,MAAM,QAAQ;AAE3B,QAAM,KAAK,KAAK,uBAAuB;AACvC,QAAM,OAAO,KAAK,UAAU,aAAa,CAAC,EAAE,YAAY;AAExD,QAAM,aAAa,KAAK,QAAQ,oCAAoC,EAAE,MAAM;AAC5E,QAAM,YAAY,KAAK,QAAQ,mCAAmC,EAAE,MAAM;AAC1E,QAAM,gBAAgB,KAAK,QAAQ,uCAAuC,EAAE,MAAM;AAElF,QAAM,OAAO,UAAU,EAAE,YAAY;AACrC,QAAM,WAAW,KAAK,MAAM,KAAK;AACjC,MAAI,MAAM,UAAU,MAAM,GAAG;AAC3B,UAAM,UAAU,KAAK,EAAE;AAAA,EACzB;AACA,QAAM,OAAO,aAAa,EAAE,YAAY;AACxC,QAAM,cAAc,KAAK,MAAM,QAAQ;AAEvC,QAAM,YAAY,KAAK,QAAQ,MAAM,EAAE,QAAQ,QAAQ,EAAE,MAAM;AAC/D,QAAM,OAAO,SAAS,EAAE,YAAY;AACpC,QAAM,WAAW,MAAM,UAAU,SAAS,CAAC,YAAY;AACrD,UAAM,SAAS;AACf,eAAW,UAAU,MAAM,KAAK,OAAO,OAAO,GAAG;AAC/C,UAAI,OAAO,SAAS,OAAO,MAAM,KAAK,EAAE,SAAS,EAAG,QAAO,OAAO;AAAA,IACpE;AACA,WAAO;AAAA,EACT,CAAC;AACD,MAAI,UAAU;AACZ,UAAM,UAAU,aAAa,QAAQ;AAAA,EACvC;AAEA,QAAM,aAAa,KAAK,UAAU,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACjF,QAAM,WAAW,KAAK,IAAI;AAC1B,QAAM,WAAW,MAAM,OAAO;AAE9B,QAAM,KAAK,UAAU,UAAU,EAAE,MAAM,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM;AACjE,QAAM,OAAO,IAAI,EAAE,UAAU,4BAA4B;AACzD,QAAM,KAAK,UAAU,aAAa,EAAE,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE,KAAK,MAAM,KAAK;AACnF,QAAM,OAAO,KAAK,UAAU,OAAO,EAAE,MAAM,IAAI,OAAO,MAAM,OAAO,GAAG,EAAE,CAAC,CAAC,EAAE,YAAY;AAC1F;",
6
6
  "names": []
7
7
  }
@@ -5,7 +5,8 @@ import Image from "next/image";
5
5
  import Link from "next/link";
6
6
  import { useRouter, useSearchParams } from "next/navigation";
7
7
  import { Card, CardContent, CardHeader, CardDescription } from "@open-mercato/ui/primitives/card";
8
- import { Input } from "@open-mercato/ui/primitives/input";
8
+ import { EmailInput } from "@open-mercato/ui/primitives/email-input";
9
+ import { PasswordInput } from "@open-mercato/ui/primitives/password-input";
9
10
  import { Label } from "@open-mercato/ui/primitives/label";
10
11
  import { Button } from "@open-mercato/ui/primitives/button";
11
12
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -290,11 +291,10 @@ function LoginPage() {
290
291
  /* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
291
292
  /* @__PURE__ */ jsx(Label, { htmlFor: "email", children: t("auth.email") }),
292
293
  /* @__PURE__ */ jsx(
293
- Input,
294
+ EmailInput,
294
295
  {
295
296
  id: "email",
296
297
  name: "email",
297
- type: "email",
298
298
  required: true,
299
299
  "aria-invalid": !!error,
300
300
  onChange: (e) => setEmail(e.target.value),
@@ -311,7 +311,7 @@ function LoginPage() {
311
311
  ),
312
312
  authOverride?.hidePassword ? null : /* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
313
313
  /* @__PURE__ */ jsx(Label, { htmlFor: "password", children: t("auth.password") }),
314
- /* @__PURE__ */ jsx(Input, { id: "password", name: "password", type: "password", required: !authOverride, "aria-invalid": !!error })
314
+ /* @__PURE__ */ jsx(PasswordInput, { id: "password", name: "password", required: !authOverride, "aria-invalid": !!error, autoComplete: "current-password" })
315
315
  ] }),
316
316
  !authOverride?.hideRememberMe && !authOverride?.hidePassword && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
317
317
  /* @__PURE__ */ jsx("input", { type: "checkbox", name: "remember", className: "accent-foreground" }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/frontend/login.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Card, CardContent, CardHeader, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { clearAllOperations } from '@open-mercato/ui/backend/operations/store'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { X } from 'lucide-react'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'\nimport type { AuthOverride, LoginFormWidgetContext } from './login-injection'\n\nconst loginTenantKey = 'om_login_tenant'\nconst loginTenantCookieMaxAge = 60 * 60 * 24 * 14\n\nfunction readTenantCookie() {\n if (typeof document === 'undefined') return null\n const entries = document.cookie.split(';')\n for (const entry of entries) {\n const [name, ...rest] = entry.trim().split('=')\n if (name === loginTenantKey) return decodeURIComponent(rest.join('='))\n }\n return null\n}\n\nfunction setTenantCookie(value: string) {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=${encodeURIComponent(value)}; path=/; max-age=${loginTenantCookieMaxAge}; samesite=lax`\n}\n\nfunction clearTenantCookie() {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=; path=/; max-age=0; samesite=lax`\n}\n\nfunction extractErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const entry of payload) {\n const resolved = extractErrorMessage(entry)\n if (resolved) return resolved\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n const candidates: unknown[] = [\n record.error,\n record.message,\n record.detail,\n record.details,\n record.description,\n ]\n for (const candidate of candidates) {\n const resolved = extractErrorMessage(candidate)\n if (resolved) return resolved\n }\n }\n return null\n}\n\nfunction looksLikeJsonString(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.startsWith('{') || trimmed.startsWith('[')\n}\n\ntype LoginResponseEventDetail = Record<string, unknown> | null\n\ntype LoginFormSectionProps = {\n children: ReactNode\n}\n\nfunction LoginFormSectionDefault({ children }: LoginFormSectionProps) {\n return <>{children}</>\n}\n\nfunction emitLoginResponseEvent(detail: LoginResponseEventDetail) {\n if (typeof window === 'undefined') return\n window.dispatchEvent(new CustomEvent('om:auth:login-response', { detail }))\n}\n\nexport default function LoginPage() {\n const t = useT()\n const translate = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n const router = useRouter()\n const searchParams = useSearchParams()\n const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()\n const requireFeature = (searchParams.get('requireFeature') || '').trim()\n const requiredRoles = requireRole ? requireRole.split(',').map((value) => value.trim()).filter(Boolean) : []\n const requiredFeatures = requireFeature ? requireFeature.split(',').map((value) => value.trim()).filter(Boolean) : []\n const translatedRoles = requiredRoles.map((role) => translate(`auth.roles.${role}`, role))\n const translatedFeatures = requiredFeatures.map((feature) => translate(`features.${feature}`, feature))\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [authOverride, setAuthOverride] = useState<AuthOverride | null>(null)\n const [authOverridePending, setAuthOverridePending] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const [email, setEmail] = useState('')\n const [tenantId, setTenantId] = useState<string | null>(null)\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [tenantLoading, setTenantLoading] = useState(false)\n const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)\n const showTenantInvalid = tenantId != null && tenantInvalid === tenantId\n const LoginFormSection = useRegisteredComponent<LoginFormSectionProps>(\n 'section:auth.login.form',\n LoginFormSectionDefault,\n )\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n useEffect(() => {\n const tenantParam = (searchParams.get('tenant') || '').trim()\n if (tenantParam) {\n setTenantId(tenantParam)\n window.localStorage.setItem(loginTenantKey, tenantParam)\n setTenantCookie(tenantParam)\n return\n }\n const storedTenant = window.localStorage.getItem(loginTenantKey) || readTenantCookie()\n if (storedTenant) {\n setTenantId(storedTenant)\n }\n }, [searchParams])\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n setTenantInvalid(null)\n return\n }\n if (tenantInvalid === tenantId) {\n setTenantName(null)\n setTenantLoading(false)\n return\n }\n let active = true\n setTenantLoading(true)\n setTenantInvalid(null)\n apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n if (result?.ok && result.tenant) {\n setTenantName(result.tenant.name)\n return\n }\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .finally(() => {\n if (active) setTenantLoading(false)\n })\n return () => {\n active = false\n }\n }, [tenantId, translate])\n\n function handleClearTenant() {\n window.localStorage.removeItem(loginTenantKey)\n clearTenantCookie()\n setTenantId(null)\n setTenantName(null)\n setTenantInvalid(null)\n const params = new URLSearchParams(searchParams)\n params.delete('tenant')\n setError(null)\n const query = params.toString()\n router.replace(query ? `/login?${query}` : '/login')\n }\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n if (!clientReady || authOverridePending) {\n return\n }\n setError(null)\n if (authOverride) {\n authOverride.onSubmit()\n return\n }\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n if (requiredRoles.length) form.set('requireRole', requiredRoles.join(','))\n const redirectParam = searchParams.get('redirect')\n if (redirectParam) form.set('redirect', redirectParam)\n const res = await fetch('/api/auth/login', { method: 'POST', body: form })\n if (res.redirected) {\n clearAllOperations()\n // NextResponse.redirect from API\n router.replace(res.url)\n return\n }\n if (!res.ok) {\n const fallback = (() => {\n if (res.status === 403) {\n return translate(\n 'auth.login.errors.permissionDenied',\n 'You do not have permission to access this area. Please contact your administrator.',\n )\n }\n if (res.status === 401 || res.status === 400) {\n return translate('auth.login.errors.invalidCredentials', 'Invalid email or password')\n }\n return translate('auth.login.errors.generic', 'An error occurred. Please try again.')\n })()\n const cloned = res.clone()\n let errorMessage = ''\n const contentType = res.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n try {\n const data = await res.json()\n errorMessage = extractErrorMessage(data) || ''\n } catch {\n try {\n const text = await cloned.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n } else {\n try {\n const text = await res.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n setError(errorMessage || fallback)\n return\n }\n // In case API returns 200 with JSON\n const data = await res.json().catch(() => null) as LoginResponseEventDetail\n emitLoginResponseEvent(data)\n clearAllOperations()\n if (data && typeof data.redirect === 'string' && data.redirect.length > 0) {\n router.replace(data.redirect)\n }\n } catch (err: unknown) {\n // Handle any errors thrown (e.g., network errors or thrown exceptions)\n const message = err instanceof Error ? err.message : ''\n setError(message || translate('auth.login.errors.generic', 'An error occurred. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n }\n\n const loginFormContext = useMemo<LoginFormWidgetContext>(() => ({\n email,\n tenantId,\n searchParams,\n setAuthOverride,\n setAuthOverridePending,\n setError,\n }), [email, tenantId, searchParams])\n\n const formReady = clientReady && !authOverridePending\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader className=\"flex flex-col items-center gap-4 text-center p-10\">\n <Image alt={translate('auth.login.logoAlt', 'Open Mercato logo')} src=\"/open-mercato.svg\" width={150} height={150} priority />\n <h1 className=\"text-2xl font-semibold\">{translate('auth.login.brandName', 'Open Mercato')}</h1>\n <CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>\n </CardHeader>\n <CardContent>\n <LoginFormSection>\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate data-auth-ready={formReady ? '1' : '0'}>\n {tenantId ? (\n <input type=\"hidden\" name=\"tenantId\" value={tenantId} />\n ) : null}\n {!!translatedRoles.length && (\n <Alert variant=\"info\" className=\"text-center\">\n <AlertDescription>\n {translate(\n translatedRoles.length > 1 ? 'auth.login.requireRolesMessage' : 'auth.login.requireRoleMessage',\n translatedRoles.length > 1\n ? 'Access requires one of the following roles: {roles}'\n : 'Access requires role: {roles}',\n { roles: translatedRoles.join(', ') },\n )}\n </AlertDescription>\n </Alert>\n )}\n {!!translatedFeatures.length && (\n <Alert variant=\"info\" className=\"text-center\">\n <AlertDescription>\n {translate('auth.login.featureDenied', \"You don't have access to this feature ({feature}). Please contact your administrator.\", {\n feature: translatedFeatures.join(', '),\n })}\n </AlertDescription>\n </Alert>\n )}\n {showTenantInvalid ? (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700\">\n <div className=\"font-medium\">{translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')}</div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-red-300 text-red-700\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : tenantId ? (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900\">\n <div className=\"font-medium\">\n {tenantLoading\n ? translate('auth.login.tenantLoading', 'Loading tenant details...')\n : translate('auth.login.tenantBanner', \"You're logging in to {tenant} tenant.\", {\n tenant: tenantName || tenantId,\n })}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-emerald-300 text-emerald-900\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : null}\n {error && !showTenantInvalid && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700\" role=\"alert\" aria-live=\"polite\">\n {error}\n </div>\n )}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <Input\n id=\"email\"\n name=\"email\"\n type=\"email\"\n required\n aria-invalid={!!error}\n onChange={(e) => setEmail(e.target.value)}\n onBlur={(e) => setEmail(e.target.value)}\n />\n </div>\n <InjectionSpot<LoginFormWidgetContext>\n spotId=\"auth.login:form\"\n context={loginFormContext}\n />\n {authOverride?.hidePassword ? null : (\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.password')}</Label>\n <Input id=\"password\" name=\"password\" type=\"password\" required={!authOverride} aria-invalid={!!error} />\n </div>\n )}\n {!authOverride?.hideRememberMe && !authOverride?.hidePassword && (\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input type=\"checkbox\" name=\"remember\" className=\"accent-foreground\" />\n <span>{translate('auth.login.rememberMe', 'Remember me')}</span>\n </label>\n )}\n <Button type=\"submit\" disabled={submitting || !formReady} className=\"h-10 mt-2\">\n {submitting\n ? translate('auth.login.loading', 'Loading...')\n : authOverride\n ? authOverride.providerLabel\n : translate('auth.signIn', 'Sign in')}\n </Button>\n {!authOverride?.hideForgotPassword && (\n <div className=\"text-xs text-muted-foreground mt-2\">\n <Link className=\"underline\" href=\"/reset\">\n {translate('auth.login.forgotPassword', 'Forgot password?')}\n </Link>\n </div>\n )}\n </form>\n </LoginFormSection>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { ReactNode } from 'react'\nimport Image from 'next/image'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { Card, CardContent, CardHeader, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { EmailInput } from '@open-mercato/ui/primitives/email-input'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { clearAllOperations } from '@open-mercato/ui/backend/operations/store'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { X } from 'lucide-react'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'\nimport type { AuthOverride, LoginFormWidgetContext } from './login-injection'\n\nconst loginTenantKey = 'om_login_tenant'\nconst loginTenantCookieMaxAge = 60 * 60 * 24 * 14\n\nfunction readTenantCookie() {\n if (typeof document === 'undefined') return null\n const entries = document.cookie.split(';')\n for (const entry of entries) {\n const [name, ...rest] = entry.trim().split('=')\n if (name === loginTenantKey) return decodeURIComponent(rest.join('='))\n }\n return null\n}\n\nfunction setTenantCookie(value: string) {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=${encodeURIComponent(value)}; path=/; max-age=${loginTenantCookieMaxAge}; samesite=lax`\n}\n\nfunction clearTenantCookie() {\n if (typeof document === 'undefined') return\n document.cookie = `${loginTenantKey}=; path=/; max-age=0; samesite=lax`\n}\n\nfunction extractErrorMessage(payload: unknown): string | null {\n if (!payload) return null\n if (typeof payload === 'string') return payload\n if (Array.isArray(payload)) {\n for (const entry of payload) {\n const resolved = extractErrorMessage(entry)\n if (resolved) return resolved\n }\n return null\n }\n if (typeof payload === 'object') {\n const record = payload as Record<string, unknown>\n const candidates: unknown[] = [\n record.error,\n record.message,\n record.detail,\n record.details,\n record.description,\n ]\n for (const candidate of candidates) {\n const resolved = extractErrorMessage(candidate)\n if (resolved) return resolved\n }\n }\n return null\n}\n\nfunction looksLikeJsonString(value: string): boolean {\n const trimmed = value.trim()\n return trimmed.startsWith('{') || trimmed.startsWith('[')\n}\n\ntype LoginResponseEventDetail = Record<string, unknown> | null\n\ntype LoginFormSectionProps = {\n children: ReactNode\n}\n\nfunction LoginFormSectionDefault({ children }: LoginFormSectionProps) {\n return <>{children}</>\n}\n\nfunction emitLoginResponseEvent(detail: LoginResponseEventDetail) {\n if (typeof window === 'undefined') return\n window.dispatchEvent(new CustomEvent('om:auth:login-response', { detail }))\n}\n\nexport default function LoginPage() {\n const t = useT()\n const translate = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n const router = useRouter()\n const searchParams = useSearchParams()\n const requireRole = (searchParams.get('requireRole') || searchParams.get('role') || '').trim()\n const requireFeature = (searchParams.get('requireFeature') || '').trim()\n const requiredRoles = requireRole ? requireRole.split(',').map((value) => value.trim()).filter(Boolean) : []\n const requiredFeatures = requireFeature ? requireFeature.split(',').map((value) => value.trim()).filter(Boolean) : []\n const translatedRoles = requiredRoles.map((role) => translate(`auth.roles.${role}`, role))\n const translatedFeatures = requiredFeatures.map((feature) => translate(`features.${feature}`, feature))\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [authOverride, setAuthOverride] = useState<AuthOverride | null>(null)\n const [authOverridePending, setAuthOverridePending] = useState(false)\n const [clientReady, setClientReady] = useState(false)\n const [email, setEmail] = useState('')\n const [tenantId, setTenantId] = useState<string | null>(null)\n const [tenantName, setTenantName] = useState<string | null>(null)\n const [tenantLoading, setTenantLoading] = useState(false)\n const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)\n const showTenantInvalid = tenantId != null && tenantInvalid === tenantId\n const LoginFormSection = useRegisteredComponent<LoginFormSectionProps>(\n 'section:auth.login.form',\n LoginFormSectionDefault,\n )\n\n useEffect(() => {\n setClientReady(true)\n }, [])\n\n useEffect(() => {\n const tenantParam = (searchParams.get('tenant') || '').trim()\n if (tenantParam) {\n setTenantId(tenantParam)\n window.localStorage.setItem(loginTenantKey, tenantParam)\n setTenantCookie(tenantParam)\n return\n }\n const storedTenant = window.localStorage.getItem(loginTenantKey) || readTenantCookie()\n if (storedTenant) {\n setTenantId(storedTenant)\n }\n }, [searchParams])\n\n useEffect(() => {\n if (!tenantId) {\n setTenantName(null)\n setTenantInvalid(null)\n return\n }\n if (tenantInvalid === tenantId) {\n setTenantName(null)\n setTenantLoading(false)\n return\n }\n let active = true\n setTenantLoading(true)\n setTenantInvalid(null)\n apiCall<{ ok: boolean; tenant?: { id: string; name: string }; error?: string }>(\n `/api/directory/tenants/lookup?tenantId=${encodeURIComponent(tenantId)}`,\n )\n .then(({ result }) => {\n if (!active) return\n if (result?.ok && result.tenant) {\n setTenantName(result.tenant.name)\n return\n }\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .catch(() => {\n if (!active) return\n setTenantName(null)\n setTenantInvalid(tenantId)\n setError(null)\n })\n .finally(() => {\n if (active) setTenantLoading(false)\n })\n return () => {\n active = false\n }\n }, [tenantId, translate])\n\n function handleClearTenant() {\n window.localStorage.removeItem(loginTenantKey)\n clearTenantCookie()\n setTenantId(null)\n setTenantName(null)\n setTenantInvalid(null)\n const params = new URLSearchParams(searchParams)\n params.delete('tenant')\n setError(null)\n const query = params.toString()\n router.replace(query ? `/login?${query}` : '/login')\n }\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n if (!clientReady || authOverridePending) {\n return\n }\n setError(null)\n if (authOverride) {\n authOverride.onSubmit()\n return\n }\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n if (requiredRoles.length) form.set('requireRole', requiredRoles.join(','))\n const redirectParam = searchParams.get('redirect')\n if (redirectParam) form.set('redirect', redirectParam)\n const res = await fetch('/api/auth/login', { method: 'POST', body: form })\n if (res.redirected) {\n clearAllOperations()\n // NextResponse.redirect from API\n router.replace(res.url)\n return\n }\n if (!res.ok) {\n const fallback = (() => {\n if (res.status === 403) {\n return translate(\n 'auth.login.errors.permissionDenied',\n 'You do not have permission to access this area. Please contact your administrator.',\n )\n }\n if (res.status === 401 || res.status === 400) {\n return translate('auth.login.errors.invalidCredentials', 'Invalid email or password')\n }\n return translate('auth.login.errors.generic', 'An error occurred. Please try again.')\n })()\n const cloned = res.clone()\n let errorMessage = ''\n const contentType = res.headers.get('content-type') || ''\n if (contentType.includes('application/json')) {\n try {\n const data = await res.json()\n errorMessage = extractErrorMessage(data) || ''\n } catch {\n try {\n const text = await cloned.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n } else {\n try {\n const text = await res.text()\n const trimmed = text.trim()\n if (trimmed && !looksLikeJsonString(trimmed)) {\n errorMessage = trimmed\n }\n } catch {\n errorMessage = ''\n }\n }\n setError(errorMessage || fallback)\n return\n }\n // In case API returns 200 with JSON\n const data = await res.json().catch(() => null) as LoginResponseEventDetail\n emitLoginResponseEvent(data)\n clearAllOperations()\n if (data && typeof data.redirect === 'string' && data.redirect.length > 0) {\n router.replace(data.redirect)\n }\n } catch (err: unknown) {\n // Handle any errors thrown (e.g., network errors or thrown exceptions)\n const message = err instanceof Error ? err.message : ''\n setError(message || translate('auth.login.errors.generic', 'An error occurred. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n }\n\n const loginFormContext = useMemo<LoginFormWidgetContext>(() => ({\n email,\n tenantId,\n searchParams,\n setAuthOverride,\n setAuthOverridePending,\n setError,\n }), [email, tenantId, searchParams])\n\n const formReady = clientReady && !authOverridePending\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader className=\"flex flex-col items-center gap-4 text-center p-10\">\n <Image alt={translate('auth.login.logoAlt', 'Open Mercato logo')} src=\"/open-mercato.svg\" width={150} height={150} priority />\n <h1 className=\"text-2xl font-semibold\">{translate('auth.login.brandName', 'Open Mercato')}</h1>\n <CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>\n </CardHeader>\n <CardContent>\n <LoginFormSection>\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate data-auth-ready={formReady ? '1' : '0'}>\n {tenantId ? (\n <input type=\"hidden\" name=\"tenantId\" value={tenantId} />\n ) : null}\n {!!translatedRoles.length && (\n <Alert variant=\"info\" className=\"text-center\">\n <AlertDescription>\n {translate(\n translatedRoles.length > 1 ? 'auth.login.requireRolesMessage' : 'auth.login.requireRoleMessage',\n translatedRoles.length > 1\n ? 'Access requires one of the following roles: {roles}'\n : 'Access requires role: {roles}',\n { roles: translatedRoles.join(', ') },\n )}\n </AlertDescription>\n </Alert>\n )}\n {!!translatedFeatures.length && (\n <Alert variant=\"info\" className=\"text-center\">\n <AlertDescription>\n {translate('auth.login.featureDenied', \"You don't have access to this feature ({feature}). Please contact your administrator.\", {\n feature: translatedFeatures.join(', '),\n })}\n </AlertDescription>\n </Alert>\n )}\n {showTenantInvalid ? (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-xs text-red-700\">\n <div className=\"font-medium\">{translate('auth.login.errors.tenantInvalid', 'Tenant not found. Clear the tenant selection and try again.')}</div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-red-300 text-red-700\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : tenantId ? (\n <div className=\"rounded-md border border-emerald-200 bg-emerald-50 px-3 py-2 text-center text-xs text-emerald-900\">\n <div className=\"font-medium\">\n {tenantLoading\n ? translate('auth.login.tenantLoading', 'Loading tenant details...')\n : translate('auth.login.tenantBanner', \"You're logging in to {tenant} tenant.\", {\n tenant: tenantName || tenantId,\n })}\n </div>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"mt-2 border-emerald-300 text-emerald-900\" onClick={handleClearTenant}>\n <X className=\"mr-2 size-4\" aria-hidden=\"true\" />\n {translate('auth.login.tenantClear', 'Clear')}\n </Button>\n </div>\n ) : null}\n {error && !showTenantInvalid && (\n <div className=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-center text-sm text-red-700\" role=\"alert\" aria-live=\"polite\">\n {error}\n </div>\n )}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <EmailInput\n id=\"email\"\n name=\"email\"\n required\n aria-invalid={!!error}\n onChange={(e) => setEmail(e.target.value)}\n onBlur={(e) => setEmail(e.target.value)}\n />\n </div>\n <InjectionSpot<LoginFormWidgetContext>\n spotId=\"auth.login:form\"\n context={loginFormContext}\n />\n {authOverride?.hidePassword ? null : (\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.password')}</Label>\n <PasswordInput id=\"password\" name=\"password\" required={!authOverride} aria-invalid={!!error} autoComplete=\"current-password\" />\n </div>\n )}\n {!authOverride?.hideRememberMe && !authOverride?.hidePassword && (\n <label className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <input type=\"checkbox\" name=\"remember\" className=\"accent-foreground\" />\n <span>{translate('auth.login.rememberMe', 'Remember me')}</span>\n </label>\n )}\n <Button type=\"submit\" disabled={submitting || !formReady} className=\"h-10 mt-2\">\n {submitting\n ? translate('auth.login.loading', 'Loading...')\n : authOverride\n ? authOverride.providerLabel\n : translate('auth.signIn', 'Sign in')}\n </Button>\n {!authOverride?.hideForgotPassword && (\n <div className=\"text-xs text-muted-foreground mt-2\">\n <Link className=\"underline\" href=\"/reset\">\n {translate('auth.login.forgotPassword', 'Forgot password?')}\n </Link>\n </div>\n )}\n </form>\n </LoginFormSection>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["data"]
7
7
  }
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@open-mercato/ui/primitives/card";
4
4
  import { Button } from "@open-mercato/ui/primitives/button";
5
- import { Input } from "@open-mercato/ui/primitives/input";
5
+ import { PasswordInput } from "@open-mercato/ui/primitives/password-input";
6
6
  import { Label } from "@open-mercato/ui/primitives/label";
7
7
  import { useState } from "react";
8
8
  import { useRouter } from "next/navigation";
@@ -60,19 +60,19 @@ function ResetWithTokenPage({ params }) {
60
60
  error && /* @__PURE__ */ jsx("div", { className: "text-sm text-status-error-text", children: error }),
61
61
  /* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
62
62
  /* @__PURE__ */ jsx(Label, { htmlFor: "password", children: t("auth.reset.form.password", "New password") }),
63
- /* @__PURE__ */ jsx(Input, { id: "password", name: "password", type: "password", required: true, minLength: passwordPolicy.minLength }),
63
+ /* @__PURE__ */ jsx(PasswordInput, { id: "password", name: "password", required: true, minLength: passwordPolicy.minLength, autoComplete: "new-password" }),
64
64
  passwordDescription ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: passwordDescription }) : null
65
65
  ] }),
66
66
  /* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
67
67
  /* @__PURE__ */ jsx(Label, { htmlFor: "confirmPassword", children: t("auth.profile.form.confirmPassword", "Confirm new password") }),
68
68
  /* @__PURE__ */ jsx(
69
- Input,
69
+ PasswordInput,
70
70
  {
71
71
  id: "confirmPassword",
72
72
  name: "confirmPassword",
73
- type: "password",
74
73
  required: true,
75
- minLength: passwordPolicy.minLength
74
+ minLength: passwordPolicy.minLength,
75
+ autoComplete: "new-password"
76
76
  }
77
77
  )
78
78
  ] }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/auth/frontend/reset/%5Btoken%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\nexport default function ResetWithTokenPage({ params }: { params: { token: string } }) {\n const router = useRouter()\n const t = useT()\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const passwordPolicy = getPasswordPolicy()\n const passwordRequirements = formatPasswordRequirements(passwordPolicy, t)\n const passwordDescription = passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : ''\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n const form = new FormData(e.currentTarget)\n const password = String(form.get('password') ?? '')\n const confirmPassword = String(form.get('confirmPassword') ?? '')\n\n if (!password) {\n setError(t('auth.profile.form.errors.newPasswordRequired', 'New password is required.'))\n return\n }\n if (!confirmPassword) {\n setError(t('auth.profile.form.errors.confirmPasswordRequired', 'Please confirm the new password.'))\n return\n }\n if (password !== confirmPassword) {\n setError(t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'))\n return\n }\n\n setSubmitting(true)\n try {\n form.set('token', params.token)\n const { ok, result } = await apiCall<{ ok?: boolean; error?: string; redirect?: string }>(\n '/api/auth/reset/confirm',\n { method: 'POST', body: form },\n )\n if (!ok || result?.ok === false) {\n setError(result?.error || t('auth.reset.errors.failed', 'Unable to reset password'))\n return\n }\n router.replace(result?.redirect || '/login')\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.reset.title', 'Set a new password')}</CardTitle>\n <CardDescription>{t('auth.reset.subtitle', 'Choose a strong password for your account.')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit}>\n {error && <div className=\"text-sm text-status-error-text\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.reset.form.password', 'New password')}</Label>\n <Input id=\"password\" name=\"password\" type=\"password\" required minLength={passwordPolicy.minLength} />\n {passwordDescription ? (\n <p className=\"text-xs text-muted-foreground\">{passwordDescription}</p>\n ) : null}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"confirmPassword\">{t('auth.profile.form.confirmPassword', 'Confirm new password')}</Label>\n <Input\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n type=\"password\"\n required\n minLength={passwordPolicy.minLength}\n />\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? t('auth.reset.form.loading', '...') : t('auth.reset.form.submit', 'Update password')}\n </Button>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\nexport default function ResetWithTokenPage({ params }: { params: { token: string } }) {\n const router = useRouter()\n const t = useT()\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const passwordPolicy = getPasswordPolicy()\n const passwordRequirements = formatPasswordRequirements(passwordPolicy, t)\n const passwordDescription = passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : ''\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n const form = new FormData(e.currentTarget)\n const password = String(form.get('password') ?? '')\n const confirmPassword = String(form.get('confirmPassword') ?? '')\n\n if (!password) {\n setError(t('auth.profile.form.errors.newPasswordRequired', 'New password is required.'))\n return\n }\n if (!confirmPassword) {\n setError(t('auth.profile.form.errors.confirmPasswordRequired', 'Please confirm the new password.'))\n return\n }\n if (password !== confirmPassword) {\n setError(t('auth.profile.form.errors.passwordMismatch', 'Passwords do not match.'))\n return\n }\n\n setSubmitting(true)\n try {\n form.set('token', params.token)\n const { ok, result } = await apiCall<{ ok?: boolean; error?: string; redirect?: string }>(\n '/api/auth/reset/confirm',\n { method: 'POST', body: form },\n )\n if (!ok || result?.ok === false) {\n setError(result?.error || t('auth.reset.errors.failed', 'Unable to reset password'))\n return\n }\n router.replace(result?.redirect || '/login')\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.reset.title', 'Set a new password')}</CardTitle>\n <CardDescription>{t('auth.reset.subtitle', 'Choose a strong password for your account.')}</CardDescription>\n </CardHeader>\n <CardContent>\n <form className=\"grid gap-3\" onSubmit={onSubmit}>\n {error && <div className=\"text-sm text-status-error-text\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"password\">{t('auth.reset.form.password', 'New password')}</Label>\n <PasswordInput id=\"password\" name=\"password\" required minLength={passwordPolicy.minLength} autoComplete=\"new-password\" />\n {passwordDescription ? (\n <p className=\"text-xs text-muted-foreground\">{passwordDescription}</p>\n ) : null}\n </div>\n <div className=\"grid gap-1\">\n <Label htmlFor=\"confirmPassword\">{t('auth.profile.form.confirmPassword', 'Confirm new password')}</Label>\n <PasswordInput\n id=\"confirmPassword\"\n name=\"confirmPassword\"\n required\n minLength={passwordPolicy.minLength}\n autoComplete=\"new-password\"\n />\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? t('auth.reset.form.loading', '...') : t('auth.reset.form.submit', 'Update password')}\n </Button>\n </form>\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,7 +4,7 @@ import { useState } from "react";
4
4
  import Link from "next/link";
5
5
  import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@open-mercato/ui/primitives/card";
6
6
  import { Button } from "@open-mercato/ui/primitives/button";
7
- import { Input } from "@open-mercato/ui/primitives/input";
7
+ import { EmailInput } from "@open-mercato/ui/primitives/email-input";
8
8
  import { Label } from "@open-mercato/ui/primitives/label";
9
9
  import { useT } from "@open-mercato/shared/lib/i18n/context";
10
10
  function ResetPage() {
@@ -47,7 +47,7 @@ function ResetPage() {
47
47
  error && /* @__PURE__ */ jsx("div", { className: "text-sm text-red-600", children: error }),
48
48
  /* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
49
49
  /* @__PURE__ */ jsx(Label, { htmlFor: "email", children: t("auth.email") }),
50
- /* @__PURE__ */ jsx(Input, { id: "email", name: "email", type: "email", required: true, "aria-invalid": !!fieldError, "aria-describedby": fieldError ? "email-error" : void 0 }),
50
+ /* @__PURE__ */ jsx(EmailInput, { id: "email", name: "email", required: true, "aria-invalid": !!fieldError, "aria-describedby": fieldError ? "email-error" : void 0 }),
51
51
  fieldError && /* @__PURE__ */ jsx("p", { id: "email-error", className: "text-sm text-red-600", children: fieldError })
52
52
  ] }),
53
53
  /* @__PURE__ */ jsx(Button, { type: "submit", className: "mt-2 w-full", disabled: submitting, children: submitting ? "..." : t("auth.sendResetLink") }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/frontend/reset.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useState } from 'react'\nimport Link from 'next/link'\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport default function ResetPage() {\n const t = useT()\n const [sent, setSent] = useState(false)\n const [submitting, setSubmitting] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [fieldError, setFieldError] = useState<string | null>(null)\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n setFieldError(null)\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n const res = await fetch('/api/auth/reset', { method: 'POST', body: form })\n if (!res.ok) {\n const data = await res.json().catch(() => null)\n if (data?.fieldErrors?.email?.length) {\n setFieldError(t('auth.reset.errors.emailInvalid', 'Please enter a valid email address.'))\n return\n }\n setError(data?.error || t('auth.reset.error', 'Something went wrong'))\n return\n }\n setSent(true)\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.resetPassword')}</CardTitle>\n <CardDescription>{t('auth.reset.description', 'Enter your email to receive reset link')}</CardDescription>\n </CardHeader>\n <CardContent>\n {sent ? (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-muted-foreground\">\n {t('auth.reset.sent', 'If an account with that email exists, we sent a reset link. Please check your inbox.')}\n </p>\n <Link href=\"/login\" className=\"text-sm underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n ) : (\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate>\n {error && <div className=\"text-sm text-red-600\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <Input id=\"email\" name=\"email\" type=\"email\" required aria-invalid={!!fieldError} aria-describedby={fieldError ? 'email-error' : undefined} />\n {fieldError && <p id=\"email-error\" className=\"text-sm text-red-600\">{fieldError}</p>}\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? '...' : t('auth.sendResetLink')}\n </Button>\n <div className=\"text-center\">\n <Link href=\"/login\" className=\"text-xs text-muted-foreground underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n </form>\n )}\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
- "mappings": ";AA0CQ,SACE,KADF;AAzCR,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,SAAS,MAAM,aAAa,YAAY,WAAW,uBAAuB;AAC1E,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,YAAY;AAEN,SAAR,YAA6B;AAClC,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAEhE,iBAAe,SAAS,GAAqC;AAC3D,MAAE,eAAe;AACjB,aAAS,IAAI;AACb,kBAAc,IAAI;AAClB,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,OAAO,IAAI,SAAS,EAAE,aAAa;AACzC,YAAM,MAAM,MAAM,MAAM,mBAAmB,EAAE,QAAQ,QAAQ,MAAM,KAAK,CAAC;AACzE,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAC9C,YAAI,MAAM,aAAa,OAAO,QAAQ;AACpC,wBAAc,EAAE,kCAAkC,qCAAqC,CAAC;AACxF;AAAA,QACF;AACA,iBAAS,MAAM,SAAS,EAAE,oBAAoB,sBAAsB,CAAC;AACrE;AAAA,MACF;AACA,cAAQ,IAAI;AAAA,IACd,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SACE,oBAAC,SAAI,WAAU,kDACb,+BAAC,QAAK,WAAU,mBACd;AAAA,yBAAC,cACC;AAAA,0BAAC,aAAW,YAAE,oBAAoB,GAAE;AAAA,MACpC,oBAAC,mBAAiB,YAAE,0BAA0B,wCAAwC,GAAE;AAAA,OAC1F;AAAA,IACA,oBAAC,eACE,iBACC,qBAAC,SAAI,WAAU,cACb;AAAA,0BAAC,OAAE,WAAU,iCACV,YAAE,mBAAmB,sFAAsF,GAC9G;AAAA,MACA,oBAAC,QAAK,MAAK,UAAS,WAAU,qBAC3B,YAAE,0BAA0B,eAAe,GAC9C;AAAA,OACF,IAEA,qBAAC,UAAK,WAAU,cAAa,UAAoB,YAAU,MACxD;AAAA,eAAS,oBAAC,SAAI,WAAU,wBAAwB,iBAAM;AAAA,MACvD,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,SAAQ,SAAS,YAAE,YAAY,GAAE;AAAA,QACxC,oBAAC,SAAM,IAAG,SAAQ,MAAK,SAAQ,MAAK,SAAQ,UAAQ,MAAC,gBAAc,CAAC,CAAC,YAAY,oBAAkB,aAAa,gBAAgB,QAAW;AAAA,QAC1I,cAAc,oBAAC,OAAE,IAAG,eAAc,WAAU,wBAAwB,sBAAW;AAAA,SAClF;AAAA,MACA,oBAAC,UAAO,MAAK,UAAS,WAAU,eAAc,UAAU,YACrD,uBAAa,QAAQ,EAAE,oBAAoB,GAC9C;AAAA,MACA,oBAAC,SAAI,WAAU,eACb,8BAAC,QAAK,MAAK,UAAS,WAAU,2CAC3B,YAAE,0BAA0B,eAAe,GAC9C,GACF;AAAA,OACF,GAEJ;AAAA,KACF,GACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\nimport { useState } from 'react'\nimport Link from 'next/link'\nimport { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@open-mercato/ui/primitives/card'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { EmailInput } from '@open-mercato/ui/primitives/email-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport default function ResetPage() {\n const t = useT()\n const [sent, setSent] = useState(false)\n const [submitting, setSubmitting] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [fieldError, setFieldError] = useState<string | null>(null)\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault()\n setError(null)\n setFieldError(null)\n setSubmitting(true)\n try {\n const form = new FormData(e.currentTarget)\n const res = await fetch('/api/auth/reset', { method: 'POST', body: form })\n if (!res.ok) {\n const data = await res.json().catch(() => null)\n if (data?.fieldErrors?.email?.length) {\n setFieldError(t('auth.reset.errors.emailInvalid', 'Please enter a valid email address.'))\n return\n }\n setError(data?.error || t('auth.reset.error', 'Something went wrong'))\n return\n }\n setSent(true)\n } finally {\n setSubmitting(false)\n }\n }\n\n return (\n <div className=\"min-h-svh flex items-center justify-center p-4\">\n <Card className=\"w-full max-w-sm\">\n <CardHeader>\n <CardTitle>{t('auth.resetPassword')}</CardTitle>\n <CardDescription>{t('auth.reset.description', 'Enter your email to receive reset link')}</CardDescription>\n </CardHeader>\n <CardContent>\n {sent ? (\n <div className=\"grid gap-3\">\n <p className=\"text-sm text-muted-foreground\">\n {t('auth.reset.sent', 'If an account with that email exists, we sent a reset link. Please check your inbox.')}\n </p>\n <Link href=\"/login\" className=\"text-sm underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n ) : (\n <form className=\"grid gap-3\" onSubmit={onSubmit} noValidate>\n {error && <div className=\"text-sm text-red-600\">{error}</div>}\n <div className=\"grid gap-1\">\n <Label htmlFor=\"email\">{t('auth.email')}</Label>\n <EmailInput id=\"email\" name=\"email\" required aria-invalid={!!fieldError} aria-describedby={fieldError ? 'email-error' : undefined} />\n {fieldError && <p id=\"email-error\" className=\"text-sm text-red-600\">{fieldError}</p>}\n </div>\n <Button type=\"submit\" className=\"mt-2 w-full\" disabled={submitting}>\n {submitting ? '...' : t('auth.sendResetLink')}\n </Button>\n <div className=\"text-center\">\n <Link href=\"/login\" className=\"text-xs text-muted-foreground underline\">\n {t('auth.reset.backToLogin', 'Back to login')}\n </Link>\n </div>\n </form>\n )}\n </CardContent>\n </Card>\n </div>\n )\n}\n"],
5
+ "mappings": ";AA0CQ,SACE,KADF;AAzCR,SAAS,gBAAgB;AACzB,OAAO,UAAU;AACjB,SAAS,MAAM,aAAa,YAAY,WAAW,uBAAuB;AAC1E,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,YAAY;AAEN,SAAR,YAA6B;AAClC,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,KAAK;AACtC,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AACtD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAEhE,iBAAe,SAAS,GAAqC;AAC3D,MAAE,eAAe;AACjB,aAAS,IAAI;AACb,kBAAc,IAAI;AAClB,kBAAc,IAAI;AAClB,QAAI;AACF,YAAM,OAAO,IAAI,SAAS,EAAE,aAAa;AACzC,YAAM,MAAM,MAAM,MAAM,mBAAmB,EAAE,QAAQ,QAAQ,MAAM,KAAK,CAAC;AACzE,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAC9C,YAAI,MAAM,aAAa,OAAO,QAAQ;AACpC,wBAAc,EAAE,kCAAkC,qCAAqC,CAAC;AACxF;AAAA,QACF;AACA,iBAAS,MAAM,SAAS,EAAE,oBAAoB,sBAAsB,CAAC;AACrE;AAAA,MACF;AACA,cAAQ,IAAI;AAAA,IACd,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SACE,oBAAC,SAAI,WAAU,kDACb,+BAAC,QAAK,WAAU,mBACd;AAAA,yBAAC,cACC;AAAA,0BAAC,aAAW,YAAE,oBAAoB,GAAE;AAAA,MACpC,oBAAC,mBAAiB,YAAE,0BAA0B,wCAAwC,GAAE;AAAA,OAC1F;AAAA,IACA,oBAAC,eACE,iBACC,qBAAC,SAAI,WAAU,cACb;AAAA,0BAAC,OAAE,WAAU,iCACV,YAAE,mBAAmB,sFAAsF,GAC9G;AAAA,MACA,oBAAC,QAAK,MAAK,UAAS,WAAU,qBAC3B,YAAE,0BAA0B,eAAe,GAC9C;AAAA,OACF,IAEA,qBAAC,UAAK,WAAU,cAAa,UAAoB,YAAU,MACxD;AAAA,eAAS,oBAAC,SAAI,WAAU,wBAAwB,iBAAM;AAAA,MACvD,qBAAC,SAAI,WAAU,cACb;AAAA,4BAAC,SAAM,SAAQ,SAAS,YAAE,YAAY,GAAE;AAAA,QACxC,oBAAC,cAAW,IAAG,SAAQ,MAAK,SAAQ,UAAQ,MAAC,gBAAc,CAAC,CAAC,YAAY,oBAAkB,aAAa,gBAAgB,QAAW;AAAA,QAClI,cAAc,oBAAC,OAAE,IAAG,eAAc,WAAU,wBAAwB,sBAAW;AAAA,SAClF;AAAA,MACA,oBAAC,UAAO,MAAK,UAAS,WAAU,eAAc,UAAU,YACrD,uBAAa,QAAQ,EAAE,oBAAoB,GAC9C;AAAA,MACA,oBAAC,SAAI,WAAU,eACb,8BAAC,QAAK,MAAK,UAAS,WAAU,2CAC3B,YAAE,0BAA0B,eAAe,GAC9C,GACF;AAAA,OACF,GAEJ;AAAA,KACF,GACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,7 @@ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
7
  import { FormHeader } from "@open-mercato/ui/backend/forms";
8
8
  import { Button } from "@open-mercato/ui/primitives/button";
9
9
  import { Input } from "@open-mercato/ui/primitives/input";
10
+ import { PasswordInput } from "@open-mercato/ui/primitives/password-input";
10
11
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
11
12
  import { SwitchField } from "@open-mercato/ui/primitives/switch-field";
12
13
  import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@open-mercato/ui/primitives/dialog";
@@ -80,15 +81,15 @@ function ResetPasswordDialog({
80
81
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
81
82
  /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", htmlFor: "reset-password", children: t("customer_accounts.admin.detail.resetPassword.fields.newPassword", "New Password") }),
82
83
  /* @__PURE__ */ jsx(
83
- Input,
84
+ PasswordInput,
84
85
  {
85
86
  id: "reset-password",
86
- type: "password",
87
87
  required: true,
88
88
  minLength: 8,
89
89
  value: newPassword,
90
90
  onChange: (event) => setNewPassword(event.target.value),
91
- placeholder: t("customer_accounts.admin.detail.resetPassword.fields.placeholder", "Min. 8 characters")
91
+ placeholder: t("customer_accounts.admin.detail.resetPassword.fields.placeholder", "Min. 8 characters"),
92
+ autoComplete: "new-password"
92
93
  }
93
94
  )
94
95
  ] }),