@stigmer/react 3.1.7 → 3.1.9
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/agent/AgentDetailView.d.ts +9 -1
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +15 -4
- package/agent/AgentDetailView.js.map +1 -1
- package/environment/EnvironmentListPanel.d.ts.map +1 -1
- package/environment/EnvironmentListPanel.js +11 -3
- package/environment/EnvironmentListPanel.js.map +1 -1
- package/environment/EnvironmentPicker.d.ts +17 -1
- package/environment/EnvironmentPicker.d.ts.map +1 -1
- package/environment/EnvironmentPicker.js +3 -2
- package/environment/EnvironmentPicker.js.map +1 -1
- package/environment/EnvironmentVariableEditor.d.ts.map +1 -1
- package/environment/EnvironmentVariableEditor.js +9 -3
- package/environment/EnvironmentVariableEditor.js.map +1 -1
- package/environment/index.d.ts +1 -0
- package/environment/index.d.ts.map +1 -1
- package/environment/index.js +1 -0
- package/environment/index.js.map +1 -1
- package/environment/shareRestriction.d.ts +13 -0
- package/environment/shareRestriction.d.ts.map +1 -0
- package/environment/shareRestriction.js +15 -0
- package/environment/shareRestriction.js.map +1 -0
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +12 -0
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/ToolRunGroup.js +4 -0
- package/execution/ToolRunGroup.js.map +1 -1
- package/execution/WriteBackCard.d.ts +14 -5
- package/execution/WriteBackCard.d.ts.map +1 -1
- package/execution/WriteBackCard.js +103 -32
- package/execution/WriteBackCard.js.map +1 -1
- package/execution/WriteBacksWidget.d.ts +1 -1
- package/execution/WriteBacksWidget.js +2 -2
- package/execution/WriteBacksWidget.js.map +1 -1
- package/execution/index.d.ts +2 -0
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +1 -0
- package/execution/index.js.map +1 -1
- package/execution/write-back-utils.d.ts +57 -0
- package/execution/write-back-utils.d.ts.map +1 -0
- package/execution/write-back-utils.js +81 -0
- package/execution/write-back-utils.js.map +1 -0
- package/index.d.ts +6 -2
- package/index.d.ts.map +1 -1
- package/index.js +6 -1
- package/index.js.map +1 -1
- package/internal/dev/dom-counter.d.ts.map +1 -1
- package/internal/dev/dom-counter.js +2 -2
- package/internal/dev/dom-counter.js.map +1 -1
- package/internal/dev/enabled.d.ts +7 -0
- package/internal/dev/enabled.d.ts.map +1 -0
- package/internal/dev/enabled.js +38 -0
- package/internal/dev/enabled.js.map +1 -0
- package/internal/dev/profiler-wrapper.d.ts +3 -3
- package/internal/dev/profiler-wrapper.d.ts.map +1 -1
- package/internal/dev/profiler-wrapper.js +5 -5
- package/internal/dev/profiler-wrapper.js.map +1 -1
- package/internal/dev/use-key-stability.d.ts.map +1 -1
- package/internal/dev/use-key-stability.js +2 -2
- package/internal/dev/use-key-stability.js.map +1 -1
- package/internal/dev/use-render-tracer.d.ts +2 -2
- package/internal/dev/use-render-tracer.d.ts.map +1 -1
- package/internal/dev/use-render-tracer.js +4 -4
- package/internal/dev/use-render-tracer.js.map +1 -1
- package/internal/dev/use-stream-rate.d.ts +2 -1
- package/internal/dev/use-stream-rate.d.ts.map +1 -1
- package/internal/dev/use-stream-rate.js +4 -3
- package/internal/dev/use-stream-rate.js.map +1 -1
- package/library/ResourceVisibilityControl.d.ts +3 -0
- package/library/ResourceVisibilityControl.d.ts.map +1 -1
- package/library/ResourceVisibilityControl.js +22 -8
- package/library/ResourceVisibilityControl.js.map +1 -1
- package/library/useUpdateVisibility.d.ts +5 -3
- package/library/useUpdateVisibility.d.ts.map +1 -1
- package/library/useUpdateVisibility.js +7 -2
- package/library/useUpdateVisibility.js.map +1 -1
- package/library/visibilityLevels.d.ts +17 -0
- package/library/visibilityLevels.d.ts.map +1 -1
- package/library/visibilityLevels.js +32 -0
- package/library/visibilityLevels.js.map +1 -1
- package/package.json +4 -4
- package/session/NewSessionViewer.d.ts +11 -1
- package/session/NewSessionViewer.d.ts.map +1 -1
- package/session/NewSessionViewer.js +28 -8
- package/session/NewSessionViewer.js.map +1 -1
- package/session/SessionViewer.d.ts +9 -2
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +26 -12
- package/session/SessionViewer.js.map +1 -1
- package/session/audience.d.ts +12 -4
- package/session/audience.d.ts.map +1 -1
- package/session/facets/ChangesTab.d.ts.map +1 -1
- package/session/facets/ChangesTab.js +3 -1
- package/session/facets/ChangesTab.js.map +1 -1
- package/session/useNewSessionFlow.d.ts +12 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +13 -1
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionPageFlow.d.ts +13 -0
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +7 -2
- package/session/useSessionPageFlow.js.map +1 -1
- package/sharing/ShareAgentDialog.d.ts +57 -0
- package/sharing/ShareAgentDialog.d.ts.map +1 -0
- package/sharing/ShareAgentDialog.js +478 -0
- package/sharing/ShareAgentDialog.js.map +1 -0
- package/sharing/SharedAgentChat.d.ts +84 -0
- package/sharing/SharedAgentChat.d.ts.map +1 -0
- package/sharing/SharedAgentChat.js +80 -0
- package/sharing/SharedAgentChat.js.map +1 -0
- package/sharing/index.d.ts +18 -0
- package/sharing/index.d.ts.map +1 -0
- package/sharing/index.js +12 -0
- package/sharing/index.js.map +1 -0
- package/sharing/useAgentShare.d.ts +43 -0
- package/sharing/useAgentShare.d.ts.map +1 -0
- package/sharing/useAgentShare.js +54 -0
- package/sharing/useAgentShare.js.map +1 -0
- package/sharing/useRotateShareLink.d.ts +45 -0
- package/sharing/useRotateShareLink.d.ts.map +1 -0
- package/sharing/useRotateShareLink.js +58 -0
- package/sharing/useRotateShareLink.js.map +1 -0
- package/sharing/useSaveAgentShare.d.ts +105 -0
- package/sharing/useSaveAgentShare.d.ts.map +1 -0
- package/sharing/useSaveAgentShare.js +97 -0
- package/sharing/useSaveAgentShare.js.map +1 -0
- package/sharing/useShareAgent.d.ts +69 -0
- package/sharing/useShareAgent.d.ts.map +1 -0
- package/sharing/useShareAgent.js +42 -0
- package/sharing/useShareAgent.js.map +1 -0
- package/sharing/useShareToolReadiness.d.ts +52 -0
- package/sharing/useShareToolReadiness.d.ts.map +1 -0
- package/sharing/useShareToolReadiness.js +89 -0
- package/sharing/useShareToolReadiness.js.map +1 -0
- package/sharing/useSharedAgentProfile.d.ts +85 -0
- package/sharing/useSharedAgentProfile.d.ts.map +1 -0
- package/sharing/useSharedAgentProfile.js +73 -0
- package/sharing/useSharedAgentProfile.js.map +1 -0
- package/src/agent/AgentDetailView.tsx +28 -3
- package/src/environment/EnvironmentListPanel.tsx +47 -18
- package/src/environment/EnvironmentPicker.tsx +23 -2
- package/src/environment/EnvironmentVariableEditor.tsx +27 -17
- package/src/environment/__tests__/shareRestriction.test.ts +35 -0
- package/src/environment/index.ts +5 -0
- package/src/environment/shareRestriction.ts +22 -0
- package/src/execution/ToolCallItem.tsx +19 -1
- package/src/execution/ToolRunGroup.tsx +4 -0
- package/src/execution/WriteBackCard.tsx +285 -88
- package/src/execution/WriteBacksWidget.tsx +5 -5
- package/src/execution/__tests__/ToolCallItem.test.tsx +51 -0
- package/src/execution/__tests__/ToolRunGroup.test.tsx +22 -0
- package/src/execution/__tests__/WriteBackCard.test.tsx +64 -10
- package/src/execution/__tests__/write-back-utils.test.ts +135 -0
- package/src/execution/index.ts +7 -0
- package/src/execution/write-back-utils.ts +107 -0
- package/src/index.ts +38 -0
- package/src/internal/dev/__tests__/use-key-stability.test.ts +4 -0
- package/src/internal/dev/__tests__/use-render-tracer.test.ts +4 -0
- package/src/internal/dev/dom-counter.ts +2 -3
- package/src/internal/dev/enabled.ts +39 -0
- package/src/internal/dev/profiler-wrapper.tsx +5 -6
- package/src/internal/dev/use-key-stability.ts +2 -3
- package/src/internal/dev/use-render-tracer.ts +4 -5
- package/src/internal/dev/use-stream-rate.ts +4 -4
- package/src/library/ResourceVisibilityControl.tsx +23 -7
- package/src/library/__tests__/visibilityLevels.test.ts +33 -0
- package/src/library/useUpdateVisibility.ts +9 -3
- package/src/library/visibilityLevels.ts +36 -0
- package/src/session/NewSessionViewer.tsx +67 -31
- package/src/session/SessionViewer.tsx +67 -36
- package/src/session/__tests__/audienceWiring.test.tsx +99 -0
- package/src/session/__tests__/useNewSessionFlow.test.tsx +75 -1
- package/src/session/__tests__/useSessionPageFlow.guest.test.tsx +112 -0
- package/src/session/audience.ts +12 -4
- package/src/session/facets/ChangesTab.tsx +6 -4
- package/src/session/useNewSessionFlow.ts +26 -1
- package/src/session/useSessionPageFlow.ts +24 -2
- package/src/sharing/ShareAgentDialog.tsx +1489 -0
- package/src/sharing/SharedAgentChat.tsx +283 -0
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +926 -0
- package/src/sharing/__tests__/SharedAgentChat.test.tsx +222 -0
- package/src/sharing/__tests__/useAgentShare.test.tsx +149 -0
- package/src/sharing/__tests__/useRotateShareLink.test.tsx +96 -0
- package/src/sharing/__tests__/useSaveAgentShare.test.tsx +241 -0
- package/src/sharing/__tests__/useShareAgent.test.tsx +131 -0
- package/src/sharing/__tests__/useShareToolReadiness.test.tsx +219 -0
- package/src/sharing/__tests__/useSharedAgentProfile.test.tsx +201 -0
- package/src/sharing/index.ts +32 -0
- package/src/sharing/useAgentShare.ts +91 -0
- package/src/sharing/useRotateShareLink.ts +81 -0
- package/src/sharing/useSaveAgentShare.ts +186 -0
- package/src/sharing/useShareAgent.tsx +107 -0
- package/src/sharing/useShareToolReadiness.ts +131 -0
- package/src/sharing/useSharedAgentProfile.ts +132 -0
- package/src/switch/Switch.tsx +97 -0
- package/src/switch/__tests__/Switch.test.tsx +107 -0
- package/src/switch/index.ts +2 -0
- package/styles.css +1 -1
- package/switch/Switch.d.ts +47 -0
- package/switch/Switch.d.ts.map +1 -0
- package/switch/Switch.js +41 -0
- package/switch/Switch.js.map +1 -0
- package/switch/index.d.ts +3 -0
- package/switch/index.d.ts.map +1 -0
- package/switch/index.js +2 -0
- package/switch/index.js.map +1 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { getUserMessage } from "@stigmer/sdk";
|
|
6
|
+
import { NewSessionViewer } from "../session/NewSessionViewer.js";
|
|
7
|
+
import { SessionViewer } from "../session/SessionViewer.js";
|
|
8
|
+
import { useSharedAgentProfile } from "./useSharedAgentProfile.js";
|
|
9
|
+
import type { SharingAudience } from "./useSaveAgentShare.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Public API
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
/** Props for {@link SharedAgentChat}. */
|
|
16
|
+
export interface SharedAgentChatProps {
|
|
17
|
+
/** Organization slug from the share URL. */
|
|
18
|
+
readonly org: string;
|
|
19
|
+
/** Agent slug from the share URL. */
|
|
20
|
+
readonly slug: string;
|
|
21
|
+
/**
|
|
22
|
+
* Heading above the composer before the first message.
|
|
23
|
+
* Defaults to a prompt built from the agent's name.
|
|
24
|
+
*/
|
|
25
|
+
readonly heading?: string;
|
|
26
|
+
/** Placeholder for the composer textarea. */
|
|
27
|
+
readonly placeholder?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Render the "Powered by Stigmer" footer.
|
|
30
|
+
*
|
|
31
|
+
* @default true
|
|
32
|
+
*/
|
|
33
|
+
readonly showPoweredBy?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Called after the visitor's session and first execution are
|
|
36
|
+
* created — e.g. to reflect the session in the host page's URL.
|
|
37
|
+
*/
|
|
38
|
+
readonly onSessionCreated?: (sessionId: string) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Which sharing audience this surface serves. Selects the profile
|
|
41
|
+
* resolution path: `"public"` uses the anonymous `getSharedProfile`
|
|
42
|
+
* RPC (pair with `createGuestAuth`); `"org"` uses the authenticated
|
|
43
|
+
* `getSharedProfileForMember` RPC and requires a `StigmerProvider`
|
|
44
|
+
* whose client carries a signed-in org member's token. The chat
|
|
45
|
+
* presentation is identical either way.
|
|
46
|
+
*
|
|
47
|
+
* @default "public"
|
|
48
|
+
*/
|
|
49
|
+
readonly sharingAudience?: SharingAudience;
|
|
50
|
+
/**
|
|
51
|
+
* Share-link token from the URL's `?k=` parameter (public audience
|
|
52
|
+
* only). Required when the share link has been locked with a
|
|
53
|
+
* rotatable token; harmless on plain links. Pair it with the same
|
|
54
|
+
* `linkToken` on `createGuestAuth` so profile resolution and token
|
|
55
|
+
* minting present the same credentialing.
|
|
56
|
+
*/
|
|
57
|
+
readonly linkToken?: string;
|
|
58
|
+
/** Additional CSS class names for the root container. */
|
|
59
|
+
readonly className?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The complete shared-agent chat experience for anonymous visitors —
|
|
64
|
+
* the organism behind a shared agent's hosted page and embeds.
|
|
65
|
+
*
|
|
66
|
+
* Resolves the agent's public profile (name, description, icon, and
|
|
67
|
+
* the pinned deployment) via {@link useSharedAgentProfile}, then
|
|
68
|
+
* renders the session organisms with `audience="guest"`: a pure chat
|
|
69
|
+
* surface with no configuration pickers, pinned to the shared agent's
|
|
70
|
+
* default instance so the session binds synchronously on mount.
|
|
71
|
+
*
|
|
72
|
+
* Handles all states: loading, unavailable (the agent does not exist
|
|
73
|
+
* or is not shared — indistinguishable by design), transient errors
|
|
74
|
+
* (with retry), and the live chat.
|
|
75
|
+
*
|
|
76
|
+
* **Auth contract:** requires a `StigmerProvider` whose client can
|
|
77
|
+
* chat with this agent. For public shares (the default), pair with
|
|
78
|
+
* `createGuestAuth({ baseUrl, org, slug })` from `@stigmer/sdk` —
|
|
79
|
+
* the profile fetch is public and sessions use the guest token the
|
|
80
|
+
* provider mints on demand. For org-members-only shares, pass
|
|
81
|
+
* `sharingAudience="org"` and a client carrying the signed-in
|
|
82
|
+
* member's own token; the presentation stays the same pure chat.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* const guestAuth = createGuestAuth({ baseUrl, org, slug });
|
|
87
|
+
* const client = useMemo(
|
|
88
|
+
* () => new Stigmer({ baseUrl, getAccessToken: guestAuth.getAccessToken }),
|
|
89
|
+
* [guestAuth],
|
|
90
|
+
* );
|
|
91
|
+
*
|
|
92
|
+
* <StigmerProvider client={client} colorMode="system">
|
|
93
|
+
* <SharedAgentChat org={org} slug={slug} />
|
|
94
|
+
* </StigmerProvider>
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function SharedAgentChat({
|
|
98
|
+
org,
|
|
99
|
+
slug,
|
|
100
|
+
heading,
|
|
101
|
+
placeholder,
|
|
102
|
+
showPoweredBy = true,
|
|
103
|
+
onSessionCreated,
|
|
104
|
+
sharingAudience = "public",
|
|
105
|
+
linkToken,
|
|
106
|
+
className,
|
|
107
|
+
}: SharedAgentChatProps) {
|
|
108
|
+
const { profile, isLoading, error, refetch } = useSharedAgentProfile(org, slug, {
|
|
109
|
+
audience: sharingAudience,
|
|
110
|
+
linkToken,
|
|
111
|
+
});
|
|
112
|
+
const [sessionId, setSessionId] = useState<string | null>(null);
|
|
113
|
+
const [submitError, setSubmitError] = useState<string | null>(null);
|
|
114
|
+
|
|
115
|
+
const handleSessionCreated = useCallback(
|
|
116
|
+
(id: string) => {
|
|
117
|
+
setSubmitError(null);
|
|
118
|
+
setSessionId(id);
|
|
119
|
+
onSessionCreated?.(id);
|
|
120
|
+
},
|
|
121
|
+
[onSessionCreated],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (isLoading) {
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
className={cn("flex h-full w-full items-center justify-center", className)}
|
|
128
|
+
aria-busy="true"
|
|
129
|
+
aria-label="Loading agent"
|
|
130
|
+
>
|
|
131
|
+
<LoadingSkeleton />
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (error) {
|
|
137
|
+
return (
|
|
138
|
+
<div className={cn("flex h-full w-full items-center justify-center", className)}>
|
|
139
|
+
<StateCard
|
|
140
|
+
title="Something went wrong"
|
|
141
|
+
message={getUserMessage(error)}
|
|
142
|
+
onRetry={refetch}
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// NOT_FOUND covers both "no such agent" and "sharing disabled" — the
|
|
149
|
+
// server keeps them indistinguishable so a revoked link leaks nothing.
|
|
150
|
+
// A profile without a default instance is equally unusable: there is
|
|
151
|
+
// no deployment to chat with, so it presents as the same state.
|
|
152
|
+
if (!profile || !profile.defaultInstanceId) {
|
|
153
|
+
return (
|
|
154
|
+
<div className={cn("flex h-full w-full items-center justify-center", className)}>
|
|
155
|
+
<StateCard
|
|
156
|
+
title="This agent isn't available"
|
|
157
|
+
message="The link may be incorrect, or sharing may have been turned off."
|
|
158
|
+
/>
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<div className={cn("flex h-full w-full flex-col", className)}>
|
|
165
|
+
<header className="flex items-center gap-3 border-b border-border px-4 py-3">
|
|
166
|
+
{profile.iconUrl ? (
|
|
167
|
+
<img
|
|
168
|
+
src={profile.iconUrl}
|
|
169
|
+
alt=""
|
|
170
|
+
className="size-8 rounded-md object-cover"
|
|
171
|
+
/>
|
|
172
|
+
) : (
|
|
173
|
+
<div
|
|
174
|
+
aria-hidden="true"
|
|
175
|
+
className="flex size-8 items-center justify-center rounded-md bg-muted text-sm font-semibold text-muted-foreground"
|
|
176
|
+
>
|
|
177
|
+
{(profile.name || slug).charAt(0).toUpperCase()}
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
<div className="min-w-0">
|
|
181
|
+
<h1 className="truncate text-sm font-semibold text-foreground">
|
|
182
|
+
{profile.name || slug}
|
|
183
|
+
</h1>
|
|
184
|
+
{profile.description && (
|
|
185
|
+
<p className="truncate text-xs text-muted-foreground">
|
|
186
|
+
{profile.description}
|
|
187
|
+
</p>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
</header>
|
|
191
|
+
|
|
192
|
+
<div className="min-h-0 flex-1">
|
|
193
|
+
{sessionId ? (
|
|
194
|
+
<SessionViewer
|
|
195
|
+
sessionId={sessionId}
|
|
196
|
+
org={org}
|
|
197
|
+
audience="guest"
|
|
198
|
+
enableGitHub={false}
|
|
199
|
+
/>
|
|
200
|
+
) : (
|
|
201
|
+
<NewSessionViewer
|
|
202
|
+
org={org}
|
|
203
|
+
audience="guest"
|
|
204
|
+
initialAgentRef={{ org, slug }}
|
|
205
|
+
initialInstanceId={profile.defaultInstanceId}
|
|
206
|
+
enableGitHub={false}
|
|
207
|
+
heading={heading ?? `Chat with ${profile.name || slug}`}
|
|
208
|
+
placeholder={placeholder ?? "Ask anything\u2026"}
|
|
209
|
+
onSessionCreated={handleSessionCreated}
|
|
210
|
+
onError={setSubmitError}
|
|
211
|
+
footerContent={
|
|
212
|
+
submitError ? (
|
|
213
|
+
<p
|
|
214
|
+
role="alert"
|
|
215
|
+
className="text-center text-xs text-destructive"
|
|
216
|
+
>
|
|
217
|
+
{submitError}
|
|
218
|
+
</p>
|
|
219
|
+
) : undefined
|
|
220
|
+
}
|
|
221
|
+
/>
|
|
222
|
+
)}
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
{showPoweredBy && (
|
|
226
|
+
<footer className="border-t border-border px-4 py-2 text-center">
|
|
227
|
+
<a
|
|
228
|
+
href="https://stigmer.ai"
|
|
229
|
+
target="_blank"
|
|
230
|
+
rel="noopener noreferrer"
|
|
231
|
+
className="text-[0.65rem] text-muted-foreground hover:text-foreground transition-colors"
|
|
232
|
+
>
|
|
233
|
+
Powered by Stigmer
|
|
234
|
+
</a>
|
|
235
|
+
</footer>
|
|
236
|
+
)}
|
|
237
|
+
</div>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
// State presentation
|
|
243
|
+
// ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
function LoadingSkeleton() {
|
|
246
|
+
return (
|
|
247
|
+
<div className="w-full max-w-sm space-y-3 px-6">
|
|
248
|
+
<div className="mx-auto h-8 w-8 animate-pulse rounded-md bg-muted" />
|
|
249
|
+
<div className="mx-auto h-4 w-2/3 animate-pulse rounded bg-muted" />
|
|
250
|
+
<div className="mx-auto h-3 w-1/2 animate-pulse rounded bg-muted" />
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function StateCard({
|
|
256
|
+
title,
|
|
257
|
+
message,
|
|
258
|
+
onRetry,
|
|
259
|
+
}: {
|
|
260
|
+
readonly title: string;
|
|
261
|
+
readonly message: string;
|
|
262
|
+
readonly onRetry?: () => void;
|
|
263
|
+
}) {
|
|
264
|
+
return (
|
|
265
|
+
<div className="mx-6 w-full max-w-sm rounded-lg border border-border bg-card p-6 text-center shadow-sm">
|
|
266
|
+
<h2 className="text-base font-semibold text-foreground">{title}</h2>
|
|
267
|
+
<p className="mt-1 text-sm text-muted-foreground">{message}</p>
|
|
268
|
+
{onRetry && (
|
|
269
|
+
<button
|
|
270
|
+
type="button"
|
|
271
|
+
onClick={onRetry}
|
|
272
|
+
className={cn(
|
|
273
|
+
"mt-4 inline-flex items-center justify-center rounded-md px-4 py-2 text-sm font-medium",
|
|
274
|
+
"bg-primary text-primary-foreground hover:bg-primary-hover",
|
|
275
|
+
"transition-colors",
|
|
276
|
+
)}
|
|
277
|
+
>
|
|
278
|
+
Try again
|
|
279
|
+
</button>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|