|
1 | | -import { ArrowDown, Lock } from 'lucide-react'; |
| 1 | +'use client'; |
2 | 2 |
|
3 | | -import { Badge } from '@/components/ui/badge'; |
4 | | -import { Button } from '@/components/ui/button'; |
5 | | -import { GridPattern } from '@/components/ui/grid-pattern'; |
| 3 | +import { useState } from 'react'; |
6 | 4 | import Link from 'next/link'; |
| 5 | +import { roles } from '@/data/forms/roles'; |
| 6 | +import { getRolesByDepartment } from '@/lib/utils'; |
| 7 | +import { Input } from '@/components/ui/input'; |
| 8 | +import { |
| 9 | + Card, |
| 10 | + CardHeader, |
| 11 | + CardTitle, |
| 12 | + CardDescription, |
| 13 | + CardContent, |
| 14 | +} from '@/components/ui/card'; |
| 15 | +import { Badge } from '@/components/ui/badge'; |
7 | 16 |
|
8 | | -const Opportunities = () => { |
9 | | - const opportunities = [ |
10 | | - { |
11 | | - category: 'Community & Moderation', |
12 | | - openings: [ |
13 | | - { |
14 | | - title: 'Moderator', |
15 | | - description: |
16 | | - 'Moderators are responsible for enforcing our Code of Conduct and keeping the community safe and welcoming. They are the first line of defense and backbone of the community.', |
17 | | - link: '/apply/mod', |
18 | | - }, |
19 | | - { |
20 | | - title: 'Senior Moderator', |
21 | | - description: |
22 | | - 'Senior Moderators are responsible for overseeing Moderator actions, resolving complex disputes, and keeping the Moderator team running smoothly with wisdom and support.', |
23 | | - link: '/apply/sr-mod', |
24 | | - }, |
25 | | - { |
26 | | - title: 'Assistant Administrator', |
27 | | - description: |
28 | | - 'The Assistant Administrator is responsible for supporting Administrators in the day-to-day running of the community. They help with note taking, task management, and other administrative duties.', |
29 | | - link: '/apply/admin-assistant ', |
30 | | - }, |
31 | | - { |
32 | | - title: 'Director of Moderation', |
33 | | - description: |
34 | | - 'The Director of Moderation is responsible for overseeing the entire moderation team. They establish tone and culture for the team, develop policies and procedures, and onboard new moderators.', |
35 | | - link: '/apply/mod-director', |
36 | | - }, |
37 | | - { |
38 | | - title: 'Administrator', |
39 | | - description: |
40 | | - 'Administrators are responsible for the overall management of the community. They set the vision and direction for the community, make key decisions, and act as a liaison between community and staff.', |
41 | | - link: '/apply/admin', |
42 | | - }, |
43 | | - ], |
44 | | - }, |
45 | | - { |
46 | | - category: 'Systems & Development', |
47 | | - openings: [ |
48 | | - { |
49 | | - title: 'MediaWiki Administrator', |
50 | | - description: |
51 | | - 'The MediaWiki Administrator is responsible for maintaining and improving our wiki. They manage user permissions, handle technical issues, and ensure that the wiki is up to date and running smoothly.', |
52 | | - link: '/apply/wiki-admin', |
53 | | - }, |
54 | | - { |
55 | | - title: 'Web Developer', |
56 | | - description: |
57 | | - 'Web Developers, whether frontend or backend, are responsible for rolling out any web-based projects. They work closely with the creative team and other system administrators to bring these projects to life.', |
58 | | - link: '/apply/web-dev', |
59 | | - }, |
60 | | - { |
61 | | - title: 'Python Developer', |
62 | | - description: |
63 | | - 'Python Developers are responsible for developing and maintaining our open source bot, Tux. They are responsible for adding new features, fixing bugs, and listening to community feedback.', |
64 | | - link: '/apply/python-dev', |
65 | | - }, |
66 | | - ], |
67 | | - }, |
68 | | - { |
69 | | - category: 'Creative Direction & Design', |
70 | | - openings: [ |
71 | | - { |
72 | | - title: 'Creative Director', |
73 | | - description: |
74 | | - 'The Creative Director is responsible for developing and establishing our creative vision. They oversee all design, branding and marketing efforts, and ensure that we maintain a consistent visual identity.', |
75 | | - link: '/apply/creative-director', |
76 | | - }, |
77 | | - { |
78 | | - title: 'Graphic Designer', |
79 | | - description: |
80 | | - 'Graphic Designers are responsible for creating visual assets and branding. They work closely with the creative team to develop logos, emojis, banners, and other visual elements as needed.', |
81 | | - link: '/apply/graphic-designer', |
82 | | - }, |
83 | | - ], |
84 | | - }, |
85 | | - ]; |
| 17 | +export default function GetInvolvedPage() { |
| 18 | + const [searchQuery, setSearchQuery] = useState(''); |
| 19 | + const rolesByDepartment = getRolesByDepartment(roles); |
86 | 20 |
|
87 | | - return ( |
88 | | - <section className="py-32"> |
89 | | - <div className="container"> |
90 | | - <div className="mx-auto max-w-(--breakpoint-lg)"> |
91 | | - <h1 className="text-3xl font-bold md:text-5xl">How you can help</h1> |
92 | | - <div className="mx-auto mt-14 flex flex-col gap-16"> |
93 | | - {opportunities.map((oppCategory) => ( |
94 | | - <div key={oppCategory.category}> |
95 | | - <Badge variant="outline" className="px-2 mb-2"> |
96 | | - {oppCategory.category} |
97 | | - </Badge> |
98 | | - <div> |
99 | | - {oppCategory.openings.map((opp) => ( |
100 | | - <div key={opp.title} className="group block border-b py-7"> |
101 | | - <h2 className="text-2xl font-semibold">{opp.title}</h2> |
102 | | - <p className="mt-1 font-medium text-muted-foreground"> |
103 | | - {opp.description} |
104 | | - </p> |
105 | | - <div className="mt-6 flex justify-between gap-4"> |
106 | | - <Button |
107 | | - variant="outline" |
108 | | - className="w-full bg-tokyonight-brightBlack hover:bg-tokyonight-brightBlack hover:text-white pointer-events-none" |
109 | | - > |
110 | | - Application opening soon |
111 | | - <Link className="ml-2" href={opp.link}> |
112 | | - <Lock className="size-4" /> |
113 | | - </Link> |
114 | | - </Button> |
115 | | - </div> |
116 | | - </div> |
117 | | - ))} |
118 | | - </div> |
119 | | - </div> |
120 | | - ))} |
121 | | - </div> |
122 | | - </div> |
123 | | - </div> |
124 | | - </section> |
125 | | - ); |
126 | | -}; |
| 21 | + // Filter roles based on search query |
| 22 | + const filteredDepartments = Object.entries(rolesByDepartment) |
| 23 | + .map(([department, departmentRoles]) => ({ |
| 24 | + department, |
| 25 | + roles: departmentRoles.filter( |
| 26 | + (role) => |
| 27 | + role.name.toLowerCase().includes(searchQuery.toLowerCase()) || |
| 28 | + role.description.toLowerCase().includes(searchQuery.toLowerCase()) || |
| 29 | + department.toLowerCase().includes(searchQuery.toLowerCase()) |
| 30 | + ), |
| 31 | + })) |
| 32 | + .filter((dept) => dept.roles.length > 0); |
127 | 33 |
|
128 | | -const GetInvolvedHero = () => { |
129 | 34 | return ( |
130 | | - <section className="relative overflow-hidden pt-32"> |
131 | | - <div className="container"> |
132 | | - <div className="mx-auto flex max-w-5xl flex-col items-center"> |
133 | | - <GridPattern className="absolute hidden opacity-25 [mask-image:linear-gradient(to_right,white,transparent,transparent,white)] lg:block" /> |
134 | | - |
135 | | - <div className="z-10 mx-auto flex max-w-5xl flex-col items-center gap-6 text-center"> |
136 | | - <Badge variant="outline"> |
137 | | - Open Positions |
138 | | - <ArrowDown className="ml-2 size-4" /> |
139 | | - </Badge> |
| 35 | + <div className="container mx-auto py-8 px-4"> |
| 36 | + <div className="max-w-2xl mx-auto text-center mb-12"> |
| 37 | + <h1 className="text-4xl font-bold mb-4">Get Involved</h1> |
| 38 | + <p className="text-lg text-muted-foreground"> |
| 39 | + Join our community and make a difference. Find the perfect role that |
| 40 | + matches your skills and interests. |
| 41 | + </p> |
| 42 | + </div> |
140 | 43 |
|
141 | | - <div> |
142 | | - <h1 className="mb-6 text-pretty text-4xl font-bold lg:text-7xl"> |
143 | | - Get Involved |
144 | | - </h1> |
| 44 | + <div className="max-w-md mx-auto mb-8"> |
| 45 | + <Input |
| 46 | + type="search" |
| 47 | + placeholder="Search roles by title, department, or description..." |
| 48 | + value={searchQuery} |
| 49 | + onChange={(e) => setSearchQuery(e.target.value)} |
| 50 | + className="w-full" |
| 51 | + /> |
| 52 | + </div> |
145 | 53 |
|
146 | | - <p className="text-muted-foreground lg:text-xl"> |
147 | | - All Things Linux is a community-driven project so we are always |
148 | | - looking for help with our mission. Whether you are a veteran |
149 | | - developer or just starting out, there are plenty of ways to get |
150 | | - involved and make a difference. |
151 | | - </p> |
| 54 | + <div className="space-y-12"> |
| 55 | + {filteredDepartments.map(({ department, roles }) => ( |
| 56 | + <div key={department}> |
| 57 | + <h2 className="text-2xl font-semibold mb-6">{department}</h2> |
| 58 | + <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> |
| 59 | + {roles.map((role) => ( |
| 60 | + <Link |
| 61 | + key={role.slug} |
| 62 | + href={`/apply/${role.slug}`} |
| 63 | + className="block transition-transform hover:scale-105" |
| 64 | + > |
| 65 | + <Card> |
| 66 | + <CardHeader> |
| 67 | + <div className="flex justify-between items-start"> |
| 68 | + <CardTitle className="text-xl">{role.name}</CardTitle> |
| 69 | + <Badge variant="secondary">{department}</Badge> |
| 70 | + </div> |
| 71 | + <CardDescription>{role.description}</CardDescription> |
| 72 | + </CardHeader> |
| 73 | + <CardContent> |
| 74 | + <div className="flex justify-between items-center"> |
| 75 | + <span className="text-sm text-muted-foreground"> |
| 76 | + {role.questions.length} questions |
| 77 | + </span> |
| 78 | + <span className="text-sm font-medium text-primary"> |
| 79 | + Apply Now → |
| 80 | + </span> |
| 81 | + </div> |
| 82 | + </CardContent> |
| 83 | + </Card> |
| 84 | + </Link> |
| 85 | + ))} |
152 | 86 | </div> |
153 | 87 | </div> |
154 | | - </div> |
155 | | - </div> |
156 | | - </section> |
157 | | - ); |
158 | | -}; |
| 88 | + ))} |
159 | 89 |
|
160 | | -const GetInvolved = () => { |
161 | | - return ( |
162 | | - <> |
163 | | - <GetInvolvedHero /> |
164 | | - <Opportunities /> |
165 | | - </> |
| 90 | + {filteredDepartments.length === 0 && ( |
| 91 | + <div className="text-center py-12"> |
| 92 | + <h3 className="text-lg font-medium">No roles found</h3> |
| 93 | + <p className="text-muted-foreground mt-2"> |
| 94 | + Try adjusting your search query |
| 95 | + </p> |
| 96 | + </div> |
| 97 | + )} |
| 98 | + </div> |
| 99 | + </div> |
166 | 100 | ); |
167 | | -}; |
168 | | - |
169 | | -export default GetInvolved; |
| 101 | +} |
0 commit comments