@burdenoff/microfe-organization 2026.527.1 → 2026.527.2

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.
@@ -53,6 +53,7 @@ function s({ organizationId: s, onSubmit: c, onCancel: l, loading: u }) {
53
53
  ...f,
54
54
  email: e.target.value
55
55
  }),
56
+ "aria-label": d("members.invite.emailLabel", "Email Address"),
56
57
  className: "\n w-full pl-10 pr-4 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
57
58
  placeholder: d("members.invite.emailPlaceholder", "user@example.com"),
58
59
  disabled: u,
@@ -86,6 +87,7 @@ function s({ organizationId: s, onSubmit: c, onCancel: l, loading: u }) {
86
87
  ...f,
87
88
  message: e.target.value
88
89
  }),
90
+ "aria-label": d("members.invite.messageLabel", "Personal Message"),
89
91
  className: "\n w-full px-4 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n resize-none\n ",
90
92
  placeholder: d("members.invite.messagePlaceholder", "Add a personal message to the invite..."),
91
93
  rows: 3,
@@ -1 +1 @@
1
- {"version":3,"file":"InviteMemberForm.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/InviteMemberForm.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Loader2, Mail, Send } from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface InviteMemberFormProps {\n organizationId: string;\n onSubmit: (data: { organizationId: string; email: string; message?: string }) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\n/**\n * Invite Member Form\n * Email-based form for inviting users to an organization.\n * Role defaults to MEMBER on the backend — admin can change it after the user joins.\n */\nexport function InviteMemberForm({\n organizationId,\n onSubmit,\n onCancel,\n loading,\n}: InviteMemberFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n email: '',\n message: '',\n });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await onSubmit({\n organizationId,\n email: formData.email,\n message: formData.message || undefined,\n });\n setFormData({ email: '', message: '' });\n } catch {\n // Error is surfaced by the parent page via the mutation hook's error state\n }\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Email */}\n <div>\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('members.invite.emailLabel', 'Email Address')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <Mail size={16} className=\"text-[var(--color-text-muted)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={(e) => setFormData({ ...formData, email: e.target.value })}\n className=\"\n w-full pl-10 pr-4 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('members.invite.emailPlaceholder', 'user@example.com')}\n disabled={loading}\n required\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-muted)] mt-1\">\n {tr(\n 'members.invite.emailHelp',\n 'The user will be invited as a Member. You can change their role after they join.'\n )}\n </p>\n </div>\n\n {/* Message (optional) */}\n <div>\n <label\n htmlFor=\"message\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('members.invite.messageLabel', 'Personal Message')}{' '}\n <span className=\"text-[var(--color-text-muted)]\">\n ({tr('common.optional', 'optional')})\n </span>\n </label>\n <textarea\n id=\"message\"\n value={formData.message}\n onChange={(e) => setFormData({ ...formData, message: e.target.value })}\n className=\"\n w-full px-4 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n resize-none\n \"\n placeholder={tr(\n 'members.invite.messagePlaceholder',\n 'Add a personal message to the invite...'\n )}\n rows={3}\n maxLength={500}\n disabled={loading}\n />\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90 hover:scale-[1.02]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[180px]\n \"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : <Send size={16} />}\n {tr('members.invite.send', 'Send Invite')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":";;;;;AAgBA,SAAgB,EAAiB,EAC/B,mBACA,aACA,aACA,cACwB;CACxB,IAAM,IAAK,GAAO,EACZ,CAAC,GAAU,KAAe,EAAS;EACvC,OAAO;EACP,SAAS;EACV,CAAC;AAgBF,QACE,kBAAC,QAAD;EAAM,UAfa,OAAO,MAAuB;AACjD,KAAE,gBAAgB;AAClB,OAAI;AAMF,IALA,MAAM,EAAS;KACb;KACA,OAAO,EAAS;KAChB,SAAS,EAAS,WAAW,KAAA;KAC9B,CAAC,EACF,EAAY;KAAE,OAAO;KAAI,SAAS;KAAI,CAAC;WACjC;;EAMsB,WAAU;YAAxC;GAEE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;eAFZ;MAIG,EAAG,6BAA6B,gBAAgB;MAAE;MACnD,kBAAC,QAAD;OAAM,WAAU;iBAAwC;OAAQ,CAAA;MAC1D;;IACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAM,MAAM;OAAI,WAAU;OAAmC,CAAA;MACzD,CAAA,EACN,kBAAC,SAAD;MACE,IAAG;MACH,MAAK;MACL,OAAO,EAAS;MAChB,WAAW,MAAM,EAAY;OAAE,GAAG;OAAU,OAAO,EAAE,OAAO;OAAO,CAAC;MACpE,WAAU;MAUV,aAAa,EAAG,mCAAmC,mBAAmB;MACtE,UAAU;MACV,UAAA;MACA,CAAA,CACE;;IACN,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,4BACA,mFACD;KACC,CAAA;IACA,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IACE,SAAQ;IACR,WAAU;cAFZ;KAIG,EAAG,+BAA+B,mBAAmB;KAAE;KACxD,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OAAiD;OAC7C,EAAG,mBAAmB,WAAW;OAAC;OAC/B;;KACD;OACR,kBAAC,YAAD;IACE,IAAG;IACH,OAAO,EAAS;IAChB,WAAW,MAAM,EAAY;KAAE,GAAG;KAAU,SAAS,EAAE,OAAO;KAAO,CAAC;IACtE,WAAU;IAWV,aAAa,EACX,qCACA,0CACD;IACD,MAAM;IACN,WAAW;IACX,UAAU;IACV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU;KACV,WAAU;eAHZ,CAgBG,IAAU,kBAAC,GAAD;MAAS,MAAM;MAAI,WAAU;MAAiB,CAAA,GAAG,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EAC7E,EAAG,uBAAuB,cAAc,CAClC;QAET,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU;KACV,WAAU;eAcT,EAAG,iBAAiB,SAAS;KACvB,CAAA,CACL;;GACD"}
1
+ {"version":3,"file":"InviteMemberForm.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/InviteMemberForm.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Loader2, Mail, Send } from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface InviteMemberFormProps {\n organizationId: string;\n onSubmit: (data: { organizationId: string; email: string; message?: string }) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\n/**\n * Invite Member Form\n * Email-based form for inviting users to an organization.\n * Role defaults to MEMBER on the backend — admin can change it after the user joins.\n */\nexport function InviteMemberForm({\n organizationId,\n onSubmit,\n onCancel,\n loading,\n}: InviteMemberFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n email: '',\n message: '',\n });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await onSubmit({\n organizationId,\n email: formData.email,\n message: formData.message || undefined,\n });\n setFormData({ email: '', message: '' });\n } catch {\n // Error is surfaced by the parent page via the mutation hook's error state\n }\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Email */}\n <div>\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('members.invite.emailLabel', 'Email Address')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <Mail size={16} className=\"text-[var(--color-text-muted)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={(e) => setFormData({ ...formData, email: e.target.value })}\n aria-label={tr('members.invite.emailLabel', 'Email Address')}\n className=\"\n w-full pl-10 pr-4 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('members.invite.emailPlaceholder', 'user@example.com')}\n disabled={loading}\n required\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-muted)] mt-1\">\n {tr(\n 'members.invite.emailHelp',\n 'The user will be invited as a Member. You can change their role after they join.'\n )}\n </p>\n </div>\n\n {/* Message (optional) */}\n <div>\n <label\n htmlFor=\"message\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('members.invite.messageLabel', 'Personal Message')}{' '}\n <span className=\"text-[var(--color-text-muted)]\">\n ({tr('common.optional', 'optional')})\n </span>\n </label>\n <textarea\n id=\"message\"\n value={formData.message}\n onChange={(e) => setFormData({ ...formData, message: e.target.value })}\n aria-label={tr('members.invite.messageLabel', 'Personal Message')}\n className=\"\n w-full px-4 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n resize-none\n \"\n placeholder={tr(\n 'members.invite.messagePlaceholder',\n 'Add a personal message to the invite...'\n )}\n rows={3}\n maxLength={500}\n disabled={loading}\n />\n </div>\n\n {/* Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90 hover:scale-[1.02]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[180px]\n \"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : <Send size={16} />}\n {tr('members.invite.send', 'Send Invite')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":";;;;;AAgBA,SAAgB,EAAiB,EAC/B,mBACA,aACA,aACA,cACwB;CACxB,IAAM,IAAK,GAAO,EACZ,CAAC,GAAU,KAAe,EAAS;EACvC,OAAO;EACP,SAAS;EACV,CAAC;AAgBF,QACE,kBAAC,QAAD;EAAM,UAfa,OAAO,MAAuB;AACjD,KAAE,gBAAgB;AAClB,OAAI;AAMF,IALA,MAAM,EAAS;KACb;KACA,OAAO,EAAS;KAChB,SAAS,EAAS,WAAW,KAAA;KAC9B,CAAC,EACF,EAAY;KAAE,OAAO;KAAI,SAAS;KAAI,CAAC;WACjC;;EAMsB,WAAU;YAAxC;GAEE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;eAFZ;MAIG,EAAG,6BAA6B,gBAAgB;MAAE;MACnD,kBAAC,QAAD;OAAM,WAAU;iBAAwC;OAAQ,CAAA;MAC1D;;IACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAM,MAAM;OAAI,WAAU;OAAmC,CAAA;MACzD,CAAA,EACN,kBAAC,SAAD;MACE,IAAG;MACH,MAAK;MACL,OAAO,EAAS;MAChB,WAAW,MAAM,EAAY;OAAE,GAAG;OAAU,OAAO,EAAE,OAAO;OAAO,CAAC;MACpE,cAAY,EAAG,6BAA6B,gBAAgB;MAC5D,WAAU;MAUV,aAAa,EAAG,mCAAmC,mBAAmB;MACtE,UAAU;MACV,UAAA;MACA,CAAA,CACE;;IACN,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,4BACA,mFACD;KACC,CAAA;IACA,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IACE,SAAQ;IACR,WAAU;cAFZ;KAIG,EAAG,+BAA+B,mBAAmB;KAAE;KACxD,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OAAiD;OAC7C,EAAG,mBAAmB,WAAW;OAAC;OAC/B;;KACD;OACR,kBAAC,YAAD;IACE,IAAG;IACH,OAAO,EAAS;IAChB,WAAW,MAAM,EAAY;KAAE,GAAG;KAAU,SAAS,EAAE,OAAO;KAAO,CAAC;IACtE,cAAY,EAAG,+BAA+B,mBAAmB;IACjE,WAAU;IAWV,aAAa,EACX,qCACA,0CACD;IACD,MAAM;IACN,WAAW;IACX,UAAU;IACV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU;KACV,WAAU;eAHZ,CAgBG,IAAU,kBAAC,GAAD;MAAS,MAAM;MAAI,WAAU;MAAiB,CAAA,GAAG,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EAC7E,EAAG,uBAAuB,cAAc,CAClC;QAET,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU;KACV,WAAU;eAcT,EAAG,iBAAiB,SAAS;KACvB,CAAA,CACL;;GACD"}
@@ -166,6 +166,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
166
166
  ...w,
167
167
  tenantId: e.target.value
168
168
  }),
169
+ "aria-label": x("organizations.form.tenant", "Tenant"),
169
170
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
170
171
  placeholder: x("organizations.form.tenantPlaceholder", "Enter tenant ID"),
171
172
  disabled: b,
@@ -198,6 +199,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
198
199
  ...w,
199
200
  name: e.target.value
200
201
  }),
202
+ "aria-label": x("organizations.form.name", "Organization Name"),
201
203
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
202
204
  placeholder: x("organizations.form.namePlaceholder", "Enter organization name"),
203
205
  disabled: b,
@@ -226,6 +228,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
226
228
  id: "slug",
227
229
  type: "text",
228
230
  value: w.slug,
231
+ "aria-label": x("common.slug", "Slug"),
229
232
  onChange: (e) => {
230
233
  k(!0), N.current = null, P.current = null, T({
231
234
  ...w,
@@ -258,6 +261,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
258
261
  ...w,
259
262
  description: e.target.value
260
263
  }),
264
+ "aria-label": x("common.description", "Description"),
261
265
  rows: 4,
262
266
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
263
267
  placeholder: x("organizations.form.descriptionPlaceholder", "Enter organization description"),
@@ -276,6 +280,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
276
280
  ...w,
277
281
  logo: e.target.value
278
282
  }),
283
+ "aria-label": x("organizations.form.logoUrl", "Logo URL"),
279
284
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
280
285
  placeholder: "https://example.com/logo.png",
281
286
  disabled: b,
@@ -293,6 +298,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
293
298
  ...w,
294
299
  website: e.target.value
295
300
  }),
301
+ "aria-label": x("organizations.detail.website", "Website"),
296
302
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
297
303
  placeholder: "https://example.com",
298
304
  disabled: b,
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationForm.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/components/OrganizationForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport { useGetOrganizationBySlugLazyQuery } from '../../../../generated/global-operations';\nimport { useDebouncedValue } from '../../../shared/hooks/useDebouncedValue';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateOrganizationInput, UpdateOrganizationInput, Organization } from '../types';\nimport { createOrganizationSchema, updateOrganizationSchema } from '../schemas';\n\ninterface OrganizationFormProps {\n organization?: Organization | null;\n tenantId?: string;\n tenantName?: string;\n onSubmit: (data: CreateOrganizationInput | UpdateOrganizationInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\ntype SlugStatus =\n | { tone: 'muted'; message: string }\n | { tone: 'success'; message: string }\n | { tone: 'warning'; message: string };\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\nfunction getSlugStatusClassName(tone: SlugStatus['tone']): string {\n switch (tone) {\n case 'success':\n return 'text-[var(--color-status-success-text)]';\n case 'warning':\n return 'text-[var(--color-status-warning-text)]';\n default:\n return 'text-[var(--color-text-muted)]';\n }\n}\n\nexport function OrganizationForm({\n organization,\n tenantId,\n tenantName,\n onSubmit,\n onCancel,\n loading,\n}: OrganizationFormProps) {\n const tr = useTr();\n const isCreateMode = !organization;\n const currentOrganizationId = organization?.id ?? null;\n const [formData, setFormData] = useState({\n name: organization?.name || '',\n slug: organization?.slug || '',\n description: organization?.description || '',\n logo: organization?.logo || '',\n website: organization?.website || '',\n tenantId: tenantId || organization?.tenantId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [hasManualSlugEdits, setHasManualSlugEdits] = useState(false);\n const [slugStatus, setSlugStatus] = useState<SlugStatus>({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n const slugRequestIdRef = useRef(0);\n const lastAdjustedSlugRef = useRef<string | null>(null);\n const lastAdjustedBaseSlugRef = useRef<string | null>(null);\n const debouncedSlug = useDebouncedValue(formData.slug, 350);\n const [fetchOrganizationBySlug] = useGetOrganizationBySlugLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n const tenantDisplayValue = useMemo(() => {\n if (!formData.tenantId) {\n return '';\n }\n\n if (tenantName) {\n return `${tenantName} (${formData.tenantId})`;\n }\n\n return formData.tenantId;\n }, [formData.tenantId, tenantName]);\n\n useEffect(() => {\n if (!isCreateMode || tenantId === undefined) {\n return;\n }\n\n setFormData((previousState) => {\n if (previousState.tenantId === tenantId) {\n return previousState;\n }\n\n return {\n ...previousState,\n tenantId,\n };\n });\n }, [isCreateMode, tenantId]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n const generatedSlug = slugify(formData.name);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n\n if (!hasManualSlugEdits) {\n setFormData((previousState) => ({\n ...previousState,\n slug: generatedSlug,\n }));\n }\n\n if (!generatedSlug) {\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n }\n }, [formData.name, hasManualSlugEdits, isCreateMode, tr]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n if (!formData.tenantId || !debouncedSlug) {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n return;\n }\n\n if (lastAdjustedSlugRef.current === debouncedSlug && lastAdjustedBaseSlugRef.current !== null) {\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${debouncedSlug}” ${tr('organizations.form.slugBecause', 'because')} “${lastAdjustedBaseSlugRef.current}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n return;\n }\n\n let isCancelled = false;\n const requestId = ++slugRequestIdRef.current;\n\n const resolveAvailableSlug = async () => {\n setSlugStatus({\n tone: 'muted',\n message: tr('organizations.form.slugChecking', 'Checking slug availability…'),\n });\n\n const baseSlug = debouncedSlug;\n let suffix = 2;\n let candidateSlug = baseSlug;\n\n while (true) {\n const result = await fetchOrganizationBySlug({\n variables: {\n tenantId: formData.tenantId,\n slug: candidateSlug,\n },\n });\n\n if (isCancelled || slugRequestIdRef.current !== requestId) {\n return;\n }\n\n const existingOrganizationId = (\n result.data?.organizationBySlug as { id?: string } | null | undefined\n )?.id;\n if (!existingOrganizationId || existingOrganizationId === currentOrganizationId) {\n if (candidateSlug !== baseSlug) {\n lastAdjustedSlugRef.current = candidateSlug;\n lastAdjustedBaseSlugRef.current = baseSlug;\n setFormData((previousState) => {\n if (previousState.slug !== baseSlug) {\n return previousState;\n }\n\n return {\n ...previousState,\n slug: candidateSlug,\n };\n });\n\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${candidateSlug}” ${tr('organizations.form.slugBecause', 'because')} “${baseSlug}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n } else {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'success',\n message: `${tr('organizations.form.slugPrefix', 'Slug')} “${candidateSlug}” ${tr('organizations.form.slugAvailable', 'is available.')}`,\n });\n }\n\n return;\n }\n\n candidateSlug = `${baseSlug}-${suffix}`;\n suffix += 1;\n }\n };\n\n void resolveAvailableSlug();\n\n return () => {\n isCancelled = true;\n };\n }, [\n currentOrganizationId,\n debouncedSlug,\n fetchOrganizationBySlug,\n formData.tenantId,\n isCreateMode,\n tr,\n ]);\n\n const validate = () => {\n try {\n const schema = organization ? updateOrganizationSchema : createOrganizationSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (error) {\n if (error instanceof ZodError) {\n const newErrors: Record<string, string> = {};\n error.issues.forEach((issue) => {\n const path = issue.path.join('.');\n newErrors[path] = issue.message;\n });\n setErrors(newErrors);\n }\n return false;\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = organization\n ? {\n // Update only changed fields\n name: formData.name !== organization.name ? formData.name : undefined,\n description:\n formData.description !== organization.description ? formData.description : undefined,\n logo: formData.logo !== organization.logo ? formData.logo : undefined,\n website: formData.website !== organization.website ? formData.website : undefined,\n }\n : {\n // Create with all fields\n tenantId: formData.tenantId,\n name: formData.name,\n slug: formData.slug,\n description: formData.description || undefined,\n logo: formData.logo || undefined,\n website: formData.website || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Tenant ID (only for create) */}\n {isCreateMode && (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n {tenantId ? (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={tenantDisplayValue}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-muted)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n disabled:cursor-not-allowed\n \"\n disabled\n readOnly\n aria-label={tr('organizations.form.selectedTenant', 'Selected tenant')}\n />\n ) : (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={formData.tenantId}\n onChange={(e) => setFormData({ ...formData, tenantId: e.target.value })}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.tenantPlaceholder', 'Enter tenant ID')}\n disabled={loading}\n required\n autoComplete=\"off\"\n />\n )}\n {errors.tenantId && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.tenantId}</p>\n )}\n </div>\n )}\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"name\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.name', 'Organization Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData({ ...formData, name: e.target.value })}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.namePlaceholder', 'Enter organization name')}\n disabled={loading}\n required\n autoComplete=\"organization\"\n />\n {errors.name && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n )}\n </div>\n\n {/* Slug */}\n <div>\n <label\n htmlFor=\"slug\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n onChange={(e) => {\n setHasManualSlugEdits(true);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setFormData({\n ...formData,\n slug: slugify(e.target.value),\n });\n }}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.slugPlaceholder', 'organization-slug')}\n disabled={loading || !!organization}\n required={!organization}\n autoComplete=\"off\"\n />\n <p\n className={`text-xs mt-1 ${getSlugStatusClassName(\n isCreateMode ? slugStatus.tone : 'muted'\n )}`}\n >\n {isCreateMode\n ? slugStatus.message\n : tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n )}\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"description\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.description', 'Description')}\n </label>\n <textarea\n id=\"description\"\n value={formData.description}\n onChange={(e) => setFormData({ ...formData, description: e.target.value })}\n rows={4}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr(\n 'organizations.form.descriptionPlaceholder',\n 'Enter organization description'\n )}\n disabled={loading}\n autoComplete=\"off\"\n />\n </div>\n\n {/* Logo URL */}\n <div>\n <label\n htmlFor=\"logo\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.logoUrl', 'Logo URL')}\n </label>\n <input\n id=\"logo\"\n type=\"url\"\n value={formData.logo}\n onChange={(e) => setFormData({ ...formData, logo: e.target.value })}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com/logo.png\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Website */}\n <div>\n <label\n htmlFor=\"website\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.detail.website', 'Website')}\n </label>\n <input\n id=\"website\"\n type=\"url\"\n value={formData.website}\n onChange={(e) => setFormData({ ...formData, website: e.target.value })}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Form Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90 hover:scale-[1.02]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[220px]\n \"\n >\n {loading && <Loader2 size={16} className=\"animate-spin\" />}\n {organization\n ? tr('organizations.form.updateSubmit', 'Update Organization')\n : tr('organizations.create.button', 'Create Organization')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"OrganizationForm.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/components/OrganizationForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport { useGetOrganizationBySlugLazyQuery } from '../../../../generated/global-operations';\nimport { useDebouncedValue } from '../../../shared/hooks/useDebouncedValue';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateOrganizationInput, UpdateOrganizationInput, Organization } from '../types';\nimport { createOrganizationSchema, updateOrganizationSchema } from '../schemas';\n\ninterface OrganizationFormProps {\n organization?: Organization | null;\n tenantId?: string;\n tenantName?: string;\n onSubmit: (data: CreateOrganizationInput | UpdateOrganizationInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\ntype SlugStatus =\n | { tone: 'muted'; message: string }\n | { tone: 'success'; message: string }\n | { tone: 'warning'; message: string };\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\nfunction getSlugStatusClassName(tone: SlugStatus['tone']): string {\n switch (tone) {\n case 'success':\n return 'text-[var(--color-status-success-text)]';\n case 'warning':\n return 'text-[var(--color-status-warning-text)]';\n default:\n return 'text-[var(--color-text-muted)]';\n }\n}\n\nexport function OrganizationForm({\n organization,\n tenantId,\n tenantName,\n onSubmit,\n onCancel,\n loading,\n}: OrganizationFormProps) {\n const tr = useTr();\n const isCreateMode = !organization;\n const currentOrganizationId = organization?.id ?? null;\n // react-doctor-disable-next-line react-doctor/no-event-handler\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: organization?.name || '',\n slug: organization?.slug || '',\n description: organization?.description || '',\n logo: organization?.logo || '',\n website: organization?.website || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || organization?.tenantId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n // react-doctor-disable-next-line react-doctor/rerender-state-only-in-handlers\n const [hasManualSlugEdits, setHasManualSlugEdits] = useState(false);\n const [slugStatus, setSlugStatus] = useState<SlugStatus>({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n const slugRequestIdRef = useRef(0);\n const lastAdjustedSlugRef = useRef<string | null>(null);\n const lastAdjustedBaseSlugRef = useRef<string | null>(null);\n const debouncedSlug = useDebouncedValue(formData.slug, 350);\n const [fetchOrganizationBySlug] = useGetOrganizationBySlugLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n const tenantDisplayValue = useMemo(() => {\n if (!formData.tenantId) {\n return '';\n }\n\n if (tenantName) {\n return `${tenantName} (${formData.tenantId})`;\n }\n\n return formData.tenantId;\n }, [formData.tenantId, tenantName]);\n\n useEffect(() => {\n if (!isCreateMode || tenantId === undefined) {\n return;\n }\n\n setFormData((previousState) => {\n if (previousState.tenantId === tenantId) {\n return previousState;\n }\n\n return {\n ...previousState,\n tenantId,\n };\n });\n }, [isCreateMode, tenantId]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n const generatedSlug = slugify(formData.name);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (!hasManualSlugEdits) {\n setFormData((previousState) => ({\n ...previousState,\n slug: generatedSlug,\n }));\n }\n\n if (!generatedSlug) {\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n }\n }, [formData.name, hasManualSlugEdits, isCreateMode, tr]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n if (!formData.tenantId || !debouncedSlug) {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n return;\n }\n\n if (lastAdjustedSlugRef.current === debouncedSlug && lastAdjustedBaseSlugRef.current !== null) {\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${debouncedSlug}” ${tr('organizations.form.slugBecause', 'because')} “${lastAdjustedBaseSlugRef.current}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n return;\n }\n\n let isCancelled = false;\n const requestId = ++slugRequestIdRef.current;\n\n const resolveAvailableSlug = async () => {\n setSlugStatus({\n tone: 'muted',\n message: tr('organizations.form.slugChecking', 'Checking slug availability…'),\n });\n\n const baseSlug = debouncedSlug;\n let suffix = 2;\n let candidateSlug = baseSlug;\n\n while (true) {\n const result = await fetchOrganizationBySlug({\n variables: {\n tenantId: formData.tenantId,\n slug: candidateSlug,\n },\n });\n\n if (isCancelled || slugRequestIdRef.current !== requestId) {\n return;\n }\n\n const existingOrganizationId = (\n result.data?.organizationBySlug as { id?: string } | null | undefined\n )?.id;\n if (!existingOrganizationId || existingOrganizationId === currentOrganizationId) {\n if (candidateSlug !== baseSlug) {\n lastAdjustedSlugRef.current = candidateSlug;\n lastAdjustedBaseSlugRef.current = baseSlug;\n setFormData((previousState) => {\n if (previousState.slug !== baseSlug) {\n return previousState;\n }\n\n return {\n ...previousState,\n slug: candidateSlug,\n };\n });\n\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${candidateSlug}” ${tr('organizations.form.slugBecause', 'because')} “${baseSlug}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n } else {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'success',\n message: `${tr('organizations.form.slugPrefix', 'Slug')} “${candidateSlug}” ${tr('organizations.form.slugAvailable', 'is available.')}`,\n });\n }\n\n return;\n }\n\n candidateSlug = `${baseSlug}-${suffix}`;\n suffix += 1;\n }\n };\n\n void resolveAvailableSlug();\n\n return () => {\n isCancelled = true;\n };\n }, [\n currentOrganizationId,\n debouncedSlug,\n fetchOrganizationBySlug,\n formData.tenantId,\n isCreateMode,\n tr,\n ]);\n\n const validate = () => {\n try {\n const schema = organization ? updateOrganizationSchema : createOrganizationSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (error) {\n if (error instanceof ZodError) {\n const newErrors: Record<string, string> = {};\n error.issues.forEach((issue) => {\n const path = issue.path.join('.');\n newErrors[path] = issue.message;\n });\n setErrors(newErrors);\n }\n return false;\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = organization\n ? {\n // Update only changed fields\n name: formData.name !== organization.name ? formData.name : undefined,\n description:\n formData.description !== organization.description ? formData.description : undefined,\n logo: formData.logo !== organization.logo ? formData.logo : undefined,\n website: formData.website !== organization.website ? formData.website : undefined,\n }\n : {\n // Create with all fields\n tenantId: formData.tenantId,\n name: formData.name,\n slug: formData.slug,\n description: formData.description || undefined,\n logo: formData.logo || undefined,\n website: formData.website || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Tenant ID (only for create) */}\n {isCreateMode && (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n {tenantId ? (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={tenantDisplayValue}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-muted)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n disabled:cursor-not-allowed\n \"\n disabled\n readOnly\n aria-label={tr('organizations.form.selectedTenant', 'Selected tenant')}\n />\n ) : (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={formData.tenantId}\n onChange={(e) => setFormData({ ...formData, tenantId: e.target.value })}\n aria-label={tr('organizations.form.tenant', 'Tenant')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.tenantPlaceholder', 'Enter tenant ID')}\n disabled={loading}\n required\n autoComplete=\"off\"\n />\n )}\n {errors.tenantId && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.tenantId}</p>\n )}\n </div>\n )}\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"name\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.name', 'Organization Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData({ ...formData, name: e.target.value })}\n aria-label={tr('organizations.form.name', 'Organization Name')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.namePlaceholder', 'Enter organization name')}\n disabled={loading}\n required\n autoComplete=\"organization\"\n />\n {errors.name && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n )}\n </div>\n\n {/* Slug */}\n <div>\n <label\n htmlFor=\"slug\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n aria-label={tr('common.slug', 'Slug')}\n onChange={(e) => {\n setHasManualSlugEdits(true);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setFormData({\n ...formData,\n slug: slugify(e.target.value),\n });\n }}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.slugPlaceholder', 'organization-slug')}\n disabled={loading || !!organization}\n required={!organization}\n autoComplete=\"off\"\n />\n <p\n className={`text-xs mt-1 ${getSlugStatusClassName(\n isCreateMode ? slugStatus.tone : 'muted'\n )}`}\n >\n {isCreateMode\n ? slugStatus.message\n : tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n )}\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"description\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.description', 'Description')}\n </label>\n <textarea\n id=\"description\"\n value={formData.description}\n onChange={(e) => setFormData({ ...formData, description: e.target.value })}\n aria-label={tr('common.description', 'Description')}\n rows={4}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr(\n 'organizations.form.descriptionPlaceholder',\n 'Enter organization description'\n )}\n disabled={loading}\n autoComplete=\"off\"\n />\n </div>\n\n {/* Logo URL */}\n <div>\n <label\n htmlFor=\"logo\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.logoUrl', 'Logo URL')}\n </label>\n <input\n id=\"logo\"\n type=\"url\"\n value={formData.logo}\n onChange={(e) => setFormData({ ...formData, logo: e.target.value })}\n aria-label={tr('organizations.form.logoUrl', 'Logo URL')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com/logo.png\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Website */}\n <div>\n <label\n htmlFor=\"website\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.detail.website', 'Website')}\n </label>\n <input\n id=\"website\"\n type=\"url\"\n value={formData.website}\n onChange={(e) => setFormData({ ...formData, website: e.target.value })}\n aria-label={tr('organizations.detail.website', 'Website')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Form Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90 hover:scale-[1.02]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[220px]\n \"\n >\n {loading && <Loader2 size={16} className=\"animate-spin\" />}\n {organization\n ? tr('organizations.form.updateSubmit', 'Update Organization')\n : tr('organizations.create.button', 'Create Organization')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useOrgsWithPermission.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/hooks/useOrgsWithPermission.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useAuth } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n CheckRbacPermissionsDocument,\n type CheckRbacPermissionsQuery,\n type CheckRbacPermissionsQueryVariables,\n} from '../../../../generated/global-operations';\nimport { ActorType } from '../../../../generated/global-types';\n\ntype PermissionCheckResult = CheckRbacPermissionsQuery['checkPermissions'][number];\n\n// Matches the server-side `MAX_BULK_CHECK_SIZE` on `checkPermissions`.\n// Kept as a constant so the chunking intent is explicit at the call site.\nconst CHECK_PERMISSIONS_BATCH_LIMIT = 50;\n\n/**\n * Filter a set of organization ids down to those where the current user has\n * a specific RBAC permission. Bulk-checks against the rbac service so the\n * result tracks the authoritative evaluator — including overrides and any\n * future OPA policies — instead of client-side-duplicating the\n * role→permission mapping.\n *\n * The server caps a single bulk call at `CHECK_PERMISSIONS_BATCH_LIMIT`\n * inputs, so when a user has more orgs than that we chunk into multiple\n * parallel queries via `client.query()` and merge results positionally.\n * Most users will fit in one batch; the chunking is defensive for power\n * users / service accounts with many memberships (the WorkspaceForm\n * caller fetches up to 100 orgs, so two batches is realistic).\n *\n * Use this whenever a control surface needs to constrain itself to \"orgs\n * where I can do X\" rather than \"orgs I'm a member of\" — e.g. the org\n * picker on Create Workspace, Invite Member targets, role-assignment\n * dropdowns. The backend `@rbac` directives still enforce on submission;\n * this is purely UX (don't show options that will 403).\n */\nexport function useOrgsWithPermission(\n orgIds: readonly string[],\n resourceType: string,\n action: string\n): { allowedIds: Set<string>; loading: boolean; isError: boolean } {\n const { user } = useAuth();\n const actorId = user?.id ?? '';\n const client = useApolloClient();\n\n // `stableKey` lets memos depend on the *values* of orgIds, not the array\n // reference — callers passing a fresh array with the same ids won't bust\n // the query cache or trigger refetch loops.\n const stableKey = useMemo(() => orgIds.join(''), [orgIds]);\n const stableIds = useMemo(() => [...orgIds], [stableKey]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const inputs = useMemo(\n () =>\n stableIds.map((scopeId) => ({\n actorId,\n actorType: ActorType.User,\n scopeId,\n resourceType,\n action,\n })),\n [stableIds, actorId, resourceType, action]\n );\n\n const skip = !actorId || stableIds.length === 0;\n\n // Fan out the bulk check into <=BATCH_LIMIT-sized chunks, run them in\n // parallel via the Apollo client (so per-chunk caching still works), and\n // concat results in input order to preserve the positional contract.\n // We can't use multiple useQuery hooks here because the chunk count is\n // dynamic, hence the client.query + Promise.all pattern in an effect.\n const [results, setResults] = useState<PermissionCheckResult[]>([]);\n const [loading, setLoading] = useState(false);\n const [hadError, setHadError] = useState(false);\n\n useEffect(() => {\n if (skip) {\n setResults([]);\n setLoading(false);\n setHadError(false);\n return;\n }\n\n let cancelled = false;\n setLoading(true);\n setHadError(false);\n\n const chunks: (typeof inputs)[] = [];\n for (let i = 0; i < inputs.length; i += CHECK_PERMISSIONS_BATCH_LIMIT) {\n chunks.push(inputs.slice(i, i + CHECK_PERMISSIONS_BATCH_LIMIT));\n }\n\n Promise.all(\n chunks.map((chunk) =>\n client.query<CheckRbacPermissionsQuery, CheckRbacPermissionsQueryVariables>({\n query: CheckRbacPermissionsDocument,\n variables: { inputs: chunk },\n fetchPolicy: 'cache-first',\n })\n )\n )\n .then((responses) => {\n if (cancelled) return;\n const merged = responses.flatMap((r) => r.data?.checkPermissions ?? []);\n setResults(merged);\n setLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setResults([]);\n setHadError(true);\n setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, inputs, skip]);\n\n const allowedIds = useMemo(() => {\n if (skip) return new Set<string>();\n const out = new Set<string>();\n // Length-guard the positional mapping so we never silently\n // mis-attribute a permission to the wrong org if the backend ever\n // truncates, reorders, or filters the response.\n const bounded = Math.min(results.length, stableIds.length);\n for (let i = 0; i < bounded; i += 1) {\n if (results[i].allowed && stableIds[i]) out.add(stableIds[i]);\n }\n return out;\n }, [results, stableIds, skip]);\n\n return {\n allowedIds,\n loading: !skip && loading,\n isError: !skip && hadError,\n };\n}\n"],"mappings":";;;;;;AAcA,IAAM,IAAgC;AAsBtC,SAAgB,EACd,GACA,GACA,GACiE;CACjE,IAAM,EAAE,YAAS,GAAS,EACpB,IAAU,GAAM,MAAM,IACtB,IAAS,GAAiB,EAM1B,IAAY,QAAc,CAAC,GAAG,EAAO,EAAE,CAD3B,QAAc,EAAO,KAAK,GAAG,EAAE,CAAC,EAAO,CAAC,CACF,CAAC,EAEnD,IAAS,QAEX,EAAU,KAAK,OAAa;EAC1B;EACA,WAAW,EAAU;EACrB;EACA;EACA;EACD,EAAE,EACL;EAAC;EAAW;EAAS;EAAc;EAAO,CAC3C,EAEK,IAAO,CAAC,KAAW,EAAU,WAAW,GAOxC,CAAC,GAAS,KAAc,EAAkC,EAAE,CAAC,EAC7D,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAU,KAAe,EAAS,GAAM;AA2D/C,QAzDA,QAAgB;AACd,MAAI,GAAM;AAGR,GAFA,EAAW,EAAE,CAAC,EACd,EAAW,GAAM,EACjB,EAAY,GAAM;AAClB;;EAGF,IAAI,IAAY;AAEhB,EADA,EAAW,GAAK,EAChB,EAAY,GAAM;EAElB,IAAM,IAA4B,EAAE;AACpC,OAAK,IAAI,IAAI,GAAG,IAAI,EAAO,QAAQ,KAAK,EACtC,GAAO,KAAK,EAAO,MAAM,GAAG,IAAI,EAA8B,CAAC;AAyBjE,SAtBA,QAAQ,IACN,EAAO,KAAK,MACV,EAAO,MAAqE;GAC1E,OAAO;GACP,WAAW,EAAE,QAAQ,GAAO;GAC5B,aAAa;GACd,CAAC,CACH,CACF,CACE,MAAM,MAAc;AACf,SAEJ,EADe,EAAU,SAAS,MAAM,EAAE,MAAM,oBAAoB,EAAE,CAAC,CACrD,EAClB,EAAW,GAAM;IACjB,CACD,YAAY;AACP,SACJ,EAAW,EAAE,CAAC,EACd,EAAY,GAAK,EACjB,EAAW,GAAM;IACjB,QAES;AACX,OAAY;;IAEb;EAAC;EAAQ;EAAQ;EAAK,CAAC,EAenB;EACL,YAdiB,QAAc;AAC/B,OAAI,EAAM,wBAAO,IAAI,KAAa;GAClC,IAAM,oBAAM,IAAI,KAAa,EAIvB,IAAU,KAAK,IAAI,EAAQ,QAAQ,EAAU,OAAO;AAC1D,QAAK,IAAI,IAAI,GAAG,IAAI,GAAS,KAAK,EAChC,CAAI,EAAQ,GAAG,WAAW,EAAU,MAAI,EAAI,IAAI,EAAU,GAAG;AAE/D,UAAO;KACN;GAAC;GAAS;GAAW;GAAK,CAAC;EAI5B,SAAS,CAAC,KAAQ;EAClB,SAAS,CAAC,KAAQ;EACnB"}
1
+ {"version":3,"file":"useOrgsWithPermission.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/hooks/useOrgsWithPermission.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useAuth } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n CheckRbacPermissionsDocument,\n type CheckRbacPermissionsQuery,\n type CheckRbacPermissionsQueryVariables,\n} from '../../../../generated/global-operations';\nimport { ActorType } from '../../../../generated/global-types';\n\ntype PermissionCheckResult = CheckRbacPermissionsQuery['checkPermissions'][number];\n\n// Matches the server-side `MAX_BULK_CHECK_SIZE` on `checkPermissions`.\n// Kept as a constant so the chunking intent is explicit at the call site.\nconst CHECK_PERMISSIONS_BATCH_LIMIT = 50;\n\n/**\n * Filter a set of organization ids down to those where the current user has\n * a specific RBAC permission. Bulk-checks against the rbac service so the\n * result tracks the authoritative evaluator — including overrides and any\n * future OPA policies — instead of client-side-duplicating the\n * role→permission mapping.\n *\n * The server caps a single bulk call at `CHECK_PERMISSIONS_BATCH_LIMIT`\n * inputs, so when a user has more orgs than that we chunk into multiple\n * parallel queries via `client.query()` and merge results positionally.\n * Most users will fit in one batch; the chunking is defensive for power\n * users / service accounts with many memberships (the WorkspaceForm\n * caller fetches up to 100 orgs, so two batches is realistic).\n *\n * Use this whenever a control surface needs to constrain itself to \"orgs\n * where I can do X\" rather than \"orgs I'm a member of\" — e.g. the org\n * picker on Create Workspace, Invite Member targets, role-assignment\n * dropdowns. The backend `@rbac` directives still enforce on submission;\n * this is purely UX (don't show options that will 403).\n */\nexport function useOrgsWithPermission(\n orgIds: readonly string[],\n resourceType: string,\n action: string\n): { allowedIds: Set<string>; loading: boolean; isError: boolean } {\n const { user } = useAuth();\n const actorId = user?.id ?? '';\n const client = useApolloClient();\n\n // `stableKey` lets memos depend on the *values* of orgIds, not the array\n // reference — callers passing a fresh array with the same ids won't bust\n // the query cache or trigger refetch loops.\n const stableKey = useMemo(() => orgIds.join(''), [orgIds]);\n // react-doctor-disable-next-line react-doctor/exhaustive-deps\n const stableIds = useMemo(() => [...orgIds], [stableKey]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const inputs = useMemo(\n () =>\n stableIds.map((scopeId) => ({\n actorId,\n actorType: ActorType.User,\n scopeId,\n resourceType,\n action,\n })),\n [stableIds, actorId, resourceType, action]\n );\n\n const skip = !actorId || stableIds.length === 0;\n\n // Fan out the bulk check into <=BATCH_LIMIT-sized chunks, run them in\n // parallel via the Apollo client (so per-chunk caching still works), and\n // concat results in input order to preserve the positional contract.\n // We can't use multiple useQuery hooks here because the chunk count is\n // dynamic, hence the client.query + Promise.all pattern in an effect.\n const [results, setResults] = useState<PermissionCheckResult[]>([]);\n const [loading, setLoading] = useState(false);\n const [hadError, setHadError] = useState(false);\n\n useEffect(() => {\n if (skip) {\n setResults([]);\n setLoading(false);\n setHadError(false);\n return;\n }\n\n let cancelled = false;\n setLoading(true);\n setHadError(false);\n\n const chunks: (typeof inputs)[] = [];\n for (let i = 0; i < inputs.length; i += CHECK_PERMISSIONS_BATCH_LIMIT) {\n chunks.push(inputs.slice(i, i + CHECK_PERMISSIONS_BATCH_LIMIT));\n }\n\n Promise.all(\n chunks.map((chunk) =>\n client.query<CheckRbacPermissionsQuery, CheckRbacPermissionsQueryVariables>({\n query: CheckRbacPermissionsDocument,\n variables: { inputs: chunk },\n fetchPolicy: 'cache-first',\n })\n )\n )\n .then((responses) => {\n if (cancelled) return;\n const merged = responses.flatMap((r) => r.data?.checkPermissions ?? []);\n setResults(merged);\n setLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setResults([]);\n setHadError(true);\n setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, inputs, skip]);\n\n const allowedIds = useMemo(() => {\n if (skip) return new Set<string>();\n const out = new Set<string>();\n // Length-guard the positional mapping so we never silently\n // mis-attribute a permission to the wrong org if the backend ever\n // truncates, reorders, or filters the response.\n const bounded = Math.min(results.length, stableIds.length);\n for (let i = 0; i < bounded; i += 1) {\n if (results[i].allowed && stableIds[i]) out.add(stableIds[i]);\n }\n return out;\n }, [results, stableIds, skip]);\n\n return {\n allowedIds,\n loading: !skip && loading,\n isError: !skip && hadError,\n };\n}\n"],"mappings":";;;;;;AAcA,IAAM,IAAgC;AAsBtC,SAAgB,EACd,GACA,GACA,GACiE;CACjE,IAAM,EAAE,YAAS,GAAS,EACpB,IAAU,GAAM,MAAM,IACtB,IAAS,GAAiB,EAO1B,IAAY,QAAc,CAAC,GAAG,EAAO,EAAE,CAF3B,QAAc,EAAO,KAAK,GAAG,EAAE,CAAC,EAAO,CAAC,CAEF,CAAC,EAEnD,IAAS,QAEX,EAAU,KAAK,OAAa;EAC1B;EACA,WAAW,EAAU;EACrB;EACA;EACA;EACD,EAAE,EACL;EAAC;EAAW;EAAS;EAAc;EAAO,CAC3C,EAEK,IAAO,CAAC,KAAW,EAAU,WAAW,GAOxC,CAAC,GAAS,KAAc,EAAkC,EAAE,CAAC,EAC7D,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAU,KAAe,EAAS,GAAM;AA2D/C,QAzDA,QAAgB;AACd,MAAI,GAAM;AAGR,GAFA,EAAW,EAAE,CAAC,EACd,EAAW,GAAM,EACjB,EAAY,GAAM;AAClB;;EAGF,IAAI,IAAY;AAEhB,EADA,EAAW,GAAK,EAChB,EAAY,GAAM;EAElB,IAAM,IAA4B,EAAE;AACpC,OAAK,IAAI,IAAI,GAAG,IAAI,EAAO,QAAQ,KAAK,EACtC,GAAO,KAAK,EAAO,MAAM,GAAG,IAAI,EAA8B,CAAC;AAyBjE,SAtBA,QAAQ,IACN,EAAO,KAAK,MACV,EAAO,MAAqE;GAC1E,OAAO;GACP,WAAW,EAAE,QAAQ,GAAO;GAC5B,aAAa;GACd,CAAC,CACH,CACF,CACE,MAAM,MAAc;AACf,SAEJ,EADe,EAAU,SAAS,MAAM,EAAE,MAAM,oBAAoB,EAAE,CAAC,CACrD,EAClB,EAAW,GAAM;IACjB,CACD,YAAY;AACP,SACJ,EAAW,EAAE,CAAC,EACd,EAAY,GAAK,EACjB,EAAW,GAAM;IACjB,QAES;AACX,OAAY;;IAEb;EAAC;EAAQ;EAAQ;EAAK,CAAC,EAenB;EACL,YAdiB,QAAc;AAC/B,OAAI,EAAM,wBAAO,IAAI,KAAa;GAClC,IAAM,oBAAM,IAAI,KAAa,EAIvB,IAAU,KAAK,IAAI,EAAQ,QAAQ,EAAU,OAAO;AAC1D,QAAK,IAAI,IAAI,GAAG,IAAI,GAAS,KAAK,EAChC,CAAI,EAAQ,GAAG,WAAW,EAAU,MAAI,EAAI,IAAI,EAAU,GAAG;AAE/D,UAAO;KACN;GAAC;GAAS;GAAW;GAAK,CAAC;EAI5B,SAAS,CAAC,KAAQ;EAClB,SAAS,CAAC,KAAQ;EACnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"AddWorkspaceMemberDialog.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Loader2, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useMembers } from '../../members/hooks/useMembers';\nimport { useAddWorkspaceMember } from '../hooks/useWorkspaceMembers';\nimport type { WorkspaceMember } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface AddWorkspaceMemberDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n organizationId: string;\n existingMembers: WorkspaceMember[];\n onInviteOrganizationMember?: () => void;\n}\n\nexport function AddWorkspaceMemberDialog({\n open,\n onOpenChange,\n workspaceId,\n organizationId,\n existingMembers,\n onInviteOrganizationMember,\n}: AddWorkspaceMemberDialogProps) {\n const tr = useTr();\n const [selectedUserId, setSelectedUserId] = useState('');\n const [selectedRole, setSelectedRole] = useState<'MEMBER' | 'WORKSPACE_ADMIN'>('MEMBER');\n const [submissionError, setSubmissionError] = useState<string | null>(null);\n\n const { members: organizationMembers, loading: membersLoading } = useMembers(\n organizationId,\n 100,\n 0,\n open\n );\n const { addWorkspaceMember, loading: addingMember } = useAddWorkspaceMember();\n\n const existingUserIds = useMemo(\n () => new Set(existingMembers.map((member) => member.userId)),\n [existingMembers]\n );\n\n const availableMembers = useMemo(\n () => organizationMembers.filter((member) => !existingUserIds.has(member.userId)),\n [existingUserIds, organizationMembers]\n );\n\n useEffect(() => {\n if (!open) {\n setSelectedUserId('');\n setSelectedRole('MEMBER');\n setSubmissionError(null);\n return;\n }\n\n if (!selectedUserId && availableMembers.length > 0) {\n setSelectedUserId(availableMembers[0]?.userId ?? '');\n }\n }, [availableMembers, open, selectedUserId]);\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!selectedUserId) {\n setSubmissionError(\n tr(\n 'workspaces.addMember.selectRequired',\n 'Select an organization member to add to this workspace.'\n )\n );\n return;\n }\n\n try {\n setSubmissionError(null);\n const result = await addWorkspaceMember({\n workspaceId,\n userId: selectedUserId,\n role: selectedRole,\n });\n\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n\n toast.success(tr('workspaces.addMember.success', 'Workspace member added successfully'));\n onOpenChange(false);\n } catch (error) {\n setSubmissionError(\n error instanceof Error\n ? error.message\n : tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-[520px] border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2 text-[var(--color-text-primary)]\">\n <UserPlus size={18} aria-hidden=\"true\" />\n {tr('workspaces.addMember.title', 'Add workspace member')}\n </DialogTitle>\n <DialogDescription className=\"text-[var(--color-text-secondary)]\">\n {tr(\n 'workspaces.addMember.description',\n 'Choose an existing organization member and assign their workspace role.'\n )}\n </DialogDescription>\n </DialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {submissionError ? (\n <div className=\"flex items-start gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3 text-sm text-[var(--color-status-error-text)]\">\n <AlertCircle size={16} className=\"mt-0.5 shrink-0\" aria-hidden=\"true\" />\n <span>{submissionError}</span>\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"workspace-member-user\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.memberLabel', 'Organization member')}\n </label>\n <select\n id=\"workspace-member-user\"\n value={selectedUserId}\n onChange={(event) => setSelectedUserId(event.target.value)}\n disabled={membersLoading || addingMember || availableMembers.length === 0}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n {availableMembers.length === 0 ? (\n <option value=\"\">\n {tr(\n 'workspaces.addMember.noneAvailable',\n 'No eligible organization members available'\n )}\n </option>\n ) : null}\n {availableMembers.map((member) => (\n <option key={member.id} value={member.userId}>\n {member.userId} ({member.role})\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.addMember.filterHelp',\n 'Only members that belong to this organization and are not already in the workspace are listed.'\n )}\n </p>\n {availableMembers.length === 0 && onInviteOrganizationMember ? (\n <button\n type=\"button\"\n onClick={() => {\n onOpenChange(false);\n onInviteOrganizationMember();\n }}\n className=\"mt-2 inline-flex items-center gap-2 text-sm font-medium text-[var(--color-action-primary-bg)] hover:underline\"\n >\n {tr(\n 'workspaces.addMember.inviteInstead',\n 'Invite a new organization member instead'\n )}\n </button>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"workspace-member-role\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.roleLabel', 'Workspace role')}\n </label>\n <select\n id=\"workspace-member-role\"\n value={selectedRole}\n onChange={(event) =>\n setSelectedRole(event.target.value as 'MEMBER' | 'WORKSPACE_ADMIN')\n }\n disabled={addingMember}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n <option value=\"MEMBER\">{tr('members.role.member', 'Member')}</option>\n <option value=\"WORKSPACE_ADMIN\">\n {tr('members.role.workspaceAdmin', 'Workspace Admin')}\n </option>\n </select>\n </div>\n\n <DialogFooter className=\"gap-3 sm:gap-0\">\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"rounded-lg border border-[var(--color-border-default)] px-4 py-2 text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-subtle)]\"\n disabled={addingMember}\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={addingMember || membersLoading || availableMembers.length === 0}\n >\n {addingMember ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {tr('workspaces.addMember.submit', 'Add member')}\n </button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":";;;;;;;;;AAyBA,SAAgB,EAAyB,EACvC,SACA,iBACA,gBACA,mBACA,oBACA,iCACgC;CAChC,IAAM,IAAK,GAAO,EACZ,CAAC,GAAgB,KAAqB,EAAS,GAAG,EAClD,CAAC,GAAc,KAAmB,EAAuC,SAAS,EAClF,CAAC,GAAiB,KAAsB,EAAwB,KAAK,EAErE,EAAE,SAAS,GAAqB,SAAS,MAAmB,EAChE,GACA,KACA,GACA,EACD,EACK,EAAE,uBAAoB,SAAS,MAAiB,GAAuB,EAEvE,IAAkB,QAChB,IAAI,IAAI,EAAgB,KAAK,MAAW,EAAO,OAAO,CAAC,EAC7D,CAAC,EAAgB,CAClB,EAEK,IAAmB,QACjB,EAAoB,QAAQ,MAAW,CAAC,EAAgB,IAAI,EAAO,OAAO,CAAC,EACjF,CAAC,GAAiB,EAAoB,CACvC;AAsDD,QApDA,QAAgB;AACd,MAAI,CAAC,GAAM;AAGT,GAFA,EAAkB,GAAG,EACrB,EAAgB,SAAS,EACzB,EAAmB,KAAK;AACxB;;AAGF,EAAI,CAAC,KAAkB,EAAiB,SAAS,KAC/C,EAAkB,EAAiB,IAAI,UAAU,GAAG;IAErD;EAAC;EAAkB;EAAM;EAAe,CAAC,EA0C1C,kBAAC,GAAD;EAAc;EAAoB;YAChC,kBAAC,GAAD;GAAe,WAAU;aAAzB,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;IAAa,WAAU;cAAvB,CACE,kBAAC,GAAD;KAAU,MAAM;KAAI,eAAY;KAAS,CAAA,EACxC,EAAG,8BAA8B,uBAAuB,CAC7C;OACd,kBAAC,GAAD;IAAmB,WAAU;cAC1B,EACC,oCACA,0EACD;IACiB,CAAA,CACP,EAAA,CAAA,EAEf,kBAAC,QAAD;IAAM,UAvDS,OAAO,MAA2B;AAGrD,SAFA,EAAM,gBAAgB,EAElB,CAAC,GAAgB;AACnB,QACE,EACE,uCACA,0DACD,CACF;AACD;;AAGF,SAAI;AACF,QAAmB,KAAK;MACxB,IAAM,IAAS,MAAM,EAAmB;OACtC;OACA,QAAQ;OACR,MAAM;OACP,CAAC;AAEF,UAAI,EAAO,QAAQ,OACjB,OAAU,MACR,EAAO,OAAO,IAAI,WAChB,EAAG,+BAA+B,iCAAiC,CACtE;AAIH,MADA,EAAM,QAAQ,EAAG,gCAAgC,sCAAsC,CAAC,EACxF,EAAa,GAAM;cACZ,GAAO;AACd,QACE,aAAiB,QACb,EAAM,UACN,EAAG,+BAA+B,iCAAiC,CACxE;;;IAoB+B,WAAU;cAAxC;KACG,IACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAa,MAAM;OAAI,WAAU;OAAkB,eAAY;OAAS,CAAA,EACxE,kBAAC,QAAD,EAAA,UAAO,GAAuB,CAAA,CAC1B;UACJ;KAEJ,kBAAC,OAAD,EAAA,UAAA;MACE,kBAAC,SAAD;OACE,SAAQ;OACR,WAAU;iBAET,EAAG,oCAAoC,sBAAsB;OACxD,CAAA;MACR,kBAAC,UAAD;OACE,IAAG;OACH,OAAO;OACP,WAAW,MAAU,EAAkB,EAAM,OAAO,MAAM;OAC1D,UAAU,KAAkB,KAAgB,EAAiB,WAAW;OACxE,WAAU;iBALZ,CAOG,EAAiB,WAAW,IAC3B,kBAAC,UAAD;QAAQ,OAAM;kBACX,EACC,sCACA,6CACD;QACM,CAAA,GACP,MACH,EAAiB,KAAK,MACrB,kBAAC,UAAD;QAAwB,OAAO,EAAO;kBAAtC;SACG,EAAO;SAAO;SAAG,EAAO;SAAK;SACvB;UAFI,EAAO,GAEX,CACT,CACK;;MACT,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,mCACA,iGACD;OACC,CAAA;MACH,EAAiB,WAAW,KAAK,IAChC,kBAAC,UAAD;OACE,MAAK;OACL,eAAe;AAEb,QADA,EAAa,GAAM,EACnB,GAA4B;;OAE9B,WAAU;iBAET,EACC,sCACA,2CACD;OACM,CAAA,GACP;MACA,EAAA,CAAA;KAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;MACE,SAAQ;MACR,WAAU;gBAET,EAAG,kCAAkC,iBAAiB;MACjD,CAAA,EACR,kBAAC,UAAD;MACE,IAAG;MACH,OAAO;MACP,WAAW,MACT,EAAgB,EAAM,OAAO,MAAsC;MAErE,UAAU;MACV,WAAU;gBAPZ,CASE,kBAAC,UAAD;OAAQ,OAAM;iBAAU,EAAG,uBAAuB,SAAS;OAAU,CAAA,EACrE,kBAAC,UAAD;OAAQ,OAAM;iBACX,EAAG,+BAA+B,kBAAkB;OAC9C,CAAA,CACF;QACL,EAAA,CAAA;KAEN,kBAAC,GAAD;MAAc,WAAU;gBAAxB,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAa,GAAM;OAClC,WAAU;OACV,UAAU;iBAET,EAAG,iBAAiB,SAAS;OACvB,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,WAAU;OACV,UAAU,KAAgB,KAAkB,EAAiB,WAAW;iBAH1E,CAKG,IAAe,kBAAC,GAAD;QAAS,MAAM;QAAI,WAAU;QAAiB,CAAA,GAAG,MAChE,EAAG,+BAA+B,aAAa,CACzC;SACI;;KACV;MACO;;EACT,CAAA"}
1
+ {"version":3,"file":"AddWorkspaceMemberDialog.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Loader2, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useMembers } from '../../members/hooks/useMembers';\nimport { useAddWorkspaceMember } from '../hooks/useWorkspaceMembers';\nimport type { WorkspaceMember } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface AddWorkspaceMemberDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n organizationId: string;\n existingMembers: WorkspaceMember[];\n onInviteOrganizationMember?: () => void;\n}\n\nexport function AddWorkspaceMemberDialog({\n open,\n onOpenChange,\n workspaceId,\n organizationId,\n existingMembers,\n onInviteOrganizationMember,\n}: AddWorkspaceMemberDialogProps) {\n const tr = useTr();\n const [selectedUserId, setSelectedUserId] = useState('');\n const [selectedRole, setSelectedRole] = useState<'MEMBER' | 'WORKSPACE_ADMIN'>('MEMBER');\n const [submissionError, setSubmissionError] = useState<string | null>(null);\n\n const { members: organizationMembers, loading: membersLoading } = useMembers(\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId,\n 100,\n 0,\n // react-doctor-disable-next-line react-doctor/no-event-handler\n open\n );\n const { addWorkspaceMember, loading: addingMember } = useAddWorkspaceMember();\n\n const existingUserIds = useMemo(\n // react-doctor-disable-next-line react-doctor/no-event-handler\n () => new Set(existingMembers.map((member) => member.userId)),\n [existingMembers]\n );\n\n const availableMembers = useMemo(\n () => organizationMembers.filter((member) => !existingUserIds.has(member.userId)),\n [existingUserIds, organizationMembers]\n );\n\n useEffect(() => {\n if (!open) {\n setSelectedUserId('');\n setSelectedRole('MEMBER');\n setSubmissionError(null);\n return;\n }\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (!selectedUserId && availableMembers.length > 0) {\n setSelectedUserId(availableMembers[0]?.userId ?? '');\n }\n }, [availableMembers, open, selectedUserId]);\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!selectedUserId) {\n setSubmissionError(\n tr(\n 'workspaces.addMember.selectRequired',\n 'Select an organization member to add to this workspace.'\n )\n );\n return;\n }\n\n try {\n setSubmissionError(null);\n const result = await addWorkspaceMember({\n workspaceId,\n userId: selectedUserId,\n role: selectedRole,\n });\n\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n\n toast.success(tr('workspaces.addMember.success', 'Workspace member added successfully'));\n onOpenChange(false);\n } catch (error) {\n setSubmissionError(\n error instanceof Error\n ? error.message\n : tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-[520px] border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2 text-[var(--color-text-primary)]\">\n <UserPlus size={18} aria-hidden=\"true\" />\n {tr('workspaces.addMember.title', 'Add workspace member')}\n </DialogTitle>\n <DialogDescription className=\"text-[var(--color-text-secondary)]\">\n {tr(\n 'workspaces.addMember.description',\n 'Choose an existing organization member and assign their workspace role.'\n )}\n </DialogDescription>\n </DialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {submissionError ? (\n <div className=\"flex items-start gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3 text-sm text-[var(--color-status-error-text)]\">\n <AlertCircle size={16} className=\"mt-0.5 shrink-0\" aria-hidden=\"true\" />\n <span>{submissionError}</span>\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"workspace-member-user\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.memberLabel', 'Organization member')}\n </label>\n <select\n id=\"workspace-member-user\"\n value={selectedUserId}\n onChange={(event) => setSelectedUserId(event.target.value)}\n disabled={membersLoading || addingMember || availableMembers.length === 0}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n {availableMembers.length === 0 ? (\n <option value=\"\">\n {tr(\n 'workspaces.addMember.noneAvailable',\n 'No eligible organization members available'\n )}\n </option>\n ) : null}\n {availableMembers.map((member) => (\n <option key={member.id} value={member.userId}>\n {member.userId} ({member.role})\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.addMember.filterHelp',\n 'Only members that belong to this organization and are not already in the workspace are listed.'\n )}\n </p>\n {availableMembers.length === 0 && onInviteOrganizationMember ? (\n <button\n type=\"button\"\n onClick={() => {\n onOpenChange(false);\n onInviteOrganizationMember();\n }}\n className=\"mt-2 inline-flex items-center gap-2 text-sm font-medium text-[var(--color-action-primary-bg)] hover:underline\"\n >\n {tr(\n 'workspaces.addMember.inviteInstead',\n 'Invite a new organization member instead'\n )}\n </button>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"workspace-member-role\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.roleLabel', 'Workspace role')}\n </label>\n <select\n id=\"workspace-member-role\"\n value={selectedRole}\n onChange={(event) =>\n setSelectedRole(event.target.value as 'MEMBER' | 'WORKSPACE_ADMIN')\n }\n disabled={addingMember}\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n <option value=\"MEMBER\">{tr('members.role.member', 'Member')}</option>\n <option value=\"WORKSPACE_ADMIN\">\n {tr('members.role.workspaceAdmin', 'Workspace Admin')}\n </option>\n </select>\n </div>\n\n <DialogFooter className=\"gap-3 sm:gap-0\">\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"rounded-lg border border-[var(--color-border-default)] px-4 py-2 text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-subtle)]\"\n disabled={addingMember}\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={addingMember || membersLoading || availableMembers.length === 0}\n >\n {addingMember ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {tr('workspaces.addMember.submit', 'Add member')}\n </button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
@@ -8,10 +8,10 @@ function o({ workspace: o, onClick: s, onEdit: c, onDelete: l, showActions: u =
8
8
  s && s(o);
9
9
  };
10
10
  return /* @__PURE__ */ a("div", {
11
- onClick: f,
12
- onKeyDown: (e) => {
13
- s && (e.key === "Enter" || e.key === " ") && (e.preventDefault(), f());
14
- },
11
+ onClick: s ? f : void 0,
12
+ onKeyDown: s ? (e) => {
13
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), f());
14
+ } : void 0,
15
15
  className: `
16
16
  w-full text-left
17
17
  bg-[var(--color-bg-surface)]
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceCard.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceCard.tsx"],"sourcesContent":["import { Briefcase, MoreVertical } from 'lucide-react';\nimport { WorkspaceStatusBadge } from './WorkspaceStatusBadge';\nimport type { Workspace } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceCardProps {\n workspace: Workspace;\n onClick?: (workspace: Workspace) => void;\n onEdit?: (workspace: Workspace) => void;\n onDelete?: (workspace: Workspace) => void;\n showActions?: boolean;\n}\n\n/**\n * WorkspaceCard\n * Displays a workspace in a card format for grid views\n */\nexport function WorkspaceCard({\n workspace,\n onClick,\n onEdit,\n onDelete,\n showActions = true,\n}: WorkspaceCardProps) {\n const tr = useTr();\n const handleCardClick = () => {\n if (onClick) {\n onClick(workspace);\n }\n };\n\n const handleEdit = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onEdit) {\n onEdit(workspace);\n }\n };\n\n const handleDelete = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onDelete) {\n onDelete(workspace);\n }\n };\n\n return (\n <div\n onClick={handleCardClick}\n onKeyDown={(event) => {\n if (!onClick) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n }}\n className={`\n w-full text-left\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n p-6\n shadow-[var(--shadow-elevation-1)]\n transition-all\n ${onClick ? 'cursor-pointer hover:shadow-[var(--shadow-elevation-2)] hover:border-[var(--color-border-hover)]' : ''}\n `}\n aria-label={\n onClick ? `${tr('workspaces.card.open', 'Open workspace')} ${workspace.name}` : undefined\n }\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-12 rounded-lg bg-[var(--color-status-info-muted)] flex items-center justify-center\">\n <Briefcase size={24} className=\"text-[var(--color-status-info)]\" />\n </div>\n <div className=\"flex-1\">\n <h3 className=\"font-semibold text-[var(--color-text-primary)] text-lg\">\n {workspace.name}\n </h3>\n <p className=\"text-sm text-[var(--color-text-muted)] font-mono\">/{workspace.slug}</p>\n </div>\n </div>\n\n {showActions && (\n <button\n type=\"button\"\n onClick={(e) => e.stopPropagation()}\n className=\"\n p-1.5 rounded-lg\n text-[var(--color-text-secondary)]\n hover:bg-[var(--color-bg-subtle)]\n active:scale-95\n transition-all duration-200\n \"\n aria-label={`${tr('workspaces.card.actionsFor', 'Workspace actions for')} ${workspace.name}`}\n >\n <MoreVertical size={18} />\n </button>\n )}\n </div>\n\n {/* Badges */}\n <div className=\"flex gap-2 mb-4\">\n <WorkspaceStatusBadge status={workspace.status} />\n <span className=\"inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-[var(--color-bg-subtle)] text-[var(--color-text-secondary)]\">\n {workspace.type}\n </span>\n </div>\n\n {/* Member Count */}\n {workspace.memberCount !== undefined && (\n <p className=\"text-sm text-[var(--color-text-secondary)] mb-4\">\n {workspace.memberCount}{' '}\n {workspace.memberCount === 1\n ? tr('members.role.member', 'Member').toLowerCase()\n : tr('workspaces.list.members', 'Members').toLowerCase()}\n </p>\n )}\n\n {/* Metadata */}\n <div className=\"pt-2 border-t border-[var(--color-border-default)]\">\n <p className=\"text-xs text-[var(--color-text-muted)]\">\n {tr('common.created', 'Created')} {new Date(workspace.createdAt).toLocaleDateString()}\n </p>\n </div>\n\n {/* Footer Actions */}\n {showActions && (onEdit || onDelete) && (\n <div className=\"flex gap-2 mt-4 pt-4 border-t border-[var(--color-border-default)]\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={handleEdit}\n className=\"\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-bg-subtle)]\n text-[var(--color-text-primary)]\n rounded-lg\n hover:bg-[var(--color-bg-muted)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n \"\n >\n {tr('common.edit', 'Edit')}\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={handleDelete}\n className=\"\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-status-error-muted)]\n text-[var(--color-status-error)]\n rounded-lg\n hover:opacity-80\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n \"\n >\n {tr('common.delete', 'Delete')}\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AAiBA,SAAgB,EAAc,EAC5B,cACA,YACA,WACA,aACA,iBAAc,MACO;CACrB,IAAM,IAAK,GAAO,EACZ,UAAwB;AAC5B,EAAI,KACF,EAAQ,EAAU;;AAkBtB,QACE,kBAAC,OAAD;EACE,SAAS;EACT,YAAY,MAAU;AACf,SACD,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,GAAiB;;EAGrB,WAAW;;;;;;;;UAQP,IAAU,qGAAqG,GAAG;;EAEtH,cACE,IAAU,GAAG,EAAG,wBAAwB,iBAAiB,CAAC,GAAG,EAAU,SAAS,KAAA;EAElF,MAAM,IAAU,WAAW,KAAA;EAC3B,UAAU,IAAU,IAAI,KAAA;YAvB1B;GA0BE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAW,MAAM;OAAI,WAAU;OAAoC,CAAA;MAC/D,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAU;OACR,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAAgE,KAAE,EAAU,KAAS;SACjF;QACF;QAEL,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,MAAM,EAAE,iBAAiB;KACnC,WAAU;KAOV,cAAY,GAAG,EAAG,8BAA8B,wBAAwB,CAAC,GAAG,EAAU;eAEtF,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA;KACnB,CAAA,CAEP;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAsB,QAAQ,EAAU,QAAU,CAAA,EAClD,kBAAC,QAAD;KAAM,WAAU;eACb,EAAU;KACN,CAAA,CACH;;GAGL,EAAU,gBAAgB,KAAA,KACzB,kBAAC,KAAD;IAAG,WAAU;cAAb;KACG,EAAU;KAAa;KACvB,EAAU,gBAAgB,IACvB,EAAG,uBAAuB,SAAS,CAAC,aAAa,GACjD,EAAG,2BAA2B,UAAU,CAAC,aAAa;KACxD;;GAIN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAG,kBAAkB,UAAU;MAAC;MAAE,IAAI,KAAK,EAAU,UAAU,CAAC,oBAAoB;MACnF;;IACA,CAAA;GAGL,MAAgB,KAAU,MACzB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAvGQ,MAAwB;AAE1C,MADA,EAAE,iBAAiB,EACf,KACF,EAAO,EAAU;;KAqGT,WAAU;eAWT,EAAG,eAAe,OAAO;KACnB,CAAA,EAEV,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAlHU,MAAwB;AAE5C,MADA,EAAE,iBAAiB,EACf,KACF,EAAS,EAAU;;KAgHX,WAAU;eAWT,EAAG,iBAAiB,SAAS;KACvB,CAAA,CAEP;;GAEJ"}
1
+ {"version":3,"file":"WorkspaceCard.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceCard.tsx"],"sourcesContent":["import { Briefcase, MoreVertical } from 'lucide-react';\nimport { WorkspaceStatusBadge } from './WorkspaceStatusBadge';\nimport type { Workspace } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceCardProps {\n workspace: Workspace;\n onClick?: (workspace: Workspace) => void;\n onEdit?: (workspace: Workspace) => void;\n onDelete?: (workspace: Workspace) => void;\n showActions?: boolean;\n}\n\n/**\n * WorkspaceCard\n * Displays a workspace in a card format for grid views\n */\nexport function WorkspaceCard({\n workspace,\n onClick,\n onEdit,\n onDelete,\n showActions = true,\n}: WorkspaceCardProps) {\n const tr = useTr();\n const handleCardClick = () => {\n if (onClick) {\n onClick(workspace);\n }\n };\n\n const handleEdit = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onEdit) {\n onEdit(workspace);\n }\n };\n\n const handleDelete = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onDelete) {\n onDelete(workspace);\n }\n };\n\n return (\n // react-doctor-disable-next-line react-doctor/no-static-element-interactions\n <div\n onClick={onClick ? handleCardClick : undefined}\n onKeyDown={\n onClick\n ? (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n }\n : undefined\n }\n className={`\n w-full text-left\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n p-6\n shadow-[var(--shadow-elevation-1)]\n transition-all\n ${onClick ? 'cursor-pointer hover:shadow-[var(--shadow-elevation-2)] hover:border-[var(--color-border-hover)]' : ''}\n `}\n aria-label={\n onClick ? `${tr('workspaces.card.open', 'Open workspace')} ${workspace.name}` : undefined\n }\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-12 rounded-lg bg-[var(--color-status-info-muted)] flex items-center justify-center\">\n <Briefcase size={24} className=\"text-[var(--color-status-info)]\" />\n </div>\n <div className=\"flex-1\">\n <h3 className=\"font-semibold text-[var(--color-text-primary)] text-lg\">\n {workspace.name}\n </h3>\n <p className=\"text-sm text-[var(--color-text-muted)] font-mono\">/{workspace.slug}</p>\n </div>\n </div>\n\n {showActions && (\n <button\n type=\"button\"\n onClick={(e) => e.stopPropagation()}\n className=\"\n p-1.5 rounded-lg\n text-[var(--color-text-secondary)]\n hover:bg-[var(--color-bg-subtle)]\n active:scale-95\n transition-all duration-200\n \"\n aria-label={`${tr('workspaces.card.actionsFor', 'Workspace actions for')} ${workspace.name}`}\n >\n <MoreVertical size={18} />\n </button>\n )}\n </div>\n\n {/* Badges */}\n <div className=\"flex gap-2 mb-4\">\n <WorkspaceStatusBadge status={workspace.status} />\n <span className=\"inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-[var(--color-bg-subtle)] text-[var(--color-text-secondary)]\">\n {workspace.type}\n </span>\n </div>\n\n {/* Member Count */}\n {workspace.memberCount !== undefined && (\n <p className=\"text-sm text-[var(--color-text-secondary)] mb-4\">\n {workspace.memberCount}{' '}\n {workspace.memberCount === 1\n ? tr('members.role.member', 'Member').toLowerCase()\n : tr('workspaces.list.members', 'Members').toLowerCase()}\n </p>\n )}\n\n {/* Metadata */}\n <div className=\"pt-2 border-t border-[var(--color-border-default)]\">\n <p className=\"text-xs text-[var(--color-text-muted)]\">\n {tr('common.created', 'Created')} {new Date(workspace.createdAt).toLocaleDateString()}\n </p>\n </div>\n\n {/* Footer Actions */}\n {showActions && (onEdit || onDelete) && (\n <div className=\"flex gap-2 mt-4 pt-4 border-t border-[var(--color-border-default)]\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={handleEdit}\n className=\"\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-bg-subtle)]\n text-[var(--color-text-primary)]\n rounded-lg\n hover:bg-[var(--color-bg-muted)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n \"\n >\n {tr('common.edit', 'Edit')}\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={handleDelete}\n className=\"\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-status-error-muted)]\n text-[var(--color-status-error)]\n rounded-lg\n hover:opacity-80\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n \"\n >\n {tr('common.delete', 'Delete')}\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AAiBA,SAAgB,EAAc,EAC5B,cACA,YACA,WACA,aACA,iBAAc,MACO;CACrB,IAAM,IAAK,GAAO,EACZ,UAAwB;AAC5B,EAAI,KACF,EAAQ,EAAU;;AAkBtB,QAEE,kBAAC,OAAD;EACE,SAAS,IAAU,IAAkB,KAAA;EACrC,WACE,KACK,MAAU;AACT,IAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,GAAiB;MAGrB,KAAA;EAEN,WAAW;;;;;;;;UAQP,IAAU,qGAAqG,GAAG;;EAEtH,cACE,IAAU,GAAG,EAAG,wBAAwB,iBAAiB,CAAC,GAAG,EAAU,SAAS,KAAA;EAElF,MAAM,IAAU,WAAW,KAAA;EAC3B,UAAU,IAAU,IAAI,KAAA;YA1B1B;GA6BE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAW,MAAM;OAAI,WAAU;OAAoC,CAAA;MAC/D,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAU;OACR,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAAgE,KAAE,EAAU,KAAS;SACjF;QACF;QAEL,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,MAAM,EAAE,iBAAiB;KACnC,WAAU;KAOV,cAAY,GAAG,EAAG,8BAA8B,wBAAwB,CAAC,GAAG,EAAU;eAEtF,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA;KACnB,CAAA,CAEP;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAsB,QAAQ,EAAU,QAAU,CAAA,EAClD,kBAAC,QAAD;KAAM,WAAU;eACb,EAAU;KACN,CAAA,CACH;;GAGL,EAAU,gBAAgB,KAAA,KACzB,kBAAC,KAAD;IAAG,WAAU;cAAb;KACG,EAAU;KAAa;KACvB,EAAU,gBAAgB,IACvB,EAAG,uBAAuB,SAAS,CAAC,aAAa,GACjD,EAAG,2BAA2B,UAAU,CAAC,aAAa;KACxD;;GAIN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAG,kBAAkB,UAAU;MAAC;MAAE,IAAI,KAAK,EAAU,UAAU,CAAC,oBAAoB;MACnF;;IACA,CAAA;GAGL,MAAgB,KAAU,MACzB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UA3GQ,MAAwB;AAE1C,MADA,EAAE,iBAAiB,EACf,KACF,EAAO,EAAU;;KAyGT,WAAU;eAWT,EAAG,eAAe,OAAO;KACnB,CAAA,EAEV,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAtHU,MAAwB;AAE5C,MADA,EAAE,iBAAiB,EACf,KACF,EAAS,EAAU;;KAoHX,WAAU;eAWT,EAAG,iBAAiB,SAAS;KACvB,CAAA,CAEP;;GAEJ"}
@@ -151,6 +151,7 @@ function g({ workspace: g, tenantId: _, tenantName: v, organizationId: y, organi
151
151
  id: "tenantId",
152
152
  type: "text",
153
153
  value: B,
154
+ "aria-label": T("organizations.form.tenant", "Tenant"),
154
155
  onChange: (e) => D((t) => ({
155
156
  ...t,
156
157
  tenantId: e.target.value
@@ -245,6 +246,7 @@ function g({ workspace: g, tenantId: _, tenantName: v, organizationId: y, organi
245
246
  id: "organizationId",
246
247
  type: "text",
247
248
  value: V,
249
+ "aria-label": T("common.organization", "Organization"),
248
250
  onChange: (e) => D((t) => ({
249
251
  ...t,
250
252
  organizationId: e.target.value
@@ -285,6 +287,7 @@ function g({ workspace: g, tenantId: _, tenantName: v, organizationId: y, organi
285
287
  id: "name",
286
288
  type: "text",
287
289
  value: E.name,
290
+ "aria-label": T("workspaces.form.name", "Workspace Name"),
288
291
  onChange: (e) => D((t) => ({
289
292
  ...t,
290
293
  name: e.target.value
@@ -317,6 +320,7 @@ function g({ workspace: g, tenantId: _, tenantName: v, organizationId: y, organi
317
320
  id: "slug",
318
321
  type: "text",
319
322
  value: E.slug,
323
+ "aria-label": T("common.slug", "Slug"),
320
324
  onChange: (e) => D((t) => ({
321
325
  ...t,
322
326
  slug: e.target.value.toLowerCase().replace(/\s+/g, "-")
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceForm.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Building, Home, Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput, Workspace } from '../types';\nimport { createWorkspaceSchema, updateWorkspaceSchema } from '../schemas';\nimport { useOrganizations } from '../../organizations/hooks/useOrganizations';\nimport { useOrgsWithPermission } from '../../organizations/hooks/useOrgsWithPermission';\nimport { useTenants } from '../hooks/useWorkspaces';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceFormProps {\n workspace?: Workspace | null;\n tenantId?: string;\n tenantName?: string;\n organizationId?: string;\n organizationName?: string;\n showOrganizationSelector?: boolean;\n onSubmit: (data: CreateWorkspaceInput | UpdateWorkspaceInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\nexport function WorkspaceForm({\n workspace,\n tenantId,\n tenantName,\n organizationId,\n organizationName,\n showOrganizationSelector = false,\n onSubmit,\n onCancel,\n loading,\n}: WorkspaceFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n name: workspace?.name || '',\n slug: workspace?.slug || '',\n type: workspace?.type || 'PERSONAL',\n tenantId: tenantId || workspace?.tenantId || '',\n organizationId: organizationId || workspace?.organizationId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const shouldLoadTenants = !workspace && showOrganizationSelector && !tenantId;\n const shouldLoadOrganizations = !workspace && showOrganizationSelector && !organizationId;\n\n const { tenants, loading: tenantsLoading } = useTenants(100, 0, shouldLoadTenants);\n const { organizations, loading: organizationsLoading } = useOrganizations(\n formData.tenantId ? { tenantId: formData.tenantId } : undefined,\n 100,\n 0,\n shouldLoadOrganizations\n );\n\n // Hide orgs where the user can't actually create a workspace, so the\n // dropdown only surfaces options the gateway @rbac(workspace:create) will\n // accept. Membership alone isn't enough — a Viewer of an org is a member\n // but has workspace=VIEWER (no create). The bulk checkPermissions call is\n // one round-trip regardless of how many orgs are on the list.\n const allOrgIds = useMemo(\n () => organizations.map((organization) => organization.id),\n [organizations]\n );\n const { allowedIds: workspaceCreateAllowedOrgIds, loading: permissionCheckLoading } =\n useOrgsWithPermission(allOrgIds, 'workspace', 'create');\n const filterByPermission = !workspace && showOrganizationSelector && !organizationId;\n const permittedOrganizations = useMemo(\n () =>\n filterByPermission\n ? organizations.filter((organization) => workspaceCreateAllowedOrgIds.has(organization.id))\n : organizations,\n [organizations, workspaceCreateAllowedOrgIds, filterByPermission]\n );\n\n useEffect(() => {\n if (tenantId && tenantId !== formData.tenantId) {\n setFormData((previousState) => ({ ...previousState, tenantId }));\n }\n }, [formData.tenantId, tenantId]);\n\n useEffect(() => {\n if (organizationId && organizationId !== formData.organizationId) {\n setFormData((previousState) => ({ ...previousState, organizationId }));\n }\n }, [formData.organizationId, organizationId]);\n\n useEffect(() => {\n if (!showOrganizationSelector || organizationId || workspace) {\n return;\n }\n\n setFormData((previousState) => {\n if (!previousState.organizationId) {\n return previousState;\n }\n\n const hasMatchingOrganization = permittedOrganizations.some(\n (organization) => organization.id === previousState.organizationId\n );\n\n if (hasMatchingOrganization) {\n return previousState;\n }\n\n return { ...previousState, organizationId: '' };\n });\n }, [organizationId, permittedOrganizations, showOrganizationSelector, workspace]);\n\n const selectedTenantLabel = useMemo(() => {\n if (tenantName) return tenantName;\n\n const selectedTenant = tenants.find((tenant) => tenant.id === formData.tenantId);\n return selectedTenant?.name ?? formData.tenantId;\n }, [formData.tenantId, tenantName, tenants]);\n\n const selectedOrganizationLabel = useMemo(() => {\n if (organizationName) return organizationName;\n\n const selectedOrganization = permittedOrganizations.find(\n (organization) => organization.id === formData.organizationId\n );\n return selectedOrganization?.name ?? formData.organizationId;\n }, [formData.organizationId, organizationName, permittedOrganizations]);\n\n const validate = () => {\n try {\n const schema = workspace ? updateWorkspaceSchema : createWorkspaceSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (validationError) {\n if (validationError instanceof ZodError) {\n const nextErrors: Record<string, string> = {};\n validationError.issues.forEach((issue) => {\n const path = issue.path.join('.');\n nextErrors[path] = issue.message;\n });\n setErrors(nextErrors);\n }\n\n return false;\n }\n };\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = workspace\n ? {\n name: formData.name !== workspace.name ? formData.name : undefined,\n type: formData.type !== workspace.type ? formData.type : undefined,\n }\n : {\n tenantId: formData.tenantId,\n organizationId: formData.organizationId,\n name: formData.name,\n slug: formData.slug,\n type: formData.type || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {!workspace ? (\n <>\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n {showOrganizationSelector && !tenantId ? (\n <div className=\"relative\">\n <select\n id=\"tenantId\"\n value={formData.tenantId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n tenantId: event.target.value,\n organizationId: '',\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={loading || tenantsLoading}\n required\n >\n <option value=\"\">\n {tr('workspaces.form.selectTenant', 'Select a tenant...')}\n </option>\n {tenants.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name} ({tenant.slug})\n </option>\n ))}\n </select>\n <Home\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {tenantsLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"relative\">\n <input\n id=\"tenantId\"\n type=\"text\"\n value={selectedTenantLabel}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n tenantId: event.target.value,\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-4 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('organizations.form.tenantPlaceholder', 'Enter tenant ID')}\n disabled={loading || Boolean(tenantId)}\n required\n autoComplete=\"off\"\n />\n <Home\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n </div>\n )}\n\n {(tenantId || formData.tenantId) && (\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.form.creatingUnderTenant',\n 'Creating workspace under the selected tenant.'\n )}\n </p>\n )}\n {errors.tenantId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.tenantId}\n </p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"organizationId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.organization', 'Organization')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n {showOrganizationSelector && !organizationId ? (\n <div className=\"relative\">\n <select\n id=\"organizationId\"\n value={formData.organizationId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={\n loading || organizationsLoading || permissionCheckLoading || !formData.tenantId\n }\n required\n >\n <option value=\"\">\n {!formData.tenantId\n ? tr('workspaces.form.selectTenantFirst', 'Select a tenant first')\n : permittedOrganizations.length === 0 &&\n !organizationsLoading &&\n !permissionCheckLoading\n ? tr(\n 'workspaces.form.noOrganizationsAvailable',\n 'No organizations available — you need workspace create permission'\n )\n : tr('workspaces.form.selectOrganization', 'Select an organization...')}\n </option>\n {permittedOrganizations.map((organization) => (\n <option key={organization.id} value={organization.id}>\n {organization.name} ({organization.slug})\n </option>\n ))}\n </select>\n <Building\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {organizationsLoading || permissionCheckLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"relative\">\n <input\n id=\"organizationId\"\n type=\"text\"\n value={selectedOrganizationLabel}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-4 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.orgIdPlaceholder', 'Enter organization ID')}\n disabled={loading || Boolean(organizationId)}\n required\n autoComplete=\"off\"\n />\n <Building\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n </div>\n )}\n\n {(organizationId || formData.organizationId) && (\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.form.creatingUnderOrg',\n 'Creating workspace under the selected organization.'\n )}\n </p>\n )}\n {errors.organizationId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.organizationId}\n </p>\n ) : null}\n </div>\n </>\n ) : null}\n\n <div>\n <label\n htmlFor=\"name\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.form.name', 'Workspace Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(event) =>\n setFormData((previousState) => ({ ...previousState, name: event.target.value }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.namePlaceholder', 'Enter workspace name')}\n disabled={loading}\n required\n autoComplete=\"organization-title\"\n />\n {errors.name ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"slug\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n slug: event.target.value.toLowerCase().replace(/\\s+/g, '-'),\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.slugPlaceholder', 'workspace-slug')}\n disabled={loading || Boolean(workspace)}\n required={!workspace}\n autoComplete=\"off\"\n />\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"type\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.list.column.type', 'Type')}\n </label>\n <select\n id=\"type\"\n value={formData.type}\n onChange={(event) =>\n setFormData((previousState) => ({ ...previousState, type: event.target.value }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={loading}\n >\n <option value=\"PERSONAL\">{tr('workspaces.type.personal', 'Personal')}</option>\n <option value=\"TEAM\">{tr('workspaces.type.team', 'Team')}</option>\n <option value=\"ENTERPRISE\">{tr('workspaces.type.enterprise', 'Enterprise')}</option>\n </select>\n </div>\n\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 hover:scale-[1.02] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[220px]\"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {workspace\n ? tr('workspaces.form.updateSubmit', 'Update Workspace')\n : tr('workspaces.create.button', 'Create Workspace')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WorkspaceForm.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Building, Home, Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput, Workspace } from '../types';\nimport { createWorkspaceSchema, updateWorkspaceSchema } from '../schemas';\nimport { useOrganizations } from '../../organizations/hooks/useOrganizations';\nimport { useOrgsWithPermission } from '../../organizations/hooks/useOrgsWithPermission';\nimport { useTenants } from '../hooks/useWorkspaces';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceFormProps {\n workspace?: Workspace | null;\n tenantId?: string;\n tenantName?: string;\n organizationId?: string;\n organizationName?: string;\n showOrganizationSelector?: boolean;\n onSubmit: (data: CreateWorkspaceInput | UpdateWorkspaceInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\nexport function WorkspaceForm({\n workspace,\n tenantId,\n tenantName,\n organizationId,\n organizationName,\n showOrganizationSelector = false,\n onSubmit,\n onCancel,\n loading,\n}: WorkspaceFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: workspace?.name || '',\n slug: workspace?.slug || '',\n type: workspace?.type || 'PERSONAL',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || workspace?.tenantId || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId: organizationId || workspace?.organizationId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const shouldLoadTenants = !workspace && showOrganizationSelector && !tenantId;\n const shouldLoadOrganizations = !workspace && showOrganizationSelector && !organizationId;\n\n const { tenants, loading: tenantsLoading } = useTenants(100, 0, shouldLoadTenants);\n const { organizations, loading: organizationsLoading } = useOrganizations(\n formData.tenantId ? { tenantId: formData.tenantId } : undefined,\n 100,\n 0,\n shouldLoadOrganizations\n );\n\n // Hide orgs where the user can't actually create a workspace, so the\n // dropdown only surfaces options the gateway @rbac(workspace:create) will\n // accept. Membership alone isn't enough — a Viewer of an org is a member\n // but has workspace=VIEWER (no create). The bulk checkPermissions call is\n // one round-trip regardless of how many orgs are on the list.\n const allOrgIds = useMemo(\n () => organizations.map((organization) => organization.id),\n [organizations]\n );\n const { allowedIds: workspaceCreateAllowedOrgIds, loading: permissionCheckLoading } =\n useOrgsWithPermission(allOrgIds, 'workspace', 'create');\n const filterByPermission = !workspace && showOrganizationSelector && !organizationId;\n const permittedOrganizations = useMemo(\n () =>\n filterByPermission\n ? organizations.filter((organization) => workspaceCreateAllowedOrgIds.has(organization.id))\n : organizations,\n [organizations, workspaceCreateAllowedOrgIds, filterByPermission]\n );\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (tenantId && tenantId !== formData.tenantId) {\n setFormData((previousState) => ({ ...previousState, tenantId }));\n }\n }, [formData.tenantId, tenantId]);\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (organizationId && organizationId !== formData.organizationId) {\n setFormData((previousState) => ({ ...previousState, organizationId }));\n }\n }, [formData.organizationId, organizationId]);\n\n useEffect(() => {\n if (!showOrganizationSelector || organizationId || workspace) {\n return;\n }\n\n setFormData((previousState) => {\n if (!previousState.organizationId) {\n return previousState;\n }\n\n const hasMatchingOrganization = permittedOrganizations.some(\n (organization) => organization.id === previousState.organizationId\n );\n\n if (hasMatchingOrganization) {\n return previousState;\n }\n\n return { ...previousState, organizationId: '' };\n });\n }, [organizationId, permittedOrganizations, showOrganizationSelector, workspace]);\n\n const selectedTenantLabel = useMemo(() => {\n if (tenantName) return tenantName;\n\n const selectedTenant = tenants.find((tenant) => tenant.id === formData.tenantId);\n return selectedTenant?.name ?? formData.tenantId;\n }, [formData.tenantId, tenantName, tenants]);\n\n const selectedOrganizationLabel = useMemo(() => {\n if (organizationName) return organizationName;\n\n const selectedOrganization = permittedOrganizations.find(\n (organization) => organization.id === formData.organizationId\n );\n return selectedOrganization?.name ?? formData.organizationId;\n }, [formData.organizationId, organizationName, permittedOrganizations]);\n\n const validate = () => {\n try {\n const schema = workspace ? updateWorkspaceSchema : createWorkspaceSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (validationError) {\n if (validationError instanceof ZodError) {\n const nextErrors: Record<string, string> = {};\n validationError.issues.forEach((issue) => {\n const path = issue.path.join('.');\n nextErrors[path] = issue.message;\n });\n setErrors(nextErrors);\n }\n\n return false;\n }\n };\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = workspace\n ? {\n name: formData.name !== workspace.name ? formData.name : undefined,\n type: formData.type !== workspace.type ? formData.type : undefined,\n }\n : {\n tenantId: formData.tenantId,\n organizationId: formData.organizationId,\n name: formData.name,\n slug: formData.slug,\n type: formData.type || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {!workspace ? (\n <>\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n {showOrganizationSelector && !tenantId ? (\n <div className=\"relative\">\n <select\n id=\"tenantId\"\n value={formData.tenantId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n tenantId: event.target.value,\n organizationId: '',\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={loading || tenantsLoading}\n required\n >\n <option value=\"\">\n {tr('workspaces.form.selectTenant', 'Select a tenant...')}\n </option>\n {tenants.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name} ({tenant.slug})\n </option>\n ))}\n </select>\n <Home\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {tenantsLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"relative\">\n <input\n id=\"tenantId\"\n type=\"text\"\n value={selectedTenantLabel}\n aria-label={tr('organizations.form.tenant', 'Tenant')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n tenantId: event.target.value,\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-4 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('organizations.form.tenantPlaceholder', 'Enter tenant ID')}\n disabled={loading || Boolean(tenantId)}\n required\n autoComplete=\"off\"\n />\n <Home\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n </div>\n )}\n\n {(tenantId || formData.tenantId) && (\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.form.creatingUnderTenant',\n 'Creating workspace under the selected tenant.'\n )}\n </p>\n )}\n {errors.tenantId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.tenantId}\n </p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"organizationId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.organization', 'Organization')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n {showOrganizationSelector && !organizationId ? (\n <div className=\"relative\">\n <select\n id=\"organizationId\"\n value={formData.organizationId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={\n loading || organizationsLoading || permissionCheckLoading || !formData.tenantId\n }\n required\n >\n <option value=\"\">\n {!formData.tenantId\n ? tr('workspaces.form.selectTenantFirst', 'Select a tenant first')\n : permittedOrganizations.length === 0 &&\n !organizationsLoading &&\n !permissionCheckLoading\n ? tr(\n 'workspaces.form.noOrganizationsAvailable',\n 'No organizations available — you need workspace create permission'\n )\n : tr('workspaces.form.selectOrganization', 'Select an organization...')}\n </option>\n {permittedOrganizations.map((organization) => (\n <option key={organization.id} value={organization.id}>\n {organization.name} ({organization.slug})\n </option>\n ))}\n </select>\n <Building\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {organizationsLoading || permissionCheckLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"relative\">\n <input\n id=\"organizationId\"\n type=\"text\"\n value={selectedOrganizationLabel}\n aria-label={tr('common.organization', 'Organization')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-4 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.orgIdPlaceholder', 'Enter organization ID')}\n disabled={loading || Boolean(organizationId)}\n required\n autoComplete=\"off\"\n />\n <Building\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n </div>\n )}\n\n {(organizationId || formData.organizationId) && (\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.form.creatingUnderOrg',\n 'Creating workspace under the selected organization.'\n )}\n </p>\n )}\n {errors.organizationId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.organizationId}\n </p>\n ) : null}\n </div>\n </>\n ) : null}\n\n <div>\n <label\n htmlFor=\"name\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.form.name', 'Workspace Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n aria-label={tr('workspaces.form.name', 'Workspace Name')}\n onChange={(event) =>\n setFormData((previousState) => ({ ...previousState, name: event.target.value }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.namePlaceholder', 'Enter workspace name')}\n disabled={loading}\n required\n autoComplete=\"organization-title\"\n />\n {errors.name ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"slug\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n aria-label={tr('common.slug', 'Slug')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n slug: event.target.value.toLowerCase().replace(/\\s+/g, '-'),\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.slugPlaceholder', 'workspace-slug')}\n disabled={loading || Boolean(workspace)}\n required={!workspace}\n autoComplete=\"off\"\n />\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"type\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.list.column.type', 'Type')}\n </label>\n <select\n id=\"type\"\n value={formData.type}\n onChange={(event) =>\n setFormData((previousState) => ({ ...previousState, type: event.target.value }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={loading}\n >\n <option value=\"PERSONAL\">{tr('workspaces.type.personal', 'Personal')}</option>\n <option value=\"TEAM\">{tr('workspaces.type.team', 'Team')}</option>\n <option value=\"ENTERPRISE\">{tr('workspaces.type.enterprise', 'Enterprise')}</option>\n </select>\n </div>\n\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 hover:scale-[1.02] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[220px]\"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {workspace\n ? tr('workspaces.form.updateSubmit', 'Update Workspace')\n : tr('workspaces.create.button', 'Create Workspace')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-organization",
3
- "version": "2026.527.1",
3
+ "version": "2026.527.2",
4
4
  "description": "Organization management microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [
@@ -30,6 +30,7 @@
30
30
  "lint:sanity": "if [ \"${CI:-}\" = \"true\" ]; then bun run lint; else bun run lint -- --cache --cache-location .eslintcache; fi",
31
31
  "format": "prettier --write \"src/**/*.{ts,tsx}\"",
32
32
  "type:check": "tsc --noEmit",
33
+ "knip": "bunx knip",
33
34
  "test": "vitest run",
34
35
  "test:watch": "vitest",
35
36
  "test:coverage": "vitest run --coverage",