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:
co-authored by
Maulana Sodiqin
parent
8947b4d05e
commit
ab744b411d
@@ -4,7 +4,6 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>IMPHNEN x Kolosal.ai Hackathon 2025</title>
|
<title>IMPHNEN x Kolosal.ai Hackathon 2025</title>
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
@@ -20,7 +19,6 @@
|
|||||||
href="/images/imphnen-logo-simple.svg"
|
href="/images/imphnen-logo-simple.svg"
|
||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="/src/index.css" />
|
<link rel="stylesheet" href="/src/index.css" />
|
||||||
<!-- Open Graph -->
|
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:url" content="https://hackathon.imphnen.dev/" />
|
<meta property="og:url" content="https://hackathon.imphnen.dev/" />
|
||||||
<meta property="og:title" content="IMPHNEN x Kolosal.ai Hackathon 2025" />
|
<meta property="og:title" content="IMPHNEN x Kolosal.ai Hackathon 2025" />
|
||||||
@@ -28,7 +26,6 @@
|
|||||||
property="og:description"
|
property="og:description"
|
||||||
content="Total hadiah Rp14.500.000! Daftar sekarang dan wujudkan inovasi AI-mu untuk membantu usaha lokal."
|
content="Total hadiah Rp14.500.000! Daftar sekarang dan wujudkan inovasi AI-mu untuk membantu usaha lokal."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<meta property="twitter:card" content="summary_large_image" />
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
<meta property="twitter:url" content="https://hackathon.imphnen.dev/" />
|
<meta property="twitter:url" content="https://hackathon.imphnen.dev/" />
|
||||||
<meta
|
<meta
|
||||||
|
|||||||
@@ -13,62 +13,72 @@ const CallbackPage: FC = (): ReactElement => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleCallback = async () => {
|
const handleCallback = async () => {
|
||||||
if (hasRunRef.current) {
|
if (hasRunRef.current) {
|
||||||
console.log('[Callback] Already processed, skipping...');
|
// console.log('[Callback] Already processed, skipping...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hasRunRef.current = true;
|
hasRunRef.current = true;
|
||||||
try {
|
try {
|
||||||
console.log('[Callback] Processing OAuth callback...');
|
// console.log('[Callback] Processing OAuth callback...');
|
||||||
console.log('[Callback] Current URL:', globalThis.location.href);
|
// console.log('[Callback] Current URL:', globalThis.location.href);
|
||||||
|
|
||||||
// Supabase client is configured with detectSessionInUrl: true
|
// Supabase client is configured with detectSessionInUrl: true
|
||||||
// This means Supabase automatically detects and processes OAuth tokens from the URL hash
|
// 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
|
// 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...');
|
// console.log('[Callback] Waiting for Supabase to process OAuth callback...');
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
// Get the session that Supabase automatically created from the URL hash
|
// 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) {
|
if (sessionError) {
|
||||||
console.error('[Callback] Session error:', sessionError);
|
// console.error('[Callback] Session error:', sessionError);
|
||||||
throw new Error(sessionError.message || 'Failed to get session');
|
throw new Error(sessionError.message || 'Failed to get session');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sessionData || !sessionData.user) {
|
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:', {
|
// console.log('[Callback] Supabase session established:', {
|
||||||
userId: sessionData.user.id,
|
// userId: sessionData.user.id,
|
||||||
email: sessionData.user.email,
|
// email: sessionData.user.email,
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Create/update user in the users table (for foreign key constraints)
|
// 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
|
const { data: userData, error: upsertError } = await supabase
|
||||||
.from('users')
|
.from('users')
|
||||||
.upsert({
|
.upsert(
|
||||||
id: sessionData.user.id,
|
{
|
||||||
email: sessionData.user.email || '',
|
id: sessionData.user.id,
|
||||||
fullname: sessionData.user.user_metadata?.full_name ||
|
email: sessionData.user.email || '',
|
||||||
sessionData.user.user_metadata?.name ||
|
fullname:
|
||||||
sessionData.user.email?.split('@')[0] || '',
|
sessionData.user.user_metadata?.full_name ||
|
||||||
avatar: sessionData.user.user_metadata?.avatar_url || '',
|
sessionData.user.user_metadata?.name ||
|
||||||
is_active: true,
|
sessionData.user.email?.split('@')[0] ||
|
||||||
updated_at: new Date().toISOString(),
|
'',
|
||||||
}, {
|
avatar: sessionData.user.user_metadata?.avatar_url || '',
|
||||||
onConflict: 'id',
|
is_active: true,
|
||||||
})
|
updated_at: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onConflict: 'id',
|
||||||
|
}
|
||||||
|
)
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (upsertError) {
|
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
|
// Don't throw - continue with login even if user record creation fails
|
||||||
} else {
|
} 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
|
// Store user-friendly data in Zustand for UI purposes
|
||||||
@@ -77,9 +87,11 @@ const CallbackPage: FC = (): ReactElement => {
|
|||||||
const userRecord = userData || {
|
const userRecord = userData || {
|
||||||
id: sessionData.user.id,
|
id: sessionData.user.id,
|
||||||
email: sessionData.user.email || '',
|
email: sessionData.user.email || '',
|
||||||
fullname: sessionData.user.user_metadata?.full_name ||
|
fullname:
|
||||||
sessionData.user.user_metadata?.name ||
|
sessionData.user.user_metadata?.full_name ||
|
||||||
sessionData.user.email?.split('@')[0] || '',
|
sessionData.user.user_metadata?.name ||
|
||||||
|
sessionData.user.email?.split('@')[0] ||
|
||||||
|
'',
|
||||||
avatar: sessionData.user.user_metadata?.avatar_url || '',
|
avatar: sessionData.user.user_metadata?.avatar_url || '',
|
||||||
phone_number: '',
|
phone_number: '',
|
||||||
birthdate: '',
|
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!');
|
toast.success('Login successful!');
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
|
|
||||||
// Check if user has completed onboarding (has location)
|
// Check if user has completed onboarding (has location)
|
||||||
// Use globalThis.location.replace for hard redirect to prevent history issues
|
// Use globalThis.location.replace for hard redirect to prevent history issues
|
||||||
if (userRecord.location) {
|
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');
|
globalThis.location.replace('/dashboard');
|
||||||
} else {
|
} else {
|
||||||
console.log('[Callback] User needs onboarding, redirecting...');
|
// console.log('[Callback] User needs onboarding, redirecting...');
|
||||||
globalThis.location.replace('/onboarding/user');
|
globalThis.location.replace('/onboarding/user');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Callback] Error:', err);
|
// console.error('[Callback] Error:', err);
|
||||||
setError((err as Error).message);
|
setError((err as Error).message);
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
toast.error('An error occurred during login');
|
toast.error('An error occurred during login');
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { supabase } from '@imphnen-frontend-service/service';
|
import { supabase } from '@imphnen-frontend-service/service';
|
||||||
import { Link } from 'react-router';
|
import { Link, useNavigate } from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
export default function ForgotPasswordPage() {
|
export default function ForgotPasswordPage() {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [emailSent, setEmailSent] = useState(false);
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -30,7 +32,7 @@ export default function ForgotPasswordPage() {
|
|||||||
setEmailSent(true);
|
setEmailSent(true);
|
||||||
toast.success('Password reset email sent! Check your inbox.');
|
toast.success('Password reset email sent! Check your inbox.');
|
||||||
} catch (err) {
|
} 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');
|
toast.error((err as Error).message || 'Failed to send reset email');
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -55,7 +57,8 @@ export default function ForgotPasswordPage() {
|
|||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<p className="text-sm text-gray-600">
|
<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>
|
</p>
|
||||||
|
|
||||||
<Link to="/auth/login">
|
<Link to="/auth/login">
|
||||||
@@ -90,7 +93,10 @@ export default function ForgotPasswordPage() {
|
|||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<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 Address
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -114,9 +120,13 @@ export default function ForgotPasswordPage() {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-8">
|
||||||
<Link to="/auth/login" className="text-primary-600 hover:text-primary-700 font-semibold">
|
<Link
|
||||||
← Back to Login
|
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>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
import { useState } from 'react';
|
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 { GithubOutlined } from '@ant-design/icons';
|
||||||
import { useNavigate, Link } from 'react-router';
|
import { useNavigate, Link } from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
console.log('[LoginPage] Rendering...');
|
// console.log('[LoginPage] Rendering...');
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setSession } = useAuthStore();
|
const { setSession } = useAuthStore();
|
||||||
@@ -28,10 +34,10 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setIsEmailLoading(true);
|
setIsEmailLoading(true);
|
||||||
console.log('[Login] Attempting email login...');
|
// console.log('[Login] Attempting email login...');
|
||||||
|
|
||||||
const result = await signInWithEmail(email, password);
|
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
|
// Get user data from database
|
||||||
const { data: userData } = await supabase
|
const { data: userData } = await supabase
|
||||||
@@ -49,7 +55,8 @@ export default function LoginPage() {
|
|||||||
user: {
|
user: {
|
||||||
id: result.user.id,
|
id: result.user.id,
|
||||||
email: result.user.email || '',
|
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 || '',
|
phone_number: userData?.phone_number || '',
|
||||||
avatar: userData?.avatar || '',
|
avatar: userData?.avatar || '',
|
||||||
birthdate: userData?.birthdate || '',
|
birthdate: userData?.birthdate || '',
|
||||||
@@ -86,22 +93,22 @@ export default function LoginPage() {
|
|||||||
const handleGithubLogin = async () => {
|
const handleGithubLogin = async () => {
|
||||||
try {
|
try {
|
||||||
setIsGithubLoading(true);
|
setIsGithubLoading(true);
|
||||||
console.log('[Login] Initiating GitHub OAuth...');
|
// console.log('[Login] Initiating GitHub OAuth...');
|
||||||
|
|
||||||
const result = await signInWithGitHub();
|
const result = await signInWithGitHub();
|
||||||
console.log('[Login] OAuth result:', result);
|
// console.log('[Login] OAuth result:', result);
|
||||||
|
|
||||||
// Check if we got a redirect URL
|
// Check if we got a redirect URL
|
||||||
if (result?.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
|
// Manually redirect immediately
|
||||||
globalThis.location.href = result.url;
|
globalThis.location.href = result.url;
|
||||||
} else {
|
} else {
|
||||||
console.error('[Login] No OAuth URL returned');
|
// console.error('[Login] No OAuth URL returned');
|
||||||
setIsGithubLoading(false);
|
setIsGithubLoading(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Login] GitHub login failed:', error);
|
// console.error('[Login] GitHub login failed');
|
||||||
setIsGithubLoading(false);
|
setIsGithubLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -109,11 +116,19 @@ export default function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-screen bg-gray-50 p-4">
|
<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">
|
<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">
|
<div className="text-center mb-8">
|
||||||
<h2 className="text-3xl font-bold text-gray-900 mb-2">
|
<h2 className="text-3xl font-bold text-gray-900 mb-2">
|
||||||
Welcome Back
|
Welcome Back
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600 font-sans">
|
||||||
Sign in to join or create your hackathon team
|
Sign in to join or create your hackathon team
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,7 +141,10 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
<form onSubmit={handleEmailLogin} className="space-y-4">
|
<form onSubmit={handleEmailLogin} className="space-y-4">
|
||||||
<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
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -143,10 +161,16 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<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
|
Password
|
||||||
</label>
|
</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?
|
Forgot password?
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,7 +189,7 @@ export default function LoginPage() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isEmailLoading}
|
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'}
|
{isEmailLoading ? 'Signing in...' : 'Sign in with Email'}
|
||||||
</button>
|
</button>
|
||||||
@@ -181,7 +205,7 @@ export default function LoginPage() {
|
|||||||
onClick={handleGithubLogin}
|
onClick={handleGithubLogin}
|
||||||
disabled={isGithubLoading}
|
disabled={isGithubLoading}
|
||||||
type="button"
|
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" />
|
<GithubOutlined className="text-xl" />
|
||||||
<span>
|
<span>
|
||||||
@@ -192,7 +216,10 @@ export default function LoginPage() {
|
|||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<p className="text-gray-600 text-sm">
|
<p className="text-gray-600 text-sm">
|
||||||
Don't have an account?{' '}
|
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
|
Sign up
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function ResetPasswordPage() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const { error } = await supabase.auth.updateUser({
|
const { error } = await supabase.auth.updateUser({
|
||||||
password: password
|
password: password,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -52,7 +52,7 @@ export default function ResetPasswordPage() {
|
|||||||
await supabase.auth.signOut();
|
await supabase.auth.signOut();
|
||||||
navigate('/auth/login');
|
navigate('/auth/login');
|
||||||
} catch (err) {
|
} 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');
|
toast.error((err as Error).message || 'Failed to reset password');
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@@ -77,14 +77,15 @@ export default function ResetPasswordPage() {
|
|||||||
<h2 className="text-3xl font-bold text-gray-900 mb-2">
|
<h2 className="text-3xl font-bold text-gray-900 mb-2">
|
||||||
Set New Password
|
Set New Password
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">Enter your new password below</p>
|
||||||
Enter your new password below
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<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"
|
||||||
|
>
|
||||||
New Password
|
New Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -101,7 +102,10 @@ export default function ResetPasswordPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Confirm New Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { useState } from 'react';
|
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 { GithubOutlined } from '@ant-design/icons';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
export default function SignupPage() {
|
export default function SignupPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -38,30 +44,31 @@ export default function SignupPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setIsEmailLoading(true);
|
setIsEmailLoading(true);
|
||||||
console.log('[Signup] Attempting email signup...');
|
// console.log('[Signup] Attempting email signup...');
|
||||||
|
|
||||||
const result = await signUpWithEmail(email, password, fullname);
|
const result = await signUpWithEmail(email, password, fullname);
|
||||||
console.log('[Signup] Email signup successful:', result);
|
// console.log('[Signup] Email signup successful:', result);
|
||||||
|
|
||||||
if (!result.user) {
|
if (!result.user) {
|
||||||
throw new Error('Signup failed - no user returned');
|
throw new Error('Signup failed - no user returned');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create user record in database
|
// Create user record in database
|
||||||
const { error: upsertError } = await supabase
|
const { error: upsertError } = await supabase.from('users').upsert(
|
||||||
.from('users')
|
{
|
||||||
.upsert({
|
|
||||||
id: result.user.id,
|
id: result.user.id,
|
||||||
email: result.user.email || '',
|
email: result.user.email || '',
|
||||||
fullname: fullname,
|
fullname: fullname,
|
||||||
is_active: true,
|
is_active: true,
|
||||||
updated_at: new Date().toISOString(),
|
updated_at: new Date().toISOString(),
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
onConflict: 'id',
|
onConflict: 'id',
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (upsertError) {
|
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
|
// If session is available (email confirmation disabled), store it
|
||||||
@@ -94,13 +101,15 @@ export default function SignupPage() {
|
|||||||
navigate('/onboarding/user');
|
navigate('/onboarding/user');
|
||||||
} else {
|
} else {
|
||||||
// Email confirmation is enabled
|
// 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(() => {
|
setTimeout(() => {
|
||||||
navigate('/auth/login');
|
navigate('/auth/login');
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Signup] Email signup failed:', err);
|
// console.error('[Signup] Email signup failed:', err);
|
||||||
setError((err as Error).message || 'Signup failed');
|
setError((err as Error).message || 'Signup failed');
|
||||||
setIsEmailLoading(false);
|
setIsEmailLoading(false);
|
||||||
}
|
}
|
||||||
@@ -109,20 +118,20 @@ export default function SignupPage() {
|
|||||||
const handleGithubLogin = async () => {
|
const handleGithubLogin = async () => {
|
||||||
try {
|
try {
|
||||||
setIsGithubLoading(true);
|
setIsGithubLoading(true);
|
||||||
console.log('[Signup] Initiating GitHub OAuth...');
|
// console.log('[Signup] Initiating GitHub OAuth...');
|
||||||
|
|
||||||
const result = await signInWithGitHub();
|
const result = await signInWithGitHub();
|
||||||
console.log('[Signup] OAuth result:', result);
|
// console.log('[Signup] OAuth result:', result);
|
||||||
|
|
||||||
if (result?.url) {
|
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;
|
globalThis.location.href = result.url;
|
||||||
} else {
|
} else {
|
||||||
console.error('[Signup] No OAuth URL returned');
|
// console.error('[Signup] No OAuth URL returned');
|
||||||
setIsGithubLoading(false);
|
setIsGithubLoading(false);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Signup] GitHub login failed:', error);
|
// console.error('[Signup] GitHub login failed:', error);
|
||||||
setIsGithubLoading(false);
|
setIsGithubLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -130,13 +139,19 @@ export default function SignupPage() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-screen bg-gray-50 p-4">
|
<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">
|
<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">
|
<div className="text-center mb-8">
|
||||||
<h2 className="text-3xl font-bold text-gray-900 mb-2">
|
<h2 className="text-3xl font-bold text-gray-900 mb-2">
|
||||||
Create Account
|
Create Account
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600">Join the hackathon community</p>
|
||||||
Join the hackathon community
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
@@ -147,7 +162,10 @@ export default function SignupPage() {
|
|||||||
|
|
||||||
<form onSubmit={handleEmailSignup} className="space-y-4">
|
<form onSubmit={handleEmailSignup} className="space-y-4">
|
||||||
<div>
|
<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
|
Full Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -163,7 +181,10 @@ export default function SignupPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Email
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -179,7 +200,10 @@ export default function SignupPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -195,7 +219,10 @@ export default function SignupPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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
|
Confirm Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -213,7 +240,7 @@ export default function SignupPage() {
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isEmailLoading}
|
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'}
|
{isEmailLoading ? 'Creating account...' : 'Create Account'}
|
||||||
</button>
|
</button>
|
||||||
@@ -229,7 +256,7 @@ export default function SignupPage() {
|
|||||||
onClick={handleGithubLogin}
|
onClick={handleGithubLogin}
|
||||||
disabled={isGithubLoading}
|
disabled={isGithubLoading}
|
||||||
type="button"
|
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" />
|
<GithubOutlined className="text-xl" />
|
||||||
<span>
|
<span>
|
||||||
@@ -240,7 +267,10 @@ export default function SignupPage() {
|
|||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<p className="text-gray-600 text-sm">
|
<p className="text-gray-600 text-sm">
|
||||||
Already have an account?{' '}
|
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
|
Sign in
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
+190
-177
@@ -64,71 +64,74 @@ export default function HomePage() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-white">
|
<div className="min-h-screen bg-white">
|
||||||
{/* Navigation */}
|
{/* 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 id="#top" className="hidden"></div>
|
||||||
<div className="flex items-center gap-2">
|
<nav className="border-b border-gray-200 bg-white sticky top-0 z-50">
|
||||||
<span className="text-lg md:text-xl font-bold">IMPHNEN</span>
|
<div className="flex items-center justify-between max-w-7xl mx-auto px-4 md:px-8 py-4">
|
||||||
<span className="text-lg md:text-xl font-bold text-primary-500">
|
<div className="flex items-center gap-2">
|
||||||
Hackathon
|
<span className="text-lg md:text-xl font-bold">IMPHNEN</span>
|
||||||
</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>
|
</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>
|
</nav>
|
||||||
|
|
||||||
{/* Mobile Menu */}
|
{/* Mobile Menu */}
|
||||||
@@ -164,90 +167,96 @@ export default function HomePage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* 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">
|
<section className="relative w-full overflow-hidden py-20">
|
||||||
{/* Logos */}
|
<div className="absolute inset-0 overflow-hidden">
|
||||||
<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
|
||||||
<div className="flex items-center">
|
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"
|
||||||
<img
|
style={{ transform: 'translate(10px, -5px)', opacity: 0.9 }}
|
||||||
src="images/imphnen-logo.svg"
|
></div>
|
||||||
alt="IMPHNEN"
|
<div
|
||||||
className="h-12 md:h-16"
|
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>
|
||||||
<span className="text-3xl md:text-5xl lg:text-6xl font-bold text-gray-400">
|
<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>
|
||||||
×
|
|
||||||
</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>
|
||||||
|
<div className="mx-auto container px-4 relative flex flex-col items-center">
|
||||||
{/* Title */}
|
{/* Logos */}
|
||||||
<h1 className="text-h1 font-bold text-gray-900 mb-4 text-center">
|
<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">
|
||||||
Hackathon
|
<div className="flex items-center">
|
||||||
</h1>
|
<img
|
||||||
|
src="images/imphnen-logo.svg"
|
||||||
{/* Subtitle */}
|
alt="IMPHNEN"
|
||||||
<p className="text-p1 text-primary-500 font-semibold mb-6 md:mb-8 text-center px-4">
|
className="h-12 md:h-16"
|
||||||
"Inovasi AI: Mendorong Usaha Lokal dengan AI Inklusif"
|
/>
|
||||||
</p>
|
</div>
|
||||||
|
<span className="text-3xl md:text-5xl font-bold text-gray-400">
|
||||||
{/* Description */}
|
×
|
||||||
<p className="text-p3 text-gray-600 max-w-lg text-center mb-8 md:mb-12 px-4">
|
</span>
|
||||||
Kompetisi pengembangan teknologi untuk menciptakan
|
<div className="flex items-center">
|
||||||
<br className="hidden md:block" /> solusi inovatif yang menghadirkan
|
<img
|
||||||
dampak nyata
|
src="images/sponsors/kolosal-logo_rlxbck.svg"
|
||||||
</p>
|
alt="Kolosal.ai"
|
||||||
|
className="h-8 md:h-12"
|
||||||
{/* Status */}
|
/>
|
||||||
<div className="flex items-center gap-4 md:gap-8 mb-10 md:mb-16 text-base text-gray-600">
|
</div>
|
||||||
<div className="flex items-center gap-2 md:gap-3">
|
|
||||||
<Icon icon="streamline-plump:web" width="16" height="16" />
|
|
||||||
<span>Online</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 md:gap-3 text-center">
|
{/* Title */}
|
||||||
<Icon icon="heroicons:clock" width="16" height="16" />
|
<h1 className="text-h1 font-bold text-gray-900 mb-4 text-center">
|
||||||
<span>Pendaftaran hingga 30 November 2025</span>
|
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>
|
||||||
</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>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -257,12 +266,12 @@ export default function HomePage() {
|
|||||||
<h2 className="text-3xl md:text-5xl font-bold mb-10">
|
<h2 className="text-3xl md:text-5xl font-bold mb-10">
|
||||||
Tentang <span className="text-primary-500">Hackathon</span>
|
Tentang <span className="text-primary-500">Hackathon</span>
|
||||||
</h2>
|
</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>{' '}
|
<span className="font-semibold text-gray-900">Hackathon</span>{' '}
|
||||||
adalah kompetisi pengembangan teknologi yang mengajak talenta muda
|
adalah kompetisi pengembangan teknologi yang mengajak talenta muda
|
||||||
untuk berkolaborasi dalam menciptakan solusi inovatif.
|
untuk berkolaborasi dalam menciptakan solusi inovatif.
|
||||||
</p>
|
</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{' '}
|
IMPHNEN bersama Kolosal.ai mengadakan Hackathon dengan tema lomba{' '}
|
||||||
<span className="font-semibold text-primary-500">
|
<span className="font-semibold text-primary-500">
|
||||||
"Inovasi AI: Mendorong Usaha Lokal dengan AI Inklusif"
|
"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">
|
<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="max-w-lg md:max-w-6xl mx-auto">
|
||||||
<div className="text-center mb-12">
|
<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>
|
Hadiah <span className="text-primary-500">Menarik</span>
|
||||||
</h2>
|
</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
|
Total Prize Pool Rp14.500.000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
{/* Prize 1 */}
|
{/* 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="flex justify-center mb-4">
|
||||||
<div className="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center">
|
<div className="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
@@ -295,14 +304,14 @@ export default function HomePage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl font-bold text-center mb-2">Juara 1</h3>
|
<h3 className="text-xl font-bold text-center mb-2">Juara 1</h3>
|
||||||
<p className="text-3xl font-bold text-primary-500 text-center">
|
<p className="text-2xl font-bold text-primary-500 text-center font-sans">
|
||||||
Rp6.000.000
|
Rp6.000.000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Prize 2 */}
|
{/* 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="flex justify-center mb-4">
|
||||||
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center">
|
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
@@ -311,14 +320,14 @@ export default function HomePage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl font-bold text-center mb-2">Juara 2</h3>
|
<h3 className="text-xl font-bold text-center mb-2">Juara 2</h3>
|
||||||
<p className="text-3xl font-bold text-primary-500 text-center">
|
<p className="text-2xl font-bold text-primary-500 text-center font-sans">
|
||||||
Rp4.000.000
|
Rp4.000.000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Prize 3 */}
|
{/* 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="flex justify-center mb-4">
|
||||||
<div className="w-16 h-16 bg-orange-100 rounded-full flex items-center justify-center">
|
<div className="w-16 h-16 bg-orange-100 rounded-full flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
@@ -327,14 +336,14 @@ export default function HomePage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-2xl font-bold text-center mb-2">Juara 3</h3>
|
<h3 className="text-xl font-bold text-center mb-2">Juara 3</h3>
|
||||||
<p className="text-3xl font-bold text-orange-600 text-center">
|
<p className="text-2xl font-bold text-orange-600 text-center font-sans">
|
||||||
Rp2.500.000
|
Rp2.500.000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Special Prize */}
|
{/* 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="flex justify-center mb-4">
|
||||||
<div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center">
|
<div className="w-16 h-16 bg-purple-100 rounded-full flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
@@ -343,10 +352,10 @@ export default function HomePage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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
|
Juara Kategori Lainnya
|
||||||
</h3>
|
</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
|
Rp2.000.000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -356,8 +365,8 @@ export default function HomePage() {
|
|||||||
|
|
||||||
{/* Timeline Section */}
|
{/* Timeline Section */}
|
||||||
<section id="timeline" className="py-16 md:py-24 px-4 md:px-8 bg-white">
|
<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="max-w-4xl mx-auto font-sans">
|
||||||
<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">
|
<h2 className="text-3xl md:text-5xl font-bold mb-4">
|
||||||
Timeline <span className="text-primary-500">Acara</span>
|
Timeline <span className="text-primary-500">Acara</span>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -456,9 +465,9 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Judges 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="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">
|
<h2 className="text-3xl md:text-5xl font-bold mb-4">
|
||||||
Dewan <span className="text-primary-500">Juri</span>
|
Dewan <span className="text-primary-500">Juri</span>
|
||||||
</h2>
|
</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">
|
<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 */}
|
{/* 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> */}
|
{/* <div className="w-24 h-24 bg-gray-200 rounded-full mx-auto mb-4"></div> */}
|
||||||
<h3 className="text-p3 font-bold mb-1">
|
<h3 className="text-p3 font-bold mb-1">
|
||||||
Alifais Farrel Ramdhani
|
Alifais Farrel Ramdhani
|
||||||
@@ -481,7 +490,7 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Judge 2 */}
|
{/* 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> */}
|
{/* <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>
|
<h3 className="text-p3 font-bold mb-1">Anka Tama</h3>
|
||||||
<div>
|
<div>
|
||||||
@@ -491,7 +500,7 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Judge 3 */}
|
{/* 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> */}
|
{/* <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>
|
<h3 className="text-p3 font-bold mb-1">Hafid Nur</h3>
|
||||||
<div>
|
<div>
|
||||||
@@ -515,7 +524,7 @@ export default function HomePage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4 font-sans">
|
||||||
{faqs.map((faq, index) => (
|
{faqs.map((faq, index) => (
|
||||||
<div key={index} className="border border-gray-200 rounded-lg">
|
<div key={index} className="border border-gray-200 rounded-lg">
|
||||||
<button
|
<button
|
||||||
@@ -551,7 +560,7 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Sponsors 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">
|
<div className="max-w-6xl mx-auto text-center">
|
||||||
<h2 className="text-3xl md:text-5xl font-bold mb-4">
|
<h2 className="text-3xl md:text-5xl font-bold mb-4">
|
||||||
Sponsor & <span className="text-primary-500">Partner</span>
|
Sponsor & <span className="text-primary-500">Partner</span>
|
||||||
@@ -580,10 +589,10 @@ export default function HomePage() {
|
|||||||
{/* CTA Section */}
|
{/* CTA Section */}
|
||||||
<section
|
<section
|
||||||
id="masuk"
|
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">
|
<div className="max-w-4xl mx-auto text-center font-sans">
|
||||||
<h2 className="text-3xl md:text-5xl font-bold mb-6">
|
<h2 className="text-3xl md:text-5xl font-bold mb-6 font-bai-jamjuree">
|
||||||
Segera Daftarkan <span className="text-primary-500">Timmu!</span>
|
Segera Daftarkan <span className="text-primary-500">Timmu!</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg md:text-xl text-left md:text-center text-gray-600 mb-8">
|
<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>
|
</section>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* 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="max-w-6xl mx-auto">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-8">
|
||||||
{/* Brand */}
|
{/* Brand */}
|
||||||
<div>
|
<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">IMPHNEN</span>
|
||||||
<span className="text-2xl font-bold text-blue-500">
|
<span className="text-2xl font-bold text-blue-500">
|
||||||
Hackathon
|
Hackathon
|
||||||
@@ -689,7 +698,9 @@ export default function HomePage() {
|
|||||||
|
|
||||||
{/* Quick Links */}
|
{/* Quick Links */}
|
||||||
<div>
|
<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">
|
<ul className="space-y-2 text-gray-400">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
@@ -725,7 +736,9 @@ export default function HomePage() {
|
|||||||
|
|
||||||
{/* Contact */}
|
{/* Contact */}
|
||||||
<div>
|
<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">
|
<ul className="space-y-2 text-gray-400 text-sm">
|
||||||
<li className="flex items-start gap-2">
|
<li className="flex items-start gap-2">
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
Reference in New Issue
Block a user