@actuate-media/cms-admin 0.48.0 → 0.49.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/CHANGELOG.md +22 -0
- package/dist/AdminRoot.d.ts +10 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +25 -43
- package/dist/AdminRoot.js.map +1 -1
- package/dist/LoginRoot.d.ts +54 -0
- package/dist/LoginRoot.d.ts.map +1 -0
- package/dist/LoginRoot.js +59 -0
- package/dist/LoginRoot.js.map +1 -0
- package/dist/__tests__/components/modal.render.test.d.ts +2 -0
- package/dist/__tests__/components/modal.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/modal.render.test.js +37 -0
- package/dist/__tests__/components/modal.render.test.js.map +1 -0
- package/dist/__tests__/views/login-root.render.test.d.ts +2 -0
- package/dist/__tests__/views/login-root.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/login-root.render.test.js +79 -0
- package/dist/__tests__/views/login-root.render.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/MediaPickerModal.d.ts +1 -1
- package/dist/components/MediaPickerModal.d.ts.map +1 -1
- package/dist/components/MediaPickerModal.js +12 -11
- package/dist/components/MediaPickerModal.js.map +1 -1
- package/dist/components/SchedulePublishDialog.d.ts +1 -1
- package/dist/components/SchedulePublishDialog.d.ts.map +1 -1
- package/dist/components/SchedulePublishDialog.js +6 -4
- package/dist/components/SchedulePublishDialog.js.map +1 -1
- package/dist/components/SharePreviewLinkDialog.d.ts +1 -1
- package/dist/components/SharePreviewLinkDialog.d.ts.map +1 -1
- package/dist/components/SharePreviewLinkDialog.js +10 -9
- package/dist/components/SharePreviewLinkDialog.js.map +1 -1
- package/dist/components/VersionHistory.d.ts +1 -1
- package/dist/components/VersionHistory.d.ts.map +1 -1
- package/dist/components/VersionHistory.js +14 -14
- package/dist/components/VersionHistory.js.map +1 -1
- package/dist/components/ui/Modal.d.ts +17 -1
- package/dist/components/ui/Modal.d.ts.map +1 -1
- package/dist/components/ui/Modal.js +16 -15
- package/dist/components/ui/Modal.js.map +1 -1
- package/dist/fields/BlockBuilderField.d.ts.map +1 -1
- package/dist/fields/BlockBuilderField.js +6 -4
- package/dist/fields/BlockBuilderField.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/root-chrome.d.ts +15 -0
- package/dist/lib/root-chrome.d.ts.map +1 -0
- package/dist/lib/root-chrome.js +41 -0
- package/dist/lib/root-chrome.js.map +1 -0
- package/dist/views/HiddenLoginNotFound.d.ts +8 -0
- package/dist/views/HiddenLoginNotFound.d.ts.map +1 -0
- package/dist/views/HiddenLoginNotFound.js +29 -0
- package/dist/views/HiddenLoginNotFound.js.map +1 -0
- package/dist/views/page-builder/AIGenerateDialog.d.ts +1 -1
- package/dist/views/page-builder/AIGenerateDialog.d.ts.map +1 -1
- package/dist/views/page-builder/AIGenerateDialog.js +23 -23
- package/dist/views/page-builder/AIGenerateDialog.js.map +1 -1
- package/package.json +2 -2
- package/src/AdminRoot.tsx +61 -59
- package/src/LoginRoot.tsx +166 -0
- package/src/__tests__/components/modal.render.test.tsx +57 -0
- package/src/__tests__/views/login-root.render.test.tsx +115 -0
- package/src/components/MediaPickerModal.tsx +143 -119
- package/src/components/SchedulePublishDialog.tsx +118 -93
- package/src/components/SharePreviewLinkDialog.tsx +115 -93
- package/src/components/VersionHistory.tsx +98 -82
- package/src/components/ui/Modal.tsx +48 -39
- package/src/fields/BlockBuilderField.tsx +26 -19
- package/src/index.ts +2 -0
- package/src/lib/root-chrome.ts +40 -0
- package/src/views/HiddenLoginNotFound.tsx +45 -0
- package/src/views/page-builder/AIGenerateDialog.tsx +347 -335
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
3
|
+
import { useEffect, useId, useState } from 'react'
|
|
4
|
+
import * as Dialog from '@radix-ui/react-dialog'
|
|
4
5
|
import { Calendar, Clock, Loader2, X } from 'lucide-react'
|
|
5
6
|
import { toast } from 'sonner'
|
|
6
7
|
import { cmsApi } from '../lib/api.js'
|
|
8
|
+
import { adminPortalContainer } from '../lib/portal-container.js'
|
|
7
9
|
|
|
8
10
|
export interface SchedulePublishDialogProps {
|
|
9
11
|
collectionSlug: string
|
|
@@ -56,6 +58,8 @@ export function SchedulePublishDialog({
|
|
|
56
58
|
const [includeUnpublish, setIncludeUnpublish] = useState(false)
|
|
57
59
|
const [saving, setSaving] = useState(false)
|
|
58
60
|
const [cancelling, setCancelling] = useState(false)
|
|
61
|
+
const publishAtId = useId()
|
|
62
|
+
const unpublishAtId = useId()
|
|
59
63
|
|
|
60
64
|
const hasExistingSchedule = Boolean(scheduledAt || scheduledUnpublishAt)
|
|
61
65
|
|
|
@@ -133,109 +137,130 @@ export function SchedulePublishDialog({
|
|
|
133
137
|
onClose()
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
if (!open) return null
|
|
137
|
-
|
|
138
140
|
return (
|
|
139
|
-
<
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
<div className="flex items-center
|
|
144
|
-
<
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
className="rounded-lg p-1.5 transition-colors hover:bg-gray-100"
|
|
150
|
-
>
|
|
151
|
-
<X className="h-5 w-5 text-gray-500" />
|
|
152
|
-
</button>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
<div className="space-y-4 px-4 py-4">
|
|
156
|
-
<div className="space-y-1.5">
|
|
157
|
-
<label className="text-sm font-medium text-gray-700">Publish at</label>
|
|
158
|
-
<div className="relative">
|
|
159
|
-
<Clock className="pointer-events-none absolute top-2.5 left-2.5 h-4 w-4 text-gray-400" />
|
|
160
|
-
<input
|
|
161
|
-
type="datetime-local"
|
|
162
|
-
value={publishAt}
|
|
163
|
-
onChange={(e) => setPublishAt(e.target.value)}
|
|
164
|
-
min={toLocalDateTimeInput(new Date(Date.now() + 60 * 1000))}
|
|
165
|
-
className="w-full rounded-md border border-gray-300 bg-white py-2 pr-3 pl-9 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
|
166
|
-
/>
|
|
141
|
+
<Dialog.Root open={open} onOpenChange={(next) => !next && onClose()}>
|
|
142
|
+
<Dialog.Portal container={adminPortalContainer()}>
|
|
143
|
+
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/40" />
|
|
144
|
+
<Dialog.Content className="bg-card text-card-foreground border-border fixed top-1/2 left-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border shadow-xl focus:outline-none">
|
|
145
|
+
<div className="border-border flex items-center justify-between border-b px-4 py-3">
|
|
146
|
+
<div className="flex items-center gap-2">
|
|
147
|
+
<Calendar className="text-muted-foreground h-5 w-5" aria-hidden />
|
|
148
|
+
<Dialog.Title className="text-foreground text-lg font-medium">
|
|
149
|
+
Schedule publishing
|
|
150
|
+
</Dialog.Title>
|
|
167
151
|
</div>
|
|
168
|
-
<
|
|
169
|
-
|
|
170
|
-
|
|
152
|
+
<Dialog.Close asChild>
|
|
153
|
+
<button
|
|
154
|
+
type="button"
|
|
155
|
+
aria-label="Close dialog"
|
|
156
|
+
className="text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring rounded-lg p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none"
|
|
157
|
+
>
|
|
158
|
+
<X className="h-5 w-5" aria-hidden />
|
|
159
|
+
</button>
|
|
160
|
+
</Dialog.Close>
|
|
171
161
|
</div>
|
|
172
162
|
|
|
173
|
-
<
|
|
174
|
-
<input
|
|
175
|
-
type="checkbox"
|
|
176
|
-
checked={includeUnpublish}
|
|
177
|
-
onChange={(e) => setIncludeUnpublish(e.target.checked)}
|
|
178
|
-
className="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
179
|
-
/>
|
|
180
|
-
Also schedule an unpublish
|
|
181
|
-
</label>
|
|
182
|
-
|
|
183
|
-
{includeUnpublish && (
|
|
163
|
+
<div className="space-y-4 px-4 py-4">
|
|
184
164
|
<div className="space-y-1.5">
|
|
185
|
-
<label className="text-sm font-medium
|
|
165
|
+
<label htmlFor={publishAtId} className="text-foreground text-sm font-medium">
|
|
166
|
+
Publish at
|
|
167
|
+
</label>
|
|
186
168
|
<div className="relative">
|
|
187
|
-
<Clock
|
|
169
|
+
<Clock
|
|
170
|
+
className="text-muted-foreground pointer-events-none absolute top-2.5 left-2.5 h-4 w-4"
|
|
171
|
+
aria-hidden
|
|
172
|
+
/>
|
|
188
173
|
<input
|
|
174
|
+
id={publishAtId}
|
|
189
175
|
type="datetime-local"
|
|
190
|
-
value={
|
|
191
|
-
onChange={(e) =>
|
|
192
|
-
min={
|
|
193
|
-
className="
|
|
176
|
+
value={publishAt}
|
|
177
|
+
onChange={(e) => setPublishAt(e.target.value)}
|
|
178
|
+
min={toLocalDateTimeInput(new Date(Date.now() + 60 * 1000))}
|
|
179
|
+
className="border-input bg-background text-foreground focus:border-ring focus:ring-ring w-full rounded-md border py-2 pr-3 pl-9 text-sm focus:ring-1 focus:outline-none"
|
|
194
180
|
/>
|
|
195
181
|
</div>
|
|
182
|
+
<p className="text-muted-foreground text-xs">
|
|
183
|
+
The document will move from DRAFT to PUBLISHED at this time (your local timezone).
|
|
184
|
+
</p>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<label className="text-foreground flex items-center gap-2 text-sm">
|
|
188
|
+
<input
|
|
189
|
+
type="checkbox"
|
|
190
|
+
checked={includeUnpublish}
|
|
191
|
+
onChange={(e) => setIncludeUnpublish(e.target.checked)}
|
|
192
|
+
className="border-input text-primary focus:ring-ring h-4 w-4 rounded"
|
|
193
|
+
/>
|
|
194
|
+
Also schedule an unpublish
|
|
195
|
+
</label>
|
|
196
|
+
|
|
197
|
+
{includeUnpublish && (
|
|
198
|
+
<div className="space-y-1.5">
|
|
199
|
+
<label htmlFor={unpublishAtId} className="text-foreground text-sm font-medium">
|
|
200
|
+
Unpublish at
|
|
201
|
+
</label>
|
|
202
|
+
<div className="relative">
|
|
203
|
+
<Clock
|
|
204
|
+
className="text-muted-foreground pointer-events-none absolute top-2.5 left-2.5 h-4 w-4"
|
|
205
|
+
aria-hidden
|
|
206
|
+
/>
|
|
207
|
+
<input
|
|
208
|
+
id={unpublishAtId}
|
|
209
|
+
type="datetime-local"
|
|
210
|
+
value={unpublishAt}
|
|
211
|
+
onChange={(e) => setUnpublishAt(e.target.value)}
|
|
212
|
+
min={publishAt || toLocalDateTimeInput(new Date(Date.now() + 60 * 1000))}
|
|
213
|
+
className="border-input bg-background text-foreground focus:border-ring focus:ring-ring w-full rounded-md border py-2 pr-3 pl-9 text-sm focus:ring-1 focus:outline-none"
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<div className="border-border flex items-center justify-between gap-2 border-t px-4 py-3">
|
|
221
|
+
{hasExistingSchedule ? (
|
|
222
|
+
<button
|
|
223
|
+
type="button"
|
|
224
|
+
onClick={handleCancelSchedule}
|
|
225
|
+
disabled={cancelling || saving}
|
|
226
|
+
className="text-destructive hover:bg-destructive/10 inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium disabled:opacity-50"
|
|
227
|
+
>
|
|
228
|
+
{cancelling ? (
|
|
229
|
+
<Loader2 className="h-4 w-4 animate-spin" aria-hidden />
|
|
230
|
+
) : (
|
|
231
|
+
<X className="h-4 w-4" aria-hidden />
|
|
232
|
+
)}
|
|
233
|
+
Cancel schedule
|
|
234
|
+
</button>
|
|
235
|
+
) : (
|
|
236
|
+
<span />
|
|
237
|
+
)}
|
|
238
|
+
<div className="flex items-center gap-2">
|
|
239
|
+
<Dialog.Close asChild>
|
|
240
|
+
<button
|
|
241
|
+
type="button"
|
|
242
|
+
className="text-foreground hover:bg-accent rounded-md px-3 py-1.5 text-sm"
|
|
243
|
+
>
|
|
244
|
+
Close
|
|
245
|
+
</button>
|
|
246
|
+
</Dialog.Close>
|
|
247
|
+
<button
|
|
248
|
+
type="button"
|
|
249
|
+
onClick={handleSave}
|
|
250
|
+
disabled={saving || cancelling}
|
|
251
|
+
className="bg-primary text-primary-foreground inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium hover:opacity-90 disabled:opacity-50"
|
|
252
|
+
>
|
|
253
|
+
{saving ? (
|
|
254
|
+
<Loader2 className="h-4 w-4 animate-spin" aria-hidden />
|
|
255
|
+
) : (
|
|
256
|
+
<Calendar className="h-4 w-4" aria-hidden />
|
|
257
|
+
)}
|
|
258
|
+
{hasExistingSchedule ? 'Reschedule' : 'Schedule'}
|
|
259
|
+
</button>
|
|
196
260
|
</div>
|
|
197
|
-
)}
|
|
198
|
-
</div>
|
|
199
|
-
|
|
200
|
-
<div className="flex items-center justify-between gap-2 border-t border-gray-200 px-4 py-3">
|
|
201
|
-
{hasExistingSchedule ? (
|
|
202
|
-
<button
|
|
203
|
-
onClick={handleCancelSchedule}
|
|
204
|
-
disabled={cancelling || saving}
|
|
205
|
-
className="inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-red-700 hover:bg-red-50 disabled:opacity-50"
|
|
206
|
-
>
|
|
207
|
-
{cancelling ? (
|
|
208
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
209
|
-
) : (
|
|
210
|
-
<X className="h-4 w-4" />
|
|
211
|
-
)}
|
|
212
|
-
Cancel schedule
|
|
213
|
-
</button>
|
|
214
|
-
) : (
|
|
215
|
-
<span />
|
|
216
|
-
)}
|
|
217
|
-
<div className="flex items-center gap-2">
|
|
218
|
-
<button
|
|
219
|
-
onClick={onClose}
|
|
220
|
-
className="rounded-md px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-100"
|
|
221
|
-
>
|
|
222
|
-
Close
|
|
223
|
-
</button>
|
|
224
|
-
<button
|
|
225
|
-
onClick={handleSave}
|
|
226
|
-
disabled={saving || cancelling}
|
|
227
|
-
className="inline-flex items-center gap-1.5 rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
|
228
|
-
>
|
|
229
|
-
{saving ? (
|
|
230
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
231
|
-
) : (
|
|
232
|
-
<Calendar className="h-4 w-4" />
|
|
233
|
-
)}
|
|
234
|
-
{hasExistingSchedule ? 'Reschedule' : 'Schedule'}
|
|
235
|
-
</button>
|
|
236
261
|
</div>
|
|
237
|
-
</
|
|
238
|
-
</
|
|
239
|
-
</
|
|
262
|
+
</Dialog.Content>
|
|
263
|
+
</Dialog.Portal>
|
|
264
|
+
</Dialog.Root>
|
|
240
265
|
)
|
|
241
266
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useState } from 'react'
|
|
3
|
+
import { useId, useState } from 'react'
|
|
4
|
+
import * as Dialog from '@radix-ui/react-dialog'
|
|
4
5
|
import { Check, Copy, Link2, Loader2, Share2, X } from 'lucide-react'
|
|
5
6
|
import { toast } from 'sonner'
|
|
6
7
|
import { cmsApi } from '../lib/api.js'
|
|
8
|
+
import { adminPortalContainer } from '../lib/portal-container.js'
|
|
7
9
|
|
|
8
10
|
export interface SharePreviewLinkDialogProps {
|
|
9
11
|
collectionSlug: string
|
|
@@ -75,6 +77,7 @@ export function SharePreviewLinkDialog({
|
|
|
75
77
|
const [expiresAt, setExpiresAt] = useState<string>('')
|
|
76
78
|
const [generating, setGenerating] = useState(false)
|
|
77
79
|
const [copied, setCopied] = useState(false)
|
|
80
|
+
const previewUrlId = useId()
|
|
78
81
|
|
|
79
82
|
async function generate() {
|
|
80
83
|
setGenerating(true)
|
|
@@ -121,107 +124,126 @@ export function SharePreviewLinkDialog({
|
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
if (!open) return null
|
|
125
|
-
|
|
126
127
|
return (
|
|
127
|
-
<
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
<
|
|
131
|
-
<div className="flex items-center
|
|
132
|
-
<
|
|
133
|
-
|
|
128
|
+
<Dialog.Root open={open} onOpenChange={(next) => !next && onClose()}>
|
|
129
|
+
<Dialog.Portal container={adminPortalContainer()}>
|
|
130
|
+
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/40" />
|
|
131
|
+
<Dialog.Content className="bg-card text-card-foreground border-border fixed top-1/2 left-1/2 z-50 w-full max-w-lg -translate-x-1/2 -translate-y-1/2 rounded-lg border shadow-xl focus:outline-none">
|
|
132
|
+
<div className="border-border flex items-center justify-between border-b px-4 py-3">
|
|
133
|
+
<div className="flex items-center gap-2">
|
|
134
|
+
<Share2 className="text-muted-foreground h-5 w-5" aria-hidden />
|
|
135
|
+
<Dialog.Title className="text-foreground text-lg font-medium">
|
|
136
|
+
Share preview link
|
|
137
|
+
</Dialog.Title>
|
|
138
|
+
</div>
|
|
139
|
+
<Dialog.Close asChild>
|
|
140
|
+
<button
|
|
141
|
+
type="button"
|
|
142
|
+
aria-label="Close dialog"
|
|
143
|
+
className="text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring rounded-lg p-1.5 transition-colors focus-visible:ring-2 focus-visible:outline-none"
|
|
144
|
+
>
|
|
145
|
+
<X className="h-5 w-5" aria-hidden />
|
|
146
|
+
</button>
|
|
147
|
+
</Dialog.Close>
|
|
134
148
|
</div>
|
|
135
|
-
<button
|
|
136
|
-
onClick={onClose}
|
|
137
|
-
className="rounded-lg p-1.5 transition-colors hover:bg-gray-100"
|
|
138
|
-
>
|
|
139
|
-
<X className="h-5 w-5 text-gray-500" />
|
|
140
|
-
</button>
|
|
141
|
-
</div>
|
|
142
149
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
<div className="space-y-4 px-4 py-4">
|
|
151
|
+
<Dialog.Description className="text-muted-foreground text-sm">
|
|
152
|
+
Generate a signed URL that lets anyone with the link view the current draft of this
|
|
153
|
+
document — even if it's never been published.
|
|
154
|
+
</Dialog.Description>
|
|
148
155
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
<div className="space-y-2">
|
|
157
|
+
<span className="text-foreground text-sm font-medium">Link lifetime</span>
|
|
158
|
+
<div className="grid grid-cols-2 gap-2">
|
|
159
|
+
{TTL_CHOICES.map((choice) => {
|
|
160
|
+
const selected = ttl === choice.seconds
|
|
161
|
+
return (
|
|
162
|
+
<button
|
|
163
|
+
key={choice.seconds}
|
|
164
|
+
type="button"
|
|
165
|
+
aria-pressed={selected}
|
|
166
|
+
onClick={() => setTtl(choice.seconds)}
|
|
167
|
+
className={`rounded-md border px-3 py-2 text-left text-sm transition-colors ${
|
|
168
|
+
selected
|
|
169
|
+
? 'border-primary bg-primary/10 text-foreground'
|
|
170
|
+
: 'border-border hover:border-input hover:bg-accent'
|
|
171
|
+
}`}
|
|
172
|
+
>
|
|
173
|
+
<div className="font-medium">{choice.label}</div>
|
|
174
|
+
<div className="text-muted-foreground text-xs">{choice.hint}</div>
|
|
175
|
+
</button>
|
|
176
|
+
)
|
|
177
|
+
})}
|
|
178
|
+
</div>
|
|
179
|
+
<p className="text-muted-foreground text-xs">
|
|
180
|
+
Preview tokens can't be revoked individually — pick the shortest TTL that fits.
|
|
181
|
+
</p>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
{previewUrl ? (
|
|
185
|
+
<div className="border-border bg-muted space-y-2 rounded-lg border p-3">
|
|
186
|
+
<label
|
|
187
|
+
htmlFor={previewUrlId}
|
|
188
|
+
className="text-muted-foreground text-xs font-medium tracking-wide uppercase"
|
|
189
|
+
>
|
|
190
|
+
Preview URL
|
|
191
|
+
</label>
|
|
192
|
+
<div className="flex items-stretch gap-2">
|
|
193
|
+
<input
|
|
194
|
+
id={previewUrlId}
|
|
195
|
+
readOnly
|
|
196
|
+
value={previewUrl}
|
|
197
|
+
onFocus={(e) => e.currentTarget.select()}
|
|
198
|
+
className="border-input bg-background text-foreground flex-1 rounded-md border px-2 py-1.5 font-mono text-xs"
|
|
199
|
+
/>
|
|
155
200
|
<button
|
|
156
|
-
|
|
157
|
-
onClick={
|
|
158
|
-
className=
|
|
159
|
-
selected
|
|
160
|
-
? 'border-blue-500 bg-blue-50 text-blue-900'
|
|
161
|
-
: 'border-gray-200 hover:border-gray-300 hover:bg-gray-50'
|
|
162
|
-
}`}
|
|
201
|
+
type="button"
|
|
202
|
+
onClick={copyToClipboard}
|
|
203
|
+
className="bg-primary text-primary-foreground inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium hover:opacity-90"
|
|
163
204
|
>
|
|
164
|
-
|
|
165
|
-
|
|
205
|
+
{copied ? (
|
|
206
|
+
<Check className="h-3.5 w-3.5" aria-hidden />
|
|
207
|
+
) : (
|
|
208
|
+
<Copy className="h-3.5 w-3.5" aria-hidden />
|
|
209
|
+
)}
|
|
210
|
+
{copied ? 'Copied' : 'Copy'}
|
|
166
211
|
</button>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
212
|
+
</div>
|
|
213
|
+
{expiresAt && (
|
|
214
|
+
<p className="text-muted-foreground text-xs">
|
|
215
|
+
Expires {new Date(expiresAt).toLocaleString()}
|
|
216
|
+
</p>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
) : null}
|
|
173
220
|
</div>
|
|
174
221
|
|
|
175
|
-
|
|
176
|
-
<
|
|
177
|
-
<
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
</div>
|
|
195
|
-
{expiresAt && (
|
|
196
|
-
<p className="text-xs text-gray-500">
|
|
197
|
-
Expires {new Date(expiresAt).toLocaleString()}
|
|
198
|
-
</p>
|
|
222
|
+
<div className="border-border flex items-center justify-end gap-2 border-t px-4 py-3">
|
|
223
|
+
<Dialog.Close asChild>
|
|
224
|
+
<button
|
|
225
|
+
type="button"
|
|
226
|
+
className="text-foreground hover:bg-accent rounded-md px-3 py-1.5 text-sm"
|
|
227
|
+
>
|
|
228
|
+
Close
|
|
229
|
+
</button>
|
|
230
|
+
</Dialog.Close>
|
|
231
|
+
<button
|
|
232
|
+
type="button"
|
|
233
|
+
onClick={generate}
|
|
234
|
+
disabled={generating}
|
|
235
|
+
className="bg-primary text-primary-foreground inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium hover:opacity-90 disabled:opacity-50"
|
|
236
|
+
>
|
|
237
|
+
{generating ? (
|
|
238
|
+
<Loader2 className="h-4 w-4 animate-spin" aria-hidden />
|
|
239
|
+
) : (
|
|
240
|
+
<Link2 className="h-4 w-4" aria-hidden />
|
|
199
241
|
)}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
onClick={onClose}
|
|
207
|
-
className="rounded-md px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-100"
|
|
208
|
-
>
|
|
209
|
-
Close
|
|
210
|
-
</button>
|
|
211
|
-
<button
|
|
212
|
-
onClick={generate}
|
|
213
|
-
disabled={generating}
|
|
214
|
-
className="inline-flex items-center gap-1.5 rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
|
215
|
-
>
|
|
216
|
-
{generating ? (
|
|
217
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
218
|
-
) : (
|
|
219
|
-
<Link2 className="h-4 w-4" />
|
|
220
|
-
)}
|
|
221
|
-
{previewUrl ? 'Generate new link' : 'Generate link'}
|
|
222
|
-
</button>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
242
|
+
{previewUrl ? 'Generate new link' : 'Generate link'}
|
|
243
|
+
</button>
|
|
244
|
+
</div>
|
|
245
|
+
</Dialog.Content>
|
|
246
|
+
</Dialog.Portal>
|
|
247
|
+
</Dialog.Root>
|
|
226
248
|
)
|
|
227
249
|
}
|