#DONE
Improve the Profile page UX by reducing visual clutter and making important information more visible.
- Auto-save indicator is hard to spot - Can appear off-screen when user scrolls
- Upload CV section takes too much space - Even when CV is already uploaded
- Checkbox lists are overwhelming - Too many options visible at once
- Parsed CV Data section is buried - Most important info is at the bottom, easy to miss
Create a reusable toast notification system for use throughout the app - auto-save, API responses, user actions, errors, etc.
Requirements:
- Fixed position (bottom-right corner), always visible regardless of scroll
- Multiple toast types: success, error, warning, info
- Auto-dismiss after 3 seconds (configurable)
- Stack multiple toasts if needed
- Accessible from anywhere in the app via hook or context
Example usage:
// Auto-save
toast.success('Changes saved')
// API responses
toast.success('Cover letter generated')
toast.error('Failed to fetch jobs')
// User actions
toast.info('Job added to matches')
toast.warning('You have unsaved changes')Implementation options:
react-hot-toast- lightweight, easy to use (recommended)- Custom implementation with Context + Portal
Setup with react-hot-toast:
// App.tsx
import { Toaster } from 'react-hot-toast'
function App() {
return (
<>
<Toaster position="bottom-right" />
{/* rest of app */}
</>
)
}
// Anywhere in the app
import toast from 'react-hot-toast'
toast.success('It works!')Use for:
- Auto-save confirmations
- API success/error responses
- Form submissions
- Job scraping status
- Cover letter generation
- Any user feedback
Current: Large dropzone always visible, even when CV uploaded
New behavior:
- If CV is uploaded, show compact version:
📄 CV-Nanni-Software-Dev.pdf (uploaded 21/12/2025) [Replace] - "Replace" button opens file picker
- Only show full dropzone when no CV is uploaded
Component structure:
{hasCV ? (
<CompactCVDisplay filename={cv.filename} date={cv.uploadedAt} onReplace={handleReplace} />
) : (
<CVDropzone onUpload={handleUpload} />
)}Convert all checkbox lists to expandable/collapsible sections.
Current: All checkboxes visible, overwhelming
New:
▶ Job Types (2 selected)
▶ Remote Work Preference (1 selected)
▶ Preferred Countries (2 selected)
▶ Employment Eligibility (1 selected)
Behavior:
- Show selected count in header
- Collapsed by default
- Click header to expand/collapse
- Optionally remember expand/collapse state in localStorage
Apply to:
- Job Types
- Remote Work Preference
- Preferred Countries/Locations
- Employment Eligibility
Current order:
- Profile Information
- Upload CV + Job Preferences (side by side)
- Parsed CV Data (bottom, easy to miss)
New order:
- Profile Information (compact: name, experience, CV file status)
- Parsed CV Data (skills, experience, education) - THE MAIN CONTENT
- Job Preferences (collapsible sections)
This puts the most important information (parsed CV) front and center.
| File | Changes |
|---|---|
frontend/src/pages/ProfilePage.tsx |
Reorder sections, integrate toast |
frontend/src/components/CVUpload.tsx |
Add compact mode when CV exists |
frontend/src/components/PreferencesForm.tsx |
Add collapsible checkbox groups |
frontend/src/components/Toast.tsx |
New component (or use react-hot-toast) |
frontend/src/hooks/useAutoSave.ts |
Trigger toast on save success/error |
- Use
react-hot-toastfor quick implementation, or build custom if you want more control - For collapsible sections, can use simple useState or a small component:
<CollapsibleSection title="Job Types" selectedCount={2}> {/* checkboxes */} </CollapsibleSection>
- Test on mobile - collapsible sections help a lot on small screens
- Toast appears on save (visible regardless of scroll position)
- CV upload section is compact when CV exists
- All checkbox groups are collapsible
- Parsed CV Data is above Job Preferences
- Page feels less cluttered and more scannable
#TODO
Expand job sources beyond tech/software to include creative, design, and 3D job boards. The existing matching algorithm will handle relevance - users with creative skills will match creative jobs.
| Site | Type | Notes |
|---|---|---|
| Dribbble Jobs | API | Has public API, design/UI/UX focused |
| Authentic Jobs | RSS | Has RSS feed, design + dev |
| Site | Type | Notes |
|---|---|---|
| ArtStation Jobs | Scrape | 3D, game art, VFX, concept art |
| Behance Jobs | Scrape | Adobe's creative job board |
- Motionographer (motion design)
- Creativepool
- Working Not Working
- Krop
Follow existing RemoteOK scraper pattern in backend/app/scrapers/:
# backend/app/scrapers/dribbble.py
async def scrape_dribbble() -> list[dict]:
"""
Scrape Dribbble jobs API
Returns normalized job format
"""
# Fetch from API/RSS/HTML
# Normalize to standard job format
# Return list of jobsAll scrapers must return jobs in this format:
{
"source": "dribbble", # unique source identifier
"source_id": "123456", # unique ID from source
"title": "Senior 3D Artist",
"company": "Studio Name",
"description": "Full job description...",
"url": "https://dribbble.com/jobs/123456",
"location": "Remote",
"salary_min": None,
"salary_max": None,
"tags": ["3D", "Blender", "Maya"],
"remote_type": "full", # full, hybrid, onsite
"posted_at": datetime,
}Update backend/app/services/scraper.py:
from app.scrapers import remoteok, dribbble, artstation
async def scrape_all_sources():
"""Run all enabled scrapers"""
jobs = []
jobs.extend(await remoteok.scrape())
jobs.extend(await dribbble.scrape())
jobs.extend(await artstation.scrape())
# ... dedupe and save to DBJobs are deduped by (source, source_id) composite key - already implemented.
- URL: https://dribbble.com/jobs
- Check for API: https://dribbble.com/api or scrape HTML
- Categories: Product Design, UI/UX, Brand, Illustration, Animation
- URL: https://authenticjobs.com/
- Has RSS: https://authenticjobs.com/rss/index.xml
- Categories: Design, Development, Creative
- URL: https://www.artstation.com/jobs
- Will need HTML scraping
- Categories: 3D, Concept Art, Game Art, VFX, Animation
- URL: https://www.behance.net/joblist
- Adobe-owned, may need scraping
- Categories: Graphic Design, UI/UX, 3D, Motion
Create:
backend/app/scrapers/dribbble.pybackend/app/scrapers/authentic_jobs.pybackend/app/scrapers/artstation.pybackend/app/scrapers/behance.py
Modify:
backend/app/services/scraper.py- Add new sourcesbackend/app/routers/jobs.py- Maybe add source filter param
- Run each scraper individually and verify job format
- Check deduplication works (run twice, no duplicates)
- Verify jobs appear in /jobs endpoint
- Test that creative jobs match creative CVs (not dev CVs)
- No UI changes needed - matching algorithm handles relevance
- No new database columns needed
- Start with Dribbble (has API), then add others
- Be respectful with scraping - add delays, respect robots.txt