224 lines
8.8 KiB
TypeScript
224 lines
8.8 KiB
TypeScript
// frontend\src\components\ui\CookieModal.tsx
|
|
|
|
'use client'
|
|
|
|
import { Dialog } from '@headlessui/react'
|
|
import { useEffect, useState, useRef } from 'react'
|
|
import Button from './Button'
|
|
|
|
export type CookieEntry = { name: string; value: string }
|
|
|
|
type CookieModalProps = {
|
|
open: boolean
|
|
onClose: () => void
|
|
onApply: (cookies: CookieEntry[]) => void
|
|
initialCookies: CookieEntry[]
|
|
}
|
|
|
|
export default function CookieModal({
|
|
open,
|
|
onClose,
|
|
onApply,
|
|
initialCookies,
|
|
}: CookieModalProps) {
|
|
const [name, setName] = useState('')
|
|
const [value, setValue] = useState('')
|
|
const [cookies, setCookies] = useState<CookieEntry[]>([])
|
|
const wasOpen = useRef(false)
|
|
|
|
useEffect(() => {
|
|
if (open && !wasOpen.current) {
|
|
setName('')
|
|
setValue('')
|
|
setCookies(initialCookies ?? [])
|
|
}
|
|
wasOpen.current = open
|
|
}, [open, initialCookies])
|
|
|
|
function addCookie() {
|
|
const n = name.trim()
|
|
const v = value.trim()
|
|
if (!n || !v) return
|
|
|
|
setCookies((prev) => {
|
|
const filtered = prev.filter((c) => c.name !== n)
|
|
return [...filtered, { name: n, value: v }]
|
|
})
|
|
|
|
setName('')
|
|
setValue('')
|
|
}
|
|
|
|
function removeCookie(n: string) {
|
|
setCookies((prev) => prev.filter((c) => c.name !== n))
|
|
}
|
|
|
|
function applyAndClose() {
|
|
onApply(cookies)
|
|
onClose()
|
|
}
|
|
|
|
return (
|
|
<Dialog open={open} onClose={onClose} className="relative z-[100]">
|
|
<div className="fixed inset-0 bg-black/40 backdrop-blur-[2px]" aria-hidden="true" />
|
|
|
|
<div className="fixed inset-0 flex items-center justify-center p-4">
|
|
<Dialog.Panel
|
|
className="
|
|
w-full max-w-2xl rounded-xl
|
|
border border-gray-200/80 bg-white shadow-xl
|
|
dark:border-white/10 dark:bg-gray-800 dark:outline dark:-outline-offset-1 dark:outline-white/10
|
|
"
|
|
>
|
|
<div className="border-b border-gray-200/70 px-6 py-4 dark:border-white/10">
|
|
<Dialog.Title className="text-base font-semibold text-gray-900 dark:text-white">
|
|
Zusätzliche Cookies
|
|
</Dialog.Title>
|
|
<p className="mt-1 text-sm text-gray-600 dark:text-gray-300">
|
|
Füge zusätzliche Cookies hinzu oder aktualisiere bestehende Werte.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="space-y-4 px-6 py-5">
|
|
<div className="rounded-lg border border-gray-200/70 bg-gray-50/70 p-3 dark:border-white/10 dark:bg-white/5">
|
|
<div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
|
<div className="sm:col-span-1">
|
|
<label className="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
Name
|
|
</label>
|
|
<input
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="z. B. cf_clearance"
|
|
className="
|
|
w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm
|
|
placeholder:text-gray-400
|
|
focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20
|
|
dark:border-white/10 dark:bg-white/10 dark:text-white dark:placeholder:text-gray-400
|
|
dark:focus:border-indigo-400 dark:focus:ring-indigo-400/20
|
|
"
|
|
/>
|
|
</div>
|
|
|
|
<div className="sm:col-span-2">
|
|
<label className="mb-1 block text-xs font-medium text-gray-700 dark:text-gray-300">
|
|
Wert
|
|
</label>
|
|
<input
|
|
value={value}
|
|
onChange={(e) => setValue(e.target.value)}
|
|
placeholder="Cookie-Wert"
|
|
className="
|
|
w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm
|
|
placeholder:text-gray-400
|
|
focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500/20
|
|
dark:border-white/10 dark:bg-white/10 dark:text-white dark:placeholder:text-gray-400
|
|
dark:focus:border-indigo-400 dark:focus:ring-indigo-400/20
|
|
"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-3 flex justify-end">
|
|
<Button
|
|
size="sm"
|
|
variant="secondary"
|
|
onClick={addCookie}
|
|
disabled={!name.trim() || !value.trim()}
|
|
>
|
|
Hinzufügen
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-lg border border-gray-200/70 bg-white/70 shadow-sm dark:border-white/10 dark:bg-white/5">
|
|
<div className="border-b border-gray-200/70 px-4 py-3 dark:border-white/10">
|
|
<div className="text-sm font-semibold text-gray-900 dark:text-white">
|
|
Aktuelle Cookies
|
|
</div>
|
|
<div className="mt-0.5 text-xs text-gray-600 dark:text-gray-300">
|
|
{cookies.length} Eintrag{cookies.length === 1 ? '' : 'e'}
|
|
</div>
|
|
</div>
|
|
|
|
{cookies.length === 0 ? (
|
|
<div className="px-4 py-6 text-sm text-gray-500 dark:text-gray-400">
|
|
Noch keine Cookies hinzugefügt.
|
|
</div>
|
|
) : (
|
|
<div className="overflow-hidden">
|
|
<table className="w-full table-fixed text-sm">
|
|
<thead className="bg-gray-50 text-gray-700 dark:bg-white/5 dark:text-gray-200">
|
|
<tr className="border-b border-gray-200/70 dark:border-white/10">
|
|
<th className="w-[180px] px-4 py-2.5 text-left font-medium">Name</th>
|
|
<th className="w-[calc(100%-290px)] px-4 py-2.5 text-left font-medium">Wert</th>
|
|
<th className="w-[110px] px-4 py-2.5 text-right font-medium">Aktion</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<tbody className="divide-y divide-gray-200/70 dark:divide-white/10">
|
|
{cookies.map((c) => (
|
|
<tr
|
|
key={c.name}
|
|
className="bg-white hover:bg-gray-50/70 dark:bg-transparent dark:hover:bg-white/5"
|
|
>
|
|
<td className="px-4 py-3 align-top">
|
|
<div
|
|
className="
|
|
inline-flex max-w-full items-center rounded-md
|
|
border border-gray-200 bg-gray-50 px-3 py-2
|
|
font-mono text-xs text-gray-800
|
|
dark:border-white/10 dark:bg-white/5 dark:text-gray-100
|
|
"
|
|
title={c.name}
|
|
>
|
|
<span className="truncate">{c.name}</span>
|
|
</div>
|
|
</td>
|
|
|
|
<td className="px-4 py-3 align-top">
|
|
<div
|
|
className="
|
|
rounded-md border border-gray-200 bg-gray-50 px-3 py-2
|
|
font-mono text-xs text-gray-700
|
|
dark:border-white/10 dark:bg-black/10 dark:text-gray-200
|
|
"
|
|
title={c.value}
|
|
>
|
|
<span className="block truncate">{c.value}</span>
|
|
</div>
|
|
</td>
|
|
|
|
<td className="w-[110px] px-4 py-3 text-right align-top whitespace-nowrap">
|
|
<Button
|
|
type="button"
|
|
size="sm"
|
|
variant="secondary"
|
|
onClick={() => removeCookie(c.name)}
|
|
className="text-red-700 dark:text-red-300"
|
|
>
|
|
Entfernen
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-end gap-2 border-t border-gray-200/70 px-6 py-4 dark:border-white/10">
|
|
<Button variant="secondary" onClick={onClose}>
|
|
Abbrechen
|
|
</Button>
|
|
<Button variant="primary" onClick={applyAndClose}>
|
|
Übernehmen
|
|
</Button>
|
|
</div>
|
|
</Dialog.Panel>
|
|
</div>
|
|
</Dialog>
|
|
)
|
|
} |