Merge branch 'develop' into hackathon/fix-ui

This commit is contained in:
Hafid Nur
2025-11-26 16:38:22 +07:00
9 changed files with 19 additions and 185 deletions
@@ -1,89 +0,0 @@
import { FC, ReactElement, useEffect, useState } from 'react';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { useNavigate } from 'react-router';
import { useGitHubAuth } from '@imphnen-frontend-service/service';
import { useSession } from '@imphnen-frontend-service/utils';
import { GithubOutlined } from '@ant-design/icons';
const LoginPage: FC = (): ReactElement => {
console.log('[LoginPage] Rendering...');
const navigate = useNavigate();
const { signInWithGitHub } = useGitHubAuth();
const [isGithubLoading, setIsGithubLoading] = useState(false);
const { isAuthenticated } = useSession();
console.log('[LoginPage] isAuthenticated:', isAuthenticated);
useEffect(() => {
console.log('[LoginPage] useEffect - isAuthenticated:', isAuthenticated);
if (isAuthenticated) {
console.log('[LoginPage] Redirecting to dashboard...');
navigate('/dashboard');
}
}, [isAuthenticated, navigate]);
const handleGithubLogin = async () => {
try {
setIsGithubLoading(true);
console.log('[Login] Initiating GitHub OAuth...');
const result = await signInWithGitHub();
console.log('[Login] OAuth result:', result);
// Check if we got a redirect URL
if (result?.url) {
console.log('[Login] Redirecting to GitHub OAuth:', result.url);
// Supabase should handle the redirect automatically
// If we're still here after 2 seconds, manually redirect
setTimeout(() => {
if (result.url) {
globalThis.location.href = result.url;
}
}, 2000);
} else {
console.error('[Login] No OAuth URL returned');
setIsGithubLoading(false);
}
} catch (error) {
console.error('[Login] GitHub login failed:', error);
setIsGithubLoading(false);
}
};
return (
<div className="flex justify-center items-center min-h-screen bg-gray-50 px-4">
<div className="bg-white w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-100">
<div className="text-center mb-8">
<h2 className="text-3xl font-bold text-gray-900 mb-2">
Welcome to Hackathon
</h2>
<p className="text-gray-600">
Sign in with your GitHub account to join or create your hackathon team
</p>
</div>
<Button
className="w-full gap-2 py-3"
variant="secondary"
onClick={handleGithubLogin}
disabled={isGithubLoading}
type="button"
>
<GithubOutlined className="text-xl" />
<span className="font-semibold">
{isGithubLoading ? 'Connecting...' : 'Sign in with GitHub'}
</span>
</Button>
<div className="mt-6 text-center">
<p className="text-gray-500 text-sm">
By signing in, you agree to our Terms of Service and Privacy Policy
</p>
</div>
</div>
</div>
);
};
export default LoginPage;
+1 -11
View File
@@ -14,18 +14,14 @@ export default function RootLayout() {
const checkAuth = async () => {
const pathname = location.pathname;
console.log('[Layout] Checking auth for route:', pathname);
// Allow hackathon pages without checks
if (pathname.startsWith('/hackathons')) {
console.log('[Layout] Public route, allowing access');
setIsChecking(false);
return;
}
// Allow auth callback without checks
if (pathname === '/auth/callback') {
console.log('[Layout] Auth callback, allowing access');
setIsChecking(false);
return;
}
@@ -43,27 +39,23 @@ export default function RootLayout() {
if (isPublicAuthPage) {
// If already authenticated and not on password reset pages, redirect to dashboard
if (session && pathname !== '/auth/reset-password') {
console.log('[Layout] Already authenticated, redirecting to dashboard');
navigate('/dashboard', { replace: true });
setIsChecking(false);
return;
}
// Allow unauthenticated access
console.log('[Layout] Public auth page, allowing access');
setIsChecking(false);
return;
}
// Home page - allow everyone to view the landing page
if (pathname === '/') {
console.log('[Layout] Landing page, allowing access');
setIsChecking(false);
return;
}
// Require authentication for all other routes
if (!session) {
console.log('[Layout] No session, redirecting to login');
navigate('/auth/login', { replace: true });
setIsChecking(false);
return;
@@ -87,17 +79,15 @@ export default function RootLayout() {
const hasLocation = !!userData?.location;
if (!hasLocation) {
console.log('[Layout] User needs onboarding, redirecting');
navigate('/onboarding/user', { replace: true });
setIsChecking(false);
return;
}
} catch (error) {
console.error('[Layout] Unexpected error checking onboarding:', error);
// Silently handle error
}
}
console.log('[Layout] Auth check passed');
setIsChecking(false);
};
@@ -12,6 +12,7 @@ import {
useAuthStore,
} from '@imphnen-frontend-service/service';
import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner';
import { CitySelect } from '../../../components/city-select';
import { Icon } from '@iconify/react';
@@ -70,41 +71,34 @@ const UserOnboardingPage: FC = (): ReactElement => {
const onSubmit = form.handleSubmit(async (data) => {
try {
// console.log('[Onboarding] Starting submission...', data);
let avatarUrl = session?.user?.avatar || null;
// Upload avatar if a new file was selected
if (avatarFile) {
// console.log('[Onboarding] Uploading avatar...');
const uploadResult = await uploadAvatar(avatarFile);
avatarUrl = uploadResult.data.url;
// console.log('[Onboarding] Avatar uploaded:', avatarUrl);
}
// Update user in Supabase
// console.log('[Onboarding] Updating user in Supabase...');
const result = await updateUser({
await updateUser({
fullname: data.fullname,
avatar: avatarUrl,
location: data.location,
bio: data.bio,
skills: data.skills,
});
// console.log('[Onboarding] User updated successfully:', result);
// Wait a bit for the onSuccess handler to update localStorage
// The updateUser mutation's onSuccess handler updates the Zustand store and localStorage
await new Promise((resolve) => setTimeout(resolve, 100));
// console.log('[Onboarding] Navigating to dashboard...');
// Use window.location for a full page reload to ensure middleware sees updated localStorage
globalThis.location.href = '/dashboard';
} catch (error) {
// console.error('[Onboarding] Onboarding failed:', error);
alert(
`Onboarding failed: ${
error instanceof Error ? error.message : 'Unknown error'
}`
toast.error(
error instanceof Error
? error.message
: 'Onboarding failed. Please try again.'
);
}
});
+2 -2
View File
@@ -138,8 +138,8 @@ const ProfilePage: FC = (): ReactElement => {
const isLoading = isUpdating || isUploading;
return (
<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-md p-8 rounded-xl shadow-lg">
<div className="fixed inset-0 bg-black/30 dark:bg-black/50 backdrop-blur-sm flex flex-col justify-center items-center px-4 py-8 z-50 overflow-y-auto">
<div className="bg-white dark:bg-neutral-900 w-full max-w-md p-8 rounded-xl shadow-xl dark:shadow-neutral-950/50">
<div className="mb-6">
<div className="flex items-center justify-between mb-2">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
@@ -5,7 +5,8 @@ import { useNavigate, useParams } from 'react-router';
import { useForm, Controller } from 'react-hook-form';
import { teamUpdateSchema, TTeamUpdateForm, useUpdateTeam, useTeamById, ETeamVisibility, useUploadFile, useAuthStore } from '@imphnen-frontend-service/service';
import { zodResolver } from '@hookform/resolvers/zod';
import INDONESIAN_CITIES from '../../../../constants/cities';
import { CitySelect } from '../../../../components/city-select';
const EditTeamPage: FC = (): ReactElement => {
const { teamId } = useParams<{ teamId: string }>();
@@ -229,22 +230,12 @@ const EditTeamPage: FC = (): ReactElement => {
control={form.control}
name="city"
render={({ field, fieldState }) => (
<div>
<select
{...field}
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) => (
<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>
@@ -85,54 +85,6 @@ const TeamDashboardPage: FC = (): ReactElement => {
);
const canInvite = isLeader && members.length < MAX_TEAM_MEMBERS;
// Calculate total images to load
const totalImagesToLoad =
(team?.banner ? 1 : 0) +
(team?.logo ? 1 : 0) +
members.filter((m: any) => m.user?.avatar).length;
// Track image loading with timeout fallback
useEffect(() => {
if (!isLoadingTeam && !isLoadingMembers && team) {
if (totalImagesToLoad === 0) {
setImagesLoaded(true);
} else if (imageLoadCount >= totalImagesToLoad) {
setImagesLoaded(true);
}
}
}, [
isLoadingTeam,
isLoadingMembers,
team,
imageLoadCount,
totalImagesToLoad,
]);
// Fallback timeout - if images take too long, show content anyway
useEffect(() => {
if (!isLoadingTeam && !isLoadingMembers && team && !imagesLoaded) {
const timeout = setTimeout(() => {
setImagesLoaded(true);
}, 3000); // 3 second timeout
return () => clearTimeout(timeout);
}
}, [isLoadingTeam, isLoadingMembers, team, imagesLoaded]);
const handleImageLoad = () => {
setImageLoadCount((prev) => prev + 1);
};
// Also count error as loaded to prevent stuck
const handleImageError = () => {
setImageLoadCount((prev) => prev + 1);
};
console.log('Leader check:', {
currentUserId,
leaderId: team?.leader_id,
isLeader,
});
const handleInviteMember = async (e: React.FormEvent) => {
e.preventDefault();
if (!inviteEmail.trim() || isInviting) return;
-4
View File
@@ -37,8 +37,6 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
const url = new URL(request.url);
const pathname = url.pathname;
console.log('[Middleware] Checking route:', pathname);
// Get session from Supabase (authoritative source)
const { data: { session: supabaseSession }, error: sessionError } = await supabase.auth.getSession();
@@ -86,10 +84,8 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
let hasLocation = false;
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
console.log('[Middleware] Using cached onboarding status');
hasLocation = cached.hasLocation;
} else {
console.log('[Middleware] Fetching fresh onboarding status');
const { data: userData, error: userError } = await supabase
.from('users')
.select('location')
+1 -1
View File
@@ -56,7 +56,7 @@ export const Input: FC<TInputProps> = ({
};
const mergedClassName = cn(
`px-[12px] py-[8px] text-neutral-800 bg-white placeholder:text-neutral-300 border border-neutral-200 hover:border-blue-300 focus:outline-1 focus:outline-blue-500 rounded-md font-bai-jamjuree w-full ${
`px-[12px] py-[8px] text-neutral-800 dark:text-white bg-white dark:bg-neutral-800 placeholder:text-neutral-300 dark:placeholder:text-neutral-500 border border-neutral-200 dark:border-neutral-600 hover:border-blue-300 dark:hover:border-blue-500 focus:outline-1 focus:outline-blue-500 dark:focus:outline-primary-500 rounded-md font-bai-jamjuree w-full ${
widthform === 'standard' ? 'min-w-70' : ''
}`,
sizeClasses[size].textSize,
+1 -1
View File
@@ -38,7 +38,7 @@ export const Textarea: FC<TTextareaProps> = ({
...rest
}): ReactElement => {
const mergedClassName = cn(
'rounded-md border border-neutral-200 hover:border-blue-300 focus:outline-1 focus:outline-blue-500 px-[12px] py-[8px] invalid:border-danger-500 invalid:text-danger-500',
'rounded-md border border-neutral-200 dark:border-neutral-600 hover:border-blue-300 dark:hover:border-blue-500 focus:outline-1 focus:outline-blue-500 dark:focus:outline-primary-500 px-[12px] py-[8px] bg-white dark:bg-neutral-800 text-neutral-800 dark:text-white placeholder:text-neutral-300 dark:placeholder:text-neutral-500 invalid:border-danger-500 invalid:text-danger-500',
sizeClasses[size],
disabled && disabledClass,
error && errorClass,