@agent-native/scheduling 0.1.11 → 0.1.13

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 (41) hide show
  1. package/dist/react/components/SlotPicker.d.ts.map +1 -1
  2. package/dist/react/components/SlotPicker.js +3 -1
  3. package/dist/react/components/SlotPicker.js.map +1 -1
  4. package/dist/react/components/booking-links/AvailabilityEditor.d.ts +2 -1
  5. package/dist/react/components/booking-links/AvailabilityEditor.d.ts.map +1 -1
  6. package/dist/react/components/booking-links/AvailabilityEditor.js +28 -22
  7. package/dist/react/components/booking-links/AvailabilityEditor.js.map +1 -1
  8. package/dist/react/components/booking-links/BookingLinkCreateDialog.d.ts.map +1 -1
  9. package/dist/react/components/booking-links/BookingLinkCreateDialog.js +6 -4
  10. package/dist/react/components/booking-links/BookingLinkCreateDialog.js.map +1 -1
  11. package/dist/react/components/booking-links/ConferencingSelector.d.ts.map +1 -1
  12. package/dist/react/components/booking-links/ConferencingSelector.js +25 -19
  13. package/dist/react/components/booking-links/ConferencingSelector.js.map +1 -1
  14. package/dist/react/components/booking-links/CustomFieldsEditor.d.ts.map +1 -1
  15. package/dist/react/components/booking-links/CustomFieldsEditor.js +39 -25
  16. package/dist/react/components/booking-links/CustomFieldsEditor.js.map +1 -1
  17. package/dist/react/components/booking-links/DurationPicker.d.ts.map +1 -1
  18. package/dist/react/components/booking-links/DurationPicker.js +6 -2
  19. package/dist/react/components/booking-links/DurationPicker.js.map +1 -1
  20. package/dist/react/components/booking-links/SlugEditor.d.ts.map +1 -1
  21. package/dist/react/components/booking-links/SlugEditor.js +5 -3
  22. package/dist/react/components/booking-links/SlugEditor.js.map +1 -1
  23. package/dist/react/i18n.d.ts +109 -0
  24. package/dist/react/i18n.d.ts.map +1 -0
  25. package/dist/react/i18n.js +976 -0
  26. package/dist/react/i18n.js.map +1 -0
  27. package/dist/schema/bookings.d.ts +12 -12
  28. package/dist/schema/event-types.d.ts +12 -12
  29. package/dist/schema/routing-forms.d.ts +12 -12
  30. package/dist/schema/schedules.d.ts +12 -12
  31. package/dist/schema/teams.d.ts +12 -12
  32. package/dist/schema/workflows.d.ts +15 -15
  33. package/package.json +10 -11
  34. package/src/react/components/SlotPicker.tsx +6 -1
  35. package/src/react/components/booking-links/AvailabilityEditor.tsx +48 -27
  36. package/src/react/components/booking-links/BookingLinkCreateDialog.tsx +17 -15
  37. package/src/react/components/booking-links/ConferencingSelector.tsx +36 -31
  38. package/src/react/components/booking-links/CustomFieldsEditor.tsx +71 -52
  39. package/src/react/components/booking-links/DurationPicker.tsx +8 -4
  40. package/src/react/components/booking-links/SlugEditor.tsx +7 -5
  41. package/src/react/i18n.ts +1050 -0
@@ -19,7 +19,7 @@ export declare const workflows: import("drizzle-orm/sqlite-core").SQLiteTableWit
19
19
  identity: undefined;
20
20
  generated: undefined;
21
21
  }, {}, {
22
- length: number | undefined;
22
+ length: number;
23
23
  }>;
24
24
  orgId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
25
25
  name: "org_id";
@@ -38,7 +38,7 @@ export declare const workflows: import("drizzle-orm/sqlite-core").SQLiteTableWit
38
38
  identity: undefined;
39
39
  generated: undefined;
40
40
  }, {}, {
41
- length: number | undefined;
41
+ length: number;
42
42
  }>;
43
43
  visibility: import("drizzle-orm/sqlite-core").SQLiteColumn<{
44
44
  name: "visibility";
@@ -57,7 +57,7 @@ export declare const workflows: import("drizzle-orm/sqlite-core").SQLiteTableWit
57
57
  identity: undefined;
58
58
  generated: undefined;
59
59
  }, {}, {
60
- length: number | undefined;
60
+ length: number;
61
61
  }>;
62
62
  id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
63
63
  name: "id";
@@ -680,7 +680,7 @@ export declare const webhooks: import("drizzle-orm/sqlite-core").SQLiteTableWith
680
680
  identity: undefined;
681
681
  generated: undefined;
682
682
  }, {}, {
683
- length: number | undefined;
683
+ length: number;
684
684
  }>;
685
685
  orgId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
686
686
  name: "org_id";
@@ -699,7 +699,7 @@ export declare const webhooks: import("drizzle-orm/sqlite-core").SQLiteTableWith
699
699
  identity: undefined;
700
700
  generated: undefined;
701
701
  }, {}, {
702
- length: number | undefined;
702
+ length: number;
703
703
  }>;
704
704
  visibility: import("drizzle-orm/sqlite-core").SQLiteColumn<{
705
705
  name: "visibility";
@@ -718,7 +718,7 @@ export declare const webhooks: import("drizzle-orm/sqlite-core").SQLiteTableWith
718
718
  identity: undefined;
719
719
  generated: undefined;
720
720
  }, {}, {
721
- length: number | undefined;
721
+ length: number;
722
722
  }>;
723
723
  id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
724
724
  name: "id";
@@ -1245,7 +1245,7 @@ export declare const workflowShares: import("drizzle-orm/sqlite-core").SQLiteTab
1245
1245
  identity: undefined;
1246
1246
  generated: undefined;
1247
1247
  }, {}, {
1248
- length: number | undefined;
1248
+ length: number;
1249
1249
  }>;
1250
1250
  resourceId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
1251
1251
  name: "resource_id";
@@ -1264,14 +1264,14 @@ export declare const workflowShares: import("drizzle-orm/sqlite-core").SQLiteTab
1264
1264
  identity: undefined;
1265
1265
  generated: undefined;
1266
1266
  }, {}, {
1267
- length: number | undefined;
1267
+ length: number;
1268
1268
  }>;
1269
1269
  principalType: import("drizzle-orm/sqlite-core").SQLiteColumn<{
1270
1270
  name: "principal_type";
1271
1271
  tableName: string;
1272
1272
  dataType: "string";
1273
1273
  columnType: "SQLiteText";
1274
- data: "user" | "org";
1274
+ data: "org" | "user";
1275
1275
  driverParam: string;
1276
1276
  notNull: true;
1277
1277
  hasDefault: false;
@@ -1283,7 +1283,7 @@ export declare const workflowShares: import("drizzle-orm/sqlite-core").SQLiteTab
1283
1283
  identity: undefined;
1284
1284
  generated: undefined;
1285
1285
  }, {}, {
1286
- length: number | undefined;
1286
+ length: number;
1287
1287
  }>;
1288
1288
  principalId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
1289
1289
  name: "principal_id";
@@ -1302,14 +1302,14 @@ export declare const workflowShares: import("drizzle-orm/sqlite-core").SQLiteTab
1302
1302
  identity: undefined;
1303
1303
  generated: undefined;
1304
1304
  }, {}, {
1305
- length: number | undefined;
1305
+ length: number;
1306
1306
  }>;
1307
1307
  role: import("drizzle-orm/sqlite-core").SQLiteColumn<{
1308
1308
  name: "role";
1309
1309
  tableName: string;
1310
1310
  dataType: "string";
1311
1311
  columnType: "SQLiteText";
1312
- data: "editor" | "admin" | "viewer";
1312
+ data: "viewer" | "editor" | "admin";
1313
1313
  driverParam: string;
1314
1314
  notNull: true;
1315
1315
  hasDefault: true;
@@ -1321,7 +1321,7 @@ export declare const workflowShares: import("drizzle-orm/sqlite-core").SQLiteTab
1321
1321
  identity: undefined;
1322
1322
  generated: undefined;
1323
1323
  }, {}, {
1324
- length: number | undefined;
1324
+ length: number;
1325
1325
  }>;
1326
1326
  createdBy: import("drizzle-orm/sqlite-core").SQLiteColumn<{
1327
1327
  name: "created_by";
@@ -1340,7 +1340,7 @@ export declare const workflowShares: import("drizzle-orm/sqlite-core").SQLiteTab
1340
1340
  identity: undefined;
1341
1341
  generated: undefined;
1342
1342
  }, {}, {
1343
- length: number | undefined;
1343
+ length: number;
1344
1344
  }>;
1345
1345
  createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
1346
1346
  name: "created_at";
@@ -1359,7 +1359,7 @@ export declare const workflowShares: import("drizzle-orm/sqlite-core").SQLiteTab
1359
1359
  identity: undefined;
1360
1360
  generated: undefined;
1361
1361
  }, {}, {
1362
- length: number | undefined;
1362
+ length: number;
1363
1363
  }>;
1364
1364
  };
1365
1365
  dialect: "sqlite";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/scheduling",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "description": "Scheduling primitives for agent-native apps — event types, availability, bookings, team scheduling, workflows, and routing forms.",
6
6
  "license": "MIT",
@@ -46,13 +46,6 @@
46
46
  "docs",
47
47
  "src"
48
48
  ],
49
- "scripts": {
50
- "build": "tsc && node scripts/build-llms-full.mjs",
51
- "dev": "tsc --watch",
52
- "typecheck": "tsc --noEmit",
53
- "test": "vitest --run",
54
- "prepublishOnly": "npm run build"
55
- },
56
49
  "dependencies": {
57
50
  "@date-fns/tz": "^1.2.0",
58
51
  "date-fns": "^4.1.0",
@@ -79,7 +72,6 @@
79
72
  }
80
73
  },
81
74
  "devDependencies": {
82
- "@agent-native/core": "workspace:*",
83
75
  "@libsql/client": "^0.15.8",
84
76
  "@tabler/icons-react": "^3.0.0",
85
77
  "@types/node": "^24.2.1",
@@ -87,6 +79,13 @@
87
79
  "drizzle-orm": "^0.45.2",
88
80
  "react": "^19.2.5",
89
81
  "typescript": "^6.0.3",
90
- "vitest": "^4.1.5"
82
+ "vitest": "^4.1.5",
83
+ "@agent-native/core": "0.76.8"
84
+ },
85
+ "scripts": {
86
+ "build": "tsc && node scripts/build-llms-full.mjs",
87
+ "dev": "tsc --watch",
88
+ "typecheck": "tsc --noEmit",
89
+ "test": "vitest --run"
91
90
  }
92
- }
91
+ }
@@ -9,6 +9,7 @@
9
9
  import type { Slot } from "../../shared/index.js";
10
10
  import { TZDate } from "@date-fns/tz";
11
11
  import { format } from "date-fns";
12
+ import { useSchedulingT } from "../i18n.js";
12
13
 
13
14
  export interface SlotPickerProps {
14
15
  slots: Slot[];
@@ -22,6 +23,7 @@ export interface SlotPickerProps {
22
23
  }
23
24
 
24
25
  export function SlotPicker(props: SlotPickerProps) {
26
+ const t = useSchedulingT();
25
27
  const grouped = new Map<string, Slot[]>();
26
28
  for (const s of props.slots) {
27
29
  const localDay = format(
@@ -58,7 +60,10 @@ export function SlotPicker(props: SlotPickerProps) {
58
60
  fmt,
59
61
  )}
60
62
  {s.seatsRemaining != null && s.seatsRemaining > 0 && (
61
- <span> · {s.seatsRemaining} left</span>
63
+ <span>
64
+ {" "}
65
+ · {s.seatsRemaining} {t("availableLeft")}
66
+ </span>
62
67
  )}
63
68
  </button>
64
69
  </li>
@@ -11,6 +11,8 @@
11
11
  */
12
12
  import { Input } from "@/components/ui/input";
13
13
  import { Switch } from "@/components/ui/switch";
14
+ import { schedulingMessage, useSchedulingT } from "../../i18n.js";
15
+ import type { LocaleCode } from "@agent-native/core/client";
14
16
 
15
17
  export type DayKey =
16
18
  | "monday"
@@ -33,14 +35,18 @@ export interface DaySchedule {
33
35
 
34
36
  export type WeeklySchedule = Record<DayKey, DaySchedule>;
35
37
 
36
- const DAYS: { key: DayKey; label: string; short: string }[] = [
37
- { key: "monday", label: "Monday", short: "Mon" },
38
- { key: "tuesday", label: "Tuesday", short: "Tue" },
39
- { key: "wednesday", label: "Wednesday", short: "Wed" },
40
- { key: "thursday", label: "Thursday", short: "Thu" },
41
- { key: "friday", label: "Friday", short: "Fri" },
42
- { key: "saturday", label: "Saturday", short: "Sat" },
43
- { key: "sunday", label: "Sunday", short: "Sun" },
38
+ const DAYS: {
39
+ key: DayKey;
40
+ labelKey: Parameters<ReturnType<typeof useSchedulingT>>[0];
41
+ shortKey: Parameters<ReturnType<typeof useSchedulingT>>[0];
42
+ }[] = [
43
+ { key: "monday", labelKey: "monday", shortKey: "mon" },
44
+ { key: "tuesday", labelKey: "tuesday", shortKey: "tue" },
45
+ { key: "wednesday", labelKey: "wednesday", shortKey: "wed" },
46
+ { key: "thursday", labelKey: "thursday", shortKey: "thu" },
47
+ { key: "friday", labelKey: "friday", shortKey: "fri" },
48
+ { key: "saturday", labelKey: "saturday", shortKey: "sat" },
49
+ { key: "sunday", labelKey: "sunday", shortKey: "sun" },
44
50
  ];
45
51
 
46
52
  export interface AvailabilityEditorProps {
@@ -52,6 +58,7 @@ export function AvailabilityEditor({
52
58
  value,
53
59
  onChange,
54
60
  }: AvailabilityEditorProps) {
61
+ const t = useSchedulingT();
55
62
  const setDay = (day: DayKey, patch: Partial<DaySchedule>) => {
56
63
  onChange({
57
64
  ...value,
@@ -74,7 +81,9 @@ export function AvailabilityEditor({
74
81
 
75
82
  return (
76
83
  <div className="space-y-2.5">
77
- {DAYS.map(({ key, label, short }) => {
84
+ {DAYS.map(({ key, labelKey, shortKey }) => {
85
+ const label = t(labelKey);
86
+ const short = t(shortKey);
78
87
  const day = value[key] ?? { enabled: false, slots: [] };
79
88
  const slot = day.slots[0] ?? { start: "09:00", end: "17:00" };
80
89
  return (
@@ -86,7 +95,7 @@ export function AvailabilityEditor({
86
95
  <Switch
87
96
  checked={day.enabled}
88
97
  onCheckedChange={(checked) => setDay(key, { enabled: checked })}
89
- aria-label={`Toggle ${label}`}
98
+ aria-label={t("toggleDay", { day: label })}
90
99
  />
91
100
  <span className="text-sm font-medium">
92
101
  <span className="hidden sm:inline">{label}</span>
@@ -102,7 +111,7 @@ export function AvailabilityEditor({
102
111
  onChange={(e) => setSlot(key, "start", e.target.value)}
103
112
  className="w-28 sm:w-32"
104
113
  />
105
- <span className="text-muted-foreground">to</span>
114
+ <span className="text-muted-foreground">{t("to")}</span>
106
115
  <Input
107
116
  type="time"
108
117
  value={slot.end}
@@ -111,7 +120,9 @@ export function AvailabilityEditor({
111
120
  />
112
121
  </div>
113
122
  ) : (
114
- <span className="text-sm text-muted-foreground">Unavailable</span>
123
+ <span className="text-sm text-muted-foreground">
124
+ {t("unavailable")}
125
+ </span>
115
126
  )}
116
127
  </div>
117
128
  );
@@ -124,7 +135,10 @@ export function AvailabilityEditor({
124
135
  * Summarize a `WeeklySchedule` in a short phrase, e.g. "Weekdays, 9 am - 5 pm".
125
136
  * Useful for list-row subtitles.
126
137
  */
127
- export function summarizeAvailability(ws: WeeklySchedule): string {
138
+ export function summarizeAvailability(
139
+ ws: WeeklySchedule,
140
+ locale: LocaleCode = "en-US",
141
+ ): string {
128
142
  const weekdayKeys: DayKey[] = [
129
143
  "monday",
130
144
  "tuesday",
@@ -136,7 +150,8 @@ export function summarizeAvailability(ws: WeeklySchedule): string {
136
150
  const allDays: DayKey[] = [...weekdayKeys, ...weekendKeys];
137
151
 
138
152
  const enabled = allDays.filter((d) => ws[d]?.enabled);
139
- if (enabled.length === 0) return "No availability set";
153
+ if (enabled.length === 0)
154
+ return schedulingMessage(locale, "noAvailabilitySet");
140
155
 
141
156
  const weekdaysOn = weekdayKeys.every((d) => ws[d]?.enabled);
142
157
  const weekendsOn = weekendKeys.every((d) => ws[d]?.enabled);
@@ -144,20 +159,26 @@ export function summarizeAvailability(ws: WeeklySchedule): string {
144
159
  const weekendsOff = weekendKeys.every((d) => !ws[d]?.enabled);
145
160
 
146
161
  let dayLabel: string;
147
- if (weekdaysOn && weekendsOn) dayLabel = "Every day";
148
- else if (weekdaysOn && weekendsOff) dayLabel = "Weekdays";
149
- else if (weekdaysOff && weekendsOn) dayLabel = "Weekends";
162
+ if (weekdaysOn && weekendsOn)
163
+ dayLabel = schedulingMessage(locale, "everyDay");
164
+ else if (weekdaysOn && weekendsOff)
165
+ dayLabel = schedulingMessage(locale, "weekdays");
166
+ else if (weekdaysOff && weekendsOn)
167
+ dayLabel = schedulingMessage(locale, "weekends");
150
168
  else {
151
- const shortNames: Record<DayKey, string> = {
152
- monday: "Mon",
153
- tuesday: "Tue",
154
- wednesday: "Wed",
155
- thursday: "Thu",
156
- friday: "Fri",
157
- saturday: "Sat",
158
- sunday: "Sun",
159
- };
160
- dayLabel = enabled.map((d) => shortNames[d]).join(", ");
169
+ const shortNames: Record<DayKey, Parameters<typeof schedulingMessage>[1]> =
170
+ {
171
+ monday: "mon",
172
+ tuesday: "tue",
173
+ wednesday: "wed",
174
+ thursday: "thu",
175
+ friday: "fri",
176
+ saturday: "sat",
177
+ sunday: "sun",
178
+ };
179
+ dayLabel = enabled
180
+ .map((d) => schedulingMessage(locale, shortNames[d]))
181
+ .join(", ");
161
182
  }
162
183
 
163
184
  const slot = ws[enabled[0]].slots[0];
@@ -22,6 +22,7 @@ import { Button } from "@/components/ui/button";
22
22
  import { Input } from "@/components/ui/input";
23
23
  import { Label } from "@/components/ui/label";
24
24
  import { Textarea } from "@/components/ui/textarea";
25
+ import { useSchedulingT } from "../../i18n.js";
25
26
 
26
27
  export interface BookingLinkCreateDraft {
27
28
  title: string;
@@ -54,13 +55,14 @@ function slugify(value: string) {
54
55
  }
55
56
 
56
57
  export function BookingLinkCreateDialog(props: BookingLinkCreateDialogProps) {
58
+ const t = useSchedulingT();
57
59
  const {
58
60
  open,
59
61
  onOpenChange,
60
62
  slugPrefix,
61
63
  defaultLength = 30,
62
64
  onSubmit,
63
- submitLabel = "Continue",
65
+ submitLabel = t("continue"),
64
66
  } = props;
65
67
 
66
68
  const [form, setForm] = useState<BookingLinkCreateDraft>({
@@ -100,17 +102,15 @@ export function BookingLinkCreateDialog(props: BookingLinkCreateDialogProps) {
100
102
  <Dialog open={open} onOpenChange={onOpenChange}>
101
103
  <DialogContent>
102
104
  <DialogHeader>
103
- <DialogTitle>Add a new event type</DialogTitle>
104
- <DialogDescription>
105
- Create a new event type for people to book.
106
- </DialogDescription>
105
+ <DialogTitle>{t("addEventTypeTitle")}</DialogTitle>
106
+ <DialogDescription>{t("addEventTypeDescription")}</DialogDescription>
107
107
  </DialogHeader>
108
108
  <div className="space-y-4">
109
109
  <div className="space-y-1.5">
110
- <Label htmlFor="blc-title">Title</Label>
110
+ <Label htmlFor="blc-title">{t("title")}</Label>
111
111
  <Input
112
112
  id="blc-title"
113
- placeholder="Quick chat"
113
+ placeholder={t("quickChatPlaceholder")}
114
114
  value={form.title}
115
115
  onChange={(e) => {
116
116
  const title = e.currentTarget.value;
@@ -124,14 +124,14 @@ export function BookingLinkCreateDialog(props: BookingLinkCreateDialogProps) {
124
124
  />
125
125
  </div>
126
126
  <div className="space-y-1.5">
127
- <Label htmlFor="blc-slug">URL</Label>
127
+ <Label htmlFor="blc-slug">{t("url")}</Label>
128
128
  <div className="flex rounded-md border border-input focus-within:ring-2 focus-within:ring-ring">
129
129
  <span className="flex items-center rounded-l-md bg-muted px-3 text-xs text-muted-foreground">
130
130
  {slugPrefix}
131
131
  </span>
132
132
  <Input
133
133
  id="blc-slug"
134
- placeholder="quick-chat"
134
+ placeholder={t("quickChatSlugPlaceholder")}
135
135
  className="rounded-l-none border-0 focus-visible:ring-0 focus-visible:ring-offset-0"
136
136
  value={form.slug}
137
137
  onChange={(e) => {
@@ -150,10 +150,10 @@ export function BookingLinkCreateDialog(props: BookingLinkCreateDialogProps) {
150
150
  </div>
151
151
  </div>
152
152
  <div className="space-y-1.5">
153
- <Label htmlFor="blc-desc">Description</Label>
153
+ <Label htmlFor="blc-desc">{t("description")}</Label>
154
154
  <Textarea
155
155
  id="blc-desc"
156
- placeholder="A short description people will see."
156
+ placeholder={t("shortDescriptionPlaceholder")}
157
157
  rows={2}
158
158
  value={form.description}
159
159
  onChange={(e) =>
@@ -162,7 +162,7 @@ export function BookingLinkCreateDialog(props: BookingLinkCreateDialogProps) {
162
162
  />
163
163
  </div>
164
164
  <div className="space-y-1.5">
165
- <Label htmlFor="blc-len">Duration</Label>
165
+ <Label htmlFor="blc-len">{t("duration")}</Label>
166
166
  <div className="flex items-center gap-2">
167
167
  <Input
168
168
  id="blc-len"
@@ -173,19 +173,21 @@ export function BookingLinkCreateDialog(props: BookingLinkCreateDialogProps) {
173
173
  setForm({ ...form, length: Number(e.currentTarget.value) })
174
174
  }
175
175
  />
176
- <span className="text-sm text-muted-foreground">minutes</span>
176
+ <span className="text-sm text-muted-foreground">
177
+ {t("minutes")}
178
+ </span>
177
179
  </div>
178
180
  </div>
179
181
  </div>
180
182
  <DialogFooter>
181
183
  <Button variant="outline" onClick={() => onOpenChange(false)}>
182
- Cancel
184
+ {t("cancel")}
183
185
  </Button>
184
186
  <Button
185
187
  onClick={submit}
186
188
  disabled={!form.title || !form.slug || submitting}
187
189
  >
188
- {submitting ? "Saving…" : submitLabel}
190
+ {submitting ? t("saving") : submitLabel}
189
191
  </Button>
190
192
  </DialogFooter>
191
193
  </DialogContent>
@@ -41,6 +41,7 @@ import {
41
41
  SelectItem,
42
42
  SelectTrigger,
43
43
  } from "@/components/ui/select";
44
+ import { useSchedulingT } from "../../i18n.js";
44
45
 
45
46
  export type ConferencingType = "none" | "google_meet" | "zoom" | "custom";
46
47
 
@@ -65,37 +66,38 @@ export interface ConferencingSelectorProps {
65
66
 
66
67
  const OPTIONS: {
67
68
  type: ConferencingType;
68
- label: string;
69
- description: string;
69
+ labelKey: Parameters<ReturnType<typeof useSchedulingT>>[0];
70
+ descriptionKey: Parameters<ReturnType<typeof useSchedulingT>>[0];
70
71
  Icon: ComponentType<{ className?: string }>;
71
72
  }[] = [
72
73
  {
73
74
  type: "none",
74
- label: "No conferencing",
75
- description: "In-person or other",
75
+ labelKey: "noConferencing",
76
+ descriptionKey: "inPersonOrOther",
76
77
  Icon: IconVideoOff,
77
78
  },
78
79
  {
79
80
  type: "google_meet",
80
- label: "Google Meet",
81
- description: "Auto-generate a Meet link",
81
+ labelKey: "googleMeet",
82
+ descriptionKey: "googleMeetDescription",
82
83
  Icon: IconBrandGoogle,
83
84
  },
84
85
  {
85
86
  type: "zoom",
86
- label: "Zoom",
87
- description: "Auto-create a meeting per booking",
87
+ labelKey: "zoom",
88
+ descriptionKey: "zoomDescription",
88
89
  Icon: IconBrandZoom,
89
90
  },
90
91
  {
91
92
  type: "custom",
92
- label: "Custom link",
93
- description: "Paste any meeting URL",
93
+ labelKey: "customLink",
94
+ descriptionKey: "customLinkDescription",
94
95
  Icon: IconLink,
95
96
  },
96
97
  ];
97
98
 
98
99
  export function ConferencingSelector(props: ConferencingSelectorProps) {
100
+ const t = useSchedulingT();
99
101
  const id = useId();
100
102
  const {
101
103
  value,
@@ -122,7 +124,7 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
122
124
  {!hideLabel && (
123
125
  <Label className="flex items-center gap-1.5">
124
126
  <IconVideo className="h-4 w-4" />
125
- Conferencing
127
+ {t("conferencing")}
126
128
  </Label>
127
129
  )}
128
130
 
@@ -141,7 +143,7 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
141
143
  <div className="min-w-0">
142
144
  <div className="flex items-center gap-2">
143
145
  <span className="truncate font-medium">
144
- {selectedOption.label}
146
+ {t(selectedOption.labelKey)}
145
147
  </span>
146
148
  {selectedStatus === "connected" &&
147
149
  selectedOption.type !== "none" &&
@@ -151,14 +153,16 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
151
153
  className="h-5 gap-1 text-[10px] font-normal"
152
154
  >
153
155
  <IconCheck className="h-3 w-3" />
154
- Connected
156
+ {t("connected")}
155
157
  </Badge>
156
158
  )}
157
159
  </div>
158
160
  <p className="truncate text-xs text-muted-foreground">
159
161
  {selectedStatus === "not-configured"
160
- ? `${selectedOption.label} is not configured on this server.`
161
- : selectedOption.description}
162
+ ? t("providerNotConfigured", {
163
+ provider: t(selectedOption.labelKey),
164
+ })
165
+ : t(selectedOption.descriptionKey)}
162
166
  </p>
163
167
  </div>
164
168
  </div>
@@ -178,19 +182,21 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
178
182
  <opt.Icon className="mt-0.5 h-4 w-4 shrink-0" />
179
183
  <div className="min-w-0">
180
184
  <div className="flex items-center gap-2">
181
- <span className="font-medium">{opt.label}</span>
185
+ <span className="font-medium">{t(opt.labelKey)}</span>
182
186
  {status === "connected" &&
183
187
  opt.type !== "none" &&
184
188
  opt.type !== "custom" && (
185
189
  <span className="text-[10px] text-muted-foreground">
186
- Connected
190
+ {t("connected")}
187
191
  </span>
188
192
  )}
189
193
  </div>
190
194
  <p className="text-xs text-muted-foreground">
191
195
  {isUnavailable
192
- ? `${opt.label} needs server OAuth credentials before it can be used.`
193
- : opt.description}
196
+ ? t("providerNeedsCredentials", {
197
+ provider: t(opt.labelKey),
198
+ })
199
+ : t(opt.descriptionKey)}
194
200
  </p>
195
201
  </div>
196
202
  </div>
@@ -205,10 +211,9 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
205
211
  <div className="rounded-md border border-border/60 bg-muted/30 p-3">
206
212
  <div className="flex flex-wrap items-center justify-between gap-2">
207
213
  <div className="min-w-0 flex-1">
208
- <p className="text-sm font-medium">Connect your Zoom account</p>
214
+ <p className="text-sm font-medium">{t("connectZoomAccount")}</p>
209
215
  <p className="text-xs text-muted-foreground">
210
- We'll create a real Zoom meeting for every booking — no need to
211
- paste a personal link.
216
+ {t("connectZoomAccountDescription")}
212
217
  </p>
213
218
  </div>
214
219
  <Button
@@ -218,13 +223,12 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
218
223
  disabled={zoomStatus === "not-configured"}
219
224
  >
220
225
  <IconBrandZoom className="mr-1.5 h-4 w-4" />
221
- Connect Zoom
226
+ {t("connectZoom")}
222
227
  </Button>
223
228
  </div>
224
229
  {zoomStatus === "not-configured" && (
225
230
  <p className="mt-2 text-[11px] text-muted-foreground">
226
- Ask your admin to set <code>ZOOM_CLIENT_ID</code> and{" "}
227
- <code>ZOOM_CLIENT_SECRET</code> to enable Zoom OAuth.
231
+ {t("zoomNotConfigured")}
228
232
  </p>
229
233
  )}
230
234
  </div>
@@ -237,10 +241,11 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
237
241
  <div className="rounded-md border border-border/60 bg-muted/30 p-3">
238
242
  <div className="flex flex-wrap items-center justify-between gap-2">
239
243
  <div className="min-w-0 flex-1">
240
- <p className="text-sm font-medium">Connect Google Calendar</p>
244
+ <p className="text-sm font-medium">
245
+ {t("connectGoogleCalendar")}
246
+ </p>
241
247
  <p className="text-xs text-muted-foreground">
242
- Meet links are auto-generated when the calendar event is
243
- created.
248
+ {t("connectGoogleCalendarDescription")}
244
249
  </p>
245
250
  </div>
246
251
  <Button
@@ -251,7 +256,7 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
251
256
  disabled={googleStatus === "not-configured"}
252
257
  >
253
258
  <IconBrandGoogle className="mr-1.5 h-4 w-4" />
254
- Connect Google
259
+ {t("connectGoogle")}
255
260
  </Button>
256
261
  </div>
257
262
  </div>
@@ -261,7 +266,7 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
261
266
  {value.type === "custom" && (
262
267
  <div className="space-y-1.5">
263
268
  <Label htmlFor={`${id}-url`} className="text-xs">
264
- Meeting URL
269
+ {t("meetingUrl")}
265
270
  </Label>
266
271
  <Input
267
272
  id={`${id}-url`}
@@ -270,7 +275,7 @@ export function ConferencingSelector(props: ConferencingSelectorProps) {
270
275
  onChange={(e) =>
271
276
  onChange({ type: "custom", url: e.currentTarget.value })
272
277
  }
273
- placeholder="https://meet.example.com/room"
278
+ placeholder={t("customMeetingUrlPlaceholder")}
274
279
  className="h-8 text-sm"
275
280
  />
276
281
  </div>