Nguyen Thanh Hoang Hoang Nguyen commited on
Commit
e0eaa09
·
unverified ·
1 Parent(s): 33b7340

feat(dashoard): implement page dashboard (#4)

Browse files

Co-authored-by: Hoang Nguyen <hoangnt@inspirelab.vn>

package-lock.json CHANGED
@@ -30,6 +30,7 @@
30
  "react-dom": "19.0.0",
31
  "react-hook-form": "^7.54.0",
32
  "tailwind-merge": "^2.6.0",
 
33
  "zod": "^3.24.0"
34
  },
35
  "devDependencies": {
@@ -24392,7 +24393,6 @@
24392
  "version": "4.0.8",
24393
  "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
24394
  "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
24395
- "dev": true,
24396
  "license": "MIT"
24397
  },
24398
  "node_modules/lodash.escaperegexp": {
@@ -37429,6 +37429,20 @@
37429
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
37430
  }
37431
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37432
  "node_modules/util": {
37433
  "version": "0.12.5",
37434
  "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
 
30
  "react-dom": "19.0.0",
31
  "react-hook-form": "^7.54.0",
32
  "tailwind-merge": "^2.6.0",
33
+ "usehooks-ts": "^3.1.1",
34
  "zod": "^3.24.0"
35
  },
36
  "devDependencies": {
 
24393
  "version": "4.0.8",
24394
  "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
24395
  "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
 
24396
  "license": "MIT"
24397
  },
24398
  "node_modules/lodash.escaperegexp": {
 
37429
  "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
37430
  }
37431
  },
37432
+ "node_modules/usehooks-ts": {
37433
+ "version": "3.1.1",
37434
+ "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.1.tgz",
37435
+ "integrity": "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA==",
37436
+ "dependencies": {
37437
+ "lodash.debounce": "^4.0.8"
37438
+ },
37439
+ "engines": {
37440
+ "node": ">=16.15.0"
37441
+ },
37442
+ "peerDependencies": {
37443
+ "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
37444
+ }
37445
+ },
37446
  "node_modules/util": {
37447
  "version": "0.12.5",
37448
  "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
package.json CHANGED
@@ -49,6 +49,7 @@
49
  "react-dom": "19.0.0",
50
  "react-hook-form": "^7.54.0",
51
  "tailwind-merge": "^2.6.0",
 
52
  "zod": "^3.24.0"
53
  },
54
  "devDependencies": {
 
49
  "react-dom": "19.0.0",
50
  "react-hook-form": "^7.54.0",
51
  "tailwind-merge": "^2.6.0",
52
+ "usehooks-ts": "^3.1.1",
53
  "zod": "^3.24.0"
54
  },
55
  "devDependencies": {
src/app/dashboard/page.tsx ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import JobCard from '@/components/dashboard/JobCard';
2
+ import JobFilters from '@/components/dashboard/JobFilters';
3
+ import SubscribeJob from '@/components/dashboard/SubscribeJob';
4
+ import { Suspense } from 'react';
5
+
6
+ const jobCategories = [
7
+ 'Design',
8
+ 'Full-stack',
9
+ 'Back-end',
10
+ 'Front-end',
11
+ 'QA Engineer',
12
+ 'Data Engineer',
13
+ 'Mobile',
14
+ 'AI Training & Labeling',
15
+ 'DevOps',
16
+ ];
17
+
18
+ const jobs = [
19
+ {
20
+ id: 1,
21
+ title: 'Software Engineer - Confidential Computing',
22
+ company: 'Nethermind',
23
+ logo: 'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/Screenshot%202025-02-15%20at%2021.58.09-ZsGA1DKAeyYCxqdocTlWoRuaKkhrH1.png',
24
+ location: 'Anywhere',
25
+ type: 'Freelancer',
26
+ postedAt: '2 days ago',
27
+ },
28
+ {
29
+ id: 2,
30
+ title: 'Marketing Manager - Payments Industry',
31
+ company: 'HitPay',
32
+ logo: 'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/Screenshot%202025-02-15%20at%2021.58.09-ZsGA1DKAeyYCxqdocTlWoRuaKkhrH1.png',
33
+ location: 'Anywhere',
34
+ type: 'Full-time',
35
+ postedAt: '2 days ago',
36
+ },
37
+ // Add more jobs as needed
38
+ ];
39
+
40
+ export default function Page() {
41
+ return (
42
+ <main className="min-h-screen bg-background pt-20">
43
+ <div className="container mx-auto px-4 py-8">
44
+ {/* Categories */}
45
+ <div className="mb-8 flex flex-wrap gap-2">
46
+ {jobCategories.map(category => (
47
+ <button type="button" key={category} className="rounded-full bg-muted px-4 py-2 transition-colors hover:bg-muted/80">
48
+ {category}
49
+ </button>
50
+ ))}
51
+ </div>
52
+
53
+ {/* Filters and Search */}
54
+ <Suspense fallback={<div>Loading filters...</div>}>
55
+ <JobFilters />
56
+ </Suspense>
57
+
58
+ {/* Job Listings */}
59
+ <div className="mt-8 grid grid-cols-1 gap-8 lg:grid-cols-3">
60
+ <div className="space-y-4 lg:col-span-2">
61
+ {jobs.map(job => (
62
+ <JobCard key={job.id} job={job} />
63
+ ))}
64
+ </div>
65
+
66
+ {/* Newsletter Signup */}
67
+ <div className="lg:col-span-1">
68
+ <div className="sticky top-4">
69
+ <SubscribeJob />
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </main>
75
+ );
76
+ }
src/app/page.tsx CHANGED
@@ -6,52 +6,6 @@ export default function Home() {
6
  return (
7
  <main className="flex min-h-screen flex-col">
8
  <div className="flex-1 space-y-16 pb-8 pt-20 md:pb-12 md:pt-24 lg:py-32">
9
- <div className="container flex flex-col items-center gap-4 text-center">
10
- <Badge className="rounded-lg" variant="secondary">
11
- NEW
12
- {' '}
13
- <span className="mx-1">+</span>
14
- {' '}
15
- v0.5.14 is now live on GitHub. Check it out!
16
- </Badge>
17
-
18
- <span className="text-4xl">❝</span>
19
-
20
- <h1 className="font-heading text-3xl sm:text-5xl md:text-6xl lg:text-7xl">
21
- Chat with AI
22
- <br />
23
- without privacy concerns
24
- </h1>
25
-
26
- <p className="max-w-2xl leading-normal text-muted-foreground sm:text-xl sm:leading-8">
27
- Jan is an open source ChatGPT-alternative that runs 100% offline.
28
- </p>
29
-
30
- <div className="space-y-4">
31
- <Button className="h-11 px-8" size="lg">
32
- <Download className="mr-2 size-4" />
33
- Download for Windows
34
- <ChevronDown className="ml-2 size-4" />
35
- </Button>
36
- <p className="text-xs text-muted-foreground">
37
- <span className="font-semibold text-yellow-500">2.5M+</span>
38
- {' '}
39
- downloads | Free & Open Source
40
- </p>
41
- </div>
42
- </div>
43
-
44
- <div className="container">
45
- <div className="relative mx-auto aspect-video max-w-5xl overflow-hidden rounded-xl border bg-background shadow-xl">
46
- <Image
47
- src="https://sjc.microlink.io/-ax0tIqUfnMYpO1Y6sFNuRcGN_Oe6cQwpzrnQR5q5pzkpfA29UGKZ228lDnpeQCpNANORBcNBmQgFoOtLn18vw.jpeg"
48
- alt="Jan AI Interface"
49
- fill
50
- className="object-cover"
51
- priority
52
- />
53
- </div>
54
- </div>
55
  <AnnouncementBanner />
56
  <FeaturesSection />
57
  <CustomizationSection />
 
6
  return (
7
  <main className="flex min-h-screen flex-col">
8
  <div className="flex-1 space-y-16 pb-8 pt-20 md:pb-12 md:pt-24 lg:py-32">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  <AnnouncementBanner />
10
  <FeaturesSection />
11
  <CustomizationSection />
src/components/HeroSection.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { Button } from '@/components/atoms';
2
  import { Download } from 'lucide-react';
3
  import Image from 'next/image';
4
 
 
1
+ import { Button } from '@/components/base';
2
  import { Download } from 'lucide-react';
3
  import Image from 'next/image';
4
 
src/components/Navbar.tsx CHANGED
@@ -1,6 +1,6 @@
1
  'use client';
2
- import { Button, Input } from '@/components/atoms';
3
- import { Search, Settings } from 'lucide-react';
4
  import Link from 'next/link';
5
 
6
  export function Navbar() {
@@ -15,11 +15,8 @@ export function Navbar() {
15
  </div>
16
 
17
  <div className="flex items-center gap-6 text-sm">
18
- <Link href="/documentation" className="hover:text-foreground/80">
19
- Documentation
20
- </Link>
21
- <Link href="/changelog" className="hover:text-foreground/80">
22
- Changelog
23
  </Link>
24
  <Link href="/about" className="hover:text-foreground/80">
25
  About
@@ -31,12 +28,15 @@ export function Navbar() {
31
  <Search className="absolute left-2 top-2.5 size-4 text-muted-foreground" />
32
  <Input placeholder="Search documentation..." className="pl-8" />
33
  </div>
34
- <Link href="/blog" className="hover:text-foreground/80">
35
- Blog
36
- </Link>
37
- <Button variant="ghost" size="icon">
38
- <Settings className="size-5" />
39
- </Button>
 
 
 
40
  </div>
41
  </div>
42
  </nav>
 
1
  'use client';
2
+ import { Button, Input } from '@/components/base';
3
+ import { Search } from 'lucide-react';
4
  import Link from 'next/link';
5
 
6
  export function Navbar() {
 
15
  </div>
16
 
17
  <div className="flex items-center gap-6 text-sm">
18
+ <Link href="/dashboard" className="hover:text-foreground/80">
19
+ Dashboard
 
 
 
20
  </Link>
21
  <Link href="/about" className="hover:text-foreground/80">
22
  About
 
28
  <Search className="absolute left-2 top-2.5 size-4 text-muted-foreground" />
29
  <Input placeholder="Search documentation..." className="pl-8" />
30
  </div>
31
+ <div className="flex gap-4">
32
+ <Button variant="default" size="default">
33
+ <Link href="/sign-in">Sign In</Link>
34
+ </Button>
35
+ <Button variant="secondary" size="default">
36
+ <Link href="/sign-up">Sign Up</Link>
37
+ </Button>
38
+ </div>
39
+
40
  </div>
41
  </div>
42
  </nav>
src/components/{atoms → base}/Badge/index.tsx RENAMED
@@ -1,3 +1,5 @@
 
 
1
  import type * as React from 'react';
2
  import { cn } from '@/utils/Helpers';
3
  import { cva, type VariantProps } from 'class-variance-authority';
 
1
+ 'use client';
2
+
3
  import type * as React from 'react';
4
  import { cn } from '@/utils/Helpers';
5
  import { cva, type VariantProps } from 'class-variance-authority';
src/components/{atoms → base}/Button/index.tsx RENAMED
@@ -1,4 +1,5 @@
1
- /* eslint-disable react-refresh/only-export-components */
 
2
  import { cn } from '@/utils/Helpers';
3
  import { cva, type VariantProps } from 'class-variance-authority';
4
  import { type ButtonHTMLAttributes, type FC, memo, type PropsWithChildren, type Ref } from 'react';
@@ -49,4 +50,5 @@ const Button: FC<ButtonProps> = memo(({
49
 
50
  Button.displayName = 'Button';
51
 
 
52
  export { Button, buttonVariants };
 
1
+ 'use client';
2
+
3
  import { cn } from '@/utils/Helpers';
4
  import { cva, type VariantProps } from 'class-variance-authority';
5
  import { type ButtonHTMLAttributes, type FC, memo, type PropsWithChildren, type Ref } from 'react';
 
50
 
51
  Button.displayName = 'Button';
52
 
53
+ // eslint-disable-next-line react-refresh/only-export-components
54
  export { Button, buttonVariants };
src/components/{atoms → base}/Card/index.tsx RENAMED
@@ -1,3 +1,5 @@
 
 
1
  import { cn } from '@/utils/Helpers';
2
  import * as React from 'react';
3
 
 
1
+ 'use client';
2
+
3
  import { cn } from '@/utils/Helpers';
4
  import * as React from 'react';
5
 
src/components/{atoms → base}/Input/index.tsx RENAMED
@@ -1,3 +1,5 @@
 
 
1
  import { cn } from '@/utils/Helpers';
2
  import { type FC, type InputHTMLAttributes, memo, type Ref } from 'react';
3
 
 
1
+ 'use client';
2
+
3
  import { cn } from '@/utils/Helpers';
4
  import { type FC, type InputHTMLAttributes, memo, type Ref } from 'react';
5
 
src/components/base/Label/index.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { cn } from '@/utils/Helpers';
4
+ import * as React from 'react';
5
+
6
+ export type LabelProps = {} & React.LabelHTMLAttributes<HTMLLabelElement>;
7
+
8
+ const Label = React.forwardRef<HTMLLabelElement, LabelProps>(({ className, ...props }, ref) => {
9
+ return (
10
+ // eslint-disable-next-line jsx-a11y/label-has-associated-control
11
+ <label
12
+ ref={ref}
13
+ className={cn(
14
+ 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
15
+ className,
16
+ )}
17
+ {...props}
18
+ />
19
+ );
20
+ });
21
+ Label.displayName = 'Label';
22
+
23
+ export { Label };
src/components/base/Select/Select.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { useOnClickOutside } from 'usehooks-ts';
5
+ import { SelectContext } from './SelectContext';
6
+
7
+ export default function Select({
8
+ children,
9
+ value: controlledValue,
10
+ onChange,
11
+ defaultValue,
12
+ }: {
13
+ children: React.ReactNode;
14
+ value?: string;
15
+ onChange?: (value: string) => void;
16
+ defaultValue?: string;
17
+ }) {
18
+ const [open, setOpen] = React.useState(false);
19
+ const [internalValue, setInternalValue] = React.useState(defaultValue || '');
20
+ const ref = React.useRef<HTMLDivElement>(null);
21
+
22
+ const handleClickOutside = () => {
23
+ setOpen(false);
24
+ };
25
+
26
+ useOnClickOutside<HTMLDivElement>(ref as React.RefObject<HTMLDivElement>, handleClickOutside);
27
+
28
+ const value = controlledValue !== undefined ? controlledValue : internalValue;
29
+ const handleChange = onChange || setInternalValue;
30
+
31
+ return (
32
+ // eslint-disable-next-line react/no-unstable-context-value
33
+ <SelectContext.Provider value={{ open, setOpen, value, onChange: handleChange }}>
34
+ <div className="relative" ref={ref}>{children}</div>
35
+ </SelectContext.Provider>
36
+ );
37
+ }
src/components/base/Select/SelectContent.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { cn } from '@/utils/Helpers';
4
+ import * as React from 'react';
5
+ import { SelectContext } from './SelectContext';
6
+
7
+ export default function SelectContent({ children, className }: { children: React.ReactNode; className?: string }) {
8
+ const context = React.useContext(SelectContext);
9
+ if (!context) {
10
+ throw new Error('SelectContent must be used within Select');
11
+ }
12
+
13
+ if (!context.open) {
14
+ return null;
15
+ }
16
+
17
+ return (
18
+ <div
19
+ className={cn(
20
+ 'absolute top-full z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover text-popover-foreground shadow-md',
21
+ className,
22
+ )}
23
+ >
24
+ <div className="p-1">{children}</div>
25
+ </div>
26
+ );
27
+ }
src/components/base/Select/SelectContext.tsx ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+
5
+ export const SelectContext = React.createContext<SelectContextType | undefined>(undefined);
6
+
7
+ type SelectContextType = {
8
+ open: boolean;
9
+ setOpen: (open: boolean) => void;
10
+ value: string;
11
+ onChange: (value: string) => void;
12
+ };
src/components/base/Select/SelectItem.tsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { cn } from '@/utils/Helpers';
4
+ import * as React from 'react';
5
+ import { SelectContext } from './SelectContext';
6
+
7
+ export default function SelectItem({
8
+ children,
9
+ value,
10
+ className,
11
+ }: {
12
+ children: React.ReactNode;
13
+ value: string;
14
+ className?: string;
15
+ }) {
16
+ const context = React.useContext(SelectContext);
17
+ if (!context) {
18
+ throw new Error('SelectItem must be used within Select');
19
+ }
20
+
21
+ const isSelected = context.value === value;
22
+
23
+ return (
24
+ <button
25
+ type="button"
26
+ onClick={() => {
27
+ context.onChange(value);
28
+ context.setOpen(false);
29
+ }}
30
+ className={cn(
31
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 px-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground',
32
+ isSelected && 'bg-accent text-accent-foreground',
33
+ className,
34
+ )}
35
+ >
36
+ {children}
37
+ </button>
38
+ );
39
+ }
src/components/base/Select/SelectTrigger.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { cn } from '@/utils/Helpers';
4
+ import { ChevronDown } from 'lucide-react';
5
+ import * as React from 'react';
6
+ import { SelectContext } from './SelectContext';
7
+
8
+ export default function SelectTrigger({ children, className }: { children: React.ReactNode; className?: string }) {
9
+ const context = React.useContext(SelectContext);
10
+ if (!context) {
11
+ throw new Error('SelectTrigger must be used within Select');
12
+ }
13
+
14
+ return (
15
+ <button
16
+ type="button"
17
+ onClick={() => context.setOpen(!context.open)}
18
+ className={cn(
19
+ 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
20
+ className,
21
+ )}
22
+ >
23
+ {children}
24
+ <ChevronDown className="size-4 opacity-50" />
25
+ </button>
26
+ );
27
+ }
src/components/base/Select/SelectValue.tsx ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { SelectContext } from './SelectContext';
5
+
6
+ export default function SelectValue({ placeholder }: { placeholder?: string }) {
7
+ const context = React.useContext(SelectContext);
8
+ if (!context) {
9
+ throw new Error('SelectValue must be used within Select');
10
+ }
11
+
12
+ return <span>{context.value || placeholder}</span>;
13
+ }
src/components/base/Select/index.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export { default as Select } from './Select';
2
+ export { default as SelectContent } from './SelectContent';
3
+ export { default as SelectItem } from './SelectItem';
4
+ export { default as SelectTrigger } from './SelectTrigger';
5
+ export { default as SelectValue } from './SelectValue';
src/components/base/Switch/index.tsx ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { cn } from '@/utils/Helpers';
4
+ import * as React from 'react';
5
+
6
+ type SwitchProps = {
7
+ onCheckedChange?: (checked: boolean) => void;
8
+ } & React.InputHTMLAttributes<HTMLInputElement>;
9
+
10
+ const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(({ className, onCheckedChange, ...props }, ref) => {
11
+ return (
12
+ <label className="relative inline-flex cursor-pointer items-center">
13
+ <input
14
+ type="checkbox"
15
+ className="peer sr-only"
16
+ ref={ref}
17
+ onChange={e => onCheckedChange?.(e.target.checked)}
18
+ {...props}
19
+ />
20
+ <div
21
+ className={cn(
22
+ 'relative h-6 w-11 rounded-full bg-muted transition-colors peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-ring peer-focus:ring-offset-2 peer-checked:bg-primary',
23
+ className,
24
+ )}
25
+ >
26
+ <div className="absolute left-[2px] top-[2px] size-5 rounded-full bg-white transition-transform peer-checked:translate-x-5" />
27
+ </div>
28
+ </label>
29
+ );
30
+ });
31
+ Switch.displayName = 'Switch';
32
+
33
+ export { Switch };
src/components/{atoms → base}/index.ts RENAMED
@@ -2,3 +2,6 @@ export { Badge } from './Badge';
2
  export { Button } from './Button';
3
  export * from './Card';
4
  export { Input } from './Input';
 
 
 
 
2
  export { Button } from './Button';
3
  export * from './Card';
4
  export { Input } from './Input';
5
+ export { Label } from './Label';
6
+ export * from './Select';
7
+ export { Switch } from './Switch';
src/components/dashboard/JobCard.tsx ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button, Card } from '@/components/base';
2
+ import { Clock, Globe } from 'lucide-react';
3
+ import Image from 'next/image';
4
+
5
+ type JobCardProps = {
6
+ job: {
7
+ id: number;
8
+ title: string;
9
+ company: string;
10
+ logo: string;
11
+ location: string;
12
+ type: string;
13
+ postedAt: string;
14
+ };
15
+ };
16
+
17
+ export default function JobCard({ job }: JobCardProps) {
18
+ return (
19
+ <Card className="rounded-lg border bg-card p-6">
20
+ <div className="flex items-start gap-4">
21
+ <div className="relative size-12 overflow-hidden rounded-full border">
22
+ <Image src={job.logo || '/placeholder.svg'} alt={`${job.company} logo`} fill className="object-cover" />
23
+ </div>
24
+ <div className="flex-1">
25
+ <h3 className="text-lg font-semibold">{job.title}</h3>
26
+ <p className="text-muted-foreground">{job.company}</p>
27
+ <div className="mt-2 flex items-center gap-4 text-sm text-muted-foreground">
28
+ <div className="flex items-center gap-1">
29
+ <Globe className="size-4" />
30
+ {job.location}
31
+ </div>
32
+ <div className="flex items-center gap-1">
33
+ <Clock className="size-4" />
34
+ {job.type}
35
+ </div>
36
+ </div>
37
+ </div>
38
+ <div className="flex flex-col gap-2">
39
+ <Button variant="outline">View</Button>
40
+ <Button>Apply</Button>
41
+ </div>
42
+ </div>
43
+ </Card>
44
+ );
45
+ }
src/components/dashboard/JobFilters.tsx ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch } from '@/components/base';
4
+ import { Search } from 'lucide-react';
5
+
6
+ export default function JobFilters() {
7
+ return (
8
+ <div className="rounded-lg bg-card p-4">
9
+ <div className="grid grid-cols-1 gap-4 md:grid-cols-4">
10
+ <Select>
11
+ <SelectTrigger>
12
+ <SelectValue placeholder="Location" />
13
+ </SelectTrigger>
14
+ <SelectContent>
15
+ <SelectItem value="anywhere">Anywhere</SelectItem>
16
+ <SelectItem value="vietnam">Vietnam</SelectItem>
17
+ <SelectItem value="usa">USA</SelectItem>
18
+ </SelectContent>
19
+ </Select>
20
+
21
+ <Select>
22
+ <SelectTrigger>
23
+ <SelectValue placeholder="Job type" />
24
+ </SelectTrigger>
25
+ <SelectContent>
26
+ <SelectItem value="fulltime">Full-time</SelectItem>
27
+ <SelectItem value="freelancer">Freelancer</SelectItem>
28
+ <SelectItem value="contract">Contract</SelectItem>
29
+ </SelectContent>
30
+ </Select>
31
+
32
+ <Select>
33
+ <SelectTrigger>
34
+ <SelectValue placeholder="Experience" />
35
+ </SelectTrigger>
36
+ <SelectContent>
37
+ <SelectItem value="entry">Entry Level</SelectItem>
38
+ <SelectItem value="mid">Mid Level</SelectItem>
39
+ <SelectItem value="senior">Senior Level</SelectItem>
40
+ </SelectContent>
41
+ </Select>
42
+
43
+ <div className="relative">
44
+ <Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
45
+ <Input className="pl-9" placeholder="E.g. Front-end developer" type="search" />
46
+ </div>
47
+ </div>
48
+
49
+ <div className="mt-4 flex items-center justify-end space-x-2">
50
+ <Switch id="closed-jobs" />
51
+ <Label htmlFor="closed-jobs">Hide closed jobs</Label>
52
+ </div>
53
+ </div>
54
+ );
55
+ }
src/components/dashboard/SubscribeJob.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { Button, Input } from '@/components/base';
4
+ import { Send } from 'lucide-react';
5
+
6
+ export default function SubscribeJob() {
7
+ return (
8
+ <div className="rounded-lg border bg-card p-6">
9
+ <div className="mb-6">
10
+ <div className="mb-4 flex size-10 items-center justify-center rounded-full bg-primary/10">
11
+ <Send className="size-5 text-primary" />
12
+ </div>
13
+ <h2 className="mb-2 text-xl font-semibold">New remote jobs in your inbox, every Monday!</h2>
14
+ <p className="text-muted-foreground">Subscribe to get your 5-minute brief on tech remote jobs every Monday</p>
15
+ </div>
16
+ <form className="space-y-4">
17
+ <Input type="email" placeholder="Enter your email" />
18
+ <Button className="w-full">Subscribe</Button>
19
+ </form>
20
+ </div>
21
+ );
22
+ }
src/components/homepage/AnnouncementBanner.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { Badge, Button } from '@/components/atoms';
2
  import { ChevronDown, Download } from 'lucide-react';
3
  import Image from 'next/image';
4
 
 
1
+ import { Badge, Button } from '@/components/base';
2
  import { ChevronDown, Download } from 'lucide-react';
3
  import Image from 'next/image';
4
 
src/components/homepage/CustomizationSection.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { Badge, Card } from '@/components/atoms';
2
  import Image from 'next/image';
3
 
4
  export function CustomizationSection() {
 
1
+ import { Badge, Card } from '@/components/base';
2
  import Image from 'next/image';
3
 
4
  export function CustomizationSection() {
src/components/homepage/FeatureSection.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { Badge } from '@/components/atoms';
2
  import { Cloud, FileText, Grid, MessageCircle, Server } from 'lucide-react';
3
  import Image from 'next/image';
4
 
 
1
+ import { Badge } from '@/components/base';
2
  import { Cloud, FileText, Grid, MessageCircle, Server } from 'lucide-react';
3
  import Image from 'next/image';
4