Skip to content

Commit

Permalink
Settings improvements (#10924)
Browse files Browse the repository at this point in the history
- Address enso-org/cloud-v2#1457
- Show x instead of a circle for close tab icon on Windows and Linux
- Free / Solo no longer shows Organization, Members, User Groups, and Activity Log
- Invite button should only be there for the admin in Team or above (should already be present)
- Should list the required symbols in the password change.
- Changing headshot appeared briefly and then disappeared.
- Fix enso-org/cloud-v2#1455
- The `Alert` component no longer focuses itself on every keystroke.
- Fix #10988
- Same issue as above.

Other related changes:
- Fix word wrapping in tooltips when a word is wider than the entire tooltip
- Add word wrapping to "members" table in members tab
- Limit length of "email" column of members table in "user groups" tab
- Hide empty sections
- Add text to empty "user groups" table in "user groups" tab
- ~~Close "set organization name" modal when organization name is set~~
- Already fixed by another PR

# Important Notes
None
  • Loading branch information
somebody1234 authored Sep 6, 2024
1 parent c87053d commit 3bcc694
Show file tree
Hide file tree
Showing 18 changed files with 209 additions and 129 deletions.
45 changes: 38 additions & 7 deletions app/.vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,80 @@
"request": "launch",
"name": "Dashboard",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "--filter", "enso-dashboard", "dev"]
"runtimeArgs": ["run", "--filter", "enso-dashboard", "dev"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "Dashboard (Electron, Linux)",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "--filter", "enso", "watch:linux"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "Dashboard (Electron, macOS)",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "--filter", "enso", "watch:macos"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "Dashboard (Electron, Windows)",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "--filter", "enso", "watch:windows"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "GUI",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["dev:gui"]
"runtimeArgs": ["dev:gui"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "GUI (Storybook)",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "--filter", "enso-gui2", "story:dev"]
"runtimeArgs": ["run", "--filter", "enso-gui2", "story:dev"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "Dashboard (Build)",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["build:gui"]
"runtimeArgs": ["build:gui"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "Dashboard (E2E UI)",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "--filter", "enso-dashboard", "test:e2e:debug"]
"runtimeArgs": ["run", "--filter", "enso-dashboard", "test:e2e:debug"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "GUI (E2E UI)",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "--filter", "enso-gui2", "test:e2e", "--", "--ui"]
"runtimeArgs": ["run", "--filter", "enso-gui2", "test:e2e", "--", "--ui"],
"outputCapture": "std"
},
{
"type": "node",
"request": "launch",
"name": "Dashboard (All tests)",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "--filter", "enso-dashboard", "test"]
"runtimeArgs": ["run", "--filter", "enso-dashboard", "test"],
"outputCapture": "std"
},
{
"type": "node",
Expand Down
15 changes: 13 additions & 2 deletions app/dashboard/e2e/organizationSettings.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
/** @file Test the organization settings tab. */
import * as test from '@playwright/test'

import { Plan } from 'enso-common/src/services/Backend'
import * as actions from './actions'

test.test('organization settings', async ({ page }) => {
const api = await actions.mockAllAndLoginAndExposeAPI({ page })
const api = await actions.mockAllAndLoginAndExposeAPI({
page,
setupAPI: (theApi) => {
theApi.setPlan(Plan.team)
},
})
const localActions = actions.settings.organization

// Setup
Expand Down Expand Up @@ -76,7 +82,12 @@ test.test('organization settings', async ({ page }) => {
})

test.test('upload organization profile picture', async ({ page }) => {
const api = await actions.mockAllAndLoginAndExposeAPI({ page })
const api = await actions.mockAllAndLoginAndExposeAPI({
page,
setupAPI: (theApi) => {
theApi.setPlan(Plan.team)
},
})
const localActions = actions.settings.organizationProfilePicture

await localActions.go(page)
Expand Down
8 changes: 2 additions & 6 deletions app/dashboard/src/components/AriaComponents/Alert/Alert.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/** @file Alert component. */
import { type ForwardedRef, type HTMLAttributes, type PropsWithChildren } from 'react'

import { mergeRefs } from '#/utilities/mergeRefs'
import { forwardRef } from '#/utilities/react'
import { tv, type VariantProps } from '#/utilities/tailwindVariants'

Expand Down Expand Up @@ -57,6 +56,7 @@ export interface AlertProps
HTMLAttributes<HTMLDivElement> {}

/** Alert component. */
// eslint-disable-next-line no-restricted-syntax
export const Alert = forwardRef(function Alert(
props: AlertProps,
ref: ForwardedRef<HTMLDivElement>,
Expand All @@ -71,11 +71,7 @@ export const Alert = forwardRef(function Alert(
return (
<div
className={ALERT_STYLES({ variant, size, className, rounded, fullWidth })}
ref={mergeRefs(ref, (e) => {
if (variant === 'error') {
e?.focus()
}
})}
ref={ref}
{...containerProps}
>
{children}
Expand Down
35 changes: 15 additions & 20 deletions app/dashboard/src/components/AriaComponents/Button/CloseButton.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,44 @@
/** @file A button for closing a modal. */

import * as React from 'react'

import Dismiss from '#/assets/dismiss.svg'

import * as textProvider from '#/providers/TextProvider'

import * as button from '#/components/AriaComponents/Button'

import * as tailwindMerge from '#/utilities/tailwindMerge'
import DismissIcon from '#/assets/dismiss.svg'
import { Button, type ButtonProps } from '#/components/AriaComponents/Button'
import { useText } from '#/providers/TextProvider'
import { twMerge } from '#/utilities/tailwindMerge'
import { isOnMacOS } from 'enso-common/src/detect'

// ===================
// === CloseButton ===
// ===================

/** Props for a {@link CloseButton}. */
export type CloseButtonProps = Omit<
button.ButtonProps,
'children' | 'rounding' | 'size' | 'variant'
>
export type CloseButtonProps = Omit<ButtonProps, 'children' | 'rounding' | 'size' | 'variant'>

/** A styled button with a close icon that appears on hover. */
export function CloseButton(props: CloseButtonProps) {
const { getText } = textProvider.useText()
const { getText } = useText()
const {
className,
icon = Dismiss,
icon = DismissIcon,
tooltip = false,
'aria-label': ariaLabel = getText('closeModalShortcut'),
...buttonProps
} = props

return (
<button.Button
<Button
variant="icon"
className={(values) =>
tailwindMerge.twMerge(
'bg-primary/30 hover:bg-red-500/80 focus-visible:bg-red-500/80 focus-visible:outline-offset-1',
twMerge(
'hover:bg-red-500/80 focus-visible:bg-red-500/80 focus-visible:outline-offset-1',
isOnMacOS() ? 'bg-primary/30' : (
'text-primary/90 hover:text-primary focus-visible:text-primary'
),
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
// @ts-expect-error ts fails to infer the type of the className prop
typeof className === 'function' ? className(values) : className,
)
}
tooltip={tooltip}
showIconOnHover
showIconOnHover={isOnMacOS()}
size="xsmall"
rounded="full"
extraClickZone="medium"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as text from '../Text'
// =================

export const TOOLTIP_STYLES = twv.tv({
base: 'group flex justify-center items-center text-center text-balance break-all z-50',
base: 'group flex justify-center items-center text-center text-balance [overflow-wrap:anywhere] z-tooltip',
variants: {
variant: {
custom: '',
Expand Down
91 changes: 48 additions & 43 deletions app/dashboard/src/components/Devtools/EnsoDevtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import * as reactQuery from '@tanstack/react-query'

import { IS_DEV_MODE } from 'enso-common/src/detect'

import cross from '#/assets/cross.svg'
import CrossIcon from '#/assets/cross.svg'
import DevtoolsLogo from '#/assets/enso_logo.svg'
import trash from '#/assets/trash.svg'
import TrashIcon from '#/assets/trash.svg'

import { SETUP_PATH } from '#/appUtils'

Expand Down Expand Up @@ -45,7 +45,7 @@ import {
} from '#/providers/FeatureFlagsProvider'
import { useLocalStorage } from '#/providers/LocalStorageProvider'
import * as backend from '#/services/Backend'
import LocalStorage from '#/utilities/LocalStorage'
import LocalStorage, { type LocalStorageData } from '#/utilities/LocalStorage'
import { unsafeEntries } from 'enso-common/src/utilities/data/object'

/**
Expand All @@ -60,6 +60,10 @@ export function EnsoDevtools() {
const enableVersionChecker = useEnableVersionChecker()
const setEnableVersionChecker = useSetEnableVersionChecker()
const { localStorage } = useLocalStorage()
const [localStorageState, setLocalStorageState] = React.useState<Partial<LocalStorageData>>({})

// Re-render when localStorage changes.
React.useEffect(() => localStorage.subscribeAll(setLocalStorageState), [localStorage])

const featureFlags = useFeatureFlags()
const setFeatureFlags = useSetFeatureFlags()
Expand Down Expand Up @@ -157,46 +161,6 @@ export function EnsoDevtools() {
)}
</ariaComponents.Form>

<Separator orientation="horizontal" className="my-3" />

<div className="mb-2 flex w-full items-center justify-between">
<Text variant="subtitle">{getText('localStorage')}</Text>

<Button
aria-label={getText('deleteAll')}
size="small"
variant="icon"
icon={trash}
onPress={() => {
for (const [key] of unsafeEntries(LocalStorage.keyMetadata)) {
localStorage.delete(key)
}
}}
/>
</div>

<div className="flex flex-col gap-0.5">
{unsafeEntries(LocalStorage.keyMetadata).map(([key]) => (
<div key={key} className="flex w-full items-center justify-between gap-1">
<Text variant="body">
{key
.replace(/[A-Z]/g, (m) => ' ' + m.toLowerCase())
.replace(/^./, (m) => m.toUpperCase())}
</Text>

<Button
variant="icon"
size="small"
aria-label={getText('delete')}
icon={cross}
onPress={() => {
localStorage.delete(key)
}}
/>
</div>
))}
</div>

<ariaComponents.Separator orientation="horizontal" className="my-3" />

<ariaComponents.Text variant="subtitle" className="mb-2">
Expand Down Expand Up @@ -292,6 +256,47 @@ export function EnsoDevtools() {
)
})}
</ariaComponents.Form>

<Separator orientation="horizontal" className="my-3" />

<div className="mb-2 flex w-full items-center justify-between">
<Text variant="subtitle">{getText('localStorage')}</Text>

<Button
aria-label={getText('deleteAll')}
size="small"
variant="icon"
icon={TrashIcon}
onPress={() => {
for (const [key] of unsafeEntries(LocalStorage.keyMetadata)) {
localStorage.delete(key)
}
}}
/>
</div>

<div className="flex flex-col gap-0.5">
{unsafeEntries(LocalStorage.keyMetadata).map(([key]) => (
<div key={key} className="flex w-full items-center justify-between gap-1">
<Text variant="body">
{key
.replace(/[A-Z]/g, (m) => ' ' + m.toLowerCase())
.replace(/^./, (m) => m.toUpperCase())}
</Text>

<Button
variant="icon"
size="small"
isDisabled={localStorageState[key] == null}
aria-label={getText('delete')}
icon={CrossIcon}
onPress={() => {
localStorage.delete(key)
}}
/>
</div>
))}
</div>
</Popover>
</ariaComponents.DialogTrigger>
</Portal>
Expand Down
2 changes: 1 addition & 1 deletion app/dashboard/src/components/styled/SidebarTabButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Button, type ButtonProps } from '#/components/AriaComponents'
import { tv } from '#/utilities/tailwindVariants'

const SIDEBAR_TAB_BUTTON_STYLES = tv({
base: 'font-medium',
base: 'z-1 font-medium',
variants: {
isActive: { true: 'bg-white opacity-100' },
},
Expand Down
16 changes: 9 additions & 7 deletions app/dashboard/src/layouts/Settings/MembersSettingsSection.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/** @file Settings tab for viewing and editing organization members. */
import * as React from 'react'

import { useMutation, useSuspenseQueries } from '@tanstack/react-query'

import { backendMutationOptions } from '#/hooks/backendHooks'
Expand Down Expand Up @@ -92,20 +90,24 @@ export default function MembersSettingsSection() {
<table className="table-fixed self-start rounded-rows">
<thead>
<tr className="h-row">
<th className="w-members-name-column border-x-2 border-transparent bg-clip-padding px-cell-x text-left text-sm font-semibold last:border-r-0">
<th className="w-48 border-x-2 border-transparent bg-clip-padding px-cell-x text-left text-sm font-semibold last:border-r-0">
{getText('name')}
</th>
<th className="w-members-email-column border-x-2 border-transparent bg-clip-padding px-cell-x text-left text-sm font-semibold last:border-r-0">
<th className="w-48 border-x-2 border-transparent bg-clip-padding px-cell-x text-left text-sm font-semibold last:border-r-0">
{getText('status')}
</th>
</tr>
</thead>
<tbody className="select-text">
{members.map((member) => (
<tr key={member.email} className="group h-row rounded-rows-child">
<td className="border-x-2 border-transparent bg-clip-padding px-4 py-1 first:rounded-l-full last:rounded-r-full last:border-r-0">
<span className="block text-sm">{member.email}</span>
<span className="block text-xs text-primary/50">{member.name}</span>
<td className="max-w-48 border-x-2 border-transparent bg-clip-padding px-4 py-1 first:rounded-l-full last:rounded-r-full last:border-r-0">
<ariaComponents.Text truncate="1" className="block">
{member.email}
</ariaComponents.Text>
<ariaComponents.Text truncate="1" className="block text-2xs text-primary/40">
{member.name}
</ariaComponents.Text>
</td>
<td className="border-x-2 border-transparent bg-clip-padding px-cell-x first:rounded-l-full last:rounded-r-full last:border-r-0">
<div className="flex flex-col">
Expand Down
Loading

0 comments on commit 3bcc694

Please sign in to comment.