360 lines
12 KiB
TypeScript
360 lines
12 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>
|
|
);
|
|
}
|