Skip to content

Commit 2aec852

Browse files
committed
feat: implement server-side API request service and update authentication flow in LoginModal and NavBarDropdownButton
1 parent 67e49af commit 2aec852

File tree

9 files changed

+98
-28
lines changed

9 files changed

+98
-28
lines changed

.env.example

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
NODE_ENV=development
22

3-
API_PROTOCOL=http
4-
API_HOST=localhost
5-
API_PORT=1234
6-
API_PREFIX=/api
7-
API_VERSION=/v2
3+
NEXT_PUBLIC_API_PROTOCOL=http
4+
NEXT_PUBLIC_API_HOST=localhost
5+
NEXT_PUBLIC_API_PORT=1234
6+
NEXT_PUBLIC_API_PREFIX=/api
7+
NEXT_PUBLIC_API_VERSION=/v2
88

99
# FOR LOCAL DEVELOPMENT ONLY
1010
# DO NOT USE IN PRODUCTION

src/app/layout.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import '../styles/index.scss';
12
import { cookies } from 'next/headers';
23
import { getLocale } from 'next-intl/server';
3-
import { reqAccountGetManyPublic } from "podverse-helpers";
44
import FavIcons from '../components/Head/FavIcons';
55
import FontPreloads from '../components/Head/FontPreloads';
66
import Manifest from '../components/Head/Manifest';
@@ -9,9 +9,10 @@ import PageWrapper from '../components/PageWrapper/PageWrapper';
99
import SideBar from '../components/SideBar/SideBar';
1010
import WindowWrapper from '../components/Window/WindowWrapper';
1111
import Providers from '../providers/Providers';
12-
import '../styles/index.scss';
1312
import { toUITheme } from '../utils/theme';
1413
import { Modals } from '../components/Modals/Modals';
14+
import { getSSRApiRequestService } from '../factories/apiRequestService';
15+
import { DTOAccount } from 'podverse-helpers';
1516

1617
export const metadata = {
1718
title: 'Podverse',
@@ -22,10 +23,24 @@ export default async function RootLayout({ children }: { children: React.ReactNo
2223
const [locale, cookieStore] = await Promise.all([getLocale(), cookies()]);
2324
const cookieTheme = cookieStore.get('theme')?.value;
2425
const theme = toUITheme(cookieTheme);
25-
26-
const hello = await reqAccountGetManyPublic();
26+
27+
let jwt;
28+
if (typeof window === "undefined") {
29+
const cookieStore = await cookies();
30+
jwt = cookieStore.get("jwt")?.value;
31+
}
32+
const ssrApiRequestService = getSSRApiRequestService(jwt);
33+
34+
const hello = await ssrApiRequestService.reqAccountGetManyPublic();
2735
console.log(hello);
2836

37+
let ssrLoggedInAccount: DTOAccount | null = null;
38+
try {
39+
ssrLoggedInAccount = await ssrApiRequestService.reqAuthMe();
40+
} catch (error) {
41+
// do nothing
42+
}
43+
2944
return (
3045
<html lang={locale} data-theme={theme}>
3146
<head>
@@ -34,7 +49,10 @@ export default async function RootLayout({ children }: { children: React.ReactNo
3449
<Manifest />
3550
</head>
3651
<body>
37-
<Providers locale={locale} theme={theme}>
52+
<Providers
53+
locale={locale}
54+
ssrLoggedInAccount={ssrLoggedInAccount}
55+
theme={theme}>
3856
<WindowWrapper>
3957
<SideBar />
4058
<PageWrapper>

src/app/page.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import React from "react";
2-
import { getTranslations } from "next-intl/server";
32

43
export default async function Home() {
5-
const t = await getTranslations('_sample');
6-
74
return (
85
<div>
96
hello home

src/components/Auth/LoginModal.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,21 @@ import { Modal } from '../Modal/Modal'
66
import { TextInput } from '../TextInput/TextInput'
77
import { useModals } from '../../contexts/Modals'
88
import styles from '../../styles/components/Auth/LoginModal.module.scss'
9+
import { apiRequestService } from '../../factories/apiRequestService';
910

1011
export const LoginModal: React.FC = () => {
1112
const { modals, closeModal } = useModals()
1213
const [email, setEmail] = useState('')
1314
const [password, setPassword] = useState('')
1415

15-
const handleSubmit = (e: React.FormEvent) => {
16+
const handleSubmit = async (e: React.FormEvent) => {
1617
e.preventDefault()
17-
// handle login logic here
18-
closeModal('LoginModal')
18+
try {
19+
await apiRequestService.reqAuthLogin({ email, password })
20+
window.location.reload();
21+
} catch (err) {
22+
console.error('Login failed:', err)
23+
}
1924
}
2025

2126
return (

src/components/NavBar/NavBarDropdownButton.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,27 @@ import DropdownMenu from "../DropdownMenu/DropdownMenu";
99
import { useDropdownKeyboardNavigation } from "../../hooks/useDropdownKeyboardNavigation";
1010
import { ROUTES } from "../../constants/routes";
1111
import { useModals } from '../../contexts/Modals'
12+
import { apiRequestService } from "../../factories/apiRequestService";
1213

1314
const NavBarDropdownButton: React.FC = () => {
14-
const { isLoggedIn } = useContext(AccountContext);
15+
const { loggedInAccount } = useContext(AccountContext);
1516
const { openModal } = useModals();
1617
const router = useRouter();
1718
const buttonRef = useRef<HTMLButtonElement>(null);
1819
const menuRef = useRef<HTMLUListElement>(null);
1920

21+
async function handleLogout() {
22+
await apiRequestService.reqAuthLogout();
23+
window.location.reload();
24+
}
25+
2026
const menuItems = [
2127
{ label: "My Profile", onClick: () => router.push(ROUTES.MY_PROFILE) },
2228
{ label: "Membership", onClick: () => router.push(ROUTES.MEMBERSHIP) },
2329
{ label: "Settings", onClick: () => router.push(ROUTES.SETTINGS) },
24-
{ label: "Login", onClick: () => openModal('LoginModal') }
30+
!!loggedInAccount
31+
? { label: "Logout", onClick: handleLogout }
32+
: { label: "Login", onClick: () => openModal('LoginModal') }
2533
];
2634

2735
const {
@@ -49,7 +57,7 @@ const NavBarDropdownButton: React.FC = () => {
4957
onClick={() => setOpen((v) => !v)}
5058
onKeyDown={handleButtonKeyDown}
5159
>
52-
{isLoggedIn ? (
60+
{!!loggedInAccount ? (
5361
<FaUserCircle className={styles.profileIcon} />
5462
) : (
5563
<FaRegUserCircle className={styles.profileIcon} />

src/config/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export const config = {
2+
api: {
3+
protocol: process.env.NEXT_PUBLIC_API_PROTOCOL || 'http',
4+
host: process.env.NEXT_PUBLIC_API_HOST || 'localhost',
5+
port: process.env.NEXT_PUBLIC_API_PORT || '1234',
6+
prefix: process.env.NEXT_PUBLIC_API_PREFIX || '/api',
7+
version: process.env.NEXT_PUBLIC_API_VERSION || '/v2'
8+
}
9+
}

src/contexts/Account.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
1+
import { DTOAccount } from "podverse-helpers";
12
import React, { createContext, useState, ReactNode } from "react";
23

34
type AccountContextType = {
4-
isLoggedIn: boolean;
5-
setIsLoggedIn: (val: boolean) => void;
5+
loggedInAccount: DTOAccount | null;
6+
setLoggedInAccount: (val: DTOAccount | null) => void;
67
};
78

89
export const AccountContext = createContext<AccountContextType>({
9-
isLoggedIn: false,
10-
setIsLoggedIn: () => {},
10+
loggedInAccount: null,
11+
setLoggedInAccount: () => {},
1112
});
1213

13-
export const AccountProvider = ({ children }: { children: ReactNode }) => {
14-
const [isLoggedIn, setIsLoggedIn] = useState(false);
14+
type AccountProviderProps = {
15+
children: ReactNode;
16+
ssrLoggedInAccount?: DTOAccount | null;
17+
};
18+
19+
export const AccountProvider = ({
20+
children,
21+
ssrLoggedInAccount = null,
22+
}: AccountProviderProps) => {
23+
const [loggedInAccount, setLoggedInAccount] = useState<DTOAccount | null>(ssrLoggedInAccount);
1524

1625
return (
17-
<AccountContext.Provider value={{ isLoggedIn, setIsLoggedIn }}>
26+
<AccountContext.Provider value={{ loggedInAccount, setLoggedInAccount }}>
1827
{children}
1928
</AccountContext.Provider>
2029
);

src/factories/apiRequestService.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { ApiRequestService } from "podverse-helpers";
2+
import { config } from "../config";
3+
4+
export function getSSRApiRequestService(jwt?: string): ApiRequestService {
5+
return new ApiRequestService({
6+
protocol: config.api.protocol,
7+
host: config.api.host,
8+
port: config.api.port,
9+
prefix: config.api.prefix,
10+
version: config.api.version,
11+
jwt,
12+
});
13+
}
14+
15+
export const apiRequestService = new ApiRequestService({
16+
protocol: config.api.protocol,
17+
host: config.api.host,
18+
port: config.api.port,
19+
prefix: config.api.prefix,
20+
version: config.api.version
21+
});

src/providers/Providers.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

33
import { NextIntlClientProvider } from 'next-intl';
4+
import { DTOAccount } from 'podverse-helpers';
45
import { AccountProvider } from '../contexts/Account';
56
import { ThemeProvider } from '../contexts/Theme';
67
import { UITheme } from '../utils/theme';
@@ -9,16 +10,18 @@ import { ModalsProvider } from '../contexts/Modals';
910
export default function Providers({
1011
children,
1112
theme,
12-
locale
13+
locale,
14+
ssrLoggedInAccount
1315
}: {
1416
children: React.ReactNode;
1517
theme: UITheme;
1618
locale: string;
19+
ssrLoggedInAccount: DTOAccount | null;
1720
}) {
1821
return (
1922
<NextIntlClientProvider locale={locale}>
2023
<ThemeProvider initialTheme={theme}>
21-
<AccountProvider>
24+
<AccountProvider ssrLoggedInAccount={ssrLoggedInAccount}>
2225
<ModalsProvider>
2326
{children}
2427
</ModalsProvider>

0 commit comments

Comments
 (0)