diff --git a/apps/web/app/test/escrow/page.tsx b/apps/web/app/test/escrow/page.tsx
new file mode 100644
index 00000000..c55f6b6d
--- /dev/null
+++ b/apps/web/app/test/escrow/page.tsx
@@ -0,0 +1,22 @@
+import { EscrowTable } from '../../../components/EscrowTable'
+
+const FeatureNotAvailable = () => (
+
+
+
Feature Not Available
+
This feature is only available in development mode.
+
+
+)
+
+export default function TestPage() {
+ if (process.env.NODE_ENV === 'production') {
+ return ; // Instead of redirecting, show the message
+ }
+
+ return (
+
+
+
+ )
+}
diff --git a/apps/web/components/EscrowTable/EscrowTable.tsx b/apps/web/components/EscrowTable/EscrowTable.tsx
new file mode 100644
index 00000000..4ef64d72
--- /dev/null
+++ b/apps/web/components/EscrowTable/EscrowTable.tsx
@@ -0,0 +1,244 @@
+"use client";
+
+import { useCallback, useEffect, useState, useMemo } from "react";
+import { createClient } from "../../lib/supabase/client";
+import type { Database } from "../../../../services/supabase/database.types";
+import { appConfig } from "../../lib/config/appConfig";
+
+type Tables = Database["public"]["Tables"];
+type EscrowRecord = Tables["escrow_status"]["Row"];
+type EscrowStatusType =
+ | "NEW"
+ | "FUNDED"
+ | "ACTIVE"
+ | "COMPLETED"
+ | "DISPUTED"
+ | "CANCELLED";
+
+export const FeatureNotAvailable = () => (
+
+
+
Feature Not Available
+
This feature is only available in development mode.
+
+
+);
+
+export function EscrowTable() {
+ const [dbStatus, setDbStatus] = useState("Checking...");
+ const [error, setError] = useState(null);
+ const [records, setRecords] = useState([]);
+ const supabase = createClient();
+ const isFeatureEnabled = useMemo(
+ () => appConfig.features.enableEscrowFeature,
+ [appConfig.features.enableEscrowFeature]
+ );
+
+ const statusColors: Record = useMemo(
+ () => ({
+ NEW: "bg-gray-100",
+ FUNDED: "bg-blue-100",
+ ACTIVE: "bg-green-100",
+ COMPLETED: "bg-purple-100",
+ DISPUTED: "bg-red-100",
+ CANCELLED: "bg-yellow-100",
+ }),
+ []
+ );
+
+ const fetchRecords = useCallback(async () => {
+ if (!isFeatureEnabled) return;
+
+ try {
+ const { data, error } = await supabase
+ .from("escrow_status")
+ .select("*")
+ .order("last_updated", { ascending: false });
+
+ if (error) {
+ setError(error.message);
+ setDbStatus("Failed");
+ } else {
+ setRecords(data || []);
+ setDbStatus("Connected");
+ }
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Unknown error");
+ setDbStatus("Failed");
+ }
+ }, [supabase, isFeatureEnabled]);
+
+ const updateStatus = async (id: string, newStatus: EscrowStatusType) => {
+ if (!isFeatureEnabled) return;
+
+ try {
+ const { error } = await supabase
+ .from("escrow_status")
+ .update({
+ status: newStatus,
+ last_updated: new Date().toISOString(),
+ })
+ .eq("id", id);
+
+ if (error) throw error;
+
+ await fetchRecords();
+ alert(`Status updated to ${newStatus}`);
+ } catch (err) {
+ console.error("Error:", err);
+ alert(
+ "Error updating status: " +
+ (err instanceof Error ? err.message : "Unknown error")
+ );
+ }
+ };
+
+ const insertTestData = async () => {
+ if (!isFeatureEnabled) return;
+
+ try {
+ const { error } = await supabase.from("escrow_status").insert([
+ {
+ escrow_id: "test-" + Date.now(),
+ status: "NEW" as EscrowStatusType,
+ current_milestone: 1,
+ total_funded: 1000,
+ total_released: 0,
+ metadata: {
+ milestoneStatus: {
+ total: 3,
+ completed: 0,
+ },
+ },
+ },
+ ]);
+
+ if (error) throw error;
+ alert("Test data inserted successfully!");
+ fetchRecords();
+ } catch (err) {
+ console.error("Error:", err);
+ alert(
+ "Error inserting test data: " +
+ (err instanceof Error ? err.message : "Unknown error")
+ );
+ }
+ };
+
+ useEffect(() => {
+ if (isFeatureEnabled) {
+ fetchRecords();
+ }
+ }, [fetchRecords, isFeatureEnabled]);
+
+ if (!isFeatureEnabled) {
+ return ;
+ }
+
+ return (
+
+
Escrow Status System Test
+
+
+
Database Status
+
+ Status: {dbStatus}
+
+ {error &&
Error: {error}
}
+
+
+
+
Test Actions
+
+
+
+
+
+
+
+
+
Current Records ({records.length})
+
+
+
+
+ ID |
+ Escrow ID |
+ Status |
+ Milestone |
+ Funded |
+ Released |
+ Actions |
+
+
+
+ {records.map((record) => (
+
+ {record.id} |
+ {record.escrow_id} |
+ {record.status} |
+ {record.current_milestone} |
+ {record.total_funded} |
+ {record.total_released} |
+
+
+ |
+
+ ))}
+
+
+
+
+
+
+
Status Legend:
+
+ {Object.entries(statusColors).map(([status, color]) => (
+
+ {status}
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/web/components/EscrowTable/index.ts b/apps/web/components/EscrowTable/index.ts
new file mode 100644
index 00000000..49e28277
--- /dev/null
+++ b/apps/web/components/EscrowTable/index.ts
@@ -0,0 +1 @@
+export * from './EscrowTable'
\ No newline at end of file
diff --git a/apps/web/hooks/escrow/useEscrow.ts b/apps/web/hooks/escrow/useEscrow.ts
new file mode 100644
index 00000000..5e4b9514
--- /dev/null
+++ b/apps/web/hooks/escrow/useEscrow.ts
@@ -0,0 +1,211 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { createClient } from '../../lib/supabase/client'
+import type { Database } from '../../../../services/supabase/database.types'
+
+type Tables = Database['public']['Tables']
+type EscrowRecord = Tables['escrow_status']['Row']
+type EscrowStatusType = 'NEW' | 'FUNDED' | 'ACTIVE' | 'COMPLETED' | 'DISPUTED' | 'CANCELLED'
+
+interface EscrowStatusState {
+ state: EscrowStatusType
+ milestoneStatus: {
+ current: number
+ total: number
+ completed: number
+ }
+ financials: {
+ totalFunded: number
+ totalReleased: number
+ pendingRelease: number
+ }
+}
+
+interface MilestoneMetadata {
+ milestoneStatus: {
+ total: number
+ completed: number
+ }
+}
+
+export function useEscrow(escrowId: string) {
+ const [status, setStatus] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+ const [rawRecord, setRawRecord] = useState(null)
+ const supabase = createClient()
+
+ const transformRecord = (record: EscrowRecord): EscrowStatusState | null => {
+ if (record.status === 'CANCELLED') return null
+
+ // Safely cast metadata with type checking
+ const metadata = typeof record.metadata === 'object' && record.metadata
+ ? record.metadata as { milestoneStatus?: { total: number; completed: number } }
+ : { milestoneStatus: { total: 0, completed: 0 } }
+
+ return {
+ state: record.status,
+ milestoneStatus: {
+ current: record.current_milestone || 0,
+ total: metadata?.milestoneStatus?.total || 0,
+ completed: metadata?.milestoneStatus?.completed || 0,
+ },
+ financials: {
+ totalFunded: Number(record.total_funded) || 0,
+ totalReleased: Number(record.total_released) || 0,
+ pendingRelease:
+ (Number(record.total_funded) || 0) -
+ (Number(record.total_released) || 0),
+ },
+ }
+ }
+
+ const fetchEscrowStatus = async () => {
+ try {
+ setLoading(true)
+ setError(null)
+
+ const { data, error: fetchError } = await supabase
+ .from('escrow_status')
+ .select('*')
+ .eq('escrow_id', escrowId)
+ .single()
+
+ if (fetchError) throw fetchError
+
+ if (data) {
+ setRawRecord(data as EscrowRecord)
+ const transformed = transformRecord(data as EscrowRecord)
+ if (transformed) setStatus(transformed)
+ }
+ } catch (err) {
+ setError(
+ err instanceof Error ? err : new Error('Failed to fetch escrow status'),
+ )
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ useEffect(() => {
+ fetchEscrowStatus()
+
+ const channel = supabase
+ .channel(`escrow_status:${escrowId}`)
+ .on(
+ 'postgres_changes',
+ {
+ event: '*',
+ schema: 'public',
+ table: 'escrow_status',
+ filter: `escrow_id=eq.${escrowId}`,
+ },
+ (payload) => {
+ if (payload.new) {
+ const newRecord = payload.new as EscrowRecord
+ setRawRecord(newRecord)
+ const transformed = transformRecord(newRecord)
+ if (transformed) setStatus(transformed)
+ }
+ },
+ )
+ .subscribe()
+
+ return () => {
+ channel.unsubscribe()
+ }
+ }, [escrowId, fetchEscrowStatus, supabase, transformRecord])
+
+ const updateStatus = async (newStatus: EscrowStatusType) => {
+ try {
+ if (!rawRecord) throw new Error('No record to update')
+
+ const { error: updateError } = await supabase
+ .from('escrow_status')
+ .update({
+ status: newStatus,
+ last_updated: new Date().toISOString(),
+ })
+ .eq('id', rawRecord.id)
+
+ if (updateError) throw updateError
+ await fetchEscrowStatus()
+ } catch (err) {
+ const error = err instanceof Error ? err : new Error('Failed to update status')
+ setError(error)
+ throw error
+ }
+ }
+
+ const updateMilestone = async (current: number, completed: number) => {
+ try {
+ if (!rawRecord) throw new Error('No record to update')
+
+ // Safely handle existing metadata
+ const existingMetadata = typeof rawRecord.metadata === 'object' && rawRecord.metadata
+ ? rawRecord.metadata as { milestoneStatus?: { total: number; completed: number } }
+ : { milestoneStatus: { total: 0, completed: 0 } }
+
+ const total = existingMetadata?.milestoneStatus?.total || completed
+
+ const updatedMetadata = {
+ milestoneStatus: {
+ total,
+ completed
+ }
+ }
+
+ const { error: updateError } = await supabase
+ .from('escrow_status')
+ .update({
+ current_milestone: current,
+ metadata: updatedMetadata,
+ last_updated: new Date().toISOString()
+ })
+ .eq('id', rawRecord.id)
+
+ if (updateError) throw updateError
+ await fetchEscrowStatus()
+ } catch (err) {
+ const error = err instanceof Error ? err : new Error('Failed to update milestone')
+ setError(error)
+ throw error
+ }
+ }
+
+ const updateFinancials = async (funded: number, released: number) => {
+ try {
+ if (!rawRecord) throw new Error('No record to update')
+ if (funded < released) {
+ throw new Error('Total funded cannot be less than total released')
+ }
+
+ const { error: updateError } = await supabase
+ .from('escrow_status')
+ .update({
+ total_funded: funded,
+ total_released: released,
+ last_updated: new Date().toISOString()
+ })
+ .eq('id', rawRecord.id)
+
+ if (updateError) throw updateError
+ await fetchEscrowStatus()
+ } catch (err) {
+ const error = err instanceof Error ? err : new Error('Failed to update financials')
+ setError(error)
+ throw error
+ }
+ }
+
+ return {
+ status,
+ loading,
+ error,
+ updateStatus,
+ updateMilestone,
+ updateFinancials,
+ refetch: fetchEscrowStatus,
+ }
+}
\ No newline at end of file
diff --git a/apps/web/lib/config/appConfig.ts b/apps/web/lib/config/appConfig.ts
new file mode 100644
index 00000000..39afbd53
--- /dev/null
+++ b/apps/web/lib/config/appConfig.ts
@@ -0,0 +1,5 @@
+export const appConfig = {
+ features: {
+ enableEscrowFeature: process.env.NODE_ENV === 'development' || process.env.NEXT_PUBLIC_ENABLE_ESCROW_FEATURE === 'true'
+ }
+ } as const
\ No newline at end of file