fixed expiring login creds
This commit is contained in:
@@ -6,7 +6,8 @@
|
|||||||
"Bash(xargs head -5)",
|
"Bash(xargs head -5)",
|
||||||
"Bash(npm install *)",
|
"Bash(npm install *)",
|
||||||
"Bash(npx tsc *)",
|
"Bash(npx tsc *)",
|
||||||
"Bash(npx eslint *)"
|
"Bash(npx eslint *)",
|
||||||
|
"Bash(python -c ' *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ class CustomTokenObtainPairSerializer(TokenObtainPairSerializer):
|
|||||||
if user is None or not user.check_password(password):
|
if user is None or not user.check_password(password):
|
||||||
raise serializers.ValidationError(_("No active account found with the given credentials"))
|
raise serializers.ValidationError(_("No active account found with the given credentials"))
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
raise serializers.ValidationError(_("Tento účet není aktivní. Ověřte prosím svůj e-mail."))
|
||||||
|
|
||||||
# Call the parent validation to create token
|
# Call the parent validation to create token
|
||||||
data = super().validate({
|
data = super().validate({
|
||||||
self.username_field: user.username,
|
self.username_field: user.username,
|
||||||
|
|||||||
@@ -87,25 +87,6 @@ class CookieTokenObtainPairView(TokenObtainPairView):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def validate(self, attrs):
|
|
||||||
username = attrs.get("username")
|
|
||||||
password = attrs.get("password")
|
|
||||||
|
|
||||||
# Přihlaš uživatele ručně
|
|
||||||
user = authenticate(request=self.context.get('request'), username=username, password=password)
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
raise AuthenticationFailed("Špatné uživatelské jméno nebo heslo.")
|
|
||||||
|
|
||||||
if not user.is_active:
|
|
||||||
raise AuthenticationFailed("Uživatel je deaktivován.")
|
|
||||||
|
|
||||||
# Nastav validní uživatele (přebere další logiku ze SimpleJWT)
|
|
||||||
self.user = user
|
|
||||||
|
|
||||||
# Vrátí access a refresh token jako obvykle
|
|
||||||
return super().validate(attrs)
|
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
tags=["account", "public"],
|
tags=["account", "public"],
|
||||||
summary="Refresh JWT token using cookie",
|
summary="Refresh JWT token using cookie",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import type {
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiAccountUsersListParams,
|
ApiAccountUsersListParams,
|
||||||
|
ChangePassword,
|
||||||
CustomUser,
|
CustomUser,
|
||||||
PaginatedCustomUserList,
|
PaginatedCustomUserList,
|
||||||
PatchedCustomUser,
|
PatchedCustomUser,
|
||||||
@@ -56,6 +57,92 @@ type NonReadonly<T> = [T] extends [UnionToIntersection<T>]
|
|||||||
}
|
}
|
||||||
: DistributeReadOnlyOverUnions<T>;
|
: DistributeReadOnlyOverUnions<T>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Change password for the authenticated user
|
||||||
|
*/
|
||||||
|
export const apiAccountPasswordChangeCreate = (
|
||||||
|
changePassword: ChangePassword,
|
||||||
|
signal?: AbortSignal,
|
||||||
|
) => {
|
||||||
|
return privateMutator<void>({
|
||||||
|
url: `/api/account/password-change/`,
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
data: changePassword,
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getApiAccountPasswordChangeCreateMutationOptions = <
|
||||||
|
TError = void,
|
||||||
|
TContext = unknown,
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof apiAccountPasswordChangeCreate>>,
|
||||||
|
TError,
|
||||||
|
{ data: ChangePassword },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
}): UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof apiAccountPasswordChangeCreate>>,
|
||||||
|
TError,
|
||||||
|
{ data: ChangePassword },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationKey = ["apiAccountPasswordChangeCreate"];
|
||||||
|
const { mutation: mutationOptions } = options
|
||||||
|
? options.mutation &&
|
||||||
|
"mutationKey" in options.mutation &&
|
||||||
|
options.mutation.mutationKey
|
||||||
|
? options
|
||||||
|
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||||
|
: { mutation: { mutationKey } };
|
||||||
|
|
||||||
|
const mutationFn: MutationFunction<
|
||||||
|
Awaited<ReturnType<typeof apiAccountPasswordChangeCreate>>,
|
||||||
|
{ data: ChangePassword }
|
||||||
|
> = (props) => {
|
||||||
|
const { data } = props ?? {};
|
||||||
|
|
||||||
|
return apiAccountPasswordChangeCreate(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiAccountPasswordChangeCreateMutationResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof apiAccountPasswordChangeCreate>>
|
||||||
|
>;
|
||||||
|
export type ApiAccountPasswordChangeCreateMutationBody = ChangePassword;
|
||||||
|
export type ApiAccountPasswordChangeCreateMutationError = void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Change password for the authenticated user
|
||||||
|
*/
|
||||||
|
export const useApiAccountPasswordChangeCreate = <
|
||||||
|
TError = void,
|
||||||
|
TContext = unknown,
|
||||||
|
>(
|
||||||
|
options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof apiAccountPasswordChangeCreate>>,
|
||||||
|
TError,
|
||||||
|
{ data: ChangePassword },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof apiAccountPasswordChangeCreate>>,
|
||||||
|
TError,
|
||||||
|
{ data: ChangePassword },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
return useMutation(
|
||||||
|
getApiAccountPasswordChangeCreateMutationOptions(options),
|
||||||
|
queryClient,
|
||||||
|
);
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Returns details of the currently authenticated user based on JWT token or session.
|
* Returns details of the currently authenticated user based on JWT token or session.
|
||||||
* @summary Get current authenticated user
|
* @summary Get current authenticated user
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ export type ApiAccountUsersListParams = {
|
|||||||
postal_code?: string;
|
postal_code?: string;
|
||||||
role?: string;
|
role?: string;
|
||||||
street?: string;
|
street?: string;
|
||||||
|
username?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
10
frontend/src/api/generated/private/models/changePassword.ts
Normal file
10
frontend/src/api/generated/private/models/changePassword.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.8.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* OpenAPI spec version: 0.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ChangePassword {
|
||||||
|
current_password: string;
|
||||||
|
new_password: string;
|
||||||
|
}
|
||||||
@@ -44,4 +44,6 @@ export interface CustomUser {
|
|||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
/** @nullable */
|
/** @nullable */
|
||||||
avatar?: string | null;
|
avatar?: string | null;
|
||||||
|
/** @nullable */
|
||||||
|
banner?: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export * from "./gopayGetStatus200";
|
|||||||
export * from "./gopayRefundPayment200";
|
export * from "./gopayRefundPayment200";
|
||||||
export * from "./hub";
|
export * from "./hub";
|
||||||
export * from "./hubPermission";
|
export * from "./hubPermission";
|
||||||
|
export * from "./changePassword";
|
||||||
export * from "./chat";
|
export * from "./chat";
|
||||||
export * from "./chatMember";
|
export * from "./chatMember";
|
||||||
export * from "./chatTypeEnum";
|
export * from "./chatTypeEnum";
|
||||||
|
|||||||
@@ -44,4 +44,6 @@ export interface PatchedCustomUser {
|
|||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
/** @nullable */
|
/** @nullable */
|
||||||
avatar?: string | null;
|
avatar?: string | null;
|
||||||
|
/** @nullable */
|
||||||
|
banner?: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
10
frontend/src/api/generated/public/models/changePassword.ts
Normal file
10
frontend/src/api/generated/public/models/changePassword.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.8.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* OpenAPI spec version: 0.0.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ChangePassword {
|
||||||
|
current_password: string;
|
||||||
|
new_password: string;
|
||||||
|
}
|
||||||
@@ -44,4 +44,6 @@ export interface CustomUser {
|
|||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
/** @nullable */
|
/** @nullable */
|
||||||
avatar?: string | null;
|
avatar?: string | null;
|
||||||
|
/** @nullable */
|
||||||
|
banner?: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export * from "./downloaderStats";
|
|||||||
export * from "./errorResponse";
|
export * from "./errorResponse";
|
||||||
export * from "./hub";
|
export * from "./hub";
|
||||||
export * from "./hubPermission";
|
export * from "./hubPermission";
|
||||||
|
export * from "./changePassword";
|
||||||
export * from "./chat";
|
export * from "./chat";
|
||||||
export * from "./chatMember";
|
export * from "./chatMember";
|
||||||
export * from "./chatTypeEnum";
|
export * from "./chatTypeEnum";
|
||||||
|
|||||||
@@ -44,4 +44,6 @@ export interface PatchedCustomUser {
|
|||||||
is_active?: boolean;
|
is_active?: boolean;
|
||||||
/** @nullable */
|
/** @nullable */
|
||||||
avatar?: string | null;
|
avatar?: string | null;
|
||||||
|
/** @nullable */
|
||||||
|
banner?: string | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import axios, { type AxiosRequestConfig } from "axios";
|
import axios, { type AxiosRequestConfig } from "axios";
|
||||||
import { AUTH_FLAG } from "@/context/AuthContext";
|
import { AUTH_FLAG } from "@/hooks/useAuth";
|
||||||
|
|
||||||
export const privateApi = axios.create({
|
export const privateApi = axios.create({
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@@ -38,7 +38,7 @@ privateApi.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error.response?.status === 401 && !original._retry) {
|
if ((error.response?.status === 401 || error.response?.status === 403) && !original._retry) {
|
||||||
if (original.url?.includes("/api/account/logout/")) {
|
if (original.url?.includes("/api/account/logout/")) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
@@ -79,8 +79,11 @@ privateApi.interceptors.response.use(
|
|||||||
await privateApi.post("/api/account/token/refresh/");
|
await privateApi.post("/api/account/token/refresh/");
|
||||||
processQueue();
|
processQueue();
|
||||||
return privateApi(original);
|
return privateApi(original);
|
||||||
} catch (refreshError) {
|
} catch (refreshError: any) {
|
||||||
processQueue(refreshError);
|
processQueue(refreshError);
|
||||||
|
// Refresh failed for any reason (400 missing cookie, 401 expired) — clear auth and redirect
|
||||||
|
localStorage.removeItem(AUTH_FLAG);
|
||||||
|
window.location.href = "/social/login";
|
||||||
return Promise.reject(refreshError);
|
return Promise.reject(refreshError);
|
||||||
} finally {
|
} finally {
|
||||||
isRefreshing = false;
|
isRefreshing = false;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import axios, { type AxiosRequestConfig } from "axios";
|
import axios, { type AxiosRequestConfig } from "axios";
|
||||||
import { AUTH_FLAG } from "@/context/AuthContext";
|
import { AUTH_FLAG } from "@/hooks/useAuth";
|
||||||
|
|
||||||
export const publicApi = axios.create({
|
export const publicApi = axios.create({
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
|
|
||||||
export default function Ad() {
|
export default function Ad() {
|
||||||
const { isAuthenticated, isLoading } = useAuth();
|
const { isAuthenticated, isLoading } = useAuth();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
FaHandsHelping,
|
FaHandsHelping,
|
||||||
} from "react-icons/fa";
|
} from "react-icons/fa";
|
||||||
import { FaClapperboard, FaCubes } from "react-icons/fa6";
|
import { FaClapperboard, FaCubes } from "react-icons/fa6";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import Avatar from "@/components/ui/Avatar";
|
import Avatar from "@/components/ui/Avatar";
|
||||||
import styles from "./navbar.module.css";
|
import styles from "./navbar.module.css";
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { Message as MessageModel } from "@/api/generated/private/models/mes
|
|||||||
import type { Chat } from "@/api/generated/private/models/chat";
|
import type { Chat } from "@/api/generated/private/models/chat";
|
||||||
import Avatar from "@/components/ui/Avatar";
|
import Avatar from "@/components/ui/Avatar";
|
||||||
import IconButton from "@/components/ui/IconButton";
|
import IconButton from "@/components/ui/IconButton";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { canDeleteMessage } from "@/hooks/usePermissions";
|
import { canDeleteMessage } from "@/hooks/usePermissions";
|
||||||
import { formatRelative } from "@/utils/relativeTime";
|
import { formatRelative } from "@/utils/relativeTime";
|
||||||
import { apiSocialMessagesDestroy } from "@/api/generated/private/chat/chat";
|
import { apiSocialMessagesDestroy } from "@/api/generated/private/chat/chat";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { FiTrash2, FiMoreHorizontal } from "react-icons/fi";
|
|||||||
import type { Post } from "@/api/generated/private/models/post";
|
import type { Post } from "@/api/generated/private/models/post";
|
||||||
import Avatar from "@/components/ui/Avatar";
|
import Avatar from "@/components/ui/Avatar";
|
||||||
import IconButton from "@/components/ui/IconButton";
|
import IconButton from "@/components/ui/IconButton";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { canDeletePost, canEditPost } from "@/hooks/usePermissions";
|
import { canDeletePost, canEditPost } from "@/hooks/usePermissions";
|
||||||
import { formatRelative } from "@/utils/relativeTime";
|
import { formatRelative } from "@/utils/relativeTime";
|
||||||
import { apiSocialPostsDestroy } from "@/api/generated/private/posts/posts";
|
import { apiSocialPostsDestroy } from "@/api/generated/private/posts/posts";
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { createContext, useContext, useState, useEffect, useRef } from "react";
|
import { createContext, useState, useEffect, useRef } from "react";
|
||||||
|
|
||||||
import { apiAccountLogoutCreate } from "@/api/generated/public/account";
|
import { apiAccountLoginCreate, apiAccountLogoutCreate } from "@/api/generated/public/account";
|
||||||
import { apiAccountUserMeRetrieve } from "@/api/generated/private/account/account";
|
import { apiAccountUserMeRetrieve } from "@/api/generated/private/account/account";
|
||||||
import { privateApi } from "@/api/privateClient";
|
import { AUTH_FLAG } from "@/hooks/useAuth";
|
||||||
|
|
||||||
import type { CustomTokenObtainPair } from "@/api/generated/public/models/customTokenObtainPair";
|
import type { CustomTokenObtainPair } from "@/api/generated/public/models/customTokenObtainPair";
|
||||||
import type { CustomUser } from "@/api/generated/private/models/customUser";
|
import type { CustomUser } from "@/api/generated/private/models/customUser";
|
||||||
|
|
||||||
export const AUTH_FLAG = "vontor_was_logged_in";
|
export interface AuthContextType {
|
||||||
|
|
||||||
interface AuthContextType {
|
|
||||||
user: CustomUser | null;
|
user: CustomUser | null;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@@ -19,7 +17,7 @@ interface AuthContextType {
|
|||||||
refreshUser: () => Promise<void>;
|
refreshUser: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
export const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
const [user, setUser] = useState<CustomUser | null>(null);
|
const [user, setUser] = useState<CustomUser | null>(null);
|
||||||
@@ -56,23 +54,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
async function login(payload: CustomTokenObtainPair) {
|
async function login(payload: CustomTokenObtainPair) {
|
||||||
setIsLoading(true);
|
await apiAccountLoginCreate(payload);
|
||||||
try {
|
localStorage.setItem(AUTH_FLAG, "true");
|
||||||
await privateApi.post("/api/account/login/", payload);
|
await refreshUser();
|
||||||
localStorage.setItem(AUTH_FLAG, "true");
|
|
||||||
await refreshUser();
|
|
||||||
} catch (err: any) {
|
|
||||||
setIsLoading(false);
|
|
||||||
const data = err.response?.data;
|
|
||||||
const errorMessage =
|
|
||||||
data?.detail ||
|
|
||||||
(typeof data === "object" && !Array.isArray(data)
|
|
||||||
? Object.values(data).flat().filter(Boolean).join(" ")
|
|
||||||
: null) ||
|
|
||||||
err.message ||
|
|
||||||
"Login failed";
|
|
||||||
throw new Error(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
@@ -94,9 +78,3 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAuth() {
|
|
||||||
const ctx = useContext(AuthContext);
|
|
||||||
if (!ctx) throw new Error("useAuth must be used inside AuthProvider");
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|||||||
10
frontend/src/hooks/useAuth.ts
Normal file
10
frontend/src/hooks/useAuth.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { AuthContext } from "@/context/AuthContext";
|
||||||
|
|
||||||
|
export const AUTH_FLAG = "vontor_was_logged_in";
|
||||||
|
|
||||||
|
export function useAuth() {
|
||||||
|
const ctx = useContext(AuthContext);
|
||||||
|
if (!ctx) throw new Error("useAuth must be used inside AuthProvider");
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
FiBookmark,
|
FiBookmark,
|
||||||
FiLogOut,
|
FiLogOut,
|
||||||
} from "react-icons/fi";
|
} from "react-icons/fi";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import Avatar from "@/components/ui/Avatar";
|
import Avatar from "@/components/ui/Avatar";
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import { FiArrowLeft, FiUser, FiLock, FiCamera, FiImage } from "react-icons/fi";
|
import { FiArrowLeft, FiUser, FiLock, FiCamera, FiImage } from "react-icons/fi";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { privateApi } from "@/api/privateClient";
|
import { privateApi } from "@/api/privateClient";
|
||||||
import { mediaUrl } from "@/utils/mediaUrl";
|
import { mediaUrl } from "@/utils/mediaUrl";
|
||||||
import Avatar from "@/components/ui/Avatar";
|
import Avatar from "@/components/ui/Avatar";
|
||||||
@@ -187,11 +187,11 @@ export default function AccountSettingsPage() {
|
|||||||
{bannerSrc && (
|
{bannerSrc && (
|
||||||
<img src={bannerSrc} alt="" className="h-full w-full object-cover" />
|
<img src={bannerSrc} alt="" className="h-full w-full object-cover" />
|
||||||
)}
|
)}
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-black/0 transition-colors group-hover:bg-black/40">
|
<div className="absolute inset-0 flex items-center justify-center bg-black/20 transition-colors group-hover:bg-black/40">
|
||||||
{bannerUploading ? (
|
{bannerUploading ? (
|
||||||
<Spinner size={22} />
|
<Spinner size={22} />
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
<div className="flex flex-col items-center gap-1 opacity-60 transition-opacity group-hover:opacity-100">
|
||||||
<FiImage size={20} className="text-white" />
|
<FiImage size={20} className="text-white" />
|
||||||
<span className="text-xs text-white/90">Změnit banner</span>
|
<span className="text-xs text-white/90">Změnit banner</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FiLogOut, FiUser } from "react-icons/fi";
|
import { FiLogOut, FiUser } from "react-icons/fi";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import Avatar from "@/components/ui/Avatar";
|
import Avatar from "@/components/ui/Avatar";
|
||||||
import Card from "@/components/ui/Card";
|
import Card from "@/components/ui/Card";
|
||||||
import EmptyState from "@/components/ui/EmptyState";
|
import EmptyState from "@/components/ui/EmptyState";
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { FiArrowLeft, FiLogOut, FiSettings, FiUser, FiCalendar, FiMapPin } from "react-icons/fi";
|
import { FiArrowLeft, FiLogOut, FiSettings, FiUser, FiCalendar, FiMapPin } from "react-icons/fi";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useApiSocialPostsList } from "@/api/generated/private/posts/posts";
|
import { useApiSocialPostsList } from "@/api/generated/private/posts/posts";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { privateApi } from "@/api/privateClient";
|
import { privateApi } from "@/api/privateClient";
|
||||||
import Avatar from "@/components/ui/Avatar";
|
import Avatar from "@/components/ui/Avatar";
|
||||||
import { mediaUrl } from "@/utils/mediaUrl";
|
import { mediaUrl } from "@/utils/mediaUrl";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
FiUser,
|
FiUser,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { useNavigate, Link } from "react-router-dom";
|
import { useNavigate, Link } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FiAtSign, FiLock } from "react-icons/fi";
|
import { FiAtSign, FiLock } from "react-icons/fi";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import Spinner from "@/components/ui/Spinner";
|
import Spinner from "@/components/ui/Spinner";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Navigate, Outlet, useLocation } from "react-router-dom";
|
import { Navigate, Outlet, useLocation } from "react-router-dom";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import Spinner from "@/components/ui/Spinner";
|
import Spinner from "@/components/ui/Spinner";
|
||||||
|
|
||||||
export default function PrivateRoute() {
|
export default function PrivateRoute() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Navigate, Outlet } from "react-router-dom";
|
import { Navigate, Outlet } from "react-router-dom";
|
||||||
import { useAuth } from "@/context/AuthContext";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
import Spinner from "@/components/ui/Spinner";
|
import Spinner from "@/components/ui/Spinner";
|
||||||
|
|
||||||
export default function PublicOnlyRoute() {
|
export default function PublicOnlyRoute() {
|
||||||
|
|||||||
Reference in New Issue
Block a user