perf: modified logout feature
This commit is contained in:
parent
6211634f96
commit
6188958a81
5 changed files with 148 additions and 2 deletions
|
|
@ -6,6 +6,7 @@ import winsooLogo from "@/assets/images/winsoo.svg";
|
|||
import { getSidebarMenuItems } from "@/constants/navigation";
|
||||
import { colors } from "@/design/tokens/colors";
|
||||
import { spacing } from "@/design/tokens/spacing";
|
||||
import LogoutButton from "@/features/auth/LogoutButton";
|
||||
|
||||
const { Header, Content, Sider } = Layout;
|
||||
const { Title } = Typography;
|
||||
|
|
@ -137,6 +138,8 @@ const AdminLayout = ({ children }: AdminLayoutProps) => {
|
|||
<Title level={4} style={{ margin: 0, color: colors.background, flex: 1, textAlign: "center" }}>
|
||||
پنل ادمین وینسو
|
||||
</Title>
|
||||
|
||||
<LogoutButton />
|
||||
</Header>
|
||||
|
||||
<Content style={{ margin: 24 }}>
|
||||
|
|
|
|||
91
src/features/auth/LogoutButton.tsx
Normal file
91
src/features/auth/LogoutButton.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { Button, Modal, Typography } from 'antd';
|
||||
import { LogOut } from 'lucide';
|
||||
import { useState } from 'react';
|
||||
import LucideIcon from '@/components/ui/LucideIcon';
|
||||
import { colors } from '@/design/tokens/colors';
|
||||
import { spacing } from '@/design/tokens/spacing';
|
||||
import { useLogout } from '@/features/auth/useLogout';
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
const LogoutButton = () => {
|
||||
const { logout, isLoggingOut } = useLogout();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const handleConfirm = () => {
|
||||
void logout();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<LucideIcon icon={LogOut} size={16} />}
|
||||
onClick={() => setIsModalOpen(true)}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: spacing.xs,
|
||||
height: 40,
|
||||
paddingInline: spacing.md,
|
||||
borderRadius: 8,
|
||||
background: 'rgba(255,255,255,0.15)',
|
||||
color: colors.background,
|
||||
}}
|
||||
>
|
||||
خروج
|
||||
</Button>
|
||||
|
||||
<Modal
|
||||
open={isModalOpen}
|
||||
onCancel={() => setIsModalOpen(false)}
|
||||
centered
|
||||
footer={null}
|
||||
width={380}
|
||||
mask={{ closable: !isLoggingOut }}
|
||||
closable={!isLoggingOut}
|
||||
>
|
||||
<div style={{ textAlign: 'center', padding: `${spacing.sm}px 0` }}>
|
||||
<div
|
||||
style={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
borderRadius: '50%',
|
||||
background: colors.primaryLight,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: `0 auto ${spacing.md}px`,
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon={LogOut} size={24} color={colors.accent} />
|
||||
</div>
|
||||
|
||||
<Title level={4} style={{ margin: 0, color: colors.text }}>
|
||||
خروج از حساب کاربری
|
||||
</Title>
|
||||
<Paragraph style={{ color: colors.textSecondary, marginTop: spacing.xs, marginBottom: 0 }}>
|
||||
آیا مطمئن هستید که میخواهید از پنل ادمین خارج شوید؟
|
||||
</Paragraph>
|
||||
|
||||
<div style={{ display: 'flex', gap: spacing.sm, marginTop: spacing.lg }}>
|
||||
<Button block disabled={isLoggingOut} onClick={() => setIsModalOpen(false)}>
|
||||
انصراف
|
||||
</Button>
|
||||
<Button
|
||||
block
|
||||
type="primary"
|
||||
loading={isLoggingOut}
|
||||
onClick={handleConfirm}
|
||||
style={{ background: colors.accent, borderColor: colors.accent }}
|
||||
>
|
||||
خروج
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoutButton;
|
||||
|
|
@ -19,4 +19,8 @@ export const authService = {
|
|||
},
|
||||
};
|
||||
},
|
||||
|
||||
logout: async (): Promise<void> => {
|
||||
await authApi.logout();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
25
src/features/auth/useLogout.ts
Normal file
25
src/features/auth/useLogout.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAppDispatch } from '@/utils/hooks';
|
||||
import { authService } from '@/features/auth/authService';
|
||||
import { logout as logoutAction } from '@/features/auth/authSlice';
|
||||
|
||||
export const useLogout = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
setIsLoggingOut(true);
|
||||
|
||||
try {
|
||||
await authService.logout();
|
||||
} finally {
|
||||
dispatch(logoutAction());
|
||||
setIsLoggingOut(false);
|
||||
navigate('/login', { replace: true });
|
||||
}
|
||||
}, [dispatch, navigate]);
|
||||
|
||||
return { logout, isLoggingOut };
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import axios from 'axios';
|
||||
import { AUTH_SCOPE } from '@/constants/auth';
|
||||
import { createBasicAuthHeader } from '@/config/env';
|
||||
import { clearAuthTokens, storeAuthTokens } from '@/utils/authToken';
|
||||
import { clearAuthTokens, getAccessToken, storeAuthTokens } from '@/utils/authToken';
|
||||
import { normalizePhoneNumber, toE164PhoneNumber } from '@/utils/phone';
|
||||
import { buildAuthUrl } from '@/utils/authUrl';
|
||||
import type { RequestOtpPayload, RequestOtpResponse, TokenResponse } from '@/types/auth';
|
||||
|
|
@ -62,7 +62,30 @@ export const authApi = {
|
|||
return response.data;
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
revokeToken: async (token: string): Promise<void> => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('token', token);
|
||||
params.append('token_type_hint', 'access_token');
|
||||
|
||||
await axios.post(buildAuthUrl('/oauth2/revoke_token/'), params, {
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Authorization: createBasicAuthHeader(),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
logout: async (): Promise<void> => {
|
||||
const accessToken = getAccessToken();
|
||||
|
||||
if (accessToken) {
|
||||
try {
|
||||
await authApi.revokeToken(accessToken);
|
||||
} catch {
|
||||
// Token may already be expired/invalid server-side; still clear the local session.
|
||||
}
|
||||
}
|
||||
|
||||
clearAuthTokens();
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue