xpeditis2.0/apps/frontend/app/dashboard/settings/organization/page.tsx
2025-11-04 07:30:15 +01:00

306 lines
11 KiB
TypeScript

/**
* Organization Settings Page
*
* Manage organization details
*/
'use client';
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { organizationsApi } from '@/lib/api';
export default function OrganizationSettingsPage() {
const queryClient = useQueryClient();
const [isEditing, setIsEditing] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
const { data: organization, isLoading } = useQuery({
queryKey: ['organization', 'current'],
queryFn: () => organizationsApi.getCurrent(),
});
const [formData, setFormData] = useState({
name: '',
contactEmail: '',
contactPhone: '',
address: {
street: '',
city: '',
postalCode: '',
country: '',
},
});
const updateMutation = useMutation({
mutationFn: (data: typeof formData) => organizationsApi.update(organization?.id || '', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['organization'] });
setSuccess('Organization updated successfully');
setIsEditing(false);
setTimeout(() => setSuccess(''), 3000);
},
onError: (err: any) => {
setError(err.response?.data?.message || 'Failed to update organization');
},
});
const handleEdit = () => {
if (organization) {
setFormData({
name: organization.name,
contactEmail: organization.contactEmail,
contactPhone: organization.contactPhone,
address: organization.address,
});
setIsEditing(true);
setError('');
setSuccess('');
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
updateMutation.mutate(formData);
};
const handleCancel = () => {
setIsEditing(false);
setError('');
};
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
if (!organization) {
return (
<div className="text-center py-12">
<h2 className="text-2xl font-semibold text-gray-900">Organization not found</h2>
</div>
);
}
return (
<div className="max-w-4xl space-y-6">
<div>
<h1 className="text-2xl font-bold text-gray-900">Organization Settings</h1>
<p className="text-sm text-gray-500 mt-1">Manage your organization information</p>
</div>
{success && (
<div className="rounded-md bg-green-50 p-4">
<div className="text-sm text-green-800">{success}</div>
</div>
)}
{error && (
<div className="rounded-md bg-red-50 p-4">
<div className="text-sm text-red-800">{error}</div>
</div>
)}
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-4 border-b flex items-center justify-between">
<h2 className="text-lg font-semibold text-gray-900">Organization Details</h2>
{!isEditing && (
<button
onClick={handleEdit}
className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
>
<svg className="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
Edit
</button>
)}
</div>
<form onSubmit={handleSubmit} className="p-6">
<div className="space-y-6">
{/* Basic Info */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label className="block text-sm font-medium text-gray-700">Organization Name</label>
{isEditing ? (
<input
type="text"
value={formData.name}
onChange={e => setFormData({ ...formData, name: e.target.value })}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border"
required
/>
) : (
<p className="mt-1 text-sm text-gray-900">{organization.name}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Type</label>
<p className="mt-1 text-sm text-gray-900">{organization.type.replace('_', ' ')}</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Contact Email</label>
{isEditing ? (
<input
type="email"
value={formData.contactEmail}
onChange={e => setFormData({ ...formData, contactEmail: e.target.value })}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border"
required
/>
) : (
<p className="mt-1 text-sm text-gray-900">{organization.contactEmail}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Contact Phone</label>
{isEditing ? (
<input
type="tel"
value={formData.contactPhone}
onChange={e => setFormData({ ...formData, contactPhone: e.target.value })}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border"
required
/>
) : (
<p className="mt-1 text-sm text-gray-900">{organization.contactPhone}</p>
)}
</div>
</div>
{/* Address */}
<div>
<h3 className="text-sm font-medium text-gray-900 mb-4">Address</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700">Street</label>
{isEditing ? (
<input
type="text"
value={formData.address.street}
onChange={e =>
setFormData({
...formData,
address: {
...formData.address,
street: e.target.value,
},
})
}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border"
required
/>
) : (
<p className="mt-1 text-sm text-gray-900">{organization.address.street}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700">City</label>
{isEditing ? (
<input
type="text"
value={formData.address.city}
onChange={e =>
setFormData({
...formData,
address: {
...formData.address,
city: e.target.value,
},
})
}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border"
required
/>
) : (
<p className="mt-1 text-sm text-gray-900">{organization.address.city}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Postal Code</label>
{isEditing ? (
<input
type="text"
value={formData.address.postalCode}
onChange={e =>
setFormData({
...formData,
address: {
...formData.address,
postalCode: e.target.value,
},
})
}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border"
required
/>
) : (
<p className="mt-1 text-sm text-gray-900">{organization.address.postalCode}</p>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700">Country</label>
{isEditing ? (
<input
type="text"
value={formData.address.country}
onChange={e =>
setFormData({
...formData,
address: {
...formData.address,
country: e.target.value,
},
})
}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border"
required
/>
) : (
<p className="mt-1 text-sm text-gray-900">{organization.address.country}</p>
)}
</div>
</div>
</div>
{isEditing && (
<div className="flex justify-end space-x-3 pt-6 border-t">
<button
type="button"
onClick={handleCancel}
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
>
Cancel
</button>
<button
type="submit"
disabled={updateMutation.isPending}
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400"
>
{updateMutation.isPending ? 'Saving...' : 'Save Changes'}
</button>
</div>
)}
</div>
</form>
</div>
</div>
);
}