Commit 1e49e9ab by asranov0003

feat: add account slice

parent 18157566
// export const API_URL = "https://cabinet.thecybernanny.com/nanny/backend/rpc"; // PROD export const API_URL = "https://cabinet.thecybernanny.com/nanny/backend/rpc"; // PROD
export const API_URL = "https://cabinet.dev.thecybernanny.com/nanny/backend/rpc"; // DEV // export const API_URL = "https://cabinet.dev.thecybernanny.com/nanny/backend/rpc"; // DEV
export const TG_USER_ID = window.Telegram.WebApp.initDataUnsafe?.user?.id || 0; export const TG_USER_ID = window.Telegram.WebApp.initDataUnsafe?.user?.id || 0;
export const TOKEN = localStorage.getItem(`token-${TG_USER_ID}`) || ""; export const TOKEN = localStorage.getItem(`token-${TG_USER_ID}`) || "";
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
} }
.sidebar { .sidebar {
width: 70%; width: 85%;
height: 100%; height: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
...@@ -39,6 +39,19 @@ ...@@ -39,6 +39,19 @@
transition: 0.3s; transition: 0.3s;
} }
.sidebar__content__info {
padding: 1rem;
display: flex;
align-items: center;
gap: 1rem;
font-size: 0.85rem;
}
.sidebar__content__info__icon {
font-size: 2.5rem;
color: var(--primary-color);
}
.sidebar__content__navs { .sidebar__content__navs {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
......
...@@ -13,6 +13,13 @@ import { MdLogout } from "react-icons/md"; ...@@ -13,6 +13,13 @@ import { MdLogout } from "react-icons/md";
import { RiHome3Line } from "react-icons/ri"; import { RiHome3Line } from "react-icons/ri";
import { BsCreditCard } from "react-icons/bs"; import { BsCreditCard } from "react-icons/bs";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import {
useAppDispatch,
useAppSelector,
type RootState,
} from "../../stores/store";
import { accountSession } from "../../stores/slices/accountSlice";
import { FaUserAlt } from "react-icons/fa";
const devices = [ const devices = [
{ {
...@@ -37,6 +44,12 @@ const Header: React.FC = () => { ...@@ -37,6 +44,12 @@ const Header: React.FC = () => {
const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const sidebarRef = useRef<HTMLDivElement>(null); const sidebarRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const { session } = useAppSelector((state: RootState) => state.account);
useEffect(() => {
dispatch(accountSession());
}, [dispatch]);
const handleOutsideClick = (e: MouseEvent) => { const handleOutsideClick = (e: MouseEvent) => {
if (sidebarRef.current && !sidebarRef.current.contains(e.target as Node)) { if (sidebarRef.current && !sidebarRef.current.contains(e.target as Node)) {
...@@ -85,6 +98,36 @@ const Header: React.FC = () => { ...@@ -85,6 +98,36 @@ const Header: React.FC = () => {
<div className="sidebar__overlay"> <div className="sidebar__overlay">
<div className="sidebar" ref={sidebarRef}> <div className="sidebar" ref={sidebarRef}>
<div className="sidebar__content"> <div className="sidebar__content">
<div className="sidebar__content__info">
<FaUserAlt className="sidebar__content__info__icon" />
<div className="text-bold">
<p>{session.login}</p>
<p>
{t("session.activeTill")}:{" "}
{session.subDateEnd ? (
<span className="text-success">{session.subDateEnd}</span>
) : (
<span className="text-danger">
{t("session.noSubscription")}
</span>
)}
</p>
<p>
{t("session.emailStatus")}:{" "}
{session.emailVerified ? (
<span className="text-success">
{t("session.verified")}
</span>
) : (
<span className="text-danger">
{t("session.unVerified")}
</span>
)}
</p>
</div>
</div>
<div className="sidebar__content__navs"> <div className="sidebar__content__navs">
<NavLink <NavLink
to={"/home"} to={"/home"}
......
...@@ -26,6 +26,13 @@ ...@@ -26,6 +26,13 @@
"settings": "Settings", "settings": "Settings",
"logout": "Logout" "logout": "Logout"
}, },
"session": {
"activeTill": "Active till",
"noSubscription": "No subscription",
"emailStatus": "Email status",
"verified": "Verified",
"unVerified": "Unverified"
},
"pincode": { "pincode": {
"enterTitle": "Enter your PIN Code", "enterTitle": "Enter your PIN Code",
"createTitle": "Create your PIN Code", "createTitle": "Create your PIN Code",
......
...@@ -26,6 +26,13 @@ ...@@ -26,6 +26,13 @@
"settings": "Настройки", "settings": "Настройки",
"logout": "Выйти" "logout": "Выйти"
}, },
"session": {
"activeTill": "Активен до",
"noSubscription": "Нет подписки",
"emailStatus": "Статус почты",
"verified": "Подтверждено",
"unVerified": "Не подтверждено"
},
"pincode": { "pincode": {
"enterTitle": "Введите PIN-код", "enterTitle": "Введите PIN-код",
"createTitle": "Создайте PIN-код", "createTitle": "Создайте PIN-код",
......
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { sendRpcRequest } from "../../services/apiClient";
interface ISession {
login: string;
emailVerified: boolean;
subDateEnd: string;
}
interface IAccountState {
session: ISession;
loading: boolean;
}
const initialState: IAccountState = {
session: {
login: "",
emailVerified: false,
subDateEnd: "",
},
loading: false,
};
export const accountSession = createAsyncThunk(
"account/session",
async (_, { rejectWithValue }) => {
try {
const response = await sendRpcRequest<ISession>("account.session");
return response;
} catch (error: unknown) {
if (typeof error === "object" && error !== null && "message" in error) {
return rejectWithValue(error.message);
}
return rejectWithValue("Unknown error occurred");
}
}
);
const accountSlice = createSlice({
name: "account",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(accountSession.pending, (state) => {
state.loading = true;
})
.addCase(accountSession.fulfilled, (state, action) => {
state.loading = false;
state.session = action.payload;
})
.addCase(accountSession.rejected, (state) => {
state.loading = false;
});
},
});
export default accountSlice.reducer;
import { configureStore } from "@reduxjs/toolkit"; import { configureStore } from "@reduxjs/toolkit";
import authSlice from "./slices/authSlice"; import authSlice from "./slices/authSlice";
import accountSlice from "./slices/accountSlice";
import { import {
useDispatch, useDispatch,
useSelector, useSelector,
...@@ -9,6 +10,7 @@ import { ...@@ -9,6 +10,7 @@ import {
export const store = configureStore({ export const store = configureStore({
reducer: { reducer: {
auth: authSlice, auth: authSlice,
account: accountSlice,
}, },
}); });
......
...@@ -28,6 +28,10 @@ body { ...@@ -28,6 +28,10 @@ body {
text-align: center; text-align: center;
} }
.text-bold {
font-weight: bold;
}
:root { :root {
--primary-color: #448AFF; --primary-color: #448AFF;
--primary-muted-color: #BBDEFB; --primary-muted-color: #BBDEFB;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment