@burdenoff/microfe-workspaces 2026.830.1 → 2026.915.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/components/CreateProjectDialog.js +65 -61
  2. package/dist/components/CreateProjectDialog.js.map +1 -1
  3. package/dist/components/CreateWorkspaceDialog.js +93 -90
  4. package/dist/components/CreateWorkspaceDialog.js.map +1 -1
  5. package/dist/components/InviteMemberDialog.js +122 -111
  6. package/dist/components/InviteMemberDialog.js.map +1 -1
  7. package/dist/components/MemberListItem.js +55 -58
  8. package/dist/components/MemberListItem.js.map +1 -1
  9. package/dist/components/ProjectCard.js +93 -92
  10. package/dist/components/ProjectCard.js.map +1 -1
  11. package/dist/components/ProjectMemberPickerDialog.js +78 -93
  12. package/dist/components/ProjectMemberPickerDialog.js.map +1 -1
  13. package/dist/components/RenameProjectDialog.js +47 -44
  14. package/dist/components/RenameProjectDialog.js.map +1 -1
  15. package/dist/components/WorkspaceCard.js +42 -43
  16. package/dist/components/WorkspaceCard.js.map +1 -1
  17. package/dist/components/WorkspacesErrorBoundary.js +43 -34
  18. package/dist/components/WorkspacesErrorBoundary.js.map +1 -1
  19. package/dist/components/tags/ProjectTagsWidget.js +75 -77
  20. package/dist/components/tags/ProjectTagsWidget.js.map +1 -1
  21. package/dist/hooks/useTr.js +14 -0
  22. package/dist/hooks/useTr.js.map +1 -0
  23. package/dist/index.js +2 -1
  24. package/dist/pages/ActivityListPage.js +3 -3
  25. package/dist/pages/CreateWorkspacePage.js +10 -10
  26. package/dist/pages/InviteAcceptancePage.js +49 -49
  27. package/dist/pages/InviteAcceptancePage.js.map +1 -1
  28. package/dist/pages/MembersListPage.js +6 -6
  29. package/dist/pages/PendingInvitesPage.js +3 -3
  30. package/dist/pages/ProjectDetailPage.js +4 -4
  31. package/dist/pages/ProjectDetailPage.js.map +1 -1
  32. package/dist/pages/ProjectMembersPage.js +3 -3
  33. package/dist/pages/ProjectsListPage.js +5 -5
  34. package/dist/pages/WorkspaceDetailPage.js +4 -4
  35. package/dist/pages/WorkspaceInvitePage.js +4 -4
  36. package/dist/pages/WorkspaceSettingsPage.js +7 -7
  37. package/dist/pages/WorkspacesListPage.js +9 -9
  38. package/dist/pages/dashboard/widgets/RecentActivityWidget.js +4 -4
  39. package/dist/pages/dashboard/widgets/StorageWidget.js +3 -3
  40. package/dist/pages/dashboard/widgets/WelcomeWidget.js +26 -16
  41. package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +1 -1
  42. package/dist/translations/en.js +908 -0
  43. package/dist/translations/en.js.map +1 -0
  44. package/package.json +1 -1
@@ -1,14 +1,15 @@
1
- import { useCallback as e, useEffect as t, useRef as n, useState as r } from "react";
2
- import { Loader2 as i, Plus as ee, Tag as a, X as te } from "lucide-react";
1
+ import { useTr as e } from "../../hooks/useTr.js";
2
+ import { useCallback as t, useEffect as n, useRef as r, useState as i } from "react";
3
+ import { Loader2 as a, Plus as ee, Tag as te, X as ne } from "lucide-react";
3
4
  import { jsx as o, jsxs as s } from "react/jsx-runtime";
4
- import { Command as ne, CommandEmpty as c, CommandGroup as l, CommandInput as u, CommandItem as d, CommandList as f, Popover as p, PopoverContent as m, PopoverTrigger as re } from "@burdenoff/fe-libs/ui";
5
- import { cn as h } from "@burdenoff/fe-libs/shared/utils";
6
- import { useGatewayClient as ie } from "@burdenoff/fe-libs/shared/graphql";
7
- import { gql as g } from "@apollo/client";
8
- import { useLazyQuery as _, useMutation as v } from "@apollo/client/react";
9
- import { TagBadge as y } from "@burdenoff/fe-libs/tag";
5
+ import { Command as re, CommandEmpty as c, CommandGroup as l, CommandInput as u, CommandItem as d, CommandList as f, Popover as p, PopoverContent as ie, PopoverTrigger as ae } from "@burdenoff/fe-libs/ui";
6
+ import { cn as m } from "@burdenoff/fe-libs/shared/utils";
7
+ import { useGatewayClient as oe } from "@burdenoff/fe-libs/shared/graphql";
8
+ import { gql as h } from "@apollo/client";
9
+ import { useLazyQuery as g, useMutation as _ } from "@apollo/client/react";
10
+ import { TagBadge as v } from "@burdenoff/fe-libs/tag";
10
11
  //#region src/components/tags/ProjectTagsWidget.tsx
11
- var b = g`
12
+ var y = h`
12
13
  query ProjectTagsWidget_SearchTags($query: String!, $vocabularyId: ID, $limit: Int) {
13
14
  searchTags(query: $query, vocabularyId: $vocabularyId, limit: $limit) {
14
15
  id
@@ -26,7 +27,7 @@ var b = g`
26
27
  }
27
28
  }
28
29
  }
29
- `, x = g`
30
+ `, b = h`
30
31
  query ProjectTagsWidget_GetVocabularyByKey($key: String!) {
31
32
  getVocabularyByKey(key: $key) {
32
33
  id
@@ -34,7 +35,7 @@ var b = g`
34
35
  name
35
36
  }
36
37
  }
37
- `, ae = g`
38
+ `, se = h`
38
39
  mutation ProjectTagsWidget_CreateVocabulary($input: CreateVocabularyInput!) {
39
40
  createVocabulary(input: $input) {
40
41
  id
@@ -42,7 +43,7 @@ var b = g`
42
43
  name
43
44
  }
44
45
  }
45
- `, oe = g`
46
+ `, x = h`
46
47
  mutation ProjectTagsWidget_CreateTag($input: CreateTagInput!) {
47
48
  createTag(input: $input) {
48
49
  id
@@ -65,11 +66,11 @@ function C(e) {
65
66
  let t = e;
66
67
  return typeof t.id == "string" && typeof t.key == "string" && typeof t.label == "string";
67
68
  }
68
- function w({ entityType: g, entityId: w, workspaceId: se, tags: T, onAdd: E, onRemove: D, allowCreate: O = !0, maxTags: k = 20, className: ce }) {
69
- let [le, A] = r(!1), [j, M] = r(""), [N, P] = r(null), [F, I] = r(!1), [L, R] = r(), z = n(null), B = ie("workspace"), V = !se || !w, H = new Set(T.map((e) => e.id)), U = T.length < k, [W, { data: ue, loading: G }] = _(b, { client: B }), K = (ue?.searchTags ?? []).filter((e) => C(e) && !H.has(e.id)), [q, { loading: J }] = v(oe, { client: B }), [Y] = _(x, { client: B }), [X] = v(ae, { client: B }), Z = e(async () => {
70
- if (L) return L;
69
+ function w({ entityType: h, entityId: w, workspaceId: ce, tags: T, onAdd: E, onRemove: D, allowCreate: O = !0, maxTags: k = 20, className: le }) {
70
+ let A = e(), [ue, j] = i(!1), [M, N] = i(""), [P, F] = i(null), [I, L] = i(!1), [R, z] = i(), B = r(null), V = oe("workspace"), H = !ce || !w, de = new Set(T.map((e) => e.id)), U = T.length < k, [W, { data: fe, loading: G }] = g(y, { client: V }), K = (fe?.searchTags ?? []).filter((e) => C(e) && !de.has(e.id)), [q, { loading: J }] = _(x, { client: V }), [Y] = g(b, { client: V }), [X] = _(se, { client: V }), Z = t(async () => {
71
+ if (R) return R;
71
72
  let e = (await Y({ variables: { key: S } })).data?.getVocabularyByKey;
72
- if (e) return R(e.id), e.id;
73
+ if (e) return z(e.id), e.id;
73
74
  let t = (await X({ variables: { input: {
74
75
  key: S,
75
76
  name: "General",
@@ -77,51 +78,52 @@ function w({ entityType: g, entityId: w, workspaceId: se, tags: T, onAdd: E, onR
77
78
  scope: "WORKSPACE",
78
79
  allowCustomTags: !0
79
80
  } } })).data?.createVocabulary;
80
- if (!t) throw Error("Failed to create default vocabulary");
81
- return R(t.id), t.id;
81
+ if (!t) throw Error(A("projectTagsWidget.createVocabularyError", "Failed to create default vocabulary"));
82
+ return z(t.id), t.id;
82
83
  }, [
83
- L,
84
+ R,
84
85
  Y,
85
- X
86
- ]), de = e((e) => {
87
- M(e), N && P(null), z.current && clearTimeout(z.current), e.trim() && (z.current = setTimeout(() => {
86
+ X,
87
+ A
88
+ ]), pe = t((e) => {
89
+ N(e), P && F(null), B.current && clearTimeout(B.current), e.trim() && (B.current = setTimeout(() => {
88
90
  W({ variables: {
89
91
  query: e,
90
- vocabularyId: L,
92
+ vocabularyId: R,
91
93
  limit: 10
92
94
  } });
93
95
  }, 250));
94
96
  }, [
95
97
  W,
96
- L,
97
- N
98
+ R,
99
+ P
98
100
  ]);
99
- t(() => () => {
100
- z.current && clearTimeout(z.current);
101
+ n(() => () => {
102
+ B.current && clearTimeout(B.current);
101
103
  }, []);
102
- let Q = e(async (e) => {
103
- if (!(!U || F)) {
104
- I(!0);
104
+ let Q = t(async (e) => {
105
+ if (!(!U || I)) {
106
+ L(!0);
105
107
  try {
106
108
  await E({
107
109
  ...e,
108
110
  vocabularyKey: e.vocabularyKey ?? e.vocabulary?.key ?? null
109
- }), M(""), A(!1);
111
+ }), N(""), j(!1);
110
112
  } finally {
111
- I(!1);
113
+ L(!1);
112
114
  }
113
115
  }
114
116
  }, [
115
117
  E,
116
118
  U,
117
- F
118
- ]), fe = e(async () => {
119
- if (!(!O || !j.trim() || J)) {
120
- P(null);
119
+ I
120
+ ]), me = t(async () => {
121
+ if (!(!O || !M.trim() || J)) {
122
+ F(null);
121
123
  try {
122
- let e = j.trim(), t = e.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
124
+ let e = M.trim(), t = e.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
123
125
  if (!t) {
124
- P("Must contain at least one letter or number");
126
+ F(A("projectTagsWidget.createValidationError", "Must contain at least one letter or number"));
125
127
  return;
126
128
  }
127
129
  let n = (await q({ variables: { input: {
@@ -132,69 +134,70 @@ function w({ entityType: g, entityId: w, workspaceId: se, tags: T, onAdd: E, onR
132
134
  if (!C(n)) throw Error("No data returned");
133
135
  await Q(n);
134
136
  } catch (e) {
135
- P(e instanceof Error ? e.message : "Failed to create tag");
137
+ F(e instanceof Error ? e.message : A("projectTagsWidget.createGenericError", "Failed to create tag"));
136
138
  }
137
139
  }
138
140
  }, [
139
141
  O,
140
- j,
142
+ M,
141
143
  J,
142
144
  Z,
143
145
  q,
144
- Q
145
- ]), pe = e(async (e) => {
146
+ Q,
147
+ A
148
+ ]), he = t(async (e) => {
146
149
  await D(e);
147
150
  }, [D]);
148
- if (V) return null;
149
- let $ = !!(O && j.trim() && !G && K.length === 0);
151
+ if (H) return null;
152
+ let $ = !!(O && M.trim() && !G && K.length === 0);
150
153
  return /* @__PURE__ */ s("div", {
151
- className: h("flex flex-wrap items-center gap-1.5", ce),
152
- children: [T.map((e) => /* @__PURE__ */ o(y, {
154
+ className: m("flex flex-wrap items-center gap-1.5", le),
155
+ children: [T.map((e) => /* @__PURE__ */ o(v, {
153
156
  id: e.id,
154
157
  label: e.label,
155
158
  color: e.color ?? void 0,
156
159
  icon: e.icon ?? void 0,
157
160
  description: e.description ?? void 0,
158
161
  removable: !0,
159
- onRemove: () => void pe(e.id),
162
+ onRemove: () => void he(e.id),
160
163
  size: "sm"
161
164
  }, e.id)), U && /* @__PURE__ */ s(p, {
162
- open: le,
163
- onOpenChange: A,
164
- children: [/* @__PURE__ */ s(re, {
165
+ open: ue,
166
+ onOpenChange: j,
167
+ children: [/* @__PURE__ */ s(ae, {
165
168
  type: "button",
166
- "aria-label": "Add tag",
167
- className: h("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border border-dashed", "border-border-subtle text-text-secondary text-xs", "hover:border-foreground hover:text-text-primary transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"),
168
- children: [/* @__PURE__ */ o(a, { className: "size-3" }), /* @__PURE__ */ o("span", { children: T.length > 0 ? "Add" : "Add tags" })]
169
- }), /* @__PURE__ */ o(m, {
169
+ "aria-label": A("projectTagsWidget.addTagAriaLabel", "Add tag"),
170
+ className: m("inline-flex items-center gap-1 px-2 py-0.5 rounded-full border border-dashed", "border-border-subtle text-text-secondary text-xs", "hover:border-foreground hover:text-text-primary transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"),
171
+ children: [/* @__PURE__ */ o(te, { className: "size-3" }), /* @__PURE__ */ o("span", { children: T.length > 0 ? A("projectTagsWidget.add", "Add") : A("projectTagsWidget.addTags", "Add tags") })]
172
+ }), /* @__PURE__ */ o(ie, {
170
173
  className: "z-50 w-72 max-w-[calc(100vw-2rem)] border-border-subtle/80 bg-popover p-0 shadow-xl",
171
174
  align: "start",
172
175
  side: "bottom",
173
176
  sideOffset: 8,
174
- children: /* @__PURE__ */ s(ne, {
177
+ children: /* @__PURE__ */ s(re, {
175
178
  shouldFilter: !1,
176
179
  children: [
177
180
  /* @__PURE__ */ s("div", {
178
181
  className: "relative",
179
182
  children: [/* @__PURE__ */ o(u, {
180
- placeholder: "Search or create a tag...",
181
- value: j,
182
- onValueChange: de,
183
+ placeholder: A("projectTagsWidget.searchPlaceholder", "Search or create a tag..."),
184
+ value: M,
185
+ onValueChange: pe,
183
186
  className: "h-10 border-0 bg-transparent pr-9 text-sm outline-none placeholder:text-text-secondary"
184
- }), G && /* @__PURE__ */ o(i, { className: "absolute right-3 top-1/2 size-3.5 -translate-y-1/2 animate-spin text-text-secondary" })]
187
+ }), G && /* @__PURE__ */ o(a, { className: "absolute right-3 top-1/2 size-3.5 -translate-y-1/2 animate-spin text-text-secondary" })]
185
188
  }),
186
189
  /* @__PURE__ */ s(f, {
187
190
  className: "max-h-48 overflow-y-auto",
188
191
  children: [
189
- j.trim() === "" && /* @__PURE__ */ o(c, {
192
+ M.trim() === "" && /* @__PURE__ */ o(c, {
190
193
  className: "py-4 text-center text-xs text-text-secondary",
191
- children: "Type to search or create a tag"
194
+ children: A("projectTagsWidget.typeToSearch", "Type to search or create a tag")
192
195
  }),
193
196
  K.length > 0 && /* @__PURE__ */ o(l, { children: K.map((e) => /* @__PURE__ */ s(d, {
194
197
  value: e.id,
195
198
  onSelect: () => void Q(e),
196
199
  className: "flex items-center gap-2 px-3 py-2 cursor-pointer",
197
- disabled: F,
200
+ disabled: I,
198
201
  children: [
199
202
  e.icon && /* @__PURE__ */ o("span", {
200
203
  className: "text-sm",
@@ -205,37 +208,32 @@ function w({ entityType: g, entityId: w, workspaceId: se, tags: T, onAdd: E, onR
205
208
  style: e.color ? { color: e.color } : void 0,
206
209
  children: e.label
207
210
  }),
208
- F && /* @__PURE__ */ o(i, { className: "ml-auto size-3 animate-spin" })
211
+ I && /* @__PURE__ */ o(a, { className: "ml-auto size-3 animate-spin" })
209
212
  ]
210
213
  }, e.id)) }),
211
214
  $ && /* @__PURE__ */ o(l, { children: /* @__PURE__ */ s(d, {
212
- value: `__create__${j}`,
213
- onSelect: () => void fe(),
215
+ value: `__create__${M}`,
216
+ onSelect: () => void me(),
214
217
  className: "flex items-center gap-2 px-3 py-2 cursor-pointer text-primary",
215
218
  disabled: J,
216
- children: [J ? /* @__PURE__ */ o(i, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ o(ee, { className: "size-3.5" }), /* @__PURE__ */ o("span", {
219
+ children: [J ? /* @__PURE__ */ o(a, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ o(ee, { className: "size-3.5" }), /* @__PURE__ */ o("span", {
217
220
  className: "text-sm truncate",
218
- children: J ? "Creating..." : `Create "${j}"`
221
+ children: J ? A("projectTagsWidget.creating", "Creating...") : A("projectTagsWidget.create", "Create \"{{label}}\"", { label: M })
219
222
  })]
220
223
  }) }),
221
- j.trim() !== "" && !G && K.length === 0 && !$ && /* @__PURE__ */ o(c, {
224
+ M.trim() !== "" && !G && K.length === 0 && !$ && /* @__PURE__ */ o(c, {
222
225
  className: "py-4 text-center text-xs text-text-secondary",
223
- children: "No tags found"
226
+ children: A("projectTagsWidget.noTagsFound", "No tags found")
224
227
  }),
225
- N && /* @__PURE__ */ o("div", {
228
+ P && /* @__PURE__ */ o("div", {
226
229
  className: "px-3 py-2 text-xs text-status-error-text border-t border-border-subtle",
227
- children: N
230
+ children: P
228
231
  })
229
232
  ]
230
233
  }),
231
234
  !U && /* @__PURE__ */ s("div", {
232
235
  className: "border-t border-border-subtle px-3 py-2 text-xs text-text-secondary flex items-center gap-1",
233
- children: [
234
- /* @__PURE__ */ o(te, { className: "size-3" }),
235
- "Maximum ",
236
- k,
237
- " tags reached"
238
- ]
236
+ children: [/* @__PURE__ */ o(ne, { className: "size-3" }), A("projectTagsWidget.maxTagsReached", "Maximum {{maxTags}} tags reached", { maxTags: k })]
239
237
  })
240
238
  ]
241
239
  })
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectTagsWidget.js","names":[],"sources":["../../../src/components/tags/ProjectTagsWidget.tsx"],"sourcesContent":["/**\n * ProjectTagsWidget\n *\n * Local replacement for `@burdenoff/fe-libs/tag` `TagsWidget`, used until\n * fe-libs ships a fix for the non-clickable `AddTagTrigger` (the published\n * trigger swallows the props Radix Slot tries to forward via\n * `<PopoverTrigger asChild>`, so the popover never opens).\n *\n * This component bypasses the broken trigger by rendering a regular\n * `<PopoverTrigger>` (no `asChild`) so Radix mounts the click-handling\n * button itself. Internals (search, create, assign, remove) mirror the\n * fe-libs `TagsWidget` so the data path through wspace-tags-svc is\n * identical.\n *\n * TODO(fe-libs): once `@burdenoff/fe-libs/tag` `AddTagTrigger` forwards\n * props/ref to the underlying button, delete this file and switch back\n * to `<TagsWidget>` from `@burdenoff/fe-libs/tag`.\n */\n\nimport { gql, type ApolloClient } from '@apollo/client';\nimport { useLazyQuery, useMutation } from '@apollo/client/react';\nimport { Loader2, Plus, Tag as TagIcon, X } from 'lucide-react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { type TagItem } from '@burdenoff/fe-libs/tag';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { cn } from '@burdenoff/fe-libs/shared/utils';\n\n// --- GraphQL ----------------------------------------------------------------\n\ninterface VocabResult {\n id: string;\n key: string;\n name: string;\n}\n\nconst SEARCH_TAGS = gql`\n query ProjectTagsWidget_SearchTags($query: String!, $vocabularyId: ID, $limit: Int) {\n searchTags(query: $query, vocabularyId: $vocabularyId, limit: $limit) {\n id\n key\n label\n value\n vocabularyId\n parentId\n hierarchyPath\n color\n icon\n description\n vocabulary {\n key\n }\n }\n }\n`;\n\nconst GET_VOCABULARY_BY_KEY = gql`\n query ProjectTagsWidget_GetVocabularyByKey($key: String!) {\n getVocabularyByKey(key: $key) {\n id\n key\n name\n }\n }\n`;\n\nconst CREATE_VOCABULARY = gql`\n mutation ProjectTagsWidget_CreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) {\n id\n key\n name\n }\n }\n`;\n\nconst CREATE_TAG = gql`\n mutation ProjectTagsWidget_CreateTag($input: CreateTagInput!) {\n createTag(input: $input) {\n id\n key\n label\n value\n vocabularyId\n parentId\n hierarchyPath\n color\n icon\n vocabulary {\n key\n }\n }\n }\n`;\n\nconst DEFAULT_VOCAB_KEY = 'general';\n\nfunction isTagItem(value: unknown): value is TagItem {\n if (!value || typeof value !== 'object') return false;\n const tag = value as Partial<Record<keyof TagItem, unknown>>;\n return typeof tag.id === 'string' && typeof tag.key === 'string' && typeof tag.label === 'string';\n}\n\n// --- Props ------------------------------------------------------------------\n\nexport interface ProjectTagsWidgetProps {\n /** Entity type recorded on the assigned-tag projection (e.g. \"Project\"). */\n entityType: string;\n /** Entity id (project id). */\n entityId: string;\n /** Workspace scope — widget renders nothing when empty. */\n workspaceId?: string;\n /** Controlled tag list owned by the parent. */\n tags: TagItem[];\n /** Persist a newly assigned tag on the entity. */\n onAdd: (tag: TagItem) => void | Promise<void>;\n /** Persist a tag removal on the entity. */\n onRemove: (tagId: string) => void | Promise<void>;\n /** Allow creating new tags on the fly. Default: true. */\n allowCreate?: boolean;\n /** Cap on assigned tags. Default 20. */\n maxTags?: number;\n className?: string;\n}\n\n// --- Component --------------------------------------------------------------\n\nexport function ProjectTagsWidget({\n entityType: _entityType,\n entityId,\n workspaceId,\n tags,\n onAdd,\n onRemove,\n allowCreate = true,\n maxTags = 20,\n className,\n}: ProjectTagsWidgetProps) {\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [createError, setCreateError] = useState<string | null>(null);\n const [adding, setAdding] = useState(false);\n const [resolvedVocabId, setResolvedVocabId] = useState<string | undefined>();\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Bun may install Apollo twice when fe-libs is consumed as a package; the runtime client is compatible.\n const wsClient = useGatewayClient('workspace') as unknown as ApolloClient;\n\n const skip = !workspaceId || !entityId;\n\n const assignedTagIds = new Set(tags.map((t) => t.id));\n const canAddMore = tags.length < maxTags;\n\n const [runSearch, { data: searchData, loading: searching }] = useLazyQuery<{\n searchTags: TagItem[];\n }>(SEARCH_TAGS, { client: wsClient });\n\n const searchResults = (searchData?.searchTags ?? []).filter(\n (tag): tag is TagItem => isTagItem(tag) && !assignedTagIds.has(tag.id)\n );\n\n const [createTag, { loading: creating }] = useMutation<{\n createTag: TagItem;\n }>(CREATE_TAG, { client: wsClient });\n\n const [fetchVocabByKey] = useLazyQuery<{\n getVocabularyByKey: VocabResult | null;\n }>(GET_VOCABULARY_BY_KEY, { client: wsClient });\n\n const [createVocabulary] = useMutation<{\n createVocabulary: VocabResult;\n }>(CREATE_VOCABULARY, { client: wsClient });\n\n const resolveVocabId = useCallback(async (): Promise<string> => {\n if (resolvedVocabId) return resolvedVocabId;\n\n const res = await fetchVocabByKey({ variables: { key: DEFAULT_VOCAB_KEY } });\n const existing = res.data?.getVocabularyByKey;\n if (existing) {\n setResolvedVocabId(existing.id);\n return existing.id;\n }\n\n const created = await createVocabulary({\n variables: {\n input: {\n key: DEFAULT_VOCAB_KEY,\n name: 'General',\n description: 'Default vocabulary for general-purpose tags',\n scope: 'WORKSPACE',\n allowCustomTags: true,\n },\n },\n });\n const newVocab = created.data?.createVocabulary;\n if (!newVocab) throw new Error('Failed to create default vocabulary');\n setResolvedVocabId(newVocab.id);\n return newVocab.id;\n }, [resolvedVocabId, fetchVocabByKey, createVocabulary]);\n\n const handleSearchChange = useCallback(\n (value: string) => {\n setSearch(value);\n if (createError) setCreateError(null);\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (!value.trim()) return;\n debounceRef.current = setTimeout(() => {\n void runSearch({\n variables: { query: value, vocabularyId: resolvedVocabId, limit: 10 },\n });\n }, 250);\n },\n [runSearch, resolvedVocabId, createError]\n );\n\n useEffect(() => {\n return () => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n };\n }, []);\n\n const handleAssign = useCallback(\n async (tag: TagItem) => {\n if (!canAddMore || adding) return;\n setAdding(true);\n try {\n const normalizedTag: TagItem = {\n ...tag,\n vocabularyKey: tag.vocabularyKey ?? tag.vocabulary?.key ?? null,\n };\n await onAdd(normalizedTag);\n setSearch('');\n setOpen(false);\n } finally {\n setAdding(false);\n }\n },\n [onAdd, canAddMore, adding]\n );\n\n const handleCreate = useCallback(async () => {\n if (!allowCreate || !search.trim() || creating) return;\n setCreateError(null);\n try {\n const label = search.trim();\n const key = label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n if (!key) {\n setCreateError('Must contain at least one letter or number');\n return;\n }\n const vocabId = await resolveVocabId();\n const res = await createTag({\n variables: { input: { key, label, vocabularyId: vocabId } },\n });\n const created = res.data?.createTag;\n if (!isTagItem(created)) throw new Error('No data returned');\n await handleAssign(created);\n } catch (err) {\n setCreateError(err instanceof Error ? err.message : 'Failed to create tag');\n }\n }, [allowCreate, search, creating, resolveVocabId, createTag, handleAssign]);\n\n const handleRemove = useCallback(\n async (tagId: string) => {\n await onRemove(tagId);\n },\n [onRemove]\n );\n\n if (skip) return null;\n\n const showCreateOption = Boolean(\n allowCreate && search.trim() && !searching && searchResults.length === 0\n );\n\n return (\n <div className={cn('flex flex-wrap items-center gap-1.5', className)}>\n {tags.map((tag) => (\n <TagBadge\n key={tag.id}\n id={tag.id}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n removable\n onRemove={() => void handleRemove(tag.id)}\n size=\"sm\"\n />\n ))}\n\n {canAddMore && (\n <Popover open={open} onOpenChange={setOpen}>\n {/*\n Intentionally do NOT use `asChild` here — the fe-libs\n `AddTagTrigger` swallows the props Radix forwards, leaving the\n trigger non-clickable. Letting Radix render its own button\n restores the click handler.\n */}\n <PopoverTrigger\n type=\"button\"\n aria-label=\"Add tag\"\n className={cn(\n 'inline-flex items-center gap-1 px-2 py-0.5 rounded-full border border-dashed',\n 'border-border-subtle text-text-secondary text-xs',\n 'hover:border-foreground hover:text-text-primary transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <TagIcon className=\"size-3\" />\n <span>{tags.length > 0 ? 'Add' : 'Add tags'}</span>\n </PopoverTrigger>\n\n <PopoverContent\n className=\"z-50 w-72 max-w-[calc(100vw-2rem)] border-border-subtle/80 bg-popover p-0 shadow-xl\"\n align=\"start\"\n side=\"bottom\"\n sideOffset={8}\n >\n <Command shouldFilter={false}>\n <div className=\"relative\">\n <CommandInput\n placeholder=\"Search or create a tag...\"\n value={search}\n onValueChange={handleSearchChange}\n className=\"h-10 border-0 bg-transparent pr-9 text-sm outline-none placeholder:text-text-secondary\"\n />\n {searching && (\n <Loader2 className=\"absolute right-3 top-1/2 size-3.5 -translate-y-1/2 animate-spin text-text-secondary\" />\n )}\n </div>\n\n <CommandList className=\"max-h-48 overflow-y-auto\">\n {search.trim() === '' && (\n <CommandEmpty className=\"py-4 text-center text-xs text-text-secondary\">\n Type to search or create a tag\n </CommandEmpty>\n )}\n\n {searchResults.length > 0 && (\n <CommandGroup>\n {searchResults.map((tag) => (\n <CommandItem\n key={tag.id}\n value={tag.id}\n onSelect={() => void handleAssign(tag)}\n className=\"flex items-center gap-2 px-3 py-2 cursor-pointer\"\n disabled={adding}\n >\n {tag.icon && <span className=\"text-sm\">{tag.icon}</span>}\n <span\n className=\"truncate text-sm\"\n style={tag.color ? { color: tag.color } : undefined}\n >\n {tag.label}\n </span>\n {adding && <Loader2 className=\"ml-auto size-3 animate-spin\" />}\n </CommandItem>\n ))}\n </CommandGroup>\n )}\n\n {showCreateOption && (\n <CommandGroup>\n <CommandItem\n value={`__create__${search}`}\n onSelect={() => void handleCreate()}\n className=\"flex items-center gap-2 px-3 py-2 cursor-pointer text-primary\"\n disabled={creating}\n >\n {creating ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Plus className=\"size-3.5\" />\n )}\n <span className=\"text-sm truncate\">\n {creating ? 'Creating...' : `Create \"${search}\"`}\n </span>\n </CommandItem>\n </CommandGroup>\n )}\n\n {search.trim() !== '' &&\n !searching &&\n searchResults.length === 0 &&\n !showCreateOption && (\n <CommandEmpty className=\"py-4 text-center text-xs text-text-secondary\">\n No tags found\n </CommandEmpty>\n )}\n\n {createError && (\n <div className=\"px-3 py-2 text-xs text-status-error-text border-t border-border-subtle\">\n {createError}\n </div>\n )}\n </CommandList>\n\n {!canAddMore && (\n <div className=\"border-t border-border-subtle px-3 py-2 text-xs text-text-secondary flex items-center gap-1\">\n <X className=\"size-3\" />\n Maximum {maxTags} tags reached\n </div>\n )}\n </Command>\n </PopoverContent>\n </Popover>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProjectTagsWidget.js","names":[],"sources":["../../../src/components/tags/ProjectTagsWidget.tsx"],"sourcesContent":["/**\n * ProjectTagsWidget\n *\n * Local replacement for `@burdenoff/fe-libs/tag` `TagsWidget`, used until\n * fe-libs ships a fix for the non-clickable `AddTagTrigger` (the published\n * trigger swallows the props Radix Slot tries to forward via\n * `<PopoverTrigger asChild>`, so the popover never opens).\n *\n * This component bypasses the broken trigger by rendering a regular\n * `<PopoverTrigger>` (no `asChild`) so Radix mounts the click-handling\n * button itself. Internals (search, create, assign, remove) mirror the\n * fe-libs `TagsWidget` so the data path through wspace-tags-svc is\n * identical.\n *\n * TODO(fe-libs): once `@burdenoff/fe-libs/tag` `AddTagTrigger` forwards\n * props/ref to the underlying button, delete this file and switch back\n * to `<TagsWidget>` from `@burdenoff/fe-libs/tag`.\n */\n\nimport { gql, type ApolloClient } from '@apollo/client';\nimport { useLazyQuery, useMutation } from '@apollo/client/react';\nimport { Loader2, Plus, Tag as TagIcon, X } from 'lucide-react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { type TagItem } from '@burdenoff/fe-libs/tag';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { cn } from '@burdenoff/fe-libs/shared/utils';\nimport { useTr } from '../../hooks/useTr';\n\n// --- GraphQL ----------------------------------------------------------------\n\ninterface VocabResult {\n id: string;\n key: string;\n name: string;\n}\n\nconst SEARCH_TAGS = gql`\n query ProjectTagsWidget_SearchTags($query: String!, $vocabularyId: ID, $limit: Int) {\n searchTags(query: $query, vocabularyId: $vocabularyId, limit: $limit) {\n id\n key\n label\n value\n vocabularyId\n parentId\n hierarchyPath\n color\n icon\n description\n vocabulary {\n key\n }\n }\n }\n`;\n\nconst GET_VOCABULARY_BY_KEY = gql`\n query ProjectTagsWidget_GetVocabularyByKey($key: String!) {\n getVocabularyByKey(key: $key) {\n id\n key\n name\n }\n }\n`;\n\nconst CREATE_VOCABULARY = gql`\n mutation ProjectTagsWidget_CreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) {\n id\n key\n name\n }\n }\n`;\n\nconst CREATE_TAG = gql`\n mutation ProjectTagsWidget_CreateTag($input: CreateTagInput!) {\n createTag(input: $input) {\n id\n key\n label\n value\n vocabularyId\n parentId\n hierarchyPath\n color\n icon\n vocabulary {\n key\n }\n }\n }\n`;\n\nconst DEFAULT_VOCAB_KEY = 'general';\n\nfunction isTagItem(value: unknown): value is TagItem {\n if (!value || typeof value !== 'object') return false;\n const tag = value as Partial<Record<keyof TagItem, unknown>>;\n return typeof tag.id === 'string' && typeof tag.key === 'string' && typeof tag.label === 'string';\n}\n\n// --- Props ------------------------------------------------------------------\n\nexport interface ProjectTagsWidgetProps {\n /** Entity type recorded on the assigned-tag projection (e.g. \"Project\"). */\n entityType: string;\n /** Entity id (project id). */\n entityId: string;\n /** Workspace scope — widget renders nothing when empty. */\n workspaceId?: string;\n /** Controlled tag list owned by the parent. */\n tags: TagItem[];\n /** Persist a newly assigned tag on the entity. */\n onAdd: (tag: TagItem) => void | Promise<void>;\n /** Persist a tag removal on the entity. */\n onRemove: (tagId: string) => void | Promise<void>;\n /** Allow creating new tags on the fly. Default: true. */\n allowCreate?: boolean;\n /** Cap on assigned tags. Default 20. */\n maxTags?: number;\n className?: string;\n}\n\n// --- Component --------------------------------------------------------------\n\nexport function ProjectTagsWidget({\n entityType: _entityType,\n entityId,\n workspaceId,\n tags,\n onAdd,\n onRemove,\n allowCreate = true,\n maxTags = 20,\n className,\n}: ProjectTagsWidgetProps) {\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [createError, setCreateError] = useState<string | null>(null);\n const [adding, setAdding] = useState(false);\n const [resolvedVocabId, setResolvedVocabId] = useState<string | undefined>();\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Bun may install Apollo twice when fe-libs is consumed as a package; the runtime client is compatible.\n const wsClient = useGatewayClient('workspace') as unknown as ApolloClient;\n\n const skip = !workspaceId || !entityId;\n\n const assignedTagIds = new Set(tags.map((t) => t.id));\n const canAddMore = tags.length < maxTags;\n\n const [runSearch, { data: searchData, loading: searching }] = useLazyQuery<{\n searchTags: TagItem[];\n }>(SEARCH_TAGS, { client: wsClient });\n\n const searchResults = (searchData?.searchTags ?? []).filter(\n (tag): tag is TagItem => isTagItem(tag) && !assignedTagIds.has(tag.id)\n );\n\n const [createTag, { loading: creating }] = useMutation<{\n createTag: TagItem;\n }>(CREATE_TAG, { client: wsClient });\n\n const [fetchVocabByKey] = useLazyQuery<{\n getVocabularyByKey: VocabResult | null;\n }>(GET_VOCABULARY_BY_KEY, { client: wsClient });\n\n const [createVocabulary] = useMutation<{\n createVocabulary: VocabResult;\n }>(CREATE_VOCABULARY, { client: wsClient });\n\n const resolveVocabId = useCallback(async (): Promise<string> => {\n if (resolvedVocabId) return resolvedVocabId;\n\n const res = await fetchVocabByKey({ variables: { key: DEFAULT_VOCAB_KEY } });\n const existing = res.data?.getVocabularyByKey;\n if (existing) {\n setResolvedVocabId(existing.id);\n return existing.id;\n }\n\n const created = await createVocabulary({\n variables: {\n input: {\n key: DEFAULT_VOCAB_KEY,\n name: 'General',\n description: 'Default vocabulary for general-purpose tags',\n scope: 'WORKSPACE',\n allowCustomTags: true,\n },\n },\n });\n const newVocab = created.data?.createVocabulary;\n if (!newVocab)\n throw new Error(\n tr('projectTagsWidget.createVocabularyError', 'Failed to create default vocabulary')\n );\n setResolvedVocabId(newVocab.id);\n return newVocab.id;\n }, [resolvedVocabId, fetchVocabByKey, createVocabulary, tr]);\n\n const handleSearchChange = useCallback(\n (value: string) => {\n setSearch(value);\n if (createError) setCreateError(null);\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (!value.trim()) return;\n debounceRef.current = setTimeout(() => {\n void runSearch({\n variables: { query: value, vocabularyId: resolvedVocabId, limit: 10 },\n });\n }, 250);\n },\n [runSearch, resolvedVocabId, createError]\n );\n\n useEffect(() => {\n return () => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n };\n }, []);\n\n const handleAssign = useCallback(\n async (tag: TagItem) => {\n if (!canAddMore || adding) return;\n setAdding(true);\n try {\n const normalizedTag: TagItem = {\n ...tag,\n vocabularyKey: tag.vocabularyKey ?? tag.vocabulary?.key ?? null,\n };\n await onAdd(normalizedTag);\n setSearch('');\n setOpen(false);\n } finally {\n setAdding(false);\n }\n },\n [onAdd, canAddMore, adding]\n );\n\n const handleCreate = useCallback(async () => {\n if (!allowCreate || !search.trim() || creating) return;\n setCreateError(null);\n try {\n const label = search.trim();\n const key = label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n if (!key) {\n setCreateError(\n tr(\n 'projectTagsWidget.createValidationError',\n 'Must contain at least one letter or number'\n )\n );\n return;\n }\n const vocabId = await resolveVocabId();\n const res = await createTag({\n variables: { input: { key, label, vocabularyId: vocabId } },\n });\n const created = res.data?.createTag;\n if (!isTagItem(created)) throw new Error('No data returned');\n await handleAssign(created);\n } catch (err) {\n setCreateError(\n err instanceof Error\n ? err.message\n : tr('projectTagsWidget.createGenericError', 'Failed to create tag')\n );\n }\n }, [allowCreate, search, creating, resolveVocabId, createTag, handleAssign, tr]);\n\n const handleRemove = useCallback(\n async (tagId: string) => {\n await onRemove(tagId);\n },\n [onRemove]\n );\n\n if (skip) return null;\n\n const showCreateOption = Boolean(\n allowCreate && search.trim() && !searching && searchResults.length === 0\n );\n\n return (\n <div className={cn('flex flex-wrap items-center gap-1.5', className)}>\n {tags.map((tag) => (\n <TagBadge\n key={tag.id}\n id={tag.id}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n removable\n onRemove={() => void handleRemove(tag.id)}\n size=\"sm\"\n />\n ))}\n\n {canAddMore && (\n <Popover open={open} onOpenChange={setOpen}>\n {/*\n Intentionally do NOT use `asChild` here — the fe-libs\n `AddTagTrigger` swallows the props Radix forwards, leaving the\n trigger non-clickable. Letting Radix render its own button\n restores the click handler.\n */}\n <PopoverTrigger\n type=\"button\"\n aria-label={tr('projectTagsWidget.addTagAriaLabel', 'Add tag')}\n className={cn(\n 'inline-flex items-center gap-1 px-2 py-0.5 rounded-full border border-dashed',\n 'border-border-subtle text-text-secondary text-xs',\n 'hover:border-foreground hover:text-text-primary transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <TagIcon className=\"size-3\" />\n <span>\n {tags.length > 0\n ? tr('projectTagsWidget.add', 'Add')\n : tr('projectTagsWidget.addTags', 'Add tags')}\n </span>\n </PopoverTrigger>\n\n <PopoverContent\n className=\"z-50 w-72 max-w-[calc(100vw-2rem)] border-border-subtle/80 bg-popover p-0 shadow-xl\"\n align=\"start\"\n side=\"bottom\"\n sideOffset={8}\n >\n <Command shouldFilter={false}>\n <div className=\"relative\">\n <CommandInput\n placeholder={tr(\n 'projectTagsWidget.searchPlaceholder',\n 'Search or create a tag...'\n )}\n value={search}\n onValueChange={handleSearchChange}\n className=\"h-10 border-0 bg-transparent pr-9 text-sm outline-none placeholder:text-text-secondary\"\n />\n {searching && (\n <Loader2 className=\"absolute right-3 top-1/2 size-3.5 -translate-y-1/2 animate-spin text-text-secondary\" />\n )}\n </div>\n\n <CommandList className=\"max-h-48 overflow-y-auto\">\n {search.trim() === '' && (\n <CommandEmpty className=\"py-4 text-center text-xs text-text-secondary\">\n {tr('projectTagsWidget.typeToSearch', 'Type to search or create a tag')}\n </CommandEmpty>\n )}\n\n {searchResults.length > 0 && (\n <CommandGroup>\n {searchResults.map((tag) => (\n <CommandItem\n key={tag.id}\n value={tag.id}\n onSelect={() => void handleAssign(tag)}\n className=\"flex items-center gap-2 px-3 py-2 cursor-pointer\"\n disabled={adding}\n >\n {tag.icon && <span className=\"text-sm\">{tag.icon}</span>}\n <span\n className=\"truncate text-sm\"\n style={tag.color ? { color: tag.color } : undefined}\n >\n {tag.label}\n </span>\n {adding && <Loader2 className=\"ml-auto size-3 animate-spin\" />}\n </CommandItem>\n ))}\n </CommandGroup>\n )}\n\n {showCreateOption && (\n <CommandGroup>\n <CommandItem\n value={`__create__${search}`}\n onSelect={() => void handleCreate()}\n className=\"flex items-center gap-2 px-3 py-2 cursor-pointer text-primary\"\n disabled={creating}\n >\n {creating ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Plus className=\"size-3.5\" />\n )}\n <span className=\"text-sm truncate\">\n {creating\n ? tr('projectTagsWidget.creating', 'Creating...')\n : tr('projectTagsWidget.create', 'Create \"{{label}}\"', {\n label: search,\n })}\n </span>\n </CommandItem>\n </CommandGroup>\n )}\n\n {search.trim() !== '' &&\n !searching &&\n searchResults.length === 0 &&\n !showCreateOption && (\n <CommandEmpty className=\"py-4 text-center text-xs text-text-secondary\">\n {tr('projectTagsWidget.noTagsFound', 'No tags found')}\n </CommandEmpty>\n )}\n\n {createError && (\n <div className=\"px-3 py-2 text-xs text-status-error-text border-t border-border-subtle\">\n {createError}\n </div>\n )}\n </CommandList>\n\n {!canAddMore && (\n <div className=\"border-t border-border-subtle px-3 py-2 text-xs text-text-secondary flex items-center gap-1\">\n <X className=\"size-3\" />\n {tr('projectTagsWidget.maxTagsReached', 'Maximum {{maxTags}} tags reached', {\n maxTags,\n })}\n </div>\n )}\n </Command>\n </PopoverContent>\n </Popover>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import { useCallback as e } from "react";
2
+ import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
3
+ //#region src/hooks/useTr.ts
4
+ function n() {
5
+ let { t: n } = t();
6
+ return e((e, t, r) => {
7
+ let i = n(e, void 0, r), a = i === e ? t : i;
8
+ return r ? Object.entries(r).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), a) : a;
9
+ }, [n]);
10
+ }
11
+ //#endregion
12
+ export { n as useTr };
13
+
14
+ //# sourceMappingURL=useTr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.js","names":[],"sources":["../../src/hooks/useTr.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport type TFunction = (\n key: string,\n fallbackOrParams?: string | Record<string, string | number>,\n params?: Record<string, string | number>\n) => string;\nexport type TrFunction = (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string;\n\n/**\n * Builds a translator function with fallback semantics for the given `t`.\n *\n * When `t(key)` returns the key itself (no translation found), the caller's\n * English `fallback` is used instead. Optional interpolation params use\n * `{{key}}` placeholders in both the translated and fallback strings.\n */\nexport function createTr(t: TFunction): TrFunction {\n return (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, undefined, params);\n const raw = translated === key ? fallback : translated;\n if (!params) return raw;\n return Object.entries(params).reduce(\n (str, [k, v]) => str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v)),\n raw\n );\n };\n}\n\n/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('workspaces.dashboard.title', 'Dashboard')}</h1>;\n * return <p>{tr('workspaces.delete.confirm', 'Delete {{count}} items?', { count: 5 })}</p>;\n */\nexport function useTr(): TrFunction {\n const { t } = useI18n();\n return useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, undefined, params);\n const raw = translated === key ? fallback : translated;\n if (!params) return raw;\n return Object.entries(params).reduce(\n (str, [k, v]) => str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v)),\n raw\n );\n },\n [t]\n );\n}\n"],"mappings":";;;AAgDA,SAAgB,IAAoB;CAClC,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,GACJ,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAa,EAAE,GAAK,KAAA,GAAW,EAAO,EACtC,IAAM,MAAe,IAAM,IAAW;AAE5C,SADK,IACE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAG,OAAO,EAAI,QAAY,OAAO,SAAS,EAAE,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC,EAC5E,EACD,GAJmB;IAMtB,CAAC,EAAE,CACJ"}
package/dist/index.js CHANGED
@@ -24,4 +24,5 @@ import { CreateProjectDialog as he } from "./components/CreateProjectDialog.js";
24
24
  import { MemberListItem as ge } from "./components/MemberListItem.js";
25
25
  import { InviteMemberDialog as _e } from "./components/InviteMemberDialog.js";
26
26
  import "./components/index.js";
27
- export { he as CreateProjectDialog, ie as CreateWorkspaceDialog, y as DashboardPage, L as InviteAcceptancePage, _e as InviteMemberDialog, ge as MemberListItem, R as PendingInvitesPage, me as ProjectCard, _ as WorkspaceCard, U as WorkspaceInvitePage, n as WorkspacesErrorBoundary, e as WorkspacesProvider, v as WorkspacesRoot, ae as formatDate, oe as formatDateTime, se as formatFileSize, ce as formatProjectStatus, le as formatRelativeTime, ue as formatWorkspaceStatus, de as getInitials, c as getMemberRoleClasses, l as getMemberRoleLabel, u as getProjectStatusClasses, d as getProjectStatusIconClass, f as getProjectStatusLabel, p as getWorkspaceStatusBgClass, m as getWorkspaceStatusClasses, h as getWorkspaceStatusIconClass, g as getWorkspaceStatusLabel, fe as toTitleCase, pe as truncate, E as useAcceptInvitation, D as useAddWorkspaceMember, Y as useArchiveProject, W as useArchiveWorkspace, O as useBulkCreateInvitations, k as useCancelInvitation, A as useCreateInvitation, X as useCreateProject, G as useCreateWorkspace, j as useDeleteInvitation, Z as useDeleteProject, K as useDeleteWorkspace, b as useInfiniteWorkspaceMembers, x as useInvitation, ne as useMyOrganizations, s as useMyPendingInvitations, z as useMyProjects, re as useMyTenants, i as useMyWorkspaces, B as useProject, V as useProjectMembersCount, H as useProjects, q as useReactivateWorkspace, M as useRejectInvitation, N as useRemoveUserFromWorkspace, P as useRemoveWorkspaceMember, F as useResendInvitation, Q as useTagProject, $ as useUnarchiveProject, ee as useUntagProject, I as useUpdateInvitation, te as useUpdateProject, J as useUpdateWorkspace, S as useUserWorkspaces, a as useWorkspace, C as useWorkspaceInvitations, w as useWorkspaceMember, T as useWorkspaceMembers, o as useWorkspaces, t as useWorkspacesContext, r as useWorkspacesStore };
27
+ import { enTranslations as ve } from "./translations/en.js";
28
+ export { he as CreateProjectDialog, ie as CreateWorkspaceDialog, y as DashboardPage, L as InviteAcceptancePage, _e as InviteMemberDialog, ge as MemberListItem, R as PendingInvitesPage, me as ProjectCard, _ as WorkspaceCard, U as WorkspaceInvitePage, n as WorkspacesErrorBoundary, e as WorkspacesProvider, v as WorkspacesRoot, ve as enTranslations, ae as formatDate, oe as formatDateTime, se as formatFileSize, ce as formatProjectStatus, le as formatRelativeTime, ue as formatWorkspaceStatus, de as getInitials, c as getMemberRoleClasses, l as getMemberRoleLabel, u as getProjectStatusClasses, d as getProjectStatusIconClass, f as getProjectStatusLabel, p as getWorkspaceStatusBgClass, m as getWorkspaceStatusClasses, h as getWorkspaceStatusIconClass, g as getWorkspaceStatusLabel, fe as toTitleCase, pe as truncate, E as useAcceptInvitation, D as useAddWorkspaceMember, Y as useArchiveProject, W as useArchiveWorkspace, O as useBulkCreateInvitations, k as useCancelInvitation, A as useCreateInvitation, X as useCreateProject, G as useCreateWorkspace, j as useDeleteInvitation, Z as useDeleteProject, K as useDeleteWorkspace, b as useInfiniteWorkspaceMembers, x as useInvitation, ne as useMyOrganizations, s as useMyPendingInvitations, z as useMyProjects, re as useMyTenants, i as useMyWorkspaces, B as useProject, V as useProjectMembersCount, H as useProjects, q as useReactivateWorkspace, M as useRejectInvitation, N as useRemoveUserFromWorkspace, P as useRemoveWorkspaceMember, F as useResendInvitation, Q as useTagProject, $ as useUnarchiveProject, ee as useUntagProject, I as useUpdateInvitation, te as useUpdateProject, J as useUpdateWorkspace, S as useUserWorkspaces, a as useWorkspace, C as useWorkspaceInvitations, w as useWorkspaceMember, T as useWorkspaceMembers, o as useWorkspaces, t as useWorkspacesContext, r as useWorkspacesStore };
@@ -5,11 +5,11 @@ import { formatDate as r } from "../utils/formatters.js";
5
5
  import { useWorkspaceActivity as i } from "../hooks/useWorkspaceActivity.js";
6
6
  import { jsx as a, jsxs as o } from "react/jsx-runtime";
7
7
  import { CardContent as s, CardHeader as c, CardTitle as l, GlassCard as u, IllustratedEmptyState as d, PagePurpose as f, Skeleton as p } from "@burdenoff/fe-libs/ui";
8
- import { useParams as m } from "react-router";
9
- import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
8
+ import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
+ import { useParams as h } from "react-router";
10
10
  //#region src/pages/ActivityListPage.tsx
11
11
  function g() {
12
- let { t: g } = h(), { workspaceId: _ } = m();
12
+ let { t: g } = m(), { workspaceId: _ } = h();
13
13
  t("workspaces.activity", {
14
14
  workspaceId: _,
15
15
  requireParams: !0
@@ -9,20 +9,20 @@ import { useEffect as o, useMemo as s } from "react";
9
9
  import { AlertCircle as c, ArrowLeft as l } from "lucide-react";
10
10
  import { jsx as u, jsxs as d } from "react/jsx-runtime";
11
11
  import { Button as f, CardContent as p, CardDescription as m, CardHeader as h, CardTitle as ne, GlassCard as g, PagePurpose as _, Skeleton as v } from "@burdenoff/fe-libs/ui";
12
- import { Link as y, useNavigate as b } from "react-router";
13
- import { useUpgradePrompt as x } from "@burdenoff/fe-libs/shared/hooks";
14
- import { AccessDenied as S, UpgradeInlineBanner as C } from "@burdenoff/fe-libs/shared/components";
15
- import { useI18n as w } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
12
+ import { useI18n as y } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
13
+ import { Link as b, useNavigate as x } from "react-router";
14
+ import { useUpgradePrompt as S } from "@burdenoff/fe-libs/shared/hooks";
15
+ import { AccessDenied as C, UpgradeInlineBanner as w } from "@burdenoff/fe-libs/shared/components";
16
16
  import { useForm as T, z as E } from "@burdenoff/fe-libs/form";
17
17
  //#region src/pages/CreateWorkspacePage.tsx
18
18
  function D() {
19
19
  n("workspaces.create");
20
- let { t: D } = w(), { isFree: O, approachingLimits: re, plan: ie } = x({ resource: "workspaces" }), k = re[0], A = !!k && typeof k.limit == "number" && k.current >= k.limit, j = O && !!k, M = s(() => E.object({
20
+ let { t: D } = y(), { isFree: O, approachingLimits: re, plan: ie } = S({ resource: "workspaces" }), k = re[0], A = !!k && typeof k.limit == "number" && k.current >= k.limit, j = O && !!k, M = s(() => E.object({
21
21
  name: E.string().min(2, t(D, "createWorkspace.validation.nameMin", "Name must be at least 2 characters")).max(100),
22
22
  slug: E.string().min(2, t(D, "createWorkspace.validation.slugMin", "Slug must be at least 2 characters")).max(50).regex(/^[a-z0-9-]+$/, t(D, "createWorkspace.validation.slugFormat", "Slug can only contain lowercase letters, numbers, and hyphens")),
23
23
  tenantId: E.string().min(1, t(D, "createWorkspace.validation.tenantRequired", "Please select a tenant")),
24
24
  organizationId: E.string().min(1, t(D, "createWorkspace.validation.organizationRequired", "Please select an organization"))
25
- }), [D]), N = b(), { basePath: P = "/" } = e(), { mutate: F, isPending: I } = ee(), { data: L, isLoading: R, error: z } = i(), { data: B, isLoading: V, error: H } = r(), U = s(() => L?.items ?? [], [L]), W = s(() => B?.items ?? [], [B]), G = s(() => U.find((e) => e.type === "SHARED" || e.slug === "burdenoff-shared")?.id ?? U[0]?.id ?? "", [U]), K = T({
25
+ }), [D]), N = x(), { basePath: P = "/" } = e(), { mutate: F, isPending: I } = ee(), { data: L, isLoading: R, error: z } = i(), { data: B, isLoading: V, error: H } = r(), U = s(() => L?.items ?? [], [L]), W = s(() => B?.items ?? [], [B]), G = s(() => U.find((e) => e.type === "SHARED" || e.slug === "burdenoff-shared")?.id ?? U[0]?.id ?? "", [U]), K = T({
26
26
  schema: M,
27
27
  defaultValues: {
28
28
  name: "",
@@ -63,11 +63,11 @@ function D() {
63
63
  }, oe = Y.size > 0, se = R || V || X, ce = !z && !H && W.length > 0;
64
64
  return !se && !Z && ce && !oe ? /* @__PURE__ */ d("div", {
65
65
  className: "p-6",
66
- children: [/* @__PURE__ */ d(y, {
66
+ children: [/* @__PURE__ */ d(b, {
67
67
  to: `${P}/list`,
68
68
  className: "inline-flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors mb-6",
69
69
  children: [/* @__PURE__ */ u(l, { size: 20 }), t(D, "createWorkspace.backToWorkspaces", "Back to Workspaces")]
70
- }), /* @__PURE__ */ u(S, {
70
+ }), /* @__PURE__ */ u(C, {
71
71
  variant: "card",
72
72
  title: t(D, "createWorkspace.permissionRequiredTitle", "Permission Required"),
73
73
  description: t(D, "createWorkspace.permissionRequiredDescription", "You don't have permission to create workspaces in any of your organizations."),
@@ -80,7 +80,7 @@ function D() {
80
80
  }) : /* @__PURE__ */ d("div", {
81
81
  className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
82
82
  children: [
83
- /* @__PURE__ */ d(y, {
83
+ /* @__PURE__ */ d(b, {
84
84
  to: `${P}/list`,
85
85
  className: "inline-flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors",
86
86
  children: [/* @__PURE__ */ u(l, { size: 20 }), t(D, "createWorkspace.backToWorkspaces", "Back to Workspaces")]
@@ -91,7 +91,7 @@ function D() {
91
91
  }),
92
92
  j && /* @__PURE__ */ u("div", {
93
93
  className: "max-w-2xl mx-auto w-full",
94
- children: /* @__PURE__ */ u(C, {
94
+ children: /* @__PURE__ */ u(w, {
95
95
  resource: "workspaces",
96
96
  current: k.current,
97
97
  limit: k.limit ?? void 0,