@agent-native/core 0.157.0 → 0.157.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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +19 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +7 -2
- package/corpus/templates/assets/app/global.css +4 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1 -2
- package/corpus/templates/brain/app/routes/sources.tsx +1 -2
- package/corpus/templates/calendar/app/hooks/use-bookings.ts +12 -2
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +64 -2
- package/corpus/templates/calendar/server/handlers/bookings.ts +219 -93
- package/corpus/templates/calendar/server/plugins/auth.ts +0 -2
- package/corpus/templates/clips/actions/create-folder.ts +7 -1
- package/corpus/templates/clips/actions/create-meeting.ts +6 -2
- package/corpus/templates/clips/actions/create-recording.ts +5 -3
- package/corpus/templates/clips/actions/finalize-recording.ts +97 -65
- package/corpus/templates/clips/actions/import-loom-recording.ts +5 -3
- package/corpus/templates/clips/actions/request-recording-access.ts +272 -0
- package/corpus/templates/clips/actions/stitch-recordings.ts +5 -3
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +38 -63
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +75 -75
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +3 -21
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +13 -30
- package/corpus/templates/clips/app/i18n/en-US.ts +10 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +5 -5
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -16
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +135 -18
- package/corpus/templates/clips/changelog/2026-08-14-long-resumable-uploads-now-abort-cleanly-and-show-errors.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-14-standardized-share-popovers.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +11 -3
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +37 -8
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +3 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +8 -25
- package/corpus/templates/clips/desktop/src/app.tsx +24 -0
- package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +42 -13
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +11 -18
- package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +1 -0
- package/corpus/templates/clips/desktop/src/shared/config.ts +1 -0
- package/corpus/templates/clips/desktop/src/styles.css +24 -9
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +8 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +154 -176
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +115 -31
- package/corpus/templates/clips/desktop/src-tauri/src/rewind_clip.rs +7 -0
- package/corpus/templates/clips/server/db/index.ts +1 -0
- package/corpus/templates/clips/server/db/schema.ts +1 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +6 -1
- package/corpus/templates/clips/server/lib/emails.ts +23 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +55 -0
- package/corpus/templates/clips/server/lib/recordings.ts +2 -1
- package/corpus/templates/clips/server/lib/resumable-upload-cleanup.ts +49 -0
- package/corpus/templates/clips/server/lib/s3-upload-provider.ts +34 -7
- package/corpus/templates/clips/server/lib/upload-interruption.ts +15 -1
- package/corpus/templates/clips/server/lib/upload-lease.ts +94 -9
- package/corpus/templates/clips/server/routes/api/agent-context.json.get.ts +82 -7
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +71 -12
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +4 -3
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +19 -0
- package/corpus/templates/clips/server/routes/api/uploads/reap.post.ts +8 -1
- package/corpus/templates/clips/shared/recording-link.ts +4 -0
- package/corpus/templates/clips/shared/share-loader-response.ts +2 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +7 -16
- package/corpus/templates/factory/AGENTS.md +5 -1
- package/corpus/templates/factory/README.md +6 -2
- package/corpus/templates/factory/actions/view-screen.ts +15 -0
- package/corpus/templates/factory/app/components/factory/FactoryAgentsView.tsx +58 -189
- package/corpus/templates/factory/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/factory/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +7 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +3 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +3 -1
- package/corpus/templates/factory/app/routes/agents.tsx +9 -0
- package/corpus/templates/mail/server/plugins/auth.ts +0 -2
- package/corpus/templates/plan/app/components/plan/LocalCodebasePicker.tsx +50 -14
- package/corpus/templates/plan/app/pages/PlansPage.tsx +9 -19
- package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +7 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +71 -22
- package/corpus/templates/slides/app/lib/editor-shortcuts.ts +46 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +13 -0
- package/dist/client/AgentPanel.d.ts +13 -1
- package/dist/client/AgentPanel.js +12 -10
- package/dist/client/AssistantChat.js +25 -5
- package/dist/client/HighlightedCodeBlock.js +12 -2
- package/dist/client/agent-chat/index.d.ts +1 -0
- package/dist/client/agent-chat/index.js +1 -0
- package/dist/client/chat/markdown-renderer.js +16 -3
- package/dist/client/chat-first/apps-rail.js +1 -1
- package/dist/client/components/ui/message-scroller.js +1 -1
- package/dist/client/settings/AccountSettingsCard.js +48 -19
- package/dist/client/sharing/ShareButton.d.ts +1 -2
- package/dist/client/sharing/ShareButton.js +5 -41
- package/dist/client/sharing/ShareDialog.js +5 -4
- package/dist/client/use-chat-threads.js +10 -14
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/localization/default-messages.d.ts +1 -0
- package/dist/localization/default-messages.js +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +15 -15
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +0 -2
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.js +18 -0
- package/dist/server/onboarding-html.d.ts +1 -0
- package/dist/server/onboarding-html.js +2 -88
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/shared/markdown-block-split.d.ts +16 -0
- package/dist/shared/markdown-block-split.js +81 -5
- package/dist/styles/agent-native.css +13 -0
- package/dist/voice/voice-cleanup-prompt.js +1 -0
- package/package.json +2 -2
- package/corpus/templates/clips/shared/mic-audio-cleanup.ts +0 -350
package/corpus/README.md
CHANGED
|
@@ -265,7 +265,18 @@ export async function getFirstPartyAnalyticsBigQueryBackfillJob(
|
|
|
265
265
|
async function getNextFirstPartyAnalyticsBigQueryBackfillJob(
|
|
266
266
|
db: Executor,
|
|
267
267
|
now: string,
|
|
268
|
+
scope?: FirstPartyAnalyticsScope,
|
|
268
269
|
): Promise<BigQueryBackfillJob | null> {
|
|
270
|
+
const scopeClauses: string[] = [];
|
|
271
|
+
const args: unknown[] = [now, now];
|
|
272
|
+
if (scope?.orgId) {
|
|
273
|
+
scopeClauses.push("AND org_id = ?");
|
|
274
|
+
args.push(scope.orgId);
|
|
275
|
+
}
|
|
276
|
+
if (scope?.userEmail) {
|
|
277
|
+
scopeClauses.push("AND owner_email = ?");
|
|
278
|
+
args.push(scope.userEmail);
|
|
279
|
+
}
|
|
269
280
|
const result = await db.execute({
|
|
270
281
|
sql: `SELECT id, org_id, owner_email, table_ref, batch_size,
|
|
271
282
|
backfill_cursor, status, copied_count, lease_token,
|
|
@@ -278,9 +289,10 @@ async function getNextFirstPartyAnalyticsBigQueryBackfillJob(
|
|
|
278
289
|
OR (status = 'running' AND lease_expires_at IS NOT NULL
|
|
279
290
|
AND lease_expires_at <= ?)
|
|
280
291
|
)
|
|
292
|
+
${scopeClauses.join("\n ")}
|
|
281
293
|
ORDER BY updated_at ASC
|
|
282
294
|
LIMIT 1`,
|
|
283
|
-
args
|
|
295
|
+
args,
|
|
284
296
|
timeoutMs: 3_000,
|
|
285
297
|
maxAttempts: 1,
|
|
286
298
|
});
|
|
@@ -820,8 +832,8 @@ async function pressureSnapshot(
|
|
|
820
832
|
sql: `SELECT
|
|
821
833
|
COUNT(*) AS total_sessions,
|
|
822
834
|
SUM(CASE WHEN state = 'active' THEN 1 ELSE 0 END) AS active_sessions,
|
|
823
|
-
SUM(CASE WHEN wait_event_type IS NOT NULL THEN 1 ELSE 0 END) AS waiting_sessions,
|
|
824
|
-
SUM(CASE WHEN wait_event_type = 'Lock' THEN 1 ELSE 0 END) AS lock_waiters
|
|
835
|
+
SUM(CASE WHEN state = 'active' AND wait_event_type IS NOT NULL THEN 1 ELSE 0 END) AS waiting_sessions,
|
|
836
|
+
SUM(CASE WHEN state = 'active' AND wait_event_type = 'Lock' THEN 1 ELSE 0 END) AS lock_waiters
|
|
825
837
|
FROM pg_stat_activity
|
|
826
838
|
WHERE pid <> pg_backend_pid()`,
|
|
827
839
|
timeoutMs: 2_000,
|
|
@@ -870,7 +882,9 @@ async function recordPressurePause(
|
|
|
870
882
|
});
|
|
871
883
|
}
|
|
872
884
|
|
|
873
|
-
export async function runFirstPartyAnalyticsBigQueryBackfillOnce(
|
|
885
|
+
export async function runFirstPartyAnalyticsBigQueryBackfillOnce(
|
|
886
|
+
scope?: FirstPartyAnalyticsScope,
|
|
887
|
+
): Promise<BigQueryBackfillSweepResult> {
|
|
874
888
|
if (process.env.ANALYTICS_BIGQUERY_BACKFILL_JOBS?.trim() === "0") {
|
|
875
889
|
return { status: "disabled", batches: 0, copied: 0, remaining: 0 };
|
|
876
890
|
}
|
|
@@ -879,6 +893,7 @@ export async function runFirstPartyAnalyticsBigQueryBackfillOnce(): Promise<BigQ
|
|
|
879
893
|
const coordinator = await getNextFirstPartyAnalyticsBigQueryBackfillJob(
|
|
880
894
|
db,
|
|
881
895
|
new Date().toISOString(),
|
|
896
|
+
scope,
|
|
882
897
|
);
|
|
883
898
|
if (!coordinator) {
|
|
884
899
|
return { status: "idle", batches: 0, copied: 0, remaining: 0 };
|
|
@@ -913,7 +913,8 @@ function addPartitionPrunedEventDeduplication(
|
|
|
913
913
|
}
|
|
914
914
|
result +=
|
|
915
915
|
sql.slice(cursor, predicateEnd) +
|
|
916
|
-
" QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY received_at DESC) = 1"
|
|
916
|
+
" QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY received_at DESC) = 1" +
|
|
917
|
+
(predicateEnd < sql.length ? " " : "");
|
|
917
918
|
cursor = predicateEnd;
|
|
918
919
|
}
|
|
919
920
|
return result;
|
|
@@ -1405,7 +1406,11 @@ export async function backfillFirstPartyAnalyticsBatch(
|
|
|
1405
1406
|
const selectedRows = rows.slice(0, boundedLimit);
|
|
1406
1407
|
if (!selectedRows.length) {
|
|
1407
1408
|
return {
|
|
1408
|
-
|
|
1409
|
+
// An empty shard has no tuple cursor. Returning the sentinel empty
|
|
1410
|
+
// cursor makes the shard worker reject an otherwise successful drain.
|
|
1411
|
+
nextCursor: parsedCursor.receivedAt
|
|
1412
|
+
? serializeBackfillCursor(parsedCursor)
|
|
1413
|
+
: null,
|
|
1409
1414
|
copied: 0,
|
|
1410
1415
|
complete: true,
|
|
1411
1416
|
};
|
|
@@ -1217,12 +1217,11 @@ export function BrandKitDetailRoute({
|
|
|
1217
1217
|
);
|
|
1218
1218
|
const shareAction = (
|
|
1219
1219
|
<ShareButton
|
|
1220
|
-
trigger="label-icon"
|
|
1221
1220
|
resourceType="asset-library"
|
|
1222
1221
|
resourceId={library.id}
|
|
1223
1222
|
allowedRoles={["viewer", "editor", "admin"]}
|
|
1224
1223
|
resourceTitle={library.title}
|
|
1225
|
-
triggerClassName="h-10
|
|
1224
|
+
triggerClassName="h-10 px-4 border-input bg-background hover:bg-accent hover:text-accent-foreground"
|
|
1226
1225
|
/>
|
|
1227
1226
|
);
|
|
1228
1227
|
const headerActions = (
|
|
@@ -1875,8 +1875,7 @@ function SourceListItem({
|
|
|
1875
1875
|
resourceId={source.id}
|
|
1876
1876
|
allowedRoles={["viewer", "editor", "admin"]}
|
|
1877
1877
|
resourceTitle={sourceName(source)}
|
|
1878
|
-
|
|
1879
|
-
triggerClassName="size-8"
|
|
1878
|
+
triggerClassName="h-8 px-2"
|
|
1880
1879
|
/>
|
|
1881
1880
|
|
|
1882
1881
|
<DropdownMenu>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
2
|
-
import type { Booking } from "@shared/api";
|
|
2
|
+
import type { Booking, BookingHost } from "@shared/api";
|
|
3
3
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
4
|
|
|
5
5
|
import { appApiPath } from "@/lib/api-path";
|
|
@@ -17,16 +17,26 @@ export function useBookings() {
|
|
|
17
17
|
return useActionQuery<Booking[]>("list-bookings");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export type BookingAvailabilityPreview = {
|
|
21
|
+
slug: string;
|
|
22
|
+
durations: number[];
|
|
23
|
+
hosts: BookingHost[];
|
|
24
|
+
};
|
|
25
|
+
|
|
20
26
|
export function useAvailableSlots(
|
|
21
27
|
date: string,
|
|
22
28
|
duration: number,
|
|
23
29
|
slug?: string,
|
|
30
|
+
draft?: BookingAvailabilityPreview,
|
|
24
31
|
) {
|
|
32
|
+
const draftParam = draft ? JSON.stringify(draft) : undefined;
|
|
33
|
+
|
|
25
34
|
return useQuery<{ start: string; end: string }[]>({
|
|
26
|
-
queryKey: ["available-slots", date, duration, slug],
|
|
35
|
+
queryKey: ["available-slots", date, duration, slug, draftParam],
|
|
27
36
|
queryFn: async () => {
|
|
28
37
|
const params = new URLSearchParams({ date, duration: String(duration) });
|
|
29
38
|
if (slug) params.set("slug", slug);
|
|
39
|
+
if (draftParam) params.set("draft", draftParam);
|
|
30
40
|
const res = await fetch(
|
|
31
41
|
appApiPath(`/api/bookings/available-slots?${params}`),
|
|
32
42
|
);
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
addMonths,
|
|
47
47
|
subMonths,
|
|
48
48
|
format,
|
|
49
|
+
parseISO,
|
|
49
50
|
startOfDay,
|
|
50
51
|
getDay,
|
|
51
52
|
} from "date-fns";
|
|
@@ -113,6 +114,10 @@ import {
|
|
|
113
114
|
useUpdateBookingLink,
|
|
114
115
|
OPTIMISTIC_PREFIX,
|
|
115
116
|
} from "@/hooks/use-booking-links";
|
|
117
|
+
import {
|
|
118
|
+
useAvailableSlots,
|
|
119
|
+
type BookingAvailabilityPreview,
|
|
120
|
+
} from "@/hooks/use-bookings";
|
|
116
121
|
import { useGoogleAuthStatus } from "@/hooks/use-google-auth";
|
|
117
122
|
import { useSettings } from "@/hooks/use-settings";
|
|
118
123
|
import { useZoomStatus, useConnectZoom } from "@/hooks/use-zoom-auth";
|
|
@@ -826,6 +831,14 @@ export default function BookingLinksPage({
|
|
|
826
831
|
!!selectedLink &&
|
|
827
832
|
savedDraftSignature !== null &&
|
|
828
833
|
draftSignature !== savedDraftSignature;
|
|
834
|
+
const availabilityPreview =
|
|
835
|
+
selectedLink && !selectedLink.id.startsWith(OPTIMISTIC_PREFIX)
|
|
836
|
+
? ({
|
|
837
|
+
slug: slugify(draft.slug),
|
|
838
|
+
durations: draft.durations,
|
|
839
|
+
hosts: draft.hosts,
|
|
840
|
+
} satisfies BookingAvailabilityPreview)
|
|
841
|
+
: undefined;
|
|
829
842
|
|
|
830
843
|
function handleCreate() {
|
|
831
844
|
setCreateDialogOpen(true);
|
|
@@ -1535,6 +1548,13 @@ export default function BookingLinksPage({
|
|
|
1535
1548
|
customFields={draft.customFields}
|
|
1536
1549
|
isActive={draft.isActive}
|
|
1537
1550
|
availability={availability ?? undefined}
|
|
1551
|
+
bookingSourceSlug={
|
|
1552
|
+
selectedLink.id?.startsWith(OPTIMISTIC_PREFIX) ||
|
|
1553
|
+
!availabilityPreview
|
|
1554
|
+
? undefined
|
|
1555
|
+
: selectedLink.slug
|
|
1556
|
+
}
|
|
1557
|
+
availabilityPreview={availabilityPreview}
|
|
1538
1558
|
bookingUrl={previewUrl}
|
|
1539
1559
|
onCopy={() => void copyPreviewUrl(draft.slug)}
|
|
1540
1560
|
openHref={bookingPreviewPath(draft.slug)}
|
|
@@ -2022,6 +2042,8 @@ function BookingPreview({
|
|
|
2022
2042
|
customFields = [],
|
|
2023
2043
|
isActive,
|
|
2024
2044
|
availability,
|
|
2045
|
+
bookingSourceSlug,
|
|
2046
|
+
availabilityPreview,
|
|
2025
2047
|
bookingUrl,
|
|
2026
2048
|
onCopy,
|
|
2027
2049
|
openHref,
|
|
@@ -2034,6 +2056,8 @@ function BookingPreview({
|
|
|
2034
2056
|
customFields?: CustomField[];
|
|
2035
2057
|
isActive: boolean;
|
|
2036
2058
|
availability?: AvailabilityConfig;
|
|
2059
|
+
bookingSourceSlug?: string;
|
|
2060
|
+
availabilityPreview?: BookingAvailabilityPreview;
|
|
2037
2061
|
bookingUrl?: string;
|
|
2038
2062
|
onCopy?: () => void;
|
|
2039
2063
|
openHref?: string;
|
|
@@ -2062,6 +2086,24 @@ function BookingPreview({
|
|
|
2062
2086
|
fieldResponses: {},
|
|
2063
2087
|
});
|
|
2064
2088
|
|
|
2089
|
+
const liveAvailabilityDate =
|
|
2090
|
+
bookingSourceSlug && selectedDate ? format(selectedDate, "yyyy-MM-dd") : "";
|
|
2091
|
+
const liveAvailabilityDuration =
|
|
2092
|
+
selectedDuration !== null && durations.includes(selectedDuration)
|
|
2093
|
+
? selectedDuration
|
|
2094
|
+
: primaryDuration;
|
|
2095
|
+
const {
|
|
2096
|
+
data: liveSlots = [],
|
|
2097
|
+
isLoading: liveSlotsLoading,
|
|
2098
|
+
isError: liveSlotsError,
|
|
2099
|
+
} = useAvailableSlots(
|
|
2100
|
+
liveAvailabilityDate,
|
|
2101
|
+
liveAvailabilityDuration,
|
|
2102
|
+
bookingSourceSlug,
|
|
2103
|
+
availabilityPreview,
|
|
2104
|
+
);
|
|
2105
|
+
const hasLiveAvailability = Boolean(bookingSourceSlug && selectedDate);
|
|
2106
|
+
|
|
2065
2107
|
// Reset selections when durations change
|
|
2066
2108
|
useEffect(() => {
|
|
2067
2109
|
setSelectedDuration(null);
|
|
@@ -2092,6 +2134,9 @@ function BookingPreview({
|
|
|
2092
2134
|
|
|
2093
2135
|
// Generate realistic time slots based on availability
|
|
2094
2136
|
const timeSlots = useMemo(() => {
|
|
2137
|
+
if (hasLiveAvailability) {
|
|
2138
|
+
return liveSlots.map((slot) => format(parseISO(slot.start), "h:mm a"));
|
|
2139
|
+
}
|
|
2095
2140
|
if (!selectedDate || !availability) {
|
|
2096
2141
|
return ["9:00 AM", "9:30 AM", "10:00 AM", "10:30 AM", "11:00 AM"];
|
|
2097
2142
|
}
|
|
@@ -2122,7 +2167,14 @@ function BookingPreview({
|
|
|
2122
2167
|
}
|
|
2123
2168
|
}
|
|
2124
2169
|
return slots;
|
|
2125
|
-
}, [
|
|
2170
|
+
}, [
|
|
2171
|
+
selectedDate,
|
|
2172
|
+
selectedDuration,
|
|
2173
|
+
primaryDuration,
|
|
2174
|
+
availability,
|
|
2175
|
+
hasLiveAvailability,
|
|
2176
|
+
liveSlots,
|
|
2177
|
+
]);
|
|
2126
2178
|
|
|
2127
2179
|
// Determine which step to show
|
|
2128
2180
|
const [forcedStep, setForcedStep] = useState<BookingPreviewStep | null>(null);
|
|
@@ -2458,7 +2510,17 @@ function BookingPreview({
|
|
|
2458
2510
|
{t("bookingLinks.availableTimes")}
|
|
2459
2511
|
</p>
|
|
2460
2512
|
)}
|
|
2461
|
-
{
|
|
2513
|
+
{liveSlotsLoading ? (
|
|
2514
|
+
<div className="grid grid-cols-3 gap-1.5">
|
|
2515
|
+
{Array.from({ length: 6 }).map((_, index) => (
|
|
2516
|
+
<Skeleton key={index} className="h-8 rounded-md" />
|
|
2517
|
+
))}
|
|
2518
|
+
</div>
|
|
2519
|
+
) : liveSlotsError ? (
|
|
2520
|
+
<p className="rounded-md border border-destructive/30 bg-destructive/[0.06] px-2.5 py-2 text-center text-xs text-destructive">
|
|
2521
|
+
{t("bookingLinks.availabilityUnavailable")}
|
|
2522
|
+
</p>
|
|
2523
|
+
) : timeSlots.length > 0 ? (
|
|
2462
2524
|
<div className="grid grid-cols-3 gap-1.5">
|
|
2463
2525
|
{timeSlots.map((slot) => (
|
|
2464
2526
|
<button
|