ultisuite-client/components/gmail/settings/automation/api-token-permission-editor.tsx
R3D347HR4Y 6ec95262af Add OnlyOffice integration and update project configurations
- 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.
2026-06-07 15:49:21 +02:00

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