Compare commits
2 Commits
refactor/f
...
fix/header
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbf1d2d02f | ||
| 0ada42017f |
@@ -96,21 +96,26 @@ export function Header() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="app-header__action"
|
||||
onClick={() => navigate('/settings')}
|
||||
title={t('header.profileSettings')}
|
||||
aria-label={t('header.profileSettings')}
|
||||
>
|
||||
<UserCog className="h-4 w-4" />
|
||||
{t('header.profile')}
|
||||
<span className="app-header__action-text">{t('header.profile')}</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="app-header__action"
|
||||
onClick={() => {
|
||||
void logout().then(() => navigate('/login'));
|
||||
}}
|
||||
title={t('header.logout')}
|
||||
aria-label={t('header.logout')}
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
{t('header.logout')}
|
||||
<span className="app-header__action-text">{t('header.logout')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
@@ -25,6 +25,7 @@ export function ProfileSettingsPage() {
|
||||
const refresh = useAuthStore((s) => s.refresh);
|
||||
const queryClient = useQueryClient();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [avatarFailed, setAvatarFailed] = useState(false);
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ['profile'],
|
||||
@@ -120,6 +121,10 @@ export function ProfileSettingsPage() {
|
||||
|
||||
const avatarPreview = watch('avatarUrl') || data?.avatarUrl;
|
||||
|
||||
useEffect(() => {
|
||||
setAvatarFailed(false);
|
||||
}, [avatarPreview]);
|
||||
|
||||
return (
|
||||
<div className="grid max-w-2xl gap-6">
|
||||
<section className="page-section">
|
||||
@@ -136,8 +141,13 @@ export function ProfileSettingsPage() {
|
||||
<form className="profile-form" onSubmit={submit}>
|
||||
<section className="profile-form__avatar-panel">
|
||||
<span className="profile-form__avatar-preview">
|
||||
{avatarPreview ? (
|
||||
<img src={avatarPreview} alt="" className="profile-form__avatar-image" />
|
||||
{avatarPreview && !avatarFailed ? (
|
||||
<img
|
||||
src={avatarPreview}
|
||||
alt=""
|
||||
className="profile-form__avatar-image"
|
||||
onError={() => setAvatarFailed(true)}
|
||||
/>
|
||||
) : (
|
||||
<Gift className="h-6 w-6" />
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type {
|
||||
@@ -19,6 +19,7 @@ export function PublicProfilePage() {
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const { slug = '' } = useParams<{ slug: string }>();
|
||||
const queryClient = useQueryClient();
|
||||
const [avatarFailed, setAvatarFailed] = useState(false);
|
||||
|
||||
const profile = useQuery({
|
||||
queryKey: ['public-profile', slug],
|
||||
@@ -57,6 +58,10 @@ export function PublicProfilePage() {
|
||||
return () => window.clearTimeout(t);
|
||||
}, [wishes.data, markSeen, queryClient, slug]);
|
||||
|
||||
useEffect(() => {
|
||||
setAvatarFailed(false);
|
||||
}, [profile.data?.avatarUrl]);
|
||||
|
||||
return (
|
||||
<div className="public-profile">
|
||||
<div className="public-profile__toolbar">
|
||||
@@ -87,11 +92,12 @@ export function PublicProfilePage() {
|
||||
<>
|
||||
<section className="public-profile__hero">
|
||||
<span className="public-profile__avatar">
|
||||
{profile.data.avatarUrl ? (
|
||||
{profile.data.avatarUrl && !avatarFailed ? (
|
||||
<img
|
||||
src={profile.data.avatarUrl}
|
||||
alt=""
|
||||
className="public-profile__avatar-image"
|
||||
onError={() => setAvatarFailed(true)}
|
||||
/>
|
||||
) : (
|
||||
<Gift className="h-6 w-6" />
|
||||
|
||||
@@ -64,7 +64,13 @@
|
||||
@apply bg-primary text-primary-foreground shadow-card hover:bg-primary;
|
||||
}
|
||||
.app-header__actions {
|
||||
@apply flex shrink-0 items-center gap-1 justify-self-start lg:justify-self-end;
|
||||
@apply flex shrink-0 items-center gap-1 justify-self-start whitespace-nowrap lg:justify-self-end;
|
||||
}
|
||||
.app-header__action {
|
||||
@apply shrink-0;
|
||||
}
|
||||
.app-header__action-text {
|
||||
@apply hidden xl:inline;
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
|
||||
@@ -4,13 +4,6 @@ server {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Static files with long cache
|
||||
location ~* \.(?:js|css|woff2?|svg|png|jpg|jpeg|gif|webp|ico)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# API proxy
|
||||
location /api/ {
|
||||
proxy_pass http://family-wishlist-backend:3000/api/;
|
||||
@@ -24,13 +17,20 @@ server {
|
||||
}
|
||||
|
||||
# Uploaded files (images)
|
||||
location /uploads/ {
|
||||
location ^~ /uploads/ {
|
||||
proxy_pass http://family-wishlist-backend:3000/uploads/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_valid 200 1h;
|
||||
}
|
||||
|
||||
# Static files with long cache
|
||||
location ~* \.(?:js|css|woff2?|svg|png|jpg|jpeg|gif|webp|ico)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public";
|
||||
try_files $uri =404;
|
||||
}
|
||||
|
||||
# SPA fallback
|
||||
location / {
|
||||
try_files $uri /index.html;
|
||||
|
||||
Reference in New Issue
Block a user