Skip to content

fix: added proper state management and route verification of dashboard #117

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions app/auth/register/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { Suspense } from "react";
import SignupForm from "@/components/AuthComponent/SignupForm";
import SearchParamsWrapper from "@/components/AuthComponent/SearchParamsWrapper";


export default function SignupPage() {
return (
<div className="">
<SignupForm />
<Suspense fallback={<div>Loading...</div>}>
<SearchParamsWrapper>
<SignupForm />
</SearchParamsWrapper>
</Suspense>
</div>
);
}
}
27 changes: 27 additions & 0 deletions components/AuthComponent/SearchParamsWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import { useSearchParams } from "next/navigation";
import React from "react";

interface ChildProps {
searchParams?: ReturnType<typeof useSearchParams>;
}

export default function SearchParamsWrapper({
children
}: {
children: React.ReactNode
}) {
const searchParams = useSearchParams();

return (
<>
{React.Children.map(children, child => {
if (React.isValidElement<ChildProps>(child)) {
return React.cloneElement(child, { searchParams });
}
return child;
})}
</>
);
}
34 changes: 28 additions & 6 deletions components/AuthComponent/SignupForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import * as z from "zod";
Expand Down Expand Up @@ -32,17 +31,27 @@ import {
CardTitle,
} from "@/components/ui/card";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { useAuthStore } from "@/store/AuthStore/useAuthStore";
import AuthBottom from "./AuthBottom";
import LoadingButton from "./LoadingButton";
import { signupSchema } from "@/validations/validation";
import { useEffect } from "react"
import { useRouter, useSearchParams } from "next/navigation"
import { useAuthStore } from "@/store/AuthStore/useAuthStore"
import { useAuth } from "@/hooks/useAuth";

type SignupFormValues = z.infer<typeof signupSchema>;

export default function SignupForm() {
interface SignupFormProps {
searchParams?: URLSearchParams;
}

export default function SignupForm({searchParams}: SignupFormProps) {
const { isSigningUp, signup, signupError } = useAuthStore();
const router = useRouter();
const [showPassword, setShowPassword] = useState(false);
const { isSigningIn, signin, signinError } = useAuthStore()
const { user, loading } = useAuth()


const form = useForm<SignupFormValues>({
resolver: zodResolver(signupSchema),
Expand All @@ -55,9 +64,22 @@ export default function SignupForm() {
},
});

const onSubmit = (data: SignupFormValues) => {
signup(data, router);
};
useEffect(() => {
// If user is already authenticated, redirect to the intended URL or dashboard
if (user && !loading) {
const redirectTo = searchParams?.get('redirect') || '/dashboard'
router.push(redirectTo)
}
}, [user, loading, router, searchParams])

const onSubmit = async (data: SignupFormValues) => {
try {
signup(data, router);
// The redirect will be handled by the useEffect above when the user state updates
} catch (error) {
console.error('Sign in error:', error)
}
}

return (
<main className="flex min-h-screen items-center justify-center md:p-0 p-2">
Expand Down
58 changes: 49 additions & 9 deletions components/DashboardV2/V2Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";
import { ChevronsDown, Github, Menu } from "lucide-react";
import React from "react";
import React , { Suspense } from "react";
import {
Sheet,
SheetContent,
Expand All @@ -23,6 +23,9 @@ import Link from "next/link";
import Image from "next/image";
import { ToggleTheme } from "./ToggleTheme";
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
import { useAuth } from "@/hooks/useAuth";
import { signout } from "@/app/actions/action";
import { useRouter } from "next/navigation";

interface RouteProps {
href: string;
Expand Down Expand Up @@ -70,8 +73,41 @@ const featureList: FeatureProps[] = [
},
];

export const V2Navbar = () => {
export const NavigationContent = () => {
const [isOpen, setIsOpen] = React.useState(false);
const { user, loading } = useAuth();
const router = useRouter();
const [authState, setAuthState] = React.useState({ user, loading });

React.useEffect(() => {
setAuthState({ user, loading });
}, [user, loading]);

const handleSignOut = async () => {
await signout();
router.push("/auth/signin");
};

const renderAuthButtons = () => {
if (authState.loading) return null;

return authState.user ? (
<Button onClick={handleSignOut} className="rounded-2xl">
Sign Out
</Button>
) : (
<>
<Link href="/auth/register">
<Button className="rounded-2xl">Register</Button>
</Link>
<Link href="/auth/signin">
<Button className="rounded-2xl">Login</Button>
</Link>
</>
);
};


return (
<header className="shadow-inner w-[90%] md:w-[70%] lg:w-[75%] lg:max-w-screen-xl top-5 mx-auto sticky border border-secondary z-40 rounded-2xl flex justify-between items-center p-2 bg-secondary/30 backdrop-blur-md">
<Link href="/" className="flex items-center font-semibold">
Expand Down Expand Up @@ -127,6 +163,8 @@ export const V2Navbar = () => {
<Separator className="mb-2" />

<ToggleTheme />
<Separator className="mb-2" />
{renderAuthButtons()}
</SheetFooter>
</SheetContent>
</Sheet>
Expand Down Expand Up @@ -181,14 +219,16 @@ export const V2Navbar = () => {

<div className="hidden lg:flex gap-2">
<ToggleTheme />

<Link href="/auth/register">
<Button className="rounded-2xl">Register</Button>
</Link>
<Link href="/auth/signin">
<Button className="rounded-2xl">Login</Button>
</Link>
{renderAuthButtons()}
</div>
</header>
);
};

export const V2Navbar = () => {
return (
<Suspense fallback={<div>Loading...</div>}>
<NavigationContent />
</Suspense>
);
};
33 changes: 33 additions & 0 deletions hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useEffect, useState } from 'react';
import { createClient } from '@/utils/supabase/client';

import { User } from '@supabase/supabase-js';

export function useAuth() {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const supabase = createClient();

useEffect(() => {
// Check active sessions and sets the user
const getUser = async () => {
const { data: { user }, error } = await supabase.auth.getUser();
setUser(user);
setLoading(false);
};

getUser();

// Listen for changes on auth state (login, logout, etc)
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user ?? null);
setLoading(false);
});

return () => {
subscription.unsubscribe();
};
}, []);

return { user, loading };
}
19 changes: 12 additions & 7 deletions store/AuthStore/useAuthStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ interface authStore {
isSigningIn: boolean;
signinError: string | null;
signin: (signinMetaData: { email: string, password: string },router: any) => void;
logout: () => void;

logout: (router: any) => void;
signupError: string | null;
isSigningUp: boolean;
signup: (signupMetaData: User,router: any) => void;
user: User | null;

authUserLoading: boolean;
fetchAuthUser: () => void;
authUser: User | null;
Expand All @@ -32,7 +30,7 @@ export const useAuthStore = create<authStore>((set) => ({
isSigningIn: false,
signin: async (signinMetaData,router) => {
const supabase = createClient()
set({ isSigningIn: true })
set({ isSigningIn: true, signinError: null })
try {
const { data, error: loginError } =
await supabase.auth.signInWithPassword(signinMetaData);
Expand All @@ -44,7 +42,8 @@ export const useAuthStore = create<authStore>((set) => ({
}

if (data.session) {
router.push("/dashboard");
// Ensure we have a session before redirecting
await router.push('/dashboard');
} else {
throw new Error("Unable to retrieve session after login.");
}
Expand All @@ -56,8 +55,14 @@ export const useAuthStore = create<authStore>((set) => ({
}
},

logout: () => {
console.log('logout');
logout: async (router) => {
const supabase = createClient()
try {
await supabase.auth.signOut();
router.push('/auth/signin');
} catch (error) {
console.error('Logout error:', error);
}
},

signupError: null,
Expand Down