diff --git a/coderd/httpapi/httpapi.go b/coderd/httpapi/httpapi.go index cd55a09d51525..a9687d58a0604 100644 --- a/coderd/httpapi/httpapi.go +++ b/coderd/httpapi/httpapi.go @@ -154,6 +154,7 @@ func ResourceNotFound(rw http.ResponseWriter) { func Forbidden(rw http.ResponseWriter) { Write(context.Background(), rw, http.StatusForbidden, codersdk.Response{ Message: "Forbidden.", + Detail: "You don't have permission to view this content. If you believe this is a mistake, please contact your administrator or try signing in with different credentials.", }) } diff --git a/site/e2e/setup/addUsersAndLicense.spec.ts b/site/e2e/setup/addUsersAndLicense.spec.ts index f6817e0fd423d..bcaa8c9281cf8 100644 --- a/site/e2e/setup/addUsersAndLicense.spec.ts +++ b/site/e2e/setup/addUsersAndLicense.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from "@playwright/test"; import { API } from "api/api"; -import { Language } from "pages/CreateUserPage/CreateUserForm"; +import { Language } from "pages/CreateUserPage/Language"; import { coderPort, license, premiumTestsRequired, users } from "../constants"; import { expectUrl } from "../expectUrl"; import { createUser } from "../helpers"; diff --git a/site/src/api/errors.ts b/site/src/api/errors.ts index f1e63d1e39caf..873163e11a68d 100644 --- a/site/src/api/errors.ts +++ b/site/src/api/errors.ts @@ -133,6 +133,14 @@ export const getErrorDetail = (error: unknown): string | undefined => { return undefined; }; +export const getErrorStatus = (error: unknown): number | undefined => { + if (isApiError(error)) { + return error.status; + } + + return undefined; +}; + export class DetailedError extends Error { constructor( message: string, diff --git a/site/src/components/Alert/ErrorAlert.tsx b/site/src/components/Alert/ErrorAlert.tsx index 73d9c62480ab8..0198ea4e99540 100644 --- a/site/src/components/Alert/ErrorAlert.tsx +++ b/site/src/components/Alert/ErrorAlert.tsx @@ -1,6 +1,7 @@ import AlertTitle from "@mui/material/AlertTitle"; -import { getErrorDetail, getErrorMessage } from "api/errors"; +import { getErrorDetail, getErrorMessage, getErrorStatus } from "api/errors"; import type { FC } from "react"; +import { Link } from "../Link/Link"; import { Alert, AlertDetail, type AlertProps } from "./Alert"; export const ErrorAlert: FC< @@ -8,6 +9,7 @@ export const ErrorAlert: FC< > = ({ error, ...alertProps }) => { const message = getErrorMessage(error, "Something went wrong."); const detail = getErrorDetail(error); + const status = getErrorStatus(error); // For some reason, the message and detail can be the same on the BE, but does // not make sense in the FE to showing them duplicated @@ -15,14 +17,28 @@ export const ErrorAlert: FC< return ( - {detail ? ( - <> - {message} - {shouldDisplayDetail && {detail}} - - ) : ( - message - )} + { + // When the error is a Forbidden response we include a link for the user to + // go back to a known viewable page. + status === 403 ? ( + <> + {message} + + {detail}{" "} + + Go to workspaces + + + + ) : detail ? ( + <> + {message} + {shouldDisplayDetail && {detail}} + + ) : ( + message + ) + } ); }; diff --git a/site/src/pages/CreateUserPage/CreateUserForm.tsx b/site/src/pages/CreateUserPage/CreateUserForm.tsx index aebdd36e45adc..be8b4a15797b5 100644 --- a/site/src/pages/CreateUserPage/CreateUserForm.tsx +++ b/site/src/pages/CreateUserPage/CreateUserForm.tsx @@ -19,18 +19,7 @@ import { onChangeTrimmed, } from "utils/formUtils"; import * as Yup from "yup"; - -export const Language = { - emailLabel: "Email", - passwordLabel: "Password", - usernameLabel: "Username", - nameLabel: "Full name", - emailInvalid: "Please enter a valid email address.", - emailRequired: "Please enter an email address.", - passwordRequired: "Please enter a password.", - createUser: "Create", - cancel: "Cancel", -}; +import { Language } from "./Language"; export const authMethodLanguage = { password: { diff --git a/site/src/pages/CreateUserPage/CreateUserPage.test.tsx b/site/src/pages/CreateUserPage/CreateUserPage.test.tsx index f8b256e2d0cbb..ec75fc9a8e244 100644 --- a/site/src/pages/CreateUserPage/CreateUserPage.test.tsx +++ b/site/src/pages/CreateUserPage/CreateUserPage.test.tsx @@ -4,8 +4,8 @@ import { renderWithAuth, waitForLoaderToBeRemoved, } from "testHelpers/renderHelpers"; -import { Language as FormLanguage } from "./CreateUserForm"; import { CreateUserPage } from "./CreateUserPage"; +import { Language as FormLanguage } from "./Language"; const renderCreateUserPage = async () => { renderWithAuth(, { diff --git a/site/src/pages/CreateUserPage/Language.ts b/site/src/pages/CreateUserPage/Language.ts new file mode 100644 index 0000000000000..d449829aea89d --- /dev/null +++ b/site/src/pages/CreateUserPage/Language.ts @@ -0,0 +1,11 @@ +export const Language = { + emailLabel: "Email", + passwordLabel: "Password", + usernameLabel: "Username", + nameLabel: "Full name", + emailInvalid: "Please enter a valid email address.", + emailRequired: "Please enter an email address.", + passwordRequired: "Please enter a password.", + createUser: "Create", + cancel: "Cancel", +};