@@ -0,0 +1,97 @@
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
import type {
|
||||
AuthState,
|
||||
LoginCredentials,
|
||||
RegisterData,
|
||||
User,
|
||||
} from "../types/auth.types";
|
||||
|
||||
// Mock API functions - замените на реальные запросы
|
||||
const mockLogin = async (
|
||||
credentials: LoginCredentials,
|
||||
): Promise<{ user: User; token: string }> => {
|
||||
// Имитация API запроса
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
if (credentials.login === "admin" && credentials.password === "admin") {
|
||||
return {
|
||||
user: {
|
||||
id: "1",
|
||||
login: credentials.login,
|
||||
firstName: "Admin",
|
||||
lastName: "User",
|
||||
},
|
||||
token: "mock-jwt-token",
|
||||
};
|
||||
}
|
||||
throw new Error("Invalid credentials");
|
||||
};
|
||||
|
||||
const mockRegister = async (
|
||||
data: RegisterData,
|
||||
): Promise<{ user: User; token: string }> => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: Date.now().toString(),
|
||||
login: data.login,
|
||||
firstName: data.firstName,
|
||||
lastName: data.lastName,
|
||||
},
|
||||
token: "mock-jwt-token",
|
||||
};
|
||||
};
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
user: null,
|
||||
token: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
login: async (credentials: LoginCredentials) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const { user, token } = await mockLogin(credentials);
|
||||
set({ user, token, isLoading: false });
|
||||
} catch (error) {
|
||||
set({
|
||||
error: error instanceof Error ? error.message : "Login failed",
|
||||
isLoading: false,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
register: async (data: RegisterData) => {
|
||||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const { user, token } = await mockRegister(data);
|
||||
set({ user, token, isLoading: false });
|
||||
} catch (error) {
|
||||
set({
|
||||
error:
|
||||
error instanceof Error ? error.message : "Registration failed",
|
||||
isLoading: false,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
set({ user: null, token: null, error: null });
|
||||
},
|
||||
|
||||
clearError: () => {
|
||||
set({ error: null });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "auth-storage",
|
||||
partialize: (state) => ({ token: state.token, user: state.user }),
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,31 @@
|
||||
export interface LoginCredentials {
|
||||
login: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface RegisterData {
|
||||
login: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
login: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
user: User | null;
|
||||
token: string | null;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
login: (credentials: LoginCredentials) => Promise<void>;
|
||||
register: (data: RegisterData) => Promise<void>;
|
||||
logout: () => void;
|
||||
clearError: () => void;
|
||||
}
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { Theme } from "@/modules/auth/types/auth.types";
|
||||
import { create } from "zustand";
|
||||
import { persist } from "zustand/middleware";
|
||||
|
||||
interface ThemeState {
|
||||
theme: Theme;
|
||||
toggleTheme: () => void;
|
||||
setTheme: (theme: Theme) => void;
|
||||
}
|
||||
|
||||
export const useThemeStore = create<ThemeState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
theme: "dark",
|
||||
toggleTheme: () => {
|
||||
set((state) => {
|
||||
const newTheme = state.theme === "dark" ? "light" : "dark";
|
||||
// Применяем класс к documentElement
|
||||
if (newTheme === "dark") {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
return { theme: newTheme };
|
||||
});
|
||||
},
|
||||
setTheme: (theme: Theme) => {
|
||||
if (theme === "dark") {
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
set({ theme });
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: "theme-storage",
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Инициализация темы при загрузке
|
||||
if (typeof window !== "undefined") {
|
||||
const storedTheme = localStorage.getItem("theme-storage");
|
||||
if (storedTheme) {
|
||||
const { state } = JSON.parse(storedTheme);
|
||||
if (state.theme === "dark") {
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
} else {
|
||||
// По умолчанию dark
|
||||
document.documentElement.classList.add("dark");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
import { useThemeStore } from "../stores/theme.store";
|
||||
import { FiSun, FiMoon } from "react-icons/fi";
|
||||
|
||||
export const ThemeToggle: React.FC = () => {
|
||||
const { theme, toggleTheme } = useThemeStore();
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className="p-2 rounded-lg bg-gray-200 dark:bg-gray-800 hover:bg-gray-300 dark:hover:bg-gray-700 transition-colors duration-200"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{theme === "dark" ? (
|
||||
<FiSun className="w-5 h-5 text-yellow-500" />
|
||||
) : (
|
||||
<FiMoon className="w-5 h-5 text-gray-700" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user