feat: Add page titles (#2070)

This commit is contained in:
Kyle Carberry
2022-06-06 08:34:10 -05:00
committed by GitHub
parent 3f3ecbf8b3
commit 0ac37b146d
17 changed files with 134 additions and 36 deletions

View File

@ -21,7 +21,6 @@
<link rel="mask-icon" href="/static/favicon.svg" color="#000000" crossorigin="use-credentials" />
<link rel="alternate icon" type="image/png" href="/favicon.png" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>Coder</title>
</head>
<body>

View File

@ -44,6 +44,7 @@
"history": "5.3.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-helmet": "^6.1.0",
"react-markdown": "8.0.3",
"react-router-dom": "6.3.0",
"sourcemapped-stacktrace": "1.1.11",
@ -73,6 +74,7 @@
"@types/node": "14.18.16",
"@types/react": "17.0.44",
"@types/react-dom": "17.0.16",
"@types/react-helmet": "^6.1.5",
"@types/superagent": "4.1.15",
"@types/uuid": "8.3.4",
"@typescript-eslint/eslint-plugin": "5.23.0",

View File

@ -1,6 +1,8 @@
import Box from "@material-ui/core/Box"
import { FC } from "react"
import { Helmet } from "react-helmet"
import { Outlet } from "react-router-dom"
import { pageTitle } from "../../util/page"
import { AuthAndFrame } from "../AuthAndFrame/AuthAndFrame"
import { Margins } from "../Margins/Margins"
import { TabPanel } from "../TabPanel/TabPanel"
@ -22,6 +24,9 @@ export const SettingsLayout: FC = () => {
return (
<AuthAndFrame>
<Box display="flex" flexDirection="column">
<Helmet>
<title>{pageTitle("Settings")}</title>
</Helmet>
<Margins>
<TabPanel title={Language.settingsLabel} menuItems={menuItems}>
<Outlet />

View File

@ -1,9 +1,11 @@
import { makeStyles } from "@material-ui/core/styles"
import { useActor } from "@xstate/react"
import React, { useContext, useEffect, useState } from "react"
import { Helmet } from "react-helmet"
import { getApiKey } from "../../api/api"
import { CliAuthToken } from "../../components/CliAuthToken/CliAuthToken"
import { FullScreenLoader } from "../../components/Loader/FullScreenLoader"
import { pageTitle } from "../../util/page"
import { XServiceContext } from "../../xServices/StateContext"
export const CliAuthenticationPage: React.FC = () => {
@ -29,6 +31,9 @@ export const CliAuthenticationPage: React.FC = () => {
return (
<div className={styles.root}>
<Helmet>
<title>{pageTitle("CLI Auth")}</title>
</Helmet>
<CliAuthToken sessionToken={apiKey} />
</div>
)

View File

@ -1,8 +1,10 @@
import { useMachine } from "@xstate/react"
import { FC } from "react"
import { Helmet } from "react-helmet"
import { useNavigate, useSearchParams } from "react-router-dom"
import { Template } from "../../api/typesGenerated"
import { useOrganizationId } from "../../hooks/useOrganizationId"
import { pageTitle } from "../../util/page"
import { createWorkspaceMachine } from "../../xServices/createWorkspace/createWorkspaceXService"
import { CreateWorkspacePageView } from "./CreateWorkspacePageView"
@ -21,6 +23,10 @@ const CreateWorkspacePage: FC = () => {
})
return (
<>
<Helmet>
<title>{pageTitle("Create Workspace")}</title>
</Helmet>
<CreateWorkspacePageView
loadingTemplates={createWorkspaceState.matches("gettingTemplates")}
loadingTemplateSchema={createWorkspaceState.matches("gettingTemplateSchema")}
@ -44,6 +50,7 @@ const CreateWorkspacePage: FC = () => {
})
}}
/>
</>
)
}

View File

@ -1,10 +1,12 @@
import { makeStyles } from "@material-ui/core/styles"
import { useActor } from "@xstate/react"
import React, { useContext } from "react"
import { Helmet } from "react-helmet"
import { Navigate, useLocation } from "react-router-dom"
import { isApiError } from "../../api/errors"
import { Footer } from "../../components/Footer/Footer"
import { SignInForm } from "../../components/SignInForm/SignInForm"
import { pageTitle } from "../../util/page"
import { retrieveRedirect } from "../../util/redirect"
import { XServiceContext } from "../../xServices/StateContext"
@ -50,6 +52,9 @@ export const LoginPage: React.FC = () => {
} else {
return (
<div className={styles.root}>
<Helmet>
<title>{pageTitle("Login")}</title>
</Helmet>
<div className={styles.layout}>
<div className={styles.container}>
<SignInForm

View File

@ -1,8 +1,10 @@
import { useMachine } from "@xstate/react"
import { FC } from "react"
import { Helmet } from "react-helmet"
import { useParams } from "react-router-dom"
import { Loader } from "../../components/Loader/Loader"
import { useOrganizationId } from "../../hooks/useOrganizationId"
import { pageTitle } from "../../util/page"
import { templateMachine } from "../../xServices/template/templateXService"
import { TemplatePageView } from "./TemplatePageView"
@ -33,10 +35,15 @@ export const TemplatePage: FC = () => {
}
return (
<>
<Helmet>
<title>{pageTitle(`${template.name} · Template`)}</title>
</Helmet>
<TemplatePageView
template={template}
activeTemplateVersion={activeTemplateVersion}
templateResources={templateResources}
/>
</>
)
}

View File

@ -1,5 +1,7 @@
import { useActor, useMachine } from "@xstate/react"
import React, { useContext } from "react"
import { Helmet } from "react-helmet"
import { pageTitle } from "../../util/page"
import { XServiceContext } from "../../xServices/StateContext"
import { templatesMachine } from "../../xServices/templates/templatesXService"
import { TemplatesPageView } from "./TemplatesPageView"
@ -10,11 +12,16 @@ const TemplatesPage: React.FC = () => {
const [templatesState] = useMachine(templatesMachine)
return (
<>
<Helmet>
<title>{pageTitle("Templates")}</title>
</Helmet>
<TemplatesPageView
templates={templatesState.context.templates}
canCreateTemplate={authState.context.permissions?.createTemplates}
loading={templatesState.hasTag("loading")}
/>
</>
)
}

View File

@ -1,6 +1,7 @@
import { makeStyles } from "@material-ui/core/styles"
import { useMachine } from "@xstate/react"
import { FC, useEffect, useRef, useState } from "react"
import { Helmet } from "react-helmet"
import { useLocation, useNavigate, useParams } from "react-router-dom"
import { v4 as uuidv4 } from "uuid"
import * as XTerm from "xterm"
@ -8,6 +9,7 @@ import { FitAddon } from "xterm-addon-fit"
import { WebLinksAddon } from "xterm-addon-web-links"
import "xterm/css/xterm.css"
import { MONOSPACE_FONT_FAMILY } from "../../theme/constants"
import { pageTitle } from "../../util/page"
import { terminalMachine } from "../../xServices/terminal/terminalXService"
export const Language = {
@ -179,6 +181,15 @@ const TerminalPage: FC<{
return (
<>
<Helmet>
<title>
{terminalState.context.workspace
? pageTitle(
`Terminal · ${terminalState.context.workspace.owner_name}/${terminalState.context.workspace.name}`,
)
: ""}
</title>
</Helmet>
{/* This overlay makes it more obvious that the terminal is disconnected. */}
{/* It's nice for situations where Coder restarts, and they are temporarily disconnected. */}
<div className={`${styles.overlay} ${isDisconnected ? "" : "connected"}`}>

View File

@ -1,9 +1,11 @@
import { useActor, useSelector } from "@xstate/react"
import React, { useContext } from "react"
import { Helmet } from "react-helmet"
import { useNavigate } from "react-router"
import * as TypesGen from "../../../api/typesGenerated"
import { CreateUserForm } from "../../../components/CreateUserForm/CreateUserForm"
import { Margins } from "../../../components/Margins/Margins"
import { pageTitle } from "../../../util/page"
import { selectOrgId } from "../../../xServices/auth/authSelectors"
import { XServiceContext } from "../../../xServices/StateContext"
@ -23,6 +25,9 @@ export const CreateUserPage: React.FC = () => {
return (
<Margins>
<Helmet>
<title>{pageTitle("Create User")}</title>
</Helmet>
<CreateUserForm
formErrors={createUserFormErrors}
onSubmit={(user: TypesGen.CreateUserRequest) => usersSend({ type: "CREATE", user })}

View File

@ -1,8 +1,10 @@
import { useActor, useSelector } from "@xstate/react"
import React, { useContext, useEffect } from "react"
import { Helmet } from "react-helmet"
import { useNavigate } from "react-router"
import { ConfirmDialog } from "../../components/ConfirmDialog/ConfirmDialog"
import { ResetPasswordDialog } from "../../components/ResetPasswordDialog/ResetPasswordDialog"
import { pageTitle } from "../../util/page"
import { selectPermissions } from "../../xServices/auth/authSelectors"
import { XServiceContext } from "../../xServices/StateContext"
import { UsersPageView } from "./UsersPageView"
@ -48,6 +50,9 @@ export const UsersPage: React.FC = () => {
return (
<>
<Helmet>
<title>{pageTitle("Users")}</title>
</Helmet>
<UsersPageView
roles={roles}
users={users}

View File

@ -2,6 +2,7 @@ import { makeStyles } from "@material-ui/core/styles"
import Typography from "@material-ui/core/Typography"
import { useMachine } from "@xstate/react"
import { FC } from "react"
import { Helmet } from "react-helmet"
import { useParams } from "react-router-dom"
import { ProvisionerJobLog } from "../../api/typesGenerated"
import { Loader } from "../../components/Loader/Loader"
@ -9,6 +10,7 @@ import { Margins } from "../../components/Margins/Margins"
import { Stack } from "../../components/Stack/Stack"
import { WorkspaceBuildLogs } from "../../components/WorkspaceBuildLogs/WorkspaceBuildLogs"
import { WorkspaceBuildStats } from "../../components/WorkspaceBuildStats/WorkspaceBuildStats"
import { pageTitle } from "../../util/page"
import { workspaceBuildMachine } from "../../xServices/workspaceBuild/workspaceBuildXService"
const sortLogsByCreatedAt = (logs: ProvisionerJobLog[]) => {
@ -34,6 +36,9 @@ export const WorkspaceBuildPage: FC = () => {
return (
<Margins>
<Helmet>
<title>{build ? pageTitle(`Build #${build.build_number} · ${build.workspace_name}`) : ""}</title>
</Helmet>
<Stack>
<Typography variant="h4" className={styles.title}>
Logs

View File

@ -1,5 +1,6 @@
import { useMachine } from "@xstate/react"
import React, { useEffect } from "react"
import { Helmet } from "react-helmet"
import { useNavigate, useParams } from "react-router-dom"
import { DeleteWorkspaceDialog } from "../../components/DeleteWorkspaceDialog/DeleteWorkspaceDialog"
import { ErrorSummary } from "../../components/ErrorSummary/ErrorSummary"
@ -8,6 +9,7 @@ import { Margins } from "../../components/Margins/Margins"
import { Stack } from "../../components/Stack/Stack"
import { Workspace } from "../../components/Workspace/Workspace"
import { firstOrItem } from "../../util/array"
import { pageTitle } from "../../util/page"
import { workspaceMachine } from "../../xServices/workspace/workspaceXService"
import { workspaceScheduleBannerMachine } from "../../xServices/workspaceSchedule/workspaceScheduleBannerXService"
@ -36,6 +38,9 @@ export const WorkspacePage: React.FC = () => {
} else {
return (
<Margins>
<Helmet>
<title>{pageTitle(`${workspace.owner_name}/${workspace.name}`)}</title>
</Helmet>
<Stack spacing={4}>
<>
<Workspace

View File

@ -11,11 +11,13 @@ import SearchIcon from "@material-ui/icons/Search"
import { useMachine } from "@xstate/react"
import { FormikErrors, useFormik } from "formik"
import { FC, useState } from "react"
import { Helmet } from "react-helmet"
import { Link as RouterLink } from "react-router-dom"
import { CloseDropdown, OpenDropdown } from "../../components/DropdownArrows/DropdownArrows"
import { Margins } from "../../components/Margins/Margins"
import { Stack } from "../../components/Stack/Stack"
import { getFormHelpers, onChangeTrimmed } from "../../util/formUtils"
import { pageTitle } from "../../util/page"
import { workspacesMachine } from "../../xServices/workspaces/workspacesXService"
import { WorkspacesPageView } from "./WorkspacesPageView"
@ -74,6 +76,9 @@ const WorkspacesPage: FC = () => {
return (
<Margins>
<Helmet>
<title>{pageTitle("Workspaces")}</title>
</Helmet>
<Stack direction="row" className={styles.workspacesHeaderContainer}>
<Stack direction="column" className={styles.filterColumn}>
<Stack direction="row" spacing={0} className={styles.filterContainer}>

3
site/src/util/page.ts Normal file
View File

@ -0,0 +1,3 @@
export const pageTitle = (prefix: string): string => {
return `${prefix} Coder`
}

View File

@ -63,7 +63,7 @@ const config: Configuration = {
port: process.env.PORT || 8080,
proxy: {
"/api": {
target: "http://localhost:3000",
target: "https://dev.coder.com",
ws: true,
secure: false,
},

View File

@ -3041,6 +3041,13 @@
dependencies:
"@types/react" "^17"
"@types/react-helmet@^6.1.5":
version "6.1.5"
resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-6.1.5.tgz#35f89a6b1646ee2bc342a33a9a6c8777933f9083"
integrity sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==
dependencies:
"@types/react" "*"
"@types/react-syntax-highlighter@11.0.5":
version "11.0.5"
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087"
@ -11389,7 +11396,7 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-fast-compare@^3.0.1, react-fast-compare@^3.2.0:
react-fast-compare@^3.0.1, react-fast-compare@^3.1.1, react-fast-compare@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
@ -11405,6 +11412,16 @@ react-helmet-async@^1.0.7:
react-fast-compare "^3.2.0"
shallowequal "^1.1.0"
react-helmet@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"
integrity sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==
dependencies:
object-assign "^4.1.1"
prop-types "^15.7.2"
react-fast-compare "^3.1.1"
react-side-effect "^2.1.0"
react-hot-loader@4.13.0:
version "4.13.0"
resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202"
@ -11506,6 +11523,11 @@ react-router@6.3.0, react-router@^6.0.0:
dependencies:
history "^5.2.0"
react-side-effect@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-side-effect/-/react-side-effect-2.1.1.tgz#66c5701c3e7560ab4822a4ee2742dee215d72eb3"
integrity sha512-2FoTQzRNTncBVtnzxFOk2mCpcfxQpenBMbk5kSVBg5UcPqV9fRbgY2zhb7GTWWOlpFmAxhClBDlIq8Rsubz1yQ==
react-sizeme@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.2.tgz#4a2f167905ba8f8b8d932a9e35164e459f9020e4"