Skip to content

Commit f33bba3

Browse files
authored
feat: add dream sync (#344)
1 parent 28952b8 commit f33bba3

File tree

104 files changed

+360863
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+360863
-0
lines changed

platforms/dreamSync/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
dist
3+
.DS_Store
4+
server/public
5+
vite.config.ts.*
6+
*.tar.gz
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
6+
</head>
7+
<body>
8+
<div id="root"></div>
9+
<script type="module" src="/src/main.tsx"></script>
10+
<!-- This is a replit script which adds a banner on the top of the page when opened in development mode outside the replit environment -->
11+
<script type="text/javascript" src="https://replit.com/public/js/replit-dev-banner.js"></script>
12+
</body>
13+
</html>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Switch, Route } from "wouter";
2+
import { queryClient } from "./lib/queryClient";
3+
import { QueryClientProvider } from "@tanstack/react-query";
4+
import { Toaster } from "@/components/ui/toaster";
5+
import { TooltipProvider } from "@/components/ui/tooltip";
6+
import { useAuth } from "@/hooks/useAuth";
7+
import { Heart } from "lucide-react";
8+
import NotFound from "@/pages/not-found";
9+
import Login from "@/pages/login";
10+
import Dashboard from "@/pages/dashboard";
11+
import Profile from "@/pages/profile";
12+
import MatchesNew from "@/pages/matches-new";
13+
import Suggestions from "@/pages/suggestions";
14+
import Wishlist from "@/pages/wishlist";
15+
import WishlistItem from "@/pages/wishlist-item";
16+
17+
function Router() {
18+
const { isAuthenticated, isLoading } = useAuth();
19+
20+
if (isLoading) {
21+
return (
22+
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 flex items-center justify-center">
23+
<div className="text-center">
24+
<div className="w-16 h-16 bg-gradient-to-br from-blue-100 via-indigo-100 to-purple-100 rounded-full flex items-center justify-center mx-auto mb-4 animate-pulse shadow-lg">
25+
<Heart className="w-8 h-8 text-blue-600" />
26+
</div>
27+
<p className="text-gray-600">Loading <span className="bg-gradient-to-r from-blue-700 via-indigo-700 to-purple-700 bg-clip-text text-transparent font-semibold">DreamSync</span>...</p>
28+
</div>
29+
</div>
30+
);
31+
}
32+
33+
return (
34+
<Switch>
35+
{!isAuthenticated ? (
36+
<Route path="/" component={Login} />
37+
) : (
38+
<>
39+
<Route path="/" component={Dashboard} />
40+
<Route path="/profile" component={Profile} />
41+
<Route path="/matches" component={MatchesNew} />
42+
<Route path="/suggestions" component={Suggestions} />
43+
<Route path="/wishlist" component={Wishlist} />
44+
<Route path="/wishlist/:id" component={WishlistItem} />
45+
</>
46+
)}
47+
<Route component={NotFound} />
48+
</Switch>
49+
);
50+
}
51+
52+
function App() {
53+
return (
54+
<QueryClientProvider client={queryClient}>
55+
<TooltipProvider>
56+
<Toaster />
57+
<Router />
58+
</TooltipProvider>
59+
</QueryClientProvider>
60+
);
61+
}
62+
63+
export default App;
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import { useState, useEffect } from "react";
2+
import { useMutation } from "@tanstack/react-query";
3+
import { useLocation } from "wouter";
4+
import { useToast } from "@/hooks/use-toast";
5+
import { apiRequest, queryClient } from "@/lib/queryClient";
6+
import { isUnauthorizedError } from "@/lib/authUtils";
7+
import { Dialog, DialogContent } from "@/components/ui/dialog";
8+
import { Button } from "@/components/ui/button";
9+
import { Input } from "@/components/ui/input";
10+
import { Label } from "@/components/ui/label";
11+
import { Textarea } from "@/components/ui/textarea";
12+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
13+
import { Star, Plus, Loader2, ChevronDown } from "lucide-react";
14+
import type { WishlistItem } from "@shared/schema";
15+
16+
interface AddWishlistModalProps {
17+
open: boolean;
18+
onOpenChange: (open: boolean) => void;
19+
editingItem?: WishlistItem | null;
20+
}
21+
22+
export default function AddWishlistModal({ open, onOpenChange, editingItem }: AddWishlistModalProps) {
23+
const { toast } = useToast();
24+
const [, setLocation] = useLocation();
25+
26+
// Form state
27+
const [title, setTitle] = useState(editingItem?.title || "");
28+
const [description, setDescription] = useState(editingItem?.description || "");
29+
const [priority, setPriority] = useState(editingItem?.priority || "medium");
30+
31+
// Reset form when modal opens/closes or editing item changes
32+
useEffect(() => {
33+
if (open) {
34+
setTitle(editingItem?.title || "");
35+
setDescription(editingItem?.description || "");
36+
setPriority(editingItem?.priority || "medium");
37+
}
38+
}, [open, editingItem]);
39+
40+
const resetForm = () => {
41+
setTitle("");
42+
setDescription("");
43+
setPriority("medium");
44+
};
45+
46+
// Add/Update wishlist item mutation
47+
const wishlistMutation = useMutation({
48+
mutationFn: async () => {
49+
const itemData = { title, description, priority };
50+
51+
if (editingItem) {
52+
await apiRequest("PUT", `/api/wishlist/${editingItem.id}`, itemData);
53+
} else {
54+
await apiRequest("POST", "/api/wishlist", itemData);
55+
}
56+
},
57+
onSuccess: (data) => {
58+
queryClient.invalidateQueries({ queryKey: ["/api/profile"] });
59+
toast({
60+
title: "Success",
61+
description: editingItem ? "Wishlist item updated successfully!" : "Wishlist item added successfully!"
62+
});
63+
resetForm();
64+
onOpenChange(false);
65+
66+
// Redirect to wishlist page with new item highlighted
67+
if (!editingItem) {
68+
setLocation("/wishlist?highlight=new");
69+
}
70+
},
71+
onError: (error) => {
72+
if (isUnauthorizedError(error)) {
73+
toast({
74+
title: "Unauthorized",
75+
description: "You are logged out. Logging in again...",
76+
variant: "destructive",
77+
});
78+
setTimeout(() => {
79+
window.location.href = "/api/login";
80+
}, 500);
81+
return;
82+
}
83+
toast({
84+
title: "Error",
85+
description: `Failed to ${editingItem ? 'update' : 'add'} wishlist item. Please try again.`,
86+
variant: "destructive",
87+
});
88+
},
89+
});
90+
91+
const handleSubmit = () => {
92+
if (!title.trim()) {
93+
toast({
94+
title: "Validation Error",
95+
description: "Please fill in the title field.",
96+
variant: "destructive",
97+
});
98+
return;
99+
}
100+
wishlistMutation.mutate();
101+
};
102+
103+
const handleCancel = () => {
104+
resetForm();
105+
onOpenChange(false);
106+
};
107+
108+
return (
109+
<Dialog open={open} onOpenChange={onOpenChange}>
110+
<DialogContent className="max-w-2xl mx-auto bg-gradient-to-br from-white via-blue-50/30 to-purple-50/30 border-0 shadow-2xl">
111+
{/* Header */}
112+
<div className="text-center mb-8 pt-2">
113+
<div className="w-16 h-16 bg-gradient-to-br from-blue-100 via-indigo-100 to-purple-100 rounded-full flex items-center justify-center mx-auto mb-4 shadow-lg">
114+
<Star className="w-8 h-8 text-blue-600" />
115+
</div>
116+
<h2 className="text-2xl font-bold bg-gradient-to-r from-blue-700 via-indigo-700 to-purple-700 bg-clip-text text-transparent">
117+
{editingItem ? "Edit Wishlist Item" : "Add Wishlist Item"}
118+
</h2>
119+
<p className="text-gray-600 mt-2">
120+
{editingItem ? "Update your wishlist item details" : "Share what you're looking to achieve or acquire"}
121+
</p>
122+
</div>
123+
124+
{/* Form Content */}
125+
<div className="space-y-6">
126+
{/* Title and Priority Row */}
127+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
128+
<div className="space-y-3">
129+
<div className="flex items-center space-x-2">
130+
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center">
131+
<Star className="h-3.5 w-3.5 text-blue-600" />
132+
</div>
133+
<Label htmlFor="title" className="text-sm font-medium text-gray-900">Title</Label>
134+
</div>
135+
<div className="bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4">
136+
<Input
137+
id="title"
138+
value={title}
139+
onChange={(e) => setTitle(e.target.value)}
140+
placeholder="Learn Spanish, Find a mentor..."
141+
className="border-0 bg-white/70 backdrop-blur-sm shadow-sm focus:ring-2 focus:ring-blue-200 focus:bg-white transition-all duration-200"
142+
/>
143+
</div>
144+
</div>
145+
146+
<div className="space-y-3">
147+
<div className="flex items-center space-x-2">
148+
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center">
149+
<Star className="h-3.5 w-3.5 text-blue-600" />
150+
</div>
151+
<Label className="text-sm font-medium text-gray-900">Priority Level</Label>
152+
</div>
153+
<div className="bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4">
154+
<DropdownMenu>
155+
<DropdownMenuTrigger asChild>
156+
<Button variant="ghost" className="w-full justify-between border border-gray-200 hover:bg-gray-50">
157+
{priority === "urgent" && "🔥 Urgent!"}
158+
{priority === "high" && "High Priority"}
159+
{priority === "medium" && "Medium Priority"}
160+
{priority === "low" && "Low Priority"}
161+
<ChevronDown className="h-4 w-4" />
162+
</Button>
163+
</DropdownMenuTrigger>
164+
<DropdownMenuContent className="w-48">
165+
<DropdownMenuItem onClick={() => setPriority("urgent")}>
166+
🔥 Urgent!
167+
</DropdownMenuItem>
168+
<DropdownMenuItem onClick={() => setPriority("high")}>
169+
High Priority
170+
</DropdownMenuItem>
171+
<DropdownMenuItem onClick={() => setPriority("medium")}>
172+
Medium Priority
173+
</DropdownMenuItem>
174+
<DropdownMenuItem onClick={() => setPriority("low")}>
175+
Low Priority
176+
</DropdownMenuItem>
177+
</DropdownMenuContent>
178+
</DropdownMenu>
179+
</div>
180+
</div>
181+
</div>
182+
183+
{/* Description */}
184+
<div className="space-y-3">
185+
<div className="flex items-center space-x-2">
186+
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center">
187+
<Star className="h-3.5 w-3.5 text-blue-600" />
188+
</div>
189+
<Label htmlFor="description" className="text-sm font-medium text-gray-900">Description</Label>
190+
</div>
191+
<div className="bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4">
192+
<Textarea
193+
id="description"
194+
value={description}
195+
onChange={(e) => setDescription(e.target.value)}
196+
placeholder="Describe what you're looking for and why it matters to you..."
197+
rows={4}
198+
className="border-0 bg-white/70 backdrop-blur-sm shadow-sm focus:ring-2 focus:ring-blue-200 focus:bg-white transition-all duration-200 resize-none"
199+
/>
200+
</div>
201+
</div>
202+
</div>
203+
204+
{/* Action Buttons */}
205+
<div className="flex justify-center sm:justify-end space-x-3 pt-3">
206+
<Button
207+
variant="outline"
208+
onClick={handleCancel}
209+
className="px-6 border-gray-200 text-gray-600 hover:bg-gray-50 rounded-lg"
210+
>
211+
Cancel
212+
</Button>
213+
<Button
214+
onClick={handleSubmit}
215+
disabled={wishlistMutation.isPending}
216+
className="px-8 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-lg shadow-lg hover:shadow-xl transition-all duration-200"
217+
>
218+
{wishlistMutation.isPending ? (
219+
<>
220+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
221+
{editingItem ? "Updating..." : "Creating..."}
222+
</>
223+
) : (
224+
<>
225+
<Star className="mr-2 h-4 w-4" />
226+
{editingItem ? "Update Item" : "Add to Wishlist"}
227+
</>
228+
)}
229+
</Button>
230+
</div>
231+
</DialogContent>
232+
</Dialog>
233+
);
234+
}

0 commit comments

Comments
 (0)