mirror of
https://github.com/FranP-code/inbox-negotiator.git
synced 2025-10-13 00:42:26 +00:00
Add personal data management to user configuration and onboarding
This commit is contained in:
@@ -29,6 +29,7 @@ import {
|
|||||||
TrendingUp,
|
TrendingUp,
|
||||||
Infinity,
|
Infinity,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
|
UserCheck,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { toast } from "../hooks/use-toast";
|
import { toast } from "../hooks/use-toast";
|
||||||
|
|
||||||
@@ -42,6 +43,18 @@ export function Configuration() {
|
|||||||
const [newEmail, setNewEmail] = useState("");
|
const [newEmail, setNewEmail] = useState("");
|
||||||
const [addingEmail, setAddingEmail] = useState(false);
|
const [addingEmail, setAddingEmail] = useState(false);
|
||||||
|
|
||||||
|
// Personal data state
|
||||||
|
const [personalData, setPersonalData] = useState({
|
||||||
|
full_name: "",
|
||||||
|
address_line_1: "",
|
||||||
|
address_line_2: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
zip_code: "",
|
||||||
|
phone_number: "",
|
||||||
|
});
|
||||||
|
const [savingPersonalData, setSavingPersonalData] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUserData();
|
fetchUserData();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -60,6 +73,13 @@ export function Configuration() {
|
|||||||
.eq("user_id", user.id)
|
.eq("user_id", user.id)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
|
// Fetch user personal data
|
||||||
|
const { data: userData } = await supabase
|
||||||
|
.from("users")
|
||||||
|
.select("*")
|
||||||
|
.eq("id", user.id)
|
||||||
|
.single();
|
||||||
|
|
||||||
// Fetch additional emails
|
// Fetch additional emails
|
||||||
const { data: emailsData } = await supabase
|
const { data: emailsData } = await supabase
|
||||||
.from("additional_emails")
|
.from("additional_emails")
|
||||||
@@ -79,6 +99,19 @@ export function Configuration() {
|
|||||||
setProfile(profileData);
|
setProfile(profileData);
|
||||||
setAdditionalEmails(emailsData || []);
|
setAdditionalEmails(emailsData || []);
|
||||||
setUsage(usageData);
|
setUsage(usageData);
|
||||||
|
|
||||||
|
// Set personal data
|
||||||
|
if (userData) {
|
||||||
|
setPersonalData({
|
||||||
|
full_name: userData.full_name || "",
|
||||||
|
address_line_1: userData.address_line_1 || "",
|
||||||
|
address_line_2: userData.address_line_2 || "",
|
||||||
|
city: userData.city || "",
|
||||||
|
state: userData.state || "",
|
||||||
|
zip_code: userData.zip_code || "",
|
||||||
|
phone_number: userData.phone_number || "",
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching user data:", error);
|
console.error("Error fetching user data:", error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -86,6 +119,44 @@ export function Configuration() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const savePersonalData = async () => {
|
||||||
|
setSavingPersonalData(true);
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { user },
|
||||||
|
} = await supabase.auth.getUser();
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from("users")
|
||||||
|
.update({
|
||||||
|
full_name: personalData.full_name || null,
|
||||||
|
address_line_1: personalData.address_line_1 || null,
|
||||||
|
address_line_2: personalData.address_line_2 || null,
|
||||||
|
city: personalData.city || null,
|
||||||
|
state: personalData.state || null,
|
||||||
|
zip_code: personalData.zip_code || null,
|
||||||
|
phone_number: personalData.phone_number || null,
|
||||||
|
})
|
||||||
|
.eq("id", user.id);
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Personal data updated",
|
||||||
|
description: "Your personal information has been saved successfully.",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({
|
||||||
|
title: "Error saving personal data",
|
||||||
|
description: error.message,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setSavingPersonalData(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const addAdditionalEmail = async () => {
|
const addAdditionalEmail = async () => {
|
||||||
if (!newEmail || !profile) return;
|
if (!newEmail || !profile) return;
|
||||||
|
|
||||||
@@ -247,6 +318,139 @@ export function Configuration() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Personal Data */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<UserCheck className="h-5 w-5" />
|
||||||
|
Personal Information
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Your personal information used in negotiation letters
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="config_full_name">Full Name</Label>
|
||||||
|
<Input
|
||||||
|
id="config_full_name"
|
||||||
|
placeholder="John Doe"
|
||||||
|
value={personalData.full_name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
full_name: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="config_address_line_1">Address Line 1</Label>
|
||||||
|
<Input
|
||||||
|
id="config_address_line_1"
|
||||||
|
placeholder="123 Main Street"
|
||||||
|
value={personalData.address_line_1}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
address_line_1: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="config_address_line_2">Address Line 2</Label>
|
||||||
|
<Input
|
||||||
|
id="config_address_line_2"
|
||||||
|
placeholder="Apt 4B"
|
||||||
|
value={personalData.address_line_2}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
address_line_2: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="config_city">City</Label>
|
||||||
|
<Input
|
||||||
|
id="config_city"
|
||||||
|
placeholder="New York"
|
||||||
|
value={personalData.city}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
city: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="config_state">State</Label>
|
||||||
|
<Input
|
||||||
|
id="config_state"
|
||||||
|
placeholder="NY"
|
||||||
|
value={personalData.state}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
state: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="config_zip_code">Zip Code</Label>
|
||||||
|
<Input
|
||||||
|
id="config_zip_code"
|
||||||
|
placeholder="10001"
|
||||||
|
value={personalData.zip_code}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
zip_code: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="config_phone_number">Phone Number</Label>
|
||||||
|
<Input
|
||||||
|
id="config_phone_number"
|
||||||
|
placeholder="(555) 123-4567"
|
||||||
|
value={personalData.phone_number}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
phone_number: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={savePersonalData}
|
||||||
|
disabled={savingPersonalData}
|
||||||
|
className="min-w-[120px]"
|
||||||
|
>
|
||||||
|
{savingPersonalData ? "Saving..." : "Save Changes"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Additional Emails */}
|
{/* Additional Emails */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
@@ -20,13 +20,61 @@ interface OnboardingDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
|
export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
|
||||||
const [step, setStep] = useState<"welcome" | "email" | "complete">("welcome");
|
const [step, setStep] = useState<
|
||||||
|
"welcome" | "personal" | "email" | "complete"
|
||||||
|
>("welcome");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [skipEmail, setSkipEmail] = useState(false);
|
const [skipEmail, setSkipEmail] = useState(false);
|
||||||
|
|
||||||
const handleAddEmail = async () => {
|
// Personal data state
|
||||||
if (!email && !skipEmail) return;
|
const [personalData, setPersonalData] = useState({
|
||||||
|
full_name: "",
|
||||||
|
address_line_1: "",
|
||||||
|
address_line_2: "",
|
||||||
|
city: "",
|
||||||
|
state: "",
|
||||||
|
zip_code: "",
|
||||||
|
phone_number: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSavePersonalData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { user },
|
||||||
|
} = await supabase.auth.getUser();
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from("users")
|
||||||
|
.update({
|
||||||
|
full_name: personalData.full_name || null,
|
||||||
|
address_line_1: personalData.address_line_1 || null,
|
||||||
|
address_line_2: personalData.address_line_2 || null,
|
||||||
|
city: personalData.city || null,
|
||||||
|
state: personalData.state || null,
|
||||||
|
zip_code: personalData.zip_code || null,
|
||||||
|
phone_number: personalData.phone_number || null,
|
||||||
|
})
|
||||||
|
.eq("id", user.id);
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
setStep("email");
|
||||||
|
} catch (error: any) {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: error.message,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddEmail = async (_skipEmail?: boolean) => {
|
||||||
|
if (!email && !_skipEmail) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -35,7 +83,7 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
|
|||||||
} = await supabase.auth.getUser();
|
} = await supabase.auth.getUser();
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
|
|
||||||
if (email && !skipEmail) {
|
if (email && !_skipEmail) {
|
||||||
const { error } = await supabase.from("additional_emails").insert({
|
const { error } = await supabase.from("additional_emails").insert({
|
||||||
user_id: user.id,
|
user_id: user.id,
|
||||||
email_address: email.trim().toLowerCase(),
|
email_address: email.trim().toLowerCase(),
|
||||||
@@ -70,6 +118,7 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
|
|||||||
|
|
||||||
const handleSkipEmail = () => {
|
const handleSkipEmail = () => {
|
||||||
setSkipEmail(true);
|
setSkipEmail(true);
|
||||||
|
handleAddEmail(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -110,7 +159,7 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
|
|||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button onClick={() => setStep("email")}>
|
<Button onClick={() => setStep("personal")}>
|
||||||
Get Started
|
Get Started
|
||||||
<ArrowRight className="h-4 w-4 ml-2" />
|
<ArrowRight className="h-4 w-4 ml-2" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -119,6 +168,153 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{step === "personal" && (
|
||||||
|
<>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<UserCheck className="h-6 w-6 text-primary" />
|
||||||
|
Personal Information
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogDescription>
|
||||||
|
Please provide your personal information. This will be used to
|
||||||
|
generate formal negotiation letters.
|
||||||
|
</DialogDescription>
|
||||||
|
<DialogDescription className="text-sm text-muted-foreground">
|
||||||
|
All fields are optional, but providing complete information will
|
||||||
|
result in more professional letters.
|
||||||
|
</DialogDescription>
|
||||||
|
|
||||||
|
<div className="space-y-4 overflow-y-auto">
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="full_name">Full Name</Label>
|
||||||
|
<Input
|
||||||
|
id="full_name"
|
||||||
|
placeholder="John Doe"
|
||||||
|
value={personalData.full_name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
full_name: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="address_line_1">Address Line 1</Label>
|
||||||
|
<Input
|
||||||
|
id="address_line_1"
|
||||||
|
placeholder="123 Main Street"
|
||||||
|
value={personalData.address_line_1}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
address_line_1: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="address_line_2">Address Line 2</Label>
|
||||||
|
<Input
|
||||||
|
id="address_line_2"
|
||||||
|
placeholder="Apt 4B"
|
||||||
|
value={personalData.address_line_2}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
address_line_2: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="city">City</Label>
|
||||||
|
<Input
|
||||||
|
id="city"
|
||||||
|
placeholder="New York"
|
||||||
|
value={personalData.city}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
city: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="state">State</Label>
|
||||||
|
<Input
|
||||||
|
id="state"
|
||||||
|
placeholder="NY"
|
||||||
|
value={personalData.state}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
state: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="zip_code">Zip Code</Label>
|
||||||
|
<Input
|
||||||
|
id="zip_code"
|
||||||
|
placeholder="10001"
|
||||||
|
value={personalData.zip_code}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
zip_code: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="phone_number">Phone Number</Label>
|
||||||
|
<Input
|
||||||
|
id="phone_number"
|
||||||
|
placeholder="(555) 123-4567"
|
||||||
|
value={personalData.phone_number}
|
||||||
|
onChange={(e) =>
|
||||||
|
setPersonalData({
|
||||||
|
...personalData,
|
||||||
|
phone_number: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setStep("email")}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
Skip for now
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleSavePersonalData}
|
||||||
|
disabled={loading}
|
||||||
|
className="min-w-[100px]"
|
||||||
|
>
|
||||||
|
{loading ? "Saving..." : "Continue"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{step === "email" && (
|
{step === "email" && (
|
||||||
<>
|
<>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
@@ -176,11 +372,11 @@ export function OnboardingDialog({ open, onComplete }: OnboardingDialogProps) {
|
|||||||
Skip for now
|
Skip for now
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAddEmail}
|
onClick={() => handleAddEmail()}
|
||||||
disabled={!email || loading}
|
disabled={!email || loading}
|
||||||
className="min-w-[100px]"
|
className="min-w-[100px]"
|
||||||
>
|
>
|
||||||
{loading ? "Adding..." : "Add Email"}
|
{loading && email ? "Adding..." : "Add Email"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<input
|
<div className="flex justify-center">
|
||||||
type={type}
|
<input
|
||||||
className={cn(
|
type={type}
|
||||||
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
className={cn(
|
||||||
className
|
"flex h-10 rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 w-[calc(100%-8px)]",
|
||||||
)}
|
className
|
||||||
ref={ref}
|
)}
|
||||||
{...props}
|
ref={ref}
|
||||||
/>
|
{...props}
|
||||||
);
|
/>
|
||||||
}
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
Input.displayName = 'Input';
|
Input.displayName = "Input";
|
||||||
|
|
||||||
export { Input };
|
export { Input };
|
||||||
|
|||||||
38
supabase/migrations/20250607007000_add_personal_data.sql
Normal file
38
supabase/migrations/20250607007000_add_personal_data.sql
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
-- Add personal data to the users table for debt negotiation letters
|
||||||
|
-- This will store the user's personal information needed for generating formal negotiation letters
|
||||||
|
|
||||||
|
-- Add personal data columns to the public.users table
|
||||||
|
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS full_name TEXT;
|
||||||
|
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS address_line_1 TEXT;
|
||||||
|
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS address_line_2 TEXT;
|
||||||
|
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS city TEXT;
|
||||||
|
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS state TEXT;
|
||||||
|
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS zip_code TEXT;
|
||||||
|
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS phone_number TEXT;
|
||||||
|
ALTER TABLE public.users ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT NOW();
|
||||||
|
|
||||||
|
-- Create index for performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_users_updated_at ON public.users(updated_at);
|
||||||
|
|
||||||
|
-- Update RLS policy to allow users to update their own data
|
||||||
|
CREATE POLICY "Users can update own profile" ON public.users
|
||||||
|
FOR UPDATE USING (auth.uid() = id);
|
||||||
|
|
||||||
|
-- Grant UPDATE permission to authenticated users
|
||||||
|
GRANT UPDATE ON public.users TO authenticated;
|
||||||
|
|
||||||
|
-- Create function to update updated_at timestamp
|
||||||
|
CREATE OR REPLACE FUNCTION update_users_updated_at_column()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at = now();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ language 'plpgsql';
|
||||||
|
|
||||||
|
-- Create trigger for updated_at
|
||||||
|
DROP TRIGGER IF EXISTS update_users_updated_at ON public.users;
|
||||||
|
CREATE TRIGGER update_users_updated_at
|
||||||
|
BEFORE UPDATE ON public.users
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_users_updated_at_column();
|
||||||
Reference in New Issue
Block a user