diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index 36eb1098..f59511a6 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -1,4 +1,5 @@ + > [!IMPORTANT] > This project is connected to [Lovable](https://lovable.dev). Avoid rewriting > published git history โ€” force pushing, or rebasing/amending/squashing commits @@ -7,4 +8,5 @@ > > Commits you push to the connected branch sync back to Lovable and show up in > the editor, so keep the branch in a working state. + diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index f2f96260..f827174b 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -64,9 +64,7 @@ const groups: Group[] = [ }, { label: "Account", - items: [ - { label: "Settings", to: "/settings", icon: }, - ], + items: [{ label: "Settings", to: "/settings", icon: }], }, ]; @@ -147,10 +145,7 @@ function SidebarGroup({ group, onNav }: { group: Group; onNav: () => void }) { className="flex w-full items-center justify-between px-2 py-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground hover:text-foreground" > {group.label} - + {open && ( @@ -161,7 +156,8 @@ function SidebarGroup({ group, onNav }: { group: Group; onNav: () => void }) { className="overflow-hidden" > {group.items.map((item) => { - const active = pathname === item.to || pathname.startsWith(item.to.split("?")[0] + "/"); + const active = + pathname === item.to || pathname.startsWith(item.to.split("?")[0] + "/"); return (
  • +
    ), img: ({ node, ...props }) => ( - // eslint-disable-next-line jsx-a11y/alt-text - + ), h1: ({ node, ...props }) => (

    @@ -46,17 +54,30 @@ export const Markdown = memo(function Markdown({ content, className }: MarkdownP

    ), h3: ({ node, ...props }) => ( -

    +

    ), h4: ({ node, ...props }) => ( -

    +

    ), p: ({ node, ...props }) =>

    , - ul: ({ node, ...props }) =>

      , - ol: ({ node, ...props }) =>
        , + ul: ({ node, ...props }) => ( +
          + ), + ol: ({ node, ...props }) => ( +
            + ), li: ({ node, ...props }) =>
          1. , blockquote: ({ node, ...props }) => ( -
            +
            ), hr: () =>
            , table: ({ node, ...props }) => ( @@ -66,14 +87,22 @@ export const Markdown = memo(function Markdown({ content, className }: MarkdownP ), thead: ({ node, ...props }) => , th: ({ node, ...props }) => ( - + + ), + td: ({ node, ...props }) => ( + ), - td: ({ node, ...props }) => , code: ({ node, className: codeClassName, children, ...props }) => { const isBlock = /language-/.test(codeClassName ?? ""); if (!isBlock) { return ( - + {children} ); @@ -85,7 +114,10 @@ export const Markdown = memo(function Markdown({ content, className }: MarkdownP ); }, pre: ({ node, ...props }) => ( -
            +            
                       ),
                     }}
                   >
            diff --git a/frontend/src/components/shared/primitives.tsx b/frontend/src/components/shared/primitives.tsx
            index 85a28fa2..4cc0689d 100644
            --- a/frontend/src/components/shared/primitives.tsx
            +++ b/frontend/src/components/shared/primitives.tsx
            @@ -1,6 +1,7 @@
             import { cn } from "@/lib/utils";
             import { Link } from "@tanstack/react-router";
             import type { ReactNode } from "react";
            +import { FolderKanban, BellOff, MessageSquareDashed, UserPlus, Search } from "lucide-react";
             
             export function SectionHeader({
               title,
            @@ -18,16 +19,11 @@ export function SectionHeader({
                   

            {title}

            {action && (actionTo ? ( - + {action} ) : ( - + ))}

    ); @@ -61,23 +57,74 @@ export function EmptyState({ title, desc, action, + variant = "default", + className, }: { title: string; desc?: string; action?: ReactNode; + variant?: "projects" | "notifications" | "messages" | "connections" | "search" | "default"; + className?: string; }) { + const renderIllustration = () => { + switch (variant) { + case "projects": + return ( +
    + +
    +
    + ); + case "notifications": + return ( +
    + +
    +
    + ); + case "messages": + return ( +
    + +
    +
    + ); + case "connections": + return ( +
    + +
    +
    + ); + case "search": + return ( +
    + +
    +
    + ); + default: + return ( +
    + โœจ +
    + ); + } + }; + return ( -
    -
    - โœจ +
    +
    + {renderIllustration()}
    -

    {title}

    - {desc &&

    {desc}

    } - {action &&
    {action}
    } +

    {title}

    + {desc &&

    {desc}

    } + {action &&
    {action}
    }
    ); } + export function TagChip({ children, className }: { children: ReactNode; className?: string }) { return ( {label}

    {value} - {suffix && {suffix}} + {suffix && ( + {suffix} + )}

    {progress !== undefined && (
    diff --git a/frontend/src/features/dashboard/sections.tsx b/frontend/src/features/dashboard/sections.tsx index 3d82a180..b6a20699 100644 --- a/frontend/src/features/dashboard/sections.tsx +++ b/frontend/src/features/dashboard/sections.tsx @@ -124,7 +124,12 @@ export function InviteRequests() {
      {data.map((r) => (
    • - + {r.icon}
      @@ -170,9 +175,7 @@ export function SuggestedBuilders() { {s} ))}
      -

      - {b.yearsExp} yrs exp -

      +

      {b.yearsExp} yrs exp

      {b.matchScore}% Match

    • @@ -354,14 +393,22 @@ export function UpcomingDeadlines() { } export function NotificationsFeed() { - const { data = [] } = useQuery({ queryKey: ["notifications"], queryFn: notificationsService.list }); + const { data = [] } = useQuery({ + queryKey: ["notifications"], + queryFn: notificationsService.list, + }); return (
        {data.map((n) => (
      • - +

        {n.text}

        {n.ago}
      • diff --git a/frontend/src/lib/logo.ts b/frontend/src/lib/logo.ts index b6de47d3..201bdac5 100644 --- a/frontend/src/lib/logo.ts +++ b/frontend/src/lib/logo.ts @@ -1,3 +1,3 @@ import logo from "@/assets/lodo-dev.jpeg"; -export const APP_LOGO = logo; \ No newline at end of file +export const APP_LOGO = logo; diff --git a/frontend/src/mocks/seed.ts b/frontend/src/mocks/seed.ts index 1e130527..8388209b 100644 --- a/frontend/src/mocks/seed.ts +++ b/frontend/src/mocks/seed.ts @@ -3,7 +3,9 @@ export type ID = string; -export interface Skill { name: string; } +export interface Skill { + name: string; +} export interface Builder { id: ID; name: string; @@ -16,6 +18,7 @@ export interface Builder { skills: string[]; online: boolean; bio: string; + availability: string; } export interface Project { id: ID; @@ -110,27 +113,261 @@ const AV = (seed: string) => `https://api.dicebear.com/9.x/notionists-neutral/svg?seed=${encodeURIComponent(seed)}&backgroundColor=b6e3f4,c0aede,d1d4f9,ffd5dc,ffdfbf`; export const builders: Builder[] = [ - { id: "b1", name: "Priya Sharma", handle: "priya_dev", role: "Frontend Developer", avatar: AV("Priya"), country: "India", yearsExp: 3, matchScore: 92, skills: ["React", "Next.js", "TypeScript"], online: true, bio: "Loves accessible UIs and design systems." }, - { id: "b2", name: "Rahul Verma", handle: "rahul_v", role: "Full Stack Developer", avatar: AV("Rahul"), country: "India", yearsExp: 4, matchScore: 89, skills: ["Node.js", "MongoDB", "Express"], online: true, bio: "Builds end-to-end features fast." }, - { id: "b3", name: "Ankit Singh", handle: "ankit_be", role: "Backend Developer", avatar: AV("Ankit"), country: "India", yearsExp: 2, matchScore: 87, skills: ["Python", "FastAPI", "PostgreSQL"], online: false, bio: "APIs, queues and Postgres tuning." }, - { id: "b4", name: "Sneha Iyer", handle: "sneha_ux", role: "UI/UX Designer", avatar: AV("Sneha"), country: "India", yearsExp: 3, matchScore: 94, skills: ["Figma", "Adobe XD"], online: true, bio: "Product design for early-stage teams." }, - { id: "b5", name: "Vikram Mehta", handle: "vikram_fs", role: "Full Stack Dev", avatar: AV("Vikram"), country: "India", yearsExp: 4, matchScore: 93, skills: ["MERN", "Next.js"], online: false, bio: "Ships side-projects on weekends." }, - { id: "b6", name: "Aditya Rao", handle: "aditya_m", role: "Mobile Developer", avatar: AV("Aditya"), country: "India", yearsExp: 3, matchScore: 91, skills: ["Flutter", "Firebase"], online: true, bio: "Cross-platform mobile since 2021." }, - { id: "b7", name: "Sarah Chen", handle: "sarah_c", role: "ML Engineer", avatar: AV("Sarah"), country: "US", yearsExp: 5, matchScore: 88, skills: ["Python", "PyTorch", "AWS"], online: true, bio: "Recsys, embeddings, evals." }, - { id: "b8", name: "Alex Johnson", handle: "alex_j", role: "DevOps", avatar: AV("Alex"), country: "UK", yearsExp: 6, matchScore: 86, skills: ["Kubernetes", "Terraform"], online: false, bio: "Infra as code, cost optimization." }, + { + id: "b1", + name: "Priya Sharma", + handle: "priya_dev", + role: "Frontend Developer", + avatar: AV("Priya"), + country: "India", + yearsExp: 3, + matchScore: 92, + skills: ["React", "Next.js", "TypeScript"], + online: true, + bio: "Loves accessible UIs and design systems.", + availability: "Full-time (40h/w)", + }, + { + id: "b2", + name: "Rahul Verma", + handle: "rahul_v", + role: "Full Stack Developer", + avatar: AV("Rahul"), + country: "India", + yearsExp: 4, + matchScore: 89, + skills: ["Node.js", "MongoDB", "Express"], + online: true, + bio: "Builds end-to-end features fast.", + availability: "Part-time (20h/w)", + }, + { + id: "b3", + name: "Ankit Singh", + handle: "ankit_be", + role: "Backend Developer", + avatar: AV("Ankit"), + country: "India", + yearsExp: 2, + matchScore: 87, + skills: ["Python", "FastAPI", "PostgreSQL"], + online: false, + bio: "APIs, queues and Postgres tuning.", + availability: "Full-time (40h/w)", + }, + { + id: "b4", + name: "Sneha Iyer", + handle: "sneha_ux", + role: "UI/UX Designer", + avatar: AV("Sneha"), + country: "India", + yearsExp: 3, + matchScore: 94, + skills: ["Figma", "Adobe XD"], + online: true, + bio: "Product design for early-stage teams.", + availability: "Contract (30h/w)", + }, + { + id: "b5", + name: "Vikram Mehta", + handle: "vikram_fs", + role: "Full Stack Dev", + avatar: AV("Vikram"), + country: "India", + yearsExp: 4, + matchScore: 93, + skills: ["MERN", "Next.js"], + online: false, + bio: "Ships side-projects on weekends.", + availability: "Part-time (15h/w)", + }, + { + id: "b6", + name: "Aditya Rao", + handle: "aditya_m", + role: "Mobile Developer", + avatar: AV("Aditya"), + country: "India", + yearsExp: 3, + matchScore: 91, + skills: ["Flutter", "Firebase"], + online: true, + bio: "Cross-platform mobile since 2021.", + availability: "Not Available", + }, + { + id: "b7", + name: "Sarah Chen", + handle: "sarah_c", + role: "ML Engineer", + avatar: AV("Sarah"), + country: "US", + yearsExp: 5, + matchScore: 88, + skills: ["Python", "PyTorch", "AWS"], + online: true, + bio: "Recsys, embeddings, evals.", + availability: "Full-time (40h/w)", + }, + { + id: "b8", + name: "Alex Johnson", + handle: "alex_j", + role: "DevOps", + avatar: AV("Alex"), + country: "UK", + yearsExp: 6, + matchScore: 86, + skills: ["Kubernetes", "Terraform"], + online: false, + bio: "Infra as code, cost optimization.", + availability: "Contract (40h/w)", + }, ]; export const projects: Project[] = [ - { id: "p1", name: "AI Chatbot", description: "Multi-agent customer support bot for SaaS.", stack: ["React", "Node.js", "MongoDB"], owner: "Nancy Patel", members: 4, stars: 24, forks: 12, progress: 75, status: "active", icon: "๐Ÿค–", language: "JavaScript", difficulty: "intermediate", remote: true, paid: true, openSource: false, ai: true, web: true, frontend: true, backend: true }, - { id: "p2", name: "AI SaaS Platform", description: "Full-stack platform with billing and dashboards.", stack: ["Next.js", "Python", "PostgreSQL"], owner: "Nancy Patel", members: 6, stars: 18, forks: 8, progress: 40, status: "active", icon: "โœจ", language: "Python", difficulty: "advanced", remote: true, paid: true, openSource: false, ai: true, web: true, frontend: true, backend: true }, - { id: "p3", name: "DevOps Dashboard", description: "K8s deploy monitoring with drift detection.", stack: ["Docker", "Kubernetes", "AWS"], owner: "Nancy Patel", members: 3, stars: 16, forks: 6, progress: 60, status: "active", icon: "๐Ÿš€", language: "Go", difficulty: "advanced", remote: true, paid: false, openSource: false, ai: false, web: true, backend: true }, - { id: "p4", name: "Blockchain Wallet", description: "Non-custodial multi-chain wallet.", stack: ["Solidity", "Web3", "React"], owner: "Nancy Patel", members: 5, stars: 14, forks: 7, progress: 25, status: "planning", icon: "๐Ÿช™", language: "TypeScript", difficulty: "advanced", remote: true, paid: false, openSource: true, ai: false, web: true, mobile: true, frontend: true }, - { id: "p5", name: "React Component Library", description: "Accessible component library with docs.", stack: ["TypeScript", "Tailwind", "Storybook"], owner: "Nancy Patel", members: 2, stars: 12, forks: 5, progress: 90, status: "active", icon: "๐Ÿงฉ", language: "TypeScript", difficulty: "beginner", remote: true, paid: false, openSource: true, ai: false, web: true, frontend: true }, - { id: "p6", name: "Open Source CRM", description: "Lightweight CRM with pipelines and reports.", stack: ["React", "Node.js", "MongoDB"], owner: "Community", members: 8, stars: 240, forks: 96, progress: 100, status: "shipped", icon: "๐Ÿ“‡", language: "JavaScript", difficulty: "intermediate", remote: false, paid: false, openSource: true, ai: false, web: true, frontend: true, backend: true }, + { + id: "p1", + name: "AI Chatbot", + description: "Multi-agent customer support bot for SaaS.", + stack: ["React", "Node.js", "MongoDB"], + owner: "Nancy Patel", + members: 4, + stars: 24, + forks: 12, + progress: 75, + status: "active", + icon: "๐Ÿค–", + language: "JavaScript", + difficulty: "intermediate", + remote: true, + paid: true, + openSource: false, + ai: true, + web: true, + frontend: true, + backend: true, + }, + { + id: "p2", + name: "AI SaaS Platform", + description: "Full-stack platform with billing and dashboards.", + stack: ["Next.js", "Python", "PostgreSQL"], + owner: "Nancy Patel", + members: 6, + stars: 18, + forks: 8, + progress: 40, + status: "active", + icon: "โœจ", + language: "Python", + difficulty: "advanced", + remote: true, + paid: true, + openSource: false, + ai: true, + web: true, + frontend: true, + backend: true, + }, + { + id: "p3", + name: "DevOps Dashboard", + description: "K8s deploy monitoring with drift detection.", + stack: ["Docker", "Kubernetes", "AWS"], + owner: "Nancy Patel", + members: 3, + stars: 16, + forks: 6, + progress: 60, + status: "active", + icon: "๐Ÿš€", + language: "Go", + difficulty: "advanced", + remote: true, + paid: false, + openSource: false, + ai: false, + web: true, + backend: true, + }, + { + id: "p4", + name: "Blockchain Wallet", + description: "Non-custodial multi-chain wallet.", + stack: ["Solidity", "Web3", "React"], + owner: "Nancy Patel", + members: 5, + stars: 14, + forks: 7, + progress: 25, + status: "planning", + icon: "๐Ÿช™", + language: "TypeScript", + difficulty: "advanced", + remote: true, + paid: false, + openSource: true, + ai: false, + web: true, + mobile: true, + frontend: true, + }, + { + id: "p5", + name: "React Component Library", + description: "Accessible component library with docs.", + stack: ["TypeScript", "Tailwind", "Storybook"], + owner: "Nancy Patel", + members: 2, + stars: 12, + forks: 5, + progress: 90, + status: "active", + icon: "๐Ÿงฉ", + language: "TypeScript", + difficulty: "beginner", + remote: true, + paid: false, + openSource: true, + ai: false, + web: true, + frontend: true, + }, + { + id: "p6", + name: "Open Source CRM", + description: "Lightweight CRM with pipelines and reports.", + stack: ["React", "Node.js", "MongoDB"], + owner: "Community", + members: 8, + stars: 240, + forks: 96, + progress: 100, + status: "shipped", + icon: "๐Ÿ“‡", + language: "JavaScript", + difficulty: "intermediate", + remote: false, + paid: false, + openSource: true, + ai: false, + web: true, + frontend: true, + backend: true, + }, ]; export const activity: Activity[] = [ - { id: "a1", kind: "join", text: "Alex joined your project", highlight: "AI Chatbot", ago: "2m ago" }, + { + id: "a1", + kind: "join", + text: "Alex joined your project", + highlight: "AI Chatbot", + ago: "2m ago", + }, { id: "a2", kind: "accept", text: "Sarah accepted your invitation", ago: "15m ago" }, { id: "a3", kind: "commit", text: "Backend API development completed", ago: "1h ago" }, { id: "a4", kind: "merge", text: "Frontend PR #24 merged", ago: "2h ago" }, @@ -147,15 +384,66 @@ export const builderRequests: BuilderRequest[] = [ ]; export const inviteRequests: InviteRequest[] = [ - { id: "i1", project: "Open Source CRM", role: "Backend Developer", dueDays: 3, by: "Alex", icon: "๐Ÿ“‡", color: "bg-info/10 text-info" }, - { id: "i2", project: "AI SaaS Platform", role: "ML Engineer", dueDays: 5, by: "Sarah", icon: "โœจ", color: "bg-primary/10 text-primary" }, - { id: "i3", project: "DevOps Dashboard", role: "DevOps Engineer", dueDays: 7, by: "Mike", icon: "๐Ÿš€", color: "bg-warning/10 text-warning" }, + { + id: "i1", + project: "Open Source CRM", + role: "Backend Developer", + dueDays: 3, + by: "Alex", + icon: "๐Ÿ“‡", + color: "bg-info/10 text-info", + }, + { + id: "i2", + project: "AI SaaS Platform", + role: "ML Engineer", + dueDays: 5, + by: "Sarah", + icon: "โœจ", + color: "bg-primary/10 text-primary", + }, + { + id: "i3", + project: "DevOps Dashboard", + role: "DevOps Engineer", + dueDays: 7, + by: "Mike", + icon: "๐Ÿš€", + color: "bg-warning/10 text-warning", + }, ]; export const flares: Flare[] = [ - { id: "f1", author: builders[3], content: "Just shipped a component library refresh โ€” new tokens, better a11y, half the CSS. AMA about migrating design systems.", tags: ["designsystems", "react"], likes: 128, comments: 22, ago: "1h ago" }, - { id: "f2", author: builders[6], content: "Wrote a small evaluator for embedding models. Cosine wasn't cutting it for our recall โ€” dot-product + normalized inputs won.", tags: ["ml", "search"], likes: 87, comments: 14, ago: "3h ago" }, - { id: "f3", author: builders[1], content: "Anyone else notice Node 22 shaving ~10% off cold starts for our fastify APIs? Ran the same suite twice.", tags: ["node", "perf"], likes: 54, comments: 9, ago: "5h ago" }, + { + id: "f1", + author: builders[3], + content: + "Just shipped a component library refresh โ€” new tokens, better a11y, half the CSS. AMA about migrating design systems.", + tags: ["designsystems", "react"], + likes: 128, + comments: 22, + ago: "1h ago", + }, + { + id: "f2", + author: builders[6], + content: + "Wrote a small evaluator for embedding models. Cosine wasn't cutting it for our recall โ€” dot-product + normalized inputs won.", + tags: ["ml", "search"], + likes: 87, + comments: 14, + ago: "3h ago", + }, + { + id: "f3", + author: builders[1], + content: + "Anyone else notice Node 22 shaving ~10% off cold starts for our fastify APIs? Ran the same suite twice.", + tags: ["node", "perf"], + likes: 54, + comments: 9, + ago: "5h ago", + }, ]; export const conversations: Conversation[] = [ @@ -178,19 +466,61 @@ export const notifications: Notification[] = [ { id: "n2", kind: "comment", text: "Sarah commented on your post", ago: "15m ago", unread: true }, { id: "n3", kind: "invite", text: "Your invitation was accepted", ago: "1h ago", unread: false }, { id: "n4", kind: "match", text: "New builder matches found", ago: "3h ago", unread: false }, - { id: "n5", kind: "hackathon", text: "Hackathon deadline reminder", ago: "1d ago", unread: false }, + { + id: "n5", + kind: "hackathon", + text: "Hackathon deadline reminder", + ago: "1d ago", + unread: false, + }, ]; export const hackathons: Hackathon[] = [ - { id: "h1", name: "AI for Good 2025", theme: "Social impact", startsIn: 12, prize: "$25k", teamSize: "2โ€“5", registered: true }, - { id: "h2", name: "DevLink Winter Jam", theme: "Any theme", startsIn: 30, prize: "$10k", teamSize: "1โ€“4", registered: false }, - { id: "h3", name: "Chain Builders", theme: "Web3", startsIn: 45, prize: "$50k", teamSize: "1โ€“5", registered: false }, + { + id: "h1", + name: "AI for Good 2025", + theme: "Social impact", + startsIn: 12, + prize: "$25k", + teamSize: "2โ€“5", + registered: true, + }, + { + id: "h2", + name: "DevLink Winter Jam", + theme: "Any theme", + startsIn: 30, + prize: "$10k", + teamSize: "1โ€“4", + registered: false, + }, + { + id: "h3", + name: "Chain Builders", + theme: "Web3", + startsIn: 45, + prize: "$50k", + teamSize: "1โ€“5", + registered: false, + }, ]; export const deadlines: Deadline[] = [ { id: "d1", project: "AI Chatbot", milestone: "Backend API", dueDays: 2, severity: "danger" }, - { id: "d2", project: "DevOps Dashboard", milestone: "Deployment", dueDays: 5, severity: "warning" }, - { id: "d3", project: "Hackathon 2025", milestone: "Registration", dueDays: 7, severity: "warning" }, + { + id: "d2", + project: "DevOps Dashboard", + milestone: "Deployment", + dueDays: 5, + severity: "warning", + }, + { + id: "d3", + project: "Hackathon 2025", + milestone: "Registration", + dueDays: 7, + severity: "warning", + }, { id: "d4", project: "Project Milestone", milestone: "v1.0", dueDays: 10, severity: "info" }, ]; diff --git a/frontend/src/routes/README.md b/frontend/src/routes/README.md index 441a4e82..0990d2f9 100644 --- a/frontend/src/routes/README.md +++ b/frontend/src/routes/README.md @@ -7,15 +7,15 @@ is `src/routes/__root.tsx`. ## Conventions -| File | URL | -| --- | --- | -| `index.tsx` | `/` | -| `about.tsx` | `/about` | -| `users/index.tsx` | `/users` | -| `users/$id.tsx` | `/users/:id` (dynamic โ€” bare `$`, no curly braces) | -| `posts/{-$category}.tsx` | `/posts/:category?` (optional segment) | -| `files/$.tsx` | `/files/*` (splat โ€” read via `_splat` param, never `*`) | -| `_layout.tsx` | layout route (renders children via ``) | -| `__root.tsx` | app shell โ€” wraps every page; preserve `` | +| File | URL | +| ------------------------ | ------------------------------------------------------- | +| `index.tsx` | `/` | +| `about.tsx` | `/about` | +| `users/index.tsx` | `/users` | +| `users/$id.tsx` | `/users/:id` (dynamic โ€” bare `$`, no curly braces) | +| `posts/{-$category}.tsx` | `/posts/:category?` (optional segment) | +| `files/$.tsx` | `/files/*` (splat โ€” read via `_splat` param, never `*`) | +| `_layout.tsx` | layout route (renders children via ``) | +| `__root.tsx` | app shell โ€” wraps every page; preserve `` | `routeTree.gen.ts` is auto-generated. Don't edit it by hand. diff --git a/frontend/src/routes/_app.analytics.tsx b/frontend/src/routes/_app.analytics.tsx index c05eb6ae..07d62d1a 100644 --- a/frontend/src/routes/_app.analytics.tsx +++ b/frontend/src/routes/_app.analytics.tsx @@ -2,8 +2,15 @@ import { createFileRoute } from "@tanstack/react-router"; import { Card } from "@/components/shared/primitives"; import { stats } from "@/mocks/seed"; import { - LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, - BarChart, Bar, CartesianGrid, + LineChart, + Line, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + BarChart, + Bar, + CartesianGrid, } from "recharts"; export const Route = createFileRoute("/_app/analytics")({ @@ -35,7 +42,9 @@ function AnalyticsPage() {
        {stats.slice(0, 4).map((s) => ( -

        {s.label}

        +

        + {s.label} +

        {s.value}

        ))} @@ -49,8 +58,21 @@ function AnalyticsPage() { - - + +
        @@ -63,7 +85,14 @@ function AnalyticsPage() { - + diff --git a/frontend/src/routes/_app.bookmarks.tsx b/frontend/src/routes/_app.bookmarks.tsx index d48b16fc..330866c8 100644 --- a/frontend/src/routes/_app.bookmarks.tsx +++ b/frontend/src/routes/_app.bookmarks.tsx @@ -21,21 +21,29 @@ function BookmarksPage() {

        Everything you've saved.

    -

    Projects

    +

    + Projects +

    {projects.slice(0, 3).map((p) => (
    - {p.icon} + + {p.icon} +

    {p.name}

    -

    {p.description}

    +

    + {p.description} +

    - {p.stack.map((s) => {s})} + {p.stack.map((s) => ( + {s} + ))}
    @@ -43,7 +51,9 @@ function BookmarksPage() {
    -

    Flares

    +

    + Flares +

    {flares.slice(0, 2).map((f) => ( diff --git a/frontend/src/routes/_app.builders.tsx b/frontend/src/routes/_app.builders.tsx index 8fdec8a2..4c2a412b 100644 --- a/frontend/src/routes/_app.builders.tsx +++ b/frontend/src/routes/_app.builders.tsx @@ -1,28 +1,234 @@ -import { createFileRoute, Link } from "@tanstack/react-router"; +import { createFileRoute, Link, useNavigate, useRouterState, Outlet } from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; import { buildersService } from "@/services"; -import { Card, TagChip, Avatar } from "@/components/shared/primitives"; +import type { Builder } from "@/services"; +import { Card, TagChip, Avatar, EmptyState } from "@/components/shared/primitives"; import { useState } from "react"; import { cn } from "@/lib/utils"; -import { Search } from "lucide-react"; +import { + Search, + Sparkles, + Calendar, + Briefcase, + ChevronRight, + Check, + Bookmark, + Star, + Award, +} from "lucide-react"; +import { motion } from "framer-motion"; + +type BuildersSearch = { + tab?: "discover" | "matches" | "connections"; +}; export const Route = createFileRoute("/_app/builders")({ + validateSearch: (search: Record): BuildersSearch => { + return { + tab: (search.tab as "discover" | "matches" | "connections") || "discover", + }; + }, head: () => ({ meta: [ { title: "Builders โ€” DevLink" }, - { name: "description", content: "Discover developers by skills, match score and availability." }, + { + name: "description", + content: "Discover developers by skills, match score and availability.", + }, ], }), component: BuildersPage, }); +const TARGET_SKILLS = [ + "React", + "Next.js", + "TypeScript", + "Node.js", + "Python", + "Figma", + "Kubernetes", + "AWS", + "PostgreSQL", +]; + +function AIMatchCard({ builder }: { builder: Builder }) { + const [bookmarked, setBookmarked] = useState(false); + + const displaySkills = builder.skills.slice(0, 3); + const remainingCount = builder.skills.length - 3; + const matchPercentage = `${builder.matchScore}%`; + const experienceText = `${builder.yearsExp} Yrs`; + const availabilityText = builder.availability.split(" ")[0]; // e.g. "Full-time" or "Part-time" + + return ( + +
    + {/* Header Banner */} +
    +
    +
    + + {builder.name} + + +
    +
    + + {/* Profile Name, Role & Bookmark */} +
    +
    + +

    + {builder.name} +

    + +

    + {builder.role} +

    +
    + +
    + + {/* Skill Tags / Matching Skills highlight */} +
    + {displaySkills.map((s: string) => { + const isMatching = TARGET_SKILLS.includes(s); + return ( + + {isMatching && ( + + )} + {s} + + ); + })} + {remainingCount > 0 && ( + + +{remainingCount} + + )} +
    + + {/* Stats Divider Grid */} +
    +
    +

    + + {matchPercentage} +

    +

    + Match +

    +
    +
    +

    + + {experienceText} +

    +

    + Experience +

    +
    +
    +

    + + {availabilityText} +

    +

    + Availability +

    +
    +
    +
    + + {/* CTA Button */} +
    + + View Details + +
    + + ); +} + function BuildersPage() { - const [tab, setTab] = useState<"discover" | "matches" | "connections">("discover"); + const pathname = useRouterState({ select: (s) => s.location.pathname }); + const { tab } = Route.useSearch(); + const navigate = useNavigate({ from: Route.fullPath }); const [q, setQ] = useState(""); - const { data = [] } = useQuery({ queryKey: ["builders", tab], queryFn: tab === "matches" ? buildersService.matches : buildersService.list }); + const { data = [] } = useQuery({ + queryKey: ["builders", tab], + queryFn: tab === "matches" ? buildersService.matches : buildersService.list, + }); + + const [connections, setConnections] = useState(() => { + try { + const stored = localStorage.getItem("devlink:connections"); + return stored ? JSON.parse(stored) : []; + } catch { + return []; + } + }); + + const handleConnect = (id: string) => { + setConnections((prev) => { + const next = prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]; + localStorage.setItem("devlink:connections", JSON.stringify(next)); + return next; + }); + }; + + const isDetails = pathname.split("/").filter(Boolean).length > 1; + + if (isDetails) { + return ; + } + + const baseData = tab === "connections" ? data.filter((b) => connections.includes(b.id)) : data; - const filtered = data.filter( - (b) => b.name.toLowerCase().includes(q.toLowerCase()) || b.skills.some((s) => s.toLowerCase().includes(q.toLowerCase())), + const filtered = baseData.filter( + (b) => + b.name.toLowerCase().includes(q.toLowerCase()) || + b.skills.some((s) => s.toLowerCase().includes(q.toLowerCase())), ); const tabs = [ @@ -43,10 +249,12 @@ function BuildersPage() { {tabs.map((t) => (
    - + setQ(e.target.value)} @@ -64,28 +275,97 @@ function BuildersPage() {
    -
    - {filtered.map((b) => ( - - -
    - -
    -

    {b.name}

    -

    {b.role}

    -

    {b.country} ยท {b.yearsExp} yrs

    -
    - {b.skills.slice(0, 3).map((s) => {s})} -
    -

    {b.matchScore}% Match

    -
    - - -
    -
    - - ))} -
    + {filtered.length === 0 ? ( + q !== "" ? ( + + ) : tab === "connections" ? ( + navigate({ search: (prev) => ({ ...prev, tab: "discover" }) })} + className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-2 text-[13px] font-semibold text-primary-foreground hover:opacity-90 cursor-pointer" + > + Discover builders + + } + /> + ) : ( + + ) + ) : tab === "matches" ? ( +
    + {filtered.map((b) => ( + + ))} +
    + ) : ( +
    + {filtered.map((b) => { + const isConnected = connections.includes(b.id); + return ( + + +
    +
    + +
    +

    {b.name}

    +

    {b.role}

    +

    + {b.country} ยท {b.yearsExp} yrs +

    +
    + {b.skills.slice(0, 3).map((s) => ( + {s} + ))} +
    +

    + {b.matchScore}% Match +

    +
    +
    + + +
    +
    + + ); + })} +
    + )}
    ); } diff --git a/frontend/src/routes/_app.dashboard.tsx b/frontend/src/routes/_app.dashboard.tsx index 6ddc9242..06b5aaf7 100644 --- a/frontend/src/routes/_app.dashboard.tsx +++ b/frontend/src/routes/_app.dashboard.tsx @@ -18,7 +18,10 @@ export const Route = createFileRoute("/_app/dashboard")({ head: () => ({ meta: [ { title: "Dashboard โ€” DevLink" }, - { name: "description", content: "Your DevLink command center: projects, matches, messages and streaks." }, + { + name: "description", + content: "Your DevLink command center: projects, matches, messages and streaks.", + }, ], }), component: Dashboard, diff --git a/frontend/src/routes/_app.hackathons.tsx b/frontend/src/routes/_app.hackathons.tsx index 0a2b0996..c7816a52 100644 --- a/frontend/src/routes/_app.hackathons.tsx +++ b/frontend/src/routes/_app.hackathons.tsx @@ -20,7 +20,9 @@ function HackathonsPage() {

    Hackathons

    -

    Join a jam, build a team, ship something new.

    +

    + Join a jam, build a team, ship something new. +

    {data.map((h) => ( @@ -38,8 +40,12 @@ function HackathonsPage() {

    {h.name}

    {h.theme}

    - Starts in {h.startsIn}d - {h.teamSize} + + Starts in {h.startsIn}d + + + {h.teamSize} + {h.prize}
    {data.length === 0 && ( -

    No messages yet โ€” say hello ๐Ÿ‘‹

    +

    + No messages yet โ€” say hello ๐Ÿ‘‹ +

    )} {data.map((m) => ( -
    +

    {m.text}

    -

    {m.at}

    +

    + {m.at} +

    ))}
    { e.preventDefault(); setText(""); }} + onSubmit={(e) => { + e.preventDefault(); + setText(""); + }} className="flex items-center gap-2 border-t border-border p-3" >

    Conversations

    -
      - {data.map((c) => ( -
    • - - -
      -

      {c.with.name}

      -

      {c.preview}

      -
      -
      - {c.ago} - {c.unread > 0 && ( - - {c.unread} - - )} -
      - -
    • - ))} -
    + {data.length === 0 ? ( +
    + +
    + ) : ( +
      + {data.map((c) => ( +
    • + + +
      +

      + {c.with.name} +

      +

      {c.preview}

      +
      +
      + {c.ago} + {c.unread > 0 && ( + + {c.unread} + + )} +
      + +
    • + ))} +
    + )} +

    Select a conversation

    -

    Choose a chat on the left to start messaging.

    +

    + Choose a chat on the left to start messaging. +

    diff --git a/frontend/src/routes/_app.notifications.tsx b/frontend/src/routes/_app.notifications.tsx index 95991e19..c52519e8 100644 --- a/frontend/src/routes/_app.notifications.tsx +++ b/frontend/src/routes/_app.notifications.tsx @@ -1,7 +1,7 @@ import { createFileRoute } from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; import { notificationsService } from "@/services"; -import { Card } from "@/components/shared/primitives"; +import { Card, EmptyState } from "@/components/shared/primitives"; import { cn } from "@/lib/utils"; export const Route = createFileRoute("/_app/notifications")({ @@ -15,7 +15,10 @@ export const Route = createFileRoute("/_app/notifications")({ }); function NotificationsPage() { - const { data = [] } = useQuery({ queryKey: ["notifications"], queryFn: notificationsService.list }); + const { data = [] } = useQuery({ + queryKey: ["notifications"], + queryFn: notificationsService.list, + }); return (
    @@ -27,17 +30,34 @@ function NotificationsPage() { Mark all read
    - -
      - {data.map((n) => ( -
    • - -

      {n.text}

      - {n.ago} -
    • - ))} -
    -
    + {data.length === 0 ? ( + + ) : ( + +
      + {data.map((n) => ( +
    • + +

      {n.text}

      + {n.ago} +
    • + ))} +
    +
    + )} +
    ); } diff --git a/frontend/src/routes/_app.profile.$username.tsx b/frontend/src/routes/_app.profile.$username.tsx index 25b0bb0c..a48b9ae0 100644 --- a/frontend/src/routes/_app.profile.$username.tsx +++ b/frontend/src/routes/_app.profile.$username.tsx @@ -7,7 +7,10 @@ export const Route = createFileRoute("/_app/profile/$username")({ head: ({ params }) => ({ meta: [ { title: `@${params.username} โ€” DevLink` }, - { name: "description", content: `${params.username}'s DevLink profile: skills, projects and activity.` }, + { + name: "description", + content: `${params.username}'s DevLink profile: skills, projects and activity.`, + }, ], }), component: ProfilePage, @@ -17,7 +20,14 @@ function ProfilePage() { const { username } = Route.useParams(); const me = username === currentUser.handle; const b = me - ? { ...builders[0], name: currentUser.name, handle: currentUser.handle, avatar: currentUser.avatar, bio: "Product engineer. Ships fast, sleeps sometimes.", role: "Full Stack Developer" } + ? { + ...builders[0], + name: currentUser.name, + handle: currentUser.handle, + avatar: currentUser.avatar, + bio: "Product engineer. Ships fast, sleeps sometimes.", + role: "Full Stack Developer", + } : builders.find((x) => x.handle === username); if (!b) throw notFound(); @@ -28,12 +38,20 @@ function ProfilePage() {

    {b.name}

    -

    @{b.handle} ยท {b.role}

    +

    + @{b.handle} ยท {b.role} +

    {b.bio}

    - {b.country} - Joined 2024 - devlink.io/{b.handle} + + {b.country} + + + Joined 2024 + + + devlink.io/{b.handle} +
    {!me && ( @@ -48,7 +66,9 @@ function ProfilePage() {

    Skills

    - {b.skills.map((s) => {s})} + {b.skills.map((s) => ( + {s} + ))}
    @@ -56,10 +76,14 @@ function ProfilePage() {
      {projects.slice(0, 4).map((p) => (
    • - {p.icon} + + {p.icon} +

      {p.name}

      -

      {p.stack.join(" ยท ")}

      +

      + {p.stack.join(" ยท ")} +

    • ))} diff --git a/frontend/src/routes/_app.projects.$projectId.tsx b/frontend/src/routes/_app.projects.$projectId.tsx index 6ef81a33..53e20ebc 100644 --- a/frontend/src/routes/_app.projects.$projectId.tsx +++ b/frontend/src/routes/_app.projects.$projectId.tsx @@ -7,10 +7,6 @@ import { useState } from "react"; import { cn } from "@/lib/utils"; import { builders, activity, currentUser } from "@/mocks/seed"; import { Markdown } from "@/components/shared/Markdown"; -import { ArrowLeft, Star, GitFork, Users2, Github } from "lucide-react"; -import { useState } from "react"; -import { cn } from "@/lib/utils"; -import { builders, activity } from "@/mocks/seed"; import { BackButton } from "@/components/shared/BackButton"; export const Route = createFileRoute("/_app/projects/$projectId")({ @@ -55,10 +51,7 @@ function ProjectDetail() { > Back to projects - +
      @@ -97,16 +90,6 @@ function ProjectDetail() { {p.members}
      -
      - - {p.stars} - - - {p.forks} - - - {p.members} -
    diff --git a/frontend/src/routes/_app.projects.tsx b/frontend/src/routes/_app.projects.tsx index 1aedbe8f..4c1e5858 100644 --- a/frontend/src/routes/_app.projects.tsx +++ b/frontend/src/routes/_app.projects.tsx @@ -1,7 +1,7 @@ -import { createFileRoute, Link } from "@tanstack/react-router"; +import { createFileRoute, Link, useRouterState, Outlet } from "@tanstack/react-router"; import { useQuery } from "@tanstack/react-query"; import { projectsService } from "@/services"; -import { Card, TagChip, SectionHeader } from "@/components/shared/primitives"; +import { Card, TagChip, SectionHeader, EmptyState } from "@/components/shared/primitives"; import { Star, GitFork, Users2, Plus, Search, SlidersHorizontal, X } from "lucide-react"; import { useState } from "react"; import { cn } from "@/lib/utils"; @@ -18,7 +18,16 @@ export const Route = createFileRoute("/_app/projects")({ const LANGUAGES = ["JavaScript", "TypeScript", "Python", "Go", "Rust", "Java", "C++"]; const DIFFICULTIES = ["beginner", "intermediate", "advanced"] as const; -const BOOL_FILTERS = ["remote", "paid", "openSource", "ai", "web", "mobile", "backend", "frontend"] as const; +const BOOL_FILTERS = [ + "remote", + "paid", + "openSource", + "ai", + "web", + "mobile", + "backend", + "frontend", +] as const; type BoolFilter = (typeof BOOL_FILTERS)[number]; const BOOL_LABELS: Record = { @@ -61,14 +70,26 @@ function toggle(set: T[], val: T): T[] { } function ProjectsPage() { + const pathname = useRouterState({ select: (s) => s.location.pathname }); const [q, setQ] = useState(""); - const [statusFilter, setStatusFilter] = useState<"all" | "active" | "planning" | "shipped">("all"); + const [statusFilter, setStatusFilter] = useState<"all" | "active" | "planning" | "shipped">( + "all", + ); const [showFilters, setShowFilters] = useState(false); const [langs, setLangs] = useState([]); const [difficulties, setDifficulties] = useState([]); const [boolFilters, setBoolFilters] = useState([]); - const { data = [], isLoading } = useQuery({ queryKey: ["projects"], queryFn: projectsService.list }); + const { data = [], isLoading } = useQuery({ + queryKey: ["projects"], + queryFn: projectsService.list, + }); + + const isDetails = pathname.split("/").filter(Boolean).length > 1; + + if (isDetails) { + return ; + } const hasActiveFilters = langs.length > 0 || difficulties.length > 0 || boolFilters.length > 0; @@ -82,7 +103,8 @@ function ProjectsPage() { if (statusFilter !== "all" && p.status !== statusFilter) return false; if (q && !p.name.toLowerCase().includes(q.toLowerCase())) return false; if (langs.length > 0 && (!p.language || !langs.includes(p.language))) return false; - if (difficulties.length > 0 && (!p.difficulty || !difficulties.includes(p.difficulty))) return false; + if (difficulties.length > 0 && (!p.difficulty || !difficulties.includes(p.difficulty))) + return false; for (const f of boolFilters) { if (!p[f]) return false; } @@ -94,7 +116,9 @@ function ProjectsPage() {

    Projects

    -

    Everything you're building, in one place.

    +

    + Everything you're building, in one place. +

    - )} -
    + q !== "" ? ( + + ) : ( + + New project + + ) : undefined + } + /> + ) ) : (
    {filtered.map((p) => ( - +
    - {p.icon} + + {p.icon} +

    {p.name}

    -

    {p.description}

    +

    + {p.description} +

    @@ -228,11 +298,15 @@ function ProjectsPage() { {s} ))} {p.difficulty && ( - + {p.difficulty} )} @@ -256,11 +330,17 @@ function ProjectsPage() { {p.forks} - {p.status} + + {p.status} +
    diff --git a/frontend/src/routes/_app.settings.tsx b/frontend/src/routes/_app.settings.tsx index faf458ee..c1c09f44 100644 --- a/frontend/src/routes/_app.settings.tsx +++ b/frontend/src/routes/_app.settings.tsx @@ -12,7 +12,10 @@ export const Route = createFileRoute("/_app/settings")({ head: () => ({ meta: [ { title: "Settings โ€” DevLink" }, - { name: "description", content: "Manage your DevLink account, appearance, notifications and billing." }, + { + name: "description", + content: "Manage your DevLink account, appearance, notifications and billing.", + }, ], }), component: SettingsPage, @@ -20,7 +23,8 @@ export const Route = createFileRoute("/_app/settings")({ function SettingsPage() { const [tab, setTab] = useState("Account"); - const inp = "w-full rounded-md border border-border bg-surface px-3 py-[8px] text-[14px] text-foreground outline-none focus:border-primary focus:ring-2 focus:ring-primary/20"; + const inp = + "w-full rounded-md border border-border bg-surface px-3 py-[8px] text-[14px] text-foreground outline-none focus:border-primary focus:ring-2 focus:ring-primary/20"; const lbl = "mb-1 block text-[13px] font-semibold text-foreground"; return ( @@ -51,26 +55,60 @@ function SettingsPage() {

    {tab}

    {tab === "Account" && ( - { e.preventDefault(); toast.success("Saved"); }} className="space-y-4"> + { + e.preventDefault(); + toast.success("Saved"); + }} + className="space-y-4" + >
    -
    -
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + +