From 2e26ef067faf07b921e0f8d75ef30dc3ebd8a283 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Afra=20=E2=80=8C?= Date: Wed, 22 Jul 2026 11:57:05 +0330 Subject: [PATCH] feat: logout added to the app --- src/assets/icons/logout.svg | 5 ++ src/components/Icon/icons.ts | 2 + src/components/LogoutModal.tsx | 29 ++++++++ src/pages/CafeOwnerPanel.tsx | 121 ++++++++++++++++++++++++--------- src/pages/Home.tsx | 25 ++++++- src/utils/logout.ts | 12 ++++ src/utils/role.ts | 4 ++ src/utils/userPhone.ts | 4 ++ 8 files changed, 167 insertions(+), 35 deletions(-) create mode 100644 src/assets/icons/logout.svg create mode 100644 src/components/LogoutModal.tsx create mode 100644 src/utils/logout.ts diff --git a/src/assets/icons/logout.svg b/src/assets/icons/logout.svg new file mode 100644 index 0000000..710c9e7 --- /dev/null +++ b/src/assets/icons/logout.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/Icon/icons.ts b/src/components/Icon/icons.ts index a99a3e7..f82b00b 100644 --- a/src/components/Icon/icons.ts +++ b/src/components/Icon/icons.ts @@ -6,6 +6,7 @@ // icons are ever imported, so an icon that no call site uses never makes it into a chunk. import ArrowRight from '../../assets/icons/arrow-right.svg?react' import Copy from '../../assets/icons/copy.svg?react' +import Logout from '../../assets/icons/logout.svg?react' import Book from '../../assets/icons/book.svg?react' import Calendar from '../../assets/icons/calendar.svg?react' import Call from '../../assets/icons/call.svg?react' @@ -30,6 +31,7 @@ import Success from '../../assets/icons/success.svg?react' export const icons = { arrowRight: ArrowRight, copy: Copy, + logout: Logout, book: Book, calendar: Calendar, call: Call, diff --git a/src/components/LogoutModal.tsx b/src/components/LogoutModal.tsx new file mode 100644 index 0000000..3aadf4b --- /dev/null +++ b/src/components/LogoutModal.tsx @@ -0,0 +1,29 @@ +import { createPortal } from 'react-dom' + +export default function LogoutModal({ onConfirm, onCancel }: { onConfirm: () => void; onCancel: () => void }) { + return createPortal( + <> +
+
+

می‌خواهید از حساب کاربری خود خارج شوید؟

+
+ + +
+
+ , + document.body, + ) +} diff --git a/src/pages/CafeOwnerPanel.tsx b/src/pages/CafeOwnerPanel.tsx index f7a6500..5077729 100644 --- a/src/pages/CafeOwnerPanel.tsx +++ b/src/pages/CafeOwnerPanel.tsx @@ -1,81 +1,136 @@ -import { ReactNode, useEffect, useState } from 'react' -import { useNavigate } from 'react-router-dom' -import Icon from '../components/Icon' -import SupportButton from '../components/SupportButton' -import { useNow } from '../hooks/useNow' -import { CafeSchedule, fetchCafeSchedules } from '../services/campaignApi' -import { getCafeUuid } from '../utils/cafe' -import HostBanner from '../assets/images/banners/Host-banner.jpg' -import { formatPersianNumber } from '../utils/formatNumber' -import { formatMatchLabel } from '../utils/matchCountdown' +import { ReactNode, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import Icon from "../components/Icon"; +import SupportButton from "../components/SupportButton"; +import LogoutModal from "../components/LogoutModal"; +import { logout } from "../utils/logout"; +import { useNow } from "../hooks/useNow"; +import { CafeSchedule, fetchCafeSchedules } from "../services/campaignApi"; +import { getCafeUuid } from "../utils/cafe"; +import HostBanner from "../assets/images/banners/Host-banner.jpg"; +import { formatPersianNumber } from "../utils/formatNumber"; +import { formatMatchLabel } from "../utils/matchCountdown"; -function PanelRow({ icon, label, onClick }: { icon: ReactNode; label: string; onClick?: () => void }) { +function PanelRow({ + icon, + label, + onClick, +}: { + icon: ReactNode; + label: string; + onClick?: () => void; +}) { return ( - - ) + ); } export default function CafeOwnerPanel() { - const navigate = useNavigate() - const now = useNow() - const [schedules, setSchedules] = useState([]) + const navigate = useNavigate(); + const now = useNow(); + const [schedules, setSchedules] = useState([]); + const [showLogout, setShowLogout] = useState(false); useEffect(() => { - const cafeUuid = getCafeUuid() - if (!cafeUuid) return + const cafeUuid = getCafeUuid(); + if (!cafeUuid) return; fetchCafeSchedules(cafeUuid) .then(setSchedules) - .catch(() => {}) - }, []) + .catch(() => {}); + }, []); return (
+
+ +
+ + {showLogout && ( + { + logout(); + navigate("/cafe/login"); + }} + onCancel={() => setShowLogout(false)} + /> + )}
-

پنل کافه

+

+ پنل کافه +

} + icon={ + + } label="اطلاعات کافه" - onClick={() => navigate('/owner/register', { state: { step: 2 } })} + onClick={() => navigate("/owner/register", { state: { step: 2 } })} + /> + } + label="افزودن رویداد" + onClick={() => navigate("/owner/events/new")} /> - } label="افزودن رویداد" onClick={() => navigate('/owner/events/new')} />
-

رویدادهای فعال شما

+

+ رویدادهای فعال شما +

{schedules.map((schedule) => { - const isPast = new Date(schedule.match_event.match_datetime).getTime() <= now.getTime() - const matchLabel = formatMatchLabel(schedule.match_event, now) + const isPast = + new Date(schedule.match_event.match_datetime).getTime() <= + now.getTime(); + const matchLabel = formatMatchLabel(schedule.match_event, now); return (

{matchLabel}

{isPast && ( - برگزار شده + + برگزار شده + )}
- {formatPersianNumber(schedule.remaining_tickets)} صندلی از {formatPersianNumber(schedule.total_tickets)} باقی مانده + {formatPersianNumber(schedule.remaining_tickets)} صندلی از{" "} + {formatPersianNumber(schedule.total_tickets)} باقی مانده
} + icon={ + + } label="مشاهده رزروها" onClick={() => navigate(`/owner/events/${schedule.uuid}/reservations`, { @@ -92,12 +147,12 @@ export default function CafeOwnerPanel() { />
- ) + ); })}
- ) + ); } diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 653ba65..dbd4ca0 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef, useState } from 'react' import SupportButton from '../components/SupportButton' +import LogoutModal from '../components/LogoutModal' import { createPortal } from 'react-dom' import { useNavigate } from 'react-router-dom' import { MapContainer, TileLayer, Marker } from 'react-leaflet' @@ -7,6 +8,7 @@ import 'leaflet/dist/leaflet.css' import '../utils/leafletIcons' import Icon from '../components/Icon' import HomeBanner from '../components/HomeBanner' +import { logout } from '../utils/logout' import { Cafe, City, fetchCafes, fetchCities, MatchEvent } from '../services/campaignApi' import { useMatchEvents } from '../hooks/useMatchEvents' import { formatMatchLabel } from '../utils/matchCountdown' @@ -79,7 +81,7 @@ function CafesMapPreview({ cafes }: { cafes: Cafe[] }) { {/* transparent overlay — captures all clicks and opens the full-page map */}
navigate('/map', { state: { cafes } })} />
@@ -271,10 +273,12 @@ function CafeCard({ cafe }: { cafe: Cafe }) { } export default function Home() { + const navigate = useNavigate() const [view, setView] = useState<'list' | 'map'>('list') const [cities, setCities] = useState([]) const [selectedCityUuid, setSelectedCityUuid] = useState('') const [selectedMatchUuid, setSelectedMatchUuid] = useState('') + const [showLogout, setShowLogout] = useState(false) const { matches, now } = useMatchEvents() const [cafes, setCafes] = useState([]) @@ -301,7 +305,24 @@ export default function Home() { return (
- +
+ + +
+ + {showLogout && ( + { logout(); navigate('/login') }} + onCancel={() => setShowLogout(false)} + /> + )} diff --git a/src/utils/logout.ts b/src/utils/logout.ts new file mode 100644 index 0000000..ae583dc --- /dev/null +++ b/src/utils/logout.ts @@ -0,0 +1,12 @@ +import { clearAuthTokens } from './authToken' +import { clearCafeUuid } from './cafe' +import { clearUserRole } from './role' +import { clearUserPhone } from './userPhone' + +export function logout() { + clearAuthTokens() + clearUserRole() + clearUserPhone() + clearCafeUuid() + sessionStorage.clear() +} diff --git a/src/utils/role.ts b/src/utils/role.ts index 4db59dd..56315aa 100644 --- a/src/utils/role.ts +++ b/src/utils/role.ts @@ -10,3 +10,7 @@ export function setUserRole(role: UserRole) { export function getUserRole(): UserRole { return localStorage.getItem(ROLE_STORAGE_KEY) === 'cafe_owner' ? 'cafe_owner' : 'customer' } + +export function clearUserRole() { + localStorage.removeItem(ROLE_STORAGE_KEY) +} diff --git a/src/utils/userPhone.ts b/src/utils/userPhone.ts index b5b8b58..9a82fb4 100644 --- a/src/utils/userPhone.ts +++ b/src/utils/userPhone.ts @@ -7,3 +7,7 @@ export function setUserPhone(phone: string) { export function getUserPhone(): string | null { return localStorage.getItem(USER_PHONE_STORAGE_KEY) } + +export function clearUserPhone() { + localStorage.removeItem(USER_PHONE_STORAGE_KEY) +}