- Updated .env.example to include configuration for OnlyOffice Document Server. - Modified the workspace configuration to remove the drive-suite path. - Adjusted TypeScript environment imports for consistency. - Enhanced Next.js configuration to disable canvas in Webpack. - Updated package.json to include new dependencies for OnlyOffice and PDF.js. - Added global styles for OnlyOffice theme integration in the CSS. - Created new layout and page components for the Drive feature, including public sharing and editing functionalities. - Updated metadata handling across various layouts to reflect the new app structure.
164 lines
5.6 KiB
TypeScript
164 lines
5.6 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { ChevronRight } from "lucide-react"
|
|
import { Checkbox } from "@/components/ui/checkbox"
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from "@/components/ui/collapsible"
|
|
import { Label } from "@/components/ui/label"
|
|
import {
|
|
API_TOKEN_PERMISSION_GROUPS,
|
|
API_TOKEN_PERMISSIONS,
|
|
type ApiTokenPermissionGrant,
|
|
type ApiTokenPermissionGroup,
|
|
} from "@/lib/mail-automation/api-token-permissions"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
function updateGrant(
|
|
grants: ApiTokenPermissionGrant[],
|
|
resource: string,
|
|
patch: Partial<Pick<ApiTokenPermissionGrant, "read" | "write">>
|
|
): ApiTokenPermissionGrant[] {
|
|
return grants.map((grant) =>
|
|
grant.resource === resource ? { ...grant, ...patch } : grant
|
|
)
|
|
}
|
|
|
|
function countSelectedInGroup(
|
|
group: ApiTokenPermissionGroup,
|
|
grants: ApiTokenPermissionGrant[]
|
|
) {
|
|
const defs = API_TOKEN_PERMISSIONS.filter((def) => def.group === group)
|
|
return defs.filter((def) => {
|
|
const grant = grants.find((g) => g.resource === def.id)
|
|
return grant?.read || grant?.write
|
|
}).length
|
|
}
|
|
|
|
export function ApiTokenPermissionEditor({
|
|
grants,
|
|
onChange,
|
|
className,
|
|
}: {
|
|
grants: ApiTokenPermissionGrant[]
|
|
onChange: (grants: ApiTokenPermissionGrant[]) => void
|
|
className?: string
|
|
}) {
|
|
return (
|
|
<div className={cn("space-y-3", className)}>
|
|
<div>
|
|
<Label className="text-sm font-medium">Permissions</Label>
|
|
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
Développez chaque domaine pour choisir lecture et écriture.
|
|
</p>
|
|
</div>
|
|
{API_TOKEN_PERMISSION_GROUPS.map((group) => (
|
|
<PermissionGroupSection
|
|
key={group.id}
|
|
group={group.id}
|
|
title={group.label}
|
|
description={group.description}
|
|
grants={grants}
|
|
onChange={onChange}
|
|
selectedCount={countSelectedInGroup(group.id, grants)}
|
|
/>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function PermissionGroupSection({
|
|
group,
|
|
title,
|
|
description,
|
|
grants,
|
|
onChange,
|
|
selectedCount,
|
|
}: {
|
|
group: ApiTokenPermissionGroup
|
|
title: string
|
|
description: string
|
|
grants: ApiTokenPermissionGrant[]
|
|
onChange: (grants: ApiTokenPermissionGrant[]) => void
|
|
selectedCount: number
|
|
}) {
|
|
const [open, setOpen] = useState(false)
|
|
const defs = API_TOKEN_PERMISSIONS.filter((def) => def.group === group)
|
|
if (defs.length === 0) return null
|
|
|
|
return (
|
|
<Collapsible open={open} onOpenChange={setOpen}>
|
|
<fieldset className="overflow-hidden rounded-md border border-border">
|
|
<CollapsibleTrigger className="flex w-full items-center gap-2 px-3 py-2.5 text-left hover:bg-muted/40">
|
|
<ChevronRight
|
|
className={cn("size-4 shrink-0 text-muted-foreground transition-transform", open && "rotate-90")}
|
|
/>
|
|
<span className="min-w-0 flex-1">
|
|
<span className="block text-sm font-medium text-foreground">{title}</span>
|
|
<span className="block text-xs text-muted-foreground">{description}</span>
|
|
</span>
|
|
<span className="shrink-0 text-xs text-muted-foreground">
|
|
{selectedCount > 0 ? `${selectedCount} active(s)` : "Aucune"}
|
|
</span>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent>
|
|
<div className="border-t border-border">
|
|
<div className="grid grid-cols-[1fr_4.5rem_4.5rem] gap-2 border-b border-border bg-muted/30 px-3 py-2 text-xs font-medium text-muted-foreground">
|
|
<span>Permission</span>
|
|
<span className="text-center">Lecture</span>
|
|
<span className="text-center">Écriture</span>
|
|
</div>
|
|
{defs.map((def) => {
|
|
const grant = grants.find((g) => g.resource === def.id) ?? {
|
|
resource: def.id,
|
|
read: false,
|
|
write: false,
|
|
}
|
|
return (
|
|
<div
|
|
key={def.id}
|
|
className="grid grid-cols-[1fr_4.5rem_4.5rem] items-start gap-2 border-b border-border px-3 py-2.5 last:border-b-0"
|
|
>
|
|
<div className="min-w-0 pr-2">
|
|
<Label className="text-sm font-medium">{def.label}</Label>
|
|
<p className="mt-0.5 text-xs text-muted-foreground">{def.description}</p>
|
|
</div>
|
|
<div className="flex justify-center pt-0.5">
|
|
{def.supportsRead ? (
|
|
<Checkbox
|
|
checked={grant.read}
|
|
onCheckedChange={(checked) =>
|
|
onChange(updateGrant(grants, def.id, { read: checked === true }))
|
|
}
|
|
aria-label={`${def.label} — lecture`}
|
|
/>
|
|
) : (
|
|
<span className="text-xs text-muted-foreground">—</span>
|
|
)}
|
|
</div>
|
|
<div className="flex justify-center pt-0.5">
|
|
{def.supportsWrite ? (
|
|
<Checkbox
|
|
checked={grant.write}
|
|
onCheckedChange={(checked) =>
|
|
onChange(updateGrant(grants, def.id, { write: checked === true }))
|
|
}
|
|
aria-label={`${def.label} — écriture`}
|
|
/>
|
|
) : (
|
|
<span className="text-xs text-muted-foreground">—</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
</CollapsibleContent>
|
|
</fieldset>
|
|
</Collapsible>
|
|
)
|
|
}
|