Add full editing for client proofing links
This commit is contained in:
@@ -208,6 +208,94 @@ function SessionCard({
|
||||
const [picks, setPicks] = useState<AdminProofPick[] | null>(null);
|
||||
const [copied, setCopied] = useState<'link' | 'lightroom' | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [albums, setAlbums] = useState<AlbumOption[] | null>(null);
|
||||
|
||||
const [editClientName, setEditClientName] = useState(session.clientName);
|
||||
const [editAlias, setEditAlias] = useState(session.alias ?? '');
|
||||
const [editAlbumId, setEditAlbumId] = useState(session.albumId);
|
||||
const [editExpiresOn, setEditExpiresOn] = useState(session.expiresOn ?? '');
|
||||
const [editDownloadsAllowed, setEditDownloadsAllowed] = useState(
|
||||
session.download !== 'none',
|
||||
);
|
||||
const [editLimit, setEditLimit] = useState(
|
||||
session.downloadLimit !== null
|
||||
? String(session.downloadLimit)
|
||||
: '',
|
||||
);
|
||||
const [editBusy, setEditBusy] = useState(false);
|
||||
|
||||
const beginEdit = async () => {
|
||||
setEditClientName(session.clientName);
|
||||
setEditAlias(session.alias ?? '');
|
||||
setEditAlbumId(session.albumId);
|
||||
setEditExpiresOn(session.expiresOn ?? '');
|
||||
setEditDownloadsAllowed(session.download !== 'none');
|
||||
setEditLimit(
|
||||
session.downloadLimit !== null
|
||||
? String(session.downloadLimit)
|
||||
: '',
|
||||
);
|
||||
|
||||
setError(null);
|
||||
|
||||
if (!albums) {
|
||||
try {
|
||||
const data = await api<{ albums: AlbumOption[] }>('/api/admin/albums');
|
||||
|
||||
setAlbums(
|
||||
[...data.albums].sort((a, b) =>
|
||||
a.albumName.localeCompare(b.albumName),
|
||||
),
|
||||
);
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setEditing(true);
|
||||
};
|
||||
|
||||
const saveEdit = async () => {
|
||||
setEditBusy(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
await api(`/api/admin/proofing/${session.id}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientName: editClientName.trim(),
|
||||
alias: editAlias.trim() || null,
|
||||
albumId: editAlbumId,
|
||||
expiresOn: editExpiresOn || null,
|
||||
|
||||
// We keep "selection" internally as our generic
|
||||
// "downloads allowed" state.
|
||||
download: editDownloadsAllowed
|
||||
? 'selection'
|
||||
: 'none',
|
||||
|
||||
downloadLimit:
|
||||
editDownloadsAllowed && editLimit
|
||||
? Number(editLimit)
|
||||
: null,
|
||||
}),
|
||||
});
|
||||
|
||||
setEditing(false);
|
||||
onChanged();
|
||||
} catch (err) {
|
||||
setError((err as Error).message);
|
||||
} finally {
|
||||
setEditBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
const linkUrl = typeof window !== 'undefined' ? `${window.location.origin}${session.link}` : '';
|
||||
|
||||
useEffect(() => {
|
||||
@@ -307,6 +395,13 @@ function SessionCard({
|
||||
{copied === 'link' ? <Icons.IconCheck size={14} /> : <Icons.IconCopy size={14} />}{' '}
|
||||
{copied === 'link' ? 'Copied' : 'Copy link'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="admin-btn admin-btn-ghost admin-btn-sm"
|
||||
onClick={() => void beginEdit()}
|
||||
>
|
||||
<Icons.IconLink size={14} /> Edit link
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="admin-btn admin-btn-ghost admin-btn-sm"
|
||||
@@ -353,6 +448,131 @@ function SessionCard({
|
||||
|
||||
{error && <p className="admin-error">{error}</p>}
|
||||
|
||||
{editing && (
|
||||
<div className="analytics-panel" style={{ marginTop: '1rem' }}>
|
||||
<div className="proofing-create-grid">
|
||||
|
||||
<div className="admin-field">
|
||||
<label>Client</label>
|
||||
<input
|
||||
value={editClientName}
|
||||
onChange={(e) => setEditClientName(e.target.value)}
|
||||
maxLength={120}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="admin-field">
|
||||
<label>Client URL alias</label>
|
||||
<input
|
||||
value={editAlias}
|
||||
onChange={(e) =>
|
||||
setEditAlias(
|
||||
e.target.value
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/[^A-Za-z0-9-]/g, ''),
|
||||
)
|
||||
}
|
||||
maxLength={64}
|
||||
placeholder="Mita"
|
||||
/>
|
||||
|
||||
<small>
|
||||
/c/{editAlias || 'Mita'}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div className="admin-field">
|
||||
<label>Album</label>
|
||||
<select
|
||||
value={editAlbumId}
|
||||
onChange={(e) => setEditAlbumId(e.target.value)}
|
||||
disabled={!albums}
|
||||
>
|
||||
{albums?.map((album) => (
|
||||
<option key={album.id} value={album.id}>
|
||||
{album.albumName} ({album.assetCount})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="admin-field">
|
||||
<label>Valid until (optional)</label>
|
||||
<input
|
||||
type="date"
|
||||
value={editExpiresOn}
|
||||
onChange={(e) => setEditExpiresOn(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="admin-field">
|
||||
<label>Downloads</label>
|
||||
|
||||
<select
|
||||
value={editDownloadsAllowed ? 'allowed' : 'none'}
|
||||
onChange={(e) =>
|
||||
setEditDownloadsAllowed(
|
||||
e.target.value === 'allowed',
|
||||
)
|
||||
}
|
||||
>
|
||||
<option value="none">Downloads disabled</option>
|
||||
<option value="allowed">Downloads allowed</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{editDownloadsAllowed && (
|
||||
<div className="admin-field">
|
||||
<label>Download limit (optional)</label>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
max={1000}
|
||||
value={editLimit}
|
||||
onChange={(e) => setEditLimit(e.target.value)}
|
||||
placeholder="Unlimited"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
{editAlbumId !== session.albumId && (
|
||||
<p className="analytics-subtitle">
|
||||
Changing the album will clear this client's current selection,
|
||||
submission state and download counter.
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div
|
||||
className="proofing-card-actions"
|
||||
style={{ marginTop: '1rem' }}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="admin-btn admin-btn-primary admin-btn-sm"
|
||||
onClick={() => void saveEdit()}
|
||||
disabled={
|
||||
editBusy ||
|
||||
!editClientName.trim() ||
|
||||
!editAlbumId
|
||||
}
|
||||
>
|
||||
{editBusy ? 'Saving…' : 'Save changes'}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="admin-btn admin-btn-ghost admin-btn-sm"
|
||||
onClick={() => setEditing(false)}
|
||||
disabled={editBusy}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{expanded && (
|
||||
<div className="proofing-card-body">
|
||||
<div className="proofing-card-actions">
|
||||
|
||||
Reference in New Issue
Block a user