feat: Add preferences pages (#893)

This commit is contained in:
Bruno Quaresma
2022-04-07 16:44:08 -03:00
committed by GitHub
parent 17848b3b86
commit 75ef1f4b26
13 changed files with 155 additions and 95 deletions

View File

@ -1,12 +1,16 @@
import React from "react"
import { Route, Routes } from "react-router-dom"
import { Navigate, Route, Routes } from "react-router-dom"
import { AuthAndNav, RequireAuth } from "./components"
import { PreferencesLayout } from "./components/Preferences/Layout"
import { IndexPage } from "./pages"
import { NotFoundPage } from "./pages/404"
import { CliAuthenticationPage } from "./pages/cli-auth"
import { HealthzPage } from "./pages/healthz"
import { SignInPage } from "./pages/login"
import { PreferencesPage } from "./pages/preferences"
import { PreferencesAccountPage } from "./pages/preferences/account"
import { PreferencesLinkedAccountsPage } from "./pages/preferences/linked-accounts"
import { PreferencesSecurityPage } from "./pages/preferences/security"
import { PreferencesSSHKeysPage } from "./pages/preferences/ssh-keys"
import { TemplatesPage } from "./pages/templates"
import { TemplatePage } from "./pages/templates/[organization]/[template]"
import { CreateWorkspacePage } from "./pages/templates/[organization]/[template]/create"
@ -68,15 +72,12 @@ export const AppRouter: React.FC = () => (
/>
</Route>
<Route path="preferences">
<Route
index
element={
<AuthAndNav>
<PreferencesPage />
</AuthAndNav>
}
/>
<Route path="preferences" element={<PreferencesLayout />}>
<Route index element={<Navigate to="account" />} />
<Route path="account" element={<PreferencesAccountPage />} />
<Route path="security" element={<PreferencesSecurityPage />} />
<Route path="ssh-keys" element={<PreferencesSSHKeysPage />} />
<Route path="linked-accounts" element={<PreferencesLinkedAccountsPage />} />
</Route>
{/* Using path="*"" means "match anything", so this route

View File

@ -1,21 +0,0 @@
import { Story } from "@storybook/react"
import React from "react"
import { Panel, PanelProps } from "./"
export default {
title: "Page/Panel",
component: Panel,
}
const Template: Story<PanelProps> = (args: PanelProps) => <Panel {...args} />
export const Example = Template.bind({})
Example.args = {
title: "Panel title",
activeTab: "oauthSettings",
menuItems: [
{ label: "OAuth Settings", value: "oauthSettings" },
{ label: "Security", value: "oauthSettings", hasChanges: true },
{ label: "Hardware", value: "oauthSettings" },
],
}

View File

@ -0,0 +1,34 @@
import Box from "@material-ui/core/Box"
import React from "react"
import { Outlet } from "react-router-dom"
import { AuthAndNav } from "../Page"
import { TabPanel } from "../TabPanel"
export const Language = {
accountLabel: "Account",
securityLabel: "Security",
sshKeysLabel: "SSH Keys",
linkedAccountsLabel: "Linked Accounts",
preferencesLabel: "Preferences",
}
const menuItems = [
{ label: Language.accountLabel, path: "/preferences/account" },
{ label: Language.securityLabel, path: "/preferences/security" },
{ label: Language.sshKeysLabel, path: "/preferences/ssh-keys" },
{ label: Language.linkedAccountsLabel, path: "/preferences/linked-accounts" },
]
export const PreferencesLayout: React.FC = () => {
return (
<AuthAndNav>
<Box display="flex" flexDirection="column">
<Box style={{ maxWidth: "1380px", margin: "1em auto" }}>
<TabPanel title={Language.preferencesLabel} menuItems={menuItems}>
<Outlet />
</TabPanel>
</Box>
</Box>
</AuthAndNav>
)
}

View File

@ -1,20 +0,0 @@
import { Story } from "@storybook/react"
import React from "react"
import { Sidebar, SidebarProps } from "./"
export default {
title: "Page/Sidebar",
component: Sidebar,
}
const Template: Story<SidebarProps> = (args: SidebarProps) => <Sidebar {...args} />
export const Example = Template.bind({})
Example.args = {
activeItem: "oauthSettings",
menuItems: [
{ label: "OAuth Settings", value: "oauthSettings" },
{ label: "Security", value: "security", hasChanges: true },
{ label: "Hardware", value: "hardware" },
],
}

View File

@ -0,0 +1,20 @@
import { Story } from "@storybook/react"
import React from "react"
import { TabPanel, TabPanelProps } from "."
export default {
title: "TabPanel/TabPanel",
component: TabPanel,
}
const Template: Story<TabPanelProps> = (args: TabPanelProps) => <TabPanel {...args} />
export const Example = Template.bind({})
Example.args = {
title: "Title",
menuItems: [
{ label: "OAuth Settings", path: "oauthSettings" },
{ label: "Security", path: "oauthSettings", hasChanges: true },
{ label: "Hardware", path: "oauthSettings" },
],
}

View File

@ -0,0 +1,19 @@
import { Story } from "@storybook/react"
import React from "react"
import { TabSidebar, TabSidebarProps } from "./TabSidebar"
export default {
title: "TabPanel/TabSidebar",
component: TabSidebar,
}
const Template: Story<TabSidebarProps> = (args: TabSidebarProps) => <TabSidebar {...args} />
export const Example = Template.bind({})
Example.args = {
menuItems: [
{ label: "OAuth Settings", path: "oauthSettings" },
{ label: "Security", path: "security", hasChanges: true },
{ label: "Hardware", path: "hardware" },
],
}

View File

@ -2,41 +2,35 @@ import List from "@material-ui/core/List"
import ListItem from "@material-ui/core/ListItem"
import { makeStyles } from "@material-ui/core/styles"
import React from "react"
import { NavLink } from "react-router-dom"
import { combineClasses } from "../../util/combine-classes"
export interface SidebarItem {
value: string
export interface TabSidebarItem {
path: string
label: string
hasChanges?: boolean
}
export interface SidebarProps {
menuItems: SidebarItem[]
activeItem?: string
onSelect?: (value: string) => void
export interface TabSidebarProps {
menuItems: TabSidebarItem[]
}
export const Sidebar: React.FC<SidebarProps> = ({ menuItems, activeItem, onSelect }) => {
export const TabSidebar: React.FC<TabSidebarProps> = ({ menuItems }) => {
const styles = useStyles()
return (
<List className={styles.menu}>
{menuItems.map(({ hasChanges, ...tab }) => {
const isActive = activeItem === tab.value
return (
<ListItem
key={tab.value}
button
onClick={onSelect ? () => onSelect(tab.value) : undefined}
className={styles.menuItem}
disableRipple
focusRipple={false}
component="li"
>
<span className={combineClasses({ [styles.menuItemSpan]: true, active: isActive })}>
{hasChanges ? `${tab.label}*` : tab.label}
</span>
</ListItem>
<NavLink to={tab.path} key={tab.path} className={styles.link}>
{({ isActive }) => (
<ListItem button className={styles.menuItem} disableRipple focusRipple={false} component="li">
<span className={combineClasses({ [styles.menuItemSpan]: true, active: isActive })}>
{hasChanges ? `${tab.label}*` : tab.label}
</span>
</ListItem>
)}
</NavLink>
)
})}
</List>
@ -49,6 +43,10 @@ const useStyles = makeStyles((theme) => ({
marginTop: theme.spacing(5),
},
link: {
textDecoration: "none",
},
menuItem: {
letterSpacing: -theme.spacing(0.0375),
padding: 0,

View File

@ -1,18 +1,16 @@
import { makeStyles } from "@material-ui/core/styles"
import { fade } from "@material-ui/core/styles/colorManipulator"
import React from "react"
import { Sidebar, SidebarItem } from "../Sidebar"
import { TabSidebar, TabSidebarItem } from "./TabSidebar"
export type AdminMenuItemCallback = (menuItem: string) => void
export interface PanelProps {
export interface TabPanelProps {
title: string
menuItems: SidebarItem[]
activeTab: string
onSelect: AdminMenuItemCallback
menuItems: TabSidebarItem[]
}
export const Panel: React.FC<PanelProps> = ({ children, title, menuItems, activeTab, onSelect }) => {
export const TabPanel: React.FC<TabPanelProps> = ({ children, title, menuItems }) => {
const styles = useStyles()
return (
@ -20,7 +18,7 @@ export const Panel: React.FC<PanelProps> = ({ children, title, menuItems, active
<div className={styles.inner}>
<div className={styles.menuPanel}>
<div className={styles.title}>{title}</div>
<Sidebar menuItems={menuItems} activeItem={activeTab} onSelect={onSelect} />
<TabSidebar menuItems={menuItems} />
</div>
<div className={styles.contentPanel}>{children}</div>

View File

@ -0,0 +1,11 @@
import React from "react"
import { Section } from "../../components/Section"
const Language = {
title: "Account",
description: "Update your display name, email, profile picture, and dotfiles preferences.",
}
export const PreferencesAccountPage: React.FC = () => {
return <Section title={Language.title} description={Language.description} />
}

View File

@ -1,15 +0,0 @@
import Box from "@material-ui/core/Box"
import Paper from "@material-ui/core/Paper"
import React from "react"
import { Header } from "../../components/Header"
import { Footer } from "../../components/Page"
export const PreferencesPage: React.FC = () => {
return (
<Box display="flex" flexDirection="column">
<Header title="Preferences" />
<Paper style={{ maxWidth: "1380px", margin: "1em auto", width: "100%" }}>Preferences here!</Paper>
<Footer />
</Box>
)
}

View File

@ -0,0 +1,12 @@
import React from "react"
import { Section } from "../../components/Section"
const Language = {
title: "Linked Accounts",
description:
"Linking your Coder account will add your workspace SSH key, allowing you to perform Git actions on all your workspaces.",
}
export const PreferencesLinkedAccountsPage: React.FC = () => {
return <Section title={Language.title} description={Language.description} />
}

View File

@ -0,0 +1,11 @@
import React from "react"
import { Section } from "../../components/Section"
const Language = {
title: "Security",
description: "Changing your password will sign you out of your current session.",
}
export const PreferencesSecurityPage: React.FC = () => {
return <Section title={Language.title} description={Language.description} />
}

View File

@ -0,0 +1,12 @@
import React from "react"
import { Section } from "../../components/Section"
const Language = {
title: "SSH Keys",
description:
"Coder automatically inserts a private key into every workspace; you can add the corresponding public key to any services (such as Git) that you need access to from your workspace.",
}
export const PreferencesSSHKeysPage: React.FC = () => {
return <Section title={Language.title} description={Language.description} />
}