Skip to content

Commit efd9490

Browse files
authored
fix(ui): mobile profile row wrap, notifications panel deploy, language selector (#11)
* fix(ui): wrap profile controls on mobile to prevent overlap The profile section's main row used a non-wrapping flex with a fixed-width language select and a logout button that kept its text label at every breakpoint. On narrow viewports the row crammed everything into one line and truncated the display name to ~0px width, so the name vanished and the action buttons (password, notifications, logout) jammed together. - Add flex-wrap to the row and its inner controls group so they reflow to a second line on small screens. - Hide the logout button's text label below sm (consistent with the password and notifications buttons) so it becomes icon-only on mobile. Verified at 375px (display name 0px->49px, select wraps to line 2, nothing off-screen) and 1440px (single line, unchanged). Refs #9 * fix(ui): deploy profile notifications panel always and fix language selector Notifications button in the profile section rendered its toggles only when estado.suscrito was true, so on a device that had not activated push the button did nothing on tap. Now the panel always deploys: shows the per-type toggles when subscribed, or a short description with an 'Activar alertas' call to action (or the relevant support reason) when not. Uses activar/soporte from usePush and reuses the existing notif i18n keys. The language Select displayed a stale value because it was derived from user.language, which is fetched once on mount and never updated after a change. Derive the displayed value from i18n.language (the source of truth) with normalization, so the Select reflects the active language immediately. Also collapse the trigger to a Languages icon on mobile (w-9) and keep the full text select at sm and above. Refs #9, Refs #10 --------- Co-authored-by: gnacho <hnacho@proton.me>
1 parent 234f71b commit efd9490

1 file changed

Lines changed: 60 additions & 18 deletions

File tree

‎app/src/pages/Ajustes.tsx‎

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
HeartPulse,
1414
House,
1515
KeyRound,
16+
Languages,
1617
LayoutGrid,
1718
LogOut,
1819
MapPin,
@@ -931,7 +932,7 @@ const TIPOS_NOTIF_USUARIO = [
931932

932933
function ProfileSection() {
933934
const { t, i18n } = useTranslation();
934-
const { estado } = usePush();
935+
const { estado, soporte, activar } = usePush();
935936
const [user, setUser] = useState<{ id: string; username: string; display_name?: string | null; email?: string | null; language: string } | null>(null);
936937
const [editing, setEditing] = useState(false);
937938
const [displayName, setDisplayName] = useState('');
@@ -1004,6 +1005,7 @@ function ProfileSection() {
10041005
} else {
10051006
localStorage.setItem(LANG_MODE_KEY, 'manual');
10061007
i18n.changeLanguage(value);
1008+
setUser((u) => (u ? { ...u, language: value } : u));
10071009
apiPut('/api/auth/profile', { language: value }).catch(() => {});
10081010
}
10091011
};
@@ -1036,19 +1038,28 @@ function ProfileSection() {
10361038
};
10371039

10381040
const isAuto = localStorage.getItem(LANG_MODE_KEY) === 'auto';
1039-
const langValue = isAuto ? 'auto' : (user?.language ?? 'es');
1041+
// Valor mostrado por el Select: fuente de verdad = i18n.language (no user.language,
1042+
// que solo se carga al montar y quedaría stale tras cambiar). Normalizado a los
1043+
// códigos de las opciones por si el detector dio p.ej. 'en-US'.
1044+
const langValue = isAuto
1045+
? 'auto'
1046+
: i18n.language.startsWith('en')
1047+
? 'en'
1048+
: i18n.language.startsWith('zh')
1049+
? 'zh-CN'
1050+
: 'es';
10401051

10411052
return (
10421053
<div className="flex flex-col gap-3">
1043-
{/* Línea principal compacta */}
1044-
<div className="flex items-center gap-3">
1054+
{/* Línea principal compacta (envuelve en móvil para no solapar) */}
1055+
<div className="flex flex-wrap items-center gap-3">
10451056
{/* Avatar con icono User */}
10461057
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-brand/12 text-brand">
10471058
<User size={20} />
10481059
</div>
10491060

10501061
{/* Nombre + idioma + acciones izquierda */}
1051-
<div className="min-w-0 flex-1 flex items-center gap-3">
1062+
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-3">
10521063
{editing ? (
10531064
<div className="flex gap-2 flex-1">
10541065
<Input
@@ -1083,8 +1094,11 @@ function ProfileSection() {
10831094
<Pencil size={13} className="text-faint opacity-0 group-hover:opacity-100 transition-opacity" />
10841095
</button>
10851096
<Select value={langValue} onValueChange={handleLang}>
1086-
<SelectTrigger className="h-8 w-[120px] text-xs">
1087-
<SelectValue />
1097+
<SelectTrigger className="h-8 w-9 justify-center text-xs sm:w-[120px] sm:justify-start">
1098+
<Languages size={16} className="sm:hidden" />
1099+
<span className="hidden sm:inline">
1100+
<SelectValue />
1101+
</span>
10881102
</SelectTrigger>
10891103
<SelectContent>
10901104
{languages.map((lang) => (
@@ -1129,7 +1143,7 @@ function ProfileSection() {
11291143
title={t('common.logout')}
11301144
>
11311145
<LogOut size={16} />
1132-
<span className="text-sm font-medium">{t('common.logout')}</span>
1146+
<span className="hidden text-sm font-medium sm:inline">{t('common.logout')}</span>
11331147
</button>
11341148
</div>
11351149

@@ -1177,18 +1191,46 @@ function ProfileSection() {
11771191
)}
11781192

11791193
{/* Notificaciones push por tipo (expandible) */}
1180-
{showNotifs && prefs && estado.suscrito && (
1194+
{showNotifs && prefs && (
11811195
<div className="flex flex-col gap-1.5 border-t border-app pt-3">
1182-
<p className="text-[11px] font-medium uppercase tracking-[0.08em] text-faint">{t('ajustes.notif.tiposTitulo')}</p>
1183-
{TIPOS_NOTIF_USUARIO.map((tipo) => (
1184-
<div key={tipo} className="flex items-center justify-between rounded-lg bg-surface px-3 py-2">
1185-
<span className="text-sm text-app">{t(`ajustes.notif.tipos.${tipo}`)}</span>
1186-
<Switch
1187-
checked={prefs[tipo] !== false}
1188-
onCheckedChange={(checked) => cambiarPref(tipo, checked)}
1189-
/>
1196+
{estado.suscrito ? (
1197+
<>
1198+
<p className="text-[11px] font-medium uppercase tracking-[0.08em] text-faint">{t('ajustes.notif.tiposTitulo')}</p>
1199+
{TIPOS_NOTIF_USUARIO.map((tipo) => (
1200+
<div key={tipo} className="flex items-center justify-between rounded-lg bg-surface px-3 py-2">
1201+
<span className="text-sm text-app">{t(`ajustes.notif.tipos.${tipo}`)}</span>
1202+
<Switch
1203+
checked={prefs[tipo] !== false}
1204+
onCheckedChange={(checked) => cambiarPref(tipo, checked)}
1205+
/>
1206+
</div>
1207+
))}
1208+
</>
1209+
) : (
1210+
<div className="flex flex-col gap-2 rounded-lg bg-surface px-3 py-2.5">
1211+
<p className="text-sm text-muted">{t('ajustes.notif.descripcion')}</p>
1212+
{soporte === 'ok' ? (
1213+
<button
1214+
type="button"
1215+
onClick={() => activar()}
1216+
disabled={estado.cargando}
1217+
className="bg-brand-gradient inline-flex h-8 items-center justify-center gap-1.5 self-start rounded px-3 text-xs font-semibold text-white disabled:opacity-60"
1218+
>
1219+
<Bell size={14} /> {t('ajustes.notif.activar')}
1220+
</button>
1221+
) : (
1222+
<p className="text-xs text-faint">
1223+
{soporte === 'requiere-https'
1224+
? t('ajustes.notif.requiereHttps')
1225+
: soporte === 'ios-necesita-instalacion'
1226+
? t('ajustes.notif.iosInstalacion')
1227+
: soporte === 'no-configurado'
1228+
? t('ajustes.notif.noConfigurado')
1229+
: t('ajustes.notif.noSoportado')}
1230+
</p>
1231+
)}
11901232
</div>
1191-
))}
1233+
)}
11921234
</div>
11931235
)}
11941236
</div>

0 commit comments

Comments
 (0)