fix: google login redirect

This commit is contained in:
mehedi-hasan 2024-04-20 13:53:18 +06:00
parent d26c214047
commit 8a6a99a81b
13 changed files with 412 additions and 88 deletions

20
.env
View File

@ -1,20 +0,0 @@
DATABASE_URL=mongodb+srv://freshbitemehedi:freshbitemehedi2810@project1.lispgny.mongodb.net/Skilld
UPLOADTHING_SECRET=sk_live_938397c2e99554ab0bc541ba01114334d2ed523a0dbb9988870bcfc05d6ad9df
UPLOADTHING_APP_ID=h07pj3xjyv
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=2aqrEwXp57xY5dA6WN5akp3ceFDVg64Qu0Ufm/Dew8g=
# Go to github and setup the oauth configuration
# https://next-auth.js.org/providers/github#configuration
# https://github.com/settings/developers
GITHUB_ID=674c632636000fe44d40
GITHUB_SECRET=0ec0b70e902ae00021514e21427108275ef4244b
NEXT_PUBLIC_SUPABASE_URL=https://ckngsmtjumbnwksjutdo.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNrbmdzbXRqdW1ibndrc2p1dGRvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTAzNDIwNjMsImV4cCI6MjAyNTkxODA2M30.ZkykkQeBh8-Ej8_2KMdFPNelEF6jzAgb1Nqd64vwAvs
NEXT_PUBLIC_SITE_URL=http://localhost:3000

View File

@ -1,29 +1,49 @@
import { Metadata } from "next"; import { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import UserAuthForm from "@/components/forms/user-auth-form";
import { buttonVariants } from "@/components/ui/button"; import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import Image from "next/image";
import SignUpForm from "@/components/forms/signup-form";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Sign In | Skilld", title: "Sign Up | Skilld",
description: "Sign In to start your journey with Skilld.", description: "Sign Up to start your journey with Skilld.",
}; };
export default function AuthenticationPage() { export default function AuthenticationPage() {
return ( return (
<div className="relative h-screen flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0"> <div className="relative h-screen flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
<Link <Link
href="/examples/authentication" href="/signin"
className={cn( className={cn(
buttonVariants({ variant: "ghost" }), buttonVariants({ variant: "ghost" }),
"absolute right-4 hidden top-4 md:right-8 md:top-8", "absolute right-4 top-4 md:right-8 md:top-8 hidden",
)} )}
> >
Login Sign In
</Link> </Link>
<div className="relative hidden h-full flex-col bg-secondary p-10 dark:border-r lg:flex"> <div className="relative hidden h-full flex-col bg-secondary p-10 dark:border-r lg:flex">
<div className="absolute inset-0 bg-secondary" /> <div className="absolute inset-0 bg-secondary" />
<div className="relative z-20 flex items-center text-lg font-medium"> <Link
href="/dashboard"
className="relative z-20 flex items-center text-lg font-medium"
>
<Image
src="/Skilld AI Logos/Skilld Logo-colour on black.png"
alt="Skilld Logo"
width={100}
height={30}
className="hidden dark:block h-[25px] w-[80px] md:w-[100px] md:h-[30px] object-contain"
/>
<Image
src="/Skilld AI Logos/Skilld Logo-colour.png"
alt="Skilld Logo"
width={100}
height={30}
className="dark:hidden h-[25px] w-[80px] md:w-[100px] md:h-[30px] object-contain"
/>
</Link>
{/* <div className="relative z-20 flex items-center text-lg font-medium">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@ -37,7 +57,7 @@ export default function AuthenticationPage() {
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" /> <path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg> </svg>
Skilld Skilld
</div> </div> */}
<div className="relative z-20 mt-auto"> <div className="relative z-20 mt-auto">
<blockquote className="space-y-2"> <blockquote className="space-y-2">
<p className="text-lg text-muted-foreground"> <p className="text-lg text-muted-foreground">
@ -57,12 +77,19 @@ export default function AuthenticationPage() {
<h1 className="text-2xl font-semibold tracking-tight"> <h1 className="text-2xl font-semibold tracking-tight">
Create an account Create an account
</h1> </h1>
<p className="text-sm text-muted-foreground"> {/* <p className="text-sm text-muted-foreground">
Enter your email below to create your account Enter your email and password below to create your account
</p> </p> */}
</div> </div>
<UserAuthForm /> <SignUpForm />
<p className="px-8 text-center text-sm text-muted-foreground"> <div>
<p className="text-center text-sm text-muted-foreground">
Already have an account?{" "}
<Link href="/signin" className="underline hover:text-primary">
Sign In
</Link>
</p>
<p className="mt-2 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{" "} By clicking continue, you agree to our{" "}
<Link <Link
href="/terms" href="/terms"
@ -82,5 +109,6 @@ export default function AuthenticationPage() {
</div> </div>
</div> </div>
</div> </div>
</div>
); );
} }

View File

@ -0,0 +1,114 @@
import { Metadata } from "next";
import Link from "next/link";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import Image from "next/image";
import SignInForm from "@/components/forms/signin-form";
export const metadata: Metadata = {
title: "Sign In | Skilld",
description: "Sign In to restart your journey with Skilld.",
};
export default function AuthenticationPage() {
return (
<div className="relative h-screen flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
<Link
href="/"
className={cn(
buttonVariants({ variant: "ghost" }),
"absolute right-4 top-4 md:right-8 md:top-8 hidden",
)}
>
Sign Up
</Link>
<div className="relative hidden h-full flex-col bg-secondary p-10 dark:border-r lg:flex">
<div className="absolute inset-0 bg-secondary" />
<Link
href="/dashboard"
className="relative z-20 flex items-center text-lg font-medium"
>
<Image
src="/Skilld AI Logos/Skilld Logo-colour on black.png"
alt="Skilld Logo"
width={100}
height={30}
className="hidden dark:block h-[25px] w-[80px] md:w-[100px] md:h-[30px] object-contain"
/>
<Image
src="/Skilld AI Logos/Skilld Logo-colour.png"
alt="Skilld Logo"
width={100}
height={30}
className="dark:hidden h-[25px] w-[80px] md:w-[100px] md:h-[30px] object-contain"
/>
</Link>
{/* <div className="relative z-20 flex items-center text-lg font-medium">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 h-6 w-6"
>
<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3" />
</svg>
Skilld
</div> */}
<div className="relative z-20 mt-auto">
<blockquote className="space-y-2">
<p className="text-lg text-muted-foreground">
&ldquo;Empower your coding journey at Skilld. Access tutorials,
challenges, and expert-led courses to master programming languages
and tools. Join a supportive community for discussions and code
reviews. Elevate your skills and stay ahead in the tech world with
us!.&rdquo;
</p>
{/* <footer className="text-sm">Ak</footer> */}
</blockquote>
</div>
</div>
<div className="p-4 lg:p-8 h-full flex items-center">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<h1 className="text-2xl font-semibold tracking-tight">
Sign In To Your Account
</h1>
{/* <p className="text-sm text-muted-foreground">
Enter your email and password below to create your account
</p> */}
</div>
<SignInForm />
<div>
<p className="text-center text-sm text-muted-foreground">
Don&#39;t have an account?{" "}
<Link href="/" className="underline hover:text-primary">
Sign Up
</Link>
</p>
{/* <p className="mt-2 text-center text-sm text-muted-foreground">
By clicking continue, you agree to our{" "}
<Link
href="/terms"
className="underline underline-offset-4 hover:text-primary"
>
Terms of Service
</Link>{" "}
and{" "}
<Link
href="/privacy"
className="underline underline-offset-4 hover:text-primary"
>
Privacy Policy
</Link>
.
</p> */}
</div>
</div>
</div>
</div>
);
}

View File

@ -1,7 +1,9 @@
import Header from "@/components/layout/header"; import Header from "@/components/layout/header";
import Sidebar from "@/components/layout/sidebar"; import Sidebar from "@/components/layout/sidebar";
import StoreProvider from "@/lib/store-provider"; import StoreProvider from "@/lib/store-provider";
import { createClient } from "@/utils/supabase/server";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { redirect } from "next/navigation";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Dashboard | Skilld", title: "Dashboard | Skilld",
@ -9,11 +11,22 @@ export const metadata: Metadata = {
"Empower your coding journey at Skilld. Access tutorials, challenges, and expert-led courses to master programming languages and tools. Join a supportive community for discussions and code reviews. Elevate your skills and stay ahead in the tech world with us!", "Empower your coding journey at Skilld. Access tutorials, challenges, and expert-led courses to master programming languages and tools. Join a supportive community for discussions and code reviews. Elevate your skills and stay ahead in the tech world with us!",
}; };
export default function DashboardLayout({ export default async function DashboardLayout({
children, children,
}: { }: {
children: React.ReactNode; children: React.ReactNode;
}) { }) {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/signin");
}
return ( return (
<> <>
<StoreProvider lastUpdate={new Date().getTime()}> <StoreProvider lastUpdate={new Date().getTime()}>

View File

@ -1,13 +1,25 @@
import { Metadata } from "next"; import { Metadata } from "next";
import React from "react"; import React from "react";
import Header from "@/components/task/header"; import Header from "@/components/task/header";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Task | Skilld", title: "Task | Skilld",
description: "Skilld", description: "Skilld",
}; };
const Layout = ({ children }: { children: React.ReactNode }) => { const Layout = async ({ children }: { children: React.ReactNode }) => {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/signin");
}
return ( return (
<div className="grid grid-rows-[53px_1fr] min-h-screen"> <div className="grid grid-rows-[53px_1fr] min-h-screen">
<Header /> <Header />

View File

@ -0,0 +1,136 @@
"use client";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter, useSearchParams } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import * as z from "zod";
import GoogleSignInButton from "../google-auth-button";
import { createClient } from "@/utils/supabase/client";
import { useToast } from "../ui/use-toast";
const formSchema = z.object({
email: z.string().email({ message: "Enter a valid email address" }),
password: z
.string()
.min(6, { message: "Password should be minimum 6 characters" }),
});
type UserFormValue = z.infer<typeof formSchema>;
export default function SignInForm() {
const router = useRouter();
const searchParams = useSearchParams();
const callbackUrl = searchParams.get("callbackUrl");
const [loading, setLoading] = useState(false);
const { toast } = useToast();
// const defaultValues = {
// email: "demo@gmail.com",
// };
const form = useForm<UserFormValue>({
resolver: zodResolver(formSchema),
// defaultValues,
});
const onSubmit = async ({ email, password }: UserFormValue) => {
console.log(email, password);
const supabase = createClient();
setLoading(true);
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
// options: {
// emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
// },
});
console.log(data);
console.log(error);
// signIn("credentials", {
// email: data.email,
// callbackUrl: callbackUrl ?? "/dashboard",
// });
setLoading(false);
if (error) {
toast({ variant: "destructive", title: error.message });
return;
}
router.push("/dashboard");
};
return (
<>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-2 w-full"
>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="Enter your email..."
disabled={loading}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="Enter your password..."
disabled={loading}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button disabled={loading} className="ml-auto w-full" type="submit">
{loading ? "Signing In..." : "Sign In"}
</Button>
</form>
</Form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<GoogleSignInButton />
</>
);
}

View File

@ -16,6 +16,7 @@ import { useForm } from "react-hook-form";
import * as z from "zod"; import * as z from "zod";
import GoogleSignInButton from "../google-auth-button"; import GoogleSignInButton from "../google-auth-button";
import { createClient } from "@/utils/supabase/client"; import { createClient } from "@/utils/supabase/client";
import { useToast } from "../ui/use-toast";
const formSchema = z.object({ const formSchema = z.object({
email: z.string().email({ message: "Enter a valid email address" }), email: z.string().email({ message: "Enter a valid email address" }),
@ -26,11 +27,14 @@ const formSchema = z.object({
type UserFormValue = z.infer<typeof formSchema>; type UserFormValue = z.infer<typeof formSchema>;
export default function UserAuthForm() { export default function SignUpForm() {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const callbackUrl = searchParams.get("callbackUrl"); const callbackUrl = searchParams.get("callbackUrl");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { toast } = useToast();
// const defaultValues = { // const defaultValues = {
// email: "demo@gmail.com", // email: "demo@gmail.com",
// }; // };
@ -42,6 +46,8 @@ export default function UserAuthForm() {
const onSubmit = async ({ email, password }: UserFormValue) => { const onSubmit = async ({ email, password }: UserFormValue) => {
console.log(email, password); console.log(email, password);
const supabase = createClient(); const supabase = createClient();
setLoading(true);
const { data, error } = await supabase.auth.signUp({ const { data, error } = await supabase.auth.signUp({
email, email,
password, password,
@ -56,9 +62,15 @@ export default function UserAuthForm() {
// callbackUrl: callbackUrl ?? "/dashboard", // callbackUrl: callbackUrl ?? "/dashboard",
// }); // });
if (!error) { setLoading(false);
router.push("/dashboard");
if (error) {
toast({ variant: "destructive", title: error.message });
return;
} }
router.push("/dashboard");
}; };
return ( return (
@ -106,7 +118,7 @@ export default function UserAuthForm() {
/> />
<Button disabled={loading} className="ml-auto w-full" type="submit"> <Button disabled={loading} className="ml-auto w-full" type="submit">
Continue With Email {loading ? "Signing Up..." : "Sign Up"}
</Button> </Button>
</form> </form>
</Form> </Form>

View File

@ -17,6 +17,8 @@ export default function GoogleSignInButton() {
redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`, redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
}, },
}); });
console.log(error);
}; };
return ( return (

View File

@ -30,6 +30,8 @@ import {
User2Icon, User2Icon,
UserX2Icon, UserX2Icon,
X, X,
Upload,
BookCheck,
} from "lucide-react"; } from "lucide-react";
export type Icon = LucideIcon; export type Icon = LucideIcon;
@ -61,7 +63,9 @@ export const Icons = {
sun: SunMedium, sun: SunMedium,
moon: Moon, moon: Moon,
laptop: Laptop, laptop: Laptop,
upload: Upload,
shieldQuestion: ShieldQuestion, shieldQuestion: ShieldQuestion,
bookCheck: BookCheck,
gitHub: ({ ...props }: LucideProps) => ( gitHub: ({ ...props }: LucideProps) => (
<svg <svg
aria-hidden="true" aria-hidden="true"

View File

@ -4,8 +4,21 @@ import { MobileSidebar } from "./mobile-sidebar";
import { UserNav } from "./user-nav"; import { UserNav } from "./user-nav";
import Link from "next/link"; import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { createClient } from "@/utils/supabase/server";
import { redirect } from "next/navigation";
export default async function Header() {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) {
return redirect("/signin");
}
export default function Header() {
return ( return (
<div className="fixed top-0 left-0 right-0 supports-backdrop-blur:bg-background/60 border-b bg-background/95 backdrop-blur z-20"> <div className="fixed top-0 left-0 right-0 supports-backdrop-blur:bg-background/60 border-b bg-background/95 backdrop-blur z-20">
<nav className="h-14 flex items-center justify-between px-4"> <nav className="h-14 flex items-center justify-between px-4">
@ -31,8 +44,11 @@ export default function Header() {
<MobileSidebar /> <MobileSidebar />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-4">
<UserNav /> <UserNav
email={user?.email || "example@gmail.com"}
avatarUrl={user?.user_metadata?.avatar_url || ""}
/>
<ThemeToggle /> <ThemeToggle />
</div> </div>
</nav> </nav>

View File

@ -10,9 +10,9 @@ export default function Sidebar() {
<div className="space-y-4 py-4"> <div className="space-y-4 py-4">
<div className="px-3 py-2"> <div className="px-3 py-2">
<div className="space-y-1"> <div className="space-y-1">
<h2 className="mb-2 px-4 text-xl font-semibold tracking-tight"> {/* <h2 className="mb-2 px-4 text-xl font-semibold tracking-tight">
Overview Overview
</h2> </h2> */}
<DashboardNav items={navItems} /> <DashboardNav items={navItems} />
</div> </div>
</div> </div>

View File

@ -12,15 +12,22 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { createClient } from "@/utils/supabase/client"; import { createClient } from "@/utils/supabase/client";
import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
export function UserNav() { export function UserNav({
email,
avatarUrl,
}: {
email: string;
avatarUrl: string;
}) {
const router = useRouter(); const router = useRouter();
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full"> <Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8"> <Avatar className="h-8 w-8">
<AvatarImage src={""} alt={""} /> <AvatarImage src={avatarUrl} alt={"User avatar"} />
<AvatarFallback>CN</AvatarFallback> <AvatarFallback>CN</AvatarFallback>
</Avatar> </Avatar>
</Button> </Button>
@ -30,25 +37,25 @@ export function UserNav() {
<div className="flex flex-col space-y-1"> <div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">John Doe</p> <p className="text-sm font-medium leading-none">John Doe</p>
<p className="text-xs leading-none text-muted-foreground"> <p className="text-xs leading-none text-muted-foreground">
john@doe.com {email}
</p> </p>
</div> </div>
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem> <DropdownMenuItem>
Profile <Link href="/dashboard/profile">Profile</Link>
<DropdownMenuShortcut>P</DropdownMenuShortcut> <DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem> {/* <DropdownMenuItem>
Billing Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut> <DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem> </DropdownMenuItem> */}
<DropdownMenuItem> {/* <DropdownMenuItem>
Settings Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut> <DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem> </DropdownMenuItem> */}
<DropdownMenuItem>New Team</DropdownMenuItem> {/* <DropdownMenuItem>New Team</DropdownMenuItem> */}
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem <DropdownMenuItem

View File

@ -23,9 +23,9 @@ export const HEADING_LINK_ANCHOR = `before:content-['#'] before:absolute before:
export const navItems: NavItem[] = [ export const navItems: NavItem[] = [
{ {
title: "Dashboard", title: "Upload CV",
href: "/dashboard", href: "/dashboard",
icon: "dashboard", icon: "upload",
label: "Dashboard", label: "Dashboard",
}, },
{ {
@ -37,7 +37,7 @@ export const navItems: NavItem[] = [
{ {
title: "Your Task", title: "Your Task",
href: "/dashboard/task/react/1", href: "/dashboard/task/react/1",
icon: "shieldQuestion", icon: "bookCheck",
label: "Your Task", label: "Your Task",
}, },