Skip to content
Open
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
271 changes: 269 additions & 2 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@react-oauth/google": "^0.12.2",
"axios": "^1.9.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"core-js": "^3.39.0",
Expand All @@ -21,7 +23,9 @@
"next": "14.2.5",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.5.0",
"react-pdf": "^9.1.1",
"sonner": "^2.0.3",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7"
},
Expand Down
7 changes: 7 additions & 0 deletions src/actions/cookie_actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"use server";

import { cookies } from "next/headers";

export const getCookie = async (name: string) => {
return cookies().get(name)?.value ?? "";
};
50 changes: 50 additions & 0 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { getCookie } from "./cookie_actions";

export const createReferralCode = async (data?: any) => {
const token = await getCookie("token");
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_STUDENT_API_URL}/api/user/referral/create`,
{
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
// "x-api-key": process.env.NEXT_PUBLIC_STUDENT_API_KEY,
Cookie: `token=${token}`,
},
credentials: "include",
}
);

const responseData = await res.json();

return responseData;
} catch (error: unknown) {
return error
}
};

export const fetchReferralCode = async () => {
const token = await getCookie("token");
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_STUDENT_API_URL}/api/user/referral/get`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
// "x-api-key": process.env.NEXT_STUDENT_API_KEY,
Cookie: `token=${token}`,
},
credentials: "include",
}
);

const responseData = await res.json();

return responseData;
} catch (error: unknown) {
return error
}
};
31 changes: 31 additions & 0 deletions src/apiClient/apiClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import axios from 'axios';

const apiClient = axios.create({
baseURL: process.env.NEXT_PUBLIC_STUDENT_API_URL,
withCredentials: true,
headers: {
"Content-Type": "application/json",
"x-api-key": process.env.NEXT_PUBLIC_STUDENT_API_KEY,
},
});

apiClient.interceptors.request.use(
(config) => {
// config.headers.Authorization = `Bearer ${yourToken}`;
return config;
},
(error) => {
return Promise.reject(error);
}
);

apiClient.interceptors.response.use(
(response) => {
return response;
},
(error) => {
return Promise.reject(error);
}
);

export default apiClient;
70 changes: 70 additions & 0 deletions src/app/(auth)/_components/GoogleLoginButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { useGoogleLogin } from "@react-oauth/google";
import axios from "axios";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { toast } from "sonner";

interface GoogleLoginButtonProps {
onLoginSuccess?: () => void;
}

const GoogleLoginButton = ({ onLoginSuccess }: GoogleLoginButtonProps) => {
const router = useRouter();

const login = useGoogleLogin({
onSuccess: async (credentialResponse) => {
try {
const res = await axios.post("/api/google/auth", {
access_token: credentialResponse.access_token
},{
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
});

// Instead of using Redux, we can store user data in localStorage or cookies if needed
// Or simply use the response data directly

toast.success("Login success", {
description: res.data.message,
});

// Call the onLoginSuccess callback if provided
if (onLoginSuccess) {
onLoginSuccess();
}

} catch (error: any) {
console.error("Axios error:", error);
toast.error("Google login failed!", {
description: error.response?.data?.message || error.message,
});
}
},
onError: (error: any) => {
console.error("Google login error:", error);
toast.error("Google login failed!", {
description: error.message,
});
},
});


return (
<button
type="button"
onClick={() => login()}
className="w-full flex items-center justify-center gap-2 py-2 px-4 font-medium bg-white hover:bg-[#e2e8f0] border border-[#e2e8f0] rounded-[7px] text-[14px] md:text-[20px] lg:text-xl md:h-12 transition-colors">
<Image
src="/assets/icons/google-icon.svg"
alt="Sign in with Google"
width={17}
height={17}
/>
<span>Sign in with Google</span>
</button>
);
};

export default GoogleLoginButton;
19 changes: 19 additions & 0 deletions src/app/(auth)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Metadata } from "next";
import React from "react";
import { Mada as FontSans } from "next/font/google";
import "../globals.css";
import { cn } from "@/lib/utils";

const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans" });

export const metadata: Metadata = {
title: "Leadlly | Auth",
};

export default function AuthLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return <div className={cn("h-main-height")}>{children}</div>;
}
22 changes: 22 additions & 0 deletions src/app/api/auth/login/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// app/api/auth/login/route.ts
import { NextRequest, NextResponse } from 'next/server';

import apiClient from '@/apiClient/apiClient';

export async function POST(req: NextRequest) {
try {

const body = await req.json();

const response = await apiClient.post('/api/auth/login', body);

const { token, ...userData } = response.data;

const res = NextResponse.json(userData);
res.cookies.set('token', token, { httpOnly: true, path: '/', sameSite: 'strict' });

return res;
} catch (error: any) {
return NextResponse.json({ message: error.message }, { status: error.response?.status || 500 });
}
}
18 changes: 18 additions & 0 deletions src/app/api/auth/logout/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from "next/server";

export async function GET(req: NextRequest) {
try {
const res = NextResponse.json({ message: "Logged Out" });

res.cookies.set("token", "", {
httpOnly: true,
path: "/",
sameSite: "strict",
expires: new Date(0),
});

return res;
} catch (error: any) {
return NextResponse.json({ message: error.message }, { status: 500 });
}
}
22 changes: 22 additions & 0 deletions src/app/api/auth/verify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// app/api/auth/login/route.ts
import { NextRequest, NextResponse } from 'next/server';

import apiClient from '@/apiClient/apiClient';

export async function POST(req: NextRequest) {
try {

const body = await req.json();
console.log(body,"this is body")
const response = await apiClient.post('/api/auth/verify', body);

const { token, ...userData } = response.data;

const res = NextResponse.json(userData);
res.cookies.set('token', token, { httpOnly: true, path: '/', sameSite: 'strict' });

return res;
} catch (error: any) {
return NextResponse.json({ message: error.message }, { status: error.response?.status || 500 });
}
}
22 changes: 22 additions & 0 deletions src/app/api/google/auth/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// app/api/auth/login/route.ts
import { NextRequest, NextResponse } from 'next/server';

import apiClient from '@/apiClient/apiClient';

export async function POST(req: NextRequest) {
try {

const body = await req.json();

const response = await apiClient.post('/api/google/auth', body);

const { token, ...userData } = response.data;

const res = NextResponse.json(userData);
res.cookies.set('token', token, { httpOnly: true, path: '/', sameSite: 'strict' });

return res;
} catch (error: any) {
return NextResponse.json({ message: error.message }, { status: error.response?.status || 500 });
}
}
11 changes: 10 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { Montserrat } from "next/font/google";
import "./globals.css";
import { GoogleOAuthProvider } from "@react-oauth/google";


// Import Montserrat font
const montserrat = Montserrat({
Expand All @@ -22,7 +24,14 @@ export default function RootLayout({
return (
<html lang="en">
{/* Apply the Montserrat font */}
<body className={montserrat.className}>{children}</body>
<body className={montserrat.className}>
<GoogleOAuthProvider
clientId={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!}
>
{children}
</GoogleOAuthProvider>

</body>
</html>
);
}
Loading