v4: the avatar menu shows at every width and holds Log out, so a profile keeps only Invite; the profile's dump cards lay out again — your own profile had four buttons in a row, Invite someone, Settings, Manage and Log out, Invite taller than the rest, and on wide screens it was the only way to reach /settings or /manage. The avatar menu now leads the header's icon row at every width, after the name and Playlists text links on wide screens, and ends with Log out under a separator, muted until hovered. The profile keeps Invite someone alone. The menu's button outweighs .app-header-nav button and the themes' button rules (Brutalist and GeoCities list it among their flat buttons), so it reads as one of the menu's links. Since b9ff9c4, a card under 472px is laid out by the narrow-card container query, but the profile columns' dense-card rules still placed the thumb, body and vote themselves, more specifically: the thumb lost its row span, so the title fell under it and every card was a tall, mostly empty box. Those rules now only make the card denser (a smaller thumb, a 2-line title, smaller type) and leave placement to the container query. 670 frontend tests pass, and tsc, eslint, deno fmt, doc:lint and i18n:compile --strict are clean; checked in screenshots in Smooth dark at 1400 and 390px, Brutalist light at 390px, GeoCities light and Kanagawa dark at 1400px, in French.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-08 22:22:15 +00:00
parent 10c9cf4269
commit 886216749e
11 changed files with 179 additions and 219 deletions

View File

@@ -431,7 +431,7 @@ msgstr "← Back"
msgid "← Back to all dumps" msgid "← Back to all dumps"
msgstr "← Back to all dumps" msgstr "← Back to all dumps"
#: src/components/profile/ProfileOwnActions.tsx:80 #: src/components/profile/ProfileOwnActions.tsx:76
msgid "+ Invite someone" msgid "+ Invite someone"
msgstr "+ Invite someone" msgstr "+ Invite someone"
@@ -518,11 +518,11 @@ msgstr "Add a comment…"
msgid "Add category" msgid "Add category"
msgstr "Add category" msgstr "Add category"
#: src/components/profile/ProfileHeader.tsx:154 #: src/components/profile/ProfileHeader.tsx:153
msgid "Add email…" msgid "Add email…"
msgstr "Add email…" msgstr "Add email…"
#: src/components/shared/ImagePicker.tsx:63 #: src/components/shared/ImagePicker.tsx:66
msgid "Add image" msgid "Add image"
msgstr "Add image" msgstr "Add image"
@@ -589,7 +589,7 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may
#: src/components/comment/CommentForm.tsx:127 #: src/components/comment/CommentForm.tsx:127
#: src/components/dump/create/DumpCreateModal.tsx:752 #: src/components/dump/create/DumpCreateModal.tsx:752
#: src/components/form/FormActions.tsx:42 #: src/components/form/FormActions.tsx:42
#: src/components/playlist/PlaylistEditForm.tsx:199 #: src/components/playlist/PlaylistEditForm.tsx:200
#: src/components/profile/DescriptionEditor.tsx:97 #: src/components/profile/DescriptionEditor.tsx:97
#: src/components/profile/EmailEditor.tsx:83 #: src/components/profile/EmailEditor.tsx:83
#: src/components/shared/ConfirmModal.tsx:63 #: src/components/shared/ConfirmModal.tsx:63
@@ -607,7 +607,7 @@ msgstr "Cancel removal"
msgid "Cancel reply" msgid "Cancel reply"
msgstr "Cancel reply" msgstr "Cancel reply"
#: src/components/layout/AppHeader.tsx:190 #: src/components/layout/AppHeader.tsx:188
msgid "Cancel search" msgid "Cancel search"
msgstr "Cancel search" msgstr "Cancel search"
@@ -620,11 +620,11 @@ msgstr "Categories"
msgid "Category" msgid "Category"
msgstr "Category" msgstr "Category"
#: src/components/profile/ProfileHeader.tsx:96 #: src/components/profile/ProfileHeader.tsx:95
msgid "Change avatar" msgid "Change avatar"
msgstr "Change avatar" msgstr "Change avatar"
#: src/components/shared/ImagePicker.tsx:63 #: src/components/shared/ImagePicker.tsx:66
msgid "Change image" msgid "Change image"
msgstr "Change image" msgstr "Change image"
@@ -695,11 +695,11 @@ msgstr "Confirm new password"
msgid "Connection lost — this message may not have been sent" msgid "Connection lost — this message may not have been sent"
msgstr "Connection lost — this message may not have been sent" msgstr "Connection lost — this message may not have been sent"
#: src/components/profile/ProfileOwnActions.tsx:105 #: src/components/profile/ProfileOwnActions.tsx:99
msgid "Copied!" msgid "Copied!"
msgstr "Copied!" msgstr "Copied!"
#: src/components/profile/ProfileOwnActions.tsx:105 #: src/components/profile/ProfileOwnActions.tsx:99
msgid "Copy" msgid "Copy"
msgstr "Copy" msgstr "Copy"
@@ -800,7 +800,7 @@ msgid "Delete failed ({0})"
msgstr "Delete failed ({0})" msgstr "Delete failed ({0})"
#: src/components/playlist/PlaylistCard.tsx:142 #: src/components/playlist/PlaylistCard.tsx:142
#: src/components/playlist/PlaylistEditForm.tsx:190 #: src/components/playlist/PlaylistEditForm.tsx:191
#: src/components/profile/ProfilePlaylistsTab.tsx:156 #: src/components/profile/ProfilePlaylistsTab.tsx:156
#: src/pages/PlaylistDetail.tsx:141 #: src/pages/PlaylistDetail.tsx:141
msgid "Delete playlist" msgid "Delete playlist"
@@ -827,7 +827,7 @@ msgstr "Delete this playlist? This cannot be undone."
msgid "deleted message" msgid "deleted message"
msgstr "deleted message" msgstr "deleted message"
#: src/components/playlist/PlaylistEditForm.tsx:175 #: src/components/playlist/PlaylistEditForm.tsx:176
msgid "Description" msgid "Description"
msgstr "Description" msgstr "Description"
@@ -869,7 +869,7 @@ msgstr "Drop a file here, or paste one"
msgid "Drop a replacement here" msgid "Drop a replacement here"
msgstr "Drop a replacement here" msgstr "Drop a replacement here"
#: src/components/layout/AppHeader.tsx:209 #: src/components/layout/AppHeader.tsx:207
msgid "Dump" msgid "Dump"
msgstr "Dump" msgstr "Dump"
@@ -914,7 +914,7 @@ msgstr "Edit {0}"
msgid "Edit description" msgid "Edit description"
msgstr "Edit description" msgstr "Edit description"
#: src/components/profile/ProfileHeader.tsx:152 #: src/components/profile/ProfileHeader.tsx:151
msgid "Edit email" msgid "Edit email"
msgstr "Edit email" msgstr "Edit email"
@@ -984,9 +984,9 @@ msgstr "Failed to create playlist"
msgid "Failed to delete" msgid "Failed to delete"
msgstr "Failed to delete" msgstr "Failed to delete"
#: src/components/profile/ProfileOwnActions.tsx:46
#: src/components/profile/ProfileOwnActions.tsx:49 #: src/components/profile/ProfileOwnActions.tsx:49
#: src/components/profile/ProfileOwnActions.tsx:52 #: src/components/profile/ProfileOwnActions.tsx:108
#: src/components/profile/ProfileOwnActions.tsx:114
msgid "Failed to generate invite" msgid "Failed to generate invite"
msgstr "Failed to generate invite" msgstr "Failed to generate invite"
@@ -1006,7 +1006,7 @@ msgstr "Failed to post comment"
msgid "Failed to post reply" msgid "Failed to post reply"
msgstr "Failed to post reply" msgstr "Failed to post reply"
#: src/components/playlist/PlaylistEditForm.tsx:184 #: src/components/playlist/PlaylistEditForm.tsx:185
#: src/components/profile/DescriptionEditor.tsx:99 #: src/components/profile/DescriptionEditor.tsx:99
#: src/components/profile/EmailEditor.tsx:86 #: src/components/profile/EmailEditor.tsx:86
msgid "Failed to save" msgid "Failed to save"
@@ -1020,7 +1020,7 @@ msgstr "Failed to save category"
msgid "Failed to save edit" msgid "Failed to save edit"
msgstr "Failed to save edit" msgstr "Failed to save edit"
#: src/components/profile/ProfileHeader.tsx:163 #: src/components/profile/ProfileHeader.tsx:162
msgid "Failed to update avatar" msgid "Failed to update avatar"
msgstr "Failed to update avatar" msgstr "Failed to update avatar"
@@ -1128,12 +1128,12 @@ msgstr "Invalid invite"
msgid "Invalid link" msgid "Invalid link"
msgstr "Invalid link" msgstr "Invalid link"
#: src/components/profile/ProfileOwnActions.tsx:88 #: src/components/profile/ProfileOwnActions.tsx:82
#: src/components/profile/ProfileOwnActions.tsx:95 #: src/components/profile/ProfileOwnActions.tsx:89
msgid "Invite link" msgid "Invite link"
msgstr "Invite link" msgstr "Invite link"
#: src/components/profile/ProfileHeader.tsx:117 #: src/components/profile/ProfileHeader.tsx:116
msgid "invited by" msgid "invited by"
msgstr "invited by" msgstr "invited by"
@@ -1176,7 +1176,7 @@ msgstr "Link or file"
msgid "Live updates are temporarily disconnected. Trying to reconnect…" msgid "Live updates are temporarily disconnected. Trying to reconnect…"
msgstr "Live updates are temporarily disconnected. Trying to reconnect…" msgstr "Live updates are temporarily disconnected. Trying to reconnect…"
#: src/components/layout/AppHeader.tsx:242 #: src/components/layout/AppHeader.tsx:240
msgid "Live updates unavailable." msgid "Live updates unavailable."
msgstr "Live updates unavailable." msgstr "Live updates unavailable."
@@ -1219,7 +1219,7 @@ msgstr "Loading profile…"
msgid "Loading…" msgid "Loading…"
msgstr "Loading…" msgstr "Loading…"
#: src/components/layout/AppHeader.tsx:216 #: src/components/layout/AppHeader.tsx:214
#: src/pages/UserLogin.tsx:41 #: src/pages/UserLogin.tsx:41
#: src/pages/UserLogin.tsx:67 #: src/pages/UserLogin.tsx:67
#: src/pages/UserLogin.tsx:86 #: src/pages/UserLogin.tsx:86
@@ -1234,7 +1234,7 @@ msgstr "Log in to like"
msgid "Log in to vote" msgid "Log in to vote"
msgstr "Log in to vote" msgstr "Log in to vote"
#: src/components/profile/ProfileHeader.tsx:188 #: src/components/layout/UserMenu.tsx:115
#: src/pages/UserPublicProfile.tsx:131 #: src/pages/UserPublicProfile.tsx:131
msgid "Log out" msgid "Log out"
msgstr "Log out" msgstr "Log out"
@@ -1251,8 +1251,7 @@ msgstr "Login failed"
msgid "Lowercase letters, numbers and single hyphens only" msgid "Lowercase letters, numbers and single hyphens only"
msgstr "Lowercase letters, numbers and single hyphens only" msgstr "Lowercase letters, numbers and single hyphens only"
#: src/components/layout/UserMenu.tsx:98 #: src/components/layout/UserMenu.tsx:102
#: src/components/profile/ProfileHeader.tsx:180
#: src/pages/Manage.tsx:24 #: src/pages/Manage.tsx:24
#: src/pages/Manage.tsx:29 #: src/pages/Manage.tsx:29
#: src/pages/UserPublicProfile.tsx:164 #: src/pages/UserPublicProfile.tsx:164
@@ -1465,7 +1464,7 @@ msgstr "Play"
#: src/components/dump/create/StepIndicator.tsx:21 #: src/components/dump/create/StepIndicator.tsx:21
#: src/components/layout/AppHeader.tsx:178 #: src/components/layout/AppHeader.tsx:178
#: src/components/layout/UserMenu.tsx:81 #: src/components/layout/UserMenu.tsx:85
#: src/components/profile/ProfileFollowTab.tsx:167 #: src/components/profile/ProfileFollowTab.tsx:167
#: src/components/profile/ProfilePlaylistsTab.tsx:118 #: src/components/profile/ProfilePlaylistsTab.tsx:118
#: src/pages/Search.tsx:123 #: src/pages/Search.tsx:123
@@ -1616,7 +1615,7 @@ msgstr "Role"
#: src/components/category/CategoryManager.tsx:216 #: src/components/category/CategoryManager.tsx:216
#: src/components/chat/ChatEditBox.tsx:85 #: src/components/chat/ChatEditBox.tsx:85
#: src/components/comment/CommentNode.tsx:216 #: src/components/comment/CommentNode.tsx:216
#: src/components/playlist/PlaylistEditForm.tsx:206 #: src/components/playlist/PlaylistEditForm.tsx:207
#: src/components/profile/DescriptionEditor.tsx:89 #: src/components/profile/DescriptionEditor.tsx:89
#: src/components/profile/EmailEditor.tsx:75 #: src/components/profile/EmailEditor.tsx:75
#: src/pages/Dump.tsx:437 #: src/pages/Dump.tsx:437
@@ -1625,7 +1624,7 @@ msgid "Save"
msgstr "Save" msgstr "Save"
#: src/components/comment/CommentNode.tsx:217 #: src/components/comment/CommentNode.tsx:217
#: src/components/playlist/PlaylistEditForm.tsx:203 #: src/components/playlist/PlaylistEditForm.tsx:204
#: src/components/profile/ChangePasswordModal.tsx:119 #: src/components/profile/ChangePasswordModal.tsx:119
#: src/components/profile/DescriptionEditor.tsx:86 #: src/components/profile/DescriptionEditor.tsx:86
#: src/components/profile/EmailEditor.tsx:72 #: src/components/profile/EmailEditor.tsx:72
@@ -1634,7 +1633,7 @@ msgstr "Save"
msgid "Saving…" msgid "Saving…"
msgstr "Saving…" msgstr "Saving…"
#: src/components/layout/AppHeader.tsx:190 #: src/components/layout/AppHeader.tsx:188
#: src/components/layout/SearchBar.tsx:83 #: src/components/layout/SearchBar.tsx:83
#: src/pages/Search.tsx:69 #: src/pages/Search.tsx:69
msgid "Search" msgid "Search"
@@ -1677,7 +1676,7 @@ msgstr "Send reset link"
msgid "Sending…" msgid "Sending…"
msgstr "Sending…" msgstr "Sending…"
#: src/components/layout/AppHeader.tsx:206 #: src/components/layout/AppHeader.tsx:204
msgid "Server unreachable" msgid "Server unreachable"
msgstr "Server unreachable" msgstr "Server unreachable"
@@ -1686,8 +1685,7 @@ msgstr "Server unreachable"
msgid "Set new password" msgid "Set new password"
msgstr "Set new password" msgstr "Set new password"
#: src/components/layout/UserMenu.tsx:89 #: src/components/layout/UserMenu.tsx:93
#: src/components/profile/ProfileHeader.tsx:176
#: src/pages/Settings.tsx:70 #: src/pages/Settings.tsx:70
#: src/pages/Settings.tsx:82 #: src/pages/Settings.tsx:82
msgid "Settings" msgid "Settings"
@@ -1697,7 +1695,7 @@ msgstr "Settings"
msgid "Several people are typing…" msgid "Several people are typing…"
msgstr "Several people are typing…" msgstr "Several people are typing…"
#: src/components/profile/ProfileOwnActions.tsx:108 #: src/components/profile/ProfileOwnActions.tsx:102
msgid "Single use, valid for 7 days." msgid "Single use, valid for 7 days."
msgstr "Single use, valid for 7 days." msgstr "Single use, valid for 7 days."
@@ -1786,7 +1784,7 @@ msgstr "Thumbnail"
#: src/components/dump/create/TitleField.tsx:45 #: src/components/dump/create/TitleField.tsx:45
#: src/components/playlist/PlaylistCreateForm.tsx:96 #: src/components/playlist/PlaylistCreateForm.tsx:96
#: src/components/playlist/PlaylistEditForm.tsx:159 #: src/components/playlist/PlaylistEditForm.tsx:160
#: src/pages/DumpEdit.tsx:516 #: src/pages/DumpEdit.tsx:516
msgid "Title" msgid "Title"
msgstr "Title" msgstr "Title"
@@ -1853,7 +1851,7 @@ msgstr "Unsubscribe"
msgid "Unsubscribed" msgid "Unsubscribed"
msgstr "Unsubscribed" msgstr "Unsubscribed"
#: src/components/profile/ProfileHeader.tsx:73 #: src/components/profile/ProfileHeader.tsx:72
msgid "Upload failed" msgid "Upload failed"
msgstr "Upload failed" msgstr "Upload failed"
@@ -1904,7 +1902,7 @@ msgstr "User"
msgid "User management" msgid "User management"
msgstr "User management" msgstr "User management"
#: src/components/layout/UserMenu.tsx:56 #: src/components/layout/UserMenu.tsx:60
msgid "User menu" msgid "User menu"
msgstr "User menu" msgstr "User menu"

View File

@@ -431,7 +431,7 @@ msgstr "← Retour"
msgid "← Back to all dumps" msgid "← Back to all dumps"
msgstr "← Retour à toutes les recos" msgstr "← Retour à toutes les recos"
#: src/components/profile/ProfileOwnActions.tsx:80 #: src/components/profile/ProfileOwnActions.tsx:76
msgid "+ Invite someone" msgid "+ Invite someone"
msgstr "+ Inviter quelqu'un" msgstr "+ Inviter quelqu'un"
@@ -518,11 +518,11 @@ msgstr "Ajouter un commentaire…"
msgid "Add category" msgid "Add category"
msgstr "Ajouter une catégorie" msgstr "Ajouter une catégorie"
#: src/components/profile/ProfileHeader.tsx:154 #: src/components/profile/ProfileHeader.tsx:153
msgid "Add email…" msgid "Add email…"
msgstr "Ajouter un e-mail…" msgstr "Ajouter un e-mail…"
#: src/components/shared/ImagePicker.tsx:63 #: src/components/shared/ImagePicker.tsx:66
msgid "Add image" msgid "Add image"
msgstr "Ajouter une image" msgstr "Ajouter une image"
@@ -589,7 +589,7 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo
#: src/components/comment/CommentForm.tsx:127 #: src/components/comment/CommentForm.tsx:127
#: src/components/dump/create/DumpCreateModal.tsx:752 #: src/components/dump/create/DumpCreateModal.tsx:752
#: src/components/form/FormActions.tsx:42 #: src/components/form/FormActions.tsx:42
#: src/components/playlist/PlaylistEditForm.tsx:199 #: src/components/playlist/PlaylistEditForm.tsx:200
#: src/components/profile/DescriptionEditor.tsx:97 #: src/components/profile/DescriptionEditor.tsx:97
#: src/components/profile/EmailEditor.tsx:83 #: src/components/profile/EmailEditor.tsx:83
#: src/components/shared/ConfirmModal.tsx:63 #: src/components/shared/ConfirmModal.tsx:63
@@ -607,7 +607,7 @@ msgstr "Annuler la suppression"
msgid "Cancel reply" msgid "Cancel reply"
msgstr "Annuler la réponse" msgstr "Annuler la réponse"
#: src/components/layout/AppHeader.tsx:190 #: src/components/layout/AppHeader.tsx:188
msgid "Cancel search" msgid "Cancel search"
msgstr "Annuler la recherche" msgstr "Annuler la recherche"
@@ -620,11 +620,11 @@ msgstr "Catégories"
msgid "Category" msgid "Category"
msgstr "Catégorie" msgstr "Catégorie"
#: src/components/profile/ProfileHeader.tsx:96 #: src/components/profile/ProfileHeader.tsx:95
msgid "Change avatar" msgid "Change avatar"
msgstr "Changer l'avatar" msgstr "Changer l'avatar"
#: src/components/shared/ImagePicker.tsx:63 #: src/components/shared/ImagePicker.tsx:66
msgid "Change image" msgid "Change image"
msgstr "Changer l'image" msgstr "Changer l'image"
@@ -695,11 +695,11 @@ msgstr "Confirmer le nouveau mot de passe"
msgid "Connection lost — this message may not have been sent" msgid "Connection lost — this message may not have been sent"
msgstr "Connexion perdue — ce message n'a peut-être pas été envoyé" msgstr "Connexion perdue — ce message n'a peut-être pas été envoyé"
#: src/components/profile/ProfileOwnActions.tsx:105 #: src/components/profile/ProfileOwnActions.tsx:99
msgid "Copied!" msgid "Copied!"
msgstr "Copié !" msgstr "Copié !"
#: src/components/profile/ProfileOwnActions.tsx:105 #: src/components/profile/ProfileOwnActions.tsx:99
msgid "Copy" msgid "Copy"
msgstr "Copier" msgstr "Copier"
@@ -800,7 +800,7 @@ msgid "Delete failed ({0})"
msgstr "Échec de la suppression ({0})" msgstr "Échec de la suppression ({0})"
#: src/components/playlist/PlaylistCard.tsx:142 #: src/components/playlist/PlaylistCard.tsx:142
#: src/components/playlist/PlaylistEditForm.tsx:190 #: src/components/playlist/PlaylistEditForm.tsx:191
#: src/components/profile/ProfilePlaylistsTab.tsx:156 #: src/components/profile/ProfilePlaylistsTab.tsx:156
#: src/pages/PlaylistDetail.tsx:141 #: src/pages/PlaylistDetail.tsx:141
msgid "Delete playlist" msgid "Delete playlist"
@@ -827,7 +827,7 @@ msgstr "Supprimer cette collection ? Cette action est irréversible."
msgid "deleted message" msgid "deleted message"
msgstr "message supprimé" msgstr "message supprimé"
#: src/components/playlist/PlaylistEditForm.tsx:175 #: src/components/playlist/PlaylistEditForm.tsx:176
msgid "Description" msgid "Description"
msgstr "Description" msgstr "Description"
@@ -869,7 +869,7 @@ msgstr "Déposez un fichier ici, ou collez-en un"
msgid "Drop a replacement here" msgid "Drop a replacement here"
msgstr "Déposez un fichier de remplacement ici" msgstr "Déposez un fichier de remplacement ici"
#: src/components/layout/AppHeader.tsx:209 #: src/components/layout/AppHeader.tsx:207
msgid "Dump" msgid "Dump"
msgstr "Reco" msgstr "Reco"
@@ -914,7 +914,7 @@ msgstr "Modifier {0}"
msgid "Edit description" msgid "Edit description"
msgstr "Modifier la description" msgstr "Modifier la description"
#: src/components/profile/ProfileHeader.tsx:152 #: src/components/profile/ProfileHeader.tsx:151
msgid "Edit email" msgid "Edit email"
msgstr "Modifier l'e-mail" msgstr "Modifier l'e-mail"
@@ -984,9 +984,9 @@ msgstr "Impossible de créer la collection"
msgid "Failed to delete" msgid "Failed to delete"
msgstr "Suppression échouée" msgstr "Suppression échouée"
#: src/components/profile/ProfileOwnActions.tsx:46
#: src/components/profile/ProfileOwnActions.tsx:49 #: src/components/profile/ProfileOwnActions.tsx:49
#: src/components/profile/ProfileOwnActions.tsx:52 #: src/components/profile/ProfileOwnActions.tsx:108
#: src/components/profile/ProfileOwnActions.tsx:114
msgid "Failed to generate invite" msgid "Failed to generate invite"
msgstr "Impossible de générer une invitation" msgstr "Impossible de générer une invitation"
@@ -1006,7 +1006,7 @@ msgstr "Impossible de publier le commentaire"
msgid "Failed to post reply" msgid "Failed to post reply"
msgstr "Impossible de publier la réponse" msgstr "Impossible de publier la réponse"
#: src/components/playlist/PlaylistEditForm.tsx:184 #: src/components/playlist/PlaylistEditForm.tsx:185
#: src/components/profile/DescriptionEditor.tsx:99 #: src/components/profile/DescriptionEditor.tsx:99
#: src/components/profile/EmailEditor.tsx:86 #: src/components/profile/EmailEditor.tsx:86
msgid "Failed to save" msgid "Failed to save"
@@ -1020,7 +1020,7 @@ msgstr "Échec de l’enregistrement de la catégorie"
msgid "Failed to save edit" msgid "Failed to save edit"
msgstr "Impossible d'enregistrer la modification" msgstr "Impossible d'enregistrer la modification"
#: src/components/profile/ProfileHeader.tsx:163 #: src/components/profile/ProfileHeader.tsx:162
msgid "Failed to update avatar" msgid "Failed to update avatar"
msgstr "Impossible de mettre à jour l'avatar" msgstr "Impossible de mettre à jour l'avatar"
@@ -1128,12 +1128,12 @@ msgstr "Invitation invalide"
msgid "Invalid link" msgid "Invalid link"
msgstr "Lien invalide" msgstr "Lien invalide"
#: src/components/profile/ProfileOwnActions.tsx:88 #: src/components/profile/ProfileOwnActions.tsx:82
#: src/components/profile/ProfileOwnActions.tsx:95 #: src/components/profile/ProfileOwnActions.tsx:89
msgid "Invite link" msgid "Invite link"
msgstr "Lien d'invitation" msgstr "Lien d'invitation"
#: src/components/profile/ProfileHeader.tsx:117 #: src/components/profile/ProfileHeader.tsx:116
msgid "invited by" msgid "invited by"
msgstr "invité par" msgstr "invité par"
@@ -1176,7 +1176,7 @@ msgstr "Lien ou fichier"
msgid "Live updates are temporarily disconnected. Trying to reconnect…" msgid "Live updates are temporarily disconnected. Trying to reconnect…"
msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…" msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…"
#: src/components/layout/AppHeader.tsx:242 #: src/components/layout/AppHeader.tsx:240
msgid "Live updates unavailable." msgid "Live updates unavailable."
msgstr "Mises à jour en direct indisponibles." msgstr "Mises à jour en direct indisponibles."
@@ -1219,7 +1219,7 @@ msgstr "Chargement du profil…"
msgid "Loading…" msgid "Loading…"
msgstr "Chargement…" msgstr "Chargement…"
#: src/components/layout/AppHeader.tsx:216 #: src/components/layout/AppHeader.tsx:214
#: src/pages/UserLogin.tsx:41 #: src/pages/UserLogin.tsx:41
#: src/pages/UserLogin.tsx:67 #: src/pages/UserLogin.tsx:67
#: src/pages/UserLogin.tsx:86 #: src/pages/UserLogin.tsx:86
@@ -1234,7 +1234,7 @@ msgstr "Se connecter pour aimer"
msgid "Log in to vote" msgid "Log in to vote"
msgstr "Se connecter pour voter" msgstr "Se connecter pour voter"
#: src/components/profile/ProfileHeader.tsx:188 #: src/components/layout/UserMenu.tsx:115
#: src/pages/UserPublicProfile.tsx:131 #: src/pages/UserPublicProfile.tsx:131
msgid "Log out" msgid "Log out"
msgstr "Se déconnecter" msgstr "Se déconnecter"
@@ -1251,8 +1251,7 @@ msgstr "Connexion échouée"
msgid "Lowercase letters, numbers and single hyphens only" msgid "Lowercase letters, numbers and single hyphens only"
msgstr "Minuscules, chiffres et tirets simples seulement" msgstr "Minuscules, chiffres et tirets simples seulement"
#: src/components/layout/UserMenu.tsx:98 #: src/components/layout/UserMenu.tsx:102
#: src/components/profile/ProfileHeader.tsx:180
#: src/pages/Manage.tsx:24 #: src/pages/Manage.tsx:24
#: src/pages/Manage.tsx:29 #: src/pages/Manage.tsx:29
#: src/pages/UserPublicProfile.tsx:164 #: src/pages/UserPublicProfile.tsx:164
@@ -1465,7 +1464,7 @@ msgstr "Lecture"
#: src/components/dump/create/StepIndicator.tsx:21 #: src/components/dump/create/StepIndicator.tsx:21
#: src/components/layout/AppHeader.tsx:178 #: src/components/layout/AppHeader.tsx:178
#: src/components/layout/UserMenu.tsx:81 #: src/components/layout/UserMenu.tsx:85
#: src/components/profile/ProfileFollowTab.tsx:167 #: src/components/profile/ProfileFollowTab.tsx:167
#: src/components/profile/ProfilePlaylistsTab.tsx:118 #: src/components/profile/ProfilePlaylistsTab.tsx:118
#: src/pages/Search.tsx:123 #: src/pages/Search.tsx:123
@@ -1616,7 +1615,7 @@ msgstr "Rôle"
#: src/components/category/CategoryManager.tsx:216 #: src/components/category/CategoryManager.tsx:216
#: src/components/chat/ChatEditBox.tsx:85 #: src/components/chat/ChatEditBox.tsx:85
#: src/components/comment/CommentNode.tsx:216 #: src/components/comment/CommentNode.tsx:216
#: src/components/playlist/PlaylistEditForm.tsx:206 #: src/components/playlist/PlaylistEditForm.tsx:207
#: src/components/profile/DescriptionEditor.tsx:89 #: src/components/profile/DescriptionEditor.tsx:89
#: src/components/profile/EmailEditor.tsx:75 #: src/components/profile/EmailEditor.tsx:75
#: src/pages/Dump.tsx:437 #: src/pages/Dump.tsx:437
@@ -1625,7 +1624,7 @@ msgid "Save"
msgstr "Enregistrer" msgstr "Enregistrer"
#: src/components/comment/CommentNode.tsx:217 #: src/components/comment/CommentNode.tsx:217
#: src/components/playlist/PlaylistEditForm.tsx:203 #: src/components/playlist/PlaylistEditForm.tsx:204
#: src/components/profile/ChangePasswordModal.tsx:119 #: src/components/profile/ChangePasswordModal.tsx:119
#: src/components/profile/DescriptionEditor.tsx:86 #: src/components/profile/DescriptionEditor.tsx:86
#: src/components/profile/EmailEditor.tsx:72 #: src/components/profile/EmailEditor.tsx:72
@@ -1634,7 +1633,7 @@ msgstr "Enregistrer"
msgid "Saving…" msgid "Saving…"
msgstr "Enregistrement…" msgstr "Enregistrement…"
#: src/components/layout/AppHeader.tsx:190 #: src/components/layout/AppHeader.tsx:188
#: src/components/layout/SearchBar.tsx:83 #: src/components/layout/SearchBar.tsx:83
#: src/pages/Search.tsx:69 #: src/pages/Search.tsx:69
msgid "Search" msgid "Search"
@@ -1677,7 +1676,7 @@ msgstr "Envoyer le lien de réinitialisation"
msgid "Sending…" msgid "Sending…"
msgstr "Envoi…" msgstr "Envoi…"
#: src/components/layout/AppHeader.tsx:206 #: src/components/layout/AppHeader.tsx:204
msgid "Server unreachable" msgid "Server unreachable"
msgstr "Serveur inaccessible" msgstr "Serveur inaccessible"
@@ -1686,8 +1685,7 @@ msgstr "Serveur inaccessible"
msgid "Set new password" msgid "Set new password"
msgstr "Définir un nouveau mot de passe" msgstr "Définir un nouveau mot de passe"
#: src/components/layout/UserMenu.tsx:89 #: src/components/layout/UserMenu.tsx:93
#: src/components/profile/ProfileHeader.tsx:176
#: src/pages/Settings.tsx:70 #: src/pages/Settings.tsx:70
#: src/pages/Settings.tsx:82 #: src/pages/Settings.tsx:82
msgid "Settings" msgid "Settings"
@@ -1697,7 +1695,7 @@ msgstr "Paramètres"
msgid "Several people are typing…" msgid "Several people are typing…"
msgstr "Plusieurs personnes écrivent…" msgstr "Plusieurs personnes écrivent…"
#: src/components/profile/ProfileOwnActions.tsx:108 #: src/components/profile/ProfileOwnActions.tsx:102
msgid "Single use, valid for 7 days." msgid "Single use, valid for 7 days."
msgstr "Usage unique, valable 7 jours." msgstr "Usage unique, valable 7 jours."
@@ -1786,7 +1784,7 @@ msgstr "Miniature"
#: src/components/dump/create/TitleField.tsx:45 #: src/components/dump/create/TitleField.tsx:45
#: src/components/playlist/PlaylistCreateForm.tsx:96 #: src/components/playlist/PlaylistCreateForm.tsx:96
#: src/components/playlist/PlaylistEditForm.tsx:159 #: src/components/playlist/PlaylistEditForm.tsx:160
#: src/pages/DumpEdit.tsx:516 #: src/pages/DumpEdit.tsx:516
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
@@ -1853,7 +1851,7 @@ msgstr "Se désabonner"
msgid "Unsubscribed" msgid "Unsubscribed"
msgstr "Désabonné" msgstr "Désabonné"
#: src/components/profile/ProfileHeader.tsx:73 #: src/components/profile/ProfileHeader.tsx:72
msgid "Upload failed" msgid "Upload failed"
msgstr "Envoi échoué" msgstr "Envoi échoué"
@@ -1904,7 +1902,7 @@ msgstr "Utilisateur"
msgid "User management" msgid "User management"
msgstr "Gestion utilisateur" msgstr "Gestion utilisateur"
#: src/components/layout/UserMenu.tsx:56 #: src/components/layout/UserMenu.tsx:60
msgid "User menu" msgid "User menu"
msgstr "Menu utilisateur" msgstr "Menu utilisateur"

View File

@@ -2186,54 +2186,28 @@ body.has-player .chat-fab {
} }
/* ── Dense dump cards in the two-column profile view ── /* ── Dense dump cards in the two-column profile view ──
The default card is proportioned for the 860px feed. Dropped into a ~400px A ~400px column is narrow enough for the narrow card's grid (the
column it falls apart: the title wraps four lines deep, the meta row stacks, `@container dump-card` rules), which places the thumb, the text and the
every category chip takes a line of its own and the vote button floats in vote; placing them here as well, more specifically, broke that grid. This
the middle of the resulting void. Below is the same card re-proportioned for only makes the card denser for a column of a dozen: a smaller thumb, the
the column — smaller thumb, title clamped to two lines, no teaser, and meta title clamped to two lines and smaller type. Scoped to the width where the
plus chips sharing the line under the title. Scoped to the width where the columns split; narrower than that the feed is full-width again. */
columns actually split; narrower than that the feed is full-width again and
the regular (then the phone) layout applies. */
@media (min-width: 900px) { @media (min-width: 900px) {
.profile-columns .dump-feed { .profile-columns .dump-feed {
padding: 0.75rem 0 0; padding: 0.75rem 0 0;
gap: 0.5rem; gap: 0.5rem;
} }
/* preview | body | vote, all top-aligned so the count sits on the title
line instead of drifting to the card's vertical middle. */
.profile-columns .dump-card-inner { .profile-columns .dump-card-inner {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: start;
gap: 0.55rem;
padding: 0.5rem 0.6rem; padding: 0.5rem 0.6rem;
} }
.profile-columns .dump-card-preview { .profile-columns .dump-card-preview {
grid-area: 1 / 1;
width: 88px; width: 88px;
height: 50px; height: 50px;
} }
.profile-columns .dump-card-vote {
grid-area: 1 / 3;
align-self: start;
}
/* Row layout with a full-width title: the title takes a line to itself, then
meta and chips flow onto the next one and only wrap when they must. */
.profile-columns .dump-card-body {
grid-area: 1 / 2;
margin-left: 0;
flex-direction: row;
flex-wrap: wrap;
align-items: baseline;
gap: 0.15rem 0.45rem;
}
.profile-columns .dump-card-title { .profile-columns .dump-card-title {
flex: 1 0 100%;
font-size: var(--text-md); font-size: var(--text-md);
line-height: 1.3; line-height: 1.3;
display: -webkit-box; display: -webkit-box;
@@ -2243,22 +2217,6 @@ body.has-player .chat-fab {
overflow: hidden; overflow: hidden;
} }
/* The teaser is the first thing to go, the same call the phone layout makes. */
.profile-columns .dump-card-comment {
display: none;
}
.profile-columns .dump-card-meta,
.profile-columns .dump-card-categories {
margin-top: 0;
}
.profile-columns .dump-card-meta {
gap: 0.4rem;
/* Keeps "3 weeks ago" and "1 comment" on one line each. */
white-space: nowrap;
}
.profile-columns .dump-card-date, .profile-columns .dump-card-date,
.profile-columns .dump-card-comment-count { .profile-columns .dump-card-comment-count {
font-size: var(--text-2xs); font-size: var(--text-2xs);
@@ -2341,10 +2299,6 @@ body.has-player .chat-fab {
background: var(--color-success); background: var(--color-success);
} }
.profile-header .btn-border {
margin-top: 0.5rem;
}
.profile-header .follow-btn { .profile-header .follow-btn {
margin-top: 0.5rem; margin-top: 0.5rem;
} }
@@ -2476,8 +2430,8 @@ body.has-player .chat-fab {
color: var(--color-accent); color: var(--color-accent);
} }
/* Grid sized by the button row; the invite panel below only fills that /* Grid sized by the button; the invite panel below only fills that width
width (width: 0 + min-width: 100% keeps it out of the sizing). */ (width: 0 + min-width: 100% keeps it out of the sizing). */
.profile-own-actions { .profile-own-actions {
display: grid; display: grid;
width: fit-content; width: fit-content;
@@ -2485,15 +2439,6 @@ body.has-player .chat-fab {
gap: 0.5rem; gap: 0.5rem;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.profile-own-actions-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
}
.profile-own-actions .btn-border {
margin-top: 0;
}
.invite-btn { .invite-btn {
padding: 0.3rem 0.9rem; padding: 0.3rem 0.9rem;
@@ -3121,18 +3066,6 @@ body.has-player .chat-fab {
} }
} }
/* Avatar menu — visible only below the text-links breakpoint */
.nav-compact {
display: flex;
align-items: center;
}
@media (min-width: 1150px) {
.nav-compact {
display: none;
}
}
.app-header-user { .app-header-user {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -3150,7 +3083,7 @@ body.has-player .chat-fab {
background: var(--color-header-user-bg-hover); background: var(--color-header-user-bg-hover);
} }
/* ── User menu (compact nav) ── */ /* ── User menu ── */
/* A flex box, so the inline-flex trigger leaves no line-box gap under it /* A flex box, so the inline-flex trigger leaves no line-box gap under it
that would sit it higher than the buttons beside it. */ that would sit it higher than the buttons beside it. */
.user-menu { .user-menu {
@@ -3206,6 +3139,7 @@ body.has-player .chat-fab {
color: var(--color-text); color: var(--color-text);
font-size: var(--text-md); font-size: var(--text-md);
font-weight: 600; font-weight: 600;
white-space: nowrap;
transition: background var(--dur-fast); transition: background var(--dur-fast);
} }
@@ -3213,6 +3147,33 @@ body.has-player .chat-fab {
background: var(--color-header-user-bg-hover); background: var(--color-header-user-bg-hover);
} }
.user-menu-separator {
margin: 0.15rem 0.35rem;
border-top: var(--border-width) solid var(--color-border-subtle);
}
/* Log out is a button among links: it drops the button's own border,
background and weight, and is muted until hovered, as ending the session
should be. Two classes, to outweigh `.app-header-nav button`, which
centres it, and the themes' `[data-style] button`. */
.user-menu .user-menu-item--logout {
display: block;
border: none;
border-radius: var(--radius-md);
background: transparent;
color: var(--color-text-muted);
font-family: inherit;
font-size: var(--text-md);
font-weight: 600;
text-align: left;
line-height: inherit;
}
.user-menu .user-menu-item--logout:hover {
background: var(--color-header-user-bg-hover);
color: var(--color-danger);
}
.user-list-popover { .user-list-popover {
/* position/top/bottom/left/max-height set inline — anchored to the /* position/top/bottom/left/max-height set inline — anchored to the
trigger's viewport rect, flipping above it when there isn't room below */ trigger's viewport rect, flipping above it when there isn't room below */

View File

@@ -159,8 +159,8 @@ export function AppHeader(
<nav className="app-header-nav"> <nav className="app-header-nav">
{ {
/* The user's own links lead the row, so the icons after them run /* The user's own links lead the row, so the icons after them run
unbroken to the "+". CSS shows the text links or the avatar's unbroken to the "+": the text links on wide screens, then the
menu, by width. */ avatar's menu at every width. */
} }
{user && ( {user && (
<> <>
@@ -178,9 +178,7 @@ export function AppHeader(
<Trans>Playlists</Trans> <Trans>Playlists</Trans>
</Link> </Link>
</div> </div>
<div className="nav-compact">
<UserMenu user={user} /> <UserMenu user={user} />
</div>
</> </>
)} )}
<button <button

View File

@@ -1,6 +1,7 @@
/** /**
* Renders the avatar menu that replaces the header's profile and playlists * Renders the avatar menu that leads the header's icon row at every width: it
* links on small screens, and leads to the settings and the site's tools. * holds the profile and playlists links (on small screens, the only ones), the
* settings, the site's tools, and logging out.
* *
* @module * @module
*/ */
@@ -13,6 +14,7 @@ import { Avatar } from "../shared/Avatar.tsx";
import type { SelfUser } from "../../../shared/user.ts"; import type { SelfUser } from "../../../shared/user.ts";
import { can } from "../../utils/auth/permissions.ts"; import { can } from "../../utils/auth/permissions.ts";
import { userPath } from "../../../shared/paths.ts"; import { userPath } from "../../../shared/paths.ts";
import { useAuth } from "../../hooks/auth/useAuth.ts";
/** Passed to {@link UserMenu} by `AppHeader`, for a signed-in user. */ /** Passed to {@link UserMenu} by `AppHeader`, for a signed-in user. */
export interface UserMenuProps { export interface UserMenuProps {
@@ -21,10 +23,12 @@ export interface UserMenuProps {
/** /**
* Renders the signed-in user's avatar, which opens a menu with links to their * Renders the signed-in user's avatar, which opens a menu with links to their
* profile, their playlists and `/settings`, and to `/manage` for those who * profile, their playlists and `/settings`, to `/manage` for those who hold
* hold `category:manage`. A click outside or Escape closes it. * `category:manage`, and a Log out button. A click outside or Escape closes
* it.
*/ */
export function UserMenu({ user }: UserMenuProps): React.JSX.Element { export function UserMenu({ user }: UserMenuProps): React.JSX.Element {
const { logout } = useAuth();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
@@ -98,6 +102,18 @@ export function UserMenu({ user }: UserMenuProps): React.JSX.Element {
<Trans>Manage</Trans> <Trans>Manage</Trans>
</Link> </Link>
)} )}
<div className="user-menu-separator" role="separator" />
<button
type="button"
className="user-menu-item user-menu-item--logout"
role="menuitem"
onClick={() => {
setOpen(false);
logout();
}}
>
<Trans>Log out</Trans>
</button>
</div> </div>
)} )}
</div> </div>

View File

@@ -7,7 +7,6 @@
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import { Link } from "react-router"; import { Link } from "react-router";
import { can } from "../../utils/auth/permissions.ts";
import { t } from "@lingui/core/macro"; import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro"; import { Trans } from "@lingui/react/macro";
import { API_URL } from "../../config/api.ts"; import { API_URL } from "../../config/api.ts";
@@ -36,13 +35,13 @@ export interface ProfileHeaderProps {
/** /**
* Renders the avatar, username, role and inviter. Visitors get a follow * Renders the avatar, username, role and inviter. Visitors get a follow
* button. On their own profile, users change their avatar * button. On their own profile, users change their avatar
* (`PUT /api/me/avatar`) and email, invite someone, reach their settings (and * (`PUT /api/me/avatar`) and email, and invite someone; their settings, the
* the site's tools, with `category:manage`) and log out. * site's tools and logging out are in the header's avatar menu.
*/ */
export function ProfileHeader( export function ProfileHeader(
{ user, isOwnProfile, onUserChange }: ProfileHeaderProps, { user, isOwnProfile, onUserChange }: ProfileHeaderProps,
): React.JSX.Element { ): React.JSX.Element {
const { user: me, authFetch, updateSession, logout } = useAuth(); const { user: me, authFetch, updateSession } = useAuth();
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
const [avatarError, setAvatarError] = useState<string | null>(null); const [avatarError, setAvatarError] = useState<string | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
@@ -170,25 +169,7 @@ export function ProfileHeader(
targetUsername={user.username} targetUsername={user.username}
/> />
)} )}
{isOwnProfile && ( {isOwnProfile && <ProfileOwnActions />}
<ProfileOwnActions>
<Link to="/settings" className="btn-border">
<Trans>Settings</Trans>
</Link>
{can(me, "category:manage") && (
<Link to="/manage" className="btn-border">
<Trans>Manage</Trans>
</Link>
)}
<button
type="button"
className="btn-border btn-border--danger"
onClick={logout}
>
<Trans>Log out</Trans>
</button>
</ProfileOwnActions>
)}
</div> </div>
</div> </div>
); );

View File

@@ -1,6 +1,6 @@
/** /**
* Renders the actions of the signed-in user's own profile header, which invite * Renders the action of the signed-in user's own profile header, which invites
* someone, beside the log out button the header adds. * someone.
* *
* @module * @module
*/ */
@@ -14,13 +14,10 @@ import { useHeightTransition } from "../../hooks/shared/useHeightTransition.ts";
import { ErrorCard } from "../shared/ErrorCard.tsx"; import { ErrorCard } from "../shared/ErrorCard.tsx";
/** /**
* Renders the "Invite someone" button, then `children`, the log out button. * Renders the "Invite someone" button, which opens a panel with an invite link.
* Each opening of the panel creates a new single-use invite link, for * Each opening of the panel creates a new single-use link, for `/register`.
* `/register`.
*/ */
export function ProfileOwnActions( export function ProfileOwnActions(): React.JSX.Element {
{ children }: { children: React.ReactNode },
): React.JSX.Element {
const { authFetch } = useAuth(); const { authFetch } = useAuth();
const [inviteUrl, setInviteUrl] = useState<string | null>(null); const [inviteUrl, setInviteUrl] = useState<string | null>(null);
const [pending, setPending] = useState(false); const [pending, setPending] = useState(false);
@@ -69,7 +66,6 @@ export function ProfileOwnActions(
return ( return (
<div className="profile-own-actions" ref={ref}> <div className="profile-own-actions" ref={ref}>
<div className="profile-own-actions-row">
<button <button
type="button" type="button"
className="invite-btn" className="invite-btn"
@@ -79,8 +75,6 @@ export function ProfileOwnActions(
> >
<Trans>+ Invite someone</Trans> <Trans>+ Invite someone</Trans>
</button> </button>
{children}
</div>
{inviteUrl && ( {inviteUrl && (
<div <div
className="invite-panel animate-in" className="invite-panel animate-in"

View File

@@ -3,7 +3,8 @@ import { screen } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event"; import { userEvent } from "@testing-library/user-event";
import { UserMenu } from "../../../components/layout/UserMenu.tsx"; import { UserMenu } from "../../../components/layout/UserMenu.tsx";
import { makeSession } from "../../fixtures.ts"; import { makeSession } from "../../fixtures.ts";
import { currentPath, renderWithContexts } from "../../render.tsx"; import { currentPath, renderApp, renderWithContexts } from "../../render.tsx";
import { AUTH_STORAGE_KEY } from "../../../utils/auth/storage.ts";
describe("UserMenu", () => { describe("UserMenu", () => {
test("opens, links to profile and playlists, closes on Escape and outside", async () => { test("opens, links to profile and playlists, closes on Escape and outside", async () => {
@@ -41,4 +42,26 @@ describe("UserMenu", () => {
expect(currentPath()).toBe("/@ann"); expect(currentPath()).toBe("/@ann");
expect(screen.queryByRole("menu")).not.toBeInTheDocument(); expect(screen.queryByRole("menu")).not.toBeInTheDocument();
}); });
test("links to /manage for those who hold category:manage", async () => {
const user = userEvent.setup();
renderWithContexts(
<UserMenu user={makeSession({ role: "admin" }).user} />,
);
await user.click(screen.getByRole("button", { name: "User menu" }));
expect(screen.getByRole("menuitem", { name: "Manage" })).toHaveAttribute(
"href",
"/manage",
);
});
test("logs out", async () => {
const session = makeSession({ username: "ann" });
renderApp(<UserMenu user={session.user} />, { session });
const user = userEvent.setup();
await user.click(screen.getByRole("button", { name: "User menu" }));
await user.click(screen.getByRole("menuitem", { name: "Log out" }));
expect(localStorage.getItem(AUTH_STORAGE_KEY)).toBeNull();
expect(screen.queryByRole("menu")).not.toBeInTheDocument();
});
}); });

View File

@@ -75,8 +75,6 @@ describe("UserPublicProfile", () => {
expect(dumpsAll).toHaveAttribute("href", "/@ann/dumps"); expect(dumpsAll).toHaveAttribute("href", "/@ann/dumps");
expect(votesAll).toHaveAttribute("href", "/@ann/upvoted"); expect(votesAll).toHaveAttribute("href", "/@ann/upvoted");
await waitFor(() => expect(document.title).toMatch(/^ann · /)); await waitFor(() => expect(document.title).toMatch(/^ann · /));
expect(screen.queryByRole("link", { name: "Settings" })).not
.toBeInTheDocument();
// The lists are read by the user's id, which a rename doesn't change. // The lists are read by the user's id, which a rename doesn't change.
expect(calls("GET /api/users/:u/dumps")[0].path).toBe( expect(calls("GET /api/users/:u/dumps")[0].path).toBe(
"/api/users/ann/dumps", "/api/users/ann/dumps",
@@ -275,11 +273,11 @@ describe("UserPublicProfile", () => {
expect(within(bob).getByRole("link", { name: /@cy/ })).toBeInTheDocument(); expect(within(bob).getByRole("link", { name: /@cy/ })).toBeInTheDocument();
}); });
test("visitors can follow; a settings link is only on your own profile", async () => { test("visitors can follow; inviting is only on your own profile", async () => {
open({ session: makeSession({ id: "bob" }) }); open({ session: makeSession({ id: "bob" }) });
expect(await screen.findByRole("button", { name: "Follow ann" })) expect(await screen.findByRole("button", { name: "Follow ann" }))
.toBeInTheDocument(); .toBeInTheDocument();
expect(screen.queryByRole("link", { name: "Settings" })).not expect(screen.queryByRole("button", { name: "+ Invite someone" })).not
.toBeInTheDocument(); .toBeInTheDocument();
}); });
@@ -414,29 +412,18 @@ describe("UserPublicProfile", () => {
} }
}); });
test("your settings, and the site's tools for an admin, are a link away", async () => { test("an admin's own profile has no manage tab: that's for others'", async () => {
open({ open({
session: { ...annSession, user: { ...annSession.user, role: "admin" } }, session: { ...annSession, user: { ...annSession.user, role: "admin" } },
user: { ...ann, role: "admin" }, user: { ...ann, role: "admin" },
}); });
expect(await screen.findByRole("link", { name: "Settings" })) expect(await screen.findByRole("button", { name: "+ Invite someone" }))
.toHaveAttribute("href", "/settings"); .toBeInTheDocument();
expect(screen.getByRole("link", { name: "Manage" })).toHaveAttribute(
"href",
"/manage",
);
// Their own profile has no manage tab: that's for others'.
expect( expect(
within(document.querySelector(".profile-tabs") as HTMLElement) within(document.querySelector(".profile-tabs") as HTMLElement)
.queryByRole("button", { name: "Manage" }), .queryByRole("button", { name: "Manage" }),
).not.toBeInTheDocument(); ).not.toBeInTheDocument();
}); });
test("log out", async () => {
const { user } = open({ session: annSession });
await user.click(await screen.findByRole("button", { name: "Log out" }));
expect(localStorage.getItem(AUTH_STORAGE_KEY)).toBeNull();
});
}); });
describe("manage", () => { describe("manage", () => {

View File

@@ -106,6 +106,7 @@
[data-style="brutalist"] .audio-player-vol-btn, [data-style="brutalist"] .audio-player-vol-btn,
[data-style="brutalist"] .rich-content-thumbnail-btn, [data-style="brutalist"] .rich-content-thumbnail-btn,
[data-style="brutalist"] .user-menu-trigger, [data-style="brutalist"] .user-menu-trigger,
[data-style="brutalist"] .user-menu-item,
[data-style="brutalist"] .auth-link-btn, [data-style="brutalist"] .auth-link-btn,
[data-style="brutalist"] .form-cancel, [data-style="brutalist"] .form-cancel,
[data-style="brutalist"] .form-label-action { [data-style="brutalist"] .form-label-action {
@@ -115,6 +116,8 @@
[data-style="brutalist"] .user-menu-trigger:hover:not(:disabled), [data-style="brutalist"] .user-menu-trigger:hover:not(:disabled),
[data-style="brutalist"] .user-menu-trigger:active, [data-style="brutalist"] .user-menu-trigger:active,
[data-style="brutalist"] .user-menu-item:hover:not(:disabled),
[data-style="brutalist"] .user-menu-item:active,
[data-style="brutalist"] .auth-link-btn:hover:not(:disabled), [data-style="brutalist"] .auth-link-btn:hover:not(:disabled),
[data-style="brutalist"] .auth-link-btn:active, [data-style="brutalist"] .auth-link-btn:active,
[data-style="brutalist"] .form-cancel:hover:not(:disabled), [data-style="brutalist"] .form-cancel:hover:not(:disabled),

View File

@@ -217,6 +217,7 @@
[data-style="geocities"] .audio-player-vol-btn, [data-style="geocities"] .audio-player-vol-btn,
[data-style="geocities"] .rich-content-thumbnail-btn, [data-style="geocities"] .rich-content-thumbnail-btn,
[data-style="geocities"] .user-menu-trigger, [data-style="geocities"] .user-menu-trigger,
[data-style="geocities"] .user-menu-item,
[data-style="geocities"] .auth-link-btn, [data-style="geocities"] .auth-link-btn,
[data-style="geocities"] .form-cancel, [data-style="geocities"] .form-cancel,
[data-style="geocities"] .form-label-action, [data-style="geocities"] .form-label-action,