diff --git a/app/(app)/comms/page.tsx b/app/(app)/comms/page.tsx index 1a6ed69..2b2bcf2 100644 --- a/app/(app)/comms/page.tsx +++ b/app/(app)/comms/page.tsx @@ -7,7 +7,7 @@ export default async function CommsPage() { if (!user) return null const { data: member } = await supabase - .from('space_members').select('*, spaces(id, name, slug)').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('*, spaces(id, name, slug)').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return null const { data: channels } = await supabase diff --git a/app/(app)/contacts/page.tsx b/app/(app)/contacts/page.tsx index 0f5cf00..4e59f02 100644 --- a/app/(app)/contacts/page.tsx +++ b/app/(app)/contacts/page.tsx @@ -7,7 +7,7 @@ export default async function ContactsPage() { if (!user) return null const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return null const { data: contacts } = await supabase diff --git a/app/(app)/import/page.tsx b/app/(app)/import/page.tsx index 29fb7b3..950ae18 100644 --- a/app/(app)/import/page.tsx +++ b/app/(app)/import/page.tsx @@ -7,7 +7,7 @@ export default async function ImportPage() { if (!user) return null const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() const isAdmin = member?.role === 'admin' || member?.role === 'board' || member?.role === 'treasurer' diff --git a/app/(app)/members/page.tsx b/app/(app)/members/page.tsx index 962ad51..548d964 100644 --- a/app/(app)/members/page.tsx +++ b/app/(app)/members/page.tsx @@ -10,7 +10,7 @@ export default async function MembersPage() { .from('space_members') .select('space_id, role') .eq('user_id', user.id) - .eq('status', 'current') + .in('status', ['current', 'unverified', 'late']) .single() if (!self) return null diff --git a/app/(app)/ops/[id]/page.tsx b/app/(app)/ops/[id]/page.tsx index 4810fc7..0b24db2 100644 --- a/app/(app)/ops/[id]/page.tsx +++ b/app/(app)/ops/[id]/page.tsx @@ -11,7 +11,7 @@ export default async function OpsEntryPage({ params }: { params: Promise<{ id: s if (!user) return null const { data: member } = await supabase - .from('space_members').select('space_id, role, display_name').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role, display_name').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return null const { data: entry } = await supabase diff --git a/app/(app)/ops/new/page.tsx b/app/(app)/ops/new/page.tsx index 00ce438..b44d654 100644 --- a/app/(app)/ops/new/page.tsx +++ b/app/(app)/ops/new/page.tsx @@ -7,7 +7,7 @@ export default async function NewOpsEntryPage() { if (!user) return null const { data: member } = await supabase - .from('space_members').select('space_id, role, display_name').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role, display_name').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return null return diff --git a/app/(app)/ops/page.tsx b/app/(app)/ops/page.tsx index b853263..8c8a2c8 100644 --- a/app/(app)/ops/page.tsx +++ b/app/(app)/ops/page.tsx @@ -7,7 +7,7 @@ export default async function OpsPage() { if (!user) return null const { data: member } = await supabase - .from('space_members').select('space_id, role, display_name, user_id').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role, display_name, user_id').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return null const canSeeSecrets = member.role === 'admin' || member.role === 'board' diff --git a/app/(app)/payments/page.tsx b/app/(app)/payments/page.tsx index d0af570..a67478c 100644 --- a/app/(app)/payments/page.tsx +++ b/app/(app)/payments/page.tsx @@ -7,7 +7,7 @@ export default async function PaymentsPage() { if (!user) return null const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return null const [{ data: payments }, { data: members }, { data: integrations }] = await Promise.all([ @@ -19,7 +19,7 @@ export default async function PaymentsPage() { supabase.from('space_members') .select('id, display_name, email') .eq('space_id', member.space_id) - .eq('status', 'current'), + .in('status', ['current', 'late']), supabase.from('integrations') .select('platform, is_connected, config') .eq('space_id', member.space_id), diff --git a/app/(app)/projects/page.tsx b/app/(app)/projects/page.tsx index 1ad76c4..8c3e325 100644 --- a/app/(app)/projects/page.tsx +++ b/app/(app)/projects/page.tsx @@ -7,7 +7,7 @@ export default async function ProjectsPage() { if (!user) return null const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return null const { data: projects } = await supabase diff --git a/app/(app)/settings/page.tsx b/app/(app)/settings/page.tsx index 88ff802..d0da5ed 100644 --- a/app/(app)/settings/page.tsx +++ b/app/(app)/settings/page.tsx @@ -8,7 +8,7 @@ export default async function SettingsPage() { if (!user) redirect('/login') const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) redirect('/login') diff --git a/app/(app)/tasks/tasks-client.tsx b/app/(app)/tasks/tasks-client.tsx index b66c6c3..2a918bd 100644 --- a/app/(app)/tasks/tasks-client.tsx +++ b/app/(app)/tasks/tasks-client.tsx @@ -53,10 +53,13 @@ export function TasksClient({ tasks: initialTasks, members, currentUserId, space const [formError, setFormError] = useState('') const [formLoading, setFormLoading] = useState(false) - // Ongoing = tasks with a recurrence (not 'none') + // Ongoing = recurring tasks (recurrence != 'none') + // Chores = chore type OR regular tasks without recurrence (the general open pool) + // Mine = tasks claimed/assigned to me + // Done = completed const isDone = (t: any) => t.status === 'done' || t.status === 'completed' const isOpen = (t: any) => !isDone(t) - const chores = tasks.filter(t => (t.type === 'chore' || t.task_type === 'chore') && isOpen(t) && (!t.recurrence || t.recurrence === 'none')) + const chores = tasks.filter(t => isOpen(t) && (!t.recurrence || t.recurrence === 'none')) const ongoing = tasks.filter(t => t.recurrence && t.recurrence !== 'none' && isOpen(t)) const mine = tasks.filter(t => (t.claimed_by === currentUserId || t.assigned_to === currentUserId) && isOpen(t)) const done = tasks.filter(isDone) @@ -154,7 +157,7 @@ export function TasksClient({ tasks: initialTasks, members, currentUserId, space activeTab === tab ? 'border-primary text-primary' : 'border-transparent text-muted-foreground hover:text-foreground' }`} > - {tab === 'mine' ? 'My Tasks' : tab.charAt(0).toUpperCase() + tab.slice(1)}{' '} + {tab === 'mine' ? 'My Tasks' : tab === 'chores' ? 'Open Tasks' : tab.charAt(0).toUpperCase() + tab.slice(1)}{' '} {tabCounts[tab] > 0 && ( )) : ( - No {activeTab === 'done' ? 'completed' : 'open'} tasks in this view + No {activeTab === 'done' ? 'completed' : activeTab === 'chores' ? 'open' : activeTab} tasks in this view setShowCreate(true)} className="font-mono text-xs text-primary mt-2 hover:underline" diff --git a/app/api/paypal/sync/route.ts b/app/api/paypal/sync/route.ts index 402c90d..0a6a7db 100644 --- a/app/api/paypal/sync/route.ts +++ b/app/api/paypal/sync/route.ts @@ -55,7 +55,7 @@ export async function POST(req: NextRequest) { if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member || !['admin', 'board', 'treasurer'].includes(member.role)) { return NextResponse.json({ error: 'Insufficient permissions' }, { status: 403 }) } diff --git a/lib/actions.ts b/lib/actions.ts index 1e59580..fc855d6 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -25,18 +25,27 @@ export async function getUser() { return user } +// ─── Helper: get current member (any non-inactive status) ──────────────────── +async function getMember(supabase: any, userId: string) { + const { data } = await supabase + .from('space_members') + .select('space_id, role, display_name') + .eq('user_id', userId) + .in('status', ['current', 'unverified', 'late']) + .single() + return data +} + export async function getCurrentMember() { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() if (!user) return null - const { data } = await supabase .from('space_members') .select('*, spaces(*)') .eq('user_id', user.id) - .eq('status', 'current') + .in('status', ['current', 'unverified', 'late']) .single() - return data } @@ -54,8 +63,7 @@ export async function createTask(formData: { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id, display_name').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { data, error } = await supabase.from('tasks').insert({ @@ -94,8 +102,7 @@ export async function claimTask(taskId: string) { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id, display_name').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { error } = await supabase.from('tasks').update({ @@ -125,8 +132,7 @@ export async function completeTask(taskId: string) { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id, display_name').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { error } = await supabase.from('tasks').update({ @@ -156,8 +162,7 @@ export async function deleteTask(taskId: string) { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { error } = await supabase.from('tasks').delete() @@ -181,8 +186,7 @@ export async function createProject(formData: { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id, display_name').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { data, error } = await supabase.from('projects').insert({ @@ -216,8 +220,7 @@ export async function updateProjectStatus(projectId: string, status: string) { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { error } = await supabase.from('projects').update({ status, updated_at: new Date().toISOString() }) @@ -234,8 +237,7 @@ export async function deleteProject(projectId: string) { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { error } = await supabase.from('projects').delete() @@ -263,7 +265,7 @@ export async function addMember(formData: { if (!user) return { error: 'Not authenticated' } const { data: self } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!self || (self.role !== 'admin' && self.role !== 'board')) return { error: 'Admin access required' } const { data, error } = await supabase.from('space_members').insert({ @@ -302,7 +304,7 @@ export async function updateMember(memberId: string, updates: { if (!user) return { error: 'Not authenticated' } const { data: self } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!self || (self.role !== 'admin' && self.role !== 'board')) return { error: 'Admin access required' } const { error } = await supabase.from('space_members').update(updates) @@ -319,7 +321,7 @@ export async function approveMember(memberId: string) { if (!user) return { error: 'Not authenticated' } const { data: self } = await supabase - .from('space_members').select('space_id, role, display_name').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role, display_name').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!self || (self.role !== 'admin' && self.role !== 'board')) return { error: 'Admin access required' } const { error } = await supabase.from('space_members').update({ status: 'current', approved: true }) @@ -346,7 +348,7 @@ export async function removeMember(memberId: string) { if (!user) return { error: 'Not authenticated' } const { data: self } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!self || self.role !== 'admin') return { error: 'Admin access required' } const { error } = await supabase.from('space_members').delete() @@ -373,8 +375,7 @@ export async function createContact(formData: { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const code = formData.name.slice(0, 3).toUpperCase() + Math.floor(Math.random() * 900 + 100) @@ -411,8 +412,7 @@ export async function updateContact(contactId: string, updates: { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { error } = await supabase.from('contacts').update({ ...updates, updated_at: new Date().toISOString() }) @@ -428,8 +428,7 @@ export async function deleteContact(contactId: string) { const { data: { user } } = await supabase.auth.getUser() if (!user) return { error: 'Not authenticated' } - const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + const member = await getMember(supabase, user.id) if (!member) return { error: 'No active membership' } const { error } = await supabase.from('contacts').delete() @@ -453,7 +452,7 @@ export async function logCashPayment(formData: { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, role, display_name').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role, display_name').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member || (member.role !== 'admin' && member.role !== 'board' && member.role !== 'treasurer')) { return { error: 'Treasurer access required' } } @@ -500,7 +499,7 @@ export async function linkPaymentToMember(paymentId: string, memberId: string) { if (!user) return { error: 'Not authenticated' } const { data: self } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!self || (self.role !== 'admin' && self.role !== 'board' && self.role !== 'treasurer')) { return { error: 'Treasurer access required' } } @@ -542,7 +541,7 @@ export async function importPaymentsCsv(rows: Array<{ if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member || (member.role !== 'admin' && member.role !== 'board' && member.role !== 'treasurer')) { return { error: 'Treasurer access required' } } @@ -582,7 +581,7 @@ export async function createKbEntry(formData: { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, display_name, handle').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, display_name, handle').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return { error: 'No active membership' } const { data, error } = await supabase.from('knowledge_base').insert({ @@ -616,7 +615,7 @@ export async function updateKbEntry(entryId: string, updates: { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, display_name').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, display_name').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return { error: 'No active membership' } // Only pass columns that actually exist in the DB @@ -640,7 +639,7 @@ export async function deleteKbEntry(entryId: string) { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member) return { error: 'No active membership' } const { error } = await supabase.from('knowledge_base').delete() @@ -665,7 +664,7 @@ export async function createSecret(formData: { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member || (member.role !== 'admin' && member.role !== 'board')) return { error: 'Admin access required' } const { data, error } = await supabase.from('secrets').insert({ @@ -688,7 +687,7 @@ export async function deleteSecret(secretId: string) { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member || member.role !== 'admin') return { error: 'Admin access required' } const { error } = await supabase.from('secrets').delete() @@ -713,7 +712,7 @@ export async function upsertAreaLead(formData: { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member || (member.role !== 'admin' && member.role !== 'board')) return { error: 'Admin access required' } const { data, error } = await supabase.from('area_leads').upsert({ @@ -745,7 +744,7 @@ export async function updateSpaceSettings(updates: { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member || member.role !== 'admin') return { error: 'Admin access required' } const { error } = await supabase.from('spaces').update({ @@ -764,7 +763,7 @@ export async function rotateWebhookSecret() { if (!user) return { error: 'Not authenticated' } const { data: member } = await supabase - .from('space_members').select('space_id, role').eq('user_id', user.id).eq('status', 'current').single() + .from('space_members').select('space_id, role').eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single() if (!member || member.role !== 'admin') return { error: 'Admin access required' } // Generate a new webhook secret @@ -785,7 +784,7 @@ export async function saveIntegration(platform: string, config: Record ({ diff --git a/scripts/fix-actions.js b/scripts/fix-actions.js new file mode 100644 index 0000000..28ae099 --- /dev/null +++ b/scripts/fix-actions.js @@ -0,0 +1,23 @@ +const fs = require('fs') +const path = require('path') + +const filePath = '/vercel/share/v0-project/lib/actions.ts' +console.log('Patching:', filePath) +let content = fs.readFileSync(filePath, 'utf8') + +// Replace ALL .eq('user_id', user.id).eq('status', 'current').single() patterns +content = content.replaceAll( + `.eq('user_id', user.id).eq('status', 'current').single()`, + `.eq('user_id', user.id).in('status', ['current', 'unverified', 'late']).single()` +) + +// Also the multi-line variant +content = content.replaceAll( + `.eq('user_id', user.id)\n .eq('status', 'current')\n .single()`, + `.eq('user_id', user.id)\n .in('status', ['current', 'unverified', 'late'])\n .single()` +) + +fs.writeFileSync(filePath, content, 'utf8') +console.log('Done. File length:', content.length) +const remaining = (content.match(/eq\('status', 'current'\)/g) || []).length +console.log('Remaining eq(status,current) occurrences:', remaining)
No {activeTab === 'done' ? 'completed' : 'open'} tasks in this view
No {activeTab === 'done' ? 'completed' : activeTab === 'chores' ? 'open' : activeTab} tasks in this view