Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions platforms/dreamSync/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
dist
.DS_Store
server/public
vite.config.ts.*
*.tar.gz
Comment on lines +1 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add .env ignores to prevent secrets leakage.
These often slip into commits; keep an example file tracked.

Apply:

 node_modules
 dist
 .DS_Store
 server/public
 vite.config.ts.*
 *.tar.gz
+# env files (keep examples)
+.env
+.env.local
+.env.*.local
+!.env.example
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
node_modules
dist
.DS_Store
server/public
vite.config.ts.*
*.tar.gz
node_modules
dist
.DS_Store
server/public
vite.config.ts.*
*.tar.gz
# env files (keep examples)
.env
.env.local
.env.*.local
!.env.example
🤖 Prompt for AI Agents
In platforms/dreamSync/.gitignore lines 1-6, add rules to ignore environment
files (e.g., .env, .env.local, .env.*.local and other env variants) to prevent
secrets from being committed, while ensuring an example file like .env.example
remains tracked; update the file by appending the appropriate .env ignore
patterns and verify .env.example is not matched by those patterns.

13 changes: 13 additions & 0 deletions platforms/dreamSync/client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don’t restrict zoom; it hurts accessibility.

Removing maximum-scale=1 improves compliance (users can zoom).

-    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
🤖 Prompt for AI Agents
In platforms/dreamSync/client/index.html around line 5, the meta viewport tag
sets maximum-scale=1 which prevents user zoom and harms accessibility; remove
the maximum-scale=1 (and any related user-scalable=no) from the content
attribute so users can zoom, leaving something like width=device-width,
initial-scale=1.0 to preserve responsive behavior while restoring accessibility.

</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<!-- This is a replit script which adds a banner on the top of the page when opened in development mode outside the replit environment -->
<script type="text/javascript" src="https://replit.com/public/js/replit-dev-banner.js"></script>
</body>
</html>
63 changes: 63 additions & 0 deletions platforms/dreamSync/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Switch, Route } from "wouter";
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { useAuth } from "@/hooks/useAuth";
import { Heart } from "lucide-react";
import NotFound from "@/pages/not-found";
import Login from "@/pages/login";
import Dashboard from "@/pages/dashboard";
import Profile from "@/pages/profile";
import MatchesNew from "@/pages/matches-new";
import Suggestions from "@/pages/suggestions";
import Wishlist from "@/pages/wishlist";
import WishlistItem from "@/pages/wishlist-item";

function Router() {
const { isAuthenticated, isLoading } = useAuth();

if (isLoading) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 flex items-center justify-center">
<div className="text-center">
<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">
<Heart className="w-8 h-8 text-blue-600" />
</div>
<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>
</div>
</div>
);
}

return (
<Switch>
{!isAuthenticated ? (
<Route path="/" component={Login} />
) : (
<>
<Route path="/" component={Dashboard} />
<Route path="/profile" component={Profile} />
<Route path="/matches" component={MatchesNew} />
<Route path="/suggestions" component={Suggestions} />
<Route path="/wishlist" component={Wishlist} />
<Route path="/wishlist/:id" component={WishlistItem} />
</>
)}
<Route component={NotFound} />
</Switch>
);
}

function App() {
return (
<QueryClientProvider client={queryClient}>
<TooltipProvider>
<Toaster />
<Router />
</TooltipProvider>
</QueryClientProvider>
);
}

export default App;
234 changes: 234 additions & 0 deletions platforms/dreamSync/client/src/components/add-wishlist-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { useLocation } from "wouter";
import { useToast } from "@/hooks/use-toast";
import { apiRequest, queryClient } from "@/lib/queryClient";
import { isUnauthorizedError } from "@/lib/authUtils";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Star, Plus, Loader2, ChevronDown } from "lucide-react";
import type { WishlistItem } from "@shared/schema";

interface AddWishlistModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
editingItem?: WishlistItem | null;
}

export default function AddWishlistModal({ open, onOpenChange, editingItem }: AddWishlistModalProps) {
const { toast } = useToast();
const [, setLocation] = useLocation();

// Form state
const [title, setTitle] = useState(editingItem?.title || "");
const [description, setDescription] = useState(editingItem?.description || "");
const [priority, setPriority] = useState(editingItem?.priority || "medium");

// Reset form when modal opens/closes or editing item changes
useEffect(() => {
if (open) {
setTitle(editingItem?.title || "");
setDescription(editingItem?.description || "");
setPriority(editingItem?.priority || "medium");
}
}, [open, editingItem]);

const resetForm = () => {
setTitle("");
setDescription("");
setPriority("medium");
};

// Add/Update wishlist item mutation
const wishlistMutation = useMutation({
mutationFn: async () => {
const itemData = { title, description, priority };

if (editingItem) {
await apiRequest("PUT", `/api/wishlist/${editingItem.id}`, itemData);
} else {
await apiRequest("POST", "/api/wishlist", itemData);
}
},
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ["/api/profile"] });
toast({
title: "Success",
description: editingItem ? "Wishlist item updated successfully!" : "Wishlist item added successfully!"
});
resetForm();
onOpenChange(false);

// Redirect to wishlist page with new item highlighted
if (!editingItem) {
setLocation("/wishlist?highlight=new");
}
},
onError: (error) => {
if (isUnauthorizedError(error)) {
toast({
title: "Unauthorized",
description: "You are logged out. Logging in again...",
variant: "destructive",
});
setTimeout(() => {
window.location.href = "/api/login";
}, 500);
return;
}
toast({
title: "Error",
description: `Failed to ${editingItem ? 'update' : 'add'} wishlist item. Please try again.`,
variant: "destructive",
});
},
});

const handleSubmit = () => {
if (!title.trim()) {
toast({
title: "Validation Error",
description: "Please fill in the title field.",
variant: "destructive",
});
return;
}
wishlistMutation.mutate();
};

const handleCancel = () => {
resetForm();
onOpenChange(false);
};

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<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">
{/* Header */}
<div className="text-center mb-8 pt-2">
<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">
<Star className="w-8 h-8 text-blue-600" />
</div>
<h2 className="text-2xl font-bold bg-gradient-to-r from-blue-700 via-indigo-700 to-purple-700 bg-clip-text text-transparent">
{editingItem ? "Edit Wishlist Item" : "Add Wishlist Item"}
</h2>
<p className="text-gray-600 mt-2">
{editingItem ? "Update your wishlist item details" : "Share what you're looking to achieve or acquire"}
</p>
</div>

{/* Form Content */}
<div className="space-y-6">
{/* Title and Priority Row */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-3">
<div className="flex items-center space-x-2">
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center">
<Star className="h-3.5 w-3.5 text-blue-600" />
</div>
<Label htmlFor="title" className="text-sm font-medium text-gray-900">Title</Label>
</div>
<div className="bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4">
<Input
id="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Learn Spanish, Find a mentor..."
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"
/>
</div>
</div>

<div className="space-y-3">
<div className="flex items-center space-x-2">
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center">
<Star className="h-3.5 w-3.5 text-blue-600" />
</div>
<Label className="text-sm font-medium text-gray-900">Priority Level</Label>
</div>
<div className="bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="w-full justify-between border border-gray-200 hover:bg-gray-50">
{priority === "urgent" && "🔥 Urgent!"}
{priority === "high" && "High Priority"}
{priority === "medium" && "Medium Priority"}
{priority === "low" && "Low Priority"}
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-48">
<DropdownMenuItem onClick={() => setPriority("urgent")}>
🔥 Urgent!
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setPriority("high")}>
High Priority
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setPriority("medium")}>
Medium Priority
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setPriority("low")}>
Low Priority
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>

{/* Description */}
<div className="space-y-3">
<div className="flex items-center space-x-2">
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-blue-100 to-purple-100 flex items-center justify-center">
<Star className="h-3.5 w-3.5 text-blue-600" />
</div>
<Label htmlFor="description" className="text-sm font-medium text-gray-900">Description</Label>
</div>
<div className="bg-gradient-to-r from-blue-50/40 to-purple-50/40 rounded-xl p-4">
<Textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Describe what you're looking for and why it matters to you..."
rows={4}
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"
/>
</div>
</div>
</div>

{/* Action Buttons */}
<div className="flex justify-center sm:justify-end space-x-3 pt-3">
<Button
variant="outline"
onClick={handleCancel}
className="px-6 border-gray-200 text-gray-600 hover:bg-gray-50 rounded-lg"
>
Cancel
</Button>
<Button
onClick={handleSubmit}
disabled={wishlistMutation.isPending}
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"
>
{wishlistMutation.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
{editingItem ? "Updating..." : "Creating..."}
</>
) : (
<>
<Star className="mr-2 h-4 w-4" />
{editingItem ? "Update Item" : "Add to Wishlist"}
</>
)}
</Button>
</div>
</DialogContent>
</Dialog>
);
}
Loading
Loading