Files
immich-power-tools/src/components/shared/ShareAssetsTrigger.tsx
T

180 lines
7.3 KiB
TypeScript

import React, { useState } from 'react'
import { Dialog, DialogTitle, DialogContent, DialogHeader, DialogTrigger, DialogDescription } from '../ui/dialog';
import { Button, ButtonProps } from '../ui/button';
import { DropdownMenuItem } from '../ui/dropdown-menu';
import { ShareLinkFilters } from '@/types/shareLink';
import { generateShareLink } from '@/handlers/api/shareLink.handler';
import { createShareKey, getShareKey } from '@/handlers/api/share-key.handler';
import { Input } from '../ui/input';
import { Label } from '@radix-ui/react-label';
import { Switch } from '../ui/switch';
import { Select, SelectValue, SelectContent, SelectItem, SelectTrigger } from '../ui/select';
interface ShareAssetsTriggerProps {
filters: ShareLinkFilters
buttonProps?: ButtonProps
asDropdownItem?: boolean
children?: React.ReactNode
}
export default function ShareAssetsTrigger({ filters, buttonProps, asDropdownItem, children }: ShareAssetsTriggerProps) {
const [generatedLink, setGeneratedLink] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const [config, setConfig] = useState<Partial<ShareLinkFilters>>({
expiresIn: "never"
});
const [endpointConfigured, setEndpointConfigured] = useState<boolean | null>(null);
const [shareKeyExists, setShareKeyExists] = useState<boolean | null>(null);
const [creatingKey, setCreatingKey] = useState(false);
const [keyError, setKeyError] = useState<string | null>(null);
const checkShareKey = async () => {
const data = await getShareKey();
setEndpointConfigured(data.endpointConfigured);
setShareKeyExists(data.exists);
};
const handleCreateKey = async () => {
setCreatingKey(true);
setKeyError(null);
try {
await createShareKey();
setShareKeyExists(true);
} catch (err: any) {
setKeyError(err.message ?? 'Failed to create share key');
}
setCreatingKey(false);
};
const handleReset = () => {
setConfig({ expiresIn: "never" });
setGeneratedLink(null);
}
const handleGenerate = async () => {
setLoading(true);
setErrorMessage(null);
return generateShareLink({ ...filters, ...config }).then(({ link }) => {
setGeneratedLink(link);
}).catch((err) => {
setErrorMessage(err.message);
}).finally(() => {
setLoading(false);
});
}
const handleCopy = async () => {
if (!generatedLink) return;
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(generatedLink);
} else {
const textArea = document.createElement('textarea');
textArea.value = generatedLink;
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
}
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 2000);
} catch {
setErrorMessage('Failed to copy link');
}
}
return (
<Dialog onOpenChange={(open) => { if (open) checkShareKey(); }}>
<DialogTrigger asChild>
{asDropdownItem ? (
<DropdownMenuItem onSelect={(e) => e.preventDefault()} className="gap-2">
{children}
</DropdownMenuItem>
) : (
<Button {...buttonProps}>{children ?? "Share"}</Button>
)}
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Share Assets</DialogTitle>
</DialogHeader>
<DialogDescription>
Generate a share link for the selected assets (either bunch of albums or bunch of people) and share it with your friends and family.
</DialogDescription>
{endpointConfigured === false && (
<div className="rounded-md border border-red-400 bg-red-50 dark:bg-red-950 p-3">
<p className="text-sm text-red-800 dark:text-red-200">
<strong>POWER_TOOLS_ENDPOINT_URL</strong> is not set. Add it to your environment variables to enable share links.
</p>
</div>
)}
{endpointConfigured && shareKeyExists === false && (
<div className="rounded-md border border-yellow-400 bg-yellow-50 dark:bg-yellow-950 p-3 flex flex-col gap-2">
<p className="text-sm text-yellow-800 dark:text-yellow-200">
Serving thumbnails on share links requires a dedicated API key. Create one now with minimal permissions.
</p>
{keyError && <p className="text-xs text-red-500">{keyError}</p>}
<Button size="sm" onClick={handleCreateKey} disabled={creatingKey} className="self-start">
{creatingKey ? 'Creating...' : 'Create API Key'}
</Button>
</div>
)}
{errorMessage && <div className="text-red-500">{errorMessage}</div>}
{generatedLink ? <div className="flex flex-col gap-2">
<Label className='text-sm'>Share Link</Label>
<Input readOnly type="text" value={generatedLink} />
<p className='text-xs text-muted-foreground'>
This is a stateless link, it will not work if you leave the page. Which means it cannot be expired.
</p>
<div className="flex gap-2">
<Button className="w-full" onClick={handleCopy}>{copied ? 'Copied' : 'Copy'}</Button>
<Button className="w-full" variant="outline" onClick={handleReset}>Generate New Link</Button>
</div>
</div> : (
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between gap-1">
<div className="flex flex-col gap-1">
<Label className="text-sm m-0">Show People</Label>
<p className="text-xs text-muted-foreground m-0">
Show the list of people in the shared photos
</p>
</div>
<Switch checked={config.p} onCheckedChange={(checked) => setConfig({ ...config, p: !!checked })} />
</div>
<div className="flex items-center justify-between gap-1">
<div className="flex flex-col gap-1">
<Label className="text-sm m-0">Link Expires In</Label>
<p className="text-xs text-muted-foreground m-0">
Should the link expire after the selected time
</p>
</div>
<Select onValueChange={(value) => setConfig({ ...config, expiresIn: value })}>
<SelectTrigger className='w-fit'>
<SelectValue placeholder="Select duration" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1h">1 Hour</SelectItem>
<SelectItem value="1d">1 Day (24 Hours)</SelectItem>
<SelectItem value="7d">7 Days</SelectItem>
<SelectItem value="30d">30 Days</SelectItem>
<SelectItem value="90d">90 Days</SelectItem>
<SelectItem value="never">Never Expires</SelectItem>
</SelectContent>
</Select>
</div>
<Button onClick={handleGenerate} disabled={loading || !endpointConfigured || !shareKeyExists}>Generate Share Link</Button>
</div>
)}
</DialogContent>
</Dialog>
)
}