ultisuite-client/components/drive/drive-add-mount-dialog.tsx
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- 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.
2026-06-12 19:10:24 +02:00

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>
)
}