Skip to content

Commit

Permalink
feat: implement authentication system
Browse files Browse the repository at this point in the history
  • Loading branch information
Javimtib92 committed Sep 30, 2024
1 parent 6539187 commit eb6a0dc
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 38 deletions.
69 changes: 54 additions & 15 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { captureRemixErrorBoundaryError } from "@sentry/remix";
import { Link, Links, Meta, Outlet, Scripts, ScrollRestoration, useLoaderData, useRouteError, useRouteLoaderData } from "@remix-run/react";
import { useUserProvider } from "@/src/shared/presentation/providers/user.provider";
import {
Form,
Link,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useFormAction,
useLoaderData,
useRouteError,
useRouteLoaderData
} from "@remix-run/react";
import { useAuthProvider } from "@/src/shared/presentation/providers/auth.provider";
import { Button } from "@/src/shared/presentation/components/button/button";
import { MainLoader } from "@/src/shared/presentation/components/main-loader/main-loader";
import { Modal } from "@/src/shared/presentation/containers/modal/modal";
Expand All @@ -12,7 +24,8 @@ import css from "./root.css";
import i18nServer, { localeCookie } from "@/src/shared/presentation/i18n/i18n.server";
import { useChangeLanguage } from "remix-i18next/react";

import { json, type LoaderFunctionArgs } from "@remix-run/node";
import { type ActionFunctionArgs, json, type LoaderFunctionArgs, redirect } from "@remix-run/node";
import { commitSession, destroySession, getSession } from "@/src/shared/presentation/controllers/session-controller";

export const ErrorBoundary = () => {
const error = useRouteError();
Expand All @@ -24,17 +37,36 @@ export const handle = { i18n: ["translation"] };

export async function loader({ request }: LoaderFunctionArgs) {
const locale = await i18nServer.getLocale(request);
return json({ locale }, { headers: { "Set-Cookie": await localeCookie.serialize(locale) } });
const session = await getSession(request.headers.get("Cookie"));

return json({ locale, loggedIn: !!session.has("userId") }, { headers: { "Set-Cookie": await localeCookie.serialize(locale) } });
}

export async function action({ request }: ActionFunctionArgs) {
const session = await getSession(request.headers.get("Cookie"));

const userId = "2"; // hardcoded user id for demonstration purposes

if (session.has("userId")) {
return redirect("/", {
headers: {
"Set-Cookie": await destroySession(session)
}
});
} else {
session.set("userId", userId);

return redirect(request.url, {
headers: {
"Set-Cookie": await commitSession(session)
}
});
}
}
export function Layout({ children }: { children: React.ReactNode }) {
const loaderData = useRouteLoaderData<typeof loader>("root");
const userLogged = useUserProvider((state) => state.logged);
const setLogged = useUserProvider((state) => state.setLogged);
const action = useFormAction();

const logUser = () => {
setLogged(!userLogged);
};
const loaderData = useRouteLoaderData<typeof loader>("root");

return (
<html lang={loaderData?.locale ?? "en"}>
Expand Down Expand Up @@ -64,9 +96,9 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Link to="/posts">list post</Link>
</li>
</ul>
<Button data-cy="login-btn" onClick={logUser}>
{userLogged ? "Log out" : "Log in"}
</Button>
<Form method="POST" action={action}>
<Button type="submit">{loaderData?.loggedIn ? "Log out" : "Log in"}</Button>
</Form>
</nav>
<main className={css.main}>{children}</main>
<footer className={css.footer}>cool footer</footer>
Expand All @@ -80,7 +112,14 @@ export function Layout({ children }: { children: React.ReactNode }) {
}

export default function App() {
const { locale } = useLoaderData<typeof loader>();
const { loggedIn, locale } = useLoaderData<typeof loader>();

useChangeLanguage(locale);
return <Outlet />;

console.log("something is going on here", loggedIn);
return (
<useAuthProvider.State initialState={{ loggedIn }}>
<Outlet />
</useAuthProvider.State>
);
}
15 changes: 15 additions & 0 deletions app/routes/users/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,24 @@ import { json } from "@remix-run/node";
import UsersListPage from "@/src/users/presentation/pages/users-list-page/users-list-page";
import type { GetUsersUseCase } from "@/src/users/application/use-cases/get-users-use-case";
import { useUsersListProvider } from "@/src/users/presentation/providers/users-list.provider";
import { useUiProvider } from "@/src/shared/presentation/providers/ui.provider";
import { useAuthProvider } from "@/src/shared/presentation/providers/auth.provider";
import { LoggingModal } from "@/src/shared/presentation/components/logging-modal/logging-modal";
import { useEffect } from "react";

export default function UsersPage() {
const data = useLoaderData<typeof loader>();
const loggedIn = useAuthProvider((state) => state.loggedIn);

const showModal = useUiProvider((state) => state.showModal);

useEffect(() => {
if (!loggedIn) showModal(<LoggingModal />);
}, [loggedIn, showModal]);

if (!loggedIn) {
return null;
}

return (
<useUsersListProvider.State initialState={{ users: data }}>
Expand Down
29 changes: 29 additions & 0 deletions src/shared/presentation/controllers/session-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createCookieSessionStorage } from "@remix-run/node";

type SessionData = {
userId: string;
};

type SessionFlashData = {
error: string;
};

const { getSession, commitSession, destroySession } = createCookieSessionStorage<SessionData, SessionFlashData>({
// a Cookie from `createCookie` or the CookieOptions to create one
cookie: {
name: "__session",

// Expires can also be set (although maxAge overrides it when used in combination).
// Note that this method is NOT recommended as `new Date` creates only one date on each server deployment, not a dynamic date in the future!
//
// expires: new Date(Date.now() + 60_000),
httpOnly: true,
maxAge: 60,
path: "/",
sameSite: "lax",
secrets: ["s3cret1"],
secure: true
}
});

export { getSession, commitSession, destroySession };
6 changes: 6 additions & 0 deletions src/shared/presentation/providers/auth.provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createProvider } from "@/src/shared/presentation/utils/zustand";
import type { AuthStateViewModel } from "../view-models/auth-state";

export const useAuthProvider = createProvider<AuthStateViewModel>(() => (_set) => ({
loggedIn: false
}));
18 changes: 0 additions & 18 deletions src/shared/presentation/providers/user.provider.ts

This file was deleted.

3 changes: 3 additions & 0 deletions src/shared/presentation/view-models/auth-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface AuthStateViewModel {
loggedIn: boolean;
}
4 changes: 0 additions & 4 deletions src/shared/presentation/view-models/user-state.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ export default function UsersListPage() {
const users = useUsersListProvider((state) => state.users);

const { t } = useTranslation();

const showModal = useUiProvider((state) => state.showModal);
const showUserModal = (user: User) => {
showModal(<UserModal user={user} data-cy="user-modal" />);
showModal(<UserModal user={user} />);
};

return (
Expand Down

0 comments on commit eb6a0dc

Please sign in to comment.