feat: contract + referral adjustments
This commit is contained in:
parent
ac358d4d7d
commit
0a8c570610
@ -15,7 +15,8 @@ export default function ContractEditor({ onSaved }: Props) {
|
|||||||
const [statusMsg, setStatusMsg] = useState<string | null>(null);
|
const [statusMsg, setStatusMsg] = useState<string | null>(null);
|
||||||
|
|
||||||
const [lang, setLang] = useState<'en' | 'de'>('en');
|
const [lang, setLang] = useState<'en' | 'de'>('en');
|
||||||
const [type, setType] = useState<string>('contract');
|
const [type, setType] = useState<'contract' | 'bill' | 'other'>('contract');
|
||||||
|
const [userType, setUserType] = useState<'personal' | 'company' | 'both'>('personal');
|
||||||
const [description, setDescription] = useState<string>('');
|
const [description, setDescription] = useState<string>('');
|
||||||
|
|
||||||
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
||||||
@ -94,10 +95,21 @@ export default function ContractEditor({ onSaved }: Props) {
|
|||||||
const slug = (s: string) =>
|
const slug = (s: string) =>
|
||||||
s.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'template';
|
s.toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'template';
|
||||||
|
|
||||||
|
// NEW: all-fields-required guard
|
||||||
|
const canSave = Boolean(
|
||||||
|
name.trim() &&
|
||||||
|
htmlCode.trim() &&
|
||||||
|
description.trim() &&
|
||||||
|
type &&
|
||||||
|
userType &&
|
||||||
|
lang
|
||||||
|
)
|
||||||
|
|
||||||
const save = async (publish: boolean) => {
|
const save = async (publish: boolean) => {
|
||||||
const html = htmlCode.trim();
|
const html = htmlCode.trim();
|
||||||
if (!name || !html) {
|
// NEW: validate all fields
|
||||||
setStatusMsg('Please enter a template name and content.');
|
if (!canSave) {
|
||||||
|
setStatusMsg('Please fill all required fields (name, HTML, type, user type, language, description).');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +125,7 @@ export default function ContractEditor({ onSaved }: Props) {
|
|||||||
type,
|
type,
|
||||||
lang,
|
lang,
|
||||||
description: description || undefined,
|
description: description || undefined,
|
||||||
user_type: 'both',
|
user_type: userType,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (publish && created?.id) {
|
if (publish && created?.id) {
|
||||||
@ -140,6 +152,7 @@ export default function ContractEditor({ onSaved }: Props) {
|
|||||||
placeholder="Template name"
|
placeholder="Template name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
required
|
||||||
className="w-full sm:w-1/2 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
className="w-full sm:w-1/2 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -164,16 +177,30 @@ export default function ContractEditor({ onSaved }: Props) {
|
|||||||
|
|
||||||
{/* New metadata inputs */}
|
{/* New metadata inputs */}
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
<input
|
<select
|
||||||
type="text"
|
|
||||||
placeholder="Type (e.g., contract, nda, invoice)"
|
|
||||||
value={type}
|
value={type}
|
||||||
onChange={(e) => setType(e.target.value)}
|
onChange={(e) => setType(e.target.value as 'contract' | 'bill' | 'other')}
|
||||||
|
required
|
||||||
className="w-full sm:w-1/3 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
className="w-full sm:w-1/3 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
||||||
/>
|
>
|
||||||
|
<option value="contract">Contract</option>
|
||||||
|
<option value="bill">Bill</option>
|
||||||
|
<option value="other">Other</option>
|
||||||
|
</select>
|
||||||
|
<select
|
||||||
|
value={userType}
|
||||||
|
onChange={(e) => setUserType(e.target.value as 'personal' | 'company' | 'both')}
|
||||||
|
required
|
||||||
|
className="w-full sm:w-40 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
||||||
|
>
|
||||||
|
<option value="personal">Personal</option>
|
||||||
|
<option value="company">Company</option>
|
||||||
|
<option value="both">Both</option>
|
||||||
|
</select>
|
||||||
<select
|
<select
|
||||||
value={lang}
|
value={lang}
|
||||||
onChange={(e) => setLang(e.target.value as 'en' | 'de')}
|
onChange={(e) => setLang(e.target.value as 'en' | 'de')}
|
||||||
|
required
|
||||||
className="w-full sm:w-32 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
className="w-full sm:w-32 rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
||||||
>
|
>
|
||||||
<option value="en">English (en)</option>
|
<option value="en">English (en)</option>
|
||||||
@ -184,6 +211,7 @@ export default function ContractEditor({ onSaved }: Props) {
|
|||||||
placeholder="Description (optional)"
|
placeholder="Description (optional)"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
required
|
||||||
className="w-full rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
className="w-full rounded-lg border border-gray-300 bg-white px-4 py-2 text-sm text-gray-900 outline-none focus:ring-2 focus:ring-indigo-500/50 shadow"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -194,6 +222,7 @@ export default function ContractEditor({ onSaved }: Props) {
|
|||||||
value={htmlCode}
|
value={htmlCode}
|
||||||
onChange={(e) => setHtmlCode(e.target.value)}
|
onChange={(e) => setHtmlCode(e.target.value)}
|
||||||
placeholder="Paste your full HTML (or snippet) here…"
|
placeholder="Paste your full HTML (or snippet) here…"
|
||||||
|
required
|
||||||
className="min-h-[320px] w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 font-mono shadow"
|
className="min-h-[320px] w-full rounded-lg border border-gray-300 bg-white px-4 py-3 text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 font-mono shadow"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -212,18 +241,20 @@ export default function ContractEditor({ onSaved }: Props) {
|
|||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
onClick={() => save(false)}
|
onClick={() => save(false)}
|
||||||
disabled={saving}
|
disabled={saving || !canSave}
|
||||||
className="inline-flex items-center rounded-lg bg-gray-100 hover:bg-gray-200 text-gray-900 px-4 py-2 text-sm font-medium shadow disabled:opacity-60 transition"
|
className="inline-flex items-center rounded-lg bg-gray-100 hover:bg-gray-200 text-gray-900 px-4 py-2 text-sm font-medium shadow disabled:opacity-60 transition"
|
||||||
>
|
>
|
||||||
Create (inactive)
|
Create (inactive)
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => save(true)}
|
onClick={() => save(true)}
|
||||||
disabled={saving}
|
disabled={saving || !canSave}
|
||||||
className="inline-flex items-center rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 text-sm font-medium shadow disabled:opacity-60 transition"
|
className="inline-flex items-center rounded-lg bg-indigo-600 hover:bg-indigo-500 text-white px-4 py-2 text-sm font-medium shadow disabled:opacity-60 transition"
|
||||||
>
|
>
|
||||||
Create & Activate
|
Create & Activate
|
||||||
</button>
|
</button>
|
||||||
|
{/* NEW: helper text */}
|
||||||
|
{!canSave && <span className="text-xs text-red-600">Fill all fields to proceed.</span>}
|
||||||
{saving && <span className="text-xs text-gray-500">Saving…</span>}
|
{saving && <span className="text-xs text-gray-500">Saving…</span>}
|
||||||
{statusMsg && <span className="text-xs text-gray-600">{statusMsg}</span>}
|
{statusMsg && <span className="text-xs text-gray-600">{statusMsg}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -164,7 +164,7 @@ export default function useContractManagement() {
|
|||||||
fd.append('type', payload.type);
|
fd.append('type', payload.type);
|
||||||
fd.append('lang', payload.lang);
|
fd.append('lang', payload.lang);
|
||||||
if (payload.description) fd.append('description', payload.description);
|
if (payload.description) fd.append('description', payload.description);
|
||||||
if (payload.user_type) fd.append('user_type', payload.user_type);
|
fd.append('user_type', (payload.user_type ?? 'both'));
|
||||||
|
|
||||||
return authorizedFetch<DocumentTemplate>('/api/document-templates', { method: 'POST', body: fd });
|
return authorizedFetch<DocumentTemplate>('/api/document-templates', { method: 'POST', body: fd });
|
||||||
}, [authorizedFetch]);
|
}, [authorizedFetch]);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, { useMemo, useState } from 'react'
|
|||||||
import { UsersIcon } from '@heroicons/react/24/outline'
|
import { UsersIcon } from '@heroicons/react/24/outline'
|
||||||
|
|
||||||
type UserType = 'personal' | 'company'
|
type UserType = 'personal' | 'company'
|
||||||
type UserStatus = 'active' | 'inactive' | 'pending' | 'blocked' // CHANGED: add inactive
|
type UserStatus = 'active' | 'inactive' | 'pending' | 'blocked'
|
||||||
|
|
||||||
export interface RegisteredUser {
|
export interface RegisteredUser {
|
||||||
id: string | number
|
id: string | number
|
||||||
@ -12,7 +12,7 @@ export interface RegisteredUser {
|
|||||||
email: string
|
email: string
|
||||||
userType: UserType
|
userType: UserType
|
||||||
registeredAt: string | Date
|
registeredAt: string | Date
|
||||||
refCode?: string // CHANGED: optional, not used in UI
|
refCode?: string
|
||||||
status: UserStatus
|
status: UserStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,50 +21,10 @@ interface Props {
|
|||||||
loading?: boolean
|
loading?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base dummy set for the widget preview
|
|
||||||
const baseUsers: RegisteredUser[] = [
|
|
||||||
{ id: 1, name: 'Alice Johnson', email: 'alice@example.com', userType: 'personal', registeredAt: '2025-03-22T10:04:00Z', refCode: 'REF-9A2F1C', status: 'active' },
|
|
||||||
{ id: 2, name: 'Beta GmbH', email: 'office@beta-gmbh.de', userType: 'company', registeredAt: '2025-03-20T08:31:00Z', refCode: 'REF-9A2F1C', status: 'pending' },
|
|
||||||
{ id: 3, name: 'Carlos Diaz', email: 'carlos@sample.io', userType: 'personal', registeredAt: '2025-03-19T14:22:00Z', refCode: 'REF-77XZQ1', status: 'active' },
|
|
||||||
{ id: 4, name: 'Delta Solutions AG', email: 'contact@delta.ag', userType: 'company', registeredAt: '2025-03-18T09:12:00Z', refCode: 'REF-77XZQ1', status: 'active' },
|
|
||||||
{ id: 5, name: 'Emily Nguyen', email: 'emily.ng@ex.com', userType: 'personal', registeredAt: '2025-03-17T19:44:00Z', refCode: 'REF-9A2F1C', status: 'blocked' },
|
|
||||||
]
|
|
||||||
|
|
||||||
// Expanded dummy list for the modal (pagination/search/filter/export demo)
|
|
||||||
function buildDummyAll(): RegisteredUser[] {
|
|
||||||
const list: RegisteredUser[] = []
|
|
||||||
let id = 1
|
|
||||||
const refCodes = ['REF-9A2F1C', 'REF-77XZQ1', 'REF-55PLK9']
|
|
||||||
const names = [
|
|
||||||
'Alice Johnson', 'Beta GmbH', 'Carlos Diaz', 'Delta Solutions AG', 'Emily Nguyen',
|
|
||||||
'Foxtrot LLC', 'Green Innovations', 'Helios Corp', 'Ivy Partners', 'Jonas Weber'
|
|
||||||
]
|
|
||||||
for (let i = 0; i < 60; i++) {
|
|
||||||
const name = names[i % names.length]
|
|
||||||
const isCompany = /GmbH|AG|LLC|Corp|Partners|Innovations/.test(name) ? 'company' : 'personal'
|
|
||||||
const status: UserStatus = (i % 9 === 0) ? 'blocked' : (i % 3 === 0 ? 'pending' : 'active')
|
|
||||||
const refCode = refCodes[i % refCodes.length]
|
|
||||||
const date = new Date(2025, 2, 28 - (i % 25), 10 + (i % 12), (i * 7) % 60, 0).toISOString()
|
|
||||||
list.push({
|
|
||||||
id: id++,
|
|
||||||
name,
|
|
||||||
email: `${name.toLowerCase().replace(/[^a-z]+/g, '.')}@example.com`,
|
|
||||||
userType: isCompany as UserType,
|
|
||||||
registeredAt: date,
|
|
||||||
refCode,
|
|
||||||
status
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// newest first
|
|
||||||
return list.sort((a, b) => new Date(b.registeredAt).getTime() - new Date(a.registeredAt).getTime())
|
|
||||||
}
|
|
||||||
|
|
||||||
const allDummyUsers = buildDummyAll()
|
|
||||||
|
|
||||||
function statusBadgeClass(s: UserStatus) {
|
function statusBadgeClass(s: UserStatus) {
|
||||||
switch (s) {
|
switch (s) {
|
||||||
case 'active': return 'bg-green-100 text-green-800'
|
case 'active': return 'bg-green-100 text-green-800'
|
||||||
case 'inactive': return 'bg-gray-100 text-gray-800' // NEW
|
case 'inactive': return 'bg-gray-100 text-gray-800'
|
||||||
case 'pending': return 'bg-amber-100 text-amber-800'
|
case 'pending': return 'bg-amber-100 text-amber-800'
|
||||||
case 'blocked': return 'bg-rose-100 text-rose-800'
|
case 'blocked': return 'bg-rose-100 text-rose-800'
|
||||||
default: return 'bg-slate-100 text-slate-800'
|
default: return 'bg-slate-100 text-slate-800'
|
||||||
@ -75,9 +35,9 @@ function typeBadgeClass(t: UserType) {
|
|||||||
return t === 'company' ? 'bg-indigo-100 text-indigo-800' : 'bg-blue-100 text-blue-800'
|
return t === 'company' ? 'bg-indigo-100 text-indigo-800' : 'bg-blue-100 text-blue-800'
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSV export helper (no ref code)
|
// CSV export helper
|
||||||
function exportCsv(rows: RegisteredUser[]) {
|
function exportCsv(rows: RegisteredUser[]) {
|
||||||
const header = ['Name', 'Email', 'Type', 'Registered', 'Status'] // CHANGED
|
const header = ['Name', 'Email', 'Type', 'Registered', 'Status']
|
||||||
const csv = [
|
const csv = [
|
||||||
header.join(','),
|
header.join(','),
|
||||||
...rows.map(r => [
|
...rows.map(r => [
|
||||||
@ -99,28 +59,28 @@ function exportCsv(rows: RegisteredUser[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function RegisteredUserList({ users, loading }: Props) {
|
export default function RegisteredUserList({ users, loading }: Props) {
|
||||||
// Normalize backend shape to local RegisteredUser shape (type/status/registeredAt)
|
// Normalize backend shape to local RegisteredUser shape
|
||||||
const normalizedUsers = useMemo<RegisteredUser[]>(() => {
|
const normalizedUsers = useMemo<RegisteredUser[]>(() => {
|
||||||
if (!users || users.length === 0) return []
|
if (!users || users.length === 0) return []
|
||||||
return users.map((u: any) => ({
|
return users.map((u: any) => ({
|
||||||
...u,
|
id: u.id ?? u._id ?? u.userId ?? '',
|
||||||
userType: u.userType ?? u.type ?? 'personal',
|
name: u.name ?? u.fullName ?? u.companyName ?? u.email ?? '',
|
||||||
status: (u.status ?? 'inactive') as UserStatus, // treat null/undefined as inactive
|
email: u.email ?? '',
|
||||||
|
userType: (u.userType ?? u.type ?? 'personal') as UserType,
|
||||||
registeredAt: u.registeredAt ?? u.createdAt ?? new Date().toISOString(),
|
registeredAt: u.registeredAt ?? u.createdAt ?? new Date().toISOString(),
|
||||||
|
status: (u.status ?? 'inactive') as UserStatus,
|
||||||
}))
|
}))
|
||||||
}, [users])
|
}, [users])
|
||||||
|
|
||||||
// Main widget rows (latest 5) - use normalized when provided, else dummy
|
// Latest 5 rows only from backend
|
||||||
const sorted = useMemo(() => {
|
const sorted = useMemo(() => {
|
||||||
const data = (normalizedUsers.length > 0 ? normalizedUsers : baseUsers).slice()
|
const data = normalizedUsers.slice()
|
||||||
return data.sort((a, b) => new Date(b.registeredAt).getTime() - new Date(a.registeredAt).getTime())
|
return data.sort((a, b) => new Date(b.registeredAt).getTime() - new Date(a.registeredAt).getTime())
|
||||||
}, [normalizedUsers])
|
}, [normalizedUsers])
|
||||||
const rows = sorted.slice(0, 5)
|
const rows = sorted.slice(0, 5)
|
||||||
|
|
||||||
// Total badge: prefer normalized list length, else dummy all
|
// Total badge: from backend list length
|
||||||
const totalRegistered = useMemo(() => {
|
const totalRegistered = normalizedUsers.length
|
||||||
return normalizedUsers.length ? normalizedUsers.length : allDummyUsers.length
|
|
||||||
}, [normalizedUsers])
|
|
||||||
|
|
||||||
// Modal state
|
// Modal state
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
@ -130,10 +90,9 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
const pageSize = 10
|
const pageSize = 10
|
||||||
|
|
||||||
// Full dataset for modal
|
// Full dataset for modal (backend only)
|
||||||
const allRows = useMemo(() => {
|
const allRows = useMemo(() => {
|
||||||
const data = normalizedUsers.length ? normalizedUsers : allDummyUsers
|
return normalizedUsers.slice().sort((a, b) => new Date(b.registeredAt).getTime() - new Date(a.registeredAt).getTime())
|
||||||
return data.sort((a, b) => new Date(b.registeredAt).getTime() - new Date(a.registeredAt).getTime())
|
|
||||||
}, [normalizedUsers])
|
}, [normalizedUsers])
|
||||||
|
|
||||||
const filtered = useMemo(() => {
|
const filtered = useMemo(() => {
|
||||||
@ -157,7 +116,7 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
setOpen(true)
|
setOpen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NEW: lock page scroll while modal is open
|
// Lock scroll when modal open
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!open) return
|
if (!open) return
|
||||||
const prev = document.body.style.overflow
|
const prev = document.body.style.overflow
|
||||||
@ -171,7 +130,6 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
<div className="p-6 border-b border-gray-100 flex items-start justify-between gap-4">
|
<div className="p-6 border-b border-gray-100 flex items-start justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xl font-semibold text-gray-900">Registered Users via Your Referral</h2>
|
<h2 className="text-xl font-semibold text-gray-900">Registered Users via Your Referral</h2>
|
||||||
{/* Moved badge directly under the title */}
|
|
||||||
<div className="mt-2 inline-flex items-center gap-2">
|
<div className="mt-2 inline-flex items-center gap-2">
|
||||||
<span className="inline-flex items-center gap-2 rounded-full bg-violet-100 text-violet-800 px-3 py-1 text-[11px] font-semibold tracking-wide">
|
<span className="inline-flex items-center gap-2 rounded-full bg-violet-100 text-violet-800 px-3 py-1 text-[11px] font-semibold tracking-wide">
|
||||||
<UsersIcon className="h-4 w-4" />
|
<UsersIcon className="h-4 w-4" />
|
||||||
@ -188,7 +146,6 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
Showing the latest 5 users. Use “View all” to see the complete list.
|
Showing the latest 5 users. Use “View all” to see the complete list.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* ...existing code... */}
|
|
||||||
<button
|
<button
|
||||||
onClick={resetAndOpen}
|
onClick={resetAndOpen}
|
||||||
className="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-500"
|
className="inline-flex items-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white hover:bg-indigo-500"
|
||||||
@ -205,7 +162,6 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Registered</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Registered</th>
|
||||||
{/* REMOVED: Ref Code column */}
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -219,7 +175,7 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
) : rows.length === 0 ? (
|
) : rows.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td className="px-6 py-6 text-sm text-gray-500" colSpan={5}>
|
<td className="px-6 py-6 text-sm text-gray-500" colSpan={5}>
|
||||||
No registered users found for your referral links.
|
No registered users found.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
@ -235,7 +191,6 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-4 text-sm text-gray-700">{date}</td>
|
<td className="px-6 py-4 text-sm text-gray-700">{date}</td>
|
||||||
{/* REMOVED: Ref Code cell */}
|
|
||||||
<td className="px-6 py-4 text-sm">
|
<td className="px-6 py-4 text-sm">
|
||||||
<span className={`inline-flex items-center rounded px-2 py-1 text-xs font-medium ${statusBadgeClass(u.status)}`}>
|
<span className={`inline-flex items-center rounded px-2 py-1 text-xs font-medium ${statusBadgeClass(u.status)}`}>
|
||||||
{u.status.charAt(0).toUpperCase() + u.status.slice(1)}
|
{u.status.charAt(0).toUpperCase() + u.status.slice(1)}
|
||||||
@ -253,11 +208,7 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
{/* Modal with full list */}
|
{/* Modal with full list */}
|
||||||
{open && (
|
{open && (
|
||||||
<div className="fixed inset-0 z-50" role="dialog" aria-modal="true">
|
<div className="fixed inset-0 z-50" role="dialog" aria-modal="true">
|
||||||
<div
|
<div className="absolute inset-0 bg-black/40" onClick={() => setOpen(false)} aria-hidden />
|
||||||
className="absolute inset-0 bg-black/40"
|
|
||||||
onClick={() => setOpen(false)}
|
|
||||||
aria-hidden
|
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 flex items-center justify-center p-4">
|
<div className="absolute inset-0 flex items-center justify-center p-4">
|
||||||
<div className="w-full max-w-6xl bg-white rounded-xl shadow-2xl ring-1 ring-black/10 overflow-hidden">
|
<div className="w-full max-w-6xl bg-white rounded-xl shadow-2xl ring-1 ring-black/10 overflow-hidden">
|
||||||
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between gap-3">
|
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between gap-3">
|
||||||
@ -281,12 +232,11 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Controls */}
|
|
||||||
<div className="px-6 py-4 border-b border-gray-100 grid grid-cols-1 md:grid-cols-4 gap-3">
|
<div className="px-6 py-4 border-b border-gray-100 grid grid-cols-1 md:grid-cols-4 gap-3">
|
||||||
<input
|
<input
|
||||||
value={query}
|
value={query}
|
||||||
onChange={e => { setQuery(e.target.value); setPage(1) }}
|
onChange={e => { setQuery(e.target.value); setPage(1) }}
|
||||||
placeholder="Search name or email…" // CHANGED
|
placeholder="Search name or email…"
|
||||||
className="md:col-span-2 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-gray-900 placeholder:text-gray-700 placeholder:opacity-100"
|
className="md:col-span-2 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent text-gray-900 placeholder:text-gray-700 placeholder:opacity-100"
|
||||||
/>
|
/>
|
||||||
<select
|
<select
|
||||||
@ -305,13 +255,12 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
>
|
>
|
||||||
<option value="all">All Status</option>
|
<option value="all">All Status</option>
|
||||||
<option value="active">Active</option>
|
<option value="active">Active</option>
|
||||||
<option value="inactive">Inactive</option> {/* NEW */}
|
<option value="inactive">Inactive</option>
|
||||||
<option value="pending">Pending</option>
|
<option value="pending">Pending</option>
|
||||||
<option value="blocked">Blocked</option>
|
<option value="blocked">Blocked</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Table */}
|
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
@ -320,7 +269,6 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Type</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Registered</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Registered</th>
|
||||||
{/* REMOVED: Ref Code column */}
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -344,7 +292,6 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-3 text-sm text-gray-700">{date}</td>
|
<td className="px-6 py-3 text-sm text-gray-700">{date}</td>
|
||||||
{/* REMOVED: Ref Code cell */}
|
|
||||||
<td className="px-6 py-3 text-sm">
|
<td className="px-6 py-3 text-sm">
|
||||||
<span className={`inline-flex items-center rounded px-2 py-1 text-xs font-medium ${statusBadgeClass(u.status)}`}>
|
<span className={`inline-flex items-center rounded px-2 py-1 text-xs font-medium ${statusBadgeClass(u.status)}`}>
|
||||||
{u.status.charAt(0).toUpperCase() + u.status.slice(1)}
|
{u.status.charAt(0).toUpperCase() + u.status.slice(1)}
|
||||||
@ -358,7 +305,6 @@ export default function RegisteredUserList({ users, loading }: Props) {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pagination */}
|
|
||||||
<div className="px-6 py-4 flex items-center justify-between gap-3">
|
<div className="px-6 py-4 flex items-center justify-between gap-3">
|
||||||
<span className="text-xs text-gray-600">
|
<span className="text-xs text-gray-600">
|
||||||
Showing {pageRows.length} of {filtered.length} users
|
Showing {pageRows.length} of {filtered.length} users
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user