diff --git a/.gitignore b/.gitignore
index a40b315..fd8dbba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,7 @@ pnpm-debug.log*
# supabase files
supabase/.branches
-supabase/.temp
\ No newline at end of file
+supabase/.temp
+
+# vercel files
+.vercel
\ No newline at end of file
diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx
index b366dc9..1eaae3f 100644
--- a/src/components/Dashboard.tsx
+++ b/src/components/Dashboard.tsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import { supabase, type Debt, type UserProfile } from "../lib/supabase";
-import { Button } from "@/components/ui/button";
+import { Button } from "./ui/button";
import { DebtCard } from "./DebtCard";
import { DebtTimeline } from "./DebtTimeline";
import { OnboardingDialog } from "./OnboardingDialog";
@@ -10,10 +10,10 @@ import {
CardDescription,
CardHeader,
CardTitle,
-} from "@/components/ui/card";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { Badge } from "@/components/ui/badge";
-import { Separator } from "@/components/ui/separator";
+} from "./ui/card";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "./ui/tabs";
+import { Badge } from "./ui/badge";
+import { Separator } from "./ui/separator";
import {
DollarSign,
TrendingUp,
@@ -315,7 +315,7 @@ export function Dashboard() {
{debtList.map((debt) => (
-
+
diff --git a/src/components/DebtCard.tsx b/src/components/DebtCard.tsx
index 7b03a8f..ad7218a 100644
--- a/src/components/DebtCard.tsx
+++ b/src/components/DebtCard.tsx
@@ -1,13 +1,16 @@
-import React from "react";
+import React, { useState, useEffect } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
-} from "@/components/ui/card";
-import { Badge } from "@/components/ui/badge";
-import { Button } from "@/components/ui/button";
+} from "./ui/card";
+import { Badge } from "./ui/badge";
+import { Button } from "./ui/button";
+import { Input } from "./ui/input";
+import { Textarea } from "./ui/textarea";
+import { Label } from "./ui/label";
import {
Dialog,
DialogContent,
@@ -15,12 +18,21 @@ import {
DialogHeader,
DialogTitle,
DialogTrigger,
-} from "@/components/ui/dialog";
-import { Calendar, DollarSign, Mail, FileText, TrendingUp } from "lucide-react";
+} from "./ui/dialog";
+import {
+ Calendar,
+ DollarSign,
+ Mail,
+ FileText,
+ TrendingUp,
+ Edit3,
+} from "lucide-react";
+import { supabase } from "../lib/supabase";
import type { Debt } from "../lib/supabase";
interface DebtCardProps {
debt: Debt;
+ onUpdate?: () => void; // Callback to refresh data after updates
}
const statusColors = {
@@ -44,7 +56,7 @@ const statusLabels = {
opted_out: "Opted Out",
};
-export function DebtCard({ debt }: DebtCardProps) {
+export function DebtCard({ debt, onUpdate }: DebtCardProps) {
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("en-US", {
style: "currency",
@@ -62,6 +74,285 @@ export function DebtCard({ debt }: DebtCardProps) {
});
};
+ // Extract variables from text in {{ variable }} format
+ const extractVariables = (text: string): string[] => {
+ const variableRegex = /\{\{\s*([^}]+)\s*\}\}/g;
+ const matches: string[] = [];
+ let match;
+ while ((match = variableRegex.exec(text)) !== null) {
+ if (!matches.includes(match[1].trim())) {
+ matches.push(match[1].trim());
+ }
+ }
+ return matches;
+ };
+
+ // Replace variables in text
+ const replaceVariables = (
+ text: string,
+ variables: Record
+ ): string => {
+ let result = text;
+ Object.entries(variables).forEach(([key, value]) => {
+ const regex = new RegExp(
+ `\\{\\{\\s*${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*\\}\\}`,
+ "g"
+ );
+ result = result.replace(regex, value);
+ });
+ return result;
+ };
+
+ const EditableResponseDialog = () => {
+ const [isEditing, setIsEditing] = useState(false);
+ const [isSaving, setIsSaving] = useState(false);
+ const [subject, setSubject] = useState("");
+ const [body, setBody] = useState("");
+ const [variables, setVariables] = useState>({});
+
+ // Initialize data when dialog opens
+ useEffect(() => {
+ if (debt.metadata?.aiEmail) {
+ const aiEmail = debt.metadata.aiEmail;
+ setSubject(aiEmail.subject || "");
+ setBody(aiEmail.body || "");
+
+ // Extract variables from both subject and body
+ const allText = `${aiEmail.subject || ""} ${aiEmail.body || ""}`;
+ const extractedVars = extractVariables(allText);
+ const initialVariables: Record = {};
+ extractedVars.forEach((variable) => {
+ initialVariables[variable] = "";
+ });
+ setVariables(initialVariables);
+ }
+ }, [debt.metadata?.aiEmail]);
+
+ // Update variables when body changes
+ const handleBodyChange = (newBody: string) => {
+ setBody(newBody);
+ // Extract variables from the new body text
+ const newVariables = extractVariables(newBody);
+ const updatedVariables = { ...variables };
+
+ // Add new variables if they don't exist
+ newVariables.forEach((variable) => {
+ if (!(variable in updatedVariables)) {
+ updatedVariables[variable] = "";
+ }
+ });
+
+ // Remove variables that no longer exist in the text
+ Object.keys(updatedVariables).forEach((variable) => {
+ if (
+ !newVariables.includes(variable) &&
+ !extractVariables(subject).includes(variable)
+ ) {
+ delete updatedVariables[variable];
+ }
+ });
+
+ setVariables(updatedVariables);
+ };
+
+ // Update variables when subject changes
+ const handleSubjectChange = (newSubject: string) => {
+ setSubject(newSubject);
+ // Extract variables from the new subject text
+ const newVariables = extractVariables(newSubject);
+ const updatedVariables = { ...variables };
+
+ // Add new variables if they don't exist
+ newVariables.forEach((variable) => {
+ if (!(variable in updatedVariables)) {
+ updatedVariables[variable] = "";
+ }
+ });
+
+ // Remove variables that no longer exist in the text
+ Object.keys(updatedVariables).forEach((variable) => {
+ if (
+ !newVariables.includes(variable) &&
+ !extractVariables(body).includes(variable)
+ ) {
+ delete updatedVariables[variable];
+ }
+ });
+
+ setVariables(updatedVariables);
+ };
+
+ // Update variables only (don't modify the text)
+ const handleVariableChange = (variableName: string, value: string) => {
+ const newVariables = { ...variables, [variableName]: value };
+ setVariables(newVariables);
+ };
+
+ // Get preview text with variables replaced
+ const getPreviewText = () => {
+ return replaceVariables(`Subject: ${subject}\n\n${body}`, variables);
+ };
+
+ // Get display text for subject (for preview in input)
+ const getSubjectDisplay = () => {
+ return replaceVariables(subject, variables);
+ };
+
+ // Get display text for body (for preview in textarea)
+ const getBodyDisplay = () => {
+ return replaceVariables(body, variables);
+ };
+
+ // Save changes to database
+ const handleSave = async () => {
+ setIsSaving(true);
+ try {
+ // Update the metadata with the new subject and body
+ const updatedMetadata = {
+ ...debt.metadata,
+ aiEmail: {
+ ...debt.metadata?.aiEmail,
+ subject,
+ body,
+ },
+ };
+
+ const { error } = await supabase
+ .from("debts")
+ .update({ metadata: updatedMetadata })
+ .eq("id", debt.id);
+
+ if (error) {
+ console.error("Error saving changes:", error);
+ // You could add toast notification here
+ return;
+ }
+
+ // Call onUpdate callback to refresh the parent component
+ if (onUpdate) {
+ onUpdate();
+ }
+
+ setIsEditing(false);
+ } catch (error) {
+ console.error("Error saving changes:", error);
+ } finally {
+ setIsSaving(false);
+ }
+ };
+
+ if (!debt.metadata?.aiEmail) return null;
+
+ return (
+
+ );
+ };
+
return (
@@ -124,29 +415,7 @@ export function DebtCard({ debt }: DebtCardProps) {
- {debt.negotiated_plan && (
-
- )}
+ {debt.metadata?.aiEmail && }