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 numberDiff line numberDiff 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
Copy Markdown
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 numberDiff line numberDiff 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
Copy Markdown
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
<metaname="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<metaname="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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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 numberDiff line numberDiff 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
Copy Markdown
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 numberDiff line numberDiff 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
Copy Markdown
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
<metaname="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<metaname="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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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
Copy Markdown
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 numberDiff line numberDiff 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
Copy Markdown
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
<metaname="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<metaname="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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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
Copy Markdown
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 numberDiff line numberDiff 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
Copy Markdown
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
<metaname="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<metaname="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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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 numberDiff line numberDiff 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
Copy Markdown
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 numberDiff line numberDiff 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
Copy Markdown
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
<metaname="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<metaname="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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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
Copy Markdown
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 numberDiff line numberDiff 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
Copy Markdown
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
<metaname="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<metaname="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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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 numberDiff line numberDiff 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
Copy Markdown
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 numberDiff line numberDiff 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
Copy Markdown
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
<metaname="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<metaname="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 numberDiff line numberDiff 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 numberDiff line numberDiff 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
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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 numberDiff line numberDiff 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
Copy Markdown
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 numberDiff line numberDiff 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
Copy Markdown
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
<metaname="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
<metaname="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 numberDiff line numberDiff 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 numberDiff line numberDiff 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