feat: implement zero-auth offline bookmarking system with glassmorphic drawer and SSR hydration safety (#462)#482
Conversation
Aditya948351
left a comment
There was a problem hiding this comment.
LGTM! Approved. Great work on this.
|
@shivani11jadhav Do resolve this Conflicts |
|
@Aditya948351 , absolutely sorry for the trouble with the asset files! I definitely didn't mean to remove or alter any .png files. It seems my local environment accidentally tracked and staged the public assets and logo pointers incorrectly during the branch setup. I am actively working on cleaning up the branch right away to restore all untouched assets, ensuring only the core bookmarking components and hooks remain. Thanks for pointing it out |
…c drawer and SSR hydration safety (devpathindcommunity-india#462)
Absolutely Fine @shivani11jadhav , Correct that now and Waiting for the More Contributions of you on this Repo! |
8d589b6 to
d2751cc
Compare
|
@shivani11jadhav Look into those Conflicts again |
There was a problem hiding this comment.
Pull request overview
Adds a zero-auth, offline bookmarking feature backed by localStorage, including UI affordances on roadmap/project cards and a global bookmark drawer accessible from the navbar, with URL-based deep-linking to open specific roadmaps.
Changes:
- Introduces a
useBookmarkshook to read/write bookmarks fromlocalStorageand sync updates across components/tabs. - Adds bookmark toggle buttons to
PremiumCard(roadmaps) andProjectCard(projects). - Adds a glassmorphic
BookmarkDrawerand a navbar trigger; supports opening a roadmap modal via?open=roadmap&title=....
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/firebase.ts | Adds fallback “mock” Firebase env values (needs production safety guard). |
| src/hooks/useBookmarks.ts | New hook providing bookmark CRUD + hydration guard; localStorage synchronization. |
| src/components/ui/PremiumCard.tsx | Adds optional bookmark overlay button for cards. |
| src/components/ui/BookmarkDrawer.tsx | New animated drawer listing/removing bookmarks and linking to saved items. |
| src/components/projects/ProjectCard.tsx | Adds offline bookmark overlay button to project cards. |
| src/components/layout/Navbar.tsx | Adds navbar bookmark icon and mounts the bookmark drawer. |
| src/components/home/ResourcesTabs.tsx | Adds deep-link handler for opening a specific roadmap modal and passes bookmark metadata into cards. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY || "mock-api-key-for-local-testing-only-123456", | ||
| authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN || "mock-app.firebaseapp.com", | ||
| projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID || "mock-app-id", | ||
| storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET || "mock-app.appspot.com", | ||
| messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID || "1234567890", |
| try { | ||
| const data = localStorage.getItem('devpath_saved_roadmaps'); | ||
| return data ? JSON.parse(data) : []; | ||
| } catch (e) { |
| const addBookmark = (item: BookmarkItem) => { | ||
| const current = getSavedBookmarks(); | ||
| if (current.some(b => b.id === item.id)) return; | ||
| const updated = [...current, item]; | ||
| localStorage.setItem('devpath_saved_roadmaps', JSON.stringify(updated)); | ||
| listeners.forEach(listener => listener()); | ||
| }; | ||
|
|
||
| const removeBookmark = (id: string) => { | ||
| const current = getSavedBookmarks(); | ||
| const updated = current.filter(b => b.id !== id); | ||
| localStorage.setItem('devpath_saved_roadmaps', JSON.stringify(updated)); | ||
| listeners.forEach(listener => listener()); | ||
| }; |
| if (current.some(b => b.id === item.id)) return; | ||
| const updated = [...current, item]; | ||
| localStorage.setItem('devpath_saved_roadmaps', JSON.stringify(updated)); | ||
| listeners.forEach(listener => listener()); | ||
| }; | ||
|
|
||
| const removeBookmark = (id: string) => { | ||
| const current = getSavedBookmarks(); | ||
| const updated = current.filter(b => b.id !== id); | ||
| localStorage.setItem('devpath_saved_roadmaps', JSON.stringify(updated)); |
| <button | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| e.preventDefault(); | ||
| toggleBookmark(bookmarkItem); | ||
| }} | ||
| className={`absolute top-4 right-4 z-30 p-2.5 rounded-xl border backdrop-blur-md transition-all shadow-md hover:scale-105 active:scale-95 ${ | ||
| bookmarked | ||
| ? 'bg-yellow-500/20 dark:bg-yellow-500/30 border-yellow-500/50 text-yellow-500 hover:bg-yellow-500/30' | ||
| : 'bg-black/5 dark:bg-white/5 border-black/10 dark:border-white/10 text-muted-foreground hover:text-foreground hover:bg-black/10 dark:hover:bg-white/10' | ||
| }`} | ||
| title={bookmarked ? "Remove Bookmark" : "Save Bookmark"} | ||
| > |
| {item.type === 'roadmap' ? ( | ||
| <Link | ||
| href={`/resources?open=roadmap&title=${encodeURIComponent(item.title)}`} | ||
| onClick={onClose} | ||
| className="inline-flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 bg-primary text-white rounded-lg hover:bg-primary/95 shadow-sm transition-all" | ||
| > | ||
| <Map size={12} /> | ||
| View Roadmap | ||
| <ExternalLink size={10} /> | ||
| </Link> | ||
| ) : ( | ||
| <Link | ||
| href="/profile" | ||
| onClick={onClose} | ||
| className="inline-flex items-center gap-1.5 text-xs font-semibold px-3 py-1.5 bg-primary text-white rounded-lg hover:bg-primary/95 shadow-sm transition-all" | ||
| > | ||
| <Code size={12} /> | ||
| View Project | ||
| <ExternalLink size={10} /> | ||
| </Link> | ||
| )} |
| <button | ||
| onClick={onClose} | ||
| className="p-2 hover:bg-black/5 dark:hover:bg-white/5 rounded-full transition-colors text-muted-foreground hover:text-foreground" | ||
| > | ||
| <X size={20} /> |
| <button | ||
| onClick={() => removeBookmark(item.id)} | ||
| className="p-1.5 hover:bg-red-500/10 text-muted-foreground hover:text-red-500 rounded-lg transition-colors" | ||
| title="Remove Bookmark" | ||
| > | ||
| <Trash2 size={16} /> |
| const getSavedBookmarks = (): BookmarkItem[] => { | ||
| if (typeof window === 'undefined') return []; | ||
| try { | ||
| const data = localStorage.getItem('devpath_saved_roadmaps'); |
| <motion.div | ||
| className="fixed top-0 right-0 bottom-0 z-[1600] w-full max-w-md bg-white/95 dark:bg-[#0f1419]/95 backdrop-blur-xl border-l border-black/5 dark:border-white/10 shadow-2xl flex flex-col" | ||
| initial={{ x: '100%' }} | ||
| animate={{ x: 0 }} | ||
| exit={{ x: '100%' }} | ||
| transition={{ type: 'spring', damping: 25, stiffness: 200 }} | ||
| > |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Aditya948351
left a comment
There was a problem hiding this comment.
I've reviewed the latest changes and the Copilot feedback. Please address Copilot's point about src/lib/firebase.ts—make sure to add a production safety guard so those fallback 'mock' Firebase env values are strictly limited to development and don't leak into production.
Also, as mentioned earlier, please make sure all merge conflicts are completely resolved and the accidentally tracked .png assets are reverted so we can finally get this merged in!
🧩 Linked Issue
Closes #462
🧠 Feature Overview & Engineering Design
To align with DevPath's standard "no-nonsense, zero-authentication direct learning" model, this PR introduces a production-ready, ultra-lightweight Offline Roadmap Bookmarking System. Users can now persistently save, organize, and toggle their preferred roadmaps and projects instantly across sessions without requiring an authenticated account layer.
🛠️ Core Implementation Details:
useBookmarks.ts(Reactive Offline State Engine):localStorageAPI.BookmarkDrawer.tsx(Premium Glassmorphic Panel):framer-motionspring mechanics.backdrop-blur-xl bg-[#0f1419]/95).Card-Level Trigger Overlays (
PremiumCard.tsx&ProjectCard.tsx):e.stopPropagation()handling to ensure bookmark toggling doesn't trigger parent card route modals.🧪 Verification & Build Status
npx tsc --noEmitchecks fully green).