diff --git a/package.json b/package.json index 78985a100..0af44e25a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "email:dev": "email dev --dir \"./src/email/templates\"", "email:build": "email build --dir \"./src/email/templates\"", "email:start": "email start", - "typecheck": "tsc --project tsconfig.json --noEmit" + "typecheck": "tsc --project tsconfig.json --noEmit", + "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,json,css,md}\"" }, "dependencies": { "@hookform/resolvers": "^3.9.0", diff --git a/src/actions/blog.ts b/src/actions/blog.ts new file mode 100644 index 000000000..9d843d8c1 --- /dev/null +++ b/src/actions/blog.ts @@ -0,0 +1,25 @@ +"use server"; + +import axios from "axios"; + +const apiUrl = process.env.API_URL; +const fetchBlogById = async (id: string) => { + try { + const response = await axios.get(`${apiUrl}/api/v1/blogs/${id}`); + return response.data; + } catch (error) { + return { + message: + axios.isAxiosError(error) && + error.response && + error.response.data.message + ? error.response.data.message + : "Something went wrong", + status_code: + axios.isAxiosError(error) && error.response + ? error.response.status + : undefined, + }; + } +}; +export { fetchBlogById }; diff --git a/src/actions/login.ts b/src/actions/login.ts deleted file mode 100644 index bbe7e3332..000000000 --- a/src/actions/login.ts +++ /dev/null @@ -1,61 +0,0 @@ -"use server"; - -import axios from "axios"; -import * as z from "zod"; - -import { LoginSchema } from "~/schemas"; - -const apiUrl = process.env.API_URL; - -export const loginUser = async (values: z.infer) => { - const validatedFields = LoginSchema.safeParse(values); - if (!validatedFields.success) { - return { - error: "Login Failed. Please check your email and password.", - }; - } - const { email, password } = validatedFields.data; - const payload = { email, password }; - try { - const response = await axios.post(`${apiUrl}/api/v1/auth/login`, payload); - - return { - status: response.status, - organisations: response.data.data.organisations, - }; - } catch (error) { - return axios.isAxiosError(error) && error.response - ? { - error: error.response.data.message || "Login failed.", - status: error.response.status, - } - : { - error: "An unexpected error occurred.", - }; - } -}; - -export const nextlogin = async (values: z.infer) => { - const validatedFields = LoginSchema.safeParse(values); - if (!validatedFields.success) { - return { - error: "Login Failed. Please check your email and password.", - }; - } - const { email, password } = validatedFields.data; - const payload = { email, password }; - try { - const response = await axios.post(`${apiUrl}/api/v1/auth/login`, payload); - - return response.data; - } catch (error) { - return axios.isAxiosError(error) && error.response - ? { - error: error.response.data.message || "Login failed.", - status: error.response.status, - } - : { - error: "An unexpected error occurred.", - }; - } -}; diff --git a/src/actions/socialAuth.ts b/src/actions/socialAuth.ts deleted file mode 100644 index 1324573ac..000000000 --- a/src/actions/socialAuth.ts +++ /dev/null @@ -1,52 +0,0 @@ -"use server"; - -import axios from "axios"; - -import { AuthResponse, ErrorResponse } from "~/types"; - -const apiUrl = process.env.API_URL; - -const googleAuth = async (idToken: string): Promise => { - try { - const response = await axios.post(`${apiUrl}/api/v1/auth/google`, { - id_token: idToken, - }); - - return { - data: response.data.user, - access_token: response.data.access_token, - }; - } catch { - throw new Error("Authentication failed"); - } -}; - -const twitterAuth = async ( - access_token: string, -): Promise => { - try { - const response = await axios.post(`${apiUrl}/api/v1/auth/twitter`, { - access_token: access_token, - }); - - return { - data: response.data.user, - access_token: response.data.access_token, - }; - } catch (error) { - return { - message: - axios.isAxiosError(error) && - error.response && - error.response.data.message - ? error.response.data.message - : "Something went wrong", - status_code: - axios.isAxiosError(error) && error.response - ? error.response.status - : undefined, - }; - } -}; - -export { googleAuth, twitterAuth }; diff --git a/src/actions/userAuth.ts b/src/actions/userAuth.ts new file mode 100644 index 000000000..8e12bfdc4 --- /dev/null +++ b/src/actions/userAuth.ts @@ -0,0 +1,102 @@ +"use server"; + +import axios from "axios"; +import * as z from "zod"; + +import { LoginSchema } from "~/schemas"; +import { AuthResponse, ErrorResponse } from "~/types"; + +const apiUrl = process.env.API_URL; + +const credentialsAuth = async ( + values: z.infer, +): Promise => { + const validatedFields = LoginSchema.safeParse(values); + if (!validatedFields.success) { + return { + message: "Something went wrong", + status_code: 401, + }; + } + const { email, password } = validatedFields.data; + const payload = { email, password }; + try { + const response = await axios.post(`${apiUrl}/api/v1/auth/login`, payload); + + return { + data: response.data.user, + access_token: response.data.access_token, + }; + } catch (error) { + return { + message: + axios.isAxiosError(error) && + error.response && + error.response.data.message + ? error.response.data.message + : "Something went wrong", + status_code: + axios.isAxiosError(error) && error.response + ? error.response.status + : undefined, + }; + } +}; + +const googleAuth = async ( + idToken: string, +): Promise => { + try { + const response = await axios.post(`${apiUrl}/api/v1/auth/google`, { + id_token: idToken, + }); + + return { + data: response.data.user, + access_token: response.data.access_token, + }; + } catch (error) { + return { + message: + axios.isAxiosError(error) && + error.response && + error.response.data.message + ? error.response.data.message + : "Something went wrong", + status_code: + axios.isAxiosError(error) && error.response + ? error.response.status + : undefined, + }; + } +}; + +const twitterAuth = async ( + access_token: string, +): Promise => { + try { + const response = await axios.post(`${apiUrl}/api/v1/auth/twitter`, { + access_token: access_token, + }); + + return { + data: response.data.user, + access_token: response.data.access_token, + }; + } catch (error) { + return { + message: + axios.isAxiosError(error) && + error.response && + error.response.data.message + ? error.response.data.message + : "Something went wrong", + status_code: + axios.isAxiosError(error) && error.response + ? error.response.status + : undefined, + }; + } +}; + +export { credentialsAuth, googleAuth, twitterAuth }; diff --git a/src/app/(landing-routes)/blog/[id]/_components/BlogDetailsPage.tsx b/src/app/(landing-routes)/blog/[id]/_components/BlogDetailsPage.tsx index f2c5fec70..e279dc2dd 100644 --- a/src/app/(landing-routes)/blog/[id]/_components/BlogDetailsPage.tsx +++ b/src/app/(landing-routes)/blog/[id]/_components/BlogDetailsPage.tsx @@ -6,6 +6,7 @@ import Image from "next/image"; import Link from "next/link"; import { FC, useEffect, useState } from "react"; +import { fetchBlogById } from "~/actions/blog"; import Comment, { CommentProperties, } from "~/components/common/comment-component"; @@ -50,18 +51,15 @@ const BlogDetailsPage: FC = ({ id }) => { const fetchData = async () => { try { setIsLoading(true); - const response = await fetch( - `https://staging.api-csharp.boilerplate.hng.tech/api/v1/blogs/${id}`, - ); - const result = await response.json(); - setPost(result.data); + const response = await fetchBlogById(id); + setPost(response.data); } finally { setIsLoading(false); } }; fetchData(); - }, []); + }, [id]); const handleSubmit = () => { if (newComment.trim()) { diff --git a/src/config/auth.config.ts b/src/config/auth.config.ts index 3206ae8d7..1bb31a242 100644 --- a/src/config/auth.config.ts +++ b/src/config/auth.config.ts @@ -4,8 +4,7 @@ import Credentials from "next-auth/providers/credentials"; import Google from "next-auth/providers/google"; import Twitter from "next-auth/providers/twitter"; -import { nextlogin } from "~/actions/login"; -import { googleAuth, twitterAuth } from "~/actions/socialAuth"; +import { credentialsAuth, googleAuth, twitterAuth } from "~/actions/userAuth"; import { LoginSchema } from "~/schemas"; import { CustomJWT } from "~/types"; @@ -32,22 +31,25 @@ export default { async authorize(credentials) { const validatedFields = LoginSchema.safeParse(credentials); if (!validatedFields.success) { - return; + // eslint-disable-next-line unicorn/no-null + return null; } const { email, password, rememberMe } = validatedFields.data; - const response = await nextlogin({ email, password, rememberMe }); + const response = await credentialsAuth({ email, password, rememberMe }); if (!response) { - return; + // eslint-disable-next-line unicorn/no-null + return null; } - const user = { - ...response.data, - ...response.data.user, - access_token: response.access_token, - }; + if (!response || !("data" in response)) { + // eslint-disable-next-line unicorn/no-null + return null; + } + const user = response.data as CustomJWT; + user.access_token = response.access_token; return user; }, }), @@ -68,7 +70,7 @@ export default { return !!user; }, - async jwt({ token, user, account, profile }) { + async jwt({ token, user, account }) { if (account?.provider === "google") { if (!account?.id_token) { return token; @@ -76,22 +78,12 @@ export default { const response = await googleAuth(account?.id_token); - if (!response.data) { - token = { - email: profile?.email, - name: profile?.given_name, - picture: profile?.picture, - } as CustomJWT; - token.access_token = response.access_token; + if (!response || !("data" in response)) { return token; } token = response.data as CustomJWT; - token.picture = profile?.picture; token.access_token = response.access_token; - - user = response.data as CustomJWT; - return token; } @@ -103,19 +95,11 @@ export default { const response = await twitterAuth(account?.access_token); if (!response || !("data" in response)) { - token = { - email: user?.email, - name: user?.name, - picture: user?.image, - } as CustomJWT; return token; } + token = response.data as CustomJWT; - token.picture = profile?.picture; token.access_token = response.access_token; - - user = response.data as CustomJWT; - return token; } @@ -126,6 +110,24 @@ export default { }, async session({ session, token }: { session: Session; token: JWT }) { const customToken = token as CustomJWT; + + if (!customToken || !customToken.id) { + return { + ...session, + user: { + id: "", + first_name: "", + last_name: "", + email: "", + image: "", + }, + access_token: undefined, + userOrg: undefined, + currentOrgId: undefined, + expires: new Date(0).toISOString(), + }; + } + session.user = { id: customToken.id as string, first_name: customToken.first_name,