feat(hackathon): enhance dark mode, city select, and auth-aware navbar

- Apply rose-pine dark theme across all pages for darker appearance
- Update Button component with dark mode variants
- Replace basic city select with searchable CitySelect on create team page
- Show Dashboard button in navbar when user is authenticated
- Apply dark mode styling to onboarding, team pages, and user profile

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Maulana Sodiqin
2025-11-26 16:06:23 +07:00
co-authored by Claude
parent 62780c8889
commit 6e6c361eb8
11 changed files with 288 additions and 271 deletions
+15 -15
View File
@@ -109,13 +109,13 @@ const UserOnboardingPage: FC = (): ReactElement => {
});
return (
<div className="flex flex-col justify-center items-center min-h-screen bg-gray-50 px-4 py-8">
<div className="bg-white w-full max-w-2xl p-8 rounded-xl shadow-lg">
<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 dark:bg-neutral-900 w-full max-w-2xl p-8 rounded-xl shadow-lg dark:shadow-neutral-950/50">
<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
</h1>
<p className="text-gray-600">
<p className="text-gray-600 dark:text-neutral-400">
Tell us more about yourself to get started
</p>
</div>
@@ -128,17 +128,17 @@ const UserOnboardingPage: FC = (): ReactElement => {
<img
src={avatarPreview}
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">
<span className="text-gray-400 text-4xl">👤</span>
<div className="w-32 h-32 rounded-full bg-gray-200 dark:bg-neutral-700 flex items-center justify-center">
<span className="text-gray-400 dark:text-neutral-500 text-4xl">👤</span>
</div>
)}
</div>
<div>
<label htmlFor="avatar" className="cursor-pointer">
<span className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 inline-block">
<span className="px-4 py-2 bg-blue-600 dark:bg-primary-600 text-white rounded-lg hover:bg-blue-700 dark:hover:bg-primary-700 inline-block">
{avatarPreview ? 'Change Photo' : 'Upload Photo'}
</span>
<input
@@ -149,7 +149,7 @@ const UserOnboardingPage: FC = (): ReactElement => {
onChange={handleAvatarChange}
/>
</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
</p>
</div>
@@ -165,7 +165,7 @@ const UserOnboardingPage: FC = (): ReactElement => {
{/* City */}
<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>
</label>
<Controller
@@ -184,7 +184,7 @@ const UserOnboardingPage: FC = (): ReactElement => {
{/* Role/Skills */}
<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">
Role / Skills
</label>
<Controller
@@ -207,9 +207,9 @@ const UserOnboardingPage: FC = (): ReactElement => {
: (field.value || []).filter((v) => v !== role);
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>
))}
</div>
@@ -220,8 +220,8 @@ const UserOnboardingPage: FC = (): ReactElement => {
{/* Bio */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
Bio <span className="text-gray-400">(Optional)</span>
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
Bio <span className="text-gray-400 dark:text-neutral-500">(Optional)</span>
</label>
<Controller
control={form.control}
+50 -24
View File
@@ -3,11 +3,14 @@ import { useState } from 'react';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { Icon } from '@iconify/react';
import { ThemeToggle } from '../components/theme-toggle';
import { useAuthStore } from '@imphnen-frontend-service/service';
export default function HomePage() {
const navigate = useNavigate();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [openFaq, setOpenFaq] = useState<number | null>(0);
const { session } = useAuthStore();
const isAuthenticated = !!session?.token;
const faqs = [
{
@@ -98,21 +101,33 @@ export default function HomePage() {
FAQ
</a>
<ThemeToggle />
<Button
onClick={() => navigate('/auth/login')}
size="sm"
variant="bordered"
className="rounded-lg text-base"
>
Masuk
</Button>
<Button
onClick={() => navigate('/auth/signup')}
size="sm"
className="rounded-lg text-base"
>
Daftar Sekarang
</Button>
{isAuthenticated ? (
<Button
onClick={() => navigate('/dashboard')}
size="sm"
className="rounded-lg text-base"
>
Dashboard
</Button>
) : (
<>
<Button
onClick={() => navigate('/auth/login')}
size="sm"
variant="bordered"
className="rounded-lg text-base"
>
Masuk
</Button>
<Button
onClick={() => navigate('/auth/signup')}
size="sm"
className="rounded-lg text-base"
>
Daftar Sekarang
</Button>
</>
)}
</div>
{/* Mobile Menu Button */}
<div className="flex items-center gap-2 md:hidden">
@@ -158,15 +173,26 @@ export default function HomePage() {
<a href="#faq" className="ms-3 text-gray-600 dark:text-neutral-400 hover:text-gray-900 dark:hover:text-white">
FAQ
</a>
<a href="#masuk" 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>
{isAuthenticated ? (
<button
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"
>
Dashboard
</button>
) : (
<>
<a href="#masuk" 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>
)}
@@ -63,14 +63,14 @@ const TeamChatPage: FC = (): ReactElement => {
};
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 */}
<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="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">Team Chat</h1>
<p className="text-gray-600 text-sm mt-0.5">{team?.name}</p>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Team Chat</h1>
<p className="text-gray-600 dark:text-neutral-400 text-sm mt-0.5">{team?.name}</p>
</div>
<Button variant="secondary" onClick={() => navigate(`/teams/${teamId}`)}>
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">
{isLoading ? (
<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>
) : messages && messages.length > 0 ? (
<div className="space-y-4">
@@ -118,18 +118,18 @@ const TeamChatPage: FC = (): ReactElement => {
<div className={`flex-1 ${isOwnMessage ? 'text-right' : 'text-left'}`}>
<div className={`inline-block ${isOwnMessage ? 'items-end' : 'items-start'}`}>
<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}
</span>
<span className="text-xs text-gray-500">
<span className="text-xs text-gray-500 dark:text-neutral-500">
{formatTime(msg.created_at)}
</span>
</div>
<div
className={`relative group rounded-2xl px-4 py-2.5 ${
isOwnMessage
? 'bg-blue-600 text-white'
: 'bg-white text-gray-900 border border-gray-200'
? 'bg-blue-600 dark:bg-primary-600 text-white'
: '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>
@@ -138,7 +138,7 @@ const TeamChatPage: FC = (): ReactElement => {
{canDelete && (
<button
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"
>
<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="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
</h3>
<p className="text-gray-600">
<p className="text-gray-600 dark:text-neutral-400">
Be the first to start the conversation!
</p>
</div>
@@ -170,7 +170,7 @@ const TeamChatPage: FC = (): ReactElement => {
</div>
{/* 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">
<form onSubmit={handleSendMessage} className="flex gap-3">
<input
@@ -178,7 +178,7 @@ const TeamChatPage: FC = (): ReactElement => {
value={message}
onChange={(e) => setMessage(e.target.value)}
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}
/>
<Button
@@ -46,17 +46,17 @@ const EditTeamPage: FC = (): ReactElement => {
if (isLoadingTeam) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-gray-600">Loading team...</div>
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
<div className="text-gray-600 dark:text-neutral-400">Loading team...</div>
</div>
);
}
if (!isLeader) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2>
<p className="text-gray-600 mb-4">Only the team leader can edit team information</p>
<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 dark:text-white mb-4">Access Denied</h2>
<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>
</div>
);
@@ -114,20 +114,20 @@ const EditTeamPage: FC = (): ReactElement => {
});
return (
<div className="min-h-screen bg-gray-50">
<div className="bg-white border-b">
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
<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">
<h1 className="text-3xl font-bold text-gray-900">Edit Team Info</h1>
<p className="text-gray-600 mt-1">Update your team details</p>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Edit Team Info</h1>
<p className="text-gray-600 dark:text-neutral-400 mt-1">Update your team details</p>
</div>
</div>
<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">
{/* Banner Upload */}
<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
</label>
{bannerPreview ? (
@@ -135,7 +135,7 @@ const EditTeamPage: FC = (): ReactElement => {
<img
src={bannerPreview}
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
type="button"
@@ -150,10 +150,10 @@ const EditTeamPage: FC = (): ReactElement => {
</button>
</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">
<p className="text-gray-500">Click to upload banner</p>
<p className="text-xs text-gray-400 mt-1">1200x400 recommended</p>
<p className="text-gray-500 dark:text-neutral-400">Click to upload banner</p>
<p className="text-xs text-gray-400 dark:text-neutral-500 mt-1">1200x400 recommended</p>
</div>
<input
type="file"
@@ -167,7 +167,7 @@ const EditTeamPage: FC = (): ReactElement => {
{/* Logo Upload */}
<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
</label>
<div className="flex items-center space-x-4">
@@ -175,11 +175,11 @@ const EditTeamPage: FC = (): ReactElement => {
<img
src={logoPreview}
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">
<span className="text-gray-400 text-3xl">👥</span>
<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 dark:text-neutral-500 text-3xl">👥</span>
</div>
)}
<div>
@@ -222,7 +222,7 @@ const EditTeamPage: FC = (): ReactElement => {
{/* City */}
<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
</label>
<Controller
@@ -232,7 +232,7 @@ const EditTeamPage: FC = (): ReactElement => {
<div>
<select
{...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>
{INDONESIAN_CITIES.map((city) => (
@@ -251,7 +251,7 @@ const EditTeamPage: FC = (): ReactElement => {
{/* Description */}
<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
</label>
<Controller
@@ -276,7 +276,7 @@ const EditTeamPage: FC = (): ReactElement => {
{/* Visibility */}
<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
</label>
<Controller
@@ -284,7 +284,7 @@ const EditTeamPage: FC = (): ReactElement => {
name="visibility"
render={({ field }) => (
<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
type="radio"
{...field}
@@ -293,13 +293,13 @@ const EditTeamPage: FC = (): ReactElement => {
className="mt-1"
/>
<div>
<p className="font-medium text-gray-900">Public</p>
<p className="text-sm text-gray-600">
<p className="font-medium text-gray-900 dark:text-white">Public</p>
<p className="text-sm text-gray-600 dark:text-neutral-400">
Team will be visible in Browse Teams
</p>
</div>
</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
type="radio"
{...field}
@@ -308,8 +308,8 @@ const EditTeamPage: FC = (): ReactElement => {
className="mt-1"
/>
<div>
<p className="font-medium text-gray-900">Private</p>
<p className="text-sm text-gray-600">
<p className="font-medium text-gray-900 dark:text-white">Private</p>
<p className="text-sm text-gray-600 dark:text-neutral-400">
Team hidden, invite-only
</p>
</div>
@@ -45,17 +45,17 @@ const ManageMembersPage: FC = (): ReactElement => {
// Show loading state while fetching team data
if (isLoadingTeam) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<p className="text-gray-600">Loading...</p>
<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) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2>
<p className="text-gray-600 mb-4">Only the team leader can manage members</p>
<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 dark:text-white mb-4">Access Denied</h2>
<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>
</div>
);
@@ -103,13 +103,13 @@ const ManageMembersPage: FC = (): ReactElement => {
};
return (
<div className="min-h-screen bg-gray-50">
<div className="bg-white border-b">
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
<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="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">Manage Members</h1>
<p className="text-gray-600 mt-1">{team?.name}</p>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Manage Members</h1>
<p className="text-gray-600 dark:text-neutral-400 mt-1">{team?.name}</p>
</div>
<div className="flex space-x-3">
<Button onClick={() => setShowInviteModal(true)}>
@@ -126,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">
{/* Join Requests */}
{joinRequests.length > 0 && (
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">
<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 dark:text-white mb-4">
Join Requests ({joinRequests.length})
</h2>
<div className="space-y-3">
{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-center space-x-3 flex-1">
{request.user.avatar ? (
@@ -142,17 +142,17 @@ const ManageMembersPage: FC = (): ReactElement => {
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">
<span className="text-gray-500">👤</span>
<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 dark:text-neutral-400">👤</span>
</div>
)}
<div className="flex-1">
<p className="font-medium text-gray-900">{request.user.fullname}</p>
<p className="text-sm text-gray-600">{request.user.email}</p>
<p className="font-medium text-gray-900 dark:text-white">{request.user.fullname}</p>
<p className="text-sm text-gray-600 dark:text-neutral-400">{request.user.email}</p>
{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 className="flex space-x-2 ml-4">
@@ -180,16 +180,16 @@ const ManageMembersPage: FC = (): ReactElement => {
)}
{/* Current Members */}
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-bold text-gray-900 mb-4">
<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 dark:text-white mb-4">
Current Members ({members.length})
</h2>
{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">
{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">
{member.user.avatar ? (
<img
@@ -198,24 +198,24 @@ const ManageMembersPage: FC = (): ReactElement => {
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">
<span className="text-gray-500">👤</span>
<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 dark:text-neutral-400">👤</span>
</div>
)}
<div>
<p className="font-medium text-gray-900">{member.user.fullname}</p>
<p className="text-sm text-gray-600">{member.user.email}</p>
<p className="font-medium text-gray-900 dark:text-white">{member.user.fullname}</p>
<p className="text-sm text-gray-600 dark:text-neutral-400">{member.user.email}</p>
{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">
{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
</span>
)}
{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
</span>
)}
@@ -239,8 +239,8 @@ const ManageMembersPage: FC = (): ReactElement => {
</div>
{/* Warning */}
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<p className="text-sm text-yellow-800">
<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 dark:text-yellow-200">
<strong>Note:</strong> Members cannot leave the team without your approval. Only you can remove members from the team.
</p>
</div>
@@ -248,22 +248,22 @@ const ManageMembersPage: FC = (): ReactElement => {
{/* Invite Member Modal */}
{showInviteModal && (
<div className="fixed inset-0 bg-black/20 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">
<h2 className="text-2xl font-bold text-gray-900 mb-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 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 dark:text-white mb-4">
Invite Member
</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.
</p>
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-6">
<p className="text-sm text-yellow-800">
<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 dark:text-yellow-200">
<strong>Important:</strong> The email you enter must match the GitHub email address the member uses to sign in.
</p>
</div>
<form onSubmit={handleInvite} className="space-y-4">
<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
</label>
<Input
@@ -15,18 +15,18 @@ const SubmissionViewPage: FC = (): ReactElement => {
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-gray-600">Loading submission...</div>
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
<div className="text-gray-600 dark:text-neutral-400">Loading submission...</div>
</div>
);
}
if (!submission) {
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>
<h2 className="text-2xl font-bold text-gray-900 mb-4">No Submission Yet</h2>
<p className="text-gray-600 mb-4">Your team hasn't submitted a project</p>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">No Submission Yet</h2>
<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>
</div>
);
@@ -43,13 +43,13 @@ const SubmissionViewPage: FC = (): ReactElement => {
: 'Not submitted';
return (
<div className="min-h-screen bg-gray-50">
<div className="bg-white border-b">
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
<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="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">Project Submission</h1>
<p className="text-gray-600 mt-1">{team?.name}</p>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Project Submission</h1>
<p className="text-gray-600 dark:text-neutral-400 mt-1">{team?.name}</p>
</div>
<Button variant="secondary" onClick={() => navigate(`/teams/${teamId}`)}>
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">
{/* 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">
<span className="text-4xl">✅</span>
<div>
<h3 className="font-bold text-green-900 text-lg">Project Submitted Successfully</h3>
<p className="text-green-700 text-sm">
<h3 className="font-bold text-green-900 dark:text-green-100 text-lg">Project Submitted Successfully</h3>
<p className="text-green-700 dark:text-green-300 text-sm">
Submitted on {submittedDate}
</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
</p>
</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 */}
<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>
@@ -86,9 +86,9 @@ const SubmissionViewPage: FC = (): ReactElement => {
<div className="p-8 space-y-6">
{/* Description */}
<div>
<h3 className="text-lg font-bold text-gray-900 mb-3">Project Description</h3>
<div className="bg-gray-50 rounded-lg p-4">
<p className="text-gray-700 whitespace-pre-wrap">{submission.description}</p>
<h3 className="text-lg font-bold text-gray-900 dark:text-white mb-3">Project Description</h3>
<div className="bg-gray-50 dark:bg-neutral-800 rounded-lg p-4">
<p className="text-gray-700 dark:text-neutral-300 whitespace-pre-wrap">{submission.description}</p>
</div>
</div>
@@ -96,12 +96,12 @@ const SubmissionViewPage: FC = (): ReactElement => {
<div className="grid gap-6 md:grid-cols-2">
{/* Repository */}
<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
href={submission.repository_url}
target="_blank"
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 className="break-all">{submission.repository_url}</span>
@@ -111,12 +111,12 @@ const SubmissionViewPage: FC = (): ReactElement => {
{/* Demo URL */}
{submission.demo_url && (
<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
href={submission.demo_url}
target="_blank"
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 className="break-all">{submission.demo_url}</span>
@@ -127,12 +127,12 @@ const SubmissionViewPage: FC = (): ReactElement => {
{/* Presentation URL */}
{submission.presentation_url && (
<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
href={submission.presentation_url}
target="_blank"
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 className="break-all">{submission.presentation_url}</span>
@@ -144,7 +144,7 @@ const SubmissionViewPage: FC = (): ReactElement => {
{/* Screenshots */}
{submission.screenshots && submission.screenshots.length > 0 && (
<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})
</h3>
<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
src={url}
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>
))}
@@ -168,22 +168,22 @@ const SubmissionViewPage: FC = (): ReactElement => {
)}
{/* Submission Info */}
<div className="bg-gray-50 rounded-lg p-4 border-t-4 border-blue-600">
<h3 className="text-sm font-bold text-gray-900 mb-2">Submission Information</h3>
<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 dark:text-white mb-2">Submission Information</h3>
<div className="grid gap-2 text-sm">
<div className="flex justify-between">
<span className="text-gray-600">Status:</span>
<span className="font-medium text-green-600">
<span className="text-gray-600 dark:text-neutral-400">Status:</span>
<span className="font-medium text-green-600 dark:text-green-400">
{submission.status === 'submitted' ? ' Submitted' : 'Draft'}
</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Submitted:</span>
<span className="font-medium text-gray-900">{submittedDate}</span>
<span className="text-gray-600 dark:text-neutral-400">Submitted:</span>
<span className="font-medium text-gray-900 dark:text-white">{submittedDate}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">Submission ID:</span>
<span className="font-medium text-gray-900 font-mono text-xs">
<span className="text-gray-600 dark:text-neutral-400">Submission ID:</span>
<span className="font-medium text-gray-900 dark:text-white font-mono text-xs">
{submission.id}
</span>
</div>
@@ -191,8 +191,8 @@ const SubmissionViewPage: FC = (): ReactElement => {
</div>
{/* Read-only Notice */}
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<p className="text-sm text-yellow-800">
<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 dark:text-yellow-200">
<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.
</p>
@@ -38,9 +38,9 @@ const SubmitProjectPage: FC = (): ReactElement => {
if (!isLeader) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2>
<p className="text-gray-600 mb-4">Only the team leader can submit projects</p>
<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 dark:text-white mb-4">Access Denied</h2>
<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>
</div>
);
@@ -48,10 +48,10 @@ const SubmitProjectPage: FC = (): ReactElement => {
if (hasSubmission) {
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>
<h2 className="text-2xl font-bold text-gray-900 mb-4">Project Already Submitted</h2>
<p className="text-gray-600 mb-4">Your team has already submitted a project</p>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">Project Already Submitted</h2>
<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">
<Button onClick={() => navigate(`/teams/${teamId}/submission`)}>
View Submission
@@ -95,22 +95,22 @@ const SubmitProjectPage: FC = (): ReactElement => {
});
return (
<div className="min-h-screen bg-gray-50">
<div className="bg-white border-b">
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
<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">
<h1 className="text-3xl font-bold text-gray-900">Submit Project</h1>
<p className="text-gray-600 mt-1">{team?.name}</p>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Submit Project</h1>
<p className="text-gray-600 dark:text-neutral-400 mt-1">{team?.name}</p>
</div>
</div>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* 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">
<span className="text-3xl"></span>
<div>
<h3 className="font-bold text-red-900 text-lg">IMPORTANT WARNING</h3>
<ul className="text-red-800 mt-2 space-y-1 text-sm">
<h3 className="font-bold text-red-900 dark:text-red-100 text-lg">IMPORTANT WARNING</h3>
<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> After submission, you CANNOT edit or change anything</li>
<li> Make sure all information is correct before submitting</li>
@@ -120,7 +120,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
</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) => {
e.preventDefault();
setShowConfirmModal(true);
@@ -135,7 +135,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
{/* Description */}
<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>
</label>
<Controller
@@ -186,8 +186,8 @@ const SubmitProjectPage: FC = (): ReactElement => {
{/* Screenshots */}
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">
Project Screenshots <span className="text-gray-400">(Optional)</span>
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300">
Project Screenshots <span className="text-gray-400 dark:text-neutral-500">(Optional)</span>
</label>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 mb-4">
{screenshots.map((url, index) => (
@@ -195,7 +195,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
<img
src={url}
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
type="button"
@@ -207,10 +207,10 @@ const SubmitProjectPage: FC = (): ReactElement => {
</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">
<p className="text-gray-500">Click to upload screenshots</p>
<p className="text-xs text-gray-400 mt-1">PNG, JPG up to 5MB each</p>
<p className="text-gray-500 dark:text-neutral-400">Click to upload screenshots</p>
<p className="text-xs text-gray-400 dark:text-neutral-500 mt-1">PNG, JPG up to 5MB each</p>
</div>
<input
type="file"
@@ -247,22 +247,22 @@ const SubmitProjectPage: FC = (): ReactElement => {
{/* Confirmation Modal */}
{showConfirmModal && (
<div className="fixed inset-0 bg-black bg-opacity-50 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="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center z-50 p-4">
<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-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
</h2>
<p className="text-red-600 font-medium">
<p className="text-red-600 dark:text-red-400 font-medium">
This action is IRREVERSIBLE!
</p>
</div>
<div className="bg-gray-50 rounded-lg p-4 mb-6">
<p className="text-sm text-gray-700 mb-3">
<div className="bg-gray-50 dark:bg-neutral-800 rounded-lg p-4 mb-6">
<p className="text-sm text-gray-700 dark:text-neutral-300 mb-3">
By clicking "Submit Project", you confirm that:
</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> You understand this can only be done once</li>
<li> You cannot edit after submission</li>
+35 -44
View File
@@ -5,7 +5,8 @@ import { useNavigate } from 'react-router';
import { useForm, Controller } from 'react-hook-form';
import { teamCreateSchema, TTeamCreateForm, useCreateTeam, ETeamVisibility, useUploadFile } from '@imphnen-frontend-service/service';
import { zodResolver } from '@hookform/resolvers/zod';
import INDONESIAN_CITIES from '../../../constants/cities';
import { CitySelect } from '../../../components/city-select';
const CreateTeamPage: FC = (): ReactElement => {
const navigate = useNavigate();
@@ -79,29 +80,29 @@ const CreateTeamPage: FC = (): ReactElement => {
});
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
{/* 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">
<h1 className="text-3xl font-bold text-gray-900">Create Your Team</h1>
<p className="text-gray-600 mt-1">Build your hackathon dream team</p>
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">Create Your Team</h1>
<p className="text-gray-600 dark:text-neutral-400 mt-1">Build your hackathon dream team</p>
</div>
</div>
<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">
{/* Banner Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Team Banner <span className="text-gray-400">(Optional)</span>
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
Team Banner <span className="text-gray-400 dark:text-neutral-500">(Optional)</span>
</label>
{bannerPreview ? (
<div className="relative">
<img
src={bannerPreview}
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
type="button"
@@ -115,10 +116,10 @@ const CreateTeamPage: FC = (): ReactElement => {
</button>
</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">
<p className="text-gray-500">Click to upload banner</p>
<p className="text-xs text-gray-400 mt-1">1200x400 recommended</p>
<p className="text-gray-500 dark:text-neutral-400">Click to upload banner</p>
<p className="text-xs text-gray-400 dark:text-neutral-500 mt-1">1200x400 recommended</p>
</div>
<input
type="file"
@@ -132,19 +133,19 @@ const CreateTeamPage: FC = (): ReactElement => {
{/* Logo Upload */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Team Logo <span className="text-gray-400">(Optional, but highly recommended)</span>
<label className="block text-sm font-medium text-gray-700 dark:text-neutral-300 mb-2">
Team Logo <span className="text-gray-400 dark:text-neutral-500">(Optional, but highly recommended)</span>
</label>
<div className="flex items-center space-x-4">
{logoPreview ? (
<img
src={logoPreview}
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">
<span className="text-gray-400 text-3xl">👥</span>
<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 dark:text-neutral-500 text-3xl">👥</span>
</div>
)}
<div>
@@ -186,36 +187,26 @@ const CreateTeamPage: FC = (): ReactElement => {
{/* City */}
<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>
</label>
<Controller
control={form.control}
name="city"
render={({ field, fieldState }) => (
<div>
<select
{...field}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<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>
<CitySelect
value={field.value}
onChange={field.onChange}
error={fieldState.error?.message}
placeholder="Search your city..."
/>
)}
/>
</div>
{/* Description */}
<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>
</label>
<Controller
@@ -239,7 +230,7 @@ const CreateTeamPage: FC = (): ReactElement => {
{/* Visibility */}
<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>
</label>
<Controller
@@ -247,7 +238,7 @@ const CreateTeamPage: FC = (): ReactElement => {
name="visibility"
render={({ field }) => (
<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
type="radio"
{...field}
@@ -256,13 +247,13 @@ const CreateTeamPage: FC = (): ReactElement => {
className="mt-1"
/>
<div>
<p className="font-medium text-gray-900">Public</p>
<p className="text-sm text-gray-600">
<p className="font-medium text-gray-900 dark:text-white">Public</p>
<p className="text-sm text-gray-600 dark:text-neutral-400">
Team will be visible in Browse Teams. Anyone can request to join.
</p>
</div>
</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
type="radio"
{...field}
@@ -271,8 +262,8 @@ const CreateTeamPage: FC = (): ReactElement => {
className="mt-1"
/>
<div>
<p className="font-medium text-gray-900">Private</p>
<p className="text-sm text-gray-600">
<p className="font-medium text-gray-900 dark:text-white">Private</p>
<p className="text-sm text-gray-600 dark:text-neutral-400">
Team is hidden from Browse Teams. Members can only join via invitation.
</p>
</div>
@@ -283,8 +274,8 @@ const CreateTeamPage: FC = (): ReactElement => {
</div>
{/* Warning */}
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
<p className="text-sm text-yellow-800">
<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 dark:text-yellow-200">
<strong>Note:</strong> As team leader, you cannot leave or join another team after creating this team.
</p>
</div>
+34 -34
View File
@@ -14,26 +14,26 @@ const UserProfilePage: FC = (): ReactElement => {
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="text-gray-600">Loading user profile...</div>
<div className="flex items-center justify-center min-h-screen bg-gray-50 dark:bg-neutral-950">
<div className="text-gray-600 dark:text-neutral-400">Loading user profile...</div>
</div>
);
}
if (error || !user) {
return (
<div className="flex flex-col items-center justify-center min-h-screen">
<h2 className="text-2xl font-bold text-gray-900 mb-4">User not found</h2>
{error && <p className="text-red-600 mb-4">{String(error)}</p>}
<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 dark:text-white mb-4">User not found</h2>
{error && <p className="text-red-600 dark:text-red-400 mb-4">{String(error)}</p>}
<Button onClick={() => navigate('/dashboard')}>Back to Dashboard</Button>
</div>
);
}
return (
<div className="min-h-screen bg-gray-50">
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
{/* 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="flex items-start justify-between">
<div className="flex items-center space-x-3 md:space-x-4">
@@ -41,19 +41,19 @@ const UserProfilePage: FC = (): ReactElement => {
<img
src={user.avatar}
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">
<span className="text-gray-500 text-2xl md:text-3xl">👤</span>
<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 dark:text-neutral-400 text-2xl md:text-3xl">👤</span>
</div>
)}
<div>
<h1 className="text-xl md:text-3xl font-bold text-gray-900">{user.fullname}</h1>
<p className="text-sm md:text-base text-gray-600 mt-1">{user.email}</p>
<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 dark:text-neutral-400 mt-1">{user.email}</p>
{user.location && (
<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>
@@ -71,21 +71,21 @@ const UserProfilePage: FC = (): ReactElement => {
<div className="lg:col-span-2 space-y-4 md:space-y-6">
{/* About Section */}
{user.bio && (
<div className="bg-white rounded-lg shadow-md 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>
<p className="text-gray-700 break-all overflow-wrap-anywhere">{user.bio}</p>
<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 dark:text-white mb-3 md:mb-4">About</h2>
<p className="text-gray-700 dark:text-neutral-300 break-all overflow-wrap-anywhere">{user.bio}</p>
</div>
)}
{/* Skills Section */}
{user.skills && user.skills.length > 0 && (
<div className="bg-white 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">Skills</h2>
<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 dark:text-white mb-3 md:mb-4">Skills</h2>
<div className="flex flex-wrap gap-3">
{user.skills.map((skill: string) => (
<span
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}
</span>
@@ -96,35 +96,35 @@ const UserProfilePage: FC = (): ReactElement => {
{/* Teams Section */}
{userTeams.length > 0 ? (
<div className="bg-white 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">Teams ({userTeams.length})</h2>
<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 dark:text-white mb-3 md:mb-4">Teams ({userTeams.length})</h2>
<div className="grid gap-4 md:grid-cols-2">
{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" />
<div className="p-4">
<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" />}
<div className="flex-1 min-w-0">
<h3 className="font-bold text-gray-900 line-clamp-2">{team.name}</h3>
<div className="text-sm text-gray-600 flex gap-2">
<h3 className="font-bold text-gray-900 dark:text-white line-clamp-2">{team.name}</h3>
<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="whitespace-nowrap shrink-0">👥 {team.members?.length || 0} members</p>
</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>
</Link>
))}
</div>
</div>
) : (
<div className="bg-white 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">Teams</h2>
<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 dark:text-white mb-3 md:mb-4">Teams</h2>
<div className="text-center py-8">
<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>
)}
@@ -133,17 +133,17 @@ const UserProfilePage: FC = (): ReactElement => {
{/* Sidebar */}
<div className="space-y-4 md:space-y-6">
{/* Contact Info */}
<div className="bg-white 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">Contact Information</h3>
<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 dark:text-white mb-3 md:mb-4">Contact Information</h3>
<div className="space-y-3">
<div>
<p className="text-sm text-gray-600">Email</p>
<p className="text-gray-900 font-medium">{user.email}</p>
<p className="text-sm text-gray-600 dark:text-neutral-400">Email</p>
<p className="text-gray-900 dark:text-white font-medium">{user.email}</p>
</div>
{user.location && (
<div>
<p className="text-sm text-gray-600">Location</p>
<p className="text-gray-900 font-medium">{user.location}</p>
<p className="text-sm text-gray-600 dark:text-neutral-400">Location</p>
<p className="text-gray-900 dark:text-white font-medium">{user.location}</p>
</div>
)}
</div>
+6 -6
View File
@@ -23,12 +23,12 @@
--color-neutral-200: #d1d1d1;
--color-neutral-300: #b0b0b0;
--color-neutral-400: #888888;
--color-neutral-500: #6d6d6d;
--color-neutral-600: #5d5d5d;
--color-neutral-700: #4f4f4f;
--color-neutral-800: #454545;
--color-neutral-900: #3d3d3d;
--color-neutral-950: #2b2b2b;
--color-neutral-500: #6e6a86;
--color-neutral-600: #56526e;
--color-neutral-700: #44415a;
--color-neutral-800: #393552;
--color-neutral-900: #2a273f;
--color-neutral-950: #232136;
--color-success-100: #e0fbd8;
--color-success-200: #bcf8b0;
+4 -4
View File
@@ -26,12 +26,12 @@ type TButtonProps = DetailedHTMLProps<
const variantClasses: Record<TButtonVariant, string> = {
primary: 'bg-primary-500 hover:bg-primary-600 text-white shadow-md',
secondary:
'bg-white hover:text-primary-600 hover:bg-gray-50 text-primary-500 shadow-md',
text: 'bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500',
'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 dark:hover:text-primary-400 hover:bg-gray-50 dark:hover:bg-neutral-800 text-primary-500 dark:text-primary-400',
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',
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> = {