Добавлен компонент AuthGuard для защиты страниц от неавторизованных пользователей. Обновлен middleware для редиректа на страницу авторизации при отсутствии токена. Обернуты страницы дашборда, аккаунта, статистики и финансов в AuthGuard для проверки авторизации.

This commit is contained in:
Redsandyg 2025-06-03 14:58:35 +03:00
parent 6ab1a42be7
commit af0c52dbb6
7 changed files with 169 additions and 127 deletions

View File

@ -5,6 +5,11 @@ export function middleware(request: NextRequest) {
// Получаем access_token из куков (SSR) // Получаем access_token из куков (SSR)
const token = request.cookies.get('access_token'); const token = request.cookies.get('access_token');
// Если не на /auth и нет токена, редиректим на /auth
if (pathname !== '/auth' && !token) {
return NextResponse.redirect(new URL('/auth', request.url));
}
// Если на /auth и токен есть, редиректим на главную
if (pathname === '/auth' && token) { if (pathname === '/auth' && token) {
return NextResponse.redirect(new URL('/', request.url)); return NextResponse.redirect(new URL('/', request.url));
} }
@ -12,5 +17,5 @@ export function middleware(request: NextRequest) {
} }
export const config = { export const config = {
matcher: ['/auth'], matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}; };

View File

@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import styles from "../../styles/dashboard.module.css"; import styles from "../../styles/dashboard.module.css";
import AuthGuard from "../../components/AuthGuard";
interface AccountData { interface AccountData {
id: number; id: number;
@ -11,40 +12,42 @@ interface AccountData {
} }
export default function AccountPage() { export default function AccountPage() {
const [account, setAccount] = useState<AccountData | null>(null); // const [account, setAccount] = useState<AccountData | null>(null);
const [loading, setLoading] = useState(true); // const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); // const [error, setError] = useState<string | null>(null);
useEffect(() => { // useEffect(() => {
fetch("/api/account") // fetch("/api/account")
.then((res) => { // .then((res) => {
if (!res.ok) throw new Error("Ошибка загрузки данных аккаунта"); // if (!res.ok) throw new Error("Ошибка загрузки данных аккаунта");
return res.json(); // return res.json();
}) // })
.then((data) => { // .then((data) => {
setAccount(data); // setAccount(data);
setLoading(false); // setLoading(false);
}) // })
.catch((err) => { // .catch((err) => {
setError(err.message); // setError(err.message);
setLoading(false); // setLoading(false);
}); // });
}, []); // }, []);
if (loading) return <div className={styles.dashboard}>Загрузка...</div>; // if (loading) return <div className={styles.dashboard}>Загрузка...</div>;
if (error) return <div className={styles.dashboard}>Ошибка: {error}</div>; // if (error) return <div className={styles.dashboard}>Ошибка: {error}</div>;
if (!account) return <div className={styles.dashboard}>Нет данных</div>; // if (!account) return <div className={styles.dashboard}>Нет данных</div>;
return ( return (
<div className={styles.dashboard}> <AuthGuard>
<h1 className={styles.title}>Аккаунт</h1> <div className={styles.dashboard}>
<div className={styles.card} style={{ maxWidth: 400, margin: "0 auto" }}> <h1 className={styles.title}>Аккаунт</h1>
<div><b>ID:</b> {account.id}</div> <div className={styles.card} style={{ maxWidth: 400, margin: "0 auto" }}>
<div><b>Логин:</b> {account.login}</div> {/* <div><b>ID:</b> {account.id}</div>
<div><b>Имя:</b> {account.name || "-"}</div> <div><b>Логин:</b> {account.login}</div>
<div><b>Email:</b> {account.email || "-"}</div> <div><b>Имя:</b> {account.name || "-"}</div>
<div><b>Баланс:</b> {account.balance.toLocaleString("ru-RU", { style: "currency", currency: "RUB" })}</div> <div><b>Email:</b> {account.email || "-"}</div>
<div><b>Баланс:</b> {account.balance.toLocaleString("ru-RU", { style: "currency", currency: "RUB" })}</div> */}
</div>
</div> </div>
</div> </AuthGuard>
); );
} }

View File

@ -14,6 +14,11 @@ export default function AuthPage() {
const [error, setError] = useState(""); const [error, setError] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
useEffect(() => {
// Удаляем токен при заходе на страницу авторизации
Cookies.remove('access_token', { path: '/' });
}, []);
useEffect(() => { useEffect(() => {
if (hasToken()) { if (hasToken()) {
window.location.href = "/"; window.location.href = "/";

View File

@ -7,6 +7,7 @@ import BillingMetricCards from "../../components/BillingMetricCards";
import PayoutsTransactionsTable from "../../components/PayoutsTransactionsTable"; import PayoutsTransactionsTable from "../../components/PayoutsTransactionsTable";
import BillingStatChart from "../../components/BillingStatChart"; import BillingStatChart from "../../components/BillingStatChart";
import DateFilters from "../../components/DateFilters"; import DateFilters from "../../components/DateFilters";
import AuthGuard from "../../components/AuthGuard";
export default function BillingPage() { export default function BillingPage() {
const [payoutForm, setPayoutForm] = useState({ const [payoutForm, setPayoutForm] = useState({
@ -36,29 +37,31 @@ export default function BillingPage() {
} }
return ( return (
<div className={styles.billingPage}> <AuthGuard>
<h1 className={styles.title}>Финансы</h1> <div className={styles.billingPage}>
<BillingMetricCards /> <h1 className={styles.title}>Финансы</h1>
<div className={styles.grid2}> <BillingMetricCards />
<BillingStatChart /> <div className={styles.grid2}>
<BillingPieChart /> <BillingStatChart />
<BillingPieChart />
</div>
<DateFilters
dateStart={filters.dateStart}
dateEnd={filters.dateEnd}
onChange={(field, value) => {
if (field === "dateStart") {
if (filters.dateEnd && value > filters.dateEnd) return;
}
if (field === "dateEnd") {
if (filters.dateStart && value < filters.dateStart) return;
}
setFilters(f => ({ ...f, [field]: value }));
}}
onApply={handleApply}
onClear={handleClear}
/>
<PayoutsTransactionsTable filters={filters} reloadKey={reloadKey} />
</div> </div>
<DateFilters </AuthGuard>
dateStart={filters.dateStart}
dateEnd={filters.dateEnd}
onChange={(field, value) => {
if (field === "dateStart") {
if (filters.dateEnd && value > filters.dateEnd) return;
}
if (field === "dateEnd") {
if (filters.dateStart && value < filters.dateStart) return;
}
setFilters(f => ({ ...f, [field]: value }));
}}
onApply={handleApply}
onClear={handleClear}
/>
<PayoutsTransactionsTable filters={filters} reloadKey={reloadKey} />
</div>
); );
} }

View File

@ -1,8 +1,8 @@
import mockData from "../data/mockData"; 'use client'
import MetricCards from "../components/MetricCards"; import MetricCards from "../components/MetricCards";
import Table from "../components/Table";
import StatCharts from "../components/StatCharts"; import StatCharts from "../components/StatCharts";
import styles from "../styles/dashboard.module.css"; import styles from "../styles/dashboard.module.css";
import AuthGuard from "../components/AuthGuard";
function formatCurrency(amount: number) { function formatCurrency(amount: number) {
return amount.toLocaleString("ru-RU", { return amount.toLocaleString("ru-RU", {
@ -14,31 +14,33 @@ function formatCurrency(amount: number) {
export default function DashboardPage() { export default function DashboardPage() {
return ( return (
<div className={styles.dashboard}> <AuthGuard>
<h1 className={styles.title}>Дашборд</h1> <div className={styles.dashboard}>
<MetricCards /> <h1 className={styles.title}>Дашборд</h1>
<StatCharts /> <MetricCards />
{/* <div className={styles.tableBlock}> <StatCharts />
<h3 className={styles.tableTitle}>Последние продажи</h3> {/* <div className={styles.tableBlock}>
<Table <h3 className={styles.tableTitle}>Последние продажи</h3>
headers={["ID реферала", "Агент", "Сумма продажи", "Комиссия", "Дата", "Статус"]} <Table
data={mockData.dashboard.recentSales} headers={["ID реферала", "Агент", "Сумма продажи", "Комиссия", "Дата", "Статус"]}
renderRow={(sale: any, index: number) => ( data={mockData.dashboard.recentSales}
<tr key={index}> renderRow={(sale: any, index: number) => (
<td>{sale.id}</td> <tr key={index}>
<td>{sale.agent}</td> <td>{sale.id}</td>
<td>{formatCurrency(sale.amount)}</td> <td>{sale.agent}</td>
<td>{formatCurrency(sale.commission)}</td> <td>{formatCurrency(sale.amount)}</td>
<td>{sale.date}</td> <td>{formatCurrency(sale.commission)}</td>
<td> <td>{sale.date}</td>
<span className={sale.status === "Выплачено" ? styles.statusPaid : styles.statusPending}> <td>
{sale.status} <span className={sale.status === "Выплачено" ? styles.statusPaid : styles.statusPending}>
</span> {sale.status}
</td> </span>
</tr> </td>
)} </tr>
/> )}
</div> */} />
</div> </div> */}
</div>
</AuthGuard>
); );
} }

View File

@ -7,6 +7,7 @@ import SalesTable from "../../components/SalesTable";
import styles from "../../styles/stat.module.css"; import styles from "../../styles/stat.module.css";
import DateInput from "../../components/DateInput"; import DateInput from "../../components/DateInput";
import DateFilters from "../../components/DateFilters"; import DateFilters from "../../components/DateFilters";
import AuthGuard from "../../components/AuthGuard";
const tabs = [ const tabs = [
{ id: "agents", label: "Агенты" }, { id: "agents", label: "Агенты" },
@ -32,53 +33,53 @@ export default function StatPage() {
setReloadKey(k => k + 1); setReloadKey(k => k + 1);
} }
return ( return (
<div className={styles.statPage}> <AuthGuard>
<div className={styles.headerRow}> <div className={styles.statPage}>
<h1 className={styles.title}>Статистика и аналитика</h1> <div className={styles.headerRow}>
{/* <button className={styles.exportBtn}>Экспорт</button> */} <h1 className={styles.title}>Статистика и аналитика</h1>
</div> {/* <button className={styles.exportBtn}>Экспорт</button> */}
<div className={styles.tabs}> </div>
{tabs.map((tab) => ( <div className={styles.tabs}>
<button {tabs.map((tab) => (
key={tab.id} <button
className={activeTab === tab.id ? styles.activeTab : styles.tab} key={tab.id}
onClick={() => setActiveTab(tab.id)} className={activeTab === tab.id ? styles.activeTab : styles.tab}
> onClick={() => setActiveTab(tab.id)}
{tab.label} >
</button> {tab.label}
))} </button>
</div> ))}
</div>
<DateFilters
dateStart={filters.dateStart} <DateFilters
dateEnd={filters.dateEnd} dateStart={filters.dateStart}
onChange={(field, value) => { dateEnd={filters.dateEnd}
if (field === "dateStart") { onChange={(field, value) => {
if (filters.dateEnd && value > filters.dateEnd) return; if (field === "dateStart") {
} if (filters.dateEnd && value > filters.dateEnd) return;
if (field === "dateEnd") { }
if (filters.dateStart && value < filters.dateStart) return; if (field === "dateEnd") {
} if (filters.dateStart && value < filters.dateStart) return;
setFilters(f => ({ ...f, [field]: value })); }
}} setFilters(f => ({ ...f, [field]: value }));
onApply={handleApply} }}
onClear={handleClear} onApply={handleApply}
/> onClear={handleClear}
/>
<div className={styles.tabContent}> <div className={styles.tabContent}>
{activeTab === "agents" && ( {activeTab === "agents" && (
<AgentsTable filters={filters} reloadKey={reloadKey} /> <AgentsTable filters={filters} reloadKey={reloadKey} />
)} )}
{activeTab === "referrals" && ( {activeTab === "referrals" && (
<ReferralsTable filters={filters} reloadKey={reloadKey} /> <ReferralsTable filters={filters} reloadKey={reloadKey} />
)} )}
{activeTab === "sales" && ( {activeTab === "sales" && (
<SalesTable filters={filters} reloadKey={reloadKey} /> <SalesTable filters={filters} reloadKey={reloadKey} />
)} )}
</div>
</div> </div>
</div> </AuthGuard>
); );
} }

View File

@ -0,0 +1,23 @@
"use client";
import { useEffect, useState, ReactNode } from "react";
import Cookies from "js-cookie";
interface AuthGuardProps {
children: ReactNode;
}
export default function AuthGuard({ children }: AuthGuardProps) {
const [checked, setChecked] = useState(false);
useEffect(() => {
const token = Cookies.get('access_token');
if (!token) {
window.location.href = "/auth";
} else {
setChecked(true);
}
}, []);
if (!checked) return null;
return <>{children}</>;
}