529 lines
26 KiB
Gherkin
529 lines
26 KiB
Gherkin
Feature: Sites and authors in the Editor, Articles and Profile screens
|
|
|
|
As a RenewCMS user who can edit articles
|
|
I want to choose an article's site and bylines in the editor, see them in the articles list, and create my own author profile
|
|
So that every article belongs to the right frontend and credits the right people
|
|
|
|
Background:
|
|
Given the screens are EditorView (/article/:id/edit), ArticlesView (/article) and ProfileView (/profile),
|
|
inside DashboardShell, per docs/design/routes.md
|
|
And the visual spec of record is docs/design/screens/editor.md (group "Editor: site and authors"),
|
|
articles.md (group "Articles: sites and authors") and profile.md (group "Profile: author profile"),
|
|
with the error copy in docs/design/states.md. Exact copy is read from those files and is not repeated here
|
|
And this feature supersedes the lines of features/sites_authors_screens.feature that said
|
|
POST /v1/author/me is not exposed in the UI and that Editor, Articles and Profile are not modified
|
|
And POST /v1/author/me answers 201 with the author and slug_adjusted, true when the stored slug differs from the plain slug of the name
|
|
And the SPA calls, with relative URLs through useApi only:
|
|
GET /v1/site/, GET /v1/author/ (page, size in 5, 25, 50 or 100, and q), POST /v1/author/, POST /v1/author/me,
|
|
GET /v1/auth/me, GET /v1/article/{id}, PATCH /v1/article/{id} with site_id and author_ids,
|
|
and GET /v1/article/?site={key}
|
|
And an article carries site_id and authors [{id, name, slug, ...}] in list order, and the first author is the primary byline
|
|
And GET /v1/auth/me carries "author" (the linked author, or null) and the SPA keeps it in the session store
|
|
And the sites fixture is Renew (id 1, renew.example, default), Journal (id 2, journal.renew.example), Projects (id 3) and Archive (id 4)
|
|
And the authors fixture is Inès Duarte (id 2), Jonas Pereira, Camille Roche and the Marta Alves of the design
|
|
And an author is drawn as AuthorAvatar (initials fallback, square), several as AuthorStack
|
|
And the domain defines only the roles "owner" and "admin" (internal/domain/user, RoleOwner and RoleAdmin)
|
|
And "author rights" means owner and admin: they may create authors and edit any author
|
|
And "verified" below names a verified account without the owner or admin role, which has no author rights
|
|
And "several sites" means GET /v1/site/ returned more than one site
|
|
And every visible string goes through t(...) with the English text as the key and a French entry in web/src/i18n/messages.ts
|
|
And a scenario tagged @not-in-design uses copy or behaviour that docs/design does not define: it takes the
|
|
proposal of this feature, has a French entry, and is listed in the .qa.md to be fed back to the design
|
|
And the screens use Nuxt UI components and design tokens only, with no raw colors
|
|
And the board frames of docs/design/source/AppFrame.dc.html and DirectoryFrame.dc.html are the layout tiebreaker
|
|
|
|
Rule: Editor: the "Site and authors" section (MetaSection)
|
|
|
|
Scenario: Collapsed by default with one site and an author
|
|
Given the install has one site and article 12 has the author "Inès Duarte"
|
|
And nothing is remembered for this browser
|
|
When the user opens /article/12/edit on desktop
|
|
Then the details sidebar shows a collapsed "Site and authors" section with the one-line summary "Inès Duarte"
|
|
And no Site select is rendered
|
|
And the section sits in the same place in every editor state that has the details sidebar
|
|
|
|
Scenario: Loading and not found
|
|
When GET /v1/article/12 is pending
|
|
Then the section header is shown with a skeleton summary
|
|
When GET /v1/article/12 answers 404
|
|
Then the "Not found" state shows no section
|
|
|
|
Scenario Outline: The section opens by itself when it needs attention
|
|
Given nothing is remembered for this browser
|
|
And the install has <sites> and the article has <authors>
|
|
When the user opens the editor on desktop
|
|
Then the section is <state>
|
|
|
|
Examples:
|
|
| sites | authors | state |
|
|
| one site | an author | collapsed |
|
|
| several sites | an author | open |
|
|
| one site | no authors | open |
|
|
| several sites | no authors | open |
|
|
|
|
Scenario: The choice is remembered per browser and wins over auto-open
|
|
Given the section is collapsed by default
|
|
When the user opens it and later opens another article in the same browser
|
|
Then the section is open there too
|
|
When the user collapses it and reloads
|
|
Then it is collapsed even if the install has several sites or the article has no authors
|
|
And the choice is kept in localStorage and nothing is sent to the API
|
|
|
|
Scenario: Mobile: inside the Details sheet, closed by default
|
|
Given the viewport is 390 px wide
|
|
When the user opens the editor
|
|
Then the section is not visible until the Details sheet is opened
|
|
And inside the sheet it is closed unless a choice is remembered
|
|
When the user opens it, closes the sheet and opens it again
|
|
Then the section keeps the choice made in the sheet
|
|
And the sheet and the desktop sidebar share the same storage key
|
|
|
|
@not-in-design
|
|
Scenario Outline: Collapsed summary line variants
|
|
Given the section is collapsed
|
|
And the install has <sites> and the article has <authors>
|
|
Then the summary line reads <summary>
|
|
|
|
Examples:
|
|
| sites | authors | summary |
|
|
| one site | Inès Duarte | "Inès Duarte" |
|
|
| one site | Inès Duarte, Jonas Pereira, Camille Roche | "Inès Duarte +2" |
|
|
| one site | no authors | "No authors" |
|
|
| several sites | Inès Duarte on Journal | "Journal · Inès Duarte" |
|
|
| several sites | no authors on Journal | "Journal · No authors" |
|
|
|
|
Rule: Site select
|
|
|
|
Scenario: One site: no Site select
|
|
Given the install has one site
|
|
When the section is open
|
|
Then there is no Site select and the "Authors" list is the first control
|
|
|
|
Scenario: Several sites: Site select above the authors
|
|
Given the install has several sites and article 12 is on "Journal"
|
|
When the section is open
|
|
Then a "Site" select above the authors shows "Journal" with "journal.renew.example"
|
|
And the helper text reads "Canonical host: journal.renew.example"
|
|
And the select is available to every role that can edit the article
|
|
|
|
Scenario: Site select options
|
|
When the user opens the Site select
|
|
Then each option shows the site name and its host, and the default site is first
|
|
|
|
Scenario: Changing the site is an edit
|
|
Given the editor shows "Saved" and the article is on "Renew"
|
|
When the user chooses "Journal"
|
|
Then the status becomes "Unsaved changes" and Save is enabled
|
|
And no request is sent yet
|
|
When the user activates Save
|
|
Then exactly one PATCH /v1/article/12 is sent containing site_id 2
|
|
And on 200 the status is "Saved" and the helper text shows the new host
|
|
|
|
Rule: Author list
|
|
|
|
Scenario: Authors are an ordered list; the first is the primary byline
|
|
Given article 12 has four authors
|
|
When the section is open
|
|
Then the first row shows the label "Primary" (@not-in-design)
|
|
And each row has a drag handle and a remove button
|
|
And the hint "Drag to reorder. The first author is the primary byline." is shown
|
|
And a long name is truncated with an ellipsis and carries the full name in title and aria-label
|
|
|
|
Scenario: Reorder with the keyboard
|
|
Given an author list of three with "Jonas Pereira" second
|
|
When the user focuses the handle of "Jonas Pereira" and presses Space or Enter
|
|
Then a live region announces the grabbed-row message of the design (position 2 of 3 and the key instructions)
|
|
When the user presses ArrowUp
|
|
Then the row moves to position 1 and the new position is announced
|
|
When the user presses Space
|
|
Then the row is dropped there and the status becomes "Unsaved changes"
|
|
When the user grabs a row, moves it and presses Escape
|
|
Then the row returns to its original position and the status does not change
|
|
|
|
@not-in-design
|
|
Scenario: Dropped and cancelled announcements
|
|
Given a row was grabbed
|
|
When the user drops it with Space
|
|
Then the live region announces the dropped message with the final position
|
|
When the user cancels with Escape
|
|
Then the live region announces the cancelled message and the original position
|
|
|
|
Scenario: Reorder with the pointer
|
|
When the user drags a row
|
|
Then the row lifts and a line shows the drop position
|
|
When the user drops it
|
|
Then the list takes the new order and the status becomes "Unsaved changes"
|
|
|
|
Scenario: Remove an author
|
|
Given the list has "Inès Duarte" and "Jonas Pereira"
|
|
When the user activates the remove button of "Inès Duarte"
|
|
Then "Jonas Pereira" becomes "Primary" and the status becomes "Unsaved changes"
|
|
When the last author is removed
|
|
Then the list shows "No authors. This article will have no byline."
|
|
|
|
Scenario: Saving authors
|
|
Given the author list was reordered to [Jonas Pereira, Inès Duarte]
|
|
When the user activates Save
|
|
Then exactly one PATCH /v1/article/12 is sent whose author_ids follow the list order
|
|
And a list emptied by the user is sent as author_ids []
|
|
And fields that did not change are not sent
|
|
|
|
Rule: Add author
|
|
|
|
Scenario: The combobox
|
|
When the user activates "Add an author"
|
|
Then a combobox opens with a "Search authors" field and GET /v1/author/ is requested with size 100
|
|
And authors already on the article are checked and dimmed
|
|
When the user activates a checked author
|
|
Then nothing happens and the author is not added twice
|
|
When the user picks "Camille Roche"
|
|
Then she is appended to the list and the status becomes "Unsaved changes"
|
|
|
|
Scenario: Searching beyond the first 100 authors
|
|
Given the install has more than 100 authors
|
|
When the user types "Zoe" in "Search authors"
|
|
Then GET /v1/author/ is requested with q "Zoe" and the list shows the matches
|
|
|
|
Scenario Outline: "Create author…" only for roles with author rights
|
|
Given the session role is "<role>"
|
|
When the combobox is open
|
|
Then the "Create author…" row is <visible>
|
|
|
|
Examples:
|
|
| role | visible |
|
|
| owner | shown |
|
|
| admin | shown |
|
|
| verified | absent |
|
|
|
|
Scenario Outline: No match
|
|
Given the session role is "<role>"
|
|
When the user types "Zoe Lambert" and nobody matches
|
|
Then the message "No author matches "Zoe Lambert"." is shown
|
|
And <create>
|
|
|
|
Examples:
|
|
| role | create |
|
|
| owner | the row Create author "Zoe Lambert"… is shown |
|
|
| verified | no create row is shown |
|
|
|
|
Scenario Outline: Authors could not be loaded
|
|
When GET /v1/author/ fails with <failure> as the combobox opens
|
|
Then the combobox shows "Couldn't load authors", "Check your connection and try again." and "Try again"
|
|
And the article's authors in the list are untouched
|
|
When the user activates "Try again"
|
|
Then GET /v1/author/ is requested again
|
|
|
|
Examples:
|
|
| failure |
|
|
| status 500 |
|
|
| a network error |
|
|
|
|
Rule: Create author dialog (owner and admin)
|
|
|
|
Scenario: Open the dialog from the combobox
|
|
Given the session role is "owner"
|
|
When the user activates Create author "Zoe Lambert"…
|
|
Then the dialog "Create author" opens over the editor, with Name prefilled "Zoe Lambert", Slug "zoe-lambert" (Auto, same SlugField as Authors), "Cancel" and "Create author"
|
|
And the unsaved text of the article is untouched
|
|
|
|
Scenario: Create succeeds
|
|
Given the Create author dialog is filled with name "Zoe Lambert" and slug "zoe-lambert"
|
|
When the user activates "Create author"
|
|
Then exactly one POST /v1/author/ is sent with name and slug
|
|
And the dialog closes, "Zoe Lambert" is appended to the author list and the status becomes "Unsaved changes"
|
|
And the article's authors are not sent to the API until Save
|
|
|
|
Scenario: "This is me" only when the user has no author profile
|
|
Given the session role is "owner" and GET /v1/auth/me has author null
|
|
When the Create author dialog is open
|
|
Then the checkbox "This is me" shows "Links the author to marta@renew.example."
|
|
And it is unchecked until the user checks it
|
|
When it is checked and the user creates the author
|
|
Then POST /v1/author/ is sent with user_id of the current user
|
|
And the session store now holds that author, so the hint and the Profile card show the profile
|
|
|
|
Scenario: No "This is me" when the user already has an author
|
|
Given GET /v1/auth/me carries an author
|
|
When the Create author dialog is open
|
|
Then there is no "This is me" checkbox and no user_id is ever sent
|
|
|
|
Scenario: Slug taken
|
|
When POST /v1/author/ answers 409 author_slug_taken
|
|
Then the dialog shows "The slug "zoe-lambert" is already used by another author." and the action "Use zoe-lambert-2"
|
|
And activating the action sets the slug and keeps the name
|
|
|
|
Scenario Outline: Create author failed
|
|
When POST /v1/author/ fails with <failure>
|
|
Then the dialog stays open with "Couldn't create the author" and "The article is unchanged. Try again in a moment."
|
|
And the values are kept and the article is unchanged
|
|
|
|
Examples:
|
|
| failure |
|
|
| status 500 |
|
|
| a network error |
|
|
|
|
Scenario: Role changed while the dialog was open
|
|
When POST /v1/author/ answers 403
|
|
Then the error toast of states.md "Only an owner or admin can change authors" shows and the dialog closes
|
|
|
|
Rule: "Create my author profile" hint (every role)
|
|
|
|
Scenario Outline: The hint appears
|
|
Given the session role is "<role>", GET /v1/auth/me has author null, the article has no authors and the hint was never dismissed
|
|
When the section is open
|
|
Then the dismissible hint "You don't have an author profile yet" with "Create my author profile" is shown
|
|
And the list shows "No authors. This article will have no byline."
|
|
|
|
Examples:
|
|
| role |
|
|
| owner |
|
|
| admin |
|
|
| verified |
|
|
|
|
Scenario Outline: The hint stays away
|
|
Given <condition>
|
|
Then the hint is not shown
|
|
|
|
Examples:
|
|
| condition |
|
|
| the user already has an author profile |
|
|
| the article has an author |
|
|
| the hint was dismissed by this user |
|
|
|
|
Scenario: Create my author profile
|
|
Given the hint is shown
|
|
When the user activates "Create my author profile"
|
|
Then exactly one POST /v1/author/me is sent with no body and no dialog opens
|
|
And on 201 the new author is added to the list as the primary author
|
|
And the status becomes "Unsaved changes" and the hint disappears
|
|
And the session store holds the author, so /profile shows the "Created" card
|
|
|
|
@not-in-design
|
|
Scenario: The hint-success toast
|
|
When POST /v1/author/me answers 201
|
|
Then a success toast shows "Author profile created" and "Marta Alves is now your byline." (the Profile copy)
|
|
|
|
Scenario Outline: Create my author profile fails
|
|
When POST /v1/author/me fails with <failure>
|
|
Then the hint stays, an error toast shows "Couldn't create your author profile" and "Try again in a moment."
|
|
And the article is unchanged
|
|
|
|
Examples:
|
|
| failure |
|
|
| status 500 |
|
|
| a network error |
|
|
|
|
Scenario: The profile already exists (409)
|
|
When POST /v1/author/me answers 409 user_already_linked
|
|
Then GET /v1/auth/me is requested again, the hint disappears and the existing author is offered in the combobox
|
|
|
|
Scenario: Dismissing the hint
|
|
When the user dismisses the hint
|
|
Then it disappears and does not come back for this user in this browser, on later visits to any article
|
|
And the dismissal is kept in localStorage keyed by user id
|
|
And dismissing sends no request
|
|
|
|
Rule: Editor failures and keyboard
|
|
|
|
Scenario: The sites could not be loaded
|
|
Given GET /v1/site/ fails
|
|
When the user opens the editor
|
|
Then the editor still loads, no Site select is rendered and no notice is shown
|
|
|
|
Scenario Outline: Save is refused for a site or author that no longer exists
|
|
Given the user changed the <what>
|
|
When PATCH /v1/article/12 answers 404 <code>
|
|
Then the existing "Save failed" state is shown with the changes kept
|
|
And an info toast "<toast>" shows and the lists are reloaded
|
|
|
|
Examples:
|
|
| what | code | toast |
|
|
| site | site_not_found | That site no longer exists |
|
|
| author | author_not_found | That author no longer exists |
|
|
|
|
Scenario: Existing save behaviour is kept
|
|
Given the user changed only the site or the authors
|
|
Then "Save and publish", the leave-confirmation dialog, Ctrl/Cmd S and the draft kept in sessionStorage after a session expiry treat the change like any other edit
|
|
And the restored draft shows "Unsaved changes" with the chosen site and author order
|
|
|
|
Rule: Articles list: Authors column, Site column and Site filter
|
|
|
|
Scenario Outline: Authors column on every frame
|
|
Given an article with <authors>
|
|
When the list is shown
|
|
Then the Authors cell shows <cell>
|
|
|
|
Examples:
|
|
| authors | cell |
|
|
| no authors | "—" |
|
|
| one author | one avatar |
|
|
| three | three avatars, no "+N" |
|
|
| five | three avatars and "+2" |
|
|
|
|
Scenario: Mobile Authors cell, and no site on the cards
|
|
Given the viewport is 390 px wide
|
|
Then the card shows the avatars with the first name of the first author, as drawn in the board
|
|
And the card shows no site, as in the design
|
|
|
|
Scenario: One site: no Site column and no filter
|
|
Given the install has one site
|
|
Then the list has an Authors column, no Site column and no Site filter
|
|
|
|
Scenario: Several sites: Site column and Site filter
|
|
Given the install has several sites
|
|
Then the list has a Site column showing the site name as read-only text for every role
|
|
And the toolbar has a Site select with "All sites" and every site name
|
|
|
|
Scenario: Choosing a site
|
|
Given the list is on page 3
|
|
When the user chooses "Journal" in the Site filter
|
|
Then the URL becomes /article?site=journal with the page omitted
|
|
And GET /v1/article/?site=journal is requested with the current q, status and sort
|
|
And the tab counts still show the full list
|
|
|
|
Scenario Outline: Other changes keep the site
|
|
Given the URL holds ?site=journal
|
|
When the user <action>
|
|
Then ?site=journal stays in the URL
|
|
|
|
Examples:
|
|
| action |
|
|
| searches for "plaster" |
|
|
| changes the tab |
|
|
| changes the sort |
|
|
| goes to page 2 |
|
|
| changes the page size |
|
|
|
|
Scenario: Choosing "All sites"
|
|
Given the URL holds ?site=journal
|
|
When the user chooses "All sites"
|
|
Then site leaves the URL, the page resets to 1 and the full list is requested
|
|
|
|
Scenario: Opening a URL with a site
|
|
When the user opens /article?site=journal&page=2
|
|
Then the Site filter shows "Journal" and GET /v1/article/?site=journal&page=2 is requested
|
|
|
|
Scenario Outline: A site key that cannot be used is dropped silently
|
|
Given the URL holds <query>
|
|
Then the key is removed from the URL with no notice and the unfiltered list is requested
|
|
|
|
Examples:
|
|
| query |
|
|
| ?site=nope |
|
|
| ?site=renew on a one-site install |
|
|
|
|
Scenario: No articles on a site
|
|
Given ?site=archive and the response is empty with no search and the tab "All"
|
|
Then the state shows "No articles on "Archive"", "Move articles here from the editor, or choose another site." and "Show all sites"
|
|
When the user activates "Show all sites"
|
|
Then site leaves the URL and the full list is requested
|
|
|
|
Scenario: Zero results on a site with a search or another tab
|
|
Given ?site=archive and a search or a tab other than "All" is active
|
|
Then the existing "No results" or "No drafts" state is shown
|
|
|
|
Scenario: Existing states keep working with a site
|
|
Then Loading, Empty, row actions, delete and pagination states behave as in docs/design/screens/articles.md with the site filter applied
|
|
|
|
Scenario: The sites could not be loaded
|
|
Given GET /v1/site/ fails
|
|
Then the list renders as a single-site install, with no Site column and no Site filter
|
|
|
|
Rule: Profile: the Author profile card
|
|
|
|
Scenario: Position and roles
|
|
Then in every Profile frame the "Author profile" card sits between "Account" and "Password"
|
|
And it is shown to every role
|
|
|
|
Scenario: Loading
|
|
When GET /v1/auth/me is pending
|
|
Then the card shows a skeleton
|
|
|
|
Scenario: No author profile
|
|
Given GET /v1/auth/me has author null
|
|
Then the card shows the copy of state 1, a "Create my author profile" button and the line "Uses your display name, Marta Alves. You can change it afterwards."
|
|
|
|
@not-in-design
|
|
Scenario: No author profile and no display name
|
|
Given GET /v1/auth/me has author null and no display name
|
|
Then the line reads "Uses your username, marta. You can change it afterwards."
|
|
|
|
Scenario: Create succeeds
|
|
When the user activates "Create my author profile"
|
|
Then exactly one POST /v1/author/me is sent
|
|
And on 201 the card shows "Author profile created", "Marta Alves is now your byline." and "Edit author" with "Linked to your account. New articles start with this author."
|
|
And the session store holds the author
|
|
|
|
Scenario: Slug adjusted
|
|
Given the 201 answer has slug_adjusted true and the slug "marta-alves-2"
|
|
Then no error is shown and the card also shows "Your slug is marta-alves-2" with "marta-alves was already used by another author. You can change it in Edit author."
|
|
|
|
Scenario Outline: The notice follows slug_adjusted, never the slug itself
|
|
Given the author's name is "<name>"
|
|
And the 201 answer has the slug "<returned>" and slug_adjusted <flag>
|
|
Then the card <notice> the "Your slug is" notice
|
|
|
|
Examples:
|
|
| name | returned | flag | notice |
|
|
| Marta Alves | marta-alves | false | does not show |
|
|
| Marta Alves | marta-alves-2 | true | shows |
|
|
| Inès Duarte | ines-duarte-2 | true | shows |
|
|
| Jean-Luc O'Neil | jean-luc-o-neil | false | does not show |
|
|
| Zoë Müller | zoe-muller | false | does not show |
|
|
| Agent 007 | agent-007 | false | does not show |
|
|
| Agent 007 | agent-007-2 | true | shows |
|
|
|
|
Scenario Outline: Create fails
|
|
When POST /v1/author/me fails with <failure>
|
|
Then the card shows "Couldn't create your author profile" and "Try again in a moment." and the button stays available
|
|
|
|
Examples:
|
|
| failure |
|
|
| status 500 |
|
|
| a network error |
|
|
|
|
Scenario: Profile already exists (409)
|
|
When POST /v1/author/me answers 409 user_already_linked
|
|
Then GET /v1/auth/me is requested again and the card shows the existing author
|
|
|
|
Scenario: Existing author on load
|
|
Given GET /v1/auth/me carries an author
|
|
Then the card shows the author's name, "Edit author" and "Linked to your account. New articles start with this author." without the "Created" banner
|
|
|
|
Scenario: Edit own author without author rights
|
|
Given the session role has no author rights and has a linked author
|
|
When the user activates "Edit author"
|
|
Then the author form opens titled "Edit Marta Alves" with Linked user "marta@renew.example" locked, "This is your author profile. Only an owner or admin can change the link." and "Save changes"
|
|
And there is no "Unlink" and no delete action on the form or on the screen
|
|
When the user saves a change
|
|
Then exactly one PATCH /v1/author/{id} is sent with only the changed fields
|
|
|
|
@not-in-design
|
|
Scenario: Edit author as owner or admin
|
|
Given the session role is "owner" or "admin" with a linked author
|
|
When the user activates "Edit author" on /profile
|
|
Then the existing AuthorFormPanel opens in place, on /profile, with "Unlink" allowed
|
|
|
|
Scenario: Language and session
|
|
Given the language is "fr"
|
|
Then every new string on the three screens is the French message
|
|
And a 401 on any new call lands on /login?redirect=<path>&reason=expired and a 403 from an unverified account lands on /register/pending
|
|
|
|
Rule: Catalogue and copy left over from the Sites and Authors screens
|
|
|
|
Scenario: Authors routes in the API catalogue
|
|
Then web/src/data/apiCatalogue.ts has an "Authors" group and an "Authors · owner and admin" group covering every /v1/author route, including POST /v1/author/me as a session-only route
|
|
And web/src/data/__tests__/apiCatalogue.spec.ts covers them
|
|
|
|
Scenario: French strings for the catalogue groups
|
|
Given the language is "fr"
|
|
Then the catalogue group titles "Sites", "Sites · owner only", "Authors" and "Authors · owner and admin" and their descriptions have French entries
|
|
|
|
Scenario: Session-only rows
|
|
Then the session-only routes of Sites and Authors appear in SESSION_GROUPS with SESSION_REQUIRED_MESSAGE
|
|
|
|
Scenario: The owner_only copy of the design board
|
|
Then the 403 page copy "You don't have access to Sites" with "Only the owner can manage sites. Ask the owner of this site if you need a change."
|
|
And the action toast "Only the owner can manage sites" with "Ask the owner of this site."
|
|
exist in the SPA and have French entries
|