Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced turbopack alias for canvas in next.config.mjs. - Updated package.json scripts for development and branding tasks. - Added new dependencies for Tiptap extensions. - Implemented new demo layouts for agenda, contacts, drive, and mail applications. - Enhanced globals.css for improved theming and splash screen animations. - Added OAuth callback handling for drive mounts. - Updated layout components to integrate new demo shells and improve structure.
210 lines
7.7 KiB
TypeScript
210 lines
7.7 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo, useState } from "react"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import { Button } from "@/components/ui/button"
|
|
import { Input } from "@/components/ui/input"
|
|
import { Label } from "@/components/ui/label"
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select"
|
|
import { useDriveMountMutations } from "@/lib/api/hooks/use-drive-queries"
|
|
import { useCurrentUser } from "@/lib/api/hooks/use-current-user"
|
|
import { driveMountOAuthProvider, buildDriveMountOAuthRedirectURI, isDriveMountOAuthConfigured, openDriveMountOAuthPopup } from "@/lib/drive/drive-mount-oauth"
|
|
|
|
type BackendChoice = "webdav" | "googledrive" | "dropbox" | "onedrive"
|
|
|
|
const OAUTH_BACKENDS: BackendChoice[] = ["googledrive", "dropbox", "onedrive"]
|
|
|
|
export function DriveAddMountDialog({
|
|
open,
|
|
onOpenChange,
|
|
}: {
|
|
open: boolean
|
|
onOpenChange: (open: boolean) => void
|
|
}) {
|
|
const { createMount, fetchOAuthURL } = useDriveMountMutations()
|
|
const { data: user } = useCurrentUser()
|
|
const configuredProviders = user?.org_drive?.configured_mount_oauth_providers ?? []
|
|
const redirectUri = buildDriveMountOAuthRedirectURI()
|
|
|
|
const [displayName, setDisplayName] = useState("")
|
|
const [backend, setBackend] = useState<BackendChoice>("webdav")
|
|
const [host, setHost] = useState("")
|
|
const [root, setRoot] = useState("/")
|
|
const [userName, setUserName] = useState("")
|
|
const [password, setPassword] = useState("")
|
|
const [secure, setSecure] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
const oauthConfigured = useMemo(
|
|
() => isDriveMountOAuthConfigured(backend, configuredProviders),
|
|
[backend, configuredProviders]
|
|
)
|
|
|
|
const reset = () => {
|
|
setDisplayName("")
|
|
setBackend("webdav")
|
|
setHost("")
|
|
setRoot("/")
|
|
setUserName("")
|
|
setPassword("")
|
|
setSecure(true)
|
|
setError(null)
|
|
}
|
|
|
|
const startOAuthConnect = async (mountId: string) => {
|
|
const { oauth_url: oauthUrl } = await fetchOAuthURL(mountId, redirectUri)
|
|
if (!oauthUrl) {
|
|
throw new Error("URL OAuth indisponible")
|
|
}
|
|
openDriveMountOAuthPopup(oauthUrl, mountId)
|
|
}
|
|
|
|
const handleSubmit = async () => {
|
|
setError(null)
|
|
if (!displayName.trim()) return
|
|
if (OAUTH_BACKENDS.includes(backend) && !oauthConfigured) {
|
|
setError("Ce fournisseur cloud n'est pas configuré par l'administration.")
|
|
return
|
|
}
|
|
try {
|
|
if (backend === "webdav") {
|
|
if (!host.trim() || !userName.trim()) return
|
|
await createMount.mutateAsync({
|
|
scope: "user",
|
|
display_name: displayName.trim(),
|
|
backend_type: "webdav",
|
|
webdav: {
|
|
host: host.trim(),
|
|
root: root.trim() || "/",
|
|
user: userName.trim(),
|
|
password,
|
|
secure,
|
|
},
|
|
})
|
|
reset()
|
|
onOpenChange(false)
|
|
return
|
|
}
|
|
|
|
const mount = await createMount.mutateAsync({
|
|
scope: "user",
|
|
display_name: displayName.trim(),
|
|
backend_type: backend,
|
|
oauth_backend: backend,
|
|
})
|
|
reset()
|
|
onOpenChange(false)
|
|
if (mount.needs_oauth || mount.status === "pending_oauth") {
|
|
await startOAuthConnect(mount.id)
|
|
}
|
|
} catch (err) {
|
|
const message = err instanceof Error ? err.message : "Impossible d'ajouter le volume"
|
|
setError(message)
|
|
}
|
|
}
|
|
|
|
const providerLabel = driveMountOAuthProvider(backend)
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onOpenChange={(next) => {
|
|
if (!next) reset()
|
|
onOpenChange(next)
|
|
}}
|
|
>
|
|
<DialogContent className="sm:max-w-md">
|
|
<DialogHeader>
|
|
<DialogTitle>Ajouter un volume monté</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="grid gap-3 py-2">
|
|
<div className="grid gap-1.5">
|
|
<Label htmlFor="mount-name">Nom affiché</Label>
|
|
<Input
|
|
id="mount-name"
|
|
value={displayName}
|
|
onChange={(e) => setDisplayName(e.target.value)}
|
|
placeholder="Mon NAS"
|
|
/>
|
|
</div>
|
|
<div className="grid gap-1.5">
|
|
<Label>Type</Label>
|
|
<Select value={backend} onValueChange={(v) => setBackend(v as BackendChoice)}>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="webdav">WebDAV / NAS / Nextcloud</SelectItem>
|
|
<SelectItem value="googledrive" disabled={!isDriveMountOAuthConfigured("googledrive", configuredProviders)}>
|
|
Google Drive{!isDriveMountOAuthConfigured("googledrive", configuredProviders) ? " (non configuré)" : ""}
|
|
</SelectItem>
|
|
<SelectItem value="dropbox" disabled={!isDriveMountOAuthConfigured("dropbox", configuredProviders)}>
|
|
Dropbox{!isDriveMountOAuthConfigured("dropbox", configuredProviders) ? " (non configuré)" : ""}
|
|
</SelectItem>
|
|
<SelectItem value="onedrive" disabled={!isDriveMountOAuthConfigured("onedrive", configuredProviders)}>
|
|
Microsoft OneDrive{!isDriveMountOAuthConfigured("onedrive", configuredProviders) ? " (non configuré)" : ""}
|
|
</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
{backend === "webdav" ? (
|
|
<>
|
|
<div className="grid gap-1.5">
|
|
<Label htmlFor="mount-host">Hôte</Label>
|
|
<Input id="mount-host" value={host} onChange={(e) => setHost(e.target.value)} placeholder="nas.example.com" />
|
|
</div>
|
|
<div className="grid gap-1.5">
|
|
<Label htmlFor="mount-root">Chemin racine</Label>
|
|
<Input id="mount-root" value={root} onChange={(e) => setRoot(e.target.value)} placeholder="/remote.php/dav/files/user" />
|
|
</div>
|
|
<div className="grid gap-1.5">
|
|
<Label htmlFor="mount-user">Utilisateur</Label>
|
|
<Input id="mount-user" value={userName} onChange={(e) => setUserName(e.target.value)} />
|
|
</div>
|
|
<div className="grid gap-1.5">
|
|
<Label htmlFor="mount-pass">Mot de passe</Label>
|
|
<Input id="mount-pass" type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
|
|
</div>
|
|
<label className="flex cursor-pointer items-center gap-2 text-sm">
|
|
<input type="checkbox" checked={secure} onChange={(e) => setSecure(e.target.checked)} />
|
|
HTTPS
|
|
</label>
|
|
</>
|
|
) : (
|
|
<div className="space-y-2 text-sm text-muted-foreground">
|
|
<p>
|
|
Après création, vous serez invité à vous connecter avec{" "}
|
|
{providerLabel === "google" ? "Google" : providerLabel === "dropbox" ? "Dropbox" : "Microsoft"}.
|
|
</p>
|
|
{redirectUri ? (
|
|
<p className="font-mono text-xs break-all">Redirect URI : {redirectUri}</p>
|
|
) : null}
|
|
</div>
|
|
)}
|
|
{error ? <p className="text-sm text-destructive">{error}</p> : null}
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
Annuler
|
|
</Button>
|
|
<Button onClick={() => void handleSubmit()} disabled={createMount.isPending || (OAUTH_BACKENDS.includes(backend) && !oauthConfigured)}>
|
|
{createMount.isPending ? "Ajout…" : OAUTH_BACKENDS.includes(backend) ? "Ajouter et connecter" : "Ajouter"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
)
|
|
}
|