@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.
- package/dist/react/components/SlotPicker.d.ts.map +1 -1
- package/dist/react/components/SlotPicker.js +3 -1
- package/dist/react/components/SlotPicker.js.map +1 -1
- package/dist/react/components/booking-links/AvailabilityEditor.d.ts +2 -1
- package/dist/react/components/booking-links/AvailabilityEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/AvailabilityEditor.js +28 -22
- package/dist/react/components/booking-links/AvailabilityEditor.js.map +1 -1
- package/dist/react/components/booking-links/BookingLinkCreateDialog.d.ts.map +1 -1
- package/dist/react/components/booking-links/BookingLinkCreateDialog.js +6 -4
- package/dist/react/components/booking-links/BookingLinkCreateDialog.js.map +1 -1
- package/dist/react/components/booking-links/ConferencingSelector.d.ts.map +1 -1
- package/dist/react/components/booking-links/ConferencingSelector.js +25 -19
- package/dist/react/components/booking-links/ConferencingSelector.js.map +1 -1
- package/dist/react/components/booking-links/CustomFieldsEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/CustomFieldsEditor.js +39 -25
- package/dist/react/components/booking-links/CustomFieldsEditor.js.map +1 -1
- package/dist/react/components/booking-links/DurationPicker.d.ts.map +1 -1
- package/dist/react/components/booking-links/DurationPicker.js +6 -2
- package/dist/react/components/booking-links/DurationPicker.js.map +1 -1
- package/dist/react/components/booking-links/SlugEditor.d.ts.map +1 -1
- package/dist/react/components/booking-links/SlugEditor.js +5 -3
- package/dist/react/components/booking-links/SlugEditor.js.map +1 -1
- package/dist/react/i18n.d.ts +109 -0
- package/dist/react/i18n.d.ts.map +1 -0
- package/dist/react/i18n.js +976 -0
- package/dist/react/i18n.js.map +1 -0
- package/dist/schema/bookings.d.ts +12 -12
- package/dist/schema/event-types.d.ts +12 -12
- package/dist/schema/routing-forms.d.ts +12 -12
- package/dist/schema/schedules.d.ts +12 -12
- package/dist/schema/teams.d.ts +12 -12
- package/dist/schema/workflows.d.ts +15 -15
- package/package.json +10 -11
- package/src/react/components/SlotPicker.tsx +6 -1
- package/src/react/components/booking-links/AvailabilityEditor.tsx +48 -27
- package/src/react/components/booking-links/BookingLinkCreateDialog.tsx +17 -15
- package/src/react/components/booking-links/ConferencingSelector.tsx +36 -31
- package/src/react/components/booking-links/CustomFieldsEditor.tsx +71 -52
- package/src/react/components/booking-links/DurationPicker.tsx +8 -4
- package/src/react/components/booking-links/SlugEditor.tsx +7 -5
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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>
|
|
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: {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{ key: "
|
|
42
|
-
{ key: "
|
|
43
|
-
{ key: "
|
|
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,
|
|
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={
|
|
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">
|
|
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(
|
|
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)
|
|
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)
|
|
148
|
-
|
|
149
|
-
else if (
|
|
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,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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 = "
|
|
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>
|
|
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">
|
|
110
|
+
<Label htmlFor="blc-title">{t("title")}</Label>
|
|
111
111
|
<Input
|
|
112
112
|
id="blc-title"
|
|
113
|
-
placeholder="
|
|
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">
|
|
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="
|
|
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">
|
|
153
|
+
<Label htmlFor="blc-desc">{t("description")}</Label>
|
|
154
154
|
<Textarea
|
|
155
155
|
id="blc-desc"
|
|
156
|
-
placeholder="
|
|
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">
|
|
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">
|
|
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
|
-
|
|
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 ? "
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
75
|
-
|
|
75
|
+
labelKey: "noConferencing",
|
|
76
|
+
descriptionKey: "inPersonOrOther",
|
|
76
77
|
Icon: IconVideoOff,
|
|
77
78
|
},
|
|
78
79
|
{
|
|
79
80
|
type: "google_meet",
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
labelKey: "googleMeet",
|
|
82
|
+
descriptionKey: "googleMeetDescription",
|
|
82
83
|
Icon: IconBrandGoogle,
|
|
83
84
|
},
|
|
84
85
|
{
|
|
85
86
|
type: "zoom",
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
labelKey: "zoom",
|
|
88
|
+
descriptionKey: "zoomDescription",
|
|
88
89
|
Icon: IconBrandZoom,
|
|
89
90
|
},
|
|
90
91
|
{
|
|
91
92
|
type: "custom",
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
?
|
|
161
|
-
|
|
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.
|
|
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
|
-
|
|
190
|
+
{t("connected")}
|
|
187
191
|
</span>
|
|
188
192
|
)}
|
|
189
193
|
</div>
|
|
190
194
|
<p className="text-xs text-muted-foreground">
|
|
191
195
|
{isUnavailable
|
|
192
|
-
?
|
|
193
|
-
|
|
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">
|
|
214
|
+
<p className="text-sm font-medium">{t("connectZoomAccount")}</p>
|
|
209
215
|
<p className="text-xs text-muted-foreground">
|
|
210
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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">
|
|
244
|
+
<p className="text-sm font-medium">
|
|
245
|
+
{t("connectGoogleCalendar")}
|
|
246
|
+
</p>
|
|
241
247
|
<p className="text-xs text-muted-foreground">
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
278
|
+
placeholder={t("customMeetingUrlPlaceholder")}
|
|
274
279
|
className="h-8 text-sm"
|
|
275
280
|
/>
|
|
276
281
|
</div>
|