refactor: generate and use Supabase TypeScript types

Co-authored-by: Moheeb Zara <1184415+virgilvox@users.noreply.github.com>
This commit is contained in:
v0
2026-03-11 08:12:39 +00:00
co-authored by Moheeb Zara
parent c34d5d9529
commit 2e136b5f55
18 changed files with 1135 additions and 342 deletions
+9 -5
View File
@@ -3,16 +3,20 @@
import { useState, useEffect, useRef } from 'react'
import { createClient } from '@/lib/supabase/client'
import { Hash, Users2, Send, ChevronLeft } from 'lucide-react'
import type { Tables } from '@/types/database'
type Channel = Tables<'comms_channels'>
type Message = Tables<'comms_messages'>
interface Props {
member: any
space: any
channels: any[]
member: Tables<'space_members'>
space: Tables<'spaces'>
channels: Channel[]
}
export default function CommsClient({ member, space, channels }: Props) {
const [selectedChannel, setSelectedChannel] = useState(channels[0] || null)
const [messages, setMessages] = useState<any[]>([])
const [selectedChannel, setSelectedChannel] = useState<Channel | null>(channels[0] || null)
const [messages, setMessages] = useState<Message[]>([])
const [newMessage, setNewMessage] = useState('')
const [sending, setSending] = useState(false)
const [showChannelList, setShowChannelList] = useState(true)
+2 -12
View File
@@ -3,19 +3,9 @@
import { useState } from 'react'
import { Plus, X, Pencil } from 'lucide-react'
import { createContact, updateContact, deleteContact } from '@/lib/actions'
import type { Tables } from '@/types/database'
interface Contact {
id: string
name: string
contact_type: string
email?: string
phone?: string
details?: string
note?: string
group_label?: string
tags?: string[]
code?: string
}
type Contact = Tables<'contacts'>
const CATEGORY_COLORS: Record<string, string> = {
vendor: 'text-primary border-primary/30 bg-primary/5',
+5 -4
View File
@@ -1,6 +1,7 @@
import { createClient } from '@/lib/supabase/server'
import Link from 'next/link'
import { redirect } from 'next/navigation'
import type { Tables } from '@/types/database'
function IcoUsers({ className }: { className?: string }) {
return <svg className={className} fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
@@ -50,7 +51,7 @@ export default async function DashboardPage() {
supabase.from('activity_log').select('*').eq('space_id', spaceId).order('created_at', { ascending: false }).limit(6),
])
const overdueTasks = (tasks ?? []).filter((t: any) => t.due_date && new Date(t.due_date) < new Date())
const overdueTasks = (tasks ?? []).filter((t) => t.due_date && new Date(t.due_date) < new Date())
const stats = [
{ label: 'Active Members', value: activeMembers ?? 0, Ico: IcoUsers, sub: 'in your space', warn: false },
@@ -91,7 +92,7 @@ export default async function DashboardPage() {
<Link href="/tasks" className="font-sans text-xs text-primary hover:underline">All tasks</Link>
</div>
<div className="bg-card rounded border border-border divide-y divide-border">
{(tasks ?? []).length > 0 ? (tasks ?? []).map((task: any) => {
{(tasks ?? []).length > 0 ? (tasks ?? []).map((task) => {
const isOverdue = task.due_date && new Date(task.due_date) < new Date()
const isDueToday = task.due_date && new Date(task.due_date).toDateString() === new Date().toDateString()
return (
@@ -127,7 +128,7 @@ export default async function DashboardPage() {
<Link href="/projects" className="font-sans text-xs text-primary hover:underline">All projects</Link>
</div>
<div className="bg-card rounded border border-border divide-y divide-border">
{(projects ?? []).length > 0 ? (projects ?? []).map((project: any) => (
{(projects ?? []).length > 0 ? (projects ?? []).map((project) => (
<div key={project.id} className="px-4 py-4">
<div className="flex items-center justify-between mb-1">
<p className="font-sans text-sm font-medium text-foreground">{project.title}</p>
@@ -167,7 +168,7 @@ export default async function DashboardPage() {
<div>
<p className="font-mono text-[10px] tracking-widest text-muted-foreground uppercase mb-3">Recent Activity</p>
<div className="space-y-3">
{(activity ?? []).length > 0 ? (activity ?? []).map((item: any) => (
{(activity ?? []).length > 0 ? (activity ?? []).map((item) => (
<div key={item.id} className="flex items-start gap-2.5">
<div className="w-1.5 h-1.5 rounded-full mt-2 flex-shrink-0 bg-primary" />
<div>
+10 -1
View File
@@ -246,13 +246,22 @@ export default function ImportClient({ spaceId, role }: Props) {
const last_paid_at = obj.last_paid_at ? new Date(obj.last_paid_at).toISOString() : null
const has_card_access = /yes|true|1|y/i.test(obj.has_card_access ?? '')
// Map tier value to valid enum or default to 'basic'
const tierMap: Record<string, 'plus' | 'basic' | 'associate'> = {
plus: 'plus', premium: 'plus', full: 'plus',
basic: 'basic', member: 'basic', standard: 'basic',
associate: 'associate', visitor: 'associate', guest: 'associate',
}
const tier = tierMap[(obj.tier || '').toLowerCase()] || 'basic'
const { error } = await supabase.from('space_members').insert({
space_id: spaceId,
user_id: crypto.randomUUID(),
display_name: obj.display_name,
email: obj.email,
phone: obj.phone || null,
handle: obj.handle || null,
tier: obj.tier || 'member',
tier,
role: 'member',
status: 'current',
approved: true,
+2 -17
View File
@@ -3,24 +3,9 @@
import { useState } from 'react'
import { Plus, X, ChevronDown } from 'lucide-react'
import { addMember, updateMember, approveMember, removeMember } from '@/lib/actions'
import type { Tables } from '@/types/database'
interface Member {
id: string
display_name: string
email: string
phone?: string
handle?: string
tier: string
role: string
status: string
joined_at?: string
last_paid_at?: string
last_payment_at?: string
payment_status?: string
payment_note?: string
has_card_access?: boolean
approved?: boolean
}
type Member = Tables<'space_members'>
interface Props {
members: Member[]
+3 -2
View File
@@ -6,10 +6,11 @@ import Link from 'next/link'
import { ArrowLeft, Pin, Lock } from 'lucide-react'
import { createKbEntry, updateKbEntry } from '@/lib/actions'
import { toast } from 'sonner'
import type { Tables } from '@/types/database'
interface Props {
entry?: any
member: any
entry?: Tables<'knowledge_base'>
member: Tables<'space_members'>
}
export default function KbEntryEditor({ entry, member }: Props) {
+5 -30
View File
@@ -6,39 +6,14 @@ import { Plus, Search, Lock, Pin, Eye, EyeOff, Pencil, Trash2, X, ChevronDown, U
import { createKbEntry, updateKbEntry, deleteKbEntry } from '@/lib/actions'
import { createClient } from '@/lib/supabase/client'
import { toast } from 'sonner'
import type { Tables, TablesInsert } from '@/types/database'
interface KbEntry {
id: string
title: string
content: string
description?: string
area?: string
visibility: string
is_pinned: boolean
tags?: string[]
icon?: string
updated_by_name?: string
updated_at: string
created_at: string
}
interface AreaLead {
id: string
area_name: string
lead_handle: string
lead_id?: string
description?: string
}
interface Secret {
id: string
title: string
area?: string
created_at: string
}
type KbEntry = Tables<'knowledge_base'>
type AreaLead = Tables<'area_leads'>
type Secret = Tables<'secrets'>
interface Props {
member: any
member: Tables<'space_members'>
spaceId: string
kbEntries: KbEntry[]
areaLeads: AreaLead[]
+4 -11
View File
@@ -4,21 +4,14 @@ import { useState } from 'react'
import { X, RefreshCcw } from 'lucide-react'
import { toast } from 'sonner'
import { logCashPayment, linkPaymentToMember } from '@/lib/actions'
import type { Tables } from '@/types/database'
interface Payment {
id: string
platform: string
amount: number
from_identifier: string
from_note?: string
transaction_date: string
link_status: string
member_id?: string
type Payment = Tables<'payments'> & {
space_members?: { display_name: string } | null
}
interface Member { id: string; display_name: string; email: string }
interface Integration { platform: string; is_connected: boolean; config?: Record<string, string> }
type Member = Pick<Tables<'space_members'>, 'id' | 'display_name' | 'email'>
type Integration = Pick<Tables<'integrations'>, 'platform' | 'is_connected' | 'config'>
interface Props {
payments: Payment[]
+2 -11
View File
@@ -3,18 +3,9 @@
import { useState } from 'react'
import { Plus, X } from 'lucide-react'
import { createProject, updateProjectStatus, deleteProject } from '@/lib/actions'
import type { Tables } from '@/types/database'
interface Project {
id: string
title: string
description?: string
status: string
area?: string
tags?: string[]
progress?: number
due_date?: string
created_at: string
}
type Project = Tables<'projects'>
const STATUS_COLS = [
{ key: 'backlog', label: 'BACKLOG', color: 'text-muted-foreground', dot: 'bg-muted-foreground' },
+3 -18
View File
@@ -3,25 +3,10 @@
import { useState } from 'react'
import { X, Check, Copy, Eye, EyeOff, RotateCcw } from 'lucide-react'
import { updateSpaceSettings, saveIntegration, disconnectIntegration, rotateWebhookSecret } from '@/lib/actions'
import type { Tables } from '@/types/database'
interface Space {
id: string
name: string
slug: string
city?: string
invite_code?: string
require_approval?: boolean
public_member_directory?: boolean
webhook_secret?: string
}
interface Integration {
id: string
platform: string
name?: string
is_connected: boolean
config?: Record<string, string>
}
type Space = Tables<'spaces'>
type Integration = Tables<'integrations'>
interface Props {
space: Space
+5 -22
View File
@@ -3,30 +3,13 @@
import { useState, useTransition } from 'react'
import { Plus, X, CheckCircle2 } from 'lucide-react'
import { createTask, claimTask, completeTask, deleteTask } from '@/lib/actions'
import type { Tables } from '@/types/database'
interface Task {
id: string
title: string
description?: string
type?: string
task_type?: string
status: string
area?: string
recurrence?: string
due_date?: string
claimed_by?: string
claimed_by_name?: string
assigned_to?: string
assigned_to_name?: string
progress?: number
subtask_completed?: number
subtask_total?: number
created_at: string
}
type Task = Tables<'tasks'>
interface Props {
tasks: Task[]
members: { id: string; display_name: string; user_id: string }[]
members: Pick<Tables<'space_members'>, 'id' | 'display_name' | 'user_id'>[]
currentUserId: string
spaceId: string
}
@@ -57,8 +40,8 @@ export function TasksClient({ tasks: initialTasks, members, currentUserId, space
// 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 isDone = (t: Task) => t.status === 'done' || t.status === 'completed'
const isOpen = (t: Task) => !isDone(t)
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))