Skip to content

Event Register #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions apps/web/app/api/events/register/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { NextResponse } from 'next/server';
import { supabase } from '@/lib/supabase';

export async function POST(request: Request) {
try {
const { fullName, email, eventId } = await request.json();

if (!fullName || !email || !eventId) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}

// First, create or get the profile
const { data: profile, error: profileError } = await supabase
.from('profiles')
.upsert(
{
email,
full_name: fullName,
},
{ onConflict: 'email' }
)
.select()
.single();

if (profileError) {
return NextResponse.json(
{ error: 'Failed to create profile' },
{ status: 500 }
);
}

// Then, register for the event
const { data: registration, error: registrationError } = await supabase
.from('events_participants')
.insert({
event_id: eventId,
profile_id: profile.id,
status: 'registered',
})
.select()
.single();

if (registrationError) {
if (registrationError.code === '23505') {
return NextResponse.json(
{ error: 'Already registered for this event' },
{ status: 409 }
);
}
return NextResponse.json(
{ error: 'Failed to register for event' },
{ status: 500 }
);
}

return NextResponse.json(registration);
} catch (error) {
console.error('Registration error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
29 changes: 21 additions & 8 deletions apps/web/app/events/[slug]/client-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import {
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { RegistrationModal } from '@/components/events/registration-modal';

interface Event {
id: string;
title: string;
date: string;
time: string;
Expand Down Expand Up @@ -62,7 +64,6 @@ const ClientEventPage: FC<ClientEventPageProps> = ({ event }) => {
const handleSocialShare = (platform: string) => {
const text = encodeURIComponent(shareData.text);
const url = encodeURIComponent(shareData.url);
// const title = encodeURIComponent(shareData.title);

const shareUrls = {
twitter: `https://twitter.com/intent/tweet?text=${text}&url=${url}`,
Expand Down Expand Up @@ -160,7 +161,8 @@ const ClientEventPage: FC<ClientEventPageProps> = ({ event }) => {
<h2 className="text-2xl font-semibold mb-4">Tentang Acara</h2>
<div className="prose prose-neutral dark:prose-invert max-w-none">
<p className="text-muted-foreground">
{event.description}
{event.description ||
`Bergabunglah dalam ${event.title} untuk meningkatkan kemampuan pengembangan software Anda. Acara ini dirancang untuk berbagi pengetahuan dan pengalaman dalam dunia teknologi.`}
</p>

<h3 className="text-xl font-semibold mt-6 mb-3">
Expand All @@ -169,7 +171,13 @@ const ClientEventPage: FC<ClientEventPageProps> = ({ event }) => {
<ul className="list-disc list-inside space-y-2 text-muted-foreground">
{event.learningPoints?.map((point, index) => (
<li key={index}>{point}</li>
))}
)) ||
[
'Fundamental dan konsep dasar',
'Best practices dan pattern',
'Tips dan trik dari praktisi',
'Studi kasus dan implementasi praktis',
].map((point, index) => <li key={index}>{point}</li>)}
</ul>

<h3 className="text-xl font-semibold mt-6 mb-3">
Expand All @@ -178,7 +186,12 @@ const ClientEventPage: FC<ClientEventPageProps> = ({ event }) => {
<ul className="list-disc list-inside space-y-2 text-muted-foreground">
{event.requirements?.map((req, index) => (
<li key={index}>{req}</li>
))}
)) ||
[
'Laptop dengan spesifikasi standar',
'Pengetahuan dasar pemrograman',
'Semangat belajar yang tinggi',
].map((req, index) => <li key={index}>{req}</li>)}
</ul>

{event.coordinates && (
Expand Down Expand Up @@ -249,10 +262,10 @@ const ClientEventPage: FC<ClientEventPageProps> = ({ event }) => {
)}

{event.status === 'upcoming' ? (
<Button className="w-full">
Daftar Sekarang
<ExternalLink className="ml-2 h-4 w-4" />
</Button>
<RegistrationModal
eventId={event.id}
eventTitle={event.title}
/>
) : (
<Button variant="outline" className="w-full">
Lihat Dokumentasi
Expand Down
36 changes: 25 additions & 11 deletions apps/web/app/events/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { notFound } from 'next/navigation';
import { events } from '@/constants/events';

type Props = {
params: { slug: string }
}
params: { slug: string };
};

export async function generateMetadata(
props: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const { params } = props;
const event = events.find(e => e.slug === params.slug);
const event = events.find((e) => e.slug === params.slug);

if (!event) {
return {
Expand All @@ -22,10 +22,14 @@ export async function generateMetadata(

return {
title: `${event.title} | LampungDevTech`,
description: event.description || `Bergabunglah dalam ${event.title} untuk meningkatkan kemampuan pengembangan software Anda.`,
description:
event.description ||
`Bergabunglah dalam ${event.title} untuk meningkatkan kemampuan pengembangan software Anda.`,
openGraph: {
title: event.title,
description: event.description || `Bergabunglah dalam ${event.title} untuk meningkatkan kemampuan pengembangan software Anda.`,
description:
event.description ||
`Bergabunglah dalam ${event.title} untuk meningkatkan kemampuan pengembangan software Anda.`,
images: [
{
url: event.image,
Expand All @@ -40,7 +44,9 @@ export async function generateMetadata(
twitter: {
card: 'summary_large_image',
title: event.title,
description: event.description || `Bergabunglah dalam ${event.title} untuk meningkatkan kemampuan pengembangan software Anda.`,
description:
event.description ||
`Bergabunglah dalam ${event.title} untuk meningkatkan kemampuan pengembangan software Anda.`,
images: [event.image],
},
};
Expand All @@ -49,12 +55,20 @@ export async function generateMetadata(
// Split into client and server components
import ClientEventPage from './client-page';

export default async function EventDetailPage({ params }: { params: { slug: string } }) {
const event = events.find(e => e.slug === params.slug);

export default async function EventDetailPage({
params,
}: {
params: { slug: string };
}) {
const event = events.find((e) => e.slug === params.slug);

if (!event) {
notFound();
}

return <ClientEventPage event={{ ...event, status: event.status as 'upcoming' | 'past' }} />;
}
return (
<ClientEventPage
event={{ ...event, status: event.status as 'upcoming' | 'past' }}
/>
);
}
137 changes: 137 additions & 0 deletions apps/web/components/events/registration-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
'use client';

import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useToast } from '@/components/ui/use-toast';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { ExternalLink, Loader2 } from 'lucide-react';

interface RegistrationModalProps {
eventId: string;
eventTitle: string;
trigger?: React.ReactNode;
}

export function RegistrationModal({
eventId,
eventTitle,
trigger,
}: RegistrationModalProps) {
const { toast } = useToast();
const [isOpen, setIsOpen] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [formData, setFormData] = useState({
fullName: '',
email: '',
});

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);

try {
const response = await fetch('/api/events/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...formData,
eventId,
}),
});

const data = await response.json();

if (!response.ok) {
throw new Error(data.error || 'Failed to register');
}

toast({
title: 'Registration Successful!',
description: 'You have successfully registered for the event.',
});

setIsOpen(false);
setFormData({ fullName: '', email: '' });
} catch (error) {
toast({
variant: 'destructive',
title: 'Registration Failed',
description:
error instanceof Error
? error.message
: 'Failed to register for the event',
});
} finally {
setIsLoading(false);
}
};

return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
{trigger || (
<Button className="w-full">
Daftar Sekarang
<ExternalLink className="ml-2 h-4 w-4" />
</Button>
)}
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Daftar Event</DialogTitle>
<DialogDescription>
Silakan isi form pendaftaran untuk mengikuti event {eventTitle}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="fullName">Nama Lengkap</Label>
<Input
id="fullName"
value={formData.fullName}
onChange={(e) =>
setFormData({ ...formData, fullName: e.target.value })
}
required
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) =>
setFormData({ ...formData, email: e.target.value })
}
required
disabled={isLoading}
/>
</div>
<Button type="submit" className="w-full" disabled={isLoading}>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Mendaftar...
</>
) : (
'Kirim Pendaftaran'
)}
</Button>
</form>
</DialogContent>
</Dialog>
);
}
7 changes: 7 additions & 0 deletions apps/web/components/ui/use-toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Toast } from '@/components/ui/toast';

export function useToast() {
return {
toast: Toast,
};
}
Loading