Skip to content

Commit 7e1ba15

Browse files
committed
feat: ambassador page
1 parent a23afec commit 7e1ba15

File tree

9 files changed

+1146
-0
lines changed

9 files changed

+1146
-0
lines changed
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
'use client';
2+
3+
import { CheckIcon, PaperPlaneIcon, SpinnerIcon } from '@phosphor-icons/react';
4+
import { useState } from 'react';
5+
import { SciFiButton } from '@/components/landing/scifi-btn';
6+
import { Input } from '@/components/ui/input';
7+
import { Label } from '@/components/ui/label';
8+
import { Textarea } from '@/components/ui/textarea';
9+
10+
interface FormData {
11+
name: string;
12+
email: string;
13+
xHandle: string;
14+
website: string;
15+
whyAmbassador: string;
16+
experience: string;
17+
audience: string;
18+
referralSource: string;
19+
}
20+
21+
const initialFormData: FormData = {
22+
name: '',
23+
email: '',
24+
xHandle: '',
25+
website: '',
26+
whyAmbassador: '',
27+
experience: '',
28+
audience: '',
29+
referralSource: '',
30+
};
31+
32+
function FormField({
33+
label,
34+
required = false,
35+
children,
36+
description,
37+
}: {
38+
label: string;
39+
required?: boolean;
40+
children: React.ReactNode;
41+
description?: string;
42+
}) {
43+
return (
44+
<div className="space-y-2">
45+
<Label className="text-foreground">
46+
{label}
47+
{required && <span className="ml-1 text-destructive">*</span>}
48+
</Label>
49+
{children}
50+
{description && (
51+
<p className="text-muted-foreground text-xs">{description}</p>
52+
)}
53+
</div>
54+
);
55+
}
56+
57+
export default function AmbassadorForm() {
58+
const [formData, setFormData] = useState<FormData>(initialFormData);
59+
const [isSubmitting, setIsSubmitting] = useState(false);
60+
const [isSubmitted, setIsSubmitted] = useState(false);
61+
62+
const handleInputChange = (
63+
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
64+
) => {
65+
const { name, value } = e.target;
66+
setFormData((prev) => ({ ...prev, [name]: value }));
67+
};
68+
69+
const handleSubmit = async (e: React.FormEvent) => {
70+
e.preventDefault();
71+
setIsSubmitting(true);
72+
73+
try {
74+
const response = await fetch('/api/ambassador/submit', {
75+
method: 'POST',
76+
headers: {
77+
'Content-Type': 'application/json',
78+
},
79+
body: JSON.stringify(formData),
80+
});
81+
82+
const data = await response.json();
83+
84+
if (!response.ok) {
85+
throw new Error(data.error || 'Submission failed');
86+
}
87+
88+
setIsSubmitted(true);
89+
} catch (error) {
90+
console.error('Form submission error:', error);
91+
alert('Failed to submit application. Please try again.');
92+
} finally {
93+
setIsSubmitting(false);
94+
}
95+
};
96+
97+
if (isSubmitted) {
98+
return (
99+
<div className="mx-auto max-w-md text-center">
100+
<div className="group relative">
101+
<div className="relative rounded border border-green-500/50 bg-green-500/5 p-8 backdrop-blur-sm">
102+
<CheckIcon
103+
className="mx-auto mb-4 h-12 w-12 text-green-500"
104+
weight="duotone"
105+
/>
106+
<h3 className="mb-2 font-semibold text-foreground text-xl">
107+
Application Submitted!
108+
</h3>
109+
<p className="text-muted-foreground text-sm">
110+
Thank you for your interest in becoming a Databuddy ambassador.
111+
We'll review your application and get back to you within 3-5
112+
business days.
113+
</p>
114+
</div>
115+
116+
{/* Sci-fi corners */}
117+
<div className="pointer-events-none absolute inset-0">
118+
<div className="absolute top-0 left-0 h-2 w-2">
119+
<div className="absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-green-500" />
120+
<div className="absolute top-0 left-0 h-2 w-0.5 origin-top bg-green-500" />
121+
</div>
122+
<div className="-scale-x-[1] absolute top-0 right-0 h-2 w-2">
123+
<div className="absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-green-500" />
124+
<div className="absolute top-0 left-0 h-2 w-0.5 origin-top bg-green-500" />
125+
</div>
126+
<div className="-scale-y-[1] absolute bottom-0 left-0 h-2 w-2">
127+
<div className="absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-green-500" />
128+
<div className="absolute top-0 left-0 h-2 w-0.5 origin-top bg-green-500" />
129+
</div>
130+
<div className="-scale-[1] absolute right-0 bottom-0 h-2 w-2">
131+
<div className="absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-green-500" />
132+
<div className="absolute top-0 left-0 h-2 w-0.5 origin-top bg-green-500" />
133+
</div>
134+
</div>
135+
</div>
136+
</div>
137+
);
138+
}
139+
140+
return (
141+
<div>
142+
{/* Header */}
143+
<div className="mb-8 text-center">
144+
<h2 className="mb-4 font-semibold text-2xl sm:text-3xl lg:text-4xl">
145+
Ambassador Application
146+
</h2>
147+
<p className="mx-auto max-w-2xl text-muted-foreground text-sm sm:text-base lg:text-lg">
148+
Tell us about yourself and why you'd be a great Databuddy ambassador
149+
</p>
150+
</div>
151+
152+
{/* Form */}
153+
<div className="group relative">
154+
<div className="relative rounded border border-border bg-card/50 p-6 backdrop-blur-sm sm:p-8">
155+
<form className="space-y-6" onSubmit={handleSubmit}>
156+
{/* Personal Information */}
157+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
158+
<FormField label="Full Name" required>
159+
<Input
160+
maxLength={100}
161+
name="name"
162+
onChange={handleInputChange}
163+
placeholder="John Doe"
164+
required
165+
type="text"
166+
value={formData.name}
167+
/>
168+
</FormField>
169+
170+
<FormField label="Email Address" required>
171+
<Input
172+
maxLength={255}
173+
name="email"
174+
onChange={handleInputChange}
175+
placeholder="[email protected]"
176+
required
177+
type="email"
178+
value={formData.email}
179+
/>
180+
</FormField>
181+
</div>
182+
183+
{/* Social & Web Presence */}
184+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
185+
<FormField
186+
description="Enter your X (Twitter) handle without the @"
187+
label="X (Twitter) Handle"
188+
>
189+
<Input
190+
maxLength={50}
191+
name="xHandle"
192+
onChange={handleInputChange}
193+
placeholder="johndoe"
194+
type="text"
195+
value={formData.xHandle}
196+
/>
197+
</FormField>
198+
199+
<FormField
200+
description="Your personal website, blog, or portfolio"
201+
label="Website"
202+
>
203+
<Input
204+
maxLength={500}
205+
name="website"
206+
onChange={handleInputChange}
207+
placeholder="https://johndoe.com"
208+
type="url"
209+
value={formData.website}
210+
/>
211+
</FormField>
212+
</div>
213+
214+
{/* Experience & Background */}
215+
<FormField
216+
description="Tell us about your experience with analytics, privacy, or developer tools (max 800 characters)"
217+
label="Relevant Experience"
218+
>
219+
<Textarea
220+
maxLength={800}
221+
name="experience"
222+
onChange={handleInputChange}
223+
placeholder="I've been working in web development for 5 years and am passionate about privacy-first solutions..."
224+
rows={4}
225+
value={formData.experience}
226+
/>
227+
<div className="text-right text-muted-foreground text-xs">
228+
{formData.experience.length}/800 characters
229+
</div>
230+
</FormField>
231+
232+
{/* Motivation */}
233+
<FormField
234+
description="Required field (max 1000 characters)"
235+
label="Why do you want to be a Databuddy ambassador?"
236+
required
237+
>
238+
<Textarea
239+
maxLength={1000}
240+
name="whyAmbassador"
241+
onChange={handleInputChange}
242+
placeholder="I believe in privacy-first analytics and want to help spread awareness about better data practices..."
243+
required
244+
rows={4}
245+
value={formData.whyAmbassador}
246+
/>
247+
<div className="text-right text-muted-foreground text-xs">
248+
{formData.whyAmbassador.length}/1000 characters
249+
</div>
250+
</FormField>
251+
252+
{/* Audience & Reach */}
253+
<FormField
254+
description="Describe your audience size and engagement across platforms (max 600 characters)"
255+
label="Audience & Reach"
256+
>
257+
<Textarea
258+
maxLength={600}
259+
name="audience"
260+
onChange={handleInputChange}
261+
placeholder="I have 5K followers on X, run a tech blog with 10K monthly visitors, and speak at 3-4 conferences per year..."
262+
rows={3}
263+
value={formData.audience}
264+
/>
265+
<div className="text-right text-muted-foreground text-xs">
266+
{formData.audience.length}/600 characters
267+
</div>
268+
</FormField>
269+
270+
{/* How did you hear about us */}
271+
<FormField label="How did you hear about Databuddy?">
272+
<Input
273+
maxLength={200}
274+
name="referralSource"
275+
onChange={handleInputChange}
276+
placeholder="Twitter, GitHub, friend recommendation, etc."
277+
type="text"
278+
value={formData.referralSource}
279+
/>
280+
</FormField>
281+
282+
{/* Submit Button */}
283+
<div className="pt-4">
284+
<SciFiButton
285+
className="w-full sm:w-auto"
286+
disabled={isSubmitting}
287+
type="submit"
288+
>
289+
{isSubmitting ? (
290+
<>
291+
<SpinnerIcon className="h-4 w-4 animate-spin" />
292+
Submitting Application...
293+
</>
294+
) : (
295+
<>
296+
<PaperPlaneIcon className="h-4 w-4" weight="duotone" />
297+
Submit Application
298+
</>
299+
)}
300+
</SciFiButton>
301+
</div>
302+
</form>
303+
</div>
304+
305+
{/* Sci-fi corners */}
306+
<div className="pointer-events-none absolute inset-0">
307+
<div className="absolute top-0 left-0 h-2 w-2 group-hover:animate-[cornerGlitch_0.6s_ease-in-out]">
308+
<div className="absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-foreground" />
309+
<div className="absolute top-0 left-0 h-2 w-0.5 origin-top bg-foreground" />
310+
</div>
311+
<div className="-scale-x-[1] absolute top-0 right-0 h-2 w-2 group-hover:animate-[cornerGlitch_0.6s_ease-in-out]">
312+
<div className="absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-foreground" />
313+
<div className="absolute top-0 left-0 h-2 w-0.5 origin-top bg-foreground" />
314+
</div>
315+
<div className="-scale-y-[1] absolute bottom-0 left-0 h-2 w-2 group-hover:animate-[cornerGlitch_0.6s_ease-in-out]">
316+
<div className="absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-foreground" />
317+
<div className="absolute top-0 left-0 h-2 w-0.5 origin-top bg-foreground" />
318+
</div>
319+
<div className="-scale-[1] absolute right-0 bottom-0 h-2 w-2 group-hover:animate-[cornerGlitch_0.6s_ease-in-out]">
320+
<div className="absolute top-0 left-0.5 h-0.5 w-1.5 origin-left bg-foreground" />
321+
<div className="absolute top-0 left-0 h-2 w-0.5 origin-top bg-foreground" />
322+
</div>
323+
</div>
324+
</div>
325+
</div>
326+
);
327+
}

0 commit comments

Comments
 (0)