import { useState } from 'react'; import { Link } from 'react-router'; import { Copy, ExternalLink, ImageOff, Link2, MoreVertical, Plus } from 'lucide-react'; import { toast } from 'sonner'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { useClient } from 'hooks/useClient'; import { copyToClipboard } from 'helpers/clipboard'; import type { SharedLink } from './shared'; import { ALBUM_PARAM, formatShortDate, photosSectionPath, shareUrl, thumbUrl } from './shared'; import { ShareLinkDialog, type ShareTarget } from './ShareLinkDialog'; import { useAlbums, useServerConfig, useSharedLinks } from './usePhotosData'; import { useAssetActions } from './useAssetActions'; // Sharing: shared albums, and the links that make them public. // // The link column shows a full URL, not a key. The address is Immich's own `externalDomain` read from // `/server/config` — this plugin still stores no public address of its own, which is what the previous // version of this file was protecting when it copied a bare key instead. The protection was never the // point; the missing address was, and Immich already knows it. export const SharingSection = () => { const { token } = useClient(); const { data: links, isLoading } = useSharedLinks(); const { data: albums } = useAlbums(); const { data: config } = useServerConfig(); const { deleteSharedLink } = useAssetActions(); const [target, setTarget] = useState(null); const [editing, setEditing] = useState(null); const [open, setOpen] = useState(false); const sharedAlbums = (albums ?? []).filter((album) => album.shared || album.hasSharedLink); const share = (next: ShareTarget) => { setEditing(null); setTarget(next); setOpen(true); }; const edit = (link: SharedLink) => { setTarget(null); setEditing(link); setOpen(true); }; return (

Sharing

{albums?.map((album) => ( share({ kind: 'album', albumId: album.id, albumName: album.albumName })} > {album.albumName} ))}

Shared albums

{sharedAlbums.length === 0 ? (

No shared albums.

) : (
{sharedAlbums.map((album) => (
{album.albumThumbnailAssetId ? ( ) : (
)}

{album.albumName}

{album.assetCount} items · {album.albumUsers.length} people

))}
)}

Shared links

{isLoading &&

Loading…

} {!isLoading && (links ?? []).length === 0 && (

No shared links.

)} {!isLoading && (links ?? []).length > 0 && !config?.externalDomain && ( /* Worth saying once, at the top, rather than per row: every link here works, and none of them can be written as a URL until Immich knows the address it answers on. */

Immich has no External Domain set, so these show as keys rather than URLs. Set it in Immich’s admin settings.

)} {(links ?? []).length > 0 && (
    {(links ?? []).map((link) => ( edit(link)} onRevoke={() => deleteSharedLink.mutate(link.id)} /> ))}
)}
); }; const LinkRow = ({ link, externalDomain, onEdit, onRevoke, }: { link: SharedLink; externalDomain: string | undefined; onEdit: () => void; onRevoke: () => void; }) => { const url = shareUrl(externalDomain, link); const expired = !!link.expiresAt && new Date(link.expiresAt).getTime() < Date.now(); return (
  • {link.album?.albumName ?? link.description ?? `${link.assets.length} items`}

    {link.expiresAt ? `${expired ? 'Expired' : 'Expires'} ${formatShortDate(link.expiresAt)}` : 'No expiry'} {link.password ? ' · password' : ''} {link.allowDownload ? ' · download' : ''} {link.allowUpload ? ' · upload' : ''} {link.showMetadata ? '' : ' · no metadata'}

    {url && ( )} Edit window.confirm('Revoke this link? Anyone holding it loses access.') && onRevoke()} > Revoke
  • ); };