Files

303 lines
18 KiB
TypeScript

'use client';
import { Fragment, useState } from 'react';
import { Dialog, Transition, Tab } from '@headlessui/react';
import {
XMarkIcon,
BuildingOfficeIcon,
MapPinIcon,
UserIcon,
CheckCircleIcon
} from '@heroicons/react/24/outline';
interface CreateAgencyModalProps {
isOpen: boolean;
onClose: () => void;
onSuccess: () => void;
}
function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ');
}
export default function CreateAgencyModal({ isOpen, onClose, onSuccess }: CreateAgencyModalProps) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [formData, setFormData] = useState({
// Agência
agencyName: '',
subdomain: '',
cnpj: '',
razaoSocial: '',
description: '',
website: '',
industry: '',
phone: '',
teamSize: '',
// Endereço
cep: '',
state: '',
city: '',
neighborhood: '',
street: '',
number: '',
complement: '',
// Admin
adminEmail: '',
adminPassword: '',
adminName: '',
});
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
setFormData(prev => ({
...prev,
[e.target.name]: e.target.value
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
try {
const response = await fetch('/api/admin/agencies/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`,
},
body: JSON.stringify(formData),
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData || 'Erro ao criar agência');
}
onSuccess();
onClose();
// Reset form?
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const tabs = [
{ name: 'Dados Gerais', icon: BuildingOfficeIcon },
{ name: 'Endereço', icon: MapPinIcon },
{ name: 'Administrador', icon: UserIcon },
];
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={onClose}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-zinc-900/40 backdrop-blur-sm transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-2xl bg-white dark:bg-zinc-900 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-3xl border border-zinc-200 dark:border-zinc-800">
<div className="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
<button
type="button"
className="rounded-md bg-white dark:bg-zinc-900 text-zinc-400 hover:text-zinc-500 focus:outline-none"
onClick={onClose}
>
<span className="sr-only">Fechar</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="p-6 sm:p-8">
<div className="sm:flex sm:items-start mb-6">
<div className="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-zinc-100 dark:bg-zinc-800 sm:mx-0 sm:h-10 sm:w-10">
<BuildingOfficeIcon className="h-6 w-6 text-[var(--brand-color)]" aria-hidden="true" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<Dialog.Title as="h3" className="text-xl font-semibold leading-6 text-zinc-900 dark:text-white">
Nova Agência
</Dialog.Title>
<div className="mt-2">
<p className="text-sm text-zinc-500 dark:text-zinc-400">
Preencha os dados abaixo para cadastrar uma nova agência parceira.
</p>
</div>
</div>
</div>
{error && (
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg text-sm text-red-800 dark:text-red-300">
{error}
</div>
)}
<form onSubmit={handleSubmit}>
<Tab.Group>
<Tab.List className="flex space-x-1 rounded-xl bg-zinc-100 dark:bg-zinc-800/50 p-1 mb-6">
{tabs.map((tab) => (
<Tab
key={tab.name}
className={({ selected }) =>
classNames(
'w-full rounded-lg py-2.5 text-sm font-medium leading-5 transition-all duration-200',
'ring-white ring-opacity-60 ring-offset-2 ring-offset-[var(--brand-color)] focus:outline-none focus:ring-2',
selected
? 'bg-white dark:bg-zinc-800 text-[var(--brand-color)] shadow'
: 'text-zinc-500 hover:bg-white/[0.12] hover:text-zinc-700 dark:hover:text-zinc-300'
)
}
>
<div className="flex items-center justify-center gap-2">
<tab.icon className="w-4 h-4" />
{tab.name}
</div>
</Tab>
))}
</Tab.List>
<Tab.Panels>
{/* Dados Gerais */}
<Tab.Panel className="space-y-4 focus:outline-none">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input label="Nome da Agência *" name="agencyName" value={formData.agencyName} onChange={handleChange} required />
<Input label="Subdomínio *" name="subdomain" value={formData.subdomain} onChange={handleChange} required prefix="http://" suffix=".aggios.app" />
<Input label="CNPJ" name="cnpj" value={formData.cnpj} onChange={handleChange} />
<Input label="Razão Social" name="razaoSocial" value={formData.razaoSocial} onChange={handleChange} />
<Input label="Telefone" name="phone" value={formData.phone} onChange={handleChange} />
<Input label="Website" name="website" value={formData.website} onChange={handleChange} />
<div className="col-span-2">
<label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">Descrição</label>
<textarea
name="description"
rows={3}
className="w-full rounded-lg border border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800/50 px-3 py-2 text-sm text-zinc-900 dark:text-white focus:border-[var(--brand-color)] focus:ring-1 focus:ring-[var(--brand-color)] outline-none transition-all"
value={formData.description}
onChange={handleChange}
/>
</div>
</div>
</Tab.Panel>
{/* Endereço */}
<Tab.Panel className="space-y-4 focus:outline-none">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Input label="CEP" name="cep" value={formData.cep} onChange={handleChange} />
<div className="grid grid-cols-2 gap-4">
<Input label="Estado" name="state" value={formData.state} onChange={handleChange} />
<Input label="Cidade" name="city" value={formData.city} onChange={handleChange} />
</div>
<Input label="Bairro" name="neighborhood" value={formData.neighborhood} onChange={handleChange} />
<Input label="Rua" name="street" value={formData.street} onChange={handleChange} />
<div className="grid grid-cols-2 gap-4">
<Input label="Número" name="number" value={formData.number} onChange={handleChange} />
<Input label="Complemento" name="complement" value={formData.complement} onChange={handleChange} />
</div>
</div>
</Tab.Panel>
{/* Administrador */}
<Tab.Panel className="space-y-4 focus:outline-none">
<div className="bg-zinc-50 dark:bg-zinc-800/50 p-4 rounded-lg mb-4 border border-zinc-100 dark:border-zinc-800">
<p className="text-sm text-zinc-600 dark:text-zinc-400 flex items-center gap-2">
<UserIcon className="w-4 h-4 text-[var(--brand-color)]" />
Este usuário será o administrador principal da agência.
</p>
</div>
<div className="grid grid-cols-1 gap-4">
<Input label="Nome Completo *" name="adminName" value={formData.adminName} onChange={handleChange} required />
<Input label="E-mail *" name="adminEmail" type="email" value={formData.adminEmail} onChange={handleChange} required />
<Input label="Senha *" name="adminPassword" type="password" value={formData.adminPassword} onChange={handleChange} required />
</div>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
<div className="mt-8 flex items-center justify-end gap-3 border-t border-zinc-100 dark:border-zinc-800 pt-6">
<button
type="button"
className="rounded-lg px-4 py-2 text-sm font-medium text-zinc-700 dark:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 transition-colors"
onClick={onClose}
>
Cancelar
</button>
<button
type="submit"
disabled={loading}
className="inline-flex justify-center rounded-lg px-4 py-2 text-sm font-medium text-white shadow-sm hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-[var(--brand-color)] focus:ring-offset-2 disabled:opacity-50 transition-all"
style={{ background: 'var(--gradient)' }}
>
{loading ? 'Criando...' : 'Criar Agência'}
</button>
</div>
</form>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
label: string;
prefix?: string;
suffix?: string;
}
function Input({ label, prefix, suffix, className, ...props }: InputProps) {
return (
<div>
<label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-1">
{label}
</label>
<div className="relative flex rounded-lg shadow-sm">
{prefix && (
<span className="inline-flex items-center rounded-l-lg border border-r-0 border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 px-3 text-zinc-500 sm:text-sm">
{prefix}
</span>
)}
<input
className={classNames(
"block w-full border-zinc-200 dark:border-zinc-700 bg-white dark:bg-zinc-800/50 text-zinc-900 dark:text-white focus:border-[var(--brand-color)] focus:ring-1 focus:ring-[var(--brand-color)] sm:text-sm outline-none transition-all py-2 px-3",
prefix ? "rounded-none" : "rounded-l-lg",
suffix ? "rounded-none" : "rounded-r-lg",
!prefix && !suffix ? "rounded-lg" : "",
className || ""
)}
{...props}
/>
{suffix && (
<span className="inline-flex items-center rounded-r-lg border border-l-0 border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800 px-3 text-zinc-500 sm:text-sm">
{suffix}
</span>
)}
</div>
</div>
);
}