Skip to content

Commit

Permalink
feature: logout (#84)
Browse files Browse the repository at this point in the history
* feature: logout

* nothing important

* dry code and lint

---------

Co-authored-by: Ebenezer Arthur <[email protected]>
  • Loading branch information
ebarthur and Ebenezer Arthur authored Sep 23, 2024
1 parent 4bda8c3 commit 3abd82b
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 27 deletions.
44 changes: 44 additions & 0 deletions client/app/components/logout-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Form } from "@remix-run/react";
import { Button } from "./button";
import { Modal } from "./modal";

interface LogoutModalProps {
isOpen: boolean;
onCancel: () => void;
onConfirm: () => void;
}

function LogoutModal({ isOpen, onCancel, onConfirm }: LogoutModalProps) {
return (
<Modal open={isOpen} onClose={onCancel}>
<div className="p-4">
<h2 className="text-base font-semibold flex items-center gap-1">
Logout
</h2>
<p className="mt-2">Are you sure you want to log out?</p>
<div className="flex justify-end mt-4 gap-2">
<Button
className="!text-black px-2 py-1 text-sm bg-gray-200 dark:bg-neutral-800 text-gray-800 !dark:text-white"
onClick={onCancel}
>
Cancel
</Button>
<Form
action="logout"
method="post"
className="transition-[background] duration-200 group cursor-pointer"
>
<Button
onClick={onConfirm}
className="text-white text-sm !bg-red-500"
>
Continue
</Button>
</Form>
</div>
</div>
</Modal>
);
}

export { LogoutModal };
127 changes: 101 additions & 26 deletions client/app/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React from "react";
import type { loader } from "~/root";
import { Avatar } from "./avatar";
import { Username } from "./username";
import { LogoutModal } from "./logout-modal";

const links = [
{
Expand Down Expand Up @@ -121,22 +122,62 @@ function Navbar() {
}

function BottomNav() {
const { user } = useRouteLoaderData<typeof loader>("root") || {};
const [showMore, setShowMore] = React.useState(false);
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const location = useLocation();

// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
React.useEffect(() => {
setShowMore(false);
}, [location.pathname]);

const handleLogoutClick = (e: React.MouseEvent) => {
e.preventDefault();
setIsDialogOpen(true);
};

const handleCancelLogout = () => {
setIsDialogOpen(false);
};
const handleConfirmLogout = () => {
setIsDialogOpen(false);
};

return (
<div className="fixed left-0 bottom-0 w-full lg:hidden">
<div
className={clsx(
"bg-zinc-50 dark:bg-neutral-900 p-4 border-t dark:border-neutral-700 container mx-auto h-[15rem] group overflow-hidden transition-[height] duration-200",
{ "!h-0 !py-0 collapsed": !showMore },
"bg-zinc-50 dark:bg-neutral-900 p-4 border-t dark:border-neutral-700 container mx-auto h-[15rem] overflow-hidden transition-[height] duration-200",
{
"!h-0 !py-0 collapsed": !showMore,
"grid grid-cols-2": Boolean(user),
"flex justify-end": !user,
},
)}
>
{Boolean(user) && (
<>
<div className="px-2 flex mb-8 items-end py-1 font-medium text-secondary">
<button
type="button"
className="flex items-center gap-4"
onClick={handleLogoutClick}
>
{" "}
<div className="i-lucide-arrow-left-circle bg-red-500 opacity-70 text-xl" />
<span className="text-secondary hover:text-dark !dark:hover:text-white">
Logout
</span>
</button>
</div>

<LogoutModal
isOpen={isDialogOpen}
onCancel={handleCancelLogout}
onConfirm={handleConfirmLogout}
/>
</>
)}
<ul className="flex flex-col items-end">
{links.slice(3).map((link) => (
<li key={link.href}>
Expand All @@ -150,7 +191,6 @@ function BottomNav() {
to={link.href}
>
<div>{link.title}</div>

<div className="text-secondary group-[.is-active]:!bg-blue-600 group-[.is-active]:!text-white text-xl py-1 rounded-full">
<div className={link.icon} />
</div>
Expand All @@ -160,7 +200,7 @@ function BottomNav() {
</ul>
</div>
<nav
className=" border-zinc-200 dark:border-neutral-800 bg-zinc-50 dark:bg-zinc-900 static z-10"
className="border-zinc-200 dark:border-neutral-800 bg-zinc-50 dark:bg-zinc-900 static z-10"
style={{ paddingBottom: "env(safe-area-inset-bottom)" }}
>
<ul className="flex p-2 justify-around">
Expand All @@ -184,7 +224,6 @@ function BottomNav() {
</NavLink>
</li>
))}

<li>
<button
type="button"
Expand All @@ -194,7 +233,6 @@ function BottomNav() {
<div className="text-secondary group-[.is-active]:!bg-amber-600 group-[.is-active]:!text-white text-xl px-4 py-1 rounded-full">
<div className="i-lucide-menu" />
</div>

<span className="text-xs text-secondary">More</span>
</button>
</li>
Expand All @@ -205,27 +243,64 @@ function BottomNav() {
}

function SideNav() {
const { user } = useRouteLoaderData<typeof loader>("root") || {};
const [isDialogOpen, setIsDialogOpen] = React.useState(false);

const handleLogoutClick = (e: React.MouseEvent) => {
e.preventDefault();
setIsDialogOpen(true);
};

const handleConfirmLogout = () => {
setIsDialogOpen(false);
};

const handleCancelLogout = () => {
setIsDialogOpen(false);
};

return (
<ul className="">
{links.map((link) => (
<li key={link.href}>
<NavLink
to={link.href}
className={({ isActive }) =>
clsx(
"px-2 py-1 hover:bg-zinc-100 dark:hover:bg-neutral-800 rounded-full font-medium flex items-center gap-2 transition-[background] duration-200",
{
"!bg-zinc-200 !dark:bg-neutral-800": isActive,
"text-secondary": !isActive,
},
)
}
<div className="flex flex-col h-full justify-between">
<ul className="flex-grow">
{links.map((link) => (
<li key={link.href}>
<NavLink
to={link.href}
className={({ isActive }) =>
clsx(
"px-2 py-1 hover:bg-zinc-100 dark:hover:bg-neutral-800 rounded-full font-medium flex items-center gap-2 transition-[background] duration-200",
{
"!bg-zinc-200 !dark:bg-neutral-800": isActive,
"text-secondary": !isActive,
},
)
}
>
<div className={clsx("opacity-70", link.icon)} /> {link.title}
</NavLink>
</li>
))}
</ul>
{Boolean(user) && (
<>
<button
type="button"
className="hover:bg-zinc-100 dark:hover:bg-neutral-800 rounded-full font-medium flex items-center gap-2 transition-[background] duration-200 group cursor-pointer"
onClick={handleLogoutClick}
>
<div className={clsx("opacity-70", link.icon)} /> {link.title}
</NavLink>
</li>
))}
</ul>
<div className="i-lucide-arrow-left-circle bg-red-600 opacity-70 " />
<span className="text-secondary group-hover:text-dark !dark:group-hover:text-white">
Logout
</span>
</button>
<LogoutModal
isOpen={isDialogOpen}
onCancel={handleCancelLogout}
onConfirm={handleConfirmLogout}
/>
</>
)}
</div>
);
}

Expand Down
15 changes: 15 additions & 0 deletions client/app/lib/logout.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { json } from "@remix-run/node";
import { authCookie } from "./cookies.server";

async function logout(request: Request) {
return json(null, {
status: 200,
headers: {
"Set-Cookie": await authCookie.serialize("auth", {
maxAge: 0,
}),
},
});
}

export { logout };
2 changes: 1 addition & 1 deletion client/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export default function App() {
hidden: hideNav,
})}
>
<div className="max-w-[15rem] sticky top-[4rem]">
<div className="max-w-[15rem] sticky top-[4rem] h-[calc(95vh-4rem)]">
<SideNav />
</div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions client/app/routes/logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { redirect, type ActionFunctionArgs } from "@remix-run/node";
import { logout } from "~/lib/logout.server";

export async function action({ request }: ActionFunctionArgs) {
return await logout(request);
}

export async function loader() {
return redirect("/discussions");
}

0 comments on commit 3abd82b

Please sign in to comment.