feat: Add Source Serif 4 and Fractul font families and integrate them across the admin UI.
This commit is contained in:
@@ -234,7 +234,7 @@ export default function UserDetailPage() {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-[#f5a623]"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -247,7 +247,7 @@ export default function UserDetailPage() {
|
||||
</div>
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/users')}
|
||||
className="mt-4 px-4 py-2 text-blue-600 hover:text-blue-800"
|
||||
className="mt-4 px-4 py-2 text-[#f5a623] hover:text-[#e09620]"
|
||||
>
|
||||
← Back to Users
|
||||
</button>
|
||||
@@ -258,10 +258,10 @@ export default function UserDetailPage() {
|
||||
if (!user) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<p className="text-gray-500">User not found</p>
|
||||
<p className="text-[#666666] font-serif">User not found</p>
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/users')}
|
||||
className="mt-4 px-4 py-2 text-blue-600 hover:text-blue-800"
|
||||
className="mt-4 px-4 py-2 text-[#f5a623] hover:text-[#e09620]"
|
||||
>
|
||||
← Back to Users
|
||||
</button>
|
||||
@@ -274,7 +274,7 @@ export default function UserDetailPage() {
|
||||
{/* Back Button */}
|
||||
<button
|
||||
onClick={() => router.push('/dashboard/users')}
|
||||
className="mb-6 flex items-center text-gray-600 hover:text-gray-900"
|
||||
className="mb-6 flex items-center text-[#666666] hover:text-[#00293d]"
|
||||
>
|
||||
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||||
@@ -295,7 +295,7 @@ export default function UserDetailPage() {
|
||||
)}
|
||||
|
||||
{/* User Header */}
|
||||
<div className="bg-white rounded-lg shadow mb-6">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-[#e5e7eb] mb-6">
|
||||
<div className="p-6">
|
||||
<div className="flex items-center">
|
||||
{/* Avatar */}
|
||||
@@ -303,8 +303,8 @@ export default function UserDetailPage() {
|
||||
{avatarUrl ? (
|
||||
<img className="h-20 w-20 rounded-full object-cover" src={avatarUrl} alt="" />
|
||||
) : (
|
||||
<div className="h-20 w-20 rounded-full bg-gray-200 flex items-center justify-center">
|
||||
<span className="text-gray-500 font-medium text-2xl">
|
||||
<div className="h-20 w-20 rounded-full bg-[#e8f0ee] flex items-center justify-center">
|
||||
<span className="text-[#666666] font-medium text-2xl">
|
||||
{user.profile?.firstName?.[0] || user.email[0].toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
@@ -313,10 +313,10 @@ export default function UserDetailPage() {
|
||||
|
||||
{/* Name & Email */}
|
||||
<div className="ml-6 flex-1">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
<h1 className="text-2xl font-bold text-[#00293d] font-fractul">
|
||||
{user.profile?.firstName} {user.profile?.lastName}
|
||||
</h1>
|
||||
<p className="text-gray-500">{user.email}</p>
|
||||
<p className="text-[#666666] font-serif">{user.email}</p>
|
||||
</div>
|
||||
|
||||
{/* Role & Status Badges */}
|
||||
@@ -326,8 +326,8 @@ export default function UserDetailPage() {
|
||||
user.role === 'ADMIN'
|
||||
? 'bg-red-100 text-red-800'
|
||||
: user.role === 'AGENT'
|
||||
? 'bg-purple-100 text-purple-800'
|
||||
: 'bg-blue-100 text-blue-800'
|
||||
? 'bg-[#fff7ed] text-[#f5a623]'
|
||||
: 'bg-[#e8f0ee] text-[#5ba4a4]'
|
||||
}`}
|
||||
>
|
||||
{user.role}
|
||||
@@ -349,20 +349,20 @@ export default function UserDetailPage() {
|
||||
</div>
|
||||
|
||||
{/* Basic Information */}
|
||||
<div className="bg-white rounded-lg shadow mb-6">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Basic Information</h2>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-[#e5e7eb] mb-6">
|
||||
<div className="px-6 py-4 border-b border-[#e5e7eb]">
|
||||
<h2 className="text-lg font-semibold text-[#00293d] font-fractul">Basic Information</h2>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Provider</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">Provider</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.authProvider === 'LOCAL' ? 'Email' : user.authProvider}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Email Verified</p>
|
||||
<p className="text-sm text-[#666666]">Email Verified</p>
|
||||
<div className="flex items-center">
|
||||
{user.emailVerified ? (
|
||||
<>
|
||||
@@ -377,25 +377,25 @@ export default function UserDetailPage() {
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-5 h-5 text-gray-400 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<svg className="w-5 h-5 text-[#9ca3af] mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-sm font-medium text-gray-500">Not Verified</span>
|
||||
<span className="text-sm font-medium text-[#666666]">Not Verified</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Joined</p>
|
||||
<p className="text-sm font-medium text-gray-900">{formatDate(user.createdAt)}</p>
|
||||
<p className="text-sm text-[#666666]">Joined</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">{formatDate(user.createdAt)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Last Login</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">Last Login</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.lastLoginAt ? formatDate(user.lastLoginAt) : 'Never'}
|
||||
</p>
|
||||
</div>
|
||||
@@ -404,33 +404,33 @@ export default function UserDetailPage() {
|
||||
</div>
|
||||
|
||||
{/* Contact Information */}
|
||||
<div className="bg-white rounded-lg shadow mb-6">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Contact Information</h2>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-[#e5e7eb] mb-6">
|
||||
<div className="px-6 py-4 border-b border-[#e5e7eb]">
|
||||
<h2 className="text-lg font-semibold text-[#00293d] font-fractul">Contact Information</h2>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Phone</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">Phone</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.profile?.phone || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">City</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">City</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.profile?.city || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">State</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">State</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.profile?.state || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Country</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">Country</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.profile?.country || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
@@ -440,21 +440,21 @@ export default function UserDetailPage() {
|
||||
|
||||
{/* Agent Details (only for AGENT role) */}
|
||||
{user.role === 'AGENT' && user.agentProfile && (
|
||||
<div className="bg-white rounded-lg shadow mb-6">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Agent Details</h2>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-[#e5e7eb] mb-6">
|
||||
<div className="px-6 py-4 border-b border-[#e5e7eb]">
|
||||
<h2 className="text-lg font-semibold text-[#00293d] font-fractul">Agent Details</h2>
|
||||
</div>
|
||||
<div className="p-6">
|
||||
{/* Agent Details Grid */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
{/* Agent Type - Editable */}
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 mb-1">Agent Type</p>
|
||||
<p className="text-sm text-[#666666] mb-1">Agent Type</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<select
|
||||
value={selectedAgentTypeId}
|
||||
onChange={(e) => setSelectedAgentTypeId(e.target.value)}
|
||||
className="w-full max-w-[160px] px-2 py-1 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-blue-500 focus:border-blue-500 text-gray-900 bg-white"
|
||||
className="w-full max-w-[160px] px-2 py-1 text-sm border border-[#e5e7eb] rounded-lg focus:ring-1 focus:ring-[#f5a623] focus:border-[#f5a623] text-[#00293d] bg-white"
|
||||
>
|
||||
<option value="">Select...</option>
|
||||
{agentTypes.map((type) => (
|
||||
@@ -467,7 +467,7 @@ export default function UserDetailPage() {
|
||||
<button
|
||||
onClick={handleUpdateAgentType}
|
||||
disabled={isUpdatingAgentType}
|
||||
className="px-2 py-1 bg-blue-600 hover:bg-blue-700 text-white text-xs font-medium rounded transition-colors disabled:opacity-50"
|
||||
className="px-2 py-1 bg-[#f5a623] hover:bg-[#e09620] text-white text-xs font-medium rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isUpdatingAgentType ? '...' : 'Save'}
|
||||
</button>
|
||||
@@ -475,32 +475,32 @@ export default function UserDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Slug</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">Slug</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.agentProfile.slug || 'Not set'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Company</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">Company</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.agentProfile.companyName || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">License Number</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">License Number</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.agentProfile.licenseNumber || 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Years of Experience</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">Years of Experience</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.agentProfile.yearsOfExperience ?? 'Not provided'}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">Profile Status</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666]">Profile Status</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.agentProfile.profileCompleteness}%{' '}
|
||||
<span
|
||||
className={`ml-1 inline-flex px-2 py-0.5 text-xs font-semibold rounded-full ${
|
||||
@@ -517,19 +517,19 @@ export default function UserDetailPage() {
|
||||
|
||||
{/* Headline & Bio */}
|
||||
{(user.agentProfile.headline || user.agentProfile.bio) && (
|
||||
<div className="mt-6 pt-6 border-t border-gray-200">
|
||||
<div className="mt-6 pt-6 border-t border-[#e5e7eb]">
|
||||
{user.agentProfile.headline && (
|
||||
<div className="mb-4">
|
||||
<p className="text-sm text-gray-500 mb-1">Headline</p>
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
<p className="text-sm text-[#666666] mb-1">Headline</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">
|
||||
{user.agentProfile.headline}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{user.agentProfile.bio && (
|
||||
<div>
|
||||
<p className="text-sm text-gray-500 mb-1">Bio</p>
|
||||
<p className="text-sm text-gray-700 whitespace-pre-wrap">
|
||||
<p className="text-sm text-[#666666] mb-1">Bio</p>
|
||||
<p className="text-sm text-[#666666] whitespace-pre-wrap">
|
||||
{user.agentProfile.bio}
|
||||
</p>
|
||||
</div>
|
||||
@@ -542,10 +542,10 @@ export default function UserDetailPage() {
|
||||
|
||||
{/* Verification Section (only for AGENT role) */}
|
||||
{user.role === 'AGENT' && user.agentProfile && (
|
||||
<div className="bg-white rounded-lg shadow mb-6">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<div className="bg-white rounded-xl shadow-sm border border-[#e5e7eb] mb-6">
|
||||
<div className="px-6 py-4 border-b border-[#e5e7eb]">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Verification Status</h2>
|
||||
<h2 className="text-lg font-semibold text-[#00293d] font-fractul">Verification Status</h2>
|
||||
{getVerificationStatusBadge(user.agentProfile.verificationStatus as VerificationStatus)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -568,21 +568,21 @@ export default function UserDetailPage() {
|
||||
|
||||
{/* Uploaded Documents */}
|
||||
<div className="mb-6">
|
||||
<h3 className="text-sm font-medium text-gray-900 mb-3">Uploaded Documents</h3>
|
||||
<h3 className="text-sm font-medium text-[#00293d] mb-3">Uploaded Documents</h3>
|
||||
{isLoadingDocuments ? (
|
||||
<div className="flex items-center justify-center py-4">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-[#f5a623]"></div>
|
||||
</div>
|
||||
) : verificationDocuments.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{verificationDocuments.map((doc, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between p-3 bg-gray-50 border border-gray-200 rounded-lg"
|
||||
className="flex items-center justify-between p-3 bg-[#f5f9f8] border border-[#e5e7eb] rounded-xl"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<svg
|
||||
className="w-8 h-8 text-gray-400 mr-3"
|
||||
className="w-8 h-8 text-[#9ca3af] mr-3"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -595,8 +595,8 @@ export default function UserDetailPage() {
|
||||
/>
|
||||
</svg>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">{doc.name}</p>
|
||||
<p className="text-xs text-gray-500">{formatFileSize(doc.size)}</p>
|
||||
<p className="text-sm font-medium text-[#00293d]">{doc.name}</p>
|
||||
<p className="text-xs text-[#666666]">{formatFileSize(doc.size)}</p>
|
||||
</div>
|
||||
</div>
|
||||
{doc.url && (
|
||||
@@ -604,7 +604,7 @@ export default function UserDetailPage() {
|
||||
href={doc.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-3 py-1 text-sm text-blue-600 hover:text-blue-800 hover:bg-blue-50 rounded transition-colors"
|
||||
className="px-3 py-1 text-sm text-[#f5a623] hover:text-[#e09620] hover:bg-[#fff7ed] rounded transition-colors"
|
||||
>
|
||||
Download
|
||||
</a>
|
||||
@@ -613,7 +613,7 @@ export default function UserDetailPage() {
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500 py-4 text-center">
|
||||
<p className="text-sm text-[#666666] py-4 text-center">
|
||||
No documents uploaded yet
|
||||
</p>
|
||||
)}
|
||||
@@ -621,19 +621,19 @@ export default function UserDetailPage() {
|
||||
|
||||
{/* Admin Actions */}
|
||||
{user.agentProfile.verificationStatus !== 'APPROVED' && (
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<h3 className="text-sm font-medium text-gray-900 mb-3">Admin Actions</h3>
|
||||
<div className="border-t border-[#e5e7eb] pt-6">
|
||||
<h3 className="text-sm font-medium text-[#00293d] mb-3">Admin Actions</h3>
|
||||
|
||||
{/* Note Input */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm text-gray-500 mb-1">
|
||||
<label className="block text-sm text-[#666666] mb-1">
|
||||
Note (optional, required for rejection)
|
||||
</label>
|
||||
<textarea
|
||||
value={verificationNote}
|
||||
onChange={(e) => setVerificationNote(e.target.value)}
|
||||
rows={2}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-900 bg-white placeholder:text-gray-500"
|
||||
className="w-full px-3 py-2 border border-[#e5e7eb] rounded-xl focus:ring-2 focus:ring-[#f5a623] focus:border-[#f5a623] text-[#00293d] bg-white placeholder:text-[#666666]"
|
||||
placeholder="Enter a note for the agent (e.g., rejection reason)"
|
||||
/>
|
||||
</div>
|
||||
@@ -736,7 +736,7 @@ export default function UserDetailPage() {
|
||||
</button>
|
||||
</div>
|
||||
{!verificationNote.trim() && (
|
||||
<p className="mt-2 text-xs text-gray-500">
|
||||
<p className="mt-2 text-xs text-[#666666]">
|
||||
* A note is required to reject verification
|
||||
</p>
|
||||
)}
|
||||
@@ -750,7 +750,7 @@ export default function UserDetailPage() {
|
||||
|
||||
{/* Already Approved Message */}
|
||||
{user.agentProfile.verificationStatus === 'APPROVED' && (
|
||||
<div className="border-t border-gray-200 pt-6">
|
||||
<div className="border-t border-[#e5e7eb] pt-6">
|
||||
<div className="flex items-center text-green-600">
|
||||
<svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
|
||||
Reference in New Issue
Block a user