Добавлен компонент AuthGuard для защиты страниц от неавторизованных пользователей. Обновлен middleware для редиректа на страницу авторизации при отсутствии токена. Обернуты страницы дашборда, аккаунта, статистики и финансов в AuthGuard для проверки авторизации.
This commit is contained in:
parent
6ab1a42be7
commit
af0c52dbb6
@ -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).*)'],
|
||||||
};
|
};
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -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 = "/";
|
||||||
|
|||||||
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
23
src/components/AuthGuard.tsx
Normal file
23
src/components/AuthGuard.tsx
Normal 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}</>;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user