Merge branch 'develop' into hackathon/fix-ui
This commit is contained in:
@@ -9,6 +9,7 @@ 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';
|
import { Icon } from '@iconify/react';
|
||||||
|
import { ThemeToggle } from '../../../components/theme-toggle';
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
// console.log('[LoginPage] Rendering...');
|
// console.log('[LoginPage] Rendering...');
|
||||||
@@ -114,28 +115,31 @@ 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 dark:bg-neutral-950 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 dark:bg-neutral-900 w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200 dark:border-neutral-700">
|
||||||
<button
|
<div className="flex items-center justify-between mb-6">
|
||||||
onClick={() => navigate('/')}
|
<button
|
||||||
className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center mb-6"
|
onClick={() => navigate('/')}
|
||||||
>
|
className="cursor-pointer text-primary-500 hover:text-primary-600 dark:text-primary-400 dark:hover:text-primary-300 text-base font-sans flex items-center"
|
||||||
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
|
>
|
||||||
Back to Homepage
|
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
|
||||||
</button>
|
Back to Homepage
|
||||||
|
</button>
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
|
||||||
<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 dark:text-white mb-2">
|
||||||
Welcome Back
|
Welcome Back
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 font-sans">
|
<p className="text-gray-600 dark:text-neutral-400 font-sans">
|
||||||
Sign in to join or create your hackathon team
|
Sign in to join or create your hackathon team
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-6 p-3 bg-red-50 border border-red-200 rounded-lg">
|
<div className="mb-6 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
||||||
<p className="text-red-600 text-sm">{error}</p>
|
<p className="text-red-600 dark:text-red-400 text-sm">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -143,7 +147,7 @@ export default function LoginPage() {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="email"
|
htmlFor="email"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-1"
|
||||||
>
|
>
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
@@ -154,7 +158,7 @@ export default function LoginPage() {
|
|||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="your@email.com"
|
placeholder="your@email.com"
|
||||||
disabled={isEmailLoading}
|
disabled={isEmailLoading}
|
||||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
className="w-full px-4 py-2.5 border border-gray-300 dark:border-neutral-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 disabled:bg-gray-100 dark:disabled:bg-neutral-700 disabled:cursor-not-allowed"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,13 +167,13 @@ export default function LoginPage() {
|
|||||||
<div className="flex items-center justify-between mb-1">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<label
|
<label
|
||||||
htmlFor="password"
|
htmlFor="password"
|
||||||
className="block text-sm font-medium text-gray-700"
|
className="block text-sm font-medium text-gray-700 dark:text-neutral-300"
|
||||||
>
|
>
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<Link
|
<Link
|
||||||
to="/auth/forgot-password"
|
to="/auth/forgot-password"
|
||||||
className="text-sm text-primary-600 hover:text-primary-700"
|
className="text-sm text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
|
||||||
>
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
</Link>
|
</Link>
|
||||||
@@ -181,7 +185,7 @@ export default function LoginPage() {
|
|||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
disabled={isEmailLoading}
|
disabled={isEmailLoading}
|
||||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
className="w-full px-4 py-2.5 border border-gray-300 dark:border-neutral-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 disabled:bg-gray-100 dark:disabled:bg-neutral-700 disabled:cursor-not-allowed"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -189,23 +193,23 @@ 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 cursor-pointer"
|
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 dark:focus:ring-offset-neutral-900 disabled:bg-gray-400 dark:disabled:bg-neutral-600 disabled:cursor-not-allowed transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
{isEmailLoading ? 'Signing in...' : 'Sign in with Email'}
|
{isEmailLoading ? 'Signing in...' : 'Sign in with Email'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="my-6 flex items-center">
|
<div className="my-6 flex items-center">
|
||||||
<div className="flex-1 border-t border-gray-300"></div>
|
<div className="flex-1 border-t border-gray-300 dark:border-neutral-600"></div>
|
||||||
<span className="px-4 text-sm text-gray-500">OR</span>
|
<span className="px-4 text-sm text-gray-500 dark:text-neutral-400">OR</span>
|
||||||
<div className="flex-1 border-t border-gray-300"></div>
|
<div className="flex-1 border-t border-gray-300 dark:border-neutral-600"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
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 cursor-pointer"
|
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 dark:bg-neutral-800 border border-gray-300 dark:border-neutral-600 rounded-lg font-semibold text-gray-900 dark:text-white hover:bg-gray-200 dark:hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 dark:focus:ring-offset-neutral-900 disabled:bg-gray-100 dark:disabled:bg-neutral-800 disabled:cursor-not-allowed transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<GithubOutlined className="text-xl" />
|
<GithubOutlined className="text-xl" />
|
||||||
<span>
|
<span>
|
||||||
@@ -214,11 +218,11 @@ export default function LoginPage() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<p className="text-gray-600 text-sm">
|
<p className="text-gray-600 dark:text-neutral-400 text-sm">
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<a
|
<a
|
||||||
href="/auth/signup"
|
href="/auth/signup"
|
||||||
className="text-primary-600 hover:text-primary-700 font-semibold"
|
className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-semibold"
|
||||||
>
|
>
|
||||||
Sign up
|
Sign up
|
||||||
</a>
|
</a>
|
||||||
@@ -226,7 +230,7 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<p className="text-gray-500 text-xs">
|
<p className="text-gray-500 dark:text-neutral-500 text-xs">
|
||||||
By signing in, you agree to our Terms of Service and Privacy Policy
|
By signing in, you agree to our Terms of Service and Privacy Policy
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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';
|
import { Icon } from '@iconify/react';
|
||||||
|
import { ThemeToggle } from '../../../components/theme-toggle';
|
||||||
|
|
||||||
export default function SignupPage() {
|
export default function SignupPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -137,26 +138,29 @@ 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 dark:bg-neutral-950 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 dark:bg-neutral-900 w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200 dark:border-neutral-700">
|
||||||
<button
|
<div className="flex items-center justify-between mb-6">
|
||||||
onClick={() => navigate('/')}
|
<button
|
||||||
className="cursor-pointer text-primary-500 hover:text-primary-600 text-base font-sans flex items-center mb-6"
|
onClick={() => navigate('/')}
|
||||||
>
|
className="cursor-pointer text-primary-500 hover:text-primary-600 dark:text-primary-400 dark:hover:text-primary-300 text-base font-sans flex items-center"
|
||||||
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
|
>
|
||||||
Back to Homepage
|
<Icon icon="ic:baseline-chevron-left" width="24" height="24" />
|
||||||
</button>
|
Back to Homepage
|
||||||
|
</button>
|
||||||
|
<ThemeToggle />
|
||||||
|
</div>
|
||||||
|
|
||||||
<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 dark:text-white mb-2">
|
||||||
Create Account
|
Create Account
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600">Join the hackathon community</p>
|
<p className="text-gray-600 dark:text-neutral-400">Join the hackathon community</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mb-6 p-3 bg-red-50 border border-red-200 rounded-lg">
|
<div className="mb-6 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
||||||
<p className="text-red-600 text-sm">{error}</p>
|
<p className="text-red-600 dark:text-red-400 text-sm">{error}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -164,7 +168,7 @@ export default function SignupPage() {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="fullname"
|
htmlFor="fullname"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-1"
|
||||||
>
|
>
|
||||||
Full Name
|
Full Name
|
||||||
</label>
|
</label>
|
||||||
@@ -175,7 +179,7 @@ export default function SignupPage() {
|
|||||||
onChange={(e) => setFullname(e.target.value)}
|
onChange={(e) => setFullname(e.target.value)}
|
||||||
placeholder="John Doe"
|
placeholder="John Doe"
|
||||||
disabled={isEmailLoading}
|
disabled={isEmailLoading}
|
||||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
className="w-full px-4 py-2.5 border border-gray-300 dark:border-neutral-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 disabled:bg-gray-100 dark:disabled:bg-neutral-700 disabled:cursor-not-allowed"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,7 +187,7 @@ export default function SignupPage() {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="email"
|
htmlFor="email"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-1"
|
||||||
>
|
>
|
||||||
Email
|
Email
|
||||||
</label>
|
</label>
|
||||||
@@ -194,7 +198,7 @@ export default function SignupPage() {
|
|||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="your@email.com"
|
placeholder="your@email.com"
|
||||||
disabled={isEmailLoading}
|
disabled={isEmailLoading}
|
||||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
className="w-full px-4 py-2.5 border border-gray-300 dark:border-neutral-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 disabled:bg-gray-100 dark:disabled:bg-neutral-700 disabled:cursor-not-allowed"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -202,7 +206,7 @@ export default function SignupPage() {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="password"
|
htmlFor="password"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-1"
|
||||||
>
|
>
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
@@ -213,7 +217,7 @@ export default function SignupPage() {
|
|||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
disabled={isEmailLoading}
|
disabled={isEmailLoading}
|
||||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
className="w-full px-4 py-2.5 border border-gray-300 dark:border-neutral-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 disabled:bg-gray-100 dark:disabled:bg-neutral-700 disabled:cursor-not-allowed"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,7 +225,7 @@ export default function SignupPage() {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="confirmPassword"
|
htmlFor="confirmPassword"
|
||||||
className="block text-sm font-medium text-gray-700 mb-1"
|
className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-1"
|
||||||
>
|
>
|
||||||
Confirm Password
|
Confirm Password
|
||||||
</label>
|
</label>
|
||||||
@@ -232,7 +236,7 @@ export default function SignupPage() {
|
|||||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
placeholder="••••••••"
|
placeholder="••••••••"
|
||||||
disabled={isEmailLoading}
|
disabled={isEmailLoading}
|
||||||
className="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed"
|
className="w-full px-4 py-2.5 border border-gray-300 dark:border-neutral-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 disabled:bg-gray-100 dark:disabled:bg-neutral-700 disabled:cursor-not-allowed"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -240,23 +244,23 @@ 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 cursor-pointer"
|
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 dark:focus:ring-offset-neutral-900 disabled:bg-gray-400 dark:disabled:bg-neutral-600 disabled:cursor-not-allowed transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
{isEmailLoading ? 'Creating account...' : 'Create Account'}
|
{isEmailLoading ? 'Creating account...' : 'Create Account'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="my-6 flex items-center">
|
<div className="my-6 flex items-center">
|
||||||
<div className="flex-1 border-t border-gray-300"></div>
|
<div className="flex-1 border-t border-gray-300 dark:border-neutral-600"></div>
|
||||||
<span className="px-4 text-sm text-gray-500">OR</span>
|
<span className="px-4 text-sm text-gray-500 dark:text-neutral-400">OR</span>
|
||||||
<div className="flex-1 border-t border-gray-300"></div>
|
<div className="flex-1 border-t border-gray-300 dark:border-neutral-600"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
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 cursor-pointer"
|
className="w-full py-3 flex items-center justify-center gap-2 bg-gray-100 dark:bg-neutral-800 border border-gray-300 dark:border-neutral-600 rounded-lg font-semibold text-gray-900 dark:text-white hover:bg-gray-200 dark:hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 dark:focus:ring-offset-neutral-900 disabled:bg-gray-100 dark:disabled:bg-neutral-800 disabled:cursor-not-allowed transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<GithubOutlined className="text-xl" />
|
<GithubOutlined className="text-xl" />
|
||||||
<span>
|
<span>
|
||||||
@@ -265,11 +269,11 @@ export default function SignupPage() {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<p className="text-gray-600 text-sm">
|
<p className="text-gray-600 dark:text-neutral-400 text-sm">
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<a
|
<a
|
||||||
href="/auth/login"
|
href="/auth/login"
|
||||||
className="text-primary-600 hover:text-primary-700 font-semibold"
|
className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-semibold"
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</a>
|
</a>
|
||||||
@@ -277,7 +281,7 @@ export default function SignupPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 text-center">
|
<div className="mt-6 text-center">
|
||||||
<p className="text-gray-500 text-xs">
|
<p className="text-gray-500 dark:text-neutral-500 text-xs">
|
||||||
By signing up, you agree to our Terms of Service and Privacy Policy
|
By signing up, you agree to our Terms of Service and Privacy Policy
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ const DashboardLayout: FC = (): ReactElement => {
|
|||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen bg-gray-50">
|
<div className="flex min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
{/* Mobile Header with Hamburger */}
|
{/* Mobile Header with Hamburger */}
|
||||||
<div className="lg:hidden sticky top-0 bg-white border-b px-4 py-3 flex items-center">
|
<div className="lg:hidden sticky top-0 bg-white dark:bg-neutral-900 border-b dark:border-neutral-700 px-4 py-3 flex items-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSidebarOpen(true)}
|
onClick={() => setSidebarOpen(true)}
|
||||||
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6 text-gray-600"
|
className="w-6 h-6 text-gray-600 dark:text-neutral-400"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -30,7 +30,9 @@ const DashboardLayout: FC = (): ReactElement => {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<h1 className="ml-3 text-lg font-bold text-gray-900">Hackathon</h1>
|
<h1 className="ml-3 text-lg font-bold text-gray-900 dark:text-white">
|
||||||
|
Hackathon
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main className="flex-1 overflow-auto">
|
<main className="flex-1 overflow-auto">
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
return (
|
return (
|
||||||
<div className="p-8 max-w-7xl mx-auto">
|
<div className="p-8 max-w-7xl mx-auto">
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h1 className="text-3xl font-bold text-gray-900">
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||||
Welcome, {user?.fullname || user?.email?.split('@')[0] || 'User'}!
|
Welcome, {user?.fullname || user?.email?.split('@')[0] || 'User'}!
|
||||||
</h1>
|
</h1>
|
||||||
{user?.location && (
|
{user?.location && (
|
||||||
<p className="text-gray-600 mt-1 font-sans flex items-center space-x-1">
|
<p className="text-gray-600 dark:text-neutral-400 mt-1 font-sans flex items-center space-x-1">
|
||||||
<Icon icon="heroicons:map-pin-16-solid" width="24" height="24" />
|
<Icon icon="heroicons:map-pin-16-solid" width="24" height="24" />
|
||||||
<span>{user.location}</span>
|
<span>{user.location}</span>
|
||||||
</p>
|
</p>
|
||||||
@@ -55,21 +55,21 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{invitations.length > 0 && (
|
{invitations.length > 0 && (
|
||||||
<div className="mb-8 bg-primary-50 border border-primary-200 rounded-lg p-6">
|
<div className="mb-8 bg-primary-50 dark:bg-blue-900/20 border border-primary-200 dark:border-blue-800 rounded-lg p-6">
|
||||||
<h2 className="text-xl font-bold text-gray-900 mb-4">
|
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
Team Invitations ({invitations.length})
|
Team Invitations ({invitations.length})
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3 font-sans">
|
<div className="space-y-3 font-sans">
|
||||||
{invitations.map((invitation) => (
|
{invitations.map((invitation) => (
|
||||||
<div
|
<div
|
||||||
key={invitation.id}
|
key={invitation.id}
|
||||||
className="bg-white p-4 rounded-lg shadow-sm flex items-center justify-between"
|
className="bg-white dark:bg-neutral-800 p-4 rounded-lg shadow-sm flex items-center justify-between"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">
|
<p className="font-medium text-gray-900 dark:text-white">
|
||||||
{invitation.team.name}
|
{invitation.team.name}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600 dark:text-neutral-400">
|
||||||
Invited by {invitation.inviter.fullname}
|
Invited by {invitation.inviter.fullname}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,7 +96,7 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{myTeams.length > 0 ? (
|
{myTeams.length > 0 ? (
|
||||||
<div className="mb-6 md:mb-8">
|
<div className="mb-6 md:mb-8">
|
||||||
<h2 className="text-xl md:text-2xl font-bold text-gray-900 mb-3 md:mb-4">
|
<h2 className="text-xl md:text-2xl font-bold text-gray-900 dark:text-white mb-3 md:mb-4">
|
||||||
My Team
|
My Team
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 md:gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
@@ -104,7 +104,7 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
<Link
|
<Link
|
||||||
key={team.id}
|
key={team.id}
|
||||||
to={'/teams/' + team.id}
|
to={'/teams/' + team.id}
|
||||||
className="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow"
|
className="bg-white dark:bg-neutral-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow"
|
||||||
>
|
>
|
||||||
{team.banner && (
|
{team.banner && (
|
||||||
<img
|
<img
|
||||||
@@ -123,15 +123,15 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base md:text-lg font-bold text-gray-900">
|
<h3 className="text-base md:text-lg font-bold text-gray-900 dark:text-white">
|
||||||
{team.name}
|
{team.name}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs md:text-sm text-gray-600">
|
<p className="text-xs md:text-sm text-gray-600 dark:text-neutral-400">
|
||||||
City: {team.city}
|
City: {team.city}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 text-xs md:text-sm line-clamp-2">
|
<p className="text-gray-600 dark:text-neutral-400 text-xs md:text-sm line-clamp-2">
|
||||||
{team.description}
|
{team.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -140,20 +140,20 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mb-6 md:mb-8 bg-white rounded-lg shadow-md p-6 md:p-8">
|
<div className="mb-6 md:mb-8 bg-white dark:bg-neutral-800 rounded-lg shadow-md p-6 md:p-8">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="text-3xl md:text-4xl mb-2 md:mb-3">👋</div>
|
<div className="text-3xl md:text-4xl mb-2 md:mb-3">👋</div>
|
||||||
<h2 className="text-xl md:text-2xl font-bold text-gray-900 mb-1 md:mb-2">
|
<h2 className="text-xl md:text-2xl font-bold text-gray-900 dark:text-white mb-1 md:mb-2">
|
||||||
You are not in a team yet
|
You are not in a team yet
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm md:text-base text-gray-600 font-sans">
|
<p className="text-sm md:text-base text-gray-600 dark:text-neutral-400 font-sans">
|
||||||
Use the sidebar to browse teams or create your own
|
Use the sidebar to browse teams or create your own
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mt-8 bg-linear-to-br from-white-50 to-primary-50 rounded-xl shadow-lg">
|
<div className="mt-8 bg-linear-to-br from-white-50 to-primary-50 dark:from-neutral-800 dark:to-neutral-900 rounded-xl shadow-lg">
|
||||||
<div className="bg-linear-to-r from-primary-600 to-indigo-600 h-24 rounded-t-xl"></div>
|
<div className="bg-linear-to-r from-primary-600 to-indigo-600 h-24 rounded-t-xl"></div>
|
||||||
<div className="px-4 md:px-8 pb-4 md:pb-8">
|
<div className="px-4 md:px-8 pb-4 md:pb-8">
|
||||||
<div className="flex flex-col md:flex-row md:items-start -mt-12 mb-4 md:mb-6">
|
<div className="flex flex-col md:flex-row md:items-start -mt-12 mb-4 md:mb-6">
|
||||||
@@ -162,30 +162,30 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
src={user.avatar}
|
src={user.avatar}
|
||||||
alt={user.fullname || 'User'}
|
alt={user.fullname || 'User'}
|
||||||
className="w-20 h-20 md:w-24 md:h-24 rounded-full border-4 border-white shadow-lg object-cover"
|
className="w-20 h-20 md:w-24 md:h-24 rounded-full border-4 border-white dark:border-neutral-700 shadow-lg object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-20 h-20 md:w-24 md:h-24 rounded-full border-4 border-white shadow-lg bg-gray-200 flex items-center justify-center">
|
<div className="w-20 h-20 md:w-24 md:h-24 rounded-full border-4 border-white dark:border-neutral-700 shadow-lg bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<span className="text-gray-400 text-3xl md:text-4xl font-sans">
|
<span className="text-gray-400 dark:text-neutral-500 text-3xl md:text-4xl font-sans">
|
||||||
U
|
U
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="md:ml-6 mt-4 md:mt-14">
|
<div className="md:ml-6 mt-4 md:mt-14">
|
||||||
<h2 className="text-xl md:text-2xl font-bold text-gray-900">
|
<h2 className="text-xl md:text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
{user?.fullname ||
|
{user?.fullname ||
|
||||||
user?.email?.split('@')[0] ||
|
user?.email?.split('@')[0] ||
|
||||||
'Unnamed User'}
|
'Unnamed User'}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{user?.location ? (
|
{user?.location ? (
|
||||||
<p className="text-gray-600 flex items-center mt-1 text-sm md:text-base font-sans">
|
<p className="text-gray-600 dark:text-neutral-400 flex items-center mt-1 text-sm md:text-base font-sans">
|
||||||
{user.location}
|
{user.location}
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<Link
|
<Link
|
||||||
to="/onboarding/user"
|
to="/onboarding/user"
|
||||||
className="text-primary-500 hover:text-primary-600 text-sm md:text-sm mt-1 flex items-center"
|
className="text-primary-500 dark:text-blue-400 hover:text-primary-600 dark:hover:text-blue-300 text-sm md:text-sm mt-1 flex items-center"
|
||||||
>
|
>
|
||||||
<span className="font-sans">Complete your profile</span>
|
<span className="font-sans">Complete your profile</span>
|
||||||
<Icon
|
<Icon
|
||||||
@@ -208,24 +208,28 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-4 md:space-y-6">
|
<div className="space-y-4 md:space-y-6">
|
||||||
{user?.bio && (
|
{user?.bio && (
|
||||||
<div className="bg-white rounded-lg p-4 shadow-sm">
|
<div className="bg-white dark:bg-neutral-800 rounded-lg p-4 shadow-sm">
|
||||||
<h3 className="text-sm font-semibold text-gray-700 uppercase tracking-wide mb-2">
|
<h3 className="text-sm font-semibold text-gray-700 dark:text-neutral-300 uppercase tracking-wide mb-2">
|
||||||
About
|
About
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-800 leading-relaxed">{user.bio}</p>
|
<p className="text-gray-800 dark:text-neutral-200 leading-relaxed">
|
||||||
|
{user.bio}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="bg-white rounded-lg p-4 shadow-sm">
|
<div className="bg-white dark:bg-neutral-800 rounded-lg p-4 shadow-sm">
|
||||||
<h3 className="font-semibold text-gray-700 tracking-wide mb-3">
|
<h3 className="font-semibold text-gray-700 dark:text-neutral-300 tracking-wide mb-3">
|
||||||
Contact
|
Contact
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex items-center text-gray-700">
|
<div className="flex items-center text-gray-700 dark:text-neutral-300">
|
||||||
<span className="text-gray-900">{user?.email}</span>
|
<span className="text-gray-900 dark:text-white">
|
||||||
|
{user?.email}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{user?.skills && user.skills.length > 0 && (
|
{user?.skills && user.skills.length > 0 && (
|
||||||
<div className="bg-white rounded-lg p-4 shadow-sm">
|
<div className="bg-white dark:bg-neutral-800 rounded-lg p-4 shadow-sm">
|
||||||
<h3 className="font-semibold text-gray-700 tracking-wide mb-3">
|
<h3 className="font-semibold text-gray-700 dark:text-neutral-300 tracking-wide mb-3">
|
||||||
Skills
|
Skills
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
|
|||||||
@@ -107,10 +107,10 @@ export default function RootLayout() {
|
|||||||
// Show loading state while checking auth
|
// Show loading state while checking auth
|
||||||
if (isChecking) {
|
if (isChecking) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center min-h-screen bg-gray-50">
|
<div className="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mb-4"></div>
|
<div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600 mb-4"></div>
|
||||||
<p className="text-gray-600">Loading...</p>
|
<p className="text-gray-600 dark:text-neutral-400">Loading...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -110,13 +110,13 @@ const UserOnboardingPage: FC = (): ReactElement => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center items-center min-h-screen bg-gray-50 px-4 py-8">
|
<div className="flex flex-col justify-center items-center min-h-screen bg-gray-50 dark:bg-neutral-950 px-4 py-8">
|
||||||
<div className="bg-white w-full max-w-2xl p-8 rounded-xl shadow-lg">
|
<div className="bg-white dark:bg-neutral-900 w-full max-w-2xl p-8 rounded-xl shadow-lg dark:shadow-neutral-950/50">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
|
||||||
Complete Your Profile
|
Complete Your Profile
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-gray-600 font-sans">
|
<p className="text-gray-600 font-sans dark:text-neutral-400">
|
||||||
Tell us more about yourself to get started
|
Tell us more about yourself to get started
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -129,22 +129,22 @@ const UserOnboardingPage: FC = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
src={avatarPreview}
|
src={avatarPreview}
|
||||||
alt="Avatar preview"
|
alt="Avatar preview"
|
||||||
className="w-32 h-32 rounded-full object-cover border-4 border-gray-200"
|
className="w-32 h-32 rounded-full object-cover border-4 border-gray-200 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-32 h-32 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-32 h-32 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
icon="ic:baseline-person"
|
icon="ic:baseline-person"
|
||||||
width="48"
|
width="48"
|
||||||
height="48"
|
height="48"
|
||||||
className="text-gray-400"
|
className="text-gray-400 dark:text-neutral-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<label htmlFor="avatar" className="cursor-pointer">
|
<label htmlFor="avatar" className="cursor-pointer">
|
||||||
<span className="px-4 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600 inline-block">
|
<span className="px-4 py-2 bg-primary-500 dark:bg-primary-600 text-white rounded-lg hover:bg-primary-600 dark:hover:bg-primary-700 inline-block">
|
||||||
{avatarPreview ? 'Change Photo' : 'Upload Photo'}
|
{avatarPreview ? 'Change Photo' : 'Upload Photo'}
|
||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
@@ -155,7 +155,7 @@ const UserOnboardingPage: FC = (): ReactElement => {
|
|||||||
onChange={handleAvatarChange}
|
onChange={handleAvatarChange}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<p className="text-xs text-gray-500 mt-2 text-center">
|
<p className="text-xs text-gray-500 dark:text-neutral-500 mt-2 text-center">
|
||||||
Optional, but highly recommended
|
Optional, but highly recommended
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,7 +174,7 @@ const UserOnboardingPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* City */}
|
{/* City */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-base font-medium text-gray-700">
|
<label className="block text-base font-medium text-gray-700 dark:text-neutral-300">
|
||||||
City <span className="text-red-500">*</span>
|
City <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -193,7 +193,7 @@ const UserOnboardingPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Role/Skills */}
|
{/* Role/Skills */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-base font-medium text-gray-700">
|
<label className="block text-base font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Role / Skills
|
Role / Skills
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -216,9 +216,11 @@ const UserOnboardingPage: FC = (): ReactElement => {
|
|||||||
: (field.value || []).filter((v) => v !== role);
|
: (field.value || []).filter((v) => v !== role);
|
||||||
field.onChange(newValue);
|
field.onChange(newValue);
|
||||||
}}
|
}}
|
||||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
className="rounded border-gray-300 dark:border-neutral-600 text-blue-600 dark:text-primary-500 focus:ring-blue-500 dark:focus:ring-primary-500 dark:bg-neutral-800"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">{role}</span>
|
<span className="text-sm dark:text-neutral-300">
|
||||||
|
{role}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -229,8 +231,11 @@ const UserOnboardingPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Bio */}
|
{/* Bio */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-base font-medium text-gray-700">
|
<label className="block text-base font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Bio <span className="text-gray-400">(Optional)</span>
|
Bio{' '}
|
||||||
|
<span className="text-gray-400 dark:text-neutral-500">
|
||||||
|
(Optional)
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
+205
-134
@@ -2,11 +2,15 @@ import { useNavigate } from 'react-router';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
import { Button } from '@imphnen-frontend-service/ui/atoms';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
|
import { ThemeToggle } from '../components/theme-toggle';
|
||||||
|
import { useAuthStore } from '@imphnen-frontend-service/service';
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
const [openFaq, setOpenFaq] = useState<number | null>(0);
|
const [openFaq, setOpenFaq] = useState<number | null>(0);
|
||||||
|
const { session } = useAuthStore();
|
||||||
|
const isAuthenticated = !!session?.token;
|
||||||
|
|
||||||
const faqs = [
|
const faqs = [
|
||||||
{
|
{
|
||||||
@@ -62,13 +66,15 @@ export default function HomePage() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen bg-white">
|
<main className="min-h-screen bg-white dark:bg-neutral-950">
|
||||||
{/* Navigation */}
|
{/* Navigation */}
|
||||||
<div id="#top" className="hidden"></div>
|
<div id="#top" className="hidden"></div>
|
||||||
<nav className="border-b border-gray-200 bg-white sticky top-0 z-50">
|
<nav className="border-b border-gray-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 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 justify-between max-w-7xl mx-auto px-4 md:px-8 py-4">
|
||||||
<div className="flex items-center gap-2">
|
<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 dark:text-white">
|
||||||
|
IMPHNEN
|
||||||
|
</span>
|
||||||
<a
|
<a
|
||||||
href="#top"
|
href="#top"
|
||||||
className="text-lg md:text-xl font-bold text-primary-500 hover:cursor-pointer"
|
className="text-lg md:text-xl font-bold text-primary-500 hover:cursor-pointer"
|
||||||
@@ -80,91 +86,121 @@ export default function HomePage() {
|
|||||||
<div className="hidden md:flex text-label1 items-center gap-4 lg:gap-8">
|
<div className="hidden md:flex text-label1 items-center gap-4 lg:gap-8">
|
||||||
<a
|
<a
|
||||||
href="#timeline"
|
href="#timeline"
|
||||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
className="text-gray-600 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
Timeline
|
Timeline
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#hadiah"
|
href="#hadiah"
|
||||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
className="text-gray-600 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
Hadiah
|
Hadiah
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#faq"
|
href="#faq"
|
||||||
className="text-gray-600 hover:text-gray-900 transition-colors"
|
className="text-gray-600 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
FAQ
|
FAQ
|
||||||
</a>
|
</a>
|
||||||
<Button
|
<ThemeToggle />
|
||||||
onClick={() => navigate('/auth/login')}
|
{isAuthenticated ? (
|
||||||
size="sm"
|
<Button
|
||||||
variant="bordered"
|
onClick={() => navigate('/dashboard')}
|
||||||
className="rounded-lg text-base"
|
size="sm"
|
||||||
>
|
className="rounded-lg text-base"
|
||||||
Masuk
|
>
|
||||||
</Button>
|
Dashboard
|
||||||
<Button
|
</Button>
|
||||||
onClick={() => navigate('/auth/signup')}
|
) : (
|
||||||
size="sm"
|
<>
|
||||||
className="rounded-lg text-base"
|
<Button
|
||||||
>
|
onClick={() => navigate('/auth/login')}
|
||||||
Daftar Sekarang
|
size="sm"
|
||||||
</Button>
|
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>
|
</div>
|
||||||
{/* Mobile Menu Button */}
|
{/* Mobile Menu Button */}
|
||||||
<button
|
<div className="flex items-center gap-2 md:hidden">
|
||||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
<ThemeToggle />
|
||||||
className="md:hidden p-2 cursor-pointer"
|
<button
|
||||||
>
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
<svg
|
className="p-2 cursor-pointer text-gray-900 dark:text-white"
|
||||||
className="w-6 h-6"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
strokeLinecap="round"
|
className="w-6 h-6"
|
||||||
strokeLinejoin="round"
|
fill="none"
|
||||||
strokeWidth={2}
|
stroke="currentColor"
|
||||||
d="M4 6h16M4 12h16M4 18h16"
|
viewBox="0 0 24 24"
|
||||||
/>
|
>
|
||||||
</svg>
|
<path
|
||||||
</button>
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Mobile Menu */}
|
{/* Mobile Menu */}
|
||||||
{mobileMenuOpen && (
|
{mobileMenuOpen && (
|
||||||
<nav className="md:hidden sticky top-18 border-b border-gray-200 bg-white z-40">
|
<nav className="md:hidden sticky top-18 border-b border-gray-200 dark:border-neutral-800 bg-white z-40 dark:bg-neutral-900">
|
||||||
<div className="flex flex-col items-start gap-4 px-4 py-4">
|
<div className="flex flex-col items-start gap-4 px-4 py-4">
|
||||||
<a
|
<a
|
||||||
href="#timeline"
|
href="#timeline"
|
||||||
className="ms-3 text-gray-600 hover:text-gray-900"
|
className="ms-3 text-gray-600 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Timeline
|
Timeline
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="#hadiah"
|
href="#hadiah"
|
||||||
className="ms-3 text-gray-600 hover:text-gray-900"
|
className="ms-3 text-gray-600 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white"
|
||||||
>
|
>
|
||||||
Hadiah
|
Hadiah
|
||||||
</a>
|
</a>
|
||||||
<a href="#faq" className="ms-3 text-gray-600 hover:text-gray-900">
|
<a
|
||||||
|
href="#faq"
|
||||||
|
className="ms-3 text-gray-600 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white"
|
||||||
|
>
|
||||||
FAQ
|
FAQ
|
||||||
</a>
|
</a>
|
||||||
<a
|
{isAuthenticated ? (
|
||||||
href="/auth/login"
|
<button
|
||||||
className="ms-3 text-gray-600 hover:text-gray-900"
|
onClick={() => navigate('/dashboard')}
|
||||||
>
|
className="px-4 py-2 bg-primary-500 text-white text-base rounded-lg hover:bg-primary-600 transition-colors text-center cursor-pointer"
|
||||||
Masuk
|
>
|
||||||
</a>
|
Dashboard
|
||||||
<button
|
</button>
|
||||||
onClick={() => navigate('/auth/login')}
|
) : (
|
||||||
className="px-4 py-2 bg-primary-500 text-white text-base rounded-lg hover:bg-primary-600 transition-colors text-center cursor-pointer"
|
<>
|
||||||
>
|
<a
|
||||||
Daftar Sekarang
|
href="#masuk"
|
||||||
</button>
|
className="ms-3 text-gray-600 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white"
|
||||||
|
>
|
||||||
|
Masuk
|
||||||
|
</a>
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/auth/login')}
|
||||||
|
className="px-4 py-2 bg-primary-500 text-white text-base rounded-lg hover:bg-primary-600 transition-colors text-center cursor-pointer"
|
||||||
|
>
|
||||||
|
Daftar Sekarang
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
)}
|
)}
|
||||||
@@ -180,7 +216,7 @@ export default function HomePage() {
|
|||||||
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"
|
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 }}
|
style={{ transform: 'translate(0px, 0px)', opacity: 1 }}
|
||||||
></div>
|
></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 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)] dark:bg-[linear-gradient(rgba(59,130,246,0.1)_1px,transparent_1px),linear-gradient(to_right,rgba(59,130,246,0.1)_1px,transparent_1px)] bg-size-[40px_40px]"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto container px-4 relative flex flex-col items-center">
|
<div className="mx-auto container px-4 relative flex flex-col items-center">
|
||||||
{/* Logos */}
|
{/* Logos */}
|
||||||
@@ -192,19 +228,19 @@ export default function HomePage() {
|
|||||||
className="h-12 md:h-16"
|
className="h-12 md:h-16"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-3xl md:text-5xl font-bold text-gray-400">
|
<span className="text-3xl md:text-5xl font-bold text-gray-400 dark:text-neutral-500">
|
||||||
×
|
×
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<img
|
<img
|
||||||
src="images/sponsors/kolosal-logo_rlxbck.svg"
|
src="images/sponsors/kolosal-logo_rlxbck.svg"
|
||||||
alt="Kolosal.ai"
|
alt="Kolosal.ai"
|
||||||
className="h-8 md:h-12"
|
className="h-8 md:h-12 dark:invert"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<h1 className="text-h1 font-bold text-gray-900 mb-4 text-center">
|
<h1 className="text-h1 font-bold text-gray-900 dark:text-white mb-4 text-center">
|
||||||
Hackathon
|
Hackathon
|
||||||
</h1>
|
</h1>
|
||||||
{/* Subtitle */}
|
{/* Subtitle */}
|
||||||
@@ -212,12 +248,12 @@ export default function HomePage() {
|
|||||||
"Inovasi AI: Mendorong Usaha Lokal dengan AI Inklusif"
|
"Inovasi AI: Mendorong Usaha Lokal dengan AI Inklusif"
|
||||||
</p>
|
</p>
|
||||||
{/* Description */}
|
{/* 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">
|
<p className="text-p3 text-gray-600 dark:text-neutral-400 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
|
Kompetisi pengembangan teknologi untuk menciptakan solusi inovatif
|
||||||
yang menghadirkan dampak nyata
|
yang menghadirkan dampak nyata
|
||||||
</p>
|
</p>
|
||||||
{/* Status */}
|
{/* 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-4 md:gap-8 mb-10 md:mb-16 text-base text-gray-600 dark:text-neutral-400">
|
||||||
<div className="flex items-center gap-2 md:gap-3">
|
<div className="flex items-center gap-2 md:gap-3">
|
||||||
<Icon icon="streamline-plump:web" width="16" height="16" />
|
<Icon icon="streamline-plump:web" width="16" height="16" />
|
||||||
<span>Online</span>
|
<span>Online</span>
|
||||||
@@ -239,7 +275,7 @@ export default function HomePage() {
|
|||||||
href="https://chat.whatsapp.com/BlxrYh9uSC37d7VPhJslGL"
|
href="https://chat.whatsapp.com/BlxrYh9uSC37d7VPhJslGL"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
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"
|
className="inline-flex items-center justify-center px-4 py-2.5 border-2 border-gray-300 dark:border-neutral-600 hover:border-gray-400 dark:hover:border-neutral-500 transition-colors text-center bg-transparent hover:text-gray-900 dark:hover:text-white hover:bg-gray-50 dark:hover:bg-neutral-800 text-base text-gray-600 dark:text-neutral-400 rounded-lg font-bai-jamjuree font-semibold"
|
||||||
>
|
>
|
||||||
Gabung Grup WA Hackathon
|
Gabung Grup WA Hackathon
|
||||||
</a>
|
</a>
|
||||||
@@ -247,7 +283,7 @@ export default function HomePage() {
|
|||||||
{/* Scroll indicator */}
|
{/* Scroll indicator */}
|
||||||
<div className="mt-12 md:mt-20">
|
<div className="mt-12 md:mt-20">
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6 text-gray-400 animate-bounce"
|
className="w-6 h-6 text-gray-400 dark:text-neutral-500 animate-bounce"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -264,17 +300,19 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* About Section */}
|
{/* About Section */}
|
||||||
<section className="py-16 md:py-24 px-4 md:px-8 bg-white">
|
<section className="py-16 md:py-24 px-4 md:px-8 bg-white dark:bg-neutral-950">
|
||||||
<div className="max-w-4xl mx-auto text-center">
|
<div className="max-w-4xl mx-auto text-center">
|
||||||
<h2 className="text-3xl md:text-5xl font-bold mb-10">
|
<h2 className="text-3xl md:text-5xl font-bold mb-10 dark:text-white">
|
||||||
Tentang <span className="text-primary-500">Hackathon</span>
|
Tentang <span className="text-primary-500">Hackathon</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="font-sans 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 dark:text-neutral-400 mb-6">
|
||||||
<span className="font-semibold text-gray-900">Hackathon</span>{' '}
|
<span className="font-semibold text-gray-900 dark:text-white">
|
||||||
|
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="font-sans 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 dark:text-neutral-400">
|
||||||
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"
|
||||||
@@ -285,10 +323,13 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Prizes Section */}
|
{/* Prizes Section */}
|
||||||
<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 dark:bg-neutral-900"
|
||||||
|
>
|
||||||
<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 font-bai-jamjuree">
|
<h2 className="text-3xl md:text-5xl font-bold mb-4 font-bai-jamjuree dark:text-white">
|
||||||
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 font-sans">
|
<p className="text-xl md:text-2xl text-primary-500 font-semibold font-sans">
|
||||||
@@ -298,67 +339,73 @@ export default function HomePage() {
|
|||||||
|
|
||||||
<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 px-4 py-8 shadow-lg border-2 border-gray-300 hover:border-primary-500 transition-colors">
|
<div className="bg-white dark:bg-neutral-800 rounded-xl px-4 py-8 shadow-lg border-2 border-gray-300 dark:border-neutral-700 hover:border-primary-500 dark: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 dark:bg-primary-900/30 rounded-full flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
icon="ic:round-star"
|
icon="ic:round-star"
|
||||||
className="h-8 w-8 text-primary-500"
|
className="h-8 w-8 text-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-center mb-2">Juara 1</h3>
|
<h3 className="text-xl font-bold text-center mb-2 dark:text-white">
|
||||||
|
Juara 1
|
||||||
|
</h3>
|
||||||
<p className="text-2xl font-bold text-primary-500 text-center font-sans">
|
<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 px-4 py-8 shadow-lg border-2 border-gray-300 hover:border-gray-500 transition-colors">
|
<div className="bg-white dark:bg-neutral-800 rounded-xl px-4 py-8 shadow-lg border-2 border-gray-300 dark:border-neutral-700 hover:border-gray-500 dark:hover:border-neutral-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 dark:bg-neutral-700 rounded-full flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
icon="ic:round-star"
|
icon="ic:round-star"
|
||||||
className="h-8 w-8 text-gray-600"
|
className="h-8 w-8 text-gray-600 dark:text-neutral-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-center mb-2">Juara 2</h3>
|
<h3 className="text-xl font-bold text-center mb-2 dark:text-white">
|
||||||
|
Juara 2
|
||||||
|
</h3>
|
||||||
<p className="text-2xl font-bold text-primary-500 text-center font-sans">
|
<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 px-4 py-8 shadow-lg border-2 border-gray-300 hover:border-orange-300 transition-colors">
|
<div className="bg-white dark:bg-neutral-800 rounded-xl px-4 py-8 shadow-lg border-2 border-gray-300 dark:border-neutral-700 hover:border-orange-300 dark:hover:border-orange-500 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 dark:bg-orange-900/30 rounded-full flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
icon="ic:round-star"
|
icon="ic:round-star"
|
||||||
className="h-8 w-8 text-orange-600"
|
className="h-8 w-8 text-orange-600 dark:text-orange-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-center mb-2">Juara 3</h3>
|
<h3 className="text-xl font-bold text-center mb-2 dark:text-white">
|
||||||
<p className="text-2xl font-bold text-orange-600 text-center font-sans">
|
Juara 3
|
||||||
|
</h3>
|
||||||
|
<p className="text-2xl font-bold text-orange-600 dark:text-orange-500 text-center font-sans">
|
||||||
Rp2.500.000
|
Rp2.500.000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Special Prize */}
|
{/* Special Prize */}
|
||||||
<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="bg-white dark:bg-neutral-800 rounded-xl px-4 py-8 shadow-lg border-2 border-gray-300 dark:border-neutral-700 hover:border-purple-300 dark:hover:border-purple-500 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 dark:bg-purple-900/30 rounded-full flex items-center justify-center">
|
||||||
<Icon
|
<Icon
|
||||||
icon="ic:round-star"
|
icon="ic:round-star"
|
||||||
className="h-8 w-8 text-purple-600"
|
className="h-8 w-8 text-purple-600 dark:text-purple-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-xl font-bold text-center mb-2">
|
<h3 className="text-xl font-bold text-center mb-2 dark:text-white">
|
||||||
Juara Kategori Lainnya
|
Juara Kategori Lainnya
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-2xl font-bold text-purple-600 text-center font-sans">
|
<p className="text-2xl font-bold text-purple-600 dark:text-purple-500 text-center font-sans">
|
||||||
Rp2.000.000
|
Rp2.000.000
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -367,13 +414,16 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 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 dark:bg-neutral-950"
|
||||||
|
>
|
||||||
<div className="max-w-4xl mx-auto font-sans">
|
<div className="max-w-4xl mx-auto font-sans">
|
||||||
<div className="text-center mb-12 font-bai-jamjuree">
|
<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 dark:text-white">
|
||||||
Timeline <span className="text-primary-500">Acara</span>
|
Timeline <span className="text-primary-500">Acara</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-gray-600">
|
<p className="text-lg text-gray-600 dark:text-neutral-400">
|
||||||
Jadwal lengkap pelaksanaan hackathon
|
Jadwal lengkap pelaksanaan hackathon
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -383,14 +433,16 @@ export default function HomePage() {
|
|||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="w-4 h-4 bg-primary-500 rounded-full"></div>
|
<div className="w-4 h-4 bg-primary-500 rounded-full"></div>
|
||||||
<div className="w-0.5 h-full bg-gray-300"></div>
|
<div className="w-0.5 h-full bg-gray-300 dark:bg-neutral-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 pb-8">
|
<div className="flex-1 pb-8">
|
||||||
<p className="text-primary-500 font-semibold mb-2">
|
<p className="text-primary-500 font-semibold mb-2">
|
||||||
30 November 2025
|
30 November 2025
|
||||||
</p>
|
</p>
|
||||||
<h3 className="text-xl font-bold mb-2">Penutupan Registrasi</h3>
|
<h3 className="text-xl font-bold mb-2 dark:text-white">
|
||||||
<p className="text-gray-600">
|
Penutupan Registrasi
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 dark:text-neutral-400">
|
||||||
Batas akhir pendaftaran peserta hackathon.
|
Batas akhir pendaftaran peserta hackathon.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -400,14 +452,16 @@ export default function HomePage() {
|
|||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="w-4 h-4 bg-primary-500 rounded-full"></div>
|
<div className="w-4 h-4 bg-primary-500 rounded-full"></div>
|
||||||
<div className="w-0.5 h-full bg-gray-300"></div>
|
<div className="w-0.5 h-full bg-gray-300 dark:bg-neutral-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 pb-8">
|
<div className="flex-1 pb-8">
|
||||||
<p className="text-primary-500 font-semibold mb-2">
|
<p className="text-primary-500 font-semibold mb-2">
|
||||||
30 November 2025
|
30 November 2025
|
||||||
</p>
|
</p>
|
||||||
<h3 className="text-xl font-bold mb-2">Technical Meeting</h3>
|
<h3 className="text-xl font-bold mb-2 dark:text-white">
|
||||||
<p className="text-gray-600">
|
Technical Meeting
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 dark:text-neutral-400">
|
||||||
Akan diadakan technical meeting terkait lomba melalui Google
|
Akan diadakan technical meeting terkait lomba melalui Google
|
||||||
Meet. Stay tune di grup WA Hackathon.
|
Meet. Stay tune di grup WA Hackathon.
|
||||||
</p>
|
</p>
|
||||||
@@ -418,14 +472,16 @@ export default function HomePage() {
|
|||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="w-4 h-4 bg-primary-500 rounded-full"></div>
|
<div className="w-4 h-4 bg-primary-500 rounded-full"></div>
|
||||||
<div className="w-0.5 h-full bg-gray-300"></div>
|
<div className="w-0.5 h-full bg-gray-300 dark:bg-neutral-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 pb-8">
|
<div className="flex-1 pb-8">
|
||||||
<p className="text-primary-500 font-semibold mb-2">
|
<p className="text-primary-500 font-semibold mb-2">
|
||||||
1 - 7 Desember 2025
|
1 - 7 Desember 2025
|
||||||
</p>
|
</p>
|
||||||
<h3 className="text-xl font-bold mb-2">Tahap Penyisihan</h3>
|
<h3 className="text-xl font-bold mb-2 dark:text-white">
|
||||||
<p className="text-gray-600">
|
Tahap Penyisihan
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 dark:text-neutral-400">
|
||||||
Peserta mengerjakan tantangan yang diberikan.
|
Peserta mengerjakan tantangan yang diberikan.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -435,14 +491,16 @@ export default function HomePage() {
|
|||||||
<div className="flex gap-6">
|
<div className="flex gap-6">
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="w-4 h-4 bg-primary-500 rounded-full"></div>
|
<div className="w-4 h-4 bg-primary-500 rounded-full"></div>
|
||||||
<div className="w-0.5 h-full bg-gray-300"></div>
|
<div className="w-0.5 h-full bg-gray-300 dark:bg-neutral-700"></div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 pb-8">
|
<div className="flex-1 pb-8">
|
||||||
<p className="text-primary-500 font-semibold mb-2">
|
<p className="text-primary-500 font-semibold mb-2">
|
||||||
8 - 14 Desember 2025
|
8 - 14 Desember 2025
|
||||||
</p>
|
</p>
|
||||||
<h3 className="text-xl font-bold mb-2">Penilaian & Webinar</h3>
|
<h3 className="text-xl font-bold mb-2 dark:text-white">
|
||||||
<p className="text-gray-600">
|
Penilaian & Webinar
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 dark:text-neutral-400">
|
||||||
Proses penilaian oleh juri dan sesi webinar.
|
Proses penilaian oleh juri dan sesi webinar.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -457,8 +515,10 @@ export default function HomePage() {
|
|||||||
<p className="text-primary-500 font-semibold mb-2">
|
<p className="text-primary-500 font-semibold mb-2">
|
||||||
15 Desember 2025
|
15 Desember 2025
|
||||||
</p>
|
</p>
|
||||||
<h3 className="text-xl font-bold mb-2">Pengumuman & Final</h3>
|
<h3 className="text-xl font-bold mb-2 dark:text-white">
|
||||||
<p className="text-gray-600">
|
Pengumuman & Final
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-600 dark:text-neutral-400">
|
||||||
Presentasi final dan pengumuman pemenang.
|
Presentasi final dan pengumuman pemenang.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -468,47 +528,50 @@ 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 font-sans">
|
<section className="py-16 md:py-24 px-4 md:px-8 bg-gray-50 dark:bg-neutral-900 font-sans">
|
||||||
<div className="md:max-w-6xl mx-auto">
|
<div className="md:max-w-6xl mx-auto">
|
||||||
<div className="text-center mb-12 font-bai-jamjuree">
|
<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 dark:text-white">
|
||||||
Dewan <span className="text-primary-500">Juri</span>
|
Dewan <span className="text-primary-500">Juri</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-gray-600">
|
<p className="text-lg text-gray-600 dark:text-neutral-400">
|
||||||
Perwakilan dari IMPHNEN dan Kolosal.ai yang akan menilai karya
|
Perwakilan dari IMPHNEN dan Kolosal.ai yang akan menilai karya
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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 px-4 py-8 shadow-lg text-center">
|
<div className="flex flex-col justify-between bg-white dark:bg-neutral-800 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 dark:text-white">
|
||||||
<h3 className="text-p3 font-bold mb-1">
|
|
||||||
Alifais Farrel Ramdhani
|
Alifais Farrel Ramdhani
|
||||||
</h3>
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-primary-500 font-semibold mb-1">CTO</p>
|
<p className="text-primary-500 font-semibold mb-1">CTO</p>
|
||||||
<p className="text-gray-600">Kolosal.ai</p>
|
<p className="text-gray-600 dark:text-neutral-400">
|
||||||
|
Kolosal.ai
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Judge 2 */}
|
{/* Judge 2 */}
|
||||||
<div className="flex flex-col justify-between bg-white rounded-xl px-4 py-8 shadow-lg text-center">
|
<div className="flex flex-col justify-between bg-white dark:bg-neutral-800 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 dark:text-white">
|
||||||
<h3 className="text-p3 font-bold mb-1">Anka Tama</h3>
|
Anka Tama
|
||||||
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-primary-500 font-semibold mb-1">Admin</p>
|
<p className="text-primary-500 font-semibold mb-1">Admin</p>
|
||||||
<p className="text-gray-600">IMPHNEN</p>
|
<p className="text-gray-600 dark:text-neutral-400">IMPHNEN</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Judge 3 */}
|
{/* Judge 3 */}
|
||||||
<div className="flex flex-col justify-between bg-white rounded-xl px-4 py-8 shadow-lg text-center">
|
<div className="flex flex-col justify-between bg-white dark:bg-neutral-800 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 dark:text-white">
|
||||||
<h3 className="text-p3 font-bold mb-1">Hafid Nur</h3>
|
Hafid Nur
|
||||||
|
</h3>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-primary-500 font-semibold mb-1">Moderator</p>
|
<p className="text-primary-500 font-semibold mb-1">Moderator</p>
|
||||||
<p className="text-gray-600">IMPHNEN</p>
|
<p className="text-gray-600 dark:text-neutral-400">IMPHNEN</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -516,25 +579,31 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* FAQ Section */}
|
{/* FAQ Section */}
|
||||||
<section id="faq" className="py-16 md:py-24 px-4 md:px-8 bg-white">
|
<section
|
||||||
|
id="faq"
|
||||||
|
className="py-16 md:py-24 px-4 md:px-8 bg-white dark:bg-neutral-950"
|
||||||
|
>
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="max-w-4xl 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 text-primary-500">
|
<h2 className="text-3xl md:text-5xl font-bold mb-4 text-primary-500">
|
||||||
FAQ
|
FAQ
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-gray-600">
|
<p className="text-lg text-gray-600 dark:text-neutral-400">
|
||||||
Pertanyaan yang Sering Diajukan
|
Pertanyaan yang Sering Diajukan
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4 font-sans">
|
<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 dark:border-neutral-700 rounded-lg"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpenFaq(openFaq === index ? null : index)}
|
onClick={() => setOpenFaq(openFaq === index ? null : index)}
|
||||||
className="w-full px-6 py-4 flex items-center justify-between text-left hover:bg-gray-50 transition-colors cursor-pointer"
|
className="w-full px-6 py-4 flex items-center justify-between text-left hover:bg-gray-50 dark:hover:bg-neutral-800 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<span className="font-semibold text-gray-900">
|
<span className="font-semibold text-gray-900 dark:text-white">
|
||||||
{faq.question}
|
{faq.question}
|
||||||
</span>
|
</span>
|
||||||
<svg
|
<svg
|
||||||
@@ -554,7 +623,9 @@ export default function HomePage() {
|
|||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
{openFaq === index && (
|
{openFaq === index && (
|
||||||
<div className="px-6 pb-4 text-gray-600">{faq.answer}</div>
|
<div className="px-6 pb-4 text-gray-600 dark:text-neutral-400">
|
||||||
|
{faq.answer}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -563,12 +634,12 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Sponsors Section */}
|
{/* Sponsors Section */}
|
||||||
<section className="py-20 md:py-28 px-4 md:px-8 bg-gray-50">
|
<section className="py-20 md:py-28 px-4 md:px-8 bg-gray-50 dark:bg-neutral-900">
|
||||||
<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 dark:text-white">
|
||||||
Sponsor & <span className="text-primary-500">Partner</span>
|
Sponsor & <span className="text-primary-500">Partner</span>
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-lg text-gray-600 mb-8">
|
<p className="text-lg text-gray-600 dark:text-neutral-400 mb-8">
|
||||||
Acara ini sepenuhnya disponsori oleh
|
Acara ini sepenuhnya disponsori oleh
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -577,12 +648,12 @@ export default function HomePage() {
|
|||||||
href="https://kolosal.ai"
|
href="https://kolosal.ai"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="bg-white rounded-xl p-8 shadow-lg inline-block border-2 border-transparent hover:border-gray-500 transition-colors"
|
className="bg-white dark:bg-neutral-800 rounded-xl p-8 shadow-lg inline-block border-2 border-transparent hover:border-gray-500 dark:hover:border-neutral-500 transition-colors"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="images/sponsors/kolosal-logo_rlxbck.svg"
|
src="images/sponsors/kolosal-logo_rlxbck.svg"
|
||||||
alt="Kolosal.ai"
|
alt="Kolosal.ai"
|
||||||
className="h-12 md:h-16"
|
className="h-12 md:h-16 dark:invert"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -592,13 +663,13 @@ export default function HomePage() {
|
|||||||
{/* CTA Section */}
|
{/* CTA Section */}
|
||||||
<section
|
<section
|
||||||
id="masuk"
|
id="masuk"
|
||||||
className="py-20 md:py-28 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 dark:from-neutral-950 dark:to-neutral-900"
|
||||||
>
|
>
|
||||||
<div className="max-w-4xl mx-auto text-center font-sans">
|
<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">
|
<h2 className="text-3xl md:text-5xl font-bold mb-6 font-bai-jamjuree dark:text-white">
|
||||||
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 dark:text-neutral-400 mb-8">
|
||||||
Jangan lewatkan kesempatan emas untuk bersaing dengan developer
|
Jangan lewatkan kesempatan emas untuk bersaing dengan developer
|
||||||
terbaik,
|
terbaik,
|
||||||
<br className="hidden md:block" /> belajar dari para ahli, dan
|
<br className="hidden md:block" /> belajar dari para ahli, dan
|
||||||
@@ -616,13 +687,13 @@ export default function HomePage() {
|
|||||||
href="https://chat.whatsapp.com/BlxrYh9uSC37d7VPhJslGL"
|
href="https://chat.whatsapp.com/BlxrYh9uSC37d7VPhJslGL"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="px-8 py-3 bg-white text-gray-900 text-lg rounded-lg border-2 border-gray-300 hover:border-gray-400 transition-colors font-semibold"
|
className="px-8 py-3 bg-white dark:bg-neutral-800 text-gray-900 dark:text-white text-lg rounded-lg border-2 border-gray-300 dark:border-neutral-600 hover:border-gray-400 dark:hover:border-neutral-500 transition-colors font-semibold"
|
||||||
>
|
>
|
||||||
Gabung Grup WA Hackathon
|
Gabung Grup WA Hackathon
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-gray-500 mt-6">
|
<p className="text-sm text-gray-500 dark:text-neutral-500 mt-6">
|
||||||
Pendaftaran ditutup pada{' '}
|
Pendaftaran ditutup pada{' '}
|
||||||
<span className="text-primary-500 font-semibold">
|
<span className="text-primary-500 font-semibold">
|
||||||
30 November 2025
|
30 November 2025
|
||||||
|
|||||||
@@ -138,12 +138,17 @@ const ProfilePage: FC = (): ReactElement => {
|
|||||||
const isLoading = isUpdating || isUploading;
|
const isLoading = isUpdating || isUploading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-center items-center min-h-screen bg-gray-50 px-4 py-8">
|
<div className="flex flex-col justify-center items-center min-h-screen bg-gray-50 dark:bg-neutral-950 px-4 py-8">
|
||||||
<div className="bg-white w-full max-w-md p-8 rounded-xl shadow-lg">
|
<div className="bg-white dark:bg-neutral-900 w-full max-w-md p-8 rounded-xl shadow-lg">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Edit Profile</h1>
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
<Link to="/dashboard" className="text-gray-500 hover:text-gray-700">
|
Edit Profile
|
||||||
|
</h1>
|
||||||
|
<Link
|
||||||
|
to="/dashboard"
|
||||||
|
className="text-gray-500 dark:text-neutral-400 hover:text-gray-700 dark:hover:text-neutral-200"
|
||||||
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6"
|
className="w-6 h-6"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -159,7 +164,9 @@ const ProfilePage: FC = (): ReactElement => {
|
|||||||
</svg>
|
</svg>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 font-sans">Update your photo and name</p>
|
<p className="text-gray-600 font-sans dark:text-neutral-400">
|
||||||
|
Update your photo and name
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={onSubmit} className="space-y-6">
|
<form onSubmit={onSubmit} className="space-y-6">
|
||||||
@@ -170,15 +177,15 @@ const ProfilePage: FC = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
src={avatarPreview}
|
src={avatarPreview}
|
||||||
alt="Avatar preview"
|
alt="Avatar preview"
|
||||||
className="w-32 h-32 rounded-full object-cover border-4 border-gray-200 group-hover:border-blue-400 transition-colors"
|
className="w-32 h-32 rounded-full object-cover border-4 border-gray-200 dark:border-neutral-700 group-hover:border-blue-400 dark:group-hover:border-blue-500 transition-colors"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-32 h-32 rounded-full bg-gray-200 flex items-center justify-center group-hover:bg-gray-300 transition-colors">
|
<div className="w-32 h-32 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center group-hover:bg-gray-300 dark:group-hover:bg-neutral-600 transition-colors">
|
||||||
<Icon
|
<Icon
|
||||||
icon="ic:baseline-person"
|
icon="ic:baseline-person"
|
||||||
width="48"
|
width="48"
|
||||||
height="48"
|
height="48"
|
||||||
className="text-gray-400"
|
className="text-gray-400 dark:text-neutral-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -216,7 +223,7 @@ const ProfilePage: FC = (): ReactElement => {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-gray-500 text-center font-sans">
|
<p className="text-sm text-gray-500 dark:text-neutral-400 text-center font-sans">
|
||||||
Click the camera icon to change your photo
|
Click the camera icon to change your photo
|
||||||
<br />
|
<br />
|
||||||
Format: JPG, PNG. Max 5MB
|
Format: JPG, PNG. Max 5MB
|
||||||
@@ -234,7 +241,7 @@ const ProfilePage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* City */}
|
{/* City */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-label1 font-medium text-gray-700">
|
<label className="block text-label1 font-medium text-gray-700 dark:text-neutral-300">
|
||||||
City
|
City
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -253,7 +260,7 @@ const ProfilePage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Role/Skills */}
|
{/* Role/Skills */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-label1 font-medium text-gray-700">
|
<label className="block text-label1 font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Role / Skills
|
Role / Skills
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -276,9 +283,11 @@ const ProfilePage: FC = (): ReactElement => {
|
|||||||
: (field.value || []).filter((v) => v !== role);
|
: (field.value || []).filter((v) => v !== role);
|
||||||
field.onChange(newValue);
|
field.onChange(newValue);
|
||||||
}}
|
}}
|
||||||
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
className="rounded border-gray-300 dark:border-neutral-600 text-blue-600 focus:ring-blue-500 dark:bg-neutral-800"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm">{role}</span>
|
<span className="text-sm dark:text-neutral-300">
|
||||||
|
{role}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -289,8 +298,11 @@ const ProfilePage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Bio */}
|
{/* Bio */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-label1 font-medium text-gray-700">
|
<label className="block text-label1 font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Bio <span className="text-gray-400">(Optional)</span>
|
Bio{' '}
|
||||||
|
<span className="text-gray-400 dark:text-neutral-500">
|
||||||
|
(Optional)
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
control={form.control}
|
control={form.control}
|
||||||
|
|||||||
@@ -63,14 +63,14 @@ const TeamChatPage: FC = (): ReactElement => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen bg-gray-50">
|
<div className="flex flex-col h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white border-b shadow-sm">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700 shadow-sm dark:shadow-neutral-950/50">
|
||||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Team Chat</h1>
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Team Chat</h1>
|
||||||
<p className="text-gray-600 text-sm mt-0.5">{team?.name}</p>
|
<p className="text-gray-600 dark:text-neutral-400 text-sm mt-0.5">{team?.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="secondary" onClick={() => navigate(`/teams/${teamId}`)}>
|
<Button variant="secondary" onClick={() => navigate(`/teams/${teamId}`)}>
|
||||||
Back to Team
|
Back to Team
|
||||||
@@ -84,7 +84,7 @@ const TeamChatPage: FC = (): ReactElement => {
|
|||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center h-64">
|
<div className="flex items-center justify-center h-64">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 dark:border-primary-400"></div>
|
||||||
</div>
|
</div>
|
||||||
) : messages && messages.length > 0 ? (
|
) : messages && messages.length > 0 ? (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
@@ -118,18 +118,18 @@ const TeamChatPage: FC = (): ReactElement => {
|
|||||||
<div className={`flex-1 ${isOwnMessage ? 'text-right' : 'text-left'}`}>
|
<div className={`flex-1 ${isOwnMessage ? 'text-right' : 'text-left'}`}>
|
||||||
<div className={`inline-block ${isOwnMessage ? 'items-end' : 'items-start'}`}>
|
<div className={`inline-block ${isOwnMessage ? 'items-end' : 'items-start'}`}>
|
||||||
<div className="flex items-baseline gap-2 mb-1">
|
<div className="flex items-baseline gap-2 mb-1">
|
||||||
<span className="font-semibold text-sm text-gray-900">
|
<span className="font-semibold text-sm text-gray-900 dark:text-white">
|
||||||
{isOwnMessage ? 'You' : msg.user?.fullname}
|
{isOwnMessage ? 'You' : msg.user?.fullname}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-500">
|
<span className="text-xs text-gray-500 dark:text-neutral-500">
|
||||||
{formatTime(msg.created_at)}
|
{formatTime(msg.created_at)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={`relative group rounded-2xl px-4 py-2.5 ${
|
className={`relative group rounded-2xl px-4 py-2.5 ${
|
||||||
isOwnMessage
|
isOwnMessage
|
||||||
? 'bg-blue-600 text-white'
|
? 'bg-blue-600 dark:bg-primary-600 text-white'
|
||||||
: 'bg-white text-gray-900 border border-gray-200'
|
: 'bg-white dark:bg-neutral-800 text-gray-900 dark:text-white border border-gray-200 dark:border-neutral-700'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<p className="text-sm whitespace-pre-wrap break-words">{msg.message}</p>
|
<p className="text-sm whitespace-pre-wrap break-words">{msg.message}</p>
|
||||||
@@ -138,7 +138,7 @@ const TeamChatPage: FC = (): ReactElement => {
|
|||||||
{canDelete && (
|
{canDelete && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDeleteMessage(msg.id)}
|
onClick={() => handleDeleteMessage(msg.id)}
|
||||||
className={`absolute top-1 ${isOwnMessage ? 'left-1' : 'right-1'} opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-gray-200 ${isOwnMessage ? 'hover:bg-blue-700' : ''}`}
|
className={`absolute top-1 ${isOwnMessage ? 'left-1' : 'right-1'} opacity-0 group-hover:opacity-100 transition-opacity p-1 rounded hover:bg-gray-200 dark:hover:bg-neutral-700 ${isOwnMessage ? 'hover:bg-blue-700 dark:hover:bg-primary-700' : ''}`}
|
||||||
title="Delete message"
|
title="Delete message"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -158,10 +158,10 @@ const TeamChatPage: FC = (): ReactElement => {
|
|||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center h-64 text-center">
|
<div className="flex flex-col items-center justify-center h-64 text-center">
|
||||||
<div className="text-6xl mb-4">💬</div>
|
<div className="text-6xl mb-4">💬</div>
|
||||||
<h3 className="text-xl font-semibold text-gray-900 mb-2">
|
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
No messages yet
|
No messages yet
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-gray-600">
|
<p className="text-gray-600 dark:text-neutral-400">
|
||||||
Be the first to start the conversation!
|
Be the first to start the conversation!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,7 +170,7 @@ const TeamChatPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Message Input */}
|
{/* Message Input */}
|
||||||
<div className="bg-white border-t shadow-lg">
|
<div className="bg-white dark:bg-neutral-900 border-t dark:border-neutral-700 shadow-lg dark:shadow-neutral-950/50">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||||
<form onSubmit={handleSendMessage} className="flex gap-3">
|
<form onSubmit={handleSendMessage} className="flex gap-3">
|
||||||
<input
|
<input
|
||||||
@@ -178,7 +178,7 @@ const TeamChatPage: FC = (): ReactElement => {
|
|||||||
value={message}
|
value={message}
|
||||||
onChange={(e) => setMessage(e.target.value)}
|
onChange={(e) => setMessage(e.target.value)}
|
||||||
placeholder="Type your message..."
|
placeholder="Type your message..."
|
||||||
className="flex-1 px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="flex-1 px-4 py-3 border border-gray-300 dark:border-neutral-600 dark:bg-neutral-800 dark:text-white dark:placeholder-neutral-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-primary-500 focus:border-transparent"
|
||||||
disabled={isSending}
|
disabled={isSending}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -46,17 +46,17 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
if (isLoadingTeam) {
|
if (isLoadingTeam) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="text-gray-600">Loading team...</div>
|
<div className="text-gray-600 dark:text-neutral-400">Loading team...</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isLeader) {
|
if (!isLeader) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2>
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">Access Denied</h2>
|
||||||
<p className="text-gray-600 mb-4">Only the team leader can edit team information</p>
|
<p className="text-gray-600 dark:text-neutral-400 mb-4">Only the team leader can edit team information</p>
|
||||||
<Button onClick={() => navigate(`/teams/${teamId}`)}>Back to Team</Button>
|
<Button onClick={() => navigate(`/teams/${teamId}`)}>Back to Team</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -114,20 +114,20 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Edit Team Info</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Edit Team Info</h1>
|
||||||
<p className="text-gray-600 mt-1">Update your team details</p>
|
<p className="text-gray-600 dark:text-neutral-400 mt-1">Update your team details</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div className="bg-white rounded-lg shadow-md p-8">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-8">
|
||||||
<form onSubmit={onSubmit} className="space-y-6">
|
<form onSubmit={onSubmit} className="space-y-6">
|
||||||
{/* Banner Upload */}
|
{/* Banner Upload */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
|
||||||
Team Banner
|
Team Banner
|
||||||
</label>
|
</label>
|
||||||
{bannerPreview ? (
|
{bannerPreview ? (
|
||||||
@@ -135,7 +135,7 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
src={bannerPreview}
|
src={bannerPreview}
|
||||||
alt="Banner preview"
|
alt="Banner preview"
|
||||||
className="w-full h-48 object-cover rounded-lg border-2 border-gray-200"
|
className="w-full h-48 object-cover rounded-lg border-2 border-gray-200 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -150,10 +150,10 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<label className="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:bg-gray-50">
|
<label className="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-300 dark:border-neutral-600 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-neutral-800">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-gray-500">Click to upload banner</p>
|
<p className="text-gray-500 dark:text-neutral-400">Click to upload banner</p>
|
||||||
<p className="text-xs text-gray-400 mt-1">1200x400 recommended</p>
|
<p className="text-xs text-gray-400 dark:text-neutral-500 mt-1">1200x400 recommended</p>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -167,7 +167,7 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Logo Upload */}
|
{/* Logo Upload */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
|
||||||
Team Logo
|
Team Logo
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
@@ -175,11 +175,11 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
src={logoPreview}
|
src={logoPreview}
|
||||||
alt="Logo preview"
|
alt="Logo preview"
|
||||||
className="w-24 h-24 rounded-full object-cover border-2 border-gray-200"
|
className="w-24 h-24 rounded-full object-cover border-2 border-gray-200 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-24 h-24 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-24 h-24 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<span className="text-gray-400 text-3xl">👥</span>
|
<span className="text-gray-400 dark:text-neutral-500 text-3xl">👥</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
@@ -222,7 +222,7 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* City */}
|
{/* City */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
|
||||||
City
|
City
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -232,7 +232,7 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
<div>
|
<div>
|
||||||
<select
|
<select
|
||||||
{...field}
|
{...field}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-neutral-600 dark:bg-neutral-800 dark:text-white rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
>
|
>
|
||||||
<option value="">Select city</option>
|
<option value="">Select city</option>
|
||||||
{INDONESIAN_CITIES.map((city) => (
|
{INDONESIAN_CITIES.map((city) => (
|
||||||
@@ -251,7 +251,7 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Description
|
Description
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -276,7 +276,7 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Visibility */}
|
{/* Visibility */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Team Visibility
|
Team Visibility
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -284,7 +284,7 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
name="visibility"
|
name="visibility"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label className="flex items-start space-x-3 cursor-pointer border rounded-lg p-4 hover:bg-gray-50">
|
<label className="flex items-start space-x-3 cursor-pointer border dark:border-neutral-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-neutral-800">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
{...field}
|
{...field}
|
||||||
@@ -293,13 +293,13 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
className="mt-1"
|
className="mt-1"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">Public</p>
|
<p className="font-medium text-gray-900 dark:text-white">Public</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600 dark:text-neutral-400">
|
||||||
Team will be visible in Browse Teams
|
Team will be visible in Browse Teams
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-start space-x-3 cursor-pointer border rounded-lg p-4 hover:bg-gray-50">
|
<label className="flex items-start space-x-3 cursor-pointer border dark:border-neutral-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-neutral-800">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
{...field}
|
{...field}
|
||||||
@@ -308,8 +308,8 @@ const EditTeamPage: FC = (): ReactElement => {
|
|||||||
className="mt-1"
|
className="mt-1"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">Private</p>
|
<p className="font-medium text-gray-900 dark:text-white">Private</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600 dark:text-neutral-400">
|
||||||
Team hidden, invite-only
|
Team hidden, invite-only
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ const TeamDetailLayout: FC = (): ReactElement => {
|
|||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen bg-gray-50">
|
<div className="flex min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col overflow-auto">
|
<div className="flex-1 flex flex-col overflow-auto">
|
||||||
{/* Mobile Header with Hamburger */}
|
{/* Mobile Header with Hamburger */}
|
||||||
<div className="lg:hidden bg-white border-b px-4 py-3 flex items-center">
|
<div className="lg:hidden bg-white dark:bg-neutral-900 border-b dark:border-neutral-700 px-4 py-3 flex items-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSidebarOpen(true)}
|
onClick={() => setSidebarOpen(true)}
|
||||||
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
|
||||||
>
|
>
|
||||||
<svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-6 h-6 text-gray-600 dark:text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<h1 className="ml-3 text-lg font-bold text-gray-900">🏆 Hackathon</h1>
|
<h1 className="ml-3 text-lg font-bold text-gray-900 dark:text-white">Hackathon</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main className="flex-1 overflow-auto">
|
<main className="flex-1 overflow-auto">
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
const { session } = useAuthStore();
|
const { session } = useAuthStore();
|
||||||
const [showInviteModal, setShowInviteModal] = useState(false);
|
const [showInviteModal, setShowInviteModal] = useState(false);
|
||||||
|
|
||||||
const { data: teamData } = useTeamById(teamId || '');
|
const { data: teamData, isLoading: isLoadingTeam } = useTeamById(teamId || '');
|
||||||
const { data: membersData, isLoading: isLoadingMembers } = useTeamMembers(teamId || '');
|
const { data: membersData, isLoading: isLoadingMembers } = useTeamMembers(teamId || '');
|
||||||
const { data: joinRequestsData } = useTeamJoinRequests(teamId || '');
|
const { data: joinRequestsData } = useTeamJoinRequests(teamId || '');
|
||||||
|
|
||||||
@@ -42,11 +42,20 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
mode: 'all',
|
mode: 'all',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show loading state while fetching team data
|
||||||
|
if (isLoadingTeam) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
|
<p className="text-gray-600 dark:text-neutral-400">Loading...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isLeader) {
|
if (!isLeader) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2>
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">Access Denied</h2>
|
||||||
<p className="text-gray-600 mb-4">Only the team leader can manage members</p>
|
<p className="text-gray-600 dark:text-neutral-400 mb-4">Only the team leader can manage members</p>
|
||||||
<Button onClick={() => navigate(`/teams/${teamId}`)}>Back to Team</Button>
|
<Button onClick={() => navigate(`/teams/${teamId}`)}>Back to Team</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -94,13 +103,13 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700">
|
||||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Manage Members</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Manage Members</h1>
|
||||||
<p className="text-gray-600 mt-1">{team?.name}</p>
|
<p className="text-gray-600 dark:text-neutral-400 mt-1">{team?.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-3">
|
<div className="flex space-x-3">
|
||||||
<Button onClick={() => setShowInviteModal(true)}>
|
<Button onClick={() => setShowInviteModal(true)}>
|
||||||
@@ -117,13 +126,13 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-6">
|
||||||
{/* Join Requests */}
|
{/* Join Requests */}
|
||||||
{joinRequests.length > 0 && (
|
{joinRequests.length > 0 && (
|
||||||
<div className="bg-white rounded-lg shadow-md p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-6">
|
||||||
<h2 className="text-xl font-bold text-gray-900 mb-4">
|
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
Join Requests ({joinRequests.length})
|
Join Requests ({joinRequests.length})
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{joinRequests.map((request) => (
|
{joinRequests.map((request) => (
|
||||||
<div key={request.id} className="border rounded-lg p-4">
|
<div key={request.id} className="border dark:border-neutral-700 rounded-lg p-4">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex items-center space-x-3 flex-1">
|
<div className="flex items-center space-x-3 flex-1">
|
||||||
{request.user.avatar ? (
|
{request.user.avatar ? (
|
||||||
@@ -133,17 +142,17 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
className="w-12 h-12 rounded-full object-cover"
|
className="w-12 h-12 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-12 h-12 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<span className="text-gray-500">👤</span>
|
<span className="text-gray-500 dark:text-neutral-400">👤</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="font-medium text-gray-900">{request.user.fullname}</p>
|
<p className="font-medium text-gray-900 dark:text-white">{request.user.fullname}</p>
|
||||||
<p className="text-sm text-gray-600">{request.user.email}</p>
|
<p className="text-sm text-gray-600 dark:text-neutral-400">{request.user.email}</p>
|
||||||
{request.user.location && (
|
{request.user.location && (
|
||||||
<p className="text-sm text-gray-500">📍 {request.user.location}</p>
|
<p className="text-sm text-gray-500 dark:text-neutral-500">📍 {request.user.location}</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-sm text-gray-700 mt-2 italic">"{request.message}"</p>
|
<p className="text-sm text-gray-700 dark:text-neutral-300 mt-2 italic">"{request.message}"</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2 ml-4">
|
<div className="flex space-x-2 ml-4">
|
||||||
@@ -171,16 +180,16 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Current Members */}
|
{/* Current Members */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-6">
|
||||||
<h2 className="text-xl font-bold text-gray-900 mb-4">
|
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
Current Members ({members.length})
|
Current Members ({members.length})
|
||||||
</h2>
|
</h2>
|
||||||
{isLoadingMembers ? (
|
{isLoadingMembers ? (
|
||||||
<p className="text-gray-600">Loading members...</p>
|
<p className="text-gray-600 dark:text-neutral-400">Loading members...</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{members.map((member) => (
|
{members.map((member) => (
|
||||||
<div key={member.id} className="border rounded-lg p-4 flex items-center justify-between">
|
<div key={member.id} className="border dark:border-neutral-700 rounded-lg p-4 flex items-center justify-between">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
{member.user.avatar ? (
|
{member.user.avatar ? (
|
||||||
<img
|
<img
|
||||||
@@ -189,24 +198,24 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
className="w-12 h-12 rounded-full object-cover"
|
className="w-12 h-12 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-12 h-12 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<span className="text-gray-500">👤</span>
|
<span className="text-gray-500 dark:text-neutral-400">👤</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">{member.user.fullname}</p>
|
<p className="font-medium text-gray-900 dark:text-white">{member.user.fullname}</p>
|
||||||
<p className="text-sm text-gray-600">{member.user.email}</p>
|
<p className="text-sm text-gray-600 dark:text-neutral-400">{member.user.email}</p>
|
||||||
{member.user.location && (
|
{member.user.location && (
|
||||||
<p className="text-sm text-gray-500">📍 {member.user.location}</p>
|
<p className="text-sm text-gray-500 dark:text-neutral-500">📍 {member.user.location}</p>
|
||||||
)}
|
)}
|
||||||
<div className="flex items-center space-x-2 mt-1">
|
<div className="flex items-center space-x-2 mt-1">
|
||||||
{member.role === 'leader' && (
|
{member.role === 'leader' && (
|
||||||
<span className="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs font-medium">
|
<span className="px-2 py-1 bg-blue-100 dark:bg-primary-900/30 text-blue-800 dark:text-primary-300 rounded text-xs font-medium">
|
||||||
Leader
|
Leader
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{member.status === ETeamMemberStatus.PENDING && (
|
{member.status === ETeamMemberStatus.PENDING && (
|
||||||
<span className="px-2 py-1 bg-yellow-100 text-yellow-800 rounded text-xs font-medium">
|
<span className="px-2 py-1 bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-300 rounded text-xs font-medium">
|
||||||
Pending Invitation
|
Pending Invitation
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -230,8 +239,8 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Warning */}
|
{/* Warning */}
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
|
||||||
<p className="text-sm text-yellow-800">
|
<p className="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<strong>Note:</strong> Members cannot leave the team without your approval. Only you can remove members from the team.
|
<strong>Note:</strong> Members cannot leave the team without your approval. Only you can remove members from the team.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -239,22 +248,22 @@ const ManageMembersPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Invite Member Modal */}
|
{/* Invite Member Modal */}
|
||||||
{showInviteModal && (
|
{showInviteModal && (
|
||||||
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/20 dark:bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-xl dark:shadow-neutral-950/50 max-w-md w-full p-6">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
Invite Member
|
Invite Member
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 mb-4">
|
<p className="text-gray-600 dark:text-neutral-400 mb-4">
|
||||||
Send an invitation to join your team. The invited member will see the invitation on their dashboard after logging in.
|
Send an invitation to join your team. The invited member will see the invitation on their dashboard after logging in.
|
||||||
</p>
|
</p>
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-6">
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3 mb-6">
|
||||||
<p className="text-sm text-yellow-800">
|
<p className="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<strong>Important:</strong> The email you enter must match the GitHub email address the member uses to sign in.
|
<strong>Important:</strong> The email you enter must match the GitHub email address the member uses to sign in.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={handleInvite} className="space-y-4">
|
<form onSubmit={handleInvite} className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
|
||||||
Email Address
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
const currentUserId = session?.user?.id;
|
const currentUserId = session?.user?.id;
|
||||||
|
|
||||||
const isLeader = currentUserId === team?.leader_id;
|
const isLeader = currentUserId === team?.leader_id;
|
||||||
|
const isMember = members.some(
|
||||||
|
(member: any) => member.user_id === currentUserId
|
||||||
|
);
|
||||||
const canInvite = isLeader && members.length < MAX_TEAM_MEMBERS;
|
const canInvite = isLeader && members.length < MAX_TEAM_MEMBERS;
|
||||||
|
|
||||||
// Calculate total images to load
|
// Calculate total images to load
|
||||||
@@ -218,7 +221,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
<div className="fixed inset-0 bg-white/60 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-white/60 flex items-center justify-center z-50">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="inline-block animate-spin rounded-full h-12 w-12 border-4 border-blue-600 border-t-transparent"></div>
|
<div className="inline-block animate-spin rounded-full h-12 w-12 border-4 border-blue-600 border-t-transparent"></div>
|
||||||
<p className="mt-4 text-gray-600 font-medium">
|
<p className="mt-4 text-gray-600 dark:text-neutral-400">
|
||||||
Loading team data...
|
Loading team data...
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -230,7 +233,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
if (!team) {
|
if (!team) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
Team not found
|
Team not found
|
||||||
</h2>
|
</h2>
|
||||||
<Button onClick={() => navigate('/dashboard')}>
|
<Button onClick={() => navigate('/dashboard')}>
|
||||||
@@ -241,7 +244,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 relative">
|
<div className="min-h-screen bg-gray-50 relative dark:bg-neutral-950">
|
||||||
{/* Loading overlay while images are loading */}
|
{/* Loading overlay while images are loading */}
|
||||||
{!imagesLoaded && totalImagesToLoad > 0 && (
|
{!imagesLoaded && totalImagesToLoad > 0 && (
|
||||||
<div className="fixed inset-0 bg-white/80 flex items-center justify-center z-50">
|
<div className="fixed inset-0 bg-white/80 flex items-center justify-center z-50">
|
||||||
@@ -256,7 +259,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Header with Banner */}
|
{/* Header with Banner */}
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700">
|
||||||
{team.banner && (
|
{team.banner && (
|
||||||
<div className="w-full h-32 md:h-48 overflow-hidden relative">
|
<div className="w-full h-32 md:h-48 overflow-hidden relative">
|
||||||
<div
|
<div
|
||||||
@@ -295,18 +298,18 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl md:text-3xl font-bold text-gray-900 line-clamp-2">
|
<h1 className="text-xl md:text-3xl font-bold text-gray-900 dark:text-white line-clamp-2">
|
||||||
{team.name}
|
{team.name}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm md:text-base text-gray-600 mt-1 line-clamp-1">
|
<p className="text-sm md:text-base text-gray-600 dark:text-neutral-400 mt-1 line-clamp-1">
|
||||||
📍 {team.city}
|
📍 {team.city}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-wrap items-center gap-2 md:space-x-4 mt-2">
|
<div className="flex flex-wrap items-center gap-2 md:space-x-4 mt-2">
|
||||||
<span className="text-sm text-gray-500">
|
<span className="text-sm text-gray-500 dark:text-neutral-400">
|
||||||
{members.length}{' '}
|
{members.length}{' '}
|
||||||
{members.length === 1 ? 'Member' : 'Members'}
|
{members.length === 1 ? 'Member' : 'Members'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-sm text-gray-500">
|
<span className="text-sm text-gray-500 dark:text-neutral-400">
|
||||||
{team.visibility === 'public' ? '🌐 Public' : '🔒 Private'}
|
{team.visibility === 'public' ? '🌐 Public' : '🔒 Private'}
|
||||||
</span>
|
</span>
|
||||||
{isLeader && (
|
{isLeader && (
|
||||||
@@ -326,19 +329,19 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="lg:col-span-2 space-y-4 md:space-y-6">
|
<div className="lg:col-span-2 space-y-4 md:space-y-6">
|
||||||
{/* Team Description */}
|
{/* Team Description */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-4 md:p-6">
|
||||||
<h2 className="text-lg md:text-xl font-bold text-gray-900 mb-3 md:mb-4">
|
<h2 className="text-lg md:text-xl font-bold text-gray-900 dark:text-white mb-3 md:mb-4">
|
||||||
About Team
|
About Team
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-700 whitespace-pre-wrap">
|
<p className="text-gray-700 dark:text-neutral-300 whitespace-pre-wrap">
|
||||||
{team.description}
|
{team.description}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Team Actions - Only for Leader */}
|
{/* Team Actions - Only for Leader */}
|
||||||
{isLeader && (
|
{isLeader && (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-4 md:p-6">
|
||||||
<h2 className="text-lg md:text-xl font-bold text-gray-900 mb-3 md:mb-4">
|
<h2 className="text-lg md:text-xl font-bold text-gray-900 dark:text-white mb-3 md:mb-4">
|
||||||
Team Management
|
Team Management
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
@@ -383,17 +386,17 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
{!canInvite && members.length >= MAX_TEAM_MEMBERS && (
|
{!canInvite && members.length >= MAX_TEAM_MEMBERS && (
|
||||||
<p className="text-sm text-gray-600 mt-3 text-center">
|
<p className="text-sm text-gray-600 dark:text-neutral-400 mt-3 text-center">
|
||||||
Maximum team size reached ({MAX_TEAM_MEMBERS} members)
|
Maximum team size reached ({MAX_TEAM_MEMBERS} members)
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Quick Actions for Members */}
|
{/* Quick Actions for Members (non-leaders) */}
|
||||||
{!isLeader && (
|
{!isLeader && isMember && (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-4 md:p-6">
|
||||||
<h2 className="text-lg md:text-xl font-bold text-gray-900 mb-3 md:mb-4">
|
<h2 className="text-lg md:text-xl font-bold text-gray-900 dark:text-white mb-3 md:mb-4">
|
||||||
Quick Actions
|
Quick Actions
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
@@ -413,16 +416,16 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Submission Status */}
|
{/* Submission Status - Only show to members */}
|
||||||
{team.has_submission && (
|
{team.has_submission && isMember && (
|
||||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 md:p-6">
|
<div className="bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg p-4 md:p-6">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<span className="text-3xl">✅</span>
|
<span className="text-3xl">✅</span>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-green-900">
|
<h3 className="font-bold text-green-900 dark:text-green-400">
|
||||||
Project Submitted
|
Project Submitted
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-green-700 text-sm">
|
<p className="text-green-700 dark:text-green-300 text-sm">
|
||||||
Your team has successfully submitted a project
|
Your team has successfully submitted a project
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -439,14 +442,14 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="space-y-4 md:space-y-6">
|
<div className="space-y-4 md:space-y-6">
|
||||||
{/* Team Leader */}
|
{/* Team Leader */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-4 md:p-6">
|
||||||
<h3 className="text-base md:text-lg font-bold text-gray-900 mb-3 md:mb-4">
|
<h3 className="text-base md:text-lg font-bold text-gray-900 dark:text-white mb-3 md:mb-4">
|
||||||
Team Leader
|
Team Leader
|
||||||
</h3>
|
</h3>
|
||||||
{team.leader && (
|
{team.leader && (
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate(`/users/${team.leader.id}`)}
|
onClick={() => navigate(`/users/${team.leader.id}`)}
|
||||||
className="w-full flex items-center space-x-3 hover:bg-gray-100 rounded-lg p-2 transition-colors text-left cursor-pointer"
|
className="w-full flex items-center space-x-3 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded-lg p-2 transition-colors text-left cursor-pointer"
|
||||||
>
|
>
|
||||||
{team.leader.avatar ? (
|
{team.leader.avatar ? (
|
||||||
<img
|
<img
|
||||||
@@ -455,23 +458,27 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
className="w-12 h-12 rounded-full object-cover"
|
className="w-12 h-12 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-12 h-12 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<span className="text-gray-500">👤</span>
|
<span className="text-gray-500 dark:text-neutral-400">
|
||||||
|
👤
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">
|
<p className="font-medium text-gray-900 dark:text-white">
|
||||||
{team.leader.fullname}
|
{team.leader.fullname}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">{team.leader.email}</p>
|
<p className="text-sm text-gray-600 dark:text-neutral-400">
|
||||||
|
{team.leader.email}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Team Members */}
|
{/* Team Members */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-800 rounded-lg shadow-md p-4 md:p-6">
|
||||||
<h3 className="text-base md:text-lg font-bold text-gray-900 mb-3 md:mb-4">
|
<h3 className="text-base md:text-lg font-bold text-gray-900 dark:text-white mb-3 md:mb-4">
|
||||||
Members ({members.length})
|
Members ({members.length})
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
@@ -479,7 +486,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
<button
|
<button
|
||||||
key={member.id}
|
key={member.id}
|
||||||
onClick={() => navigate(`/users/${member.user.id}`)}
|
onClick={() => navigate(`/users/${member.user.id}`)}
|
||||||
className="w-full flex items-center space-x-3 hover:bg-gray-100 rounded-lg p-2 transition-colors text-left cursor-pointer"
|
className="w-full flex items-center space-x-3 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded-lg p-2 transition-colors text-left cursor-pointer"
|
||||||
>
|
>
|
||||||
{member.user?.avatar ? (
|
{member.user?.avatar ? (
|
||||||
<img
|
<img
|
||||||
@@ -490,15 +497,17 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
onError={handleImageError}
|
onError={handleImageError}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-10 h-10 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<span className="text-gray-500 text-sm">👤</span>
|
<span className="text-gray-500 dark:text-neutral-400 text-sm">
|
||||||
|
👤
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="font-medium text-gray-900 truncate">
|
<p className="font-medium text-gray-900 dark:text-white truncate">
|
||||||
{member.user?.fullname || 'Unknown'}
|
{member.user?.fullname || 'Unknown'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500 dark:text-neutral-400">
|
||||||
{member.role === ETeamMemberRole.LEADER
|
{member.role === ETeamMemberRole.LEADER
|
||||||
? 'Leader'
|
? 'Leader'
|
||||||
: 'Member'}
|
: 'Member'}
|
||||||
@@ -514,17 +523,17 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Invite Member Modal */}
|
{/* Invite Member Modal */}
|
||||||
{showInviteModal && (
|
{showInviteModal && (
|
||||||
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/20 dark:bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-xl max-w-md w-full p-6">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
Invite Team Member
|
Invite Team Member
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 mb-4">
|
<p className="text-gray-600 dark:text-neutral-400 mb-4">
|
||||||
Send an invitation to join your team. The invited member will see
|
Send an invitation to join your team. The invited member will see
|
||||||
the invitation on their dashboard after logging in.
|
the invitation on their dashboard after logging in.
|
||||||
</p>
|
</p>
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-6">
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-3 mb-6">
|
||||||
<p className="text-sm text-yellow-800">
|
<p className="text-sm text-yellow-800 dark:text-yellow-300">
|
||||||
<strong>Important:</strong> The email you enter must match the
|
<strong>Important:</strong> The email you enter must match the
|
||||||
GitHub email address the member uses to sign in.
|
GitHub email address the member uses to sign in.
|
||||||
</p>
|
</p>
|
||||||
@@ -533,7 +542,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
htmlFor="invite-email"
|
htmlFor="invite-email"
|
||||||
className="block text-sm font-medium text-gray-700 mb-2"
|
className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2"
|
||||||
>
|
>
|
||||||
Email Address
|
Email Address
|
||||||
</label>
|
</label>
|
||||||
@@ -543,10 +552,10 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
value={inviteEmail}
|
value={inviteEmail}
|
||||||
onChange={(e) => setInviteEmail(e.target.value)}
|
onChange={(e) => setInviteEmail(e.target.value)}
|
||||||
placeholder="Enter email address..."
|
placeholder="Enter email address..."
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-gray-500 mt-1">
|
<p className="text-sm text-gray-500 dark:text-neutral-400 mt-1">
|
||||||
Current members: {members.length}/{MAX_TEAM_MEMBERS}
|
Current members: {members.length}/{MAX_TEAM_MEMBERS}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -577,15 +586,15 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Join Requests Modal */}
|
{/* Join Requests Modal */}
|
||||||
{showJoinRequestsModal && (
|
{showJoinRequestsModal && (
|
||||||
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/30 dark:bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||||
<div className="bg-white rounded-lg shadow-xl max-w-2xl w-full p-6 max-h-[80vh] overflow-y-auto">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-xl max-w-2xl w-full p-6 max-h-[80vh] overflow-y-auto">
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h2 className="text-2xl font-bold text-gray-900">
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||||
Join Requests ({pendingJoinRequests.length})
|
Join Requests ({pendingJoinRequests.length})
|
||||||
</h2>
|
</h2>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowJoinRequestsModal(false)}
|
onClick={() => setShowJoinRequestsModal(false)}
|
||||||
className="text-gray-400 hover:text-gray-600 text-2xl"
|
className="text-gray-400 dark:text-neutral-500 hover:text-gray-600 dark:hover:text-neutral-300 text-2xl"
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
@@ -593,10 +602,10 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{pendingJoinRequests.length === 0 ? (
|
{pendingJoinRequests.length === 0 ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-gray-600 text-lg">
|
<p className="text-gray-600 dark:text-neutral-400 text-lg">
|
||||||
No pending join requests
|
No pending join requests
|
||||||
</p>
|
</p>
|
||||||
<p className="text-gray-500 text-sm mt-2">
|
<p className="text-gray-500 dark:text-neutral-500 text-sm mt-2">
|
||||||
When users request to join your team, they'll appear here
|
When users request to join your team, they'll appear here
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -605,7 +614,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
{pendingJoinRequests.map((request: any) => (
|
{pendingJoinRequests.map((request: any) => (
|
||||||
<div
|
<div
|
||||||
key={request.id}
|
key={request.id}
|
||||||
className="border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow"
|
className="border border-gray-200 dark:border-neutral-700 rounded-lg p-4 hover:shadow-md transition-shadow"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex items-start space-x-3 flex-1">
|
<div className="flex items-start space-x-3 flex-1">
|
||||||
@@ -616,25 +625,27 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
className="w-12 h-12 rounded-full object-cover"
|
className="w-12 h-12 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center shrink-0">
|
<div className="w-12 h-12 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center shrink-0">
|
||||||
<span className="text-gray-500 text-xl">👤</span>
|
<span className="text-gray-500 dark:text-neutral-400 text-xl">
|
||||||
|
👤
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<p className="font-semibold text-gray-900">
|
<p className="font-semibold text-gray-900 dark:text-white">
|
||||||
{request.user?.fullname || 'Unknown User'}
|
{request.user?.fullname || 'Unknown User'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600 dark:text-neutral-400">
|
||||||
{request.user?.email}
|
{request.user?.email}
|
||||||
</p>
|
</p>
|
||||||
{request.message && (
|
{request.message && (
|
||||||
<div className="mt-2 bg-gray-50 rounded-lg p-3">
|
<div className="mt-2 bg-gray-50 dark:bg-neutral-800 rounded-lg p-3">
|
||||||
<p className="text-sm text-gray-700">
|
<p className="text-sm text-gray-700 dark:text-neutral-300">
|
||||||
<strong>Message:</strong> {request.message}
|
<strong>Message:</strong> {request.message}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-gray-500 mt-2">
|
<p className="text-xs text-gray-500 dark:text-neutral-500 mt-2">
|
||||||
Requested{' '}
|
Requested{' '}
|
||||||
{new Date(request.created_at).toLocaleDateString()}
|
{new Date(request.created_at).toLocaleDateString()}
|
||||||
</p>
|
</p>
|
||||||
@@ -665,8 +676,8 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{members.length >= MAX_TEAM_MEMBERS && (
|
{members.length >= MAX_TEAM_MEMBERS && (
|
||||||
<div className="mt-3 bg-yellow-50 border border-yellow-200 rounded-lg p-2">
|
<div className="mt-3 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-2">
|
||||||
<p className="text-xs text-yellow-800">
|
<p className="text-xs text-yellow-800 dark:text-yellow-300">
|
||||||
Team is full ({MAX_TEAM_MEMBERS}/{MAX_TEAM_MEMBERS}{' '}
|
Team is full ({MAX_TEAM_MEMBERS}/{MAX_TEAM_MEMBERS}{' '}
|
||||||
members). Remove a member before accepting new
|
members). Remove a member before accepting new
|
||||||
requests.
|
requests.
|
||||||
|
|||||||
@@ -15,18 +15,18 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="text-gray-600">Loading submission...</div>
|
<div className="text-gray-600 dark:text-neutral-400">Loading submission...</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!submission) {
|
if (!submission) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="text-6xl mb-4">📄</div>
|
<div className="text-6xl mb-4">📄</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">No Submission Yet</h2>
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">No Submission Yet</h2>
|
||||||
<p className="text-gray-600 mb-4">Your team hasn't submitted a project</p>
|
<p className="text-gray-600 dark:text-neutral-400 mb-4">Your team hasn't submitted a project</p>
|
||||||
<Button onClick={() => navigate(`/teams/${teamId}`)}>Back to Team</Button>
|
<Button onClick={() => navigate(`/teams/${teamId}`)}>Back to Team</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -43,13 +43,13 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
: 'Not submitted';
|
: 'Not submitted';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700">
|
||||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Project Submission</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Project Submission</h1>
|
||||||
<p className="text-gray-600 mt-1">{team?.name}</p>
|
<p className="text-gray-600 dark:text-neutral-400 mt-1">{team?.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="secondary" onClick={() => navigate(`/teams/${teamId}`)}>
|
<Button variant="secondary" onClick={() => navigate(`/teams/${teamId}`)}>
|
||||||
Back to Team
|
Back to Team
|
||||||
@@ -60,22 +60,22 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
{/* Status Banner */}
|
{/* Status Banner */}
|
||||||
<div className="bg-green-50 border border-green-500 rounded-lg p-6 mb-6">
|
<div className="bg-green-50 dark:bg-green-900/20 border border-green-500 rounded-lg p-6 mb-6">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
<span className="text-4xl">✅</span>
|
<span className="text-4xl">✅</span>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-green-900 text-lg">Project Submitted Successfully</h3>
|
<h3 className="font-bold text-green-900 dark:text-green-100 text-lg">Project Submitted Successfully</h3>
|
||||||
<p className="text-green-700 text-sm">
|
<p className="text-green-700 dark:text-green-300 text-sm">
|
||||||
Submitted on {submittedDate}
|
Submitted on {submittedDate}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-green-600 text-xs mt-1">
|
<p className="text-green-600 dark:text-green-400 text-xs mt-1">
|
||||||
This submission is now read-only and cannot be edited
|
This submission is now read-only and cannot be edited
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow-md overflow-hidden">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 overflow-hidden">
|
||||||
{/* Project Header */}
|
{/* Project Header */}
|
||||||
<div className="bg-gradient-to-r from-blue-600 to-blue-800 text-white p-8">
|
<div className="bg-gradient-to-r from-blue-600 to-blue-800 text-white p-8">
|
||||||
<h2 className="text-3xl font-bold mb-2">{submission.project_name}</h2>
|
<h2 className="text-3xl font-bold mb-2">{submission.project_name}</h2>
|
||||||
@@ -86,9 +86,9 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
<div className="p-8 space-y-6">
|
<div className="p-8 space-y-6">
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-3">Project Description</h3>
|
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-3">Project Description</h3>
|
||||||
<div className="bg-gray-50 rounded-lg p-4">
|
<div className="bg-gray-50 dark:bg-neutral-800 rounded-lg p-4">
|
||||||
<p className="text-gray-700 whitespace-pre-wrap">{submission.description}</p>
|
<p className="text-gray-700 dark:text-neutral-300 whitespace-pre-wrap">{submission.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -96,12 +96,12 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
<div className="grid gap-6 md:grid-cols-2">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
{/* Repository */}
|
{/* Repository */}
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-3">Repository</h3>
|
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-3">Repository</h3>
|
||||||
<a
|
<a
|
||||||
href={submission.repository_url}
|
href={submission.repository_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center space-x-2 text-blue-600 hover:text-blue-800"
|
className="flex items-center space-x-2 text-blue-600 dark:text-primary-400 hover:text-blue-800 dark:hover:text-primary-300"
|
||||||
>
|
>
|
||||||
<span>🔗</span>
|
<span>🔗</span>
|
||||||
<span className="break-all">{submission.repository_url}</span>
|
<span className="break-all">{submission.repository_url}</span>
|
||||||
@@ -111,12 +111,12 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
{/* Demo URL */}
|
{/* Demo URL */}
|
||||||
{submission.demo_url && (
|
{submission.demo_url && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-3">Live Demo</h3>
|
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-3">Live Demo</h3>
|
||||||
<a
|
<a
|
||||||
href={submission.demo_url}
|
href={submission.demo_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center space-x-2 text-blue-600 hover:text-blue-800"
|
className="flex items-center space-x-2 text-blue-600 dark:text-primary-400 hover:text-blue-800 dark:hover:text-primary-300"
|
||||||
>
|
>
|
||||||
<span>🌐</span>
|
<span>🌐</span>
|
||||||
<span className="break-all">{submission.demo_url}</span>
|
<span className="break-all">{submission.demo_url}</span>
|
||||||
@@ -127,12 +127,12 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
{/* Presentation URL */}
|
{/* Presentation URL */}
|
||||||
{submission.presentation_url && (
|
{submission.presentation_url && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-3">Presentation</h3>
|
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-3">Presentation</h3>
|
||||||
<a
|
<a
|
||||||
href={submission.presentation_url}
|
href={submission.presentation_url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center space-x-2 text-blue-600 hover:text-blue-800"
|
className="flex items-center space-x-2 text-blue-600 dark:text-primary-400 hover:text-blue-800 dark:hover:text-primary-300"
|
||||||
>
|
>
|
||||||
<span>📊</span>
|
<span>📊</span>
|
||||||
<span className="break-all">{submission.presentation_url}</span>
|
<span className="break-all">{submission.presentation_url}</span>
|
||||||
@@ -144,7 +144,7 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
{/* Screenshots */}
|
{/* Screenshots */}
|
||||||
{submission.screenshots && submission.screenshots.length > 0 && (
|
{submission.screenshots && submission.screenshots.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-bold text-gray-900 mb-3">
|
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-3">
|
||||||
Screenshots ({submission.screenshots.length})
|
Screenshots ({submission.screenshots.length})
|
||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
@@ -159,7 +159,7 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
src={url}
|
src={url}
|
||||||
alt={`Screenshot ${index + 1}`}
|
alt={`Screenshot ${index + 1}`}
|
||||||
className="w-full h-48 object-cover rounded-lg border-2 border-gray-200 hover:border-blue-500 transition-colors cursor-pointer"
|
className="w-full h-48 object-cover rounded-lg border-2 border-gray-200 dark:border-neutral-700 hover:border-blue-500 dark:hover:border-primary-500 transition-colors cursor-pointer"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
))}
|
))}
|
||||||
@@ -168,22 +168,22 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Submission Info */}
|
{/* Submission Info */}
|
||||||
<div className="bg-gray-50 rounded-lg p-4 border-t-4 border-blue-600">
|
<div className="bg-gray-50 dark:bg-neutral-800 rounded-lg p-4 border-t-4 border-blue-600 dark:border-primary-500">
|
||||||
<h3 className="text-sm font-bold text-gray-900 mb-2">Submission Information</h3>
|
<h3 className="text-sm font-bold text-gray-900 dark:text-white mb-2">Submission Information</h3>
|
||||||
<div className="grid gap-2 text-sm">
|
<div className="grid gap-2 text-sm">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-600">Status:</span>
|
<span className="text-gray-600 dark:text-neutral-400">Status:</span>
|
||||||
<span className="font-medium text-green-600">
|
<span className="font-medium text-green-600 dark:text-green-400">
|
||||||
{submission.status === 'submitted' ? '✓ Submitted' : 'Draft'}
|
{submission.status === 'submitted' ? '✓ Submitted' : 'Draft'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-600">Submitted:</span>
|
<span className="text-gray-600 dark:text-neutral-400">Submitted:</span>
|
||||||
<span className="font-medium text-gray-900">{submittedDate}</span>
|
<span className="font-medium text-gray-900 dark:text-white">{submittedDate}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span className="text-gray-600">Submission ID:</span>
|
<span className="text-gray-600 dark:text-neutral-400">Submission ID:</span>
|
||||||
<span className="font-medium text-gray-900 font-mono text-xs">
|
<span className="font-medium text-gray-900 dark:text-white font-mono text-xs">
|
||||||
{submission.id}
|
{submission.id}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,8 +191,8 @@ const SubmissionViewPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Read-only Notice */}
|
{/* Read-only Notice */}
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
|
||||||
<p className="text-sm text-yellow-800">
|
<p className="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<strong>Note:</strong> This submission is now locked and cannot be edited or deleted.
|
<strong>Note:</strong> This submission is now locked and cannot be edited or deleted.
|
||||||
If you need to make changes, please contact the hackathon organizers.
|
If you need to make changes, please contact the hackathon organizers.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
if (!isLeader) {
|
if (!isLeader) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2>
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">Access Denied</h2>
|
||||||
<p className="text-gray-600 mb-4">Only the team leader can submit projects</p>
|
<p className="text-gray-600 dark:text-neutral-400 mb-4">Only the team leader can submit projects</p>
|
||||||
<Button onClick={() => navigate(`/teams/${teamId}`)}>Back to Team</Button>
|
<Button onClick={() => navigate(`/teams/${teamId}`)}>Back to Team</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -48,10 +48,10 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
if (hasSubmission) {
|
if (hasSubmission) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="text-6xl mb-4">✅</div>
|
<div className="text-6xl mb-4">✅</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">Project Already Submitted</h2>
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">Project Already Submitted</h2>
|
||||||
<p className="text-gray-600 mb-4">Your team has already submitted a project</p>
|
<p className="text-gray-600 dark:text-neutral-400 mb-4">Your team has already submitted a project</p>
|
||||||
<div className="flex space-x-3">
|
<div className="flex space-x-3">
|
||||||
<Button onClick={() => navigate(`/teams/${teamId}/submission`)}>
|
<Button onClick={() => navigate(`/teams/${teamId}/submission`)}>
|
||||||
View Submission
|
View Submission
|
||||||
@@ -95,22 +95,22 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Submit Project</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Submit Project</h1>
|
||||||
<p className="text-gray-600 mt-1">{team?.name}</p>
|
<p className="text-gray-600 dark:text-neutral-400 mt-1">{team?.name}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
{/* Warning Banner */}
|
{/* Warning Banner */}
|
||||||
<div className="bg-red-50 border-2 border-red-500 rounded-lg p-6 mb-6">
|
<div className="bg-red-50 dark:bg-red-900/20 border-2 border-red-500 rounded-lg p-6 mb-6">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<span className="text-3xl">⚠️</span>
|
<span className="text-3xl">⚠️</span>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-bold text-red-900 text-lg">IMPORTANT WARNING</h3>
|
<h3 className="font-bold text-red-900 dark:text-red-100 text-lg">IMPORTANT WARNING</h3>
|
||||||
<ul className="text-red-800 mt-2 space-y-1 text-sm">
|
<ul className="text-red-800 dark:text-red-200 mt-2 space-y-1 text-sm">
|
||||||
<li>• You can only submit your project ONCE</li>
|
<li>• You can only submit your project ONCE</li>
|
||||||
<li>• After submission, you CANNOT edit or change anything</li>
|
<li>• After submission, you CANNOT edit or change anything</li>
|
||||||
<li>• Make sure all information is correct before submitting</li>
|
<li>• Make sure all information is correct before submitting</li>
|
||||||
@@ -120,7 +120,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-lg shadow-md p-8">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-8">
|
||||||
<form onSubmit={(e) => {
|
<form onSubmit={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setShowConfirmModal(true);
|
setShowConfirmModal(true);
|
||||||
@@ -135,7 +135,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Project Description <span className="text-red-500">*</span>
|
Project Description <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -186,8 +186,8 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Screenshots */}
|
{/* Screenshots */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Project Screenshots <span className="text-gray-400">(Optional)</span>
|
Project Screenshots <span className="text-gray-400 dark:text-neutral-500">(Optional)</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 mb-4">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 mb-4">
|
||||||
{screenshots.map((url, index) => (
|
{screenshots.map((url, index) => (
|
||||||
@@ -195,7 +195,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
src={url}
|
src={url}
|
||||||
alt={`Screenshot ${index + 1}`}
|
alt={`Screenshot ${index + 1}`}
|
||||||
className="w-full h-32 object-cover rounded-lg border-2 border-gray-200"
|
className="w-full h-32 object-cover rounded-lg border-2 border-gray-200 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -207,10 +207,10 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<label className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:bg-gray-50">
|
<label className="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-gray-300 dark:border-neutral-600 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-neutral-800">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-gray-500">Click to upload screenshots</p>
|
<p className="text-gray-500 dark:text-neutral-400">Click to upload screenshots</p>
|
||||||
<p className="text-xs text-gray-400 mt-1">PNG, JPG up to 5MB each</p>
|
<p className="text-xs text-gray-400 dark:text-neutral-500 mt-1">PNG, JPG up to 5MB each</p>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -247,22 +247,22 @@ const SubmitProjectPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Confirmation Modal */}
|
{/* Confirmation Modal */}
|
||||||
{showConfirmModal && (
|
{showConfirmModal && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center z-50 p-4">
|
||||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-xl dark:shadow-neutral-950/50 max-w-md w-full p-6">
|
||||||
<div className="text-center mb-6">
|
<div className="text-center mb-6">
|
||||||
<div className="text-5xl mb-4">⚠️</div>
|
<div className="text-5xl mb-4">⚠️</div>
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-2">
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
||||||
Final Confirmation
|
Final Confirmation
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-red-600 font-medium">
|
<p className="text-red-600 dark:text-red-400 font-medium">
|
||||||
This action is IRREVERSIBLE!
|
This action is IRREVERSIBLE!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-gray-50 rounded-lg p-4 mb-6">
|
<div className="bg-gray-50 dark:bg-neutral-800 rounded-lg p-4 mb-6">
|
||||||
<p className="text-sm text-gray-700 mb-3">
|
<p className="text-sm text-gray-700 dark:text-neutral-300 mb-3">
|
||||||
By clicking "Submit Project", you confirm that:
|
By clicking "Submit Project", you confirm that:
|
||||||
</p>
|
</p>
|
||||||
<ul className="text-sm text-gray-600 space-y-2">
|
<ul className="text-sm text-gray-600 dark:text-neutral-400 space-y-2">
|
||||||
<li>✓ All information is correct and complete</li>
|
<li>✓ All information is correct and complete</li>
|
||||||
<li>✓ You understand this can only be done once</li>
|
<li>✓ You understand this can only be done once</li>
|
||||||
<li>✓ You cannot edit after submission</li>
|
<li>✓ You cannot edit after submission</li>
|
||||||
|
|||||||
@@ -63,14 +63,14 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Browse Teams</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Browse Teams</h1>
|
||||||
<p className="text-gray-600 mt-1">Find and join teams looking for members</p>
|
<p className="text-gray-600 dark:text-neutral-400 mt-1">Find and join teams looking for members</p>
|
||||||
</div>
|
</div>
|
||||||
<Link to="/dashboard" className="hidden md:block">
|
<Link to="/dashboard" className="hidden md:block">
|
||||||
<Button variant="secondary">Back to Dashboard</Button>
|
<Button variant="secondary">Back to Dashboard</Button>
|
||||||
@@ -81,10 +81,10 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Filters */}
|
{/* Filters */}
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<div className="bg-white p-6 rounded-lg shadow-sm mb-6">
|
<div className="bg-white dark:bg-neutral-900 p-6 rounded-lg shadow-sm mb-6">
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
|
||||||
Search Teams
|
Search Teams
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -92,11 +92,11 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
placeholder="Search by team name..."
|
placeholder="Search by team name..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full h-[42px] px-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full h-[42px] px-3 border border-gray-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
|
||||||
Filter by City
|
Filter by City
|
||||||
</label>
|
</label>
|
||||||
<CitySelect
|
<CitySelect
|
||||||
@@ -111,17 +111,17 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
{/* Teams List */}
|
{/* Teams List */}
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<p className="text-gray-600">Loading teams...</p>
|
<p className="text-gray-600 dark:text-neutral-400">Loading teams...</p>
|
||||||
</div>
|
</div>
|
||||||
) : teams.length === 0 ? (
|
) : teams.length === 0 ? (
|
||||||
<div className="bg-white rounded-lg shadow-sm p-12 text-center">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-sm p-12 text-center">
|
||||||
<p className="text-gray-600 text-lg">No teams found</p>
|
<p className="text-gray-600 dark:text-neutral-400 text-lg">No teams found</p>
|
||||||
<p className="text-gray-500 mt-2">Try adjusting your filters</p>
|
<p className="text-gray-500 dark:text-neutral-500 mt-2">Try adjusting your filters</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{teams.map((team) => (
|
{teams.map((team) => (
|
||||||
<div key={team.id} className="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow flex flex-col">
|
<div key={team.id} className="bg-white dark:bg-neutral-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow flex flex-col">
|
||||||
<img
|
<img
|
||||||
src={team.banner || '/images/banner-imphnen.png'}
|
src={team.banner || '/images/banner-imphnen.png'}
|
||||||
alt={team.name}
|
alt={team.name}
|
||||||
@@ -136,19 +136,19 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
className="w-12 h-12 rounded-full object-cover"
|
className="w-12 h-12 rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-12 h-12 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-12 h-12 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<span className="text-gray-500 text-xl">👥</span>
|
<span className="text-gray-500 dark:text-neutral-400 text-xl">👥</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="text-lg font-bold text-gray-900 line-clamp-2">{team.name}</h3>
|
<h3 className="text-lg font-bold text-gray-900 dark:text-white line-clamp-2">{team.name}</h3>
|
||||||
<div className="text-sm text-gray-600 flex gap-2">
|
<div className="text-sm text-gray-600 dark:text-neutral-400 flex gap-2">
|
||||||
<p className="truncate flex-1 min-w-0">📍 {team.city}</p>
|
<p className="truncate flex-1 min-w-0">📍 {team.city}</p>
|
||||||
<p className="whitespace-nowrap shrink-0">👥 {team.members?.length || 0} members</p>
|
<p className="whitespace-nowrap shrink-0">👥 {team.members?.length || 0} members</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 text-sm mb-4 line-clamp-3">
|
<p className="text-gray-600 dark:text-neutral-400 text-sm mb-4 line-clamp-3">
|
||||||
{team.description}
|
{team.description}
|
||||||
</p>
|
</p>
|
||||||
<div className="space-y-3 mt-auto">
|
<div className="space-y-3 mt-auto">
|
||||||
@@ -189,23 +189,23 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Join Request Modal */}
|
{/* Join Request Modal */}
|
||||||
{showJoinModal && (
|
{showJoinModal && (
|
||||||
<div className="fixed inset-0 bg-black/30 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
<div className="fixed inset-0 bg-black/30 dark:bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-xl max-w-md w-full p-6">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
Request to Join Team
|
Request to Join Team
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-gray-600 mb-6">
|
<p className="text-gray-600 dark:text-neutral-400 mb-6">
|
||||||
Send a message to the team leader explaining why you want to join
|
Send a message to the team leader explaining why you want to join
|
||||||
</p>
|
</p>
|
||||||
<form onSubmit={onSubmit} className="space-y-4">
|
<form onSubmit={onSubmit} className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
|
||||||
Your Message
|
Your Message
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
{...form.register('message')}
|
{...form.register('message')}
|
||||||
rows={4}
|
rows={4}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
className="w-full px-3 py-2 border border-gray-300 dark:border-neutral-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500"
|
||||||
placeholder="Tell the team leader why you want to join their team..."
|
placeholder="Tell the team leader why you want to join their team..."
|
||||||
/>
|
/>
|
||||||
{form.formState.errors.message && (
|
{form.formState.errors.message && (
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { useNavigate } from 'react-router';
|
|||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import { teamCreateSchema, TTeamCreateForm, useCreateTeam, ETeamVisibility, useUploadFile } from '@imphnen-frontend-service/service';
|
import { teamCreateSchema, TTeamCreateForm, useCreateTeam, ETeamVisibility, useUploadFile } from '@imphnen-frontend-service/service';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import INDONESIAN_CITIES from '../../../constants/cities';
|
|
||||||
|
import { CitySelect } from '../../../components/city-select';
|
||||||
|
|
||||||
const CreateTeamPage: FC = (): ReactElement => {
|
const CreateTeamPage: FC = (): ReactElement => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -79,29 +80,29 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700">
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Create Your Team</h1>
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Create Your Team</h1>
|
||||||
<p className="text-gray-600 mt-1">Build your hackathon dream team</p>
|
<p className="text-gray-600 dark:text-neutral-400 mt-1">Build your hackathon dream team</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div className="bg-white rounded-lg shadow-md p-8">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-900/50 p-8">
|
||||||
<form onSubmit={onSubmit} className="space-y-6">
|
<form onSubmit={onSubmit} className="space-y-6">
|
||||||
{/* Banner Upload */}
|
{/* Banner Upload */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
|
||||||
Team Banner <span className="text-gray-400">(Optional)</span>
|
Team Banner <span className="text-gray-400 dark:text-neutral-500">(Optional)</span>
|
||||||
</label>
|
</label>
|
||||||
{bannerPreview ? (
|
{bannerPreview ? (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<img
|
<img
|
||||||
src={bannerPreview}
|
src={bannerPreview}
|
||||||
alt="Banner preview"
|
alt="Banner preview"
|
||||||
className="w-full h-48 object-cover rounded-lg border-2 border-gray-200"
|
className="w-full h-48 object-cover rounded-lg border-2 border-gray-200 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -115,10 +116,10 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<label className="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:bg-gray-50">
|
<label className="flex flex-col items-center justify-center w-full h-48 border-2 border-dashed border-gray-300 dark:border-neutral-600 rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-neutral-800">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-gray-500">Click to upload banner</p>
|
<p className="text-gray-500 dark:text-neutral-400">Click to upload banner</p>
|
||||||
<p className="text-xs text-gray-400 mt-1">1200x400 recommended</p>
|
<p className="text-xs text-gray-400 dark:text-neutral-500 mt-1">1200x400 recommended</p>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -132,19 +133,19 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Logo Upload */}
|
{/* Logo Upload */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
|
||||||
Team Logo <span className="text-gray-400">(Optional, but highly recommended)</span>
|
Team Logo <span className="text-gray-400 dark:text-neutral-500">(Optional, but highly recommended)</span>
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
{logoPreview ? (
|
{logoPreview ? (
|
||||||
<img
|
<img
|
||||||
src={logoPreview}
|
src={logoPreview}
|
||||||
alt="Logo preview"
|
alt="Logo preview"
|
||||||
className="w-24 h-24 rounded-full object-cover border-2 border-gray-200"
|
className="w-24 h-24 rounded-full object-cover border-2 border-gray-200 dark:border-neutral-700"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-24 h-24 rounded-full bg-gray-200 flex items-center justify-center">
|
<div className="w-24 h-24 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
|
||||||
<span className="text-gray-400 text-3xl">👥</span>
|
<span className="text-gray-400 dark:text-neutral-500 text-3xl">👥</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
@@ -186,36 +187,26 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* City */}
|
{/* City */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
|
||||||
City <span className="text-red-500">*</span>
|
City <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="city"
|
name="city"
|
||||||
render={({ field, fieldState }) => (
|
render={({ field, fieldState }) => (
|
||||||
<div>
|
<CitySelect
|
||||||
<select
|
value={field.value}
|
||||||
{...field}
|
onChange={field.onChange}
|
||||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
error={fieldState.error?.message}
|
||||||
>
|
placeholder="Search your city..."
|
||||||
<option value="">Select city</option>
|
/>
|
||||||
{INDONESIAN_CITIES.map((city) => (
|
|
||||||
<option key={city} value={city}>
|
|
||||||
{city}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
{fieldState.error && (
|
|
||||||
<p className="text-sm text-red-500 mt-1">{fieldState.error.message}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Description <span className="text-red-500">*</span>
|
Description <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -239,7 +230,7 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Visibility */}
|
{/* Visibility */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="block text-sm font-medium text-gray-700">
|
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
|
||||||
Team Visibility <span className="text-red-500">*</span>
|
Team Visibility <span className="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -247,7 +238,7 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
name="visibility"
|
name="visibility"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<label className="flex items-start space-x-3 cursor-pointer border rounded-lg p-4 hover:bg-gray-50">
|
<label className="flex items-start space-x-3 cursor-pointer border dark:border-neutral-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-neutral-800">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
{...field}
|
{...field}
|
||||||
@@ -256,13 +247,13 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
className="mt-1"
|
className="mt-1"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">Public</p>
|
<p className="font-medium text-gray-900 dark:text-white">Public</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600 dark:text-neutral-400">
|
||||||
Team will be visible in Browse Teams. Anyone can request to join.
|
Team will be visible in Browse Teams. Anyone can request to join.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-start space-x-3 cursor-pointer border rounded-lg p-4 hover:bg-gray-50">
|
<label className="flex items-start space-x-3 cursor-pointer border dark:border-neutral-700 rounded-lg p-4 hover:bg-gray-50 dark:hover:bg-neutral-800">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
{...field}
|
{...field}
|
||||||
@@ -271,8 +262,8 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
className="mt-1"
|
className="mt-1"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium text-gray-900">Private</p>
|
<p className="font-medium text-gray-900 dark:text-white">Private</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600 dark:text-neutral-400">
|
||||||
Team is hidden from Browse Teams. Members can only join via invitation.
|
Team is hidden from Browse Teams. Members can only join via invitation.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -283,8 +274,8 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Warning */}
|
{/* Warning */}
|
||||||
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
|
||||||
<p className="text-sm text-yellow-800">
|
<p className="text-sm text-yellow-800 dark:text-yellow-200">
|
||||||
<strong>Note:</strong> As team leader, you cannot leave or join another team after creating this team.
|
<strong>Note:</strong> As team leader, you cannot leave or join another team after creating this team.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ const TeamsLayout: FC = (): ReactElement => {
|
|||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen bg-gray-50">
|
<div className="flex min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
{/* Mobile Header with Hamburger */}
|
{/* Mobile Header with Hamburger */}
|
||||||
<div className="lg:hidden sticky top-0 bg-white border-b px-4 py-3 z-10">
|
<div className="lg:hidden sticky top-0 bg-white dark:bg-neutral-900 border-b dark:border-neutral-700 px-4 py-3 z-10">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSidebarOpen(true)}
|
onClick={() => setSidebarOpen(true)}
|
||||||
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-6 h-6 text-gray-600"
|
className="w-6 h-6 text-gray-600 dark:text-neutral-400"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ const UserDetailLayout: FC = (): ReactElement => {
|
|||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen bg-gray-50">
|
<div className="flex min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
|
||||||
|
|
||||||
<div className="flex-1 flex flex-col overflow-auto">
|
<div className="flex-1 flex flex-col overflow-auto">
|
||||||
{/* Mobile Header with Hamburger */}
|
{/* Mobile Header with Hamburger */}
|
||||||
<div className="lg:hidden bg-white border-b px-4 py-3 flex items-center">
|
<div className="lg:hidden bg-white dark:bg-neutral-900 border-b dark:border-neutral-700 px-4 py-3 flex items-center">
|
||||||
<button
|
<button
|
||||||
onClick={() => setSidebarOpen(true)}
|
onClick={() => setSidebarOpen(true)}
|
||||||
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
|
||||||
>
|
>
|
||||||
<svg className="w-6 h-6 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-6 h-6 text-gray-600 dark:text-neutral-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<h1 className="ml-3 text-lg font-bold text-gray-900">🏆 Hackathon</h1>
|
<h1 className="ml-3 text-lg font-bold text-gray-900 dark:text-white">Hackathon</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main className="flex-1 overflow-auto">
|
<main className="flex-1 overflow-auto">
|
||||||
|
|||||||
@@ -14,26 +14,26 @@ const UserProfilePage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-screen">
|
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<div className="text-gray-600">Loading user profile...</div>
|
<div className="text-gray-600 dark:text-neutral-400">Loading user profile...</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error || !user) {
|
if (error || !user) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
<div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
<h2 className="text-2xl font-bold text-gray-900 mb-4">User not found</h2>
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">User not found</h2>
|
||||||
{error && <p className="text-red-600 mb-4">{String(error)}</p>}
|
{error && <p className="text-red-600 dark:text-red-400 mb-4">{String(error)}</p>}
|
||||||
<Button onClick={() => navigate('/dashboard')}>Back to Dashboard</Button>
|
<Button onClick={() => navigate('/dashboard')}>Back to Dashboard</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="bg-white border-b">
|
<div className="bg-white dark:bg-neutral-900 border-b dark:border-neutral-700">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 md:py-6">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 md:py-6">
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="flex items-center space-x-3 md:space-x-4">
|
<div className="flex items-center space-x-3 md:space-x-4">
|
||||||
@@ -41,19 +41,19 @@ const UserProfilePage: FC = (): ReactElement => {
|
|||||||
<img
|
<img
|
||||||
src={user.avatar}
|
src={user.avatar}
|
||||||
alt={user.fullname}
|
alt={user.fullname}
|
||||||
className="w-16 h-16 md:w-20 md:h-20 rounded-full object-cover border-4 border-white shadow-lg"
|
className="w-16 h-16 md:w-20 md:h-20 rounded-full object-cover border-4 border-white dark:border-neutral-800 shadow-lg"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-16 h-16 md:w-20 md:h-20 rounded-full bg-gray-200 flex items-center justify-center border-4 border-white shadow-lg">
|
<div className="w-16 h-16 md:w-20 md:h-20 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center border-4 border-white dark:border-neutral-800 shadow-lg">
|
||||||
<span className="text-gray-500 text-2xl md:text-3xl">👤</span>
|
<span className="text-gray-500 dark:text-neutral-400 text-2xl md:text-3xl">👤</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl md:text-3xl font-bold text-gray-900">{user.fullname}</h1>
|
<h1 className="text-xl md:text-3xl font-bold text-gray-900 dark:text-white">{user.fullname}</h1>
|
||||||
<p className="text-sm md:text-base text-gray-600 mt-1">{user.email}</p>
|
<p className="text-sm md:text-base text-gray-600 dark:text-neutral-400 mt-1">{user.email}</p>
|
||||||
{user.location && (
|
{user.location && (
|
||||||
<div className="flex items-center space-x-4 mt-2">
|
<div className="flex items-center space-x-4 mt-2">
|
||||||
<span className="text-sm text-gray-500">📍 {user.location}</span>
|
<span className="text-sm text-gray-500 dark:text-neutral-500">📍 {user.location}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -71,21 +71,21 @@ const UserProfilePage: FC = (): ReactElement => {
|
|||||||
<div className="lg:col-span-2 space-y-4 md:space-y-6">
|
<div className="lg:col-span-2 space-y-4 md:space-y-6">
|
||||||
{/* About Section */}
|
{/* About Section */}
|
||||||
{user.bio && (
|
{user.bio && (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6 overflow-hidden">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-4 md:p-6 overflow-hidden">
|
||||||
<h2 className="text-lg md:text-xl font-bold text-gray-900 mb-3 md:mb-4">About</h2>
|
<h2 className="text-lg md:text-xl font-bold text-gray-900 dark:text-white mb-3 md:mb-4">About</h2>
|
||||||
<p className="text-gray-700 break-all overflow-wrap-anywhere">{user.bio}</p>
|
<p className="text-gray-700 dark:text-neutral-300 break-all overflow-wrap-anywhere">{user.bio}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Skills Section */}
|
{/* Skills Section */}
|
||||||
{user.skills && user.skills.length > 0 && (
|
{user.skills && user.skills.length > 0 && (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-4 md:p-6">
|
||||||
<h2 className="text-lg md:text-xl font-bold text-gray-900 mb-3 md:mb-4">Skills</h2>
|
<h2 className="text-lg md:text-xl font-bold text-gray-900 dark:text-white mb-3 md:mb-4">Skills</h2>
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
{user.skills.map((skill: string) => (
|
{user.skills.map((skill: string) => (
|
||||||
<span
|
<span
|
||||||
key={skill}
|
key={skill}
|
||||||
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium shadow-sm hover:bg-blue-700 transition-colors"
|
className="inline-flex items-center px-4 py-2 bg-blue-600 dark:bg-primary-600 text-white rounded-lg text-sm font-medium shadow-sm hover:bg-blue-700 dark:hover:bg-primary-700 transition-colors"
|
||||||
>
|
>
|
||||||
{skill}
|
{skill}
|
||||||
</span>
|
</span>
|
||||||
@@ -96,35 +96,35 @@ const UserProfilePage: FC = (): ReactElement => {
|
|||||||
|
|
||||||
{/* Teams Section */}
|
{/* Teams Section */}
|
||||||
{userTeams.length > 0 ? (
|
{userTeams.length > 0 ? (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-4 md:p-6">
|
||||||
<h2 className="text-lg md:text-xl font-bold text-gray-900 mb-3 md:mb-4">Teams ({userTeams.length})</h2>
|
<h2 className="text-lg md:text-xl font-bold text-gray-900 dark:text-white mb-3 md:mb-4">Teams ({userTeams.length})</h2>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
{userTeams.map((team: any) => (
|
{userTeams.map((team: any) => (
|
||||||
<Link key={team.id} to={'/teams/' + team.id} className="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow">
|
<Link key={team.id} to={'/teams/' + team.id} className="bg-white dark:bg-neutral-800 rounded-lg shadow-md dark:shadow-neutral-950/50 overflow-hidden hover:shadow-lg transition-shadow">
|
||||||
<img src={team.banner || '/images/banner-imphnen.png'} alt={team.name} className="w-full h-24 object-cover" />
|
<img src={team.banner || '/images/banner-imphnen.png'} alt={team.name} className="w-full h-24 object-cover" />
|
||||||
<div className="p-4">
|
<div className="p-4">
|
||||||
<div className="flex items-center space-x-3 mb-2">
|
<div className="flex items-center space-x-3 mb-2">
|
||||||
{team.logo && <img src={team.logo} alt={team.name} className="w-10 h-10 rounded-full object-cover" />}
|
{team.logo && <img src={team.logo} alt={team.name} className="w-10 h-10 rounded-full object-cover" />}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-bold text-gray-900 line-clamp-2">{team.name}</h3>
|
<h3 className="font-bold text-gray-900 dark:text-white line-clamp-2">{team.name}</h3>
|
||||||
<div className="text-sm text-gray-600 flex gap-2">
|
<div className="text-sm text-gray-600 dark:text-neutral-400 flex gap-2">
|
||||||
<p className="truncate flex-1 min-w-0">📍 {team.city}</p>
|
<p className="truncate flex-1 min-w-0">📍 {team.city}</p>
|
||||||
<p className="whitespace-nowrap shrink-0">👥 {team.members?.length || 0} members</p>
|
<p className="whitespace-nowrap shrink-0">👥 {team.members?.length || 0} members</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 text-sm line-clamp-2">{team.description}</p>
|
<p className="text-gray-600 dark:text-neutral-400 text-sm line-clamp-2">{team.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-4 md:p-6">
|
||||||
<h2 className="text-lg md:text-xl font-bold text-gray-900 mb-3 md:mb-4">Teams</h2>
|
<h2 className="text-lg md:text-xl font-bold text-gray-900 dark:text-white mb-3 md:mb-4">Teams</h2>
|
||||||
<div className="text-center py-8">
|
<div className="text-center py-8">
|
||||||
<div className="text-4xl mb-3">👥</div>
|
<div className="text-4xl mb-3">👥</div>
|
||||||
<p className="text-gray-600">Not in any team yet</p>
|
<p className="text-gray-600 dark:text-neutral-400">Not in any team yet</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -133,17 +133,17 @@ const UserProfilePage: FC = (): ReactElement => {
|
|||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="space-y-4 md:space-y-6">
|
<div className="space-y-4 md:space-y-6">
|
||||||
{/* Contact Info */}
|
{/* Contact Info */}
|
||||||
<div className="bg-white rounded-lg shadow-md p-4 md:p-6">
|
<div className="bg-white dark:bg-neutral-900 rounded-lg shadow-md dark:shadow-neutral-950/50 p-4 md:p-6">
|
||||||
<h3 className="text-base md:text-lg font-bold text-gray-900 mb-3 md:mb-4">Contact Information</h3>
|
<h3 className="text-base md:text-lg font-bold text-gray-900 dark:text-white mb-3 md:mb-4">Contact Information</h3>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-600">Email</p>
|
<p className="text-sm text-gray-600 dark:text-neutral-400">Email</p>
|
||||||
<p className="text-gray-900 font-medium">{user.email}</p>
|
<p className="text-gray-900 dark:text-white font-medium">{user.email}</p>
|
||||||
</div>
|
</div>
|
||||||
{user.location && (
|
{user.location && (
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm text-gray-600">Location</p>
|
<p className="text-sm text-gray-600 dark:text-neutral-400">Location</p>
|
||||||
<p className="text-gray-900 font-medium">{user.location}</p>
|
<p className="text-gray-900 dark:text-white font-medium">{user.location}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -70,15 +70,15 @@ export const CitySelect: FC<CitySelectProps> = ({
|
|||||||
}}
|
}}
|
||||||
onClick={handleInputClick}
|
onClick={handleInputClick}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={`w-full h-[42px] px-3 text-[15px] border rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
className={`w-full h-[42px] px-3 text-[15px] border rounded-md focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-neutral-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-neutral-500 ${
|
||||||
error ? 'border-red-500' : 'border-gray-300'
|
error ? 'border-red-500' : 'border-gray-300 dark:border-neutral-600'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
{value && !isOpen && (
|
{value && !isOpen && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleClearSelection}
|
onClick={handleClearSelection}
|
||||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 cursor-pointer"
|
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-neutral-500 hover:text-gray-600 cursor-pointer dark:hover:text-neutral-300"
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
@@ -88,15 +88,17 @@ export const CitySelect: FC<CitySelectProps> = ({
|
|||||||
{error && <p className="text-sm text-red-500 mt-1">{error}</p>}
|
{error && <p className="text-sm text-red-500 mt-1">{error}</p>}
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div className="absolute z-50 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg max-h-60 overflow-y-auto text-label2">
|
<div className="absolute z-50 w-full mt-1 bg-white dark:bg-neutral-800 border border-gray-300 dark:border-neutral-600 rounded-lg shadow-lg max-h-60 overflow-y-auto text-label2">
|
||||||
{filteredCities.length > 0 ? (
|
{filteredCities.length > 0 ? (
|
||||||
<ul className="py-1">
|
<ul className="py-1">
|
||||||
{filteredCities.map((city) => (
|
{filteredCities.map((city) => (
|
||||||
<li
|
<li
|
||||||
key={city}
|
key={city}
|
||||||
onClick={() => handleSelectCity(city)}
|
onClick={() => handleSelectCity(city)}
|
||||||
className={`px-3 py-2 cursor-pointer hover:bg-blue-50 ${
|
className={`px-3 py-2 cursor-pointer hover:bg-blue-50 dark:hover:bg-blue-900/30 ${
|
||||||
value === city ? 'bg-blue-100 text-blue-700' : ''
|
value === city
|
||||||
|
? 'bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-400'
|
||||||
|
: 'text-gray-900 dark:text-neutral-200'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{city}
|
{city}
|
||||||
@@ -104,7 +106,7 @@ export const CitySelect: FC<CitySelectProps> = ({
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<div className="px-3 py-2 text-gray-500 text-sm">
|
<div className="px-3 py-2 text-gray-500 dark:text-neutral-400 text-sm">
|
||||||
No cities found
|
No cities found
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FC, useState, useEffect } from 'react';
|
import { FC, useEffect } from 'react';
|
||||||
import { Link, useLocation } from 'react-router';
|
import { Link, useLocation } from 'react-router';
|
||||||
import {
|
import {
|
||||||
useMyTeams,
|
useMyTeams,
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from '@imphnen-frontend-service/service';
|
} from '@imphnen-frontend-service/service';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import { useTheme } from './theme-provider';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
@@ -26,6 +27,7 @@ export const Sidebar: FC<SidebarProps> = ({ isOpen = true, onClose }) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { session, clearSession } = useAuthStore();
|
const { session, clearSession } = useAuthStore();
|
||||||
const { data: teamsData } = useMyTeams();
|
const { data: teamsData } = useMyTeams();
|
||||||
|
const { theme, setTheme, resolvedTheme } = useTheme();
|
||||||
|
|
||||||
const user = session?.user;
|
const user = session?.user;
|
||||||
const myTeams = teamsData?.data || [];
|
const myTeams = teamsData?.data || [];
|
||||||
@@ -94,18 +96,84 @@ export const Sidebar: FC<SidebarProps> = ({ isOpen = true, onClose }) => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const cycleTheme = () => {
|
||||||
|
if (theme === 'light') setTheme('dark');
|
||||||
|
else if (theme === 'dark') setTheme('system');
|
||||||
|
else setTheme('light');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getThemeIcon = () => {
|
||||||
|
if (theme === 'system') {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (resolvedTheme === 'dark') {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getThemeLabel = () => {
|
||||||
|
if (theme === 'system') return 'System';
|
||||||
|
if (theme === 'dark') return 'Dark';
|
||||||
|
return 'Light';
|
||||||
|
};
|
||||||
|
|
||||||
const sidebarContent = (
|
const sidebarContent = (
|
||||||
<div className="w-64 bg-white border-r min-h-screen flex flex-col">
|
<div className="w-64 bg-white dark:bg-neutral-900 border-r dark:border-neutral-700 min-h-screen flex flex-col">
|
||||||
{/* Logo / Brand with Close Button */}
|
{/* Logo / Brand with Close Button */}
|
||||||
<div className="p-6 flex items-center justify-between">
|
<div className="p-6 flex items-center justify-between border-b dark:border-neutral-700">
|
||||||
<h1 className="text-xl font-bold text-gray-900">Hackathon</h1>
|
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
|
||||||
|
Hackathon
|
||||||
|
</h1>
|
||||||
{onClose && (
|
{onClose && (
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="lg:hidden p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
className="lg:hidden p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5 text-gray-500"
|
className="w-5 h-5 text-gray-500 dark:text-neutral-400"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
@@ -122,7 +190,7 @@ export const Sidebar: FC<SidebarProps> = ({ isOpen = true, onClose }) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* User Info */}
|
{/* User Info */}
|
||||||
<div className="p-4 border-y">
|
<div className="p-4 border-y dark:border-neutral-700">
|
||||||
<div className="flex items-center space-x-3">
|
<div className="flex items-center space-x-3">
|
||||||
{user?.avatar ? (
|
{user?.avatar ? (
|
||||||
<img
|
<img
|
||||||
@@ -136,15 +204,17 @@ export const Sidebar: FC<SidebarProps> = ({ isOpen = true, onClose }) => {
|
|||||||
icon="ic:baseline-person"
|
icon="ic:baseline-person"
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
className="text-gray-400"
|
className="text-gray-400 dark:text-neutral-400"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium text-gray-900 truncate">
|
<p className="text-sm font-medium text-gray-900 dark:text-white truncate">
|
||||||
{user?.fullname || user?.email?.split('@')[0] || 'User'}
|
{user?.fullname || user?.email?.split('@')[0] || 'User'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500 truncate">{user?.email}</p>
|
<p className="text-xs text-gray-500 dark:text-neutral-400 truncate">
|
||||||
|
{user?.email}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -162,8 +232,8 @@ export const Sidebar: FC<SidebarProps> = ({ isOpen = true, onClose }) => {
|
|||||||
to={item.path}
|
to={item.path}
|
||||||
className={`flex items-center space-x-3 px-4 py-3 rounded-lg transition-colors ${
|
className={`flex items-center space-x-3 px-4 py-3 rounded-lg transition-colors ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-primary-50 text-primary-600 font-medium'
|
? 'bg-primary-50 dark:bg-blue-900/30 text-primary-600 dark:text-blue-400 font-medium'
|
||||||
: 'text-gray-700 hover:bg-gray-100'
|
: 'text-gray-700 dark:text-neutral-300 hover:bg-gray-100 dark:hover:bg-neutral-800'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
@@ -175,11 +245,18 @@ export const Sidebar: FC<SidebarProps> = ({ isOpen = true, onClose }) => {
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Logout Button */}
|
{/* Theme Toggle & Logout */}
|
||||||
<div className="p-4 border-t">
|
<div className="p-4 border-t dark:border-neutral-700 space-y-2">
|
||||||
|
<button
|
||||||
|
onClick={cycleTheme}
|
||||||
|
className="flex items-center space-x-3 px-4 py-3 w-full rounded-lg text-gray-700 dark:text-neutral-300 hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors"
|
||||||
|
>
|
||||||
|
{getThemeIcon()}
|
||||||
|
<span>{getThemeLabel()}</span>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="flex items-center space-x-3 px-4 py-3 w-full rounded-lg text-red-600 hover:bg-red-50 transition-colors cursor-pointer"
|
className="flex items-center space-x-3 px-4 py-3 w-full rounded-lg text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors cursor-pointer"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon="heroicons:arrow-right-end-on-rectangle"
|
icon="heroicons:arrow-right-end-on-rectangle"
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState, FC, ReactNode } from 'react';
|
||||||
|
|
||||||
|
type Theme = 'light' | 'dark' | 'system';
|
||||||
|
|
||||||
|
interface ThemeContextType {
|
||||||
|
theme: Theme;
|
||||||
|
setTheme: (theme: Theme) => void;
|
||||||
|
resolvedTheme: 'light' | 'dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'hackathon-theme';
|
||||||
|
|
||||||
|
function getSystemTheme(): 'light' | 'dark' {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
return 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ThemeProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
defaultTheme?: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ThemeProvider: FC<ThemeProviderProps> = ({ children, defaultTheme = 'system' }) => {
|
||||||
|
const [theme, setThemeState] = useState<Theme>(() => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null;
|
||||||
|
return stored || defaultTheme;
|
||||||
|
}
|
||||||
|
return defaultTheme;
|
||||||
|
});
|
||||||
|
|
||||||
|
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>(() => {
|
||||||
|
if (theme === 'system') {
|
||||||
|
return getSystemTheme();
|
||||||
|
}
|
||||||
|
return theme;
|
||||||
|
});
|
||||||
|
|
||||||
|
const setTheme = (newTheme: Theme) => {
|
||||||
|
setThemeState(newTheme);
|
||||||
|
localStorage.setItem(STORAGE_KEY, newTheme);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = document.documentElement;
|
||||||
|
|
||||||
|
// Calculate the resolved theme
|
||||||
|
const resolved = theme === 'system' ? getSystemTheme() : theme;
|
||||||
|
setResolvedTheme(resolved);
|
||||||
|
|
||||||
|
// Apply dark class to root
|
||||||
|
if (resolved === 'dark') {
|
||||||
|
root.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
root.classList.remove('dark');
|
||||||
|
}
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
// Listen for system theme changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (theme !== 'system') return;
|
||||||
|
|
||||||
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
|
||||||
|
const handleChange = (e: MediaQueryListEvent) => {
|
||||||
|
const newTheme = e.matches ? 'dark' : 'light';
|
||||||
|
setResolvedTheme(newTheme);
|
||||||
|
|
||||||
|
const root = document.documentElement;
|
||||||
|
if (newTheme === 'dark') {
|
||||||
|
root.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
root.classList.remove('dark');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mediaQuery.addEventListener('change', handleChange);
|
||||||
|
return () => mediaQuery.removeEventListener('change', handleChange);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ theme, setTheme, resolvedTheme }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const context = useContext(ThemeContext);
|
||||||
|
if (context === undefined) {
|
||||||
|
throw new Error('useTheme must be used within a ThemeProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ThemeProvider;
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { useTheme } from './theme-provider';
|
||||||
|
|
||||||
|
interface ThemeToggleProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sun icon for light mode
|
||||||
|
const SunIcon = () => (
|
||||||
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Moon icon for dark mode
|
||||||
|
const MoonIcon = () => (
|
||||||
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
// System icon (monitor)
|
||||||
|
const SystemIcon = () => (
|
||||||
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ThemeToggle: FC<ThemeToggleProps> = ({ className = '' }) => {
|
||||||
|
const { theme, setTheme, resolvedTheme } = useTheme();
|
||||||
|
|
||||||
|
const cycleTheme = () => {
|
||||||
|
if (theme === 'light') setTheme('dark');
|
||||||
|
else if (theme === 'dark') setTheme('system');
|
||||||
|
else setTheme('light');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getThemeLabel = () => {
|
||||||
|
if (theme === 'system') return 'System';
|
||||||
|
return theme === 'dark' ? 'Dark' : 'Light';
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderIcon = () => {
|
||||||
|
if (theme === 'system') {
|
||||||
|
return <SystemIcon />;
|
||||||
|
}
|
||||||
|
return resolvedTheme === 'dark' ? <MoonIcon /> : <SunIcon />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={cycleTheme}
|
||||||
|
className={`p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-neutral-800 transition-colors cursor-pointer text-gray-600 dark:text-neutral-400 ${className}`}
|
||||||
|
title={`Theme: ${getThemeLabel()}`}
|
||||||
|
aria-label={`Current theme: ${getThemeLabel()}. Click to change.`}
|
||||||
|
>
|
||||||
|
{renderIcon()}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeToggle;
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
@source "../../../libs/ui/**/*.{ts,tsx}";
|
@source "../../../libs/ui/**/*.{ts,tsx}";
|
||||||
|
|
||||||
|
/* Enable class-based dark mode (using .dark class on html element) */
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-primary-50: #f0f8ff;
|
--color-primary-50: #f0f8ff;
|
||||||
--color-primary-100: #e1f0fd;
|
--color-primary-100: #e1f0fd;
|
||||||
@@ -20,12 +23,12 @@
|
|||||||
--color-neutral-200: #d1d1d1;
|
--color-neutral-200: #d1d1d1;
|
||||||
--color-neutral-300: #b0b0b0;
|
--color-neutral-300: #b0b0b0;
|
||||||
--color-neutral-400: #888888;
|
--color-neutral-400: #888888;
|
||||||
--color-neutral-500: #6d6d6d;
|
--color-neutral-500: #6e6a86;
|
||||||
--color-neutral-600: #5d5d5d;
|
--color-neutral-600: #56526e;
|
||||||
--color-neutral-700: #4f4f4f;
|
--color-neutral-700: #44415a;
|
||||||
--color-neutral-800: #454545;
|
--color-neutral-800: #393552;
|
||||||
--color-neutral-900: #3d3d3d;
|
--color-neutral-900: #2a273f;
|
||||||
--color-neutral-950: #2b2b2b;
|
--color-neutral-950: #232136;
|
||||||
|
|
||||||
--color-success-100: #e0fbd8;
|
--color-success-100: #e0fbd8;
|
||||||
--color-success-200: #bcf8b0;
|
--color-success-200: #bcf8b0;
|
||||||
@@ -108,6 +111,14 @@
|
|||||||
border-color: var(--color-neutral-200, currentColor);
|
border-color: var(--color-neutral-200, currentColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark *,
|
||||||
|
.dark ::after,
|
||||||
|
.dark ::before,
|
||||||
|
.dark ::backdrop,
|
||||||
|
.dark ::file-selector-button {
|
||||||
|
border-color: var(--color-neutral-700, currentColor);
|
||||||
|
}
|
||||||
|
|
||||||
/* Custom scrollbar */
|
/* Custom scrollbar */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
@@ -118,19 +129,41 @@
|
|||||||
background: var(--color-neutral-50);
|
background: var(--color-neutral-50);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark ::-webkit-scrollbar-track {
|
||||||
|
background: var(--color-neutral-900);
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #cccccc;
|
background: #cccccc;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark ::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--color-neutral-600);
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--color-neutral-300);
|
background: var(--color-neutral-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dark ::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--color-neutral-500);
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
font-family: 'Bai Jamjuree', sans-serif;
|
font-family: 'Bai Jamjuree', sans-serif;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode base styles */
|
||||||
|
.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark body {
|
||||||
|
background-color: var(--color-neutral-950);
|
||||||
|
color: var(--color-neutral-100);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
QueryProvider,
|
QueryProvider,
|
||||||
} from '@imphnen-frontend-service/utils';
|
} from '@imphnen-frontend-service/utils';
|
||||||
import { Toaster } from 'sonner';
|
import { Toaster } from 'sonner';
|
||||||
|
import { ThemeProvider } from './components/theme-provider';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
const files = import.meta.glob('./app/**/*(page|layout).tsx');
|
||||||
@@ -36,11 +37,13 @@ if (!rootElement) throw new Error('Failed to find the root element');
|
|||||||
|
|
||||||
createRoot(rootElement).render(
|
createRoot(rootElement).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryProvider>
|
<ThemeProvider defaultTheme="system">
|
||||||
<ModalLoginProvider>
|
<QueryProvider>
|
||||||
<Toaster position="top-right" />
|
<ModalLoginProvider>
|
||||||
<RouterProvider router={router} />
|
<Toaster position="top-right" richColors />
|
||||||
</ModalLoginProvider>
|
<RouterProvider router={router} />
|
||||||
</QueryProvider>
|
</ModalLoginProvider>
|
||||||
|
</QueryProvider>
|
||||||
|
</ThemeProvider>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -312,9 +312,48 @@ export const useManageMember = (teamId: string) => {
|
|||||||
|
|
||||||
export const useRemoveMember = (teamId: string) => {
|
export const useRemoveMember = (teamId: string) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { session } = useAuthStore();
|
||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (userId: string) => teamsApi.removeMember(teamId, userId),
|
mutationFn: async (userId: string) => {
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
throw new Error('You must be logged in to remove a member');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the current user is the team leader
|
||||||
|
const { data: team, error: teamError } = await supabase
|
||||||
|
.from('teams')
|
||||||
|
.select('leader_id')
|
||||||
|
.eq('id', teamId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (teamError || !team) {
|
||||||
|
throw new Error('Team not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (team.leader_id !== session.user.id) {
|
||||||
|
throw new Error('Only the team leader can remove members');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot remove the leader
|
||||||
|
if (userId === team.leader_id) {
|
||||||
|
throw new Error('Cannot remove the team leader');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the team member record
|
||||||
|
const { error: deleteError } = await supabase
|
||||||
|
.from('team_members')
|
||||||
|
.delete()
|
||||||
|
.eq('team_id', teamId)
|
||||||
|
.eq('user_id', userId);
|
||||||
|
|
||||||
|
if (deleteError) {
|
||||||
|
console.error('Failed to remove member:', deleteError);
|
||||||
|
throw new Error(deleteError.message || 'Failed to remove member');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: teamKeys.members(teamId) });
|
queryClient.invalidateQueries({ queryKey: teamKeys.members(teamId) });
|
||||||
queryClient.invalidateQueries({ queryKey: teamKeys.detail(teamId) });
|
queryClient.invalidateQueries({ queryKey: teamKeys.detail(teamId) });
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ type TButtonProps = DetailedHTMLProps<
|
|||||||
const variantClasses: Record<TButtonVariant, string> = {
|
const variantClasses: Record<TButtonVariant, string> = {
|
||||||
primary: 'bg-primary-500 hover:bg-primary-600 text-white shadow-md',
|
primary: 'bg-primary-500 hover:bg-primary-600 text-white shadow-md',
|
||||||
secondary:
|
secondary:
|
||||||
'bg-white hover:text-primary-600 hover:bg-gray-50 text-primary-500 shadow-md',
|
'bg-white dark:bg-neutral-800 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-gray-50 dark:hover:bg-neutral-700 text-primary-500 dark:text-primary-400 shadow-md dark:shadow-neutral-900/50',
|
||||||
text: 'bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500',
|
text: 'bg-transparent hover:text-primary-600 dark:hover:text-primary-400 hover:bg-gray-50 dark:hover:bg-neutral-800 text-primary-500 dark:text-primary-400',
|
||||||
bordered:
|
bordered:
|
||||||
'border border-primary-500 hover:border-primary-600 bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500',
|
'border border-primary-500 dark:border-primary-400 hover:border-primary-600 dark:hover:border-primary-300 bg-transparent hover:text-primary-600 dark:hover:text-primary-300 hover:bg-gray-50 dark:hover:bg-neutral-800 text-primary-500 dark:text-primary-400',
|
||||||
success: 'bg-success-500 hover:bg-success-600 text-white shadow-md',
|
success: 'bg-success-500 hover:bg-success-600 text-white shadow-md',
|
||||||
danger: 'bg-danger-100 hover:bg-danger-200 text-danger-500 shadow-md',
|
danger: 'bg-danger-100 dark:bg-danger-500/20 hover:bg-danger-200 dark:hover:bg-danger-500/30 text-danger-500 shadow-md dark:shadow-neutral-900/50',
|
||||||
};
|
};
|
||||||
|
|
||||||
const sizeClasses: Record<TButtonSize, string> = {
|
const sizeClasses: Record<TButtonSize, string> = {
|
||||||
|
|||||||
Reference in New Issue
Block a user