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:
@@ -431,7 +431,7 @@ msgstr "← Back"
|
||||
msgid "← Back to all dumps"
|
||||
msgstr "← Back to all dumps"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:80
|
||||
#: src/components/profile/ProfileOwnActions.tsx:76
|
||||
msgid "+ Invite someone"
|
||||
msgstr "+ Invite someone"
|
||||
|
||||
@@ -518,11 +518,11 @@ msgstr "Add a comment…"
|
||||
msgid "Add category"
|
||||
msgstr "Add category"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:154
|
||||
#: src/components/profile/ProfileHeader.tsx:153
|
||||
msgid "Add email…"
|
||||
msgstr "Add email…"
|
||||
|
||||
#: src/components/shared/ImagePicker.tsx:63
|
||||
#: src/components/shared/ImagePicker.tsx:66
|
||||
msgid "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/dump/create/DumpCreateModal.tsx:752
|
||||
#: 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/EmailEditor.tsx:83
|
||||
#: src/components/shared/ConfirmModal.tsx:63
|
||||
@@ -607,7 +607,7 @@ msgstr "Cancel removal"
|
||||
msgid "Cancel reply"
|
||||
msgstr "Cancel reply"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:190
|
||||
#: src/components/layout/AppHeader.tsx:188
|
||||
msgid "Cancel search"
|
||||
msgstr "Cancel search"
|
||||
|
||||
@@ -620,11 +620,11 @@ msgstr "Categories"
|
||||
msgid "Category"
|
||||
msgstr "Category"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:96
|
||||
#: src/components/profile/ProfileHeader.tsx:95
|
||||
msgid "Change avatar"
|
||||
msgstr "Change avatar"
|
||||
|
||||
#: src/components/shared/ImagePicker.tsx:63
|
||||
#: src/components/shared/ImagePicker.tsx:66
|
||||
msgid "Change image"
|
||||
msgstr "Change image"
|
||||
|
||||
@@ -695,11 +695,11 @@ msgstr "Confirm new password"
|
||||
msgid "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!"
|
||||
msgstr "Copied!"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:105
|
||||
#: src/components/profile/ProfileOwnActions.tsx:99
|
||||
msgid "Copy"
|
||||
msgstr "Copy"
|
||||
|
||||
@@ -800,7 +800,7 @@ msgid "Delete failed ({0})"
|
||||
msgstr "Delete failed ({0})"
|
||||
|
||||
#: 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/pages/PlaylistDetail.tsx:141
|
||||
msgid "Delete playlist"
|
||||
@@ -827,7 +827,7 @@ msgstr "Delete this playlist? This cannot be undone."
|
||||
msgid "deleted message"
|
||||
msgstr "deleted message"
|
||||
|
||||
#: src/components/playlist/PlaylistEditForm.tsx:175
|
||||
#: src/components/playlist/PlaylistEditForm.tsx:176
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
@@ -869,7 +869,7 @@ msgstr "Drop a file here, or paste one"
|
||||
msgid "Drop a replacement here"
|
||||
msgstr "Drop a replacement here"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:209
|
||||
#: src/components/layout/AppHeader.tsx:207
|
||||
msgid "Dump"
|
||||
msgstr "Dump"
|
||||
|
||||
@@ -914,7 +914,7 @@ msgstr "Edit {0}"
|
||||
msgid "Edit description"
|
||||
msgstr "Edit description"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:152
|
||||
#: src/components/profile/ProfileHeader.tsx:151
|
||||
msgid "Edit email"
|
||||
msgstr "Edit email"
|
||||
|
||||
@@ -984,9 +984,9 @@ msgstr "Failed to create playlist"
|
||||
msgid "Failed to delete"
|
||||
msgstr "Failed to delete"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:46
|
||||
#: src/components/profile/ProfileOwnActions.tsx:49
|
||||
#: src/components/profile/ProfileOwnActions.tsx:52
|
||||
#: src/components/profile/ProfileOwnActions.tsx:114
|
||||
#: src/components/profile/ProfileOwnActions.tsx:108
|
||||
msgid "Failed to generate invite"
|
||||
msgstr "Failed to generate invite"
|
||||
|
||||
@@ -1006,7 +1006,7 @@ msgstr "Failed to post comment"
|
||||
msgid "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/EmailEditor.tsx:86
|
||||
msgid "Failed to save"
|
||||
@@ -1020,7 +1020,7 @@ msgstr "Failed to save category"
|
||||
msgid "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"
|
||||
msgstr "Failed to update avatar"
|
||||
|
||||
@@ -1128,12 +1128,12 @@ msgstr "Invalid invite"
|
||||
msgid "Invalid link"
|
||||
msgstr "Invalid link"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:88
|
||||
#: src/components/profile/ProfileOwnActions.tsx:95
|
||||
#: src/components/profile/ProfileOwnActions.tsx:82
|
||||
#: src/components/profile/ProfileOwnActions.tsx:89
|
||||
msgid "Invite link"
|
||||
msgstr "Invite link"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:117
|
||||
#: src/components/profile/ProfileHeader.tsx:116
|
||||
msgid "invited by"
|
||||
msgstr "invited by"
|
||||
|
||||
@@ -1176,7 +1176,7 @@ msgstr "Link or file"
|
||||
msgid "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."
|
||||
msgstr "Live updates unavailable."
|
||||
|
||||
@@ -1219,7 +1219,7 @@ msgstr "Loading profile…"
|
||||
msgid "Loading…"
|
||||
msgstr "Loading…"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:216
|
||||
#: src/components/layout/AppHeader.tsx:214
|
||||
#: src/pages/UserLogin.tsx:41
|
||||
#: src/pages/UserLogin.tsx:67
|
||||
#: src/pages/UserLogin.tsx:86
|
||||
@@ -1234,7 +1234,7 @@ msgstr "Log in to like"
|
||||
msgid "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
|
||||
msgid "Log out"
|
||||
msgstr "Log out"
|
||||
@@ -1251,8 +1251,7 @@ msgstr "Login failed"
|
||||
msgid "Lowercase letters, numbers and single hyphens only"
|
||||
msgstr "Lowercase letters, numbers and single hyphens only"
|
||||
|
||||
#: src/components/layout/UserMenu.tsx:98
|
||||
#: src/components/profile/ProfileHeader.tsx:180
|
||||
#: src/components/layout/UserMenu.tsx:102
|
||||
#: src/pages/Manage.tsx:24
|
||||
#: src/pages/Manage.tsx:29
|
||||
#: src/pages/UserPublicProfile.tsx:164
|
||||
@@ -1465,7 +1464,7 @@ msgstr "Play"
|
||||
|
||||
#: src/components/dump/create/StepIndicator.tsx:21
|
||||
#: 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/ProfilePlaylistsTab.tsx:118
|
||||
#: src/pages/Search.tsx:123
|
||||
@@ -1616,7 +1615,7 @@ msgstr "Role"
|
||||
#: src/components/category/CategoryManager.tsx:216
|
||||
#: src/components/chat/ChatEditBox.tsx:85
|
||||
#: 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/EmailEditor.tsx:75
|
||||
#: src/pages/Dump.tsx:437
|
||||
@@ -1625,7 +1624,7 @@ msgid "Save"
|
||||
msgstr "Save"
|
||||
|
||||
#: 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/DescriptionEditor.tsx:86
|
||||
#: src/components/profile/EmailEditor.tsx:72
|
||||
@@ -1634,7 +1633,7 @@ msgstr "Save"
|
||||
msgid "Saving…"
|
||||
msgstr "Saving…"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:190
|
||||
#: src/components/layout/AppHeader.tsx:188
|
||||
#: src/components/layout/SearchBar.tsx:83
|
||||
#: src/pages/Search.tsx:69
|
||||
msgid "Search"
|
||||
@@ -1677,7 +1676,7 @@ msgstr "Send reset link"
|
||||
msgid "Sending…"
|
||||
msgstr "Sending…"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:206
|
||||
#: src/components/layout/AppHeader.tsx:204
|
||||
msgid "Server unreachable"
|
||||
msgstr "Server unreachable"
|
||||
|
||||
@@ -1686,8 +1685,7 @@ msgstr "Server unreachable"
|
||||
msgid "Set new password"
|
||||
msgstr "Set new password"
|
||||
|
||||
#: src/components/layout/UserMenu.tsx:89
|
||||
#: src/components/profile/ProfileHeader.tsx:176
|
||||
#: src/components/layout/UserMenu.tsx:93
|
||||
#: src/pages/Settings.tsx:70
|
||||
#: src/pages/Settings.tsx:82
|
||||
msgid "Settings"
|
||||
@@ -1697,7 +1695,7 @@ msgstr "Settings"
|
||||
msgid "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."
|
||||
msgstr "Single use, valid for 7 days."
|
||||
|
||||
@@ -1786,7 +1784,7 @@ msgstr "Thumbnail"
|
||||
|
||||
#: src/components/dump/create/TitleField.tsx:45
|
||||
#: src/components/playlist/PlaylistCreateForm.tsx:96
|
||||
#: src/components/playlist/PlaylistEditForm.tsx:159
|
||||
#: src/components/playlist/PlaylistEditForm.tsx:160
|
||||
#: src/pages/DumpEdit.tsx:516
|
||||
msgid "Title"
|
||||
msgstr "Title"
|
||||
@@ -1853,7 +1851,7 @@ msgstr "Unsubscribe"
|
||||
msgid "Unsubscribed"
|
||||
msgstr "Unsubscribed"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:73
|
||||
#: src/components/profile/ProfileHeader.tsx:72
|
||||
msgid "Upload failed"
|
||||
msgstr "Upload failed"
|
||||
|
||||
@@ -1904,7 +1902,7 @@ msgstr "User"
|
||||
msgid "User management"
|
||||
msgstr "User management"
|
||||
|
||||
#: src/components/layout/UserMenu.tsx:56
|
||||
#: src/components/layout/UserMenu.tsx:60
|
||||
msgid "User menu"
|
||||
msgstr "User menu"
|
||||
|
||||
|
||||
@@ -431,7 +431,7 @@ msgstr "← Retour"
|
||||
msgid "← Back to all dumps"
|
||||
msgstr "← Retour à toutes les recos"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:80
|
||||
#: src/components/profile/ProfileOwnActions.tsx:76
|
||||
msgid "+ Invite someone"
|
||||
msgstr "+ Inviter quelqu'un"
|
||||
|
||||
@@ -518,11 +518,11 @@ msgstr "Ajouter un commentaire…"
|
||||
msgid "Add category"
|
||||
msgstr "Ajouter une catégorie"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:154
|
||||
#: src/components/profile/ProfileHeader.tsx:153
|
||||
msgid "Add email…"
|
||||
msgstr "Ajouter un e-mail…"
|
||||
|
||||
#: src/components/shared/ImagePicker.tsx:63
|
||||
#: src/components/shared/ImagePicker.tsx:66
|
||||
msgid "Add 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/dump/create/DumpCreateModal.tsx:752
|
||||
#: 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/EmailEditor.tsx:83
|
||||
#: src/components/shared/ConfirmModal.tsx:63
|
||||
@@ -607,7 +607,7 @@ msgstr "Annuler la suppression"
|
||||
msgid "Cancel reply"
|
||||
msgstr "Annuler la réponse"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:190
|
||||
#: src/components/layout/AppHeader.tsx:188
|
||||
msgid "Cancel search"
|
||||
msgstr "Annuler la recherche"
|
||||
|
||||
@@ -620,11 +620,11 @@ msgstr "Catégories"
|
||||
msgid "Category"
|
||||
msgstr "Catégorie"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:96
|
||||
#: src/components/profile/ProfileHeader.tsx:95
|
||||
msgid "Change avatar"
|
||||
msgstr "Changer l'avatar"
|
||||
|
||||
#: src/components/shared/ImagePicker.tsx:63
|
||||
#: src/components/shared/ImagePicker.tsx:66
|
||||
msgid "Change 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"
|
||||
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!"
|
||||
msgstr "Copié !"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:105
|
||||
#: src/components/profile/ProfileOwnActions.tsx:99
|
||||
msgid "Copy"
|
||||
msgstr "Copier"
|
||||
|
||||
@@ -800,7 +800,7 @@ msgid "Delete failed ({0})"
|
||||
msgstr "Échec de la suppression ({0})"
|
||||
|
||||
#: 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/pages/PlaylistDetail.tsx:141
|
||||
msgid "Delete playlist"
|
||||
@@ -827,7 +827,7 @@ msgstr "Supprimer cette collection ? Cette action est irréversible."
|
||||
msgid "deleted message"
|
||||
msgstr "message supprimé"
|
||||
|
||||
#: src/components/playlist/PlaylistEditForm.tsx:175
|
||||
#: src/components/playlist/PlaylistEditForm.tsx:176
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
@@ -869,7 +869,7 @@ msgstr "Déposez un fichier ici, ou collez-en un"
|
||||
msgid "Drop a replacement here"
|
||||
msgstr "Déposez un fichier de remplacement ici"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:209
|
||||
#: src/components/layout/AppHeader.tsx:207
|
||||
msgid "Dump"
|
||||
msgstr "Reco"
|
||||
|
||||
@@ -914,7 +914,7 @@ msgstr "Modifier {0}"
|
||||
msgid "Edit description"
|
||||
msgstr "Modifier la description"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:152
|
||||
#: src/components/profile/ProfileHeader.tsx:151
|
||||
msgid "Edit email"
|
||||
msgstr "Modifier l'e-mail"
|
||||
|
||||
@@ -984,9 +984,9 @@ msgstr "Impossible de créer la collection"
|
||||
msgid "Failed to delete"
|
||||
msgstr "Suppression échouée"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:46
|
||||
#: src/components/profile/ProfileOwnActions.tsx:49
|
||||
#: src/components/profile/ProfileOwnActions.tsx:52
|
||||
#: src/components/profile/ProfileOwnActions.tsx:114
|
||||
#: src/components/profile/ProfileOwnActions.tsx:108
|
||||
msgid "Failed to generate invite"
|
||||
msgstr "Impossible de générer une invitation"
|
||||
|
||||
@@ -1006,7 +1006,7 @@ msgstr "Impossible de publier le commentaire"
|
||||
msgid "Failed to post reply"
|
||||
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/EmailEditor.tsx:86
|
||||
msgid "Failed to save"
|
||||
@@ -1020,7 +1020,7 @@ msgstr "Échec de l’enregistrement de la catégorie"
|
||||
msgid "Failed to save edit"
|
||||
msgstr "Impossible d'enregistrer la modification"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:163
|
||||
#: src/components/profile/ProfileHeader.tsx:162
|
||||
msgid "Failed to update avatar"
|
||||
msgstr "Impossible de mettre à jour l'avatar"
|
||||
|
||||
@@ -1128,12 +1128,12 @@ msgstr "Invitation invalide"
|
||||
msgid "Invalid link"
|
||||
msgstr "Lien invalide"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:88
|
||||
#: src/components/profile/ProfileOwnActions.tsx:95
|
||||
#: src/components/profile/ProfileOwnActions.tsx:82
|
||||
#: src/components/profile/ProfileOwnActions.tsx:89
|
||||
msgid "Invite link"
|
||||
msgstr "Lien d'invitation"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:117
|
||||
#: src/components/profile/ProfileHeader.tsx:116
|
||||
msgid "invited by"
|
||||
msgstr "invité par"
|
||||
|
||||
@@ -1176,7 +1176,7 @@ msgstr "Lien ou fichier"
|
||||
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
|
||||
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."
|
||||
msgstr "Mises à jour en direct indisponibles."
|
||||
|
||||
@@ -1219,7 +1219,7 @@ msgstr "Chargement du profil…"
|
||||
msgid "Loading…"
|
||||
msgstr "Chargement…"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:216
|
||||
#: src/components/layout/AppHeader.tsx:214
|
||||
#: src/pages/UserLogin.tsx:41
|
||||
#: src/pages/UserLogin.tsx:67
|
||||
#: src/pages/UserLogin.tsx:86
|
||||
@@ -1234,7 +1234,7 @@ msgstr "Se connecter pour aimer"
|
||||
msgid "Log in to vote"
|
||||
msgstr "Se connecter pour voter"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:188
|
||||
#: src/components/layout/UserMenu.tsx:115
|
||||
#: src/pages/UserPublicProfile.tsx:131
|
||||
msgid "Log out"
|
||||
msgstr "Se déconnecter"
|
||||
@@ -1251,8 +1251,7 @@ msgstr "Connexion échouée"
|
||||
msgid "Lowercase letters, numbers and single hyphens only"
|
||||
msgstr "Minuscules, chiffres et tirets simples seulement"
|
||||
|
||||
#: src/components/layout/UserMenu.tsx:98
|
||||
#: src/components/profile/ProfileHeader.tsx:180
|
||||
#: src/components/layout/UserMenu.tsx:102
|
||||
#: src/pages/Manage.tsx:24
|
||||
#: src/pages/Manage.tsx:29
|
||||
#: src/pages/UserPublicProfile.tsx:164
|
||||
@@ -1465,7 +1464,7 @@ msgstr "Lecture"
|
||||
|
||||
#: src/components/dump/create/StepIndicator.tsx:21
|
||||
#: 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/ProfilePlaylistsTab.tsx:118
|
||||
#: src/pages/Search.tsx:123
|
||||
@@ -1616,7 +1615,7 @@ msgstr "Rôle"
|
||||
#: src/components/category/CategoryManager.tsx:216
|
||||
#: src/components/chat/ChatEditBox.tsx:85
|
||||
#: 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/EmailEditor.tsx:75
|
||||
#: src/pages/Dump.tsx:437
|
||||
@@ -1625,7 +1624,7 @@ msgid "Save"
|
||||
msgstr "Enregistrer"
|
||||
|
||||
#: 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/DescriptionEditor.tsx:86
|
||||
#: src/components/profile/EmailEditor.tsx:72
|
||||
@@ -1634,7 +1633,7 @@ msgstr "Enregistrer"
|
||||
msgid "Saving…"
|
||||
msgstr "Enregistrement…"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:190
|
||||
#: src/components/layout/AppHeader.tsx:188
|
||||
#: src/components/layout/SearchBar.tsx:83
|
||||
#: src/pages/Search.tsx:69
|
||||
msgid "Search"
|
||||
@@ -1677,7 +1676,7 @@ msgstr "Envoyer le lien de réinitialisation"
|
||||
msgid "Sending…"
|
||||
msgstr "Envoi…"
|
||||
|
||||
#: src/components/layout/AppHeader.tsx:206
|
||||
#: src/components/layout/AppHeader.tsx:204
|
||||
msgid "Server unreachable"
|
||||
msgstr "Serveur inaccessible"
|
||||
|
||||
@@ -1686,8 +1685,7 @@ msgstr "Serveur inaccessible"
|
||||
msgid "Set new password"
|
||||
msgstr "Définir un nouveau mot de passe"
|
||||
|
||||
#: src/components/layout/UserMenu.tsx:89
|
||||
#: src/components/profile/ProfileHeader.tsx:176
|
||||
#: src/components/layout/UserMenu.tsx:93
|
||||
#: src/pages/Settings.tsx:70
|
||||
#: src/pages/Settings.tsx:82
|
||||
msgid "Settings"
|
||||
@@ -1697,7 +1695,7 @@ msgstr "Paramètres"
|
||||
msgid "Several people are typing…"
|
||||
msgstr "Plusieurs personnes écrivent…"
|
||||
|
||||
#: src/components/profile/ProfileOwnActions.tsx:108
|
||||
#: src/components/profile/ProfileOwnActions.tsx:102
|
||||
msgid "Single use, valid for 7 days."
|
||||
msgstr "Usage unique, valable 7 jours."
|
||||
|
||||
@@ -1786,7 +1784,7 @@ msgstr "Miniature"
|
||||
|
||||
#: src/components/dump/create/TitleField.tsx:45
|
||||
#: src/components/playlist/PlaylistCreateForm.tsx:96
|
||||
#: src/components/playlist/PlaylistEditForm.tsx:159
|
||||
#: src/components/playlist/PlaylistEditForm.tsx:160
|
||||
#: src/pages/DumpEdit.tsx:516
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
@@ -1853,7 +1851,7 @@ msgstr "Se désabonner"
|
||||
msgid "Unsubscribed"
|
||||
msgstr "Désabonné"
|
||||
|
||||
#: src/components/profile/ProfileHeader.tsx:73
|
||||
#: src/components/profile/ProfileHeader.tsx:72
|
||||
msgid "Upload failed"
|
||||
msgstr "Envoi échoué"
|
||||
|
||||
@@ -1904,7 +1902,7 @@ msgstr "Utilisateur"
|
||||
msgid "User management"
|
||||
msgstr "Gestion utilisateur"
|
||||
|
||||
#: src/components/layout/UserMenu.tsx:56
|
||||
#: src/components/layout/UserMenu.tsx:60
|
||||
msgid "User menu"
|
||||
msgstr "Menu utilisateur"
|
||||
|
||||
|
||||
113
src/App.css
113
src/App.css
@@ -2186,54 +2186,28 @@ body.has-player .chat-fab {
|
||||
}
|
||||
|
||||
/* ── Dense dump cards in the two-column profile view ──
|
||||
The default card is proportioned for the 860px feed. Dropped into a ~400px
|
||||
column it falls apart: the title wraps four lines deep, the meta row stacks,
|
||||
every category chip takes a line of its own and the vote button floats in
|
||||
the middle of the resulting void. Below is the same card re-proportioned for
|
||||
the column — smaller thumb, title clamped to two lines, no teaser, and meta
|
||||
plus chips sharing the line under the title. Scoped to the width where the
|
||||
columns actually split; narrower than that the feed is full-width again and
|
||||
the regular (then the phone) layout applies. */
|
||||
A ~400px column is narrow enough for the narrow card's grid (the
|
||||
`@container dump-card` rules), which places the thumb, the text and the
|
||||
vote; placing them here as well, more specifically, broke that grid. This
|
||||
only makes the card denser for a column of a dozen: a smaller thumb, the
|
||||
title clamped to two lines and smaller type. Scoped to the width where the
|
||||
columns split; narrower than that the feed is full-width again. */
|
||||
@media (min-width: 900px) {
|
||||
.profile-columns .dump-feed {
|
||||
padding: 0.75rem 0 0;
|
||||
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 {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: start;
|
||||
gap: 0.55rem;
|
||||
padding: 0.5rem 0.6rem;
|
||||
}
|
||||
|
||||
.profile-columns .dump-card-preview {
|
||||
grid-area: 1 / 1;
|
||||
width: 88px;
|
||||
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 {
|
||||
flex: 1 0 100%;
|
||||
font-size: var(--text-md);
|
||||
line-height: 1.3;
|
||||
display: -webkit-box;
|
||||
@@ -2243,22 +2217,6 @@ body.has-player .chat-fab {
|
||||
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-comment-count {
|
||||
font-size: var(--text-2xs);
|
||||
@@ -2341,10 +2299,6 @@ body.has-player .chat-fab {
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.profile-header .btn-border {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.profile-header .follow-btn {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@@ -2476,8 +2430,8 @@ body.has-player .chat-fab {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Grid sized by the button row; the invite panel below only fills that
|
||||
width (width: 0 + min-width: 100% keeps it out of the sizing). */
|
||||
/* Grid sized by the button; the invite panel below only fills that width
|
||||
(width: 0 + min-width: 100% keeps it out of the sizing). */
|
||||
.profile-own-actions {
|
||||
display: grid;
|
||||
width: fit-content;
|
||||
@@ -2485,15 +2439,6 @@ body.has-player .chat-fab {
|
||||
gap: 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 {
|
||||
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 {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -3150,7 +3083,7 @@ body.has-player .chat-fab {
|
||||
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
|
||||
that would sit it higher than the buttons beside it. */
|
||||
.user-menu {
|
||||
@@ -3206,6 +3139,7 @@ body.has-player .chat-fab {
|
||||
color: var(--color-text);
|
||||
font-size: var(--text-md);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
transition: background var(--dur-fast);
|
||||
}
|
||||
|
||||
@@ -3213,6 +3147,33 @@ body.has-player .chat-fab {
|
||||
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 {
|
||||
/* position/top/bottom/left/max-height set inline — anchored to the
|
||||
trigger's viewport rect, flipping above it when there isn't room below */
|
||||
|
||||
@@ -159,8 +159,8 @@ export function AppHeader(
|
||||
<nav className="app-header-nav">
|
||||
{
|
||||
/* 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
|
||||
menu, by width. */
|
||||
unbroken to the "+": the text links on wide screens, then the
|
||||
avatar's menu at every width. */
|
||||
}
|
||||
{user && (
|
||||
<>
|
||||
@@ -178,9 +178,7 @@ export function AppHeader(
|
||||
<Trans>Playlists</Trans>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="nav-compact">
|
||||
<UserMenu user={user} />
|
||||
</div>
|
||||
<UserMenu user={user} />
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Renders the avatar menu that replaces the header's profile and playlists
|
||||
* links on small screens, and leads to the settings and the site's tools.
|
||||
* Renders the avatar menu that leads the header's icon row at every width: it
|
||||
* holds the profile and playlists links (on small screens, the only ones), the
|
||||
* settings, the site's tools, and logging out.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -13,6 +14,7 @@ import { Avatar } from "../shared/Avatar.tsx";
|
||||
import type { SelfUser } from "../../../shared/user.ts";
|
||||
import { can } from "../../utils/auth/permissions.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. */
|
||||
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
|
||||
* profile, their playlists and `/settings`, and to `/manage` for those who
|
||||
* hold `category:manage`. A click outside or Escape closes it.
|
||||
* profile, their playlists and `/settings`, to `/manage` for those who hold
|
||||
* `category:manage`, and a Log out button. A click outside or Escape closes
|
||||
* it.
|
||||
*/
|
||||
export function UserMenu({ user }: UserMenuProps): React.JSX.Element {
|
||||
const { logout } = useAuth();
|
||||
const [open, setOpen] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -98,6 +102,18 @@ export function UserMenu({ user }: UserMenuProps): React.JSX.Element {
|
||||
<Trans>Manage</Trans>
|
||||
</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>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
import { useRef, useState } from "react";
|
||||
import { Link } from "react-router";
|
||||
import { can } from "../../utils/auth/permissions.ts";
|
||||
import { t } from "@lingui/core/macro";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
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
|
||||
* button. On their own profile, users change their avatar
|
||||
* (`PUT /api/me/avatar`) and email, invite someone, reach their settings (and
|
||||
* the site's tools, with `category:manage`) and log out.
|
||||
* (`PUT /api/me/avatar`) and email, and invite someone; their settings, the
|
||||
* site's tools and logging out are in the header's avatar menu.
|
||||
*/
|
||||
export function ProfileHeader(
|
||||
{ user, isOwnProfile, onUserChange }: ProfileHeaderProps,
|
||||
): React.JSX.Element {
|
||||
const { user: me, authFetch, updateSession, logout } = useAuth();
|
||||
const { user: me, authFetch, updateSession } = useAuth();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [avatarError, setAvatarError] = useState<string | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -170,25 +169,7 @@ export function ProfileHeader(
|
||||
targetUsername={user.username}
|
||||
/>
|
||||
)}
|
||||
{isOwnProfile && (
|
||||
<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>
|
||||
)}
|
||||
{isOwnProfile && <ProfileOwnActions />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Renders the actions of the signed-in user's own profile header, which invite
|
||||
* someone, beside the log out button the header adds.
|
||||
* Renders the action of the signed-in user's own profile header, which invites
|
||||
* someone.
|
||||
*
|
||||
* @module
|
||||
*/
|
||||
@@ -14,13 +14,10 @@ import { useHeightTransition } from "../../hooks/shared/useHeightTransition.ts";
|
||||
import { ErrorCard } from "../shared/ErrorCard.tsx";
|
||||
|
||||
/**
|
||||
* Renders the "Invite someone" button, then `children`, the log out button.
|
||||
* Each opening of the panel creates a new single-use invite link, for
|
||||
* `/register`.
|
||||
* Renders the "Invite someone" button, which opens a panel with an invite link.
|
||||
* Each opening of the panel creates a new single-use link, for `/register`.
|
||||
*/
|
||||
export function ProfileOwnActions(
|
||||
{ children }: { children: React.ReactNode },
|
||||
): React.JSX.Element {
|
||||
export function ProfileOwnActions(): React.JSX.Element {
|
||||
const { authFetch } = useAuth();
|
||||
const [inviteUrl, setInviteUrl] = useState<string | null>(null);
|
||||
const [pending, setPending] = useState(false);
|
||||
@@ -69,18 +66,15 @@ export function ProfileOwnActions(
|
||||
|
||||
return (
|
||||
<div className="profile-own-actions" ref={ref}>
|
||||
<div className="profile-own-actions-row">
|
||||
<button
|
||||
type="button"
|
||||
className="invite-btn"
|
||||
onClick={toggle}
|
||||
disabled={pending}
|
||||
aria-expanded={inviteUrl !== null}
|
||||
>
|
||||
<Trans>+ Invite someone</Trans>
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="invite-btn"
|
||||
onClick={toggle}
|
||||
disabled={pending}
|
||||
aria-expanded={inviteUrl !== null}
|
||||
>
|
||||
<Trans>+ Invite someone</Trans>
|
||||
</button>
|
||||
{inviteUrl && (
|
||||
<div
|
||||
className="invite-panel animate-in"
|
||||
|
||||
@@ -3,7 +3,8 @@ import { screen } from "@testing-library/react";
|
||||
import { userEvent } from "@testing-library/user-event";
|
||||
import { UserMenu } from "../../../components/layout/UserMenu.tsx";
|
||||
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", () => {
|
||||
test("opens, links to profile and playlists, closes on Escape and outside", async () => {
|
||||
@@ -41,4 +42,26 @@ describe("UserMenu", () => {
|
||||
expect(currentPath()).toBe("/@ann");
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,8 +75,6 @@ describe("UserPublicProfile", () => {
|
||||
expect(dumpsAll).toHaveAttribute("href", "/@ann/dumps");
|
||||
expect(votesAll).toHaveAttribute("href", "/@ann/upvoted");
|
||||
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.
|
||||
expect(calls("GET /api/users/:u/dumps")[0].path).toBe(
|
||||
"/api/users/ann/dumps",
|
||||
@@ -275,11 +273,11 @@ describe("UserPublicProfile", () => {
|
||||
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" }) });
|
||||
expect(await screen.findByRole("button", { name: "Follow ann" }))
|
||||
.toBeInTheDocument();
|
||||
expect(screen.queryByRole("link", { name: "Settings" })).not
|
||||
expect(screen.queryByRole("button", { name: "+ Invite someone" })).not
|
||||
.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({
|
||||
session: { ...annSession, user: { ...annSession.user, role: "admin" } },
|
||||
user: { ...ann, role: "admin" },
|
||||
});
|
||||
expect(await screen.findByRole("link", { name: "Settings" }))
|
||||
.toHaveAttribute("href", "/settings");
|
||||
expect(screen.getByRole("link", { name: "Manage" })).toHaveAttribute(
|
||||
"href",
|
||||
"/manage",
|
||||
);
|
||||
// Their own profile has no manage tab: that's for others'.
|
||||
expect(await screen.findByRole("button", { name: "+ Invite someone" }))
|
||||
.toBeInTheDocument();
|
||||
expect(
|
||||
within(document.querySelector(".profile-tabs") as HTMLElement)
|
||||
.queryByRole("button", { name: "Manage" }),
|
||||
).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", () => {
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
[data-style="brutalist"] .audio-player-vol-btn,
|
||||
[data-style="brutalist"] .rich-content-thumbnail-btn,
|
||||
[data-style="brutalist"] .user-menu-trigger,
|
||||
[data-style="brutalist"] .user-menu-item,
|
||||
[data-style="brutalist"] .auth-link-btn,
|
||||
[data-style="brutalist"] .form-cancel,
|
||||
[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: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:active,
|
||||
[data-style="brutalist"] .form-cancel:hover:not(:disabled),
|
||||
|
||||
@@ -217,6 +217,7 @@
|
||||
[data-style="geocities"] .audio-player-vol-btn,
|
||||
[data-style="geocities"] .rich-content-thumbnail-btn,
|
||||
[data-style="geocities"] .user-menu-trigger,
|
||||
[data-style="geocities"] .user-menu-item,
|
||||
[data-style="geocities"] .auth-link-btn,
|
||||
[data-style="geocities"] .form-cancel,
|
||||
[data-style="geocities"] .form-label-action,
|
||||
|
||||
Reference in New Issue
Block a user