Skip to content

Commit a9a9df8

Browse files
committed
feat: confirm logout component
1 parent 6354d12 commit a9a9df8

File tree

6 files changed

+64
-65
lines changed

6 files changed

+64
-65
lines changed

app/features/account/user-card.tsx

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@ import { Avatar, AvatarFallback } from '@/components/ui/avatar';
66
import { Badge } from '@/components/ui/badge';
77
import { Button } from '@/components/ui/button';
88
import { Card, CardAction, CardHeader, CardTitle } from '@/components/ui/card';
9-
import { ConfirmResponsiveDrawer } from '@/components/ui/confirm-responsive-drawer';
109

1110
import { AccountCardRow } from '@/features/account/account-card-row';
1211
import { ChangeNameDrawer } from '@/features/account/change-name-drawer';
13-
import { useSignOut } from '@/features/auth/utils';
12+
import { ConfirmLogout } from '@/features/auth/confirm-logout';
1413

1514
export const UserCard = () => {
1615
const session = authClient.useSession();
17-
const signOut = useSignOut();
1816
return (
1917
<Card className="gap-0 p-0">
2018
<CardHeader className="gap-y-0 py-4">
@@ -34,26 +32,12 @@ export const UserCard = () => {
3432
</div>
3533
</div>
3634
<CardAction>
37-
<ConfirmResponsiveDrawer
38-
onConfirm={async () => signOut.mutateAsync()}
39-
title="Account logout"
40-
description="You are about to end your session"
41-
confirmText={
42-
<>
43-
<LogOutIcon />
44-
Logout
45-
</>
46-
}
47-
>
48-
<Button
49-
size="sm"
50-
variant="ghost"
51-
loading={signOut.isPending || signOut.isSuccess}
52-
>
35+
<ConfirmLogout>
36+
<Button size="sm" variant="ghost">
5337
<LogOutIcon />
54-
Logout
38+
Logout {/* TODO translations */}
5539
</Button>
56-
</ConfirmResponsiveDrawer>
40+
</ConfirmLogout>
5741
</CardAction>
5842
</CardHeader>
5943

app/features/auth/confirm-logout.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { LogOutIcon } from 'lucide-react';
2+
import { ReactElement } from 'react';
3+
import { toast } from 'sonner';
4+
5+
import { authClient } from '@/lib/auth/client';
6+
7+
import { ConfirmResponsiveDrawer } from '@/components/ui/confirm-responsive-drawer';
8+
9+
export const ConfirmLogout = (props: {
10+
children: ReactElement<{ onClick: () => unknown }>;
11+
}) => {
12+
const session = authClient.useSession();
13+
return (
14+
<ConfirmResponsiveDrawer
15+
onConfirm={async () => {
16+
const response = await authClient.signOut();
17+
if (response.error) {
18+
toast.error('Failed to logout');
19+
return;
20+
}
21+
await session.refetch();
22+
}}
23+
title="Account logout"
24+
description="You are about to end your session"
25+
confirmText={
26+
<>
27+
<LogOutIcon />
28+
Logout
29+
</>
30+
}
31+
>
32+
{props.children}
33+
</ConfirmResponsiveDrawer>
34+
);
35+
};

app/features/auth/guard-authenticated.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ export const GuardAuthenticated = ({
3838
return null;
3939
}
4040

41+
// Check if onboarding is done
42+
if (!session.data.user.onboardedAt) {
43+
return <PageOnboarding />;
44+
}
45+
4146
// Unauthorized if the user permission do not match
4247
if (
4348
permissionApps &&
@@ -51,10 +56,5 @@ export const GuardAuthenticated = ({
5156
return <PageError errorCode={403} />;
5257
}
5358

54-
// Check if onboarding is done
55-
if (!session.data.user.onboardedAt) {
56-
return <PageOnboarding />;
57-
}
58-
5959
return <>{children}</>;
6060
};

app/features/auth/page-onboarding.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ import {
1616
} from '@/components/form';
1717
import { Button } from '@/components/ui/button';
1818

19+
import { ConfirmLogout } from '@/features/auth/confirm-logout';
1920
import { LayoutLogin } from '@/features/auth/layout-login';
2021
import { useMascot } from '@/features/auth/mascot';
2122
import { zFormFieldsOnboarding } from '@/features/auth/schema';
22-
import { useSignOut } from '@/features/auth/utils';
2323

2424
export const PageOnboarding = () => {
2525
const { t } = useTranslation(['auth']);
2626
const session = authClient.useSession();
27-
const signOut = useSignOut();
2827

2928
const submitOnboarding = useMutation(
3029
orpc.account.submitOnboarding.mutationOptions({
@@ -55,16 +54,12 @@ export const PageOnboarding = () => {
5554
email: session.data?.user.email,
5655
})}
5756
</p>
58-
<Button
59-
size="xs"
60-
variant="link"
61-
className="opacity-80"
62-
loading={signOut.isPending || signOut.isSuccess}
63-
onClick={() => signOut.mutate()}
64-
>
65-
<LogOutIcon />
66-
{t('auth:pageOnboarding.signOut')}
67-
</Button>
57+
<ConfirmLogout>
58+
<Button size="xs" variant="link" className="opacity-80">
59+
<LogOutIcon />
60+
{t('auth:pageOnboarding.signOut')}
61+
</Button>
62+
</ConfirmLogout>
6863
</div>
6964
}
7065
>

app/features/auth/utils.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,9 @@
1-
import { useMutation } from '@tanstack/react-query';
21
import { useRouter, useSearch } from '@tanstack/react-router';
32
import { useEffect } from 'react';
43

54
import { authClient } from '@/lib/auth/client';
65
import { Role } from '@/lib/auth/permissions';
76

8-
export const useSignOut = () => {
9-
const router = useRouter();
10-
return useMutation({
11-
mutationFn: async () => {
12-
const response = await authClient.signOut();
13-
if (response.error) {
14-
throw new Error(response.error.message);
15-
}
16-
router.navigate({ to: '/' });
17-
return response.data;
18-
},
19-
});
20-
};
21-
227
export const useRedirectAfterLogin = () => {
238
const search = useSearch({ strict: false });
249
const router = useRouter();

app/layout/manager/nav-user.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,14 @@ import {
3434
SidebarMenuItem,
3535
useSidebar,
3636
} from '@/components/ui/sidebar';
37-
import { Spinner } from '@/components/ui/spinner';
3837
import { themes } from '@/components/ui/theme-switcher';
3938

40-
import { useSignOut } from '@/features/auth/utils';
39+
import { ConfirmLogout } from '@/features/auth/confirm-logout';
4140

4241
export function NavUser() {
4342
const { t } = useTranslation(['common']);
4443
const { isMobile } = useSidebar();
4544
const session = authClient.useSession();
46-
const signOut = useSignOut();
4745
const { setOpenMobile } = useSidebar();
4846
const { theme, setTheme } = useTheme();
4947

@@ -69,11 +67,7 @@ export function NavUser() {
6967
<div className="grid flex-1 text-left text-sm leading-tight">
7068
<span className="truncate font-semibold">{user.name}</span>
7169
</div>
72-
{signOut.isPending ? (
73-
<Spinner className="ml-auto size-4" />
74-
) : (
75-
<ChevronsUpDownIcon className="ml-auto size-4" />
76-
)}
70+
<ChevronsUpDownIcon className="ml-auto size-4" />
7771
</SidebarMenuButton>
7872
</DropdownMenuTrigger>
7973
<DropdownMenuContent
@@ -140,10 +134,16 @@ export function NavUser() {
140134
</DropdownMenuItem>
141135
</DropdownMenuGroup>
142136
<DropdownMenuSeparator />
143-
<DropdownMenuItem onClick={() => signOut.mutate()}>
144-
<LogOutIcon />
145-
Log out
146-
</DropdownMenuItem>
137+
<ConfirmLogout>
138+
<DropdownMenuItem
139+
onSelect={(e) => {
140+
e.preventDefault();
141+
}}
142+
>
143+
<LogOutIcon />
144+
Log out
145+
</DropdownMenuItem>
146+
</ConfirmLogout>
147147
</DropdownMenuContent>
148148
</DropdownMenu>
149149
</SidebarMenuItem>

0 commit comments

Comments
 (0)