feat(hackathon): update landing page and auth page UI (#60)

* feat: update landing page

* feat: Add favicon and SEO (JSON-LD schema, meta tags)

- Add favicon image
- Add JSON-LD schema for Hackathon event
- Add react-helmet-async for managing meta tags

* chore: remove react-helmet-async

* chore: update landing page

* chore: update UI for signup and login

- add back button for better UX

---------

Co-authored-by: Maulana Sodiqin <sodiqincahyana1@gmail.com>
This commit is contained in:
Hafid Nur
2025-11-25 19:59:08 +07:00
committed by GitHub
co-authored by Maulana Sodiqin
parent 8947b4d05e
commit ab744b411d
7 changed files with 364 additions and 271 deletions
+46 -34
View File
@@ -13,62 +13,72 @@ const CallbackPage: FC = (): ReactElement => {
useEffect(() => {
const handleCallback = async () => {
if (hasRunRef.current) {
console.log('[Callback] Already processed, skipping...');
// console.log('[Callback] Already processed, skipping...');
return;
}
hasRunRef.current = true;
try {
console.log('[Callback] Processing OAuth callback...');
console.log('[Callback] Current URL:', globalThis.location.href);
// console.log('[Callback] Processing OAuth callback...');
// console.log('[Callback] Current URL:', globalThis.location.href);
// Supabase client is configured with detectSessionInUrl: true
// This means Supabase automatically detects and processes OAuth tokens from the URL hash
// We just need to wait a moment for it to complete, then check for the session
console.log('[Callback] Waiting for Supabase to process OAuth callback...');
await new Promise(resolve => setTimeout(resolve, 1000));
// console.log('[Callback] Waiting for Supabase to process OAuth callback...');
await new Promise((resolve) => setTimeout(resolve, 1000));
// Get the session that Supabase automatically created from the URL hash
const { data: { session: sessionData }, error: sessionError } = await supabase.auth.getSession();
const {
data: { session: sessionData },
error: sessionError,
} = await supabase.auth.getSession();
if (sessionError) {
console.error('[Callback] Session error:', sessionError);
// console.error('[Callback] Session error:', sessionError);
throw new Error(sessionError.message || 'Failed to get session');
}
if (!sessionData || !sessionData.user) {
throw new Error('No session found after OAuth callback. Please try logging in again.');
throw new Error(
'No session found after OAuth callback. Please try logging in again.'
);
}
console.log('[Callback] Supabase session established:', {
userId: sessionData.user.id,
email: sessionData.user.email,
});
// console.log('[Callback] Supabase session established:', {
// userId: sessionData.user.id,
// email: sessionData.user.email,
// });
// Create/update user in the users table (for foreign key constraints)
console.log('[Callback] Creating/updating user record...');
// console.log('[Callback] Creating/updating user record...');
const { data: userData, error: upsertError } = await supabase
.from('users')
.upsert({
id: sessionData.user.id,
email: sessionData.user.email || '',
fullname: sessionData.user.user_metadata?.full_name ||
sessionData.user.user_metadata?.name ||
sessionData.user.email?.split('@')[0] || '',
avatar: sessionData.user.user_metadata?.avatar_url || '',
is_active: true,
updated_at: new Date().toISOString(),
}, {
onConflict: 'id',
})
.upsert(
{
id: sessionData.user.id,
email: sessionData.user.email || '',
fullname:
sessionData.user.user_metadata?.full_name ||
sessionData.user.user_metadata?.name ||
sessionData.user.email?.split('@')[0] ||
'',
avatar: sessionData.user.user_metadata?.avatar_url || '',
is_active: true,
updated_at: new Date().toISOString(),
},
{
onConflict: 'id',
}
)
.select()
.single();
if (upsertError) {
console.warn('[Callback] Failed to create user record:', upsertError);
// console.warn('[Callback] Failed to create user record:', upsertError);
// Don't throw - continue with login even if user record creation fails
} else {
console.log('[Callback] User record created/updated successfully');
// console.log('[Callback] User record created/updated successfully');
}
// Store user-friendly data in Zustand for UI purposes
@@ -77,9 +87,11 @@ const CallbackPage: FC = (): ReactElement => {
const userRecord = userData || {
id: sessionData.user.id,
email: sessionData.user.email || '',
fullname: sessionData.user.user_metadata?.full_name ||
sessionData.user.user_metadata?.name ||
sessionData.user.email?.split('@')[0] || '',
fullname:
sessionData.user.user_metadata?.full_name ||
sessionData.user.user_metadata?.name ||
sessionData.user.email?.split('@')[0] ||
'',
avatar: sessionData.user.user_metadata?.avatar_url || '',
phone_number: '',
birthdate: '',
@@ -114,21 +126,21 @@ const CallbackPage: FC = (): ReactElement => {
},
});
console.log('[Callback] Session stored successfully');
// console.log('[Callback] Session stored successfully');
toast.success('Login successful!');
setIsProcessing(false);
// Check if user has completed onboarding (has location)
// Use globalThis.location.replace for hard redirect to prevent history issues
if (userRecord.location) {
console.log('[Callback] User has completed onboarding, redirecting to dashboard...');
// console.log('[Callback] User has completed onboarding, redirecting to dashboard...');
globalThis.location.replace('/dashboard');
} else {
console.log('[Callback] User needs onboarding, redirecting...');
// console.log('[Callback] User needs onboarding, redirecting...');
globalThis.location.replace('/onboarding/user');
}
} catch (err) {
console.error('[Callback] Error:', err);
// console.error('[Callback] Error:', err);
setError((err as Error).message);
setIsProcessing(false);
toast.error('An error occurred during login');
@@ -1,12 +1,14 @@
import { useState } from 'react';
import { supabase } from '@imphnen-frontend-service/service';
import { Link } from 'react-router';
import { Link, useNavigate } from 'react-router';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
export default function ForgotPasswordPage() {
const [email, setEmail] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [emailSent, setEmailSent] = useState(false);
const navigate = useNavigate();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
@@ -30,7 +32,7 @@ export default function ForgotPasswordPage() {
setEmailSent(true);
toast.success('Password reset email sent! Check your inbox.');
} catch (err) {
console.error('Failed to send reset email:', err);
// console.error('Failed to send reset email:', err);
toast.error((err as Error).message || 'Failed to send reset email');
} finally {
setIsLoading(false);
@@ -55,7 +57,8 @@ export default function ForgotPasswordPage() {
<div className="space-y-4">
<p className="text-sm text-gray-600">
Click the link in the email to reset your password. The link will expire in 1 hour.
Click the link in the email to reset your password. The link will
expire in 1 hour.
</p>
<Link to="/auth/login">
@@ -90,7 +93,10 @@ export default function ForgotPasswordPage() {
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-1"
>
Email Address
</label>
<input
@@ -114,9 +120,13 @@ export default function ForgotPasswordPage() {
</button>
</form>
<div className="mt-6 text-center">
<Link to="/auth/login" className="text-primary-600 hover:text-primary-700 font-semibold">
Back to Login
<div className="mt-8">
<Link
to="/auth/login"
className="text-primary-500 hover:text-primary-600 flex items-center"
>
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
<span> Back to Login</span>
</Link>
</div>
</div>
+44 -17
View File
@@ -1,11 +1,17 @@
import { useState } from 'react';
import { useGitHubAuth, useEmailAuth, supabase, useAuthStore } from '@imphnen-frontend-service/service';
import {
useGitHubAuth,
useEmailAuth,
supabase,
useAuthStore,
} from '@imphnen-frontend-service/service';
import { GithubOutlined } from '@ant-design/icons';
import { useNavigate, Link } from 'react-router';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
export default function LoginPage() {
console.log('[LoginPage] Rendering...');
// console.log('[LoginPage] Rendering...');
const navigate = useNavigate();
const { setSession } = useAuthStore();
@@ -28,10 +34,10 @@ export default function LoginPage() {
try {
setIsEmailLoading(true);
console.log('[Login] Attempting email login...');
// console.log('[Login] Attempting email login...');
const result = await signInWithEmail(email, password);
console.log('[Login] Email login successful:', result);
// console.log('[Login] Email login successful:', result);
// Get user data from database
const { data: userData } = await supabase
@@ -49,7 +55,8 @@ export default function LoginPage() {
user: {
id: result.user.id,
email: result.user.email || '',
fullname: userData?.fullname || result.user.user_metadata?.full_name || '',
fullname:
userData?.fullname || result.user.user_metadata?.full_name || '',
phone_number: userData?.phone_number || '',
avatar: userData?.avatar || '',
birthdate: userData?.birthdate || '',
@@ -86,22 +93,22 @@ export default function LoginPage() {
const handleGithubLogin = async () => {
try {
setIsGithubLoading(true);
console.log('[Login] Initiating GitHub OAuth...');
// console.log('[Login] Initiating GitHub OAuth...');
const result = await signInWithGitHub();
console.log('[Login] OAuth result:', result);
// console.log('[Login] OAuth result:', result);
// Check if we got a redirect URL
if (result?.url) {
console.log('[Login] Redirecting to GitHub OAuth:', result.url);
// console.log('[Login] Redirecting to GitHub OAuth:', result.url);
// Manually redirect immediately
globalThis.location.href = result.url;
} else {
console.error('[Login] No OAuth URL returned');
// console.error('[Login] No OAuth URL returned');
setIsGithubLoading(false);
}
} catch (error) {
console.error('[Login] GitHub login failed:', error);
// console.error('[Login] GitHub login failed');
setIsGithubLoading(false);
}
};
@@ -109,11 +116,19 @@ export default function LoginPage() {
return (
<div className="flex justify-center items-center min-h-screen bg-gray-50 p-4">
<div className="bg-white w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200">
<button
onClick={() => navigate('/')}
className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center mb-6"
>
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
Back to Homepage
</button>
<div className="text-center mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-2">
Welcome Back
</h2>
<p className="text-gray-600">
<p className="text-gray-600 font-sans">
Sign in to join or create your hackathon team
</p>
</div>
@@ -126,7 +141,10 @@ export default function LoginPage() {
<form onSubmit={handleEmailLogin} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-1"
>
Email
</label>
<input
@@ -143,10 +161,16 @@ export default function LoginPage() {
<div>
<div className="flex items-center justify-between mb-1">
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700"
>
Password
</label>
<Link to="/auth/forgot-password" className="text-sm text-primary-600 hover:text-primary-700">
<Link
to="/auth/forgot-password"
className="text-sm text-primary-600 hover:text-primary-700"
>
Forgot password?
</Link>
</div>
@@ -165,7 +189,7 @@ export default function LoginPage() {
<button
type="submit"
disabled={isEmailLoading}
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors cursor-pointer"
>
{isEmailLoading ? 'Signing in...' : 'Sign in with Email'}
</button>
@@ -181,7 +205,7 @@ export default function LoginPage() {
onClick={handleGithubLogin}
disabled={isGithubLoading}
type="button"
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 border border-gray-300 rounded-lg font-semibold hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors"
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 border border-gray-300 rounded-lg font-semibold hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors cursor-pointer"
>
<GithubOutlined className="text-xl" />
<span>
@@ -192,7 +216,10 @@ export default function LoginPage() {
<div className="mt-6 text-center">
<p className="text-gray-600 text-sm">
Don't have an account?{' '}
<a href="/auth/signup" className="text-primary-600 hover:text-primary-700 font-semibold">
<a
href="/auth/signup"
className="text-primary-600 hover:text-primary-700 font-semibold"
>
Sign up
</a>
</p>
@@ -39,7 +39,7 @@ export default function ResetPasswordPage() {
setIsLoading(true);
const { error } = await supabase.auth.updateUser({
password: password
password: password,
});
if (error) {
@@ -52,7 +52,7 @@ export default function ResetPasswordPage() {
await supabase.auth.signOut();
navigate('/auth/login');
} catch (err) {
console.error('Failed to reset password:', err);
// console.error('Failed to reset password:', err);
toast.error((err as Error).message || 'Failed to reset password');
} finally {
setIsLoading(false);
@@ -77,14 +77,15 @@ export default function ResetPasswordPage() {
<h2 className="text-3xl font-bold text-gray-900 mb-2">
Set New Password
</h2>
<p className="text-gray-600">
Enter your new password below
</p>
<p className="text-gray-600">Enter your new password below</p>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700 mb-1"
>
New Password
</label>
<input
@@ -101,7 +102,10 @@ export default function ResetPasswordPage() {
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-gray-700 mb-1"
>
Confirm New Password
</label>
<input
+56 -26
View File
@@ -1,8 +1,14 @@
import { useState } from 'react';
import { useGitHubAuth, useEmailAuth, supabase, useAuthStore } from '@imphnen-frontend-service/service';
import {
useGitHubAuth,
useEmailAuth,
supabase,
useAuthStore,
} from '@imphnen-frontend-service/service';
import { GithubOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router';
import { toast } from 'sonner';
import { Icon } from '@iconify/react';
export default function SignupPage() {
const navigate = useNavigate();
@@ -38,30 +44,31 @@ export default function SignupPage() {
try {
setIsEmailLoading(true);
console.log('[Signup] Attempting email signup...');
// console.log('[Signup] Attempting email signup...');
const result = await signUpWithEmail(email, password, fullname);
console.log('[Signup] Email signup successful:', result);
// console.log('[Signup] Email signup successful:', result);
if (!result.user) {
throw new Error('Signup failed - no user returned');
}
// Create user record in database
const { error: upsertError } = await supabase
.from('users')
.upsert({
const { error: upsertError } = await supabase.from('users').upsert(
{
id: result.user.id,
email: result.user.email || '',
fullname: fullname,
is_active: true,
updated_at: new Date().toISOString(),
}, {
},
{
onConflict: 'id',
});
}
);
if (upsertError) {
console.warn('[Signup] Failed to create user record:', upsertError);
console.warn('[Signup] Failed to create user record');
}
// If session is available (email confirmation disabled), store it
@@ -94,13 +101,15 @@ export default function SignupPage() {
navigate('/onboarding/user');
} else {
// Email confirmation is enabled
toast.success('Account created! Please check your email to verify your account.');
toast.success(
'Account created! Please check your email to verify your account.'
);
setTimeout(() => {
navigate('/auth/login');
}, 2000);
}
} catch (err) {
console.error('[Signup] Email signup failed:', err);
// console.error('[Signup] Email signup failed:', err);
setError((err as Error).message || 'Signup failed');
setIsEmailLoading(false);
}
@@ -109,20 +118,20 @@ export default function SignupPage() {
const handleGithubLogin = async () => {
try {
setIsGithubLoading(true);
console.log('[Signup] Initiating GitHub OAuth...');
// console.log('[Signup] Initiating GitHub OAuth...');
const result = await signInWithGitHub();
console.log('[Signup] OAuth result:', result);
// console.log('[Signup] OAuth result:', result);
if (result?.url) {
console.log('[Signup] Redirecting to GitHub OAuth:', result.url);
// console.log('[Signup] Redirecting to GitHub OAuth:', result.url);
globalThis.location.href = result.url;
} else {
console.error('[Signup] No OAuth URL returned');
// console.error('[Signup] No OAuth URL returned');
setIsGithubLoading(false);
}
} catch (error) {
console.error('[Signup] GitHub login failed:', error);
// console.error('[Signup] GitHub login failed:', error);
setIsGithubLoading(false);
}
};
@@ -130,13 +139,19 @@ export default function SignupPage() {
return (
<div className="flex justify-center items-center min-h-screen bg-gray-50 p-4">
<div className="bg-white w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200">
<button
onClick={() => navigate('/')}
className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center mb-6"
>
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
Back to Homepage
</button>
<div className="text-center mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-2">
Create Account
</h2>
<p className="text-gray-600">
Join the hackathon community
</p>
<p className="text-gray-600">Join the hackathon community</p>
</div>
{error && (
@@ -147,7 +162,10 @@ export default function SignupPage() {
<form onSubmit={handleEmailSignup} className="space-y-4">
<div>
<label htmlFor="fullname" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="fullname"
className="block text-sm font-medium text-gray-700 mb-1"
>
Full Name
</label>
<input
@@ -163,7 +181,10 @@ export default function SignupPage() {
</div>
<div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700 mb-1"
>
Email
</label>
<input
@@ -179,7 +200,10 @@ export default function SignupPage() {
</div>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="password"
className="block text-sm font-medium text-gray-700 mb-1"
>
Password
</label>
<input
@@ -195,7 +219,10 @@ export default function SignupPage() {
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-gray-700 mb-1"
>
Confirm Password
</label>
<input
@@ -213,7 +240,7 @@ export default function SignupPage() {
<button
type="submit"
disabled={isEmailLoading}
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors cursor-pointer"
>
{isEmailLoading ? 'Creating account...' : 'Create Account'}
</button>
@@ -229,7 +256,7 @@ export default function SignupPage() {
onClick={handleGithubLogin}
disabled={isGithubLoading}
type="button"
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 border border-gray-300 rounded-lg font-semibold hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors"
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 border border-gray-300 rounded-lg font-semibold hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors cursor-pointer"
>
<GithubOutlined className="text-xl" />
<span>
@@ -240,7 +267,10 @@ export default function SignupPage() {
<div className="mt-6 text-center">
<p className="text-gray-600 text-sm">
Already have an account?{' '}
<a href="/auth/login" className="text-primary-600 hover:text-primary-700 font-semibold">
<a
href="/auth/login"
className="text-primary-600 hover:text-primary-700 font-semibold"
>
Sign in
</a>
</p>
+190 -177
View File
@@ -64,71 +64,74 @@ export default function HomePage() {
return (
<div className="min-h-screen bg-white">
{/* Navigation */}
<nav className="flex items-center justify-between px-4 md:px-8 py-4 border-b border-gray-200 bg-white sticky top-0 z-50 max-w-7xl mx-auto">
<div className="flex items-center gap-2">
<span className="text-lg md:text-xl font-bold">IMPHNEN</span>
<span className="text-lg md:text-xl font-bold text-primary-500">
Hackathon
</span>
<div id="#top" className="hidden"></div>
<nav className="border-b border-gray-200 bg-white sticky top-0 z-50">
<div className="flex items-center justify-between max-w-7xl mx-auto px-4 md:px-8 py-4">
<div className="flex items-center gap-2">
<span className="text-lg md:text-xl font-bold">IMPHNEN</span>
<a
href="#top"
className="text-lg md:text-xl font-bold text-primary-500 hover:cursor-pointer"
>
Hackathon
</a>
</div>
{/* Desktop Menu */}
<div className="hidden md:flex text-label1 items-center gap-4 lg:gap-8">
<a
href="#timeline"
className="text-gray-600 hover:text-gray-900 transition-colors"
>
Timeline
</a>
<a
href="#hadiah"
className="text-gray-600 hover:text-gray-900 transition-colors"
>
Hadiah
</a>
<a
href="#faq"
className="text-gray-600 hover:text-gray-900 transition-colors"
>
FAQ
</a>
<Button
onClick={() => navigate('/auth/login')}
size="sm"
variant="bordered"
className="rounded-lg text-base"
>
Masuk
</Button>
<Button
onClick={() => navigate('/auth/signup')}
size="sm"
className="rounded-lg text-base"
>
Daftar Sekarang
</Button>
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden p-2 cursor-pointer"
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</div>
{/* Desktop Menu */}
<div className="hidden md:flex text-label1 items-center gap-4 lg:gap-8">
<a
href="#timeline"
className="text-gray-600 hover:text-gray-900 transition-colors"
>
Timeline
</a>
<a
href="#hadiah"
className="text-gray-600 hover:text-gray-900 transition-colors"
>
Hadiah
</a>
<a
href="#faq"
className="text-gray-600 hover:text-gray-900 transition-colors"
>
FAQ
</a>
<Button
onClick={() => navigate('/auth/login')}
size="sm"
variant="bordered"
className="rounded-lg text-base"
>
Masuk
</Button>
<Button
onClick={() => navigate('/auth/signup')}
size="sm"
className="rounded-lg text-base"
>
Daftar Sekarang
</Button>
</div>
{/* Mobile Menu Button */}
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden p-2 cursor-pointer"
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
</svg>
</button>
</nav>
{/* Mobile Menu */}
@@ -164,90 +167,96 @@ export default function HomePage() {
)}
{/* Hero Section */}
<section className="flex flex-col items-center justify-center px-4 md:px-8 py-20 md:py-20 bg-linear-to-b from-white to-gray-50">
{/* Logos */}
<div className="flex items-center gap-4 md:gap-8 lg:gap-12 mb-8 md:mb-12 lg:mb-16 flex-wrap justify-center">
<div className="flex items-center">
<img
src="images/imphnen-logo.svg"
alt="IMPHNEN"
className="h-12 md:h-16"
/>
</div>
<span className="text-3xl md:text-5xl lg:text-6xl font-bold text-gray-400">
×
</span>
<div className="flex items-center">
<img
src="images/sponsors/kolosal-logo_rlxbck.svg"
alt="Kolosal.ai"
className="h-8 md:h-12"
/>
</div>
<section className="relative w-full overflow-hidden py-20">
<div className="absolute inset-0 overflow-hidden">
<div
className="absolute top-1/4 -left-20 w-100 h-100 rounded-full bg-linear-to-r from-primary/20 to-blue-400/20 blur-3xl"
style={{ transform: 'translate(10px, -5px)', opacity: 0.9 }}
></div>
<div
className="absolute bottom-1/3 -right-20 w-100 h-100 rounded-full bg-linear-to-r from-blue-400/20 to-primary/20 blur-3xl"
style={{ transform: 'translate(0px, 0px)', opacity: 1 }}
></div>
<div className="absolute inset-0 bg-[linear-gradient(rgba(59,130,246,0.05)_1px,transparent_1px),linear-gradient(to_right,rgba(59,130,246,0.05)_1px,transparent_1px)] bg-size-[40px_40px]"></div>
</div>
{/* Title */}
<h1 className="text-h1 font-bold text-gray-900 mb-4 text-center">
Hackathon
</h1>
{/* Subtitle */}
<p className="text-p1 text-primary-500 font-semibold mb-6 md:mb-8 text-center px-4">
"Inovasi AI: Mendorong Usaha Lokal dengan AI Inklusif"
</p>
{/* Description */}
<p className="text-p3 text-gray-600 max-w-lg text-center mb-8 md:mb-12 px-4">
Kompetisi pengembangan teknologi untuk menciptakan
<br className="hidden md:block" /> solusi inovatif yang menghadirkan
dampak nyata
</p>
{/* Status */}
<div className="flex items-center gap-4 md:gap-8 mb-10 md:mb-16 text-base text-gray-600">
<div className="flex items-center gap-2 md:gap-3">
<Icon icon="streamline-plump:web" width="16" height="16" />
<span>Online</span>
<div className="mx-auto container px-4 relative flex flex-col items-center">
{/* Logos */}
<div className="flex items-center gap-4 md:gap-8 lg:gap-12 mb-8 md:mb-12 lg:mb-16 flex-wrap justify-center">
<div className="flex items-center">
<img
src="images/imphnen-logo.svg"
alt="IMPHNEN"
className="h-12 md:h-16"
/>
</div>
<span className="text-3xl md:text-5xl font-bold text-gray-400">
×
</span>
<div className="flex items-center">
<img
src="images/sponsors/kolosal-logo_rlxbck.svg"
alt="Kolosal.ai"
className="h-8 md:h-12"
/>
</div>
</div>
<div className="flex items-center gap-2 md:gap-3 text-center">
<Icon icon="heroicons:clock" width="16" height="16" />
<span>Pendaftaran hingga 30 November 2025</span>
{/* Title */}
<h1 className="text-h1 font-bold text-gray-900 mb-4 text-center">
Hackathon
</h1>
{/* Subtitle */}
<p className="text-p1 text-primary-500 font-semibold mb-6 md:mb-8 text-center px-4">
"Inovasi AI: Mendorong Usaha Lokal dengan AI Inklusif"
</p>
{/* Description */}
<p className="text-p3 text-gray-600 max-w-lg md:max-w-xl text-center mb-8 md:mb-12 px-4 font-sans">
Kompetisi pengembangan teknologi untuk menciptakan solusi inovatif
yang menghadirkan dampak nyata
</p>
{/* Status */}
<div className="flex items-center gap-4 md:gap-8 mb-10 md:mb-16 text-base text-gray-600">
<div className="flex items-center gap-2 md:gap-3">
<Icon icon="streamline-plump:web" width="16" height="16" />
<span>Online</span>
</div>
<div className="flex items-center gap-2 md:gap-3 text-center">
<Icon icon="heroicons:clock" width="16" height="16" />
<span>Pendaftaran hingga 30 November 2025</span>
</div>
</div>
{/* CTA Buttons */}
<div className="flex items-center gap-4 px-4">
<Button
onClick={() => navigate('/auth/signup')}
className="rounded-lg text-base max-h-auto"
>
Daftar Sekarang
</Button>
<a
href="https://chat.whatsapp.com/BlxrYh9uSC37d7VPhJslGL"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center px-4 py-2.5 border-2 border-gray-300 hover:border-gray-400 transition-colors text-center bg-transparent hover:text-gray-900 hover:bg-gray-50 text-base text-gray-600 rounded-lg font-bai-jamjuree font-semibold"
>
Gabung Grup WA Hackathon
</a>
</div>
{/* Scroll indicator */}
<div className="mt-12 md:mt-20">
<svg
className="w-6 h-6 text-gray-400 animate-bounce"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 14l-7 7m0 0l-7-7m7 7V3"
/>
</svg>
</div>
</div>
{/* CTA Buttons */}
<div className="flex items-center gap-4 px-4">
<Button
onClick={() => navigate('/auth/signup')}
className="rounded-lg text-base max-h-auto"
>
Daftar Sekarang
</Button>
<a
href="https://chat.whatsapp.com/BlxrYh9uSC37d7VPhJslGL"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center justify-center px-4 py-2.5 border-2 border-gray-300 hover:border-gray-400 transition-colors text-center bg-transparent hover:text-gray-900 hover:bg-gray-50 text-base text-gray-600 rounded-lg font-bai-jamjuree font-semibold"
>
Gabung Grup WA Hackathon
</a>
</div>
{/* Scroll indicator */}
<div className="mt-12 md:mt-20">
<svg
className="w-6 h-6 text-gray-400 animate-bounce"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 14l-7 7m0 0l-7-7m7 7V3"
/>
</svg>
</div>
</section>
@@ -257,12 +266,12 @@ export default function HomePage() {
<h2 className="text-3xl md:text-5xl font-bold mb-10">
Tentang <span className="text-primary-500">Hackathon</span>
</h2>
<p className="text-lg md:text-xl text-left md:text-center text-gray-600 mb-6">
<p className="font-sans text-lg md:text-xl text-left md:text-center text-gray-600 mb-6">
<span className="font-semibold text-gray-900">Hackathon</span>{' '}
adalah kompetisi pengembangan teknologi yang mengajak talenta muda
untuk berkolaborasi dalam menciptakan solusi inovatif.
</p>
<p className="text-lg md:text-xl text-left md:text-center text-gray-600">
<p className="font-sans text-lg md:text-xl text-left md:text-center text-gray-600">
IMPHNEN bersama Kolosal.ai mengadakan Hackathon dengan tema lomba{' '}
<span className="font-semibold text-primary-500">
"Inovasi AI: Mendorong Usaha Lokal dengan AI Inklusif"
@@ -276,17 +285,17 @@ export default function HomePage() {
<section id="hadiah" className="py-16 md:py-24 px-4 md:px-8 bg-gray-50">
<div className="max-w-lg md:max-w-6xl mx-auto">
<div className="text-center mb-12">
<h2 className="text-3xl md:text-5xl font-bold mb-4">
<h2 className="text-3xl md:text-5xl font-bold mb-4 font-bai-jamjuree">
Hadiah <span className="text-primary-500">Menarik</span>
</h2>
<p className="text-xl md:text-2xl text-primary-500 font-semibold">
<p className="text-xl md:text-2xl text-primary-500 font-semibold font-sans">
Total Prize Pool Rp14.500.000
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{/* Prize 1 */}
<div className="bg-white rounded-xl p-8 shadow-lg border-2 border-gray-300 hover:border-primary-500 transition-colors">
<div className="bg-white rounded-xl px-4 py-8 shadow-lg border-2 border-gray-300 hover:border-primary-500 transition-colors">
<div className="flex justify-center mb-4">
<div className="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center">
<Icon
@@ -295,14 +304,14 @@ export default function HomePage() {
/>
</div>
</div>
<h3 className="text-2xl font-bold text-center mb-2">Juara 1</h3>
<p className="text-3xl font-bold text-primary-500 text-center">
<h3 className="text-xl font-bold text-center mb-2">Juara 1</h3>
<p className="text-2xl font-bold text-primary-500 text-center font-sans">
Rp6.000.000
</p>
</div>
{/* Prize 2 */}
<div className="bg-white rounded-xl p-8 shadow-lg border-2 border-gray-300 hover:border-gray-500 transition-colors">
<div className="bg-white rounded-xl px-4 py-8 shadow-lg border-2 border-gray-300 hover:border-gray-500 transition-colors">
<div className="flex justify-center mb-4">
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center">
<Icon
@@ -311,14 +320,14 @@ export default function HomePage() {
/>
</div>
</div>
<h3 className="text-2xl font-bold text-center mb-2">Juara 2</h3>
<p className="text-3xl font-bold text-primary-500 text-center">
<h3 className="text-xl font-bold text-center mb-2">Juara 2</h3>
<p className="text-2xl font-bold text-primary-500 text-center font-sans">
Rp4.000.000
</p>
</div>
{/* Prize 3 */}
<div className="bg-white rounded-xl p-8 shadow-lg border-2 border-gray-300 hover:border-orange-300 transition-colors">
<div className="bg-white rounded-xl px-4 py-8 shadow-lg border-2 border-gray-300 hover:border-orange-300 transition-colors">
<div className="flex justify-center mb-4">
<div className="w-16 h-16 bg-orange-100 rounded-full flex items-center justify-center">
<Icon
@@ -327,14 +336,14 @@ export default function HomePage() {
/>
</div>
</div>
<h3 className="text-2xl font-bold text-center mb-2">Juara 3</h3>
<p className="text-3xl font-bold text-orange-600 text-center">
<h3 className="text-xl font-bold text-center mb-2">Juara 3</h3>
<p className="text-2xl font-bold text-orange-600 text-center font-sans">
Rp2.500.000
</p>
</div>
{/* Special Prize */}
<div className="bg-white rounded-xl p-8 shadow-lg border-2 border-gray-300 hover:border-purple-300 transition-colors">
<div className="bg-white rounded-xl px-4 py-8 shadow-lg border-2 border-gray-300 hover:border-purple-300 transition-colors">
<div className="flex justify-center mb-4">
<div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center">
<Icon
@@ -343,10 +352,10 @@ export default function HomePage() {
/>
</div>
</div>
<h3 className="text-2xl font-bold text-center mb-2">
<h3 className="text-xl font-bold text-center mb-2">
Juara Kategori Lainnya
</h3>
<p className="text-3xl font-bold text-purple-600 text-center">
<p className="text-2xl font-bold text-purple-600 text-center font-sans">
Rp2.000.000
</p>
</div>
@@ -356,8 +365,8 @@ export default function HomePage() {
{/* Timeline Section */}
<section id="timeline" className="py-16 md:py-24 px-4 md:px-8 bg-white">
<div className="max-w-4xl mx-auto">
<div className="text-center mb-12">
<div className="max-w-4xl mx-auto font-sans">
<div className="text-center mb-12 font-bai-jamjuree">
<h2 className="text-3xl md:text-5xl font-bold mb-4">
Timeline <span className="text-primary-500">Acara</span>
</h2>
@@ -456,9 +465,9 @@ export default function HomePage() {
</section>
{/* Judges Section */}
<section className="py-16 md:py-24 px-4 md:px-8 bg-gray-50">
<section className="py-16 md:py-24 px-4 md:px-8 bg-gray-50 font-sans">
<div className="md:max-w-6xl mx-auto">
<div className="text-center mb-12">
<div className="text-center mb-12 font-bai-jamjuree">
<h2 className="text-3xl md:text-5xl font-bold mb-4">
Dewan <span className="text-primary-500">Juri</span>
</h2>
@@ -469,7 +478,7 @@ export default function HomePage() {
<div className="w-full max-w-md md:max-w-4xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-8">
{/* Judge 1 */}
<div className="flex flex-col justify-between bg-white rounded-xl p-8 shadow-lg text-center">
<div className="flex flex-col justify-between bg-white rounded-xl px-4 py-8 shadow-lg text-center">
{/* <div className="w-24 h-24 bg-gray-200 rounded-full mx-auto mb-4"></div> */}
<h3 className="text-p3 font-bold mb-1">
Alifais Farrel Ramdhani
@@ -481,7 +490,7 @@ export default function HomePage() {
</div>
{/* Judge 2 */}
<div className="flex flex-col justify-between bg-white rounded-xl p-8 shadow-lg text-center">
<div className="flex flex-col justify-between bg-white rounded-xl px-4 py-8 shadow-lg text-center">
{/* <div className="w-24 h-24 bg-gray-200 rounded-full mx-auto mb-4"></div> */}
<h3 className="text-p3 font-bold mb-1">Anka Tama</h3>
<div>
@@ -491,7 +500,7 @@ export default function HomePage() {
</div>
{/* Judge 3 */}
<div className="flex flex-col justify-between bg-white rounded-xl p-8 shadow-lg text-center">
<div className="flex flex-col justify-between bg-white rounded-xl px-4 py-8 shadow-lg text-center">
{/* <div className="w-24 h-24 bg-gray-200 rounded-full mx-auto mb-4"></div> */}
<h3 className="text-p3 font-bold mb-1">Hafid Nur</h3>
<div>
@@ -515,7 +524,7 @@ export default function HomePage() {
</p>
</div>
<div className="space-y-4">
<div className="space-y-4 font-sans">
{faqs.map((faq, index) => (
<div key={index} className="border border-gray-200 rounded-lg">
<button
@@ -551,7 +560,7 @@ export default function HomePage() {
</section>
{/* Sponsors Section */}
<section className="py-16 md:py-24 px-4 md:px-8 bg-gray-50">
<section className="py-20 md:py-28 px-4 md:px-8 bg-gray-50">
<div className="max-w-6xl mx-auto text-center">
<h2 className="text-3xl md:text-5xl font-bold mb-4">
Sponsor & <span className="text-primary-500">Partner</span>
@@ -580,10 +589,10 @@ export default function HomePage() {
{/* CTA Section */}
<section
id="masuk"
className="py-16 md:py-24 px-4 md:px-8 bg-linear-to-b from-white to-blue-50"
className="py-20 md:py-28 px-4 md:px-8 bg-linear-to-b from-white to-blue-50"
>
<div className="max-w-4xl mx-auto text-center">
<h2 className="text-3xl md:text-5xl font-bold mb-6">
<div className="max-w-4xl mx-auto text-center font-sans">
<h2 className="text-3xl md:text-5xl font-bold mb-6 font-bai-jamjuree">
Segera Daftarkan <span className="text-primary-500">Timmu!</span>
</h2>
<p className="text-lg md:text-xl text-left md:text-center text-gray-600 mb-8">
@@ -620,12 +629,12 @@ export default function HomePage() {
</section>
{/* Footer */}
<footer className="bg-gray-900 text-white py-12 px-4 md:px-8">
<footer className="bg-gray-900 text-white py-12 px-4 md:px-8 font-sans">
<div className="max-w-6xl mx-auto">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
{/* Brand */}
<div>
<div className="flex items-center gap-2 mb-4">
<div className="flex items-center gap-2 mb-4 font-bai-jamjuree">
<span className="text-2xl font-bold">IMPHNEN</span>
<span className="text-2xl font-bold text-blue-500">
Hackathon
@@ -689,7 +698,9 @@ export default function HomePage() {
{/* Quick Links */}
<div>
<h3 className="font-bold text-lg mb-4">Quick Links</h3>
<h3 className="font-bold text-lg mb-4 font-bai-jamjuree">
Quick Links
</h3>
<ul className="space-y-2 text-gray-400">
<li>
<a
@@ -725,7 +736,9 @@ export default function HomePage() {
{/* Contact */}
<div>
<h3 className="font-bold text-lg mb-4">Contact</h3>
<h3 className="font-bold text-lg mb-4 font-bai-jamjuree">
Contact
</h3>
<ul className="space-y-2 text-gray-400 text-sm">
<li className="flex items-start gap-2">
<Icon