Skip to content
This repository was archived by the owner on Mar 24, 2026. It is now read-only.

Commit f7b048e

Browse files
committed
feat(forms): implement dynamic form generation and role management
Introduce a dynamic form generation system using Zod for schema validation, allowing for flexible and reusable form components. This change includes the creation of a `FormWrapper` component and associated input components (`InputField`, `TextareaField`, `SelectField`) to handle various form input types. The forms are now generated based on a structured set of questions, which are categorized into general, departmental, and role-specific questions. Additionally, a new role management system is implemented, organizing roles by department and allowing for easy addition and modification of roles and their associated questions. This is achieved through a centralized data structure that defines roles and their questions, improving maintainability and scalability. The changes also include the removal of hardcoded forms and schemas, replacing them with a more modular and dynamic approach. This allows for easier updates and additions to the form system without modifying the core logic. The motivation behind these changes is to enhance the flexibility and scalability of the form system, making it easier to manage and extend as new roles and questions are introduced. This approach also improves code maintainability by centralizing role and question definitions, reducing redundancy and potential errors.
1 parent c5f6fec commit f7b048e

File tree

33 files changed

+1559
-621
lines changed

33 files changed

+1559
-621
lines changed

01-2025-blog-release.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ date: '2025-01-31'
66
category: 'News'
77
---
88

9-
import { Alert } from '@/components/ui/alert';
9+
import { Alert } from '@/components/mdx/alert';
1010

1111
# Enjoy the power of MDX!
1212

@@ -90,7 +90,7 @@ function example() {
9090
9191
## GitHub-style Alerts
9292

93-
You can use GitHub-style alerts to highlight important information:
93+
You can use alerts to highlight important information:
9494

9595
<Alert type="note">
9696
This is a note alert. Use it to highlight information that users should take

app/apply/[role]/page.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use client';
2+
3+
import { useParams, notFound } from 'next/navigation';
4+
import { useForm } from 'react-hook-form';
5+
import { zodResolver } from '@hookform/resolvers/zod';
6+
import { roles } from '@/data/forms/roles';
7+
import { generalQuestions } from '@/data/forms/questions/general';
8+
import FormWrapper from '@/components/forms/FormWrapper';
9+
import { generateFormSchema } from '@/lib/utils';
10+
11+
export default function RoleApplicationPage() {
12+
const params = useParams();
13+
const roleSlug = params.role as string;
14+
15+
const role = roles.find((r) => r.slug === roleSlug);
16+
if (!role) {
17+
notFound();
18+
}
19+
20+
const questions = [
21+
// general questions
22+
...generalQuestions,
23+
// departmental and role questions
24+
...role.questions,
25+
];
26+
27+
const formSchema = generateFormSchema(questions);
28+
29+
const form = useForm({
30+
resolver: zodResolver(formSchema),
31+
defaultValues: {},
32+
});
33+
34+
const onSubmit = async (data: any) => {
35+
await fetch(`/api/forms/${role.slug}`, {
36+
method: 'POST',
37+
body: JSON.stringify(data),
38+
});
39+
40+
// handle submission confirmation logic here
41+
};
42+
43+
return (
44+
<FormWrapper
45+
form={form}
46+
questions={questions}
47+
title={`${role.name} Application`}
48+
description={`Apply for our ${role.department} team`}
49+
onSubmit={form.handleSubmit(onSubmit)}
50+
/>
51+
);
52+
}

app/blog/[category]/loading.tsx

Lines changed: 0 additions & 40 deletions
This file was deleted.

app/get-involved/page.tsx

Lines changed: 89 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,101 @@
1-
import { ArrowDown, Lock } from 'lucide-react';
1+
'use client';
22

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';
64
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';
716

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);
8620

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);
12733

128-
const GetInvolvedHero = () => {
12934
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>
14043

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>
14553

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+
))}
15286
</div>
15387
</div>
154-
</div>
155-
</div>
156-
</section>
157-
);
158-
};
88+
))}
15989

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>
166100
);
167-
};
168-
169-
export default GetInvolved;
101+
}

0 commit comments

Comments
 (0)