@rytass/bpm-core-react 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/chunks/FormBuilderView-CAqjOYAP.cjs +3 -0
- package/dist/chunks/{FormBuilderView-D6baVqBY.cjs.map → FormBuilderView-CAqjOYAP.cjs.map} +1 -1
- package/dist/chunks/{FormBuilderView-QtREqV4j.js → FormBuilderView-TzSezeqC.js} +18 -16
- package/dist/chunks/{FormBuilderView-QtREqV4j.js.map → FormBuilderView-TzSezeqC.js.map} +1 -1
- package/dist/chunks/{FormRendererView-D8QHLvd0.js → FormRendererView-BurlEDUu.js} +44 -41
- package/dist/chunks/{FormRendererView-D8QHLvd0.js.map → FormRendererView-BurlEDUu.js.map} +1 -1
- package/dist/chunks/FormRendererView-Dde1s3I0.cjs +2 -0
- package/dist/chunks/{FormRendererView-Cw9LiidV.cjs.map → FormRendererView-Dde1s3I0.cjs.map} +1 -1
- package/dist/chunks/admin-pickers-Btvij1at.cjs.map +1 -1
- package/dist/chunks/admin-pickers-DLlG_1du.js.map +1 -1
- package/dist/chunks/approval-instance-list-page-BjX2kaoF.js.map +1 -1
- package/dist/chunks/approval-instance-list-page-CWiQvnfE.cjs +2 -0
- package/dist/chunks/{approval-instance-list-page-C408vxE2.cjs.map → approval-instance-list-page-CWiQvnfE.cjs.map} +1 -1
- package/dist/chunks/auth-provider-4BeCw7cI.cjs.map +1 -1
- package/dist/chunks/auth-provider-B5oPmvk2.js.map +1 -1
- package/dist/chunks/bpm-form-field-Bc6k4ZEO.cjs.map +1 -1
- package/dist/chunks/categories-DGlxuC6X.js.map +1 -1
- package/dist/chunks/categories-Yid05WAX.cjs +2 -0
- package/dist/chunks/{categories-DUfldZ_9.cjs.map → categories-Yid05WAX.cjs.map} +1 -1
- package/dist/chunks/{compose-D-OS-Ngs.js → compose-CUOe5Ia9.js} +4 -4
- package/dist/chunks/{compose-D-OS-Ngs.js.map → compose-CUOe5Ia9.js.map} +1 -1
- package/dist/chunks/compose-CyRkXdeu.cjs +2 -0
- package/dist/chunks/{compose-CODnd9-2.cjs.map → compose-CyRkXdeu.cjs.map} +1 -1
- package/dist/chunks/dashboard-page-CH87L5nx.cjs +2 -0
- package/dist/chunks/{dashboard-page-DwHQY6Ki.cjs.map → dashboard-page-CH87L5nx.cjs.map} +1 -1
- package/dist/chunks/dashboard-page-DJ9vOPga.js.map +1 -1
- package/dist/chunks/{delegations-DLEEFBlH.js → delegations-0MbJ7CWj.js} +3 -2
- package/dist/chunks/{delegations-DLEEFBlH.js.map → delegations-0MbJ7CWj.js.map} +1 -1
- package/dist/chunks/delegations-Bc9psOZ3.cjs +2 -0
- package/dist/chunks/{delegations-CGj-tZNO.cjs.map → delegations-Bc9psOZ3.cjs.map} +1 -1
- package/dist/chunks/{delegations-DqMpI_Nk.js → delegations-DyMGIDpp.js} +3 -2
- package/dist/chunks/{delegations-DqMpI_Nk.js.map → delegations-DyMGIDpp.js.map} +1 -1
- package/dist/chunks/delegations-vpLtus9g.cjs +2 -0
- package/dist/chunks/{delegations-kbMOSEif.cjs.map → delegations-vpLtus9g.cjs.map} +1 -1
- package/dist/chunks/{designer-DDSxwtjV.js → designer-Cb3WAC46.js} +39 -30
- package/dist/chunks/{designer-DDSxwtjV.js.map → designer-Cb3WAC46.js.map} +1 -1
- package/dist/chunks/designer-zLliyrHu.cjs +65 -0
- package/dist/chunks/{designer-Cdn10NNl.cjs.map → designer-zLliyrHu.cjs.map} +1 -1
- package/dist/chunks/detail-Bp2Nv0cs.cjs +2 -0
- package/dist/chunks/{detail-CtVu5NTi.cjs.map → detail-Bp2Nv0cs.cjs.map} +1 -1
- package/dist/chunks/{detail-DAPz2qyn.js → detail-Cmw-aQcu.js} +4 -4
- package/dist/chunks/{detail-DAPz2qyn.js.map → detail-Cmw-aQcu.js.map} +1 -1
- package/dist/chunks/format-date-time-CB-LxzqT.js.map +1 -1
- package/dist/chunks/format-date-time-D-cx5M8z.cjs +2 -0
- package/dist/chunks/{format-date-time-XxBzF0F5.cjs.map → format-date-time-D-cx5M8z.cjs.map} +1 -1
- package/dist/chunks/login-9bCXyjbX.cjs.map +1 -1
- package/dist/chunks/login-BKxpLibd.js.map +1 -1
- package/dist/chunks/notifications-Bc4mgSds.cjs +2 -0
- package/dist/chunks/{notifications--hwl-DR0.cjs.map → notifications-Bc4mgSds.cjs.map} +1 -1
- package/dist/chunks/notifications-DJ5wzCye.js.map +1 -1
- package/dist/chunks/{orgs-DytLvZ_X.js → orgs-B930facb.js} +11 -10
- package/dist/chunks/{orgs-DytLvZ_X.js.map → orgs-B930facb.js.map} +1 -1
- package/dist/chunks/orgs-BefGgeyc.cjs +2 -0
- package/dist/chunks/{orgs-Ni22buHs.cjs.map → orgs-BefGgeyc.cjs.map} +1 -1
- package/dist/chunks/providers-BsEn3ikK.cjs +2 -0
- package/dist/chunks/{providers-D_zTbkQH.cjs.map → providers-BsEn3ikK.cjs.map} +1 -1
- package/dist/chunks/{providers-DEqcuXw9.js → providers-DjLh4oLY.js} +3 -3
- package/dist/chunks/{providers-DEqcuXw9.js.map → providers-DjLh4oLY.js.map} +1 -1
- package/dist/chunks/{chunk-CMqjfN_6.cjs → rolldown-runtime-BocRIvOZ.cjs} +1 -1
- package/dist/chunks/router-adapter--gYs13E8.cjs.map +1 -1
- package/dist/chunks/routes-config-fDVHmvXi.cjs.map +1 -1
- package/dist/chunks/users-C9LA-Rd7.cjs +2 -0
- package/dist/chunks/{users-0HqlM_VQ.cjs.map → users-C9LA-Rd7.cjs.map} +1 -1
- package/dist/chunks/users-FWrb3ehW.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/next/index.cjs +1 -1
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.js +9 -9
- package/dist/next/index.js.map +1 -1
- package/dist/next/workflow-chat-route.cjs +1 -1
- package/dist/next/workflow-chat-route.cjs.map +1 -1
- package/dist/next/workflow-chat-route.js +2 -1
- package/dist/next/workflow-chat-route.js.map +1 -1
- package/dist/pages/admin/delegations/index.cjs +1 -1
- package/dist/pages/admin/delegations/index.cjs.map +1 -1
- package/dist/pages/admin/delegations/index.js +1 -1
- package/dist/pages/admin/orgs/index.cjs +1 -1
- package/dist/pages/admin/orgs/index.cjs.map +1 -1
- package/dist/pages/admin/orgs/index.js +1 -1
- package/dist/pages/admin/users/index.cjs +1 -1
- package/dist/pages/admin/users/index.cjs.map +1 -1
- package/dist/pages/cc/index.cjs.map +1 -1
- package/dist/pages/dashboard/index.cjs.map +1 -1
- package/dist/pages/delegations/index.cjs +1 -1
- package/dist/pages/delegations/index.cjs.map +1 -1
- package/dist/pages/delegations/index.js +1 -1
- package/dist/pages/inbox/index.cjs.map +1 -1
- package/dist/pages/instances/detail/index.cjs +1 -1
- package/dist/pages/instances/detail/index.cjs.map +1 -1
- package/dist/pages/instances/detail/index.js +1 -1
- package/dist/pages/instances/new/index.cjs.map +1 -1
- package/dist/pages/login/index.cjs.map +1 -1
- package/dist/pages/root/index.cjs.map +1 -1
- package/dist/pages/search/index.cjs.map +1 -1
- package/dist/pages/sent/index.cjs.map +1 -1
- package/dist/pages/settings/notifications/index.cjs +1 -1
- package/dist/pages/settings/notifications/index.cjs.map +1 -1
- package/dist/pages/templates/categories/index.cjs +1 -1
- package/dist/pages/templates/categories/index.cjs.map +1 -1
- package/dist/pages/templates/compose/index.cjs +1 -1
- package/dist/pages/templates/compose/index.cjs.map +1 -1
- package/dist/pages/templates/compose/index.js +1 -1
- package/dist/pages/templates/designer/index.cjs +1 -1
- package/dist/pages/templates/designer/index.cjs.map +1 -1
- package/dist/pages/templates/designer/index.js +1 -1
- package/dist/pages/templates/index.cjs.map +1 -1
- package/dist/pages/templates/versions/index.cjs.map +1 -1
- package/dist/views/admin/delegations/index.cjs +1 -1
- package/dist/views/admin/delegations/index.js +1 -1
- package/dist/views/admin/index.cjs +1 -1
- package/dist/views/admin/index.js +2 -2
- package/dist/views/admin/orgs/index.cjs +1 -1
- package/dist/views/admin/orgs/index.js +1 -1
- package/dist/views/admin/users/index.cjs +1 -1
- package/dist/views/cc/index.cjs +1 -1
- package/dist/views/cc/index.cjs.map +1 -1
- package/dist/views/dashboard/index.cjs +1 -1
- package/dist/views/dashboard/index.cjs.map +1 -1
- package/dist/views/delegations/index.cjs +1 -1
- package/dist/views/delegations/index.js +1 -1
- package/dist/views/forms/builder/index.cjs +1 -1
- package/dist/views/forms/builder/index.js +1 -1
- package/dist/views/forms/renderer/index.cjs +1 -1
- package/dist/views/forms/renderer/index.js +1 -1
- package/dist/views/inbox/index.cjs +1 -1
- package/dist/views/inbox/index.cjs.map +1 -1
- package/dist/views/inbox/index.js.map +1 -1
- package/dist/views/instances/detail/index.cjs +1 -1
- package/dist/views/instances/detail/index.js +1 -1
- package/dist/views/instances/new/index.cjs +1 -1
- package/dist/views/instances/new/index.cjs.map +1 -1
- package/dist/views/instances/new/index.js +1 -1
- package/dist/views/instances/new/index.js.map +1 -1
- package/dist/views/search/index.cjs +1 -1
- package/dist/views/search/index.cjs.map +1 -1
- package/dist/views/sent/index.cjs +1 -1
- package/dist/views/sent/index.cjs.map +1 -1
- package/dist/views/settings/index.cjs +1 -1
- package/dist/views/settings/notifications/index.cjs +1 -1
- package/dist/views/templates/categories/index.cjs +1 -1
- package/dist/views/templates/compose/index.cjs +1 -1
- package/dist/views/templates/compose/index.js +1 -1
- package/dist/views/templates/designer/index.cjs +1 -1
- package/dist/views/templates/designer/index.js +1 -1
- package/dist/views/templates/index.cjs +1 -1
- package/dist/views/templates/index.cjs.map +1 -1
- package/dist/views/templates/index.js.map +1 -1
- package/dist/views/templates/versions/index.cjs +1 -1
- package/dist/views/templates/versions/index.cjs.map +1 -1
- package/dist/views/templates/versions/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunks/FormBuilderView-D6baVqBY.cjs +0 -3
- package/dist/chunks/FormRendererView-Cw9LiidV.cjs +0 -2
- package/dist/chunks/approval-instance-list-page-C408vxE2.cjs +0 -2
- package/dist/chunks/categories-DUfldZ_9.cjs +0 -2
- package/dist/chunks/compose-CODnd9-2.cjs +0 -2
- package/dist/chunks/dashboard-page-DwHQY6Ki.cjs +0 -2
- package/dist/chunks/delegations-CGj-tZNO.cjs +0 -2
- package/dist/chunks/delegations-kbMOSEif.cjs +0 -2
- package/dist/chunks/designer-Cdn10NNl.cjs +0 -65
- package/dist/chunks/detail-CtVu5NTi.cjs +0 -2
- package/dist/chunks/format-date-time-XxBzF0F5.cjs +0 -2
- package/dist/chunks/notifications--hwl-DR0.cjs +0 -2
- package/dist/chunks/orgs-Ni22buHs.cjs +0 -2
- package/dist/chunks/providers-D_zTbkQH.cjs +0 -2
- package/dist/chunks/users-0HqlM_VQ.cjs +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-date-time-CB-LxzqT.js","names":[],"sources":["../../src/lib/format-date-time.ts"],"sourcesContent":["import moment from 'moment';\n\n/**\n * Default BPM datetime formatter: `YYYY-MM-DD HH:mm:ss`. Returns `'-'` for\n * `null` / `undefined` so list cells render a consistent placeholder.\n */\nexport function formatDateTime(value: string | null | undefined): string {\n return value ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-';\n}\n"],"mappings":";;AAMA,SAAgB,EAAe,GAA0C;CACvE,OAAO,IAAQ,EAAO,CAAK,
|
|
1
|
+
{"version":3,"file":"format-date-time-CB-LxzqT.js","names":[],"sources":["../../src/lib/format-date-time.ts"],"sourcesContent":["import moment from 'moment';\n\n/**\n * Default BPM datetime formatter: `YYYY-MM-DD HH:mm:ss`. Returns `'-'` for\n * `null` / `undefined` so list cells render a consistent placeholder.\n */\nexport function formatDateTime(value: string | null | undefined): string {\n return value ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-';\n}\n"],"mappings":";;AAMA,SAAgB,EAAe,GAA0C;CACvE,OAAO,IAAQ,EAAO,CAAK,CAAC,CAAC,OAAO,qBAAqB,IAAI;AAC/D"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=require("./rolldown-runtime-BocRIvOZ.cjs");let t=require("moment");t=e.t(t,1);function n(e){return e?(0,t.default)(e).format(`YYYY-MM-DD HH:mm:ss`):`-`}Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return n}});
|
|
2
|
+
//# sourceMappingURL=format-date-time-D-cx5M8z.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format-date-time-
|
|
1
|
+
{"version":3,"file":"format-date-time-D-cx5M8z.cjs","names":[],"sources":["../../src/lib/format-date-time.ts"],"sourcesContent":["import moment from 'moment';\n\n/**\n * Default BPM datetime formatter: `YYYY-MM-DD HH:mm:ss`. Returns `'-'` for\n * `null` / `undefined` so list cells render a consistent placeholder.\n */\nexport function formatDateTime(value: string | null | undefined): string {\n return value ? moment(value).format('YYYY-MM-DD HH:mm:ss') : '-';\n}\n"],"mappings":"sFAMA,SAAgB,EAAe,EAA0C,CACvE,OAAO,GAAA,EAAQ,EAAA,QAAA,CAAO,CAAK,CAAC,CAAC,OAAO,qBAAqB,EAAI,GAC/D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-9bCXyjbX.cjs","names":[],"sources":["../../src/views/login/login.module.scss","../../src/views/login/LoginView.tsx"],"sourcesContent":[".loginPage {\n align-items: center;\n background: var(--mzn-color-bg-body);\n display: flex;\n min-height: 100vh;\n justify-content: center;\n padding: 32px;\n}\n\n.loginPanel {\n border: 1px solid var(--mzn-color-border);\n border-radius: 8px;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 28px;\n max-width: 440px;\n padding: 32px;\n width: 100%;\n}\n\n.brand {\n align-items: center;\n display: flex;\n gap: 14px;\n}\n\n.brandLogo {\n border-radius: 8px;\n height: 40px;\n width: 40px;\n object-fit: contain;\n}\n\n.form {\n display: flex;\n flex-direction: column;\n gap: 18px;\n}\n\n.demoUsers {\n border-top: 1px solid var(--mzn-color-border);\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding-top: 20px;\n}\n\n.demoUserList {\n display: grid;\n gap: 8px;\n}\n\n.demoUserButton {\n background: transparent;\n border: 1px solid var(--mzn-color-border);\n border-radius: 6px;\n color: inherit;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n font: inherit;\n gap: 2px;\n padding: 10px 12px;\n text-align: left;\n}\n\n.demoUserButton:hover {\n border-color: var(--mzn-color-primary);\n}\n\n.demoUserButton span:last-child {\n color: var(--mzn-color-text-secondary);\n font-size: 12px;\n}\n","'use client';\n\nimport {\n useCallback,\n useEffect,\n useState,\n type ChangeEvent,\n type FormEvent,\n type ReactElement,\n} from 'react';\nimport { Button, Input, Typography } from '@mezzanine-ui/react';\nimport { LoginIcon } from '@mezzanine-ui/icons';\nimport {\n listApiTestMembers,\n type ApiPublicMember,\n} from '@rytass/bpm-core-client';\nimport { useAuth } from '../../lib/auth-provider';\nimport { useRouterAdapter } from '../../lib/router-adapter';\nimport styles from './login.module.scss';\n\nexport interface LoginViewProps {\n /**\n * Logo image URL. Renders an inline `<img>` so the view stays framework\n * agnostic (no `next/image` dependency). Defaults to\n * `/rytass-logo.png` — host should serve a static asset at that path or\n * override via this prop.\n */\n readonly logoSrc?: string;\n /**\n * Pre-fill the identifier input. Defaults to the seeded demo account\n * email so the form is usable out of the box.\n */\n readonly defaultIdentifier?: string;\n /**\n * Pre-fill the password input. Defaults to the seeded demo password\n * (`'demo'`). Production hosts should pass an empty string.\n */\n readonly defaultPassword?: string;\n /**\n * Custom redirect target after a successful login. Defaults to reading\n * `?next=` from the host router's search params, falling back to `'/'`.\n */\n readonly defaultNextPath?: string;\n /**\n * Override the BPM admin brand title shown above the form. Defaults to\n * `'BPM Admin'`.\n */\n readonly brandTitle?: string;\n /**\n * Override the brand subtitle shown above the form. Defaults to\n * `'BPM API 登入'`.\n */\n readonly brandSubtitle?: string;\n}\n\nconst DEFAULT_LOGO = '/rytass-logo.png';\nconst DEFAULT_IDENTIFIER = 'lin.ceo@example.internal';\nconst DEFAULT_PASSWORD = 'demo';\n\n/**\n * Login UI for the BPM admin host. Renders the brand mark, identifier /\n * password fields, and a \"test members\" picker fed by\n * `listApiTestMembers()`. Self-contained: composes Mezzanine UI primitives,\n * reads the auth context, and uses the host router adapter to redirect on\n * success.\n *\n * Wrap with `<AuthProvider>` (and indirectly `<RouterAdapterProvider>`)\n * higher in the tree. The `pages/login` subpath ships a thin Next.js\n * wrapper that exports `default` (Server Component) and `metadata`.\n */\nexport function LoginView({\n logoSrc = DEFAULT_LOGO,\n defaultIdentifier = DEFAULT_IDENTIFIER,\n defaultPassword = DEFAULT_PASSWORD,\n defaultNextPath,\n brandTitle = 'BPM Admin',\n brandSubtitle = 'BPM API 登入',\n}: LoginViewProps = {}): ReactElement {\n const router = useRouterAdapter();\n const { loading, login, member } = useAuth();\n const [testMembers, setTestMembers] = useState<readonly ApiPublicMember[]>(\n [],\n );\n const [error, setError] = useState<string | null>(null);\n const [identifier, setIdentifier] = useState(defaultIdentifier);\n const [password, setPassword] = useState(defaultPassword);\n const [submitting, setSubmitting] = useState(false);\n\n useEffect((): void => {\n if (loading || !member) return;\n router.replace(resolveNextPath(defaultNextPath));\n }, [defaultNextPath, loading, member, router]);\n\n useEffect((): void => {\n void (async () => {\n try {\n setTestMembers(await listApiTestMembers());\n } catch {\n setTestMembers([]);\n }\n })();\n }, []);\n\n const handleSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n await login({ identifier, password });\n router.replace(resolveNextPath(defaultNextPath));\n } catch (loginError: unknown) {\n setError(readErrorMessage(loginError));\n } finally {\n setSubmitting(false);\n }\n },\n [defaultNextPath, identifier, login, password, router],\n );\n\n function handleIdentifierChange(event: ChangeEvent<HTMLInputElement>): void {\n setIdentifier(event.target.value);\n }\n function handlePasswordChange(event: ChangeEvent<HTMLInputElement>): void {\n setPassword(event.target.value);\n }\n\n return (\n <main className={styles.loginPage}>\n <section className={styles.loginPanel}>\n <div className={styles.brand}>\n <img alt=\"\" className={styles.brandLogo} src={logoSrc} />\n <div>\n <Typography variant=\"h3\">{brandTitle}</Typography>\n <Typography color=\"text-neutral\" variant=\"body\">\n {brandSubtitle}\n </Typography>\n </div>\n </div>\n\n <form className={styles.form} onSubmit={handleSubmit}>\n <label>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 帳號\n </Typography>\n <Input\n fullWidth\n name=\"identifier\"\n onChange={handleIdentifierChange}\n placeholder=\"member id 或 email\"\n value={identifier}\n />\n </label>\n\n <label>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 密碼\n </Typography>\n <Input\n fullWidth\n inputType=\"password\"\n name=\"password\"\n onChange={handlePasswordChange}\n value={password}\n variant=\"password\"\n />\n </label>\n\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n\n <Button\n disabled={submitting}\n icon={LoginIcon}\n iconType=\"leading\"\n type=\"submit\"\n variant=\"base-primary\"\n >\n 登入\n </Button>\n </form>\n\n {testMembers.length ? (\n <div className={styles.demoUsers}>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 測試帳號\n </Typography>\n <div className={styles.demoUserList}>\n {testMembers.map((testMember) => (\n <button\n className={styles.demoUserButton}\n key={testMember.memberId}\n onClick={(): void => setIdentifier(testMember.email)}\n type=\"button\"\n >\n <span>{testMember.name}</span>\n <span>{testMember.email}</span>\n </button>\n ))}\n </div>\n </div>\n ) : null}\n </section>\n </main>\n );\n}\n\nfunction resolveNextPath(fallback: string | undefined): string {\n if (fallback && fallback.startsWith('/') && !fallback.startsWith('//')) {\n return fallback;\n }\n const params = readBrowserSearchParams();\n const next = params.get('next');\n if (!next || !next.startsWith('/') || next.startsWith('//')) {\n return '/';\n }\n return next;\n}\n\nfunction readBrowserSearchParams(): URLSearchParams {\n if (typeof window === 'undefined') return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '登入失敗';\n}\n"],"mappings":"6gBCuDM,EAAe,mBACf,EAAqB,2BACrB,EAAmB,OAazB,SAAgB,EAAU,CACxB,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,kBACA,aAAa,YACb,gBAAgB,cACE,CAAC,EAAiB,CACpC,IAAM,EAAS,EAAA,EAAiB,EAC1B,CAAE,UAAS,QAAO,UAAW,EAAA,EAAQ,EACrC,CAAC,EAAa,IAAA,EAAA,EAAA,UAClB,CAAC,CACH,EACM,CAAC,EAAO,IAAA,EAAA,EAAA,UAAoC,IAAI,EAChD,CAAC,EAAY,IAAA,EAAA,EAAA,UAA0B,CAAiB,EACxD,CAAC,EAAU,IAAA,EAAA,EAAA,UAAwB,CAAe,EAClD,CAAC,EAAY,IAAA,EAAA,EAAA,UAA0B,EAAK,GAElD,EAAA,EAAA,eAAsB,CAChB,GAAW,CAAC,GAChB,EAAO,QAAQ,EAAgB,CAAe,CAAC,CACjD,EAAG,CAAC,EAAiB,EAAS,EAAQ,CAAM,CAAC,GAE7C,EAAA,EAAA,eAAsB,EACd,SAAY,CAChB,GAAI,CACF,EAAe,MAAA,EAAA,EAAA,oBAAyB,CAAC,CAC3C,MAAQ,CACN,EAAe,CAAC,CAAC,CACnB,CACF,GAAG,CACL,EAAG,CAAC,CAAC,EAEL,IAAM,GAAA,EAAA,EAAA,aACJ,KAAO,IAAqD,CAC1D,EAAM,eAAe,EACrB,EAAS,IAAI,EACb,EAAc,EAAI,EAClB,GAAI,CACF,MAAM,EAAM,CAAE,aAAY,UAAS,CAAC,EACpC,EAAO,QAAQ,EAAgB,CAAe,CAAC,CACjD,OAAS,EAAqB,CAC5B,EAAS,EAAiB,CAAU,CAAC,CACvC,QAAU,CACR,EAAc,EAAK,CACrB,CACF,EACA,CAAC,EAAiB,EAAY,EAAO,EAAU,CAAM,CACvD,EAEA,SAAS,EAAuB,EAA4C,CAC1E,EAAc,EAAM,OAAO,KAAK,CAClC,CACA,SAAS,EAAqB,EAA4C,CACxE,EAAY,EAAM,OAAO,KAAK,CAChC,CAEA,OACE,EAAA,EAAA,KAAC,OAAD,CAAM,UAAW,EAAO,oBACtB,EAAA,EAAA,MAAC,UAAD,CAAS,UAAW,EAAO,oBAA3B,EACE,EAAA,EAAA,MAAC,MAAD,CAAK,UAAW,EAAO,eAAvB,EACE,EAAA,EAAA,KAAC,MAAD,CAAK,IAAI,GAAG,UAAW,EAAO,UAAW,IAAK,CAAU,CAAA,GACxD,EAAA,EAAA,MAAC,MAAD,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,EAAA,WAAD,CAAY,QAAQ,cAAM,CAAuB,CAAA,GACjD,EAAA,EAAA,KAAC,EAAA,WAAD,CAAY,MAAM,eAAe,QAAQ,gBACtC,CACS,CAAA,CACT,CAAA,CAAA,CACF,KAEL,EAAA,EAAA,MAAC,OAAD,CAAM,UAAW,EAAO,KAAM,SAAU,WAAxC,EACE,EAAA,EAAA,MAAC,QAAD,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,EAAA,WAAD,CAAY,MAAM,eAAe,QAAQ,mBAAU,IAEvC,CAAA,GACZ,EAAA,EAAA,KAAC,EAAA,MAAD,CACE,UAAA,GACA,KAAK,aACL,SAAU,EACV,YAAY,oBACZ,MAAO,CACR,CAAA,CACI,CAAA,CAAA,GAEP,EAAA,EAAA,MAAC,QAAD,CAAA,SAAA,EACE,EAAA,EAAA,KAAC,EAAA,WAAD,CAAY,MAAM,eAAe,QAAQ,mBAAU,IAEvC,CAAA,GACZ,EAAA,EAAA,KAAC,EAAA,MAAD,CACE,UAAA,GACA,UAAU,WACV,KAAK,WACL,SAAU,EACV,MAAO,EACP,QAAQ,UACT,CAAA,CACI,CAAA,CAAA,EAEN,GACC,EAAA,EAAA,KAAC,EAAA,WAAD,CAAY,MAAM,aAAa,QAAQ,gBACpC,CACS,CAAA,EACV,MAEJ,EAAA,EAAA,KAAC,EAAA,OAAD,CACE,SAAU,EACV,KAAM,EAAA,UACN,SAAS,UACT,KAAK,SACL,QAAQ,wBACT,IAEO,CAAA,CACJ,IAEL,EAAY,QACX,EAAA,EAAA,MAAC,MAAD,CAAK,UAAW,EAAO,mBAAvB,EACE,EAAA,EAAA,KAAC,EAAA,WAAD,CAAY,MAAM,eAAe,QAAQ,mBAAU,MAEvC,CAAA,GACZ,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,EAAO,sBACpB,EAAY,IAAK,IAChB,EAAA,EAAA,MAAC,SAAD,CACE,UAAW,EAAO,eAElB,YAAqB,EAAc,EAAW,KAAK,EACnD,KAAK,kBAJP,EAME,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAW,IAAW,CAAA,GAC7B,EAAA,EAAA,KAAC,OAAD,CAAA,SAAO,EAAW,KAAY,CAAA,CACxB,GAND,EAAW,QAMV,CACT,CACE,CAAA,CACF,IACH,IACG,GACL,CAAA,CAEV,CAEA,SAAS,EAAgB,EAAsC,CAC7D,GAAI,GAAY,EAAS,WAAW,GAAG,GAAK,CAAC,EAAS,WAAW,IAAI,EACnE,OAAO,EAGT,IAAM,EADS,EACF,EAAO,IAAI,MAAM,EAI9B,MAHI,CAAC,GAAQ,CAAC,EAAK,WAAW,GAAG,GAAK,EAAK,WAAW,IAAI,EACjD,IAEF,CACT,CAEA,SAAS,GAA2C,CAElD,OADI,OAAO,OAAW,IAAoB,IAAI,gBACvC,IAAI,gBAAgB,OAAO,SAAS,MAAM,CACnD,CAEA,SAAS,EAAiB,EAAwB,CAChD,OAAO,aAAiB,MAAQ,EAAM,QAAU,MAClD"}
|
|
1
|
+
{"version":3,"file":"login-9bCXyjbX.cjs","names":[],"sources":["../../src/views/login/login.module.scss","../../src/views/login/LoginView.tsx"],"sourcesContent":[".loginPage {\n align-items: center;\n background: var(--mzn-color-bg-body);\n display: flex;\n min-height: 100vh;\n justify-content: center;\n padding: 32px;\n}\n\n.loginPanel {\n border: 1px solid var(--mzn-color-border);\n border-radius: 8px;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 28px;\n max-width: 440px;\n padding: 32px;\n width: 100%;\n}\n\n.brand {\n align-items: center;\n display: flex;\n gap: 14px;\n}\n\n.brandLogo {\n border-radius: 8px;\n height: 40px;\n width: 40px;\n object-fit: contain;\n}\n\n.form {\n display: flex;\n flex-direction: column;\n gap: 18px;\n}\n\n.demoUsers {\n border-top: 1px solid var(--mzn-color-border);\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding-top: 20px;\n}\n\n.demoUserList {\n display: grid;\n gap: 8px;\n}\n\n.demoUserButton {\n background: transparent;\n border: 1px solid var(--mzn-color-border);\n border-radius: 6px;\n color: inherit;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n font: inherit;\n gap: 2px;\n padding: 10px 12px;\n text-align: left;\n}\n\n.demoUserButton:hover {\n border-color: var(--mzn-color-primary);\n}\n\n.demoUserButton span:last-child {\n color: var(--mzn-color-text-secondary);\n font-size: 12px;\n}\n","'use client';\n\nimport {\n useCallback,\n useEffect,\n useState,\n type ChangeEvent,\n type FormEvent,\n type ReactElement,\n} from 'react';\nimport { Button, Input, Typography } from '@mezzanine-ui/react';\nimport { LoginIcon } from '@mezzanine-ui/icons';\nimport {\n listApiTestMembers,\n type ApiPublicMember,\n} from '@rytass/bpm-core-client';\nimport { useAuth } from '../../lib/auth-provider';\nimport { useRouterAdapter } from '../../lib/router-adapter';\nimport styles from './login.module.scss';\n\nexport interface LoginViewProps {\n /**\n * Logo image URL. Renders an inline `<img>` so the view stays framework\n * agnostic (no `next/image` dependency). Defaults to\n * `/rytass-logo.png` — host should serve a static asset at that path or\n * override via this prop.\n */\n readonly logoSrc?: string;\n /**\n * Pre-fill the identifier input. Defaults to the seeded demo account\n * email so the form is usable out of the box.\n */\n readonly defaultIdentifier?: string;\n /**\n * Pre-fill the password input. Defaults to the seeded demo password\n * (`'demo'`). Production hosts should pass an empty string.\n */\n readonly defaultPassword?: string;\n /**\n * Custom redirect target after a successful login. Defaults to reading\n * `?next=` from the host router's search params, falling back to `'/'`.\n */\n readonly defaultNextPath?: string;\n /**\n * Override the BPM admin brand title shown above the form. Defaults to\n * `'BPM Admin'`.\n */\n readonly brandTitle?: string;\n /**\n * Override the brand subtitle shown above the form. Defaults to\n * `'BPM API 登入'`.\n */\n readonly brandSubtitle?: string;\n}\n\nconst DEFAULT_LOGO = '/rytass-logo.png';\nconst DEFAULT_IDENTIFIER = 'lin.ceo@example.internal';\nconst DEFAULT_PASSWORD = 'demo';\n\n/**\n * Login UI for the BPM admin host. Renders the brand mark, identifier /\n * password fields, and a \"test members\" picker fed by\n * `listApiTestMembers()`. Self-contained: composes Mezzanine UI primitives,\n * reads the auth context, and uses the host router adapter to redirect on\n * success.\n *\n * Wrap with `<AuthProvider>` (and indirectly `<RouterAdapterProvider>`)\n * higher in the tree. The `pages/login` subpath ships a thin Next.js\n * wrapper that exports `default` (Server Component) and `metadata`.\n */\nexport function LoginView({\n logoSrc = DEFAULT_LOGO,\n defaultIdentifier = DEFAULT_IDENTIFIER,\n defaultPassword = DEFAULT_PASSWORD,\n defaultNextPath,\n brandTitle = 'BPM Admin',\n brandSubtitle = 'BPM API 登入',\n}: LoginViewProps = {}): ReactElement {\n const router = useRouterAdapter();\n const { loading, login, member } = useAuth();\n const [testMembers, setTestMembers] = useState<readonly ApiPublicMember[]>(\n [],\n );\n const [error, setError] = useState<string | null>(null);\n const [identifier, setIdentifier] = useState(defaultIdentifier);\n const [password, setPassword] = useState(defaultPassword);\n const [submitting, setSubmitting] = useState(false);\n\n useEffect((): void => {\n if (loading || !member) return;\n router.replace(resolveNextPath(defaultNextPath));\n }, [defaultNextPath, loading, member, router]);\n\n useEffect((): void => {\n void (async () => {\n try {\n setTestMembers(await listApiTestMembers());\n } catch {\n setTestMembers([]);\n }\n })();\n }, []);\n\n const handleSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n await login({ identifier, password });\n router.replace(resolveNextPath(defaultNextPath));\n } catch (loginError: unknown) {\n setError(readErrorMessage(loginError));\n } finally {\n setSubmitting(false);\n }\n },\n [defaultNextPath, identifier, login, password, router],\n );\n\n function handleIdentifierChange(event: ChangeEvent<HTMLInputElement>): void {\n setIdentifier(event.target.value);\n }\n function handlePasswordChange(event: ChangeEvent<HTMLInputElement>): void {\n setPassword(event.target.value);\n }\n\n return (\n <main className={styles.loginPage}>\n <section className={styles.loginPanel}>\n <div className={styles.brand}>\n <img alt=\"\" className={styles.brandLogo} src={logoSrc} />\n <div>\n <Typography variant=\"h3\">{brandTitle}</Typography>\n <Typography color=\"text-neutral\" variant=\"body\">\n {brandSubtitle}\n </Typography>\n </div>\n </div>\n\n <form className={styles.form} onSubmit={handleSubmit}>\n <label>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 帳號\n </Typography>\n <Input\n fullWidth\n name=\"identifier\"\n onChange={handleIdentifierChange}\n placeholder=\"member id 或 email\"\n value={identifier}\n />\n </label>\n\n <label>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 密碼\n </Typography>\n <Input\n fullWidth\n inputType=\"password\"\n name=\"password\"\n onChange={handlePasswordChange}\n value={password}\n variant=\"password\"\n />\n </label>\n\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n\n <Button\n disabled={submitting}\n icon={LoginIcon}\n iconType=\"leading\"\n type=\"submit\"\n variant=\"base-primary\"\n >\n 登入\n </Button>\n </form>\n\n {testMembers.length ? (\n <div className={styles.demoUsers}>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 測試帳號\n </Typography>\n <div className={styles.demoUserList}>\n {testMembers.map((testMember) => (\n <button\n className={styles.demoUserButton}\n key={testMember.memberId}\n onClick={(): void => setIdentifier(testMember.email)}\n type=\"button\"\n >\n <span>{testMember.name}</span>\n <span>{testMember.email}</span>\n </button>\n ))}\n </div>\n </div>\n ) : null}\n </section>\n </main>\n );\n}\n\nfunction resolveNextPath(fallback: string | undefined): string {\n if (fallback && fallback.startsWith('/') && !fallback.startsWith('//')) {\n return fallback;\n }\n const params = readBrowserSearchParams();\n const next = params.get('next');\n if (!next || !next.startsWith('/') || next.startsWith('//')) {\n return '/';\n }\n return next;\n}\n\nfunction readBrowserSearchParams(): URLSearchParams {\n if (typeof window === 'undefined') return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '登入失敗';\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login-BKxpLibd.js","names":[],"sources":["../../src/views/login/login.module.scss","../../src/views/login/LoginView.tsx"],"sourcesContent":[".loginPage {\n align-items: center;\n background: var(--mzn-color-bg-body);\n display: flex;\n min-height: 100vh;\n justify-content: center;\n padding: 32px;\n}\n\n.loginPanel {\n border: 1px solid var(--mzn-color-border);\n border-radius: 8px;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 28px;\n max-width: 440px;\n padding: 32px;\n width: 100%;\n}\n\n.brand {\n align-items: center;\n display: flex;\n gap: 14px;\n}\n\n.brandLogo {\n border-radius: 8px;\n height: 40px;\n width: 40px;\n object-fit: contain;\n}\n\n.form {\n display: flex;\n flex-direction: column;\n gap: 18px;\n}\n\n.demoUsers {\n border-top: 1px solid var(--mzn-color-border);\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding-top: 20px;\n}\n\n.demoUserList {\n display: grid;\n gap: 8px;\n}\n\n.demoUserButton {\n background: transparent;\n border: 1px solid var(--mzn-color-border);\n border-radius: 6px;\n color: inherit;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n font: inherit;\n gap: 2px;\n padding: 10px 12px;\n text-align: left;\n}\n\n.demoUserButton:hover {\n border-color: var(--mzn-color-primary);\n}\n\n.demoUserButton span:last-child {\n color: var(--mzn-color-text-secondary);\n font-size: 12px;\n}\n","'use client';\n\nimport {\n useCallback,\n useEffect,\n useState,\n type ChangeEvent,\n type FormEvent,\n type ReactElement,\n} from 'react';\nimport { Button, Input, Typography } from '@mezzanine-ui/react';\nimport { LoginIcon } from '@mezzanine-ui/icons';\nimport {\n listApiTestMembers,\n type ApiPublicMember,\n} from '@rytass/bpm-core-client';\nimport { useAuth } from '../../lib/auth-provider';\nimport { useRouterAdapter } from '../../lib/router-adapter';\nimport styles from './login.module.scss';\n\nexport interface LoginViewProps {\n /**\n * Logo image URL. Renders an inline `<img>` so the view stays framework\n * agnostic (no `next/image` dependency). Defaults to\n * `/rytass-logo.png` — host should serve a static asset at that path or\n * override via this prop.\n */\n readonly logoSrc?: string;\n /**\n * Pre-fill the identifier input. Defaults to the seeded demo account\n * email so the form is usable out of the box.\n */\n readonly defaultIdentifier?: string;\n /**\n * Pre-fill the password input. Defaults to the seeded demo password\n * (`'demo'`). Production hosts should pass an empty string.\n */\n readonly defaultPassword?: string;\n /**\n * Custom redirect target after a successful login. Defaults to reading\n * `?next=` from the host router's search params, falling back to `'/'`.\n */\n readonly defaultNextPath?: string;\n /**\n * Override the BPM admin brand title shown above the form. Defaults to\n * `'BPM Admin'`.\n */\n readonly brandTitle?: string;\n /**\n * Override the brand subtitle shown above the form. Defaults to\n * `'BPM API 登入'`.\n */\n readonly brandSubtitle?: string;\n}\n\nconst DEFAULT_LOGO = '/rytass-logo.png';\nconst DEFAULT_IDENTIFIER = 'lin.ceo@example.internal';\nconst DEFAULT_PASSWORD = 'demo';\n\n/**\n * Login UI for the BPM admin host. Renders the brand mark, identifier /\n * password fields, and a \"test members\" picker fed by\n * `listApiTestMembers()`. Self-contained: composes Mezzanine UI primitives,\n * reads the auth context, and uses the host router adapter to redirect on\n * success.\n *\n * Wrap with `<AuthProvider>` (and indirectly `<RouterAdapterProvider>`)\n * higher in the tree. The `pages/login` subpath ships a thin Next.js\n * wrapper that exports `default` (Server Component) and `metadata`.\n */\nexport function LoginView({\n logoSrc = DEFAULT_LOGO,\n defaultIdentifier = DEFAULT_IDENTIFIER,\n defaultPassword = DEFAULT_PASSWORD,\n defaultNextPath,\n brandTitle = 'BPM Admin',\n brandSubtitle = 'BPM API 登入',\n}: LoginViewProps = {}): ReactElement {\n const router = useRouterAdapter();\n const { loading, login, member } = useAuth();\n const [testMembers, setTestMembers] = useState<readonly ApiPublicMember[]>(\n [],\n );\n const [error, setError] = useState<string | null>(null);\n const [identifier, setIdentifier] = useState(defaultIdentifier);\n const [password, setPassword] = useState(defaultPassword);\n const [submitting, setSubmitting] = useState(false);\n\n useEffect((): void => {\n if (loading || !member) return;\n router.replace(resolveNextPath(defaultNextPath));\n }, [defaultNextPath, loading, member, router]);\n\n useEffect((): void => {\n void (async () => {\n try {\n setTestMembers(await listApiTestMembers());\n } catch {\n setTestMembers([]);\n }\n })();\n }, []);\n\n const handleSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n await login({ identifier, password });\n router.replace(resolveNextPath(defaultNextPath));\n } catch (loginError: unknown) {\n setError(readErrorMessage(loginError));\n } finally {\n setSubmitting(false);\n }\n },\n [defaultNextPath, identifier, login, password, router],\n );\n\n function handleIdentifierChange(event: ChangeEvent<HTMLInputElement>): void {\n setIdentifier(event.target.value);\n }\n function handlePasswordChange(event: ChangeEvent<HTMLInputElement>): void {\n setPassword(event.target.value);\n }\n\n return (\n <main className={styles.loginPage}>\n <section className={styles.loginPanel}>\n <div className={styles.brand}>\n <img alt=\"\" className={styles.brandLogo} src={logoSrc} />\n <div>\n <Typography variant=\"h3\">{brandTitle}</Typography>\n <Typography color=\"text-neutral\" variant=\"body\">\n {brandSubtitle}\n </Typography>\n </div>\n </div>\n\n <form className={styles.form} onSubmit={handleSubmit}>\n <label>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 帳號\n </Typography>\n <Input\n fullWidth\n name=\"identifier\"\n onChange={handleIdentifierChange}\n placeholder=\"member id 或 email\"\n value={identifier}\n />\n </label>\n\n <label>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 密碼\n </Typography>\n <Input\n fullWidth\n inputType=\"password\"\n name=\"password\"\n onChange={handlePasswordChange}\n value={password}\n variant=\"password\"\n />\n </label>\n\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n\n <Button\n disabled={submitting}\n icon={LoginIcon}\n iconType=\"leading\"\n type=\"submit\"\n variant=\"base-primary\"\n >\n 登入\n </Button>\n </form>\n\n {testMembers.length ? (\n <div className={styles.demoUsers}>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 測試帳號\n </Typography>\n <div className={styles.demoUserList}>\n {testMembers.map((testMember) => (\n <button\n className={styles.demoUserButton}\n key={testMember.memberId}\n onClick={(): void => setIdentifier(testMember.email)}\n type=\"button\"\n >\n <span>{testMember.name}</span>\n <span>{testMember.email}</span>\n </button>\n ))}\n </div>\n </div>\n ) : null}\n </section>\n </main>\n );\n}\n\nfunction resolveNextPath(fallback: string | undefined): string {\n if (fallback && fallback.startsWith('/') && !fallback.startsWith('//')) {\n return fallback;\n }\n const params = readBrowserSearchParams();\n const next = params.get('next');\n if (!next || !next.startsWith('/') || next.startsWith('//')) {\n return '/';\n }\n return next;\n}\n\nfunction readBrowserSearchParams(): URLSearchParams {\n if (typeof window === 'undefined') return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '登入失敗';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"login-BKxpLibd.js","names":[],"sources":["../../src/views/login/login.module.scss","../../src/views/login/LoginView.tsx"],"sourcesContent":[".loginPage {\n align-items: center;\n background: var(--mzn-color-bg-body);\n display: flex;\n min-height: 100vh;\n justify-content: center;\n padding: 32px;\n}\n\n.loginPanel {\n border: 1px solid var(--mzn-color-border);\n border-radius: 8px;\n box-sizing: border-box;\n display: flex;\n flex-direction: column;\n gap: 28px;\n max-width: 440px;\n padding: 32px;\n width: 100%;\n}\n\n.brand {\n align-items: center;\n display: flex;\n gap: 14px;\n}\n\n.brandLogo {\n border-radius: 8px;\n height: 40px;\n width: 40px;\n object-fit: contain;\n}\n\n.form {\n display: flex;\n flex-direction: column;\n gap: 18px;\n}\n\n.demoUsers {\n border-top: 1px solid var(--mzn-color-border);\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding-top: 20px;\n}\n\n.demoUserList {\n display: grid;\n gap: 8px;\n}\n\n.demoUserButton {\n background: transparent;\n border: 1px solid var(--mzn-color-border);\n border-radius: 6px;\n color: inherit;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n font: inherit;\n gap: 2px;\n padding: 10px 12px;\n text-align: left;\n}\n\n.demoUserButton:hover {\n border-color: var(--mzn-color-primary);\n}\n\n.demoUserButton span:last-child {\n color: var(--mzn-color-text-secondary);\n font-size: 12px;\n}\n","'use client';\n\nimport {\n useCallback,\n useEffect,\n useState,\n type ChangeEvent,\n type FormEvent,\n type ReactElement,\n} from 'react';\nimport { Button, Input, Typography } from '@mezzanine-ui/react';\nimport { LoginIcon } from '@mezzanine-ui/icons';\nimport {\n listApiTestMembers,\n type ApiPublicMember,\n} from '@rytass/bpm-core-client';\nimport { useAuth } from '../../lib/auth-provider';\nimport { useRouterAdapter } from '../../lib/router-adapter';\nimport styles from './login.module.scss';\n\nexport interface LoginViewProps {\n /**\n * Logo image URL. Renders an inline `<img>` so the view stays framework\n * agnostic (no `next/image` dependency). Defaults to\n * `/rytass-logo.png` — host should serve a static asset at that path or\n * override via this prop.\n */\n readonly logoSrc?: string;\n /**\n * Pre-fill the identifier input. Defaults to the seeded demo account\n * email so the form is usable out of the box.\n */\n readonly defaultIdentifier?: string;\n /**\n * Pre-fill the password input. Defaults to the seeded demo password\n * (`'demo'`). Production hosts should pass an empty string.\n */\n readonly defaultPassword?: string;\n /**\n * Custom redirect target after a successful login. Defaults to reading\n * `?next=` from the host router's search params, falling back to `'/'`.\n */\n readonly defaultNextPath?: string;\n /**\n * Override the BPM admin brand title shown above the form. Defaults to\n * `'BPM Admin'`.\n */\n readonly brandTitle?: string;\n /**\n * Override the brand subtitle shown above the form. Defaults to\n * `'BPM API 登入'`.\n */\n readonly brandSubtitle?: string;\n}\n\nconst DEFAULT_LOGO = '/rytass-logo.png';\nconst DEFAULT_IDENTIFIER = 'lin.ceo@example.internal';\nconst DEFAULT_PASSWORD = 'demo';\n\n/**\n * Login UI for the BPM admin host. Renders the brand mark, identifier /\n * password fields, and a \"test members\" picker fed by\n * `listApiTestMembers()`. Self-contained: composes Mezzanine UI primitives,\n * reads the auth context, and uses the host router adapter to redirect on\n * success.\n *\n * Wrap with `<AuthProvider>` (and indirectly `<RouterAdapterProvider>`)\n * higher in the tree. The `pages/login` subpath ships a thin Next.js\n * wrapper that exports `default` (Server Component) and `metadata`.\n */\nexport function LoginView({\n logoSrc = DEFAULT_LOGO,\n defaultIdentifier = DEFAULT_IDENTIFIER,\n defaultPassword = DEFAULT_PASSWORD,\n defaultNextPath,\n brandTitle = 'BPM Admin',\n brandSubtitle = 'BPM API 登入',\n}: LoginViewProps = {}): ReactElement {\n const router = useRouterAdapter();\n const { loading, login, member } = useAuth();\n const [testMembers, setTestMembers] = useState<readonly ApiPublicMember[]>(\n [],\n );\n const [error, setError] = useState<string | null>(null);\n const [identifier, setIdentifier] = useState(defaultIdentifier);\n const [password, setPassword] = useState(defaultPassword);\n const [submitting, setSubmitting] = useState(false);\n\n useEffect((): void => {\n if (loading || !member) return;\n router.replace(resolveNextPath(defaultNextPath));\n }, [defaultNextPath, loading, member, router]);\n\n useEffect((): void => {\n void (async () => {\n try {\n setTestMembers(await listApiTestMembers());\n } catch {\n setTestMembers([]);\n }\n })();\n }, []);\n\n const handleSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n setError(null);\n setSubmitting(true);\n try {\n await login({ identifier, password });\n router.replace(resolveNextPath(defaultNextPath));\n } catch (loginError: unknown) {\n setError(readErrorMessage(loginError));\n } finally {\n setSubmitting(false);\n }\n },\n [defaultNextPath, identifier, login, password, router],\n );\n\n function handleIdentifierChange(event: ChangeEvent<HTMLInputElement>): void {\n setIdentifier(event.target.value);\n }\n function handlePasswordChange(event: ChangeEvent<HTMLInputElement>): void {\n setPassword(event.target.value);\n }\n\n return (\n <main className={styles.loginPage}>\n <section className={styles.loginPanel}>\n <div className={styles.brand}>\n <img alt=\"\" className={styles.brandLogo} src={logoSrc} />\n <div>\n <Typography variant=\"h3\">{brandTitle}</Typography>\n <Typography color=\"text-neutral\" variant=\"body\">\n {brandSubtitle}\n </Typography>\n </div>\n </div>\n\n <form className={styles.form} onSubmit={handleSubmit}>\n <label>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 帳號\n </Typography>\n <Input\n fullWidth\n name=\"identifier\"\n onChange={handleIdentifierChange}\n placeholder=\"member id 或 email\"\n value={identifier}\n />\n </label>\n\n <label>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 密碼\n </Typography>\n <Input\n fullWidth\n inputType=\"password\"\n name=\"password\"\n onChange={handlePasswordChange}\n value={password}\n variant=\"password\"\n />\n </label>\n\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : null}\n\n <Button\n disabled={submitting}\n icon={LoginIcon}\n iconType=\"leading\"\n type=\"submit\"\n variant=\"base-primary\"\n >\n 登入\n </Button>\n </form>\n\n {testMembers.length ? (\n <div className={styles.demoUsers}>\n <Typography color=\"text-neutral\" variant=\"caption\">\n 測試帳號\n </Typography>\n <div className={styles.demoUserList}>\n {testMembers.map((testMember) => (\n <button\n className={styles.demoUserButton}\n key={testMember.memberId}\n onClick={(): void => setIdentifier(testMember.email)}\n type=\"button\"\n >\n <span>{testMember.name}</span>\n <span>{testMember.email}</span>\n </button>\n ))}\n </div>\n </div>\n ) : null}\n </section>\n </main>\n );\n}\n\nfunction resolveNextPath(fallback: string | undefined): string {\n if (fallback && fallback.startsWith('/') && !fallback.startsWith('//')) {\n return fallback;\n }\n const params = readBrowserSearchParams();\n const next = params.get('next');\n if (!next || !next.startsWith('/') || next.startsWith('//')) {\n return '/';\n }\n return next;\n}\n\nfunction readBrowserSearchParams(): URLSearchParams {\n if (typeof window === 'undefined') return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '登入失敗';\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use client";require('../notifications.css');const e=require("./rolldown-runtime-BocRIvOZ.cjs"),t=require("./auth-provider-4BeCw7cI.cjs");let n=require("react"),r=require("@mezzanine-ui/react"),i=require("react/jsx-runtime"),a=require("@rytass/bpm-core-client/workflow"),o=require("@mezzanine-ui/core/form"),s=require("@mezzanine-ui/react/ContentHeader");s=e.t(s,1);var c={preferenceFilter:`bpm_preferenceFilter_snhMa`,segmentFilterControl:`bpm_segmentFilterControl_piBnq`,segmentFilterLabel:`bpm_segmentFilterLabel_U0z8J`},l=[{id:`INSTANT`,name:`即時通知`},{id:`DAILY`,name:`每日摘要`}],u=[{id:`ON`,name:`開`},{id:`OFF`,name:`關`}],d={emailDigestMode:`INSTANT`,emailEnabled:!0,inAppEnabled:!0,memberId:``,quietHoursEnd:null,quietHoursStart:null,updatedAt:``};function f(){let{member:e}=t.n(),f=e?.memberId??null,[v,y]=(0,n.useState)(d),[b,x]=(0,n.useState)(!0),[S,C]=(0,n.useState)(!1),[w,T]=(0,n.useState)(null);(0,n.useEffect)(()=>{f&&(x(!0),T(null),(0,a.readNotificationPreference)(f).then(e=>{y(e)}).catch(e=>{T(_(e))}).finally(()=>{x(!1)}))},[f]);async function E(e){if(!f||S)return;let t=v;y(e),C(!0);try{y(await(0,a.updateNotificationPreference)({emailDigestMode:e.emailDigestMode,emailEnabled:e.emailEnabled,inAppEnabled:e.inAppEnabled,memberId:f,quietHoursEnd:e.quietHoursEnd,quietHoursStart:e.quietHoursStart}))}catch(e){y(t),T(_(e))}finally{C(!1)}}let D=b||S;return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(r.PageHeader,{children:(0,i.jsx)(s.default,{description:`調整站內通知、Email 通知與摘要頻率。`,title:`通知設定`})}),(0,i.jsx)(r.SectionGroup,{children:(0,i.jsx)(r.Section,{filterArea:(0,i.jsx)(r.FilterArea,{className:c.preferenceFilter,isDirty:!1,children:(0,i.jsxs)(r.FilterLine,{children:[(0,i.jsx)(r.Filter,{minWidth:160,span:1,children:(0,i.jsx)(r.FormField,{layout:o.FormFieldLayout.VERTICAL,name:`inAppEnabled`,style:p,children:(0,i.jsxs)(`div`,{className:c.segmentFilterControl,children:[(0,i.jsx)(`span`,{className:c.segmentFilterLabel,children:`站內通知`}),(0,i.jsx)(r.RadioGroup,{disabled:D,name:`inAppEnabled`,onChange:e=>{E({...v,inAppEnabled:h(e.target.value)})},options:[...u],size:`sub`,type:`segment`,value:g(v.inAppEnabled)})]})})}),(0,i.jsx)(r.Filter,{minWidth:180,span:1,children:(0,i.jsx)(r.FormField,{layout:o.FormFieldLayout.VERTICAL,name:`emailEnabled`,style:p,children:(0,i.jsxs)(`div`,{className:c.segmentFilterControl,children:[(0,i.jsx)(`span`,{className:c.segmentFilterLabel,children:`Email 通知`}),(0,i.jsx)(r.RadioGroup,{disabled:D,name:`emailEnabled`,onChange:e=>{E({...v,emailEnabled:h(e.target.value)})},options:[...u],size:`sub`,type:`segment`,value:g(v.emailEnabled)})]})})}),(0,i.jsx)(r.Filter,{minWidth:280,span:2,children:(0,i.jsx)(r.FormField,{fullWidth:!0,layout:o.FormFieldLayout.VERTICAL,name:`emailDigestMode`,style:p,children:(0,i.jsxs)(`div`,{className:c.segmentFilterControl,children:[(0,i.jsx)(`span`,{className:c.segmentFilterLabel,children:`Email 頻率`}),(0,i.jsx)(r.RadioGroup,{disabled:D,name:`emailDigestMode`,onChange:e=>{E({...v,emailDigestMode:m(e.target.value)})},options:[...l],size:`sub`,type:`segment`,value:v.emailDigestMode})]})})})]})}),children:w?(0,i.jsx)(r.Typography,{color:`text-error`,variant:`body`,children:w}):(0,i.jsx)(r.Typography,{color:`text-neutral`,variant:`body`,children:`偏好設定會立即生效。`})})})]})}var p={minWidth:0,whiteSpace:`nowrap`};function m(e){return e===`DAILY`?`DAILY`:`INSTANT`}function h(e){return e!==`OFF`}function g(e){return e?`ON`:`OFF`}function _(e){return e instanceof Error?e.message:`發生未知錯誤`}Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return f}});
|
|
2
|
+
//# sourceMappingURL=notifications-Bc4mgSds.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications--hwl-DR0.cjs","names":[],"sources":["../../src/views/settings/notifications/notification-settings.module.scss","../../src/views/settings/notifications/SettingsNotificationsView.tsx"],"sourcesContent":[".preferenceFilter {\n :global(.mzn-filter-area__actions) {\n display: none;\n }\n\n :global(.mzn-form-field__label-area) {\n display: none;\n }\n\n :global(.mzn-form-field__control-field-slot--main) {\n width: 100%;\n min-width: 0;\n }\n}\n\n.segmentFilterControl {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n white-space: nowrap;\n\n :global(.mzn-input-check-group--segmented) {\n align-self: flex-start;\n transform: translateY(-7px);\n }\n}\n\n.segmentFilterLabel {\n flex: 0 0 auto;\n color: var(--mzn-color-text-primary, #1f2937);\n font-size: 13px;\n line-height: 16px;\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useState,\n} from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n PageHeader,\n RadioGroup,\n Section,\n SectionGroup,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport { useAuth } from '../../../lib/auth-provider';\nimport {\n NotificationDigestMode,\n NotificationPreferenceRecord,\n readNotificationPreference,\n updateNotificationPreference,\n} from '@rytass/bpm-core-client/workflow';\nimport styles from './notification-settings.module.scss';\n\ninterface DigestOption {\n readonly id: NotificationDigestMode;\n readonly name: string;\n}\n\ntype EnabledSegmentValue = 'OFF' | 'ON';\n\ninterface EnabledSegmentOption {\n readonly id: EnabledSegmentValue;\n readonly name: string;\n}\n\nconst DIGEST_OPTIONS: readonly DigestOption[] = [\n { id: 'INSTANT', name: '即時通知' },\n { id: 'DAILY', name: '每日摘要' },\n];\n\nconst ENABLED_SEGMENT_OPTIONS: readonly EnabledSegmentOption[] = [\n { id: 'ON', name: '開' },\n { id: 'OFF', name: '關' },\n];\n\nconst DEFAULT_PREFERENCE: NotificationPreferenceRecord = {\n emailDigestMode: 'INSTANT',\n emailEnabled: true,\n inAppEnabled: true,\n memberId: '',\n quietHoursEnd: null,\n quietHoursStart: null,\n updatedAt: '',\n};\n\n\nexport function SettingsNotificationsView(): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [preference, setPreference] =\n useState<NotificationPreferenceRecord>(DEFAULT_PREFERENCE);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n setLoading(true);\n setError(null);\n\n readNotificationPreference(currentMemberId)\n .then((nextPreference): void => {\n setPreference(nextPreference);\n })\n .catch((requestError: unknown): void => {\n setError(readErrorMessage(requestError));\n })\n .finally((): void => {\n setLoading(false);\n });\n }, [currentMemberId]);\n\n async function handlePreferenceChange(\n nextPreference: NotificationPreferenceRecord,\n ): Promise<void> {\n if (!currentMemberId || saving) {\n return;\n }\n\n const previousPreference = preference;\n setPreference(nextPreference);\n setSaving(true);\n\n try {\n setPreference(\n await updateNotificationPreference({\n emailDigestMode: nextPreference.emailDigestMode,\n emailEnabled: nextPreference.emailEnabled,\n inAppEnabled: nextPreference.inAppEnabled,\n memberId: currentMemberId,\n quietHoursEnd: nextPreference.quietHoursEnd,\n quietHoursStart: nextPreference.quietHoursStart,\n }),\n );\n } catch (requestError: unknown) {\n setPreference(previousPreference);\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const controlsDisabled = loading || saving;\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"調整站內通知、Email 通知與摘要頻率。\"\n title=\"通知設定\"\n />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.preferenceFilter} isDirty={false}>\n <FilterLine>\n <Filter minWidth={160} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"inAppEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n 站內通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"inAppEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n inAppEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.inAppEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={180} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"emailEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.emailEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={280} span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"emailDigestMode\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 頻率\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailDigestMode\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailDigestMode: readDigestMode(\n event.target.value,\n ),\n });\n }}\n options={[...DIGEST_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={preference.emailDigestMode}\n />\n </div>\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : (\n <Typography color=\"text-neutral\" variant=\"body\">\n 偏好設定會立即生效。\n </Typography>\n )}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FILTER_FIELD_STYLE = {\n minWidth: 0,\n whiteSpace: 'nowrap',\n} satisfies CSSProperties;\n\nfunction readDigestMode(value: unknown): NotificationDigestMode {\n return value === 'DAILY' ? 'DAILY' : 'INSTANT';\n}\n\nfunction readEnabledSegmentValue(value: unknown): boolean {\n return value !== 'OFF';\n}\n\nfunction readEnabledSegmentValueId(enabled: boolean): EnabledSegmentValue {\n return enabled ? 'ON' : 'OFF';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"notifications-Bc4mgSds.cjs","names":[],"sources":["../../src/views/settings/notifications/notification-settings.module.scss","../../src/views/settings/notifications/SettingsNotificationsView.tsx"],"sourcesContent":[".preferenceFilter {\n :global(.mzn-filter-area__actions) {\n display: none;\n }\n\n :global(.mzn-form-field__label-area) {\n display: none;\n }\n\n :global(.mzn-form-field__control-field-slot--main) {\n width: 100%;\n min-width: 0;\n }\n}\n\n.segmentFilterControl {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n white-space: nowrap;\n\n :global(.mzn-input-check-group--segmented) {\n align-self: flex-start;\n transform: translateY(-7px);\n }\n}\n\n.segmentFilterLabel {\n flex: 0 0 auto;\n color: var(--mzn-color-text-primary, #1f2937);\n font-size: 13px;\n line-height: 16px;\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useState,\n} from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n PageHeader,\n RadioGroup,\n Section,\n SectionGroup,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport { useAuth } from '../../../lib/auth-provider';\nimport {\n NotificationDigestMode,\n NotificationPreferenceRecord,\n readNotificationPreference,\n updateNotificationPreference,\n} from '@rytass/bpm-core-client/workflow';\nimport styles from './notification-settings.module.scss';\n\ninterface DigestOption {\n readonly id: NotificationDigestMode;\n readonly name: string;\n}\n\ntype EnabledSegmentValue = 'OFF' | 'ON';\n\ninterface EnabledSegmentOption {\n readonly id: EnabledSegmentValue;\n readonly name: string;\n}\n\nconst DIGEST_OPTIONS: readonly DigestOption[] = [\n { id: 'INSTANT', name: '即時通知' },\n { id: 'DAILY', name: '每日摘要' },\n];\n\nconst ENABLED_SEGMENT_OPTIONS: readonly EnabledSegmentOption[] = [\n { id: 'ON', name: '開' },\n { id: 'OFF', name: '關' },\n];\n\nconst DEFAULT_PREFERENCE: NotificationPreferenceRecord = {\n emailDigestMode: 'INSTANT',\n emailEnabled: true,\n inAppEnabled: true,\n memberId: '',\n quietHoursEnd: null,\n quietHoursStart: null,\n updatedAt: '',\n};\n\n\nexport function SettingsNotificationsView(): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [preference, setPreference] =\n useState<NotificationPreferenceRecord>(DEFAULT_PREFERENCE);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n setLoading(true);\n setError(null);\n\n readNotificationPreference(currentMemberId)\n .then((nextPreference): void => {\n setPreference(nextPreference);\n })\n .catch((requestError: unknown): void => {\n setError(readErrorMessage(requestError));\n })\n .finally((): void => {\n setLoading(false);\n });\n }, [currentMemberId]);\n\n async function handlePreferenceChange(\n nextPreference: NotificationPreferenceRecord,\n ): Promise<void> {\n if (!currentMemberId || saving) {\n return;\n }\n\n const previousPreference = preference;\n setPreference(nextPreference);\n setSaving(true);\n\n try {\n setPreference(\n await updateNotificationPreference({\n emailDigestMode: nextPreference.emailDigestMode,\n emailEnabled: nextPreference.emailEnabled,\n inAppEnabled: nextPreference.inAppEnabled,\n memberId: currentMemberId,\n quietHoursEnd: nextPreference.quietHoursEnd,\n quietHoursStart: nextPreference.quietHoursStart,\n }),\n );\n } catch (requestError: unknown) {\n setPreference(previousPreference);\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const controlsDisabled = loading || saving;\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"調整站內通知、Email 通知與摘要頻率。\"\n title=\"通知設定\"\n />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.preferenceFilter} isDirty={false}>\n <FilterLine>\n <Filter minWidth={160} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"inAppEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n 站內通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"inAppEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n inAppEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.inAppEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={180} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"emailEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.emailEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={280} span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"emailDigestMode\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 頻率\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailDigestMode\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailDigestMode: readDigestMode(\n event.target.value,\n ),\n });\n }}\n options={[...DIGEST_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={preference.emailDigestMode}\n />\n </div>\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : (\n <Typography color=\"text-neutral\" variant=\"body\">\n 偏好設定會立即生效。\n </Typography>\n )}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FILTER_FIELD_STYLE = {\n minWidth: 0,\n whiteSpace: 'nowrap',\n} satisfies CSSProperties;\n\nfunction readDigestMode(value: unknown): NotificationDigestMode {\n return value === 'DAILY' ? 'DAILY' : 'INSTANT';\n}\n\nfunction readEnabledSegmentValue(value: unknown): boolean {\n return value !== 'OFF';\n}\n\nfunction readEnabledSegmentValueId(enabled: boolean): EnabledSegmentValue {\n return enabled ? 'ON' : 'OFF';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notifications-DJ5wzCye.js","names":[],"sources":["../../src/views/settings/notifications/notification-settings.module.scss","../../src/views/settings/notifications/SettingsNotificationsView.tsx"],"sourcesContent":[".preferenceFilter {\n :global(.mzn-filter-area__actions) {\n display: none;\n }\n\n :global(.mzn-form-field__label-area) {\n display: none;\n }\n\n :global(.mzn-form-field__control-field-slot--main) {\n width: 100%;\n min-width: 0;\n }\n}\n\n.segmentFilterControl {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n white-space: nowrap;\n\n :global(.mzn-input-check-group--segmented) {\n align-self: flex-start;\n transform: translateY(-7px);\n }\n}\n\n.segmentFilterLabel {\n flex: 0 0 auto;\n color: var(--mzn-color-text-primary, #1f2937);\n font-size: 13px;\n line-height: 16px;\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useState,\n} from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n PageHeader,\n RadioGroup,\n Section,\n SectionGroup,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport { useAuth } from '../../../lib/auth-provider';\nimport {\n NotificationDigestMode,\n NotificationPreferenceRecord,\n readNotificationPreference,\n updateNotificationPreference,\n} from '@rytass/bpm-core-client/workflow';\nimport styles from './notification-settings.module.scss';\n\ninterface DigestOption {\n readonly id: NotificationDigestMode;\n readonly name: string;\n}\n\ntype EnabledSegmentValue = 'OFF' | 'ON';\n\ninterface EnabledSegmentOption {\n readonly id: EnabledSegmentValue;\n readonly name: string;\n}\n\nconst DIGEST_OPTIONS: readonly DigestOption[] = [\n { id: 'INSTANT', name: '即時通知' },\n { id: 'DAILY', name: '每日摘要' },\n];\n\nconst ENABLED_SEGMENT_OPTIONS: readonly EnabledSegmentOption[] = [\n { id: 'ON', name: '開' },\n { id: 'OFF', name: '關' },\n];\n\nconst DEFAULT_PREFERENCE: NotificationPreferenceRecord = {\n emailDigestMode: 'INSTANT',\n emailEnabled: true,\n inAppEnabled: true,\n memberId: '',\n quietHoursEnd: null,\n quietHoursStart: null,\n updatedAt: '',\n};\n\n\nexport function SettingsNotificationsView(): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [preference, setPreference] =\n useState<NotificationPreferenceRecord>(DEFAULT_PREFERENCE);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n setLoading(true);\n setError(null);\n\n readNotificationPreference(currentMemberId)\n .then((nextPreference): void => {\n setPreference(nextPreference);\n })\n .catch((requestError: unknown): void => {\n setError(readErrorMessage(requestError));\n })\n .finally((): void => {\n setLoading(false);\n });\n }, [currentMemberId]);\n\n async function handlePreferenceChange(\n nextPreference: NotificationPreferenceRecord,\n ): Promise<void> {\n if (!currentMemberId || saving) {\n return;\n }\n\n const previousPreference = preference;\n setPreference(nextPreference);\n setSaving(true);\n\n try {\n setPreference(\n await updateNotificationPreference({\n emailDigestMode: nextPreference.emailDigestMode,\n emailEnabled: nextPreference.emailEnabled,\n inAppEnabled: nextPreference.inAppEnabled,\n memberId: currentMemberId,\n quietHoursEnd: nextPreference.quietHoursEnd,\n quietHoursStart: nextPreference.quietHoursStart,\n }),\n );\n } catch (requestError: unknown) {\n setPreference(previousPreference);\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const controlsDisabled = loading || saving;\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"調整站內通知、Email 通知與摘要頻率。\"\n title=\"通知設定\"\n />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.preferenceFilter} isDirty={false}>\n <FilterLine>\n <Filter minWidth={160} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"inAppEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n 站內通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"inAppEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n inAppEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.inAppEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={180} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"emailEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.emailEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={280} span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"emailDigestMode\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 頻率\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailDigestMode\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailDigestMode: readDigestMode(\n event.target.value,\n ),\n });\n }}\n options={[...DIGEST_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={preference.emailDigestMode}\n />\n </div>\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : (\n <Typography color=\"text-neutral\" variant=\"body\">\n 偏好設定會立即生效。\n </Typography>\n )}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FILTER_FIELD_STYLE = {\n minWidth: 0,\n whiteSpace: 'nowrap',\n} satisfies CSSProperties;\n\nfunction readDigestMode(value: unknown): NotificationDigestMode {\n return value === 'DAILY' ? 'DAILY' : 'INSTANT';\n}\n\nfunction readEnabledSegmentValue(value: unknown): boolean {\n return value !== 'OFF';\n}\n\nfunction readEnabledSegmentValueId(enabled: boolean): EnabledSegmentValue {\n return enabled ? 'ON' : 'OFF';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"notifications-DJ5wzCye.js","names":[],"sources":["../../src/views/settings/notifications/notification-settings.module.scss","../../src/views/settings/notifications/SettingsNotificationsView.tsx"],"sourcesContent":[".preferenceFilter {\n :global(.mzn-filter-area__actions) {\n display: none;\n }\n\n :global(.mzn-form-field__label-area) {\n display: none;\n }\n\n :global(.mzn-form-field__control-field-slot--main) {\n width: 100%;\n min-width: 0;\n }\n}\n\n.segmentFilterControl {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n min-width: 0;\n white-space: nowrap;\n\n :global(.mzn-input-check-group--segmented) {\n align-self: flex-start;\n transform: translateY(-7px);\n }\n}\n\n.segmentFilterLabel {\n flex: 0 0 auto;\n color: var(--mzn-color-text-primary, #1f2937);\n font-size: 13px;\n line-height: 16px;\n}\n","'use client';\n\nimport {\n ChangeEvent,\n CSSProperties,\n ReactElement,\n useEffect,\n useState,\n} from 'react';\nimport {\n Filter,\n FilterArea,\n FilterLine,\n FormField,\n PageHeader,\n RadioGroup,\n Section,\n SectionGroup,\n Typography,\n} from '@mezzanine-ui/react';\nimport ContentHeader from '@mezzanine-ui/react/ContentHeader';\nimport { FormFieldLayout } from '@mezzanine-ui/core/form';\nimport { useAuth } from '../../../lib/auth-provider';\nimport {\n NotificationDigestMode,\n NotificationPreferenceRecord,\n readNotificationPreference,\n updateNotificationPreference,\n} from '@rytass/bpm-core-client/workflow';\nimport styles from './notification-settings.module.scss';\n\ninterface DigestOption {\n readonly id: NotificationDigestMode;\n readonly name: string;\n}\n\ntype EnabledSegmentValue = 'OFF' | 'ON';\n\ninterface EnabledSegmentOption {\n readonly id: EnabledSegmentValue;\n readonly name: string;\n}\n\nconst DIGEST_OPTIONS: readonly DigestOption[] = [\n { id: 'INSTANT', name: '即時通知' },\n { id: 'DAILY', name: '每日摘要' },\n];\n\nconst ENABLED_SEGMENT_OPTIONS: readonly EnabledSegmentOption[] = [\n { id: 'ON', name: '開' },\n { id: 'OFF', name: '關' },\n];\n\nconst DEFAULT_PREFERENCE: NotificationPreferenceRecord = {\n emailDigestMode: 'INSTANT',\n emailEnabled: true,\n inAppEnabled: true,\n memberId: '',\n quietHoursEnd: null,\n quietHoursStart: null,\n updatedAt: '',\n};\n\n\nexport function SettingsNotificationsView(): ReactElement {\n const { member } = useAuth();\n const currentMemberId = member?.memberId ?? null;\n const [preference, setPreference] =\n useState<NotificationPreferenceRecord>(DEFAULT_PREFERENCE);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n useEffect((): void => {\n if (!currentMemberId) {\n return;\n }\n\n setLoading(true);\n setError(null);\n\n readNotificationPreference(currentMemberId)\n .then((nextPreference): void => {\n setPreference(nextPreference);\n })\n .catch((requestError: unknown): void => {\n setError(readErrorMessage(requestError));\n })\n .finally((): void => {\n setLoading(false);\n });\n }, [currentMemberId]);\n\n async function handlePreferenceChange(\n nextPreference: NotificationPreferenceRecord,\n ): Promise<void> {\n if (!currentMemberId || saving) {\n return;\n }\n\n const previousPreference = preference;\n setPreference(nextPreference);\n setSaving(true);\n\n try {\n setPreference(\n await updateNotificationPreference({\n emailDigestMode: nextPreference.emailDigestMode,\n emailEnabled: nextPreference.emailEnabled,\n inAppEnabled: nextPreference.inAppEnabled,\n memberId: currentMemberId,\n quietHoursEnd: nextPreference.quietHoursEnd,\n quietHoursStart: nextPreference.quietHoursStart,\n }),\n );\n } catch (requestError: unknown) {\n setPreference(previousPreference);\n setError(readErrorMessage(requestError));\n } finally {\n setSaving(false);\n }\n }\n\n const controlsDisabled = loading || saving;\n\n return (\n <>\n <PageHeader>\n <ContentHeader\n description=\"調整站內通知、Email 通知與摘要頻率。\"\n title=\"通知設定\"\n />\n </PageHeader>\n\n <SectionGroup>\n <Section\n filterArea={\n <FilterArea className={styles.preferenceFilter} isDirty={false}>\n <FilterLine>\n <Filter minWidth={160} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"inAppEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n 站內通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"inAppEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n inAppEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.inAppEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={180} span={1}>\n <FormField\n layout={FormFieldLayout.VERTICAL}\n name=\"emailEnabled\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 通知\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailEnabled\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailEnabled: readEnabledSegmentValue(\n event.target.value,\n ),\n });\n }}\n options={[...ENABLED_SEGMENT_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={readEnabledSegmentValueId(\n preference.emailEnabled,\n )}\n />\n </div>\n </FormField>\n </Filter>\n <Filter minWidth={280} span={2}>\n <FormField\n fullWidth\n layout={FormFieldLayout.VERTICAL}\n name=\"emailDigestMode\"\n style={FILTER_FIELD_STYLE}\n >\n <div className={styles.segmentFilterControl}>\n <span className={styles.segmentFilterLabel}>\n Email 頻率\n </span>\n <RadioGroup\n disabled={controlsDisabled}\n name=\"emailDigestMode\"\n onChange={(\n event: ChangeEvent<HTMLInputElement>,\n ): void => {\n void handlePreferenceChange({\n ...preference,\n emailDigestMode: readDigestMode(\n event.target.value,\n ),\n });\n }}\n options={[...DIGEST_OPTIONS]}\n size=\"sub\"\n type=\"segment\"\n value={preference.emailDigestMode}\n />\n </div>\n </FormField>\n </Filter>\n </FilterLine>\n </FilterArea>\n }\n >\n {error ? (\n <Typography color=\"text-error\" variant=\"body\">\n {error}\n </Typography>\n ) : (\n <Typography color=\"text-neutral\" variant=\"body\">\n 偏好設定會立即生效。\n </Typography>\n )}\n </Section>\n </SectionGroup>\n </>\n );\n}\n\nconst FILTER_FIELD_STYLE = {\n minWidth: 0,\n whiteSpace: 'nowrap',\n} satisfies CSSProperties;\n\nfunction readDigestMode(value: unknown): NotificationDigestMode {\n return value === 'DAILY' ? 'DAILY' : 'INSTANT';\n}\n\nfunction readEnabledSegmentValue(value: unknown): boolean {\n return value !== 'OFF';\n}\n\nfunction readEnabledSegmentValueId(enabled: boolean): EnabledSegmentValue {\n return enabled ? 'ON' : 'OFF';\n}\n\nfunction readErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : '發生未知錯誤';\n}\n"],"mappings":";;;;;;;;;;;;GC2CM,IAA0C,CAC9C;CAAE,IAAI;CAAW,MAAM;AAAO,GAC9B;CAAE,IAAI;CAAS,MAAM;AAAO,CAC9B,GAEM,IAA2D,CAC/D;CAAE,IAAI;CAAM,MAAM;AAAI,GACtB;CAAE,IAAI;CAAO,MAAM;AAAI,CACzB,GAEM,IAAmD;CACvD,iBAAiB;CACjB,cAAc;CACd,cAAc;CACd,UAAU;CACV,eAAe;CACf,iBAAiB;CACjB,WAAW;AACb;AAGA,SAAgB,IAA0C;CACxD,IAAM,EAAE,cAAW,EAAQ,GACrB,IAAkB,GAAQ,YAAY,MACtC,CAAC,GAAY,KACjB,EAAuC,CAAkB,GACrD,CAAC,GAAS,KAAc,EAAS,EAAI,GACrC,CAAC,GAAQ,KAAa,EAAS,EAAK,GACpC,CAAC,GAAO,KAAY,EAAwB,IAAI;CAEtD,QAAsB;EACf,MAIL,EAAW,EAAI,GACf,EAAS,IAAI,GAEb,EAA2B,CAAe,CAAC,CACxC,MAAM,MAAyB;GAC9B,EAAc,CAAc;EAC9B,CAAC,CAAC,CACD,OAAO,MAAgC;GACtC,EAAS,EAAiB,CAAY,CAAC;EACzC,CAAC,CAAC,CACD,cAAoB;GACnB,EAAW,EAAK;EAClB,CAAC;CACL,GAAG,CAAC,CAAe,CAAC;CAEpB,eAAe,EACb,GACe;EACf,IAAI,CAAC,KAAmB,GACtB;EAGF,IAAM,IAAqB;EAE3B,AADA,EAAc,CAAc,GAC5B,EAAU,EAAI;EAEd,IAAI;GACF,EACE,MAAM,EAA6B;IACjC,iBAAiB,EAAe;IAChC,cAAc,EAAe;IAC7B,cAAc,EAAe;IAC7B,UAAU;IACV,eAAe,EAAe;IAC9B,iBAAiB,EAAe;GAClC,CAAC,CACH;EACF,SAAS,GAAuB;GAE9B,AADA,EAAc,CAAkB,GAChC,EAAS,EAAiB,CAAY,CAAC;EACzC,UAAU;GACR,EAAU,EAAK;EACjB;CACF;CAEA,IAAM,IAAmB,KAAW;CAEpC,OACE,kBAAA,GAAA,EAAA,UAAA,CACI,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EACE,aAAY;EACZ,OAAM;CACP,CAAA,EACS,CAAA,GAEZ,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EACE,YACE,kBAAC,GAAD;GAAY,WAAW,EAAO;GAAkB,SAAS;GACvD,UAAA,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KAAQ,UAAU;KAAK,MAAM;KAC3B,UAAA,kBAAC,GAAD;MACE,QAAQ,EAAgB;MACxB,MAAK;MACL,OAAO;MAEP,UAAA,kBAAC,OAAD;OAAK,WAAW,EAAO;OAAvB,UAAA,CACE,kBAAC,QAAD;QAAM,WAAW,EAAO;QAAoB,UAAA;OAEtC,CAAA,GACN,kBAAC,GAAD;QACE,UAAU;QACV,MAAK;QACL,WACE,MACS;SACT,EAA4B;UAC1B,GAAG;UACH,cAAc,EACZ,EAAM,OAAO,KACf;SACF,CAAC;QACH;QACA,SAAS,CAAC,GAAG,CAAuB;QACpC,MAAK;QACL,MAAK;QACL,OAAO,EACL,EAAW,YACb;OACD,CAAA,CACE;;KACI,CAAA;IACL,CAAA;IACR,kBAAC,GAAD;KAAQ,UAAU;KAAK,MAAM;KAC3B,UAAA,kBAAC,GAAD;MACE,QAAQ,EAAgB;MACxB,MAAK;MACL,OAAO;MAEP,UAAA,kBAAC,OAAD;OAAK,WAAW,EAAO;OAAvB,UAAA,CACE,kBAAC,QAAD;QAAM,WAAW,EAAO;QAAoB,UAAA;OAEtC,CAAA,GACN,kBAAC,GAAD;QACE,UAAU;QACV,MAAK;QACL,WACE,MACS;SACT,EAA4B;UAC1B,GAAG;UACH,cAAc,EACZ,EAAM,OAAO,KACf;SACF,CAAC;QACH;QACA,SAAS,CAAC,GAAG,CAAuB;QACpC,MAAK;QACL,MAAK;QACL,OAAO,EACL,EAAW,YACb;OACD,CAAA,CACE;;KACI,CAAA;IACL,CAAA;IACR,kBAAC,GAAD;KAAQ,UAAU;KAAK,MAAM;KAC3B,UAAA,kBAAC,GAAD;MACE,WAAA;MACA,QAAQ,EAAgB;MACxB,MAAK;MACL,OAAO;MAEP,UAAA,kBAAC,OAAD;OAAK,WAAW,EAAO;OAAvB,UAAA,CACE,kBAAC,QAAD;QAAM,WAAW,EAAO;QAAoB,UAAA;OAEtC,CAAA,GACN,kBAAC,GAAD;QACE,UAAU;QACV,MAAK;QACL,WACE,MACS;SACT,EAA4B;UAC1B,GAAG;UACH,iBAAiB,EACf,EAAM,OAAO,KACf;SACF,CAAC;QACH;QACA,SAAS,CAAC,GAAG,CAAc;QAC3B,MAAK;QACL,MAAK;QACL,OAAO,EAAW;OACnB,CAAA,CACE;;KACI,CAAA;IACL,CAAA;GACE,EAAA,CAAA;EACF,CAAA;EAGb,UAAA,IACC,kBAAC,GAAD;GAAY,OAAM;GAAa,SAAQ;GACpC,UAAA;EACS,CAAA,IAEZ,kBAAC,GAAD;GAAY,OAAM;GAAe,SAAQ;GAAO,UAAA;EAEpC,CAAA;CAEP,CAAA,EACG,CAAA,CACd,EAAA,CAAA;AAER;AAEA,IAAM,IAAqB;CACzB,UAAU;CACV,YAAY;AACd;AAEA,SAAS,EAAe,GAAwC;CAC9D,OAAO,MAAU,UAAU,UAAU;AACvC;AAEA,SAAS,EAAwB,GAAyB;CACxD,OAAO,MAAU;AACnB;AAEA,SAAS,EAA0B,GAAuC;CACxE,OAAO,IAAU,OAAO;AAC1B;AAEA,SAAS,EAAiB,GAAwB;CAChD,OAAO,aAAiB,QAAQ,EAAM,UAAU;AAClD"}
|
|
@@ -162,7 +162,8 @@ var W = {
|
|
|
162
162
|
s,
|
|
163
163
|
i
|
|
164
164
|
]), l(() => {
|
|
165
|
-
|
|
165
|
+
let e = xe(a);
|
|
166
|
+
x(e), C(/* @__PURE__ */ new Set()), T(/* @__PURE__ */ new Map()), y(null), _(null), m(!1);
|
|
166
167
|
}, [a]), l(() => {
|
|
167
168
|
D(R.nodes);
|
|
168
169
|
}, [R.nodes]), l(() => {
|
|
@@ -554,12 +555,12 @@ function qe(e, t) {
|
|
|
554
555
|
if (n) return Array.from(document.querySelectorAll(".react-flow__node[data-id]")).map((e) => {
|
|
555
556
|
let r = e.dataset.id;
|
|
556
557
|
if (!r || r === t) return null;
|
|
557
|
-
let i = e.getBoundingClientRect()
|
|
558
|
+
let i = e.getBoundingClientRect(), a = {
|
|
559
|
+
x: i.left + i.width / 2,
|
|
560
|
+
y: i.top + i.height / 2
|
|
561
|
+
};
|
|
558
562
|
return {
|
|
559
|
-
distance: Ze(n,
|
|
560
|
-
x: i.left + i.width / 2,
|
|
561
|
-
y: i.top + i.height / 2
|
|
562
|
-
}),
|
|
563
|
+
distance: Ze(n, a),
|
|
563
564
|
id: r
|
|
564
565
|
};
|
|
565
566
|
}).filter((e) => !!e).filter((e) => e.distance <= Oe).sort((e, t) => e.distance - t.distance)[0]?.id;
|
|
@@ -690,8 +691,8 @@ function pt() {
|
|
|
690
691
|
positionPage: Ve,
|
|
691
692
|
positionPageSize: Ue,
|
|
692
693
|
positionSearchText: Ge
|
|
693
|
-
});
|
|
694
|
-
z(
|
|
694
|
+
}), t = Ot(e.memberships, e.managerResolutions), n = await j(t);
|
|
695
|
+
z(n), Pe(e.orgUnits), ke(e.orgUnitCount), Ie(e.filteredOrgUnits), Xe(e.positions), Je(e.positionCount), Re(e.filteredPositions), Ce(e.membershipCount), et(e.filteredMemberships), L(e.managerResolutionCount), Qe(e.filteredManagerResolutions);
|
|
695
696
|
} catch (e) {
|
|
696
697
|
s(Zt(e));
|
|
697
698
|
} finally {
|
|
@@ -1970,7 +1971,7 @@ function Dt({ label: t, name: n, onChange: r, placeholder: i, value: a }) {
|
|
|
1970
1971
|
});
|
|
1971
1972
|
}
|
|
1972
1973
|
function Ot(e, t) {
|
|
1973
|
-
return [
|
|
1974
|
+
return [.../* @__PURE__ */ new Set([
|
|
1974
1975
|
...e.map((e) => e.memberId),
|
|
1975
1976
|
...t.map((e) => e.managerMemberId),
|
|
1976
1977
|
...t.filter((e) => e.scopeType === "MEMBER").map((e) => e.scopeId)
|
|
@@ -2069,4 +2070,4 @@ function Zt(e) {
|
|
|
2069
2070
|
//#endregion
|
|
2070
2071
|
export { pt as t };
|
|
2071
2072
|
|
|
2072
|
-
//# sourceMappingURL=orgs-
|
|
2073
|
+
//# sourceMappingURL=orgs-B930facb.js.map
|