BarBar288's picture
Upload 122 files
27127dd verified
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useMutation } from "@tanstack/react-query";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { useAuth } from "@/hooks/use-auth";
import { useToast } from "@/hooks/use-toast";
import { updateUserProfileSchema } from "@shared/schema";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
// Create a form schema
const profileFormSchema = z.object({
fullName: z.string().optional(),
location: z.string().optional(),
interests: z.array(z.string()).optional(),
interestsInput: z.string().optional(), // For input field value only, not submitted
profession: z.string().optional(),
pets: z.string().optional(),
systemContext: z.string().optional(),
});
type ProfileFormValues = z.infer<typeof profileFormSchema>;
interface UserSettingsModalProps {
isOpen: boolean;
onClose: () => void;
}
export default function UserSettingsModal({
isOpen,
onClose,
}: UserSettingsModalProps) {
const { user } = useAuth();
const { toast } = useToast();
// Create form with default values
const form = useForm<ProfileFormValues>({
resolver: zodResolver(profileFormSchema),
defaultValues: {
fullName: "",
location: "",
interests: [],
interestsInput: "",
profession: "",
pets: "",
systemContext: "",
},
});
// Update form when user data changes
useEffect(() => {
if (user) {
// Convert interests array to comma-separated string for display
const interestsString = user.interests?.join(", ") || "";
form.reset({
fullName: user.fullName || "",
location: user.location || "",
interests: user.interests || [],
interestsInput: interestsString,
profession: user.profession || "",
pets: user.pets || "",
systemContext: user.systemContext || "",
});
}
}, [user, form]);
const updateProfileMutation = useMutation({
mutationFn: async (data: ProfileFormValues) => {
const res = await apiRequest("PATCH", "/api/user/profile", data);
if (!res.ok) {
const errorData = await res.json();
throw new Error(errorData.message || "Failed to update profile");
}
return await res.json();
},
onSuccess: (updatedUser) => {
queryClient.setQueryData(["/api/user"], updatedUser);
toast({
title: "Profile updated",
description: "Your profile has been updated successfully.",
});
onClose();
},
onError: (error: Error) => {
toast({
title: "Update failed",
description: error.message,
variant: "destructive",
});
},
});
const onSubmit = async (data: ProfileFormValues) => {
// Create a copy of the data object without interestsInput
const { interestsInput, ...submitData } = data;
// Submit data without the temporary interestsInput field
await updateProfileMutation.mutateAsync(submitData);
};
// Convert string to array for interests field if needed
const handleInterestsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
// Just store the input value as is, don't process it yet
form.setValue("interestsInput", value);
// Process for the actual interests field that gets submitted
const interestsArray = value
.split(",")
.map((item) => item.trim())
.filter((item) => item !== "");
form.setValue("interests", interestsArray);
};
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="sm:max-w-[525px] max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>User Settings</DialogTitle>
<DialogDescription>
Update your profile information and AI assistant preferences
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<div className="space-y-4">
{/* Profile Information Section */}
<div className="border-b pb-2">
<h3 className="text-lg font-medium">Profile Information</h3>
</div>
<FormField
control={form.control}
name="fullName"
render={({ field }) => (
<FormItem>
<FormLabel>Full Name</FormLabel>
<FormControl>
<Input placeholder="Your full name" {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="location"
render={({ field }) => (
<FormItem>
<FormLabel>Location</FormLabel>
<FormControl>
<Input placeholder="Your location" {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="profession"
render={({ field }) => (
<FormItem>
<FormLabel>Profession</FormLabel>
<FormControl>
<Input placeholder="Your profession" {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="interestsInput"
render={({ field }) => (
<FormItem>
<FormLabel>Interests</FormLabel>
<FormControl>
<Input
placeholder="Interests (comma-separated)"
{...field}
onChange={handleInterestsChange}
/>
</FormControl>
<FormDescription>
Enter your interests separated by commas
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="pets"
render={({ field }) => (
<FormItem>
<FormLabel>Pets</FormLabel>
<FormControl>
<Input placeholder="Your pets" {...field} value={field.value || ""} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* AI Assistant Preferences Section */}
<div className="border-b pb-2 pt-4">
<h3 className="text-lg font-medium">AI Assistant Preferences</h3>
</div>
<FormField
control={form.control}
name="additionalInfo"
render={({ field }) => (
<FormItem>
<FormLabel>Additional Information</FormLabel>
<FormControl>
<Textarea
placeholder="Add any additional information about yourself that you'd like the AI to know"
className="min-h-[100px]"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormDescription>
This information will be included in your AI context
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="space-y-4">
<div className="flex justify-between items-center">
<FormLabel className="text-base">System Context</FormLabel>
<Button
type="button"
variant="outline"
size="sm"
onClick={() => {
// Generate structured context from profile fields
const fullName = form.getValues("fullName");
const location = form.getValues("location");
const interests = form.getValues("interests");
const profession = form.getValues("profession");
const pets = form.getValues("pets");
const additionalInfo = form.getValues("additionalInfo");
// Format profile information in a structured way
let profileInfo = "";
if (fullName) profileInfo += `name: ${fullName}\n`;
if (location) profileInfo += `location: ${location}\n`;
if (interests && interests.length > 0) profileInfo += `interests: ${interests.join(", ")}\n`;
if (profession) profileInfo += `profession: ${profession}\n`;
if (pets) profileInfo += `pets: ${pets}\n`;
if (additionalInfo) profileInfo += `additional_info: ${additionalInfo}\n`;
// Get existing context
const currentContext = form.getValues("systemContext") || "";
// Set the new structured context
form.setValue("systemContext", profileInfo + "\n" + currentContext);
}}
>
Include Profile Info
</Button>
</div>
<FormField
control={form.control}
name="systemContext"
render={({ field }) => (
<FormItem>
<FormControl>
<Textarea
placeholder="Add custom context for the AI assistant to understand your requirements better"
className="min-h-[150px]"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormDescription>
This context will be provided to the AI assistant for all your conversations.
Use key-value pairs like "name: Your Name" for best results.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</div>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={onClose}
disabled={updateProfileMutation.isPending}
>
Cancel
</Button>
<Button
type="submit"
disabled={updateProfileMutation.isPending}
>
{updateProfileMutation.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Saving...
</>
) : (
"Save Changes"
)}
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
}