147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
"use client"
|
|
|
|
import { useParams, useRouter } from "next/navigation"
|
|
import { useEffect, useState } from "react"
|
|
import { Lock } from "lucide-react"
|
|
import {
|
|
PublicShareChrome,
|
|
PublicShareViewPanel,
|
|
} from "@/components/drive/public-share-view"
|
|
import { DocsLoadingSplash } from "@/components/drive/richtext/docs-loading-splash"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { usePublicShare } from "@/lib/api/hooks/use-public-share-queries"
|
|
import { folderPathFromPublicSegments } from "@/lib/api/public-share"
|
|
import {
|
|
shouldOpenInOnlyOffice,
|
|
shouldOpenInRichTextEditor,
|
|
shouldOpenInUltidrawEditor,
|
|
} from "@/lib/drive/drive-preview"
|
|
import { sharePermCanEdit } from "@/lib/drive/drive-share-permissions"
|
|
import { buildPublicShareEditHref } from "@/lib/drive/public-share-url"
|
|
import { sharePathLooksLikeEditorFile } from "@/lib/drive/share-path-looks-like-editor"
|
|
|
|
export default function PublicSharePage() {
|
|
const params = useParams()
|
|
const router = useRouter()
|
|
const token = String(params.token ?? "")
|
|
const pathSegments = params.path as string[] | undefined
|
|
const path = folderPathFromPublicSegments(pathSegments)
|
|
const pathHintsEditor = sharePathLooksLikeEditorFile(path)
|
|
|
|
const [passwordInput, setPasswordInput] = useState("")
|
|
const [password, setPassword] = useState<string | undefined>(undefined)
|
|
const [redirectingToEditor, setRedirectingToEditor] = useState(false)
|
|
|
|
const { data, isLoading, isError, error, refetch, isFetching } = usePublicShare(
|
|
token,
|
|
path,
|
|
password
|
|
)
|
|
|
|
const needsPassword =
|
|
isError && error instanceof Error && error.message === "password_required"
|
|
|
|
const file = data?.item_type === "file" ? data.file : null
|
|
const isEditorFile = Boolean(
|
|
file &&
|
|
(shouldOpenInRichTextEditor(file) ||
|
|
shouldOpenInUltidrawEditor(file) ||
|
|
shouldOpenInOnlyOffice(file))
|
|
)
|
|
const showDocsSplash =
|
|
pathHintsEditor || redirectingToEditor || (Boolean(data) && isEditorFile)
|
|
|
|
useEffect(() => {
|
|
if (password && typeof window !== "undefined") {
|
|
sessionStorage.setItem(`public-share-pw:${token}`, password)
|
|
}
|
|
}, [password, token])
|
|
|
|
useEffect(() => {
|
|
if (!file || !data || !isEditorFile) return
|
|
setRedirectingToEditor(true)
|
|
const canEdit = sharePermCanEdit(data.permissions ?? 1)
|
|
const returnTo =
|
|
typeof window !== "undefined"
|
|
? window.location.pathname + window.location.search
|
|
: undefined
|
|
const editor = shouldOpenInUltidrawEditor(file)
|
|
? "ultidraw"
|
|
: shouldOpenInRichTextEditor(file)
|
|
? "richtext"
|
|
: "office"
|
|
router.replace(
|
|
buildPublicShareEditHref(
|
|
token,
|
|
file.path,
|
|
returnTo,
|
|
canEdit ? "edit" : "view",
|
|
file.name,
|
|
editor,
|
|
data.item_type
|
|
)
|
|
)
|
|
}, [data, file, isEditorFile, router, token])
|
|
|
|
const submitPassword = (event: React.FormEvent) => {
|
|
event.preventDefault()
|
|
const trimmed = passwordInput.trim()
|
|
if (!trimmed) return
|
|
setPassword(trimmed)
|
|
}
|
|
|
|
if (
|
|
showDocsSplash &&
|
|
!needsPassword &&
|
|
(isLoading || (isFetching && !data) || redirectingToEditor || isEditorFile)
|
|
) {
|
|
const splashTitle = file?.name ?? path.split("/").filter(Boolean).pop()
|
|
return <DocsLoadingSplash phase="opening" title={splashTitle || undefined} />
|
|
}
|
|
|
|
return (
|
|
<PublicShareChrome>
|
|
{isLoading || (isFetching && !data) ? (
|
|
<DocsLoadingSplash phase="opening" />
|
|
) : needsPassword ? (
|
|
<div className="mx-auto flex min-h-[40vh] max-w-md flex-col justify-center px-4 py-12">
|
|
<div className="rounded-2xl border border-border bg-mail-surface p-6 shadow-sm">
|
|
<div className="mb-4 flex items-center gap-2 text-[#3c4043] dark:text-[#e8eaed]">
|
|
<Lock className="h-5 w-5" />
|
|
<h1 className="text-lg font-medium">Lien protégé par mot de passe</h1>
|
|
</div>
|
|
<p className="mb-4 text-sm text-muted-foreground">
|
|
Saisissez le mot de passe pour accéder à ce partage.
|
|
</p>
|
|
<form className="flex flex-col gap-3" onSubmit={submitPassword}>
|
|
<Input
|
|
type="password"
|
|
autoComplete="current-password"
|
|
placeholder="Mot de passe"
|
|
value={passwordInput}
|
|
onChange={(e) => setPasswordInput(e.target.value)}
|
|
/>
|
|
<Button type="submit">Accéder</Button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
) : isError || !data ? (
|
|
<div className="mx-auto max-w-lg px-4 py-16 text-center">
|
|
<h1 className="text-lg font-medium text-[#3c4043] dark:text-[#e8eaed]">
|
|
Partage indisponible
|
|
</h1>
|
|
<p className="mt-2 text-sm text-muted-foreground">
|
|
Ce lien est expiré, révoqué ou incorrect.
|
|
</p>
|
|
<Button type="button" variant="outline" className="mt-6" onClick={() => void refetch()}>
|
|
Réessayer
|
|
</Button>
|
|
</div>
|
|
) : (
|
|
<PublicShareViewPanel token={token} path={path} data={data} password={password} />
|
|
)}
|
|
</PublicShareChrome>
|
|
)
|
|
}
|