303 lines
18 KiB
TypeScript
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>
|
|
);
|
|
}
|