0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-10 22:22:45 -05:00

Merge pull request #1102 from logto-io/charles-ac-misc-improvements

fix(console): misc improvements and ui fixes
This commit is contained in:
Charles Zhao 2022-06-13 16:56:31 +08:00 committed by GitHub
commit 1d2272186e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 107 additions and 42 deletions

View file

@ -82,7 +82,8 @@
"eslintConfig": {
"extends": "@silverhand/react",
"rules": {
"complexity": "off"
"complexity": "off",
"@typescript-eslint/prefer-nullish-coalescing": "off"
}
},
"stylelint": {

View file

@ -6,6 +6,7 @@
flex-grow: 0;
flex-shrink: 0;
width: 248px;
overflow-y: auto;
> div + div {
margin-top: _.unit(6);

View file

@ -1,5 +1,4 @@
import { useLogto, UserInfoResponse } from '@logto/react';
import { UserRole } from '@logto/schemas';
import classNames from 'classnames';
import React, { useEffect, useRef, useState, MouseEvent } from 'react';
import { useTranslation } from 'react-i18next';
@ -10,6 +9,7 @@ import { getAvatarById } from '@/consts/avatars';
import useApi from '@/hooks/use-api';
import SignOut from '@/icons/SignOut';
import UserInfoSkeleton from '../UserInfoSkeleton';
import * as styles from './index.module.scss';
const UserInfo = () => {
@ -31,11 +31,10 @@ const UserInfo = () => {
}, [api, isAuthenticated, fetchUserInfo]);
if (!user) {
return null;
return <UserInfoSkeleton />;
}
const { sub: id, name, avatar, role_names: roleNames } = user;
const isAdmin = roleNames?.includes(UserRole.Admin);
const { sub: id, name, avatar } = user;
return (
<>
@ -48,8 +47,7 @@ const UserInfo = () => {
>
<img src={avatar ?? getAvatarById(id)} />
<div className={styles.wrapper}>
<div className={styles.name}>{name}</div>
{isAdmin && <div className={styles.role}>{t('user_details.roles.admin')}</div>}
<div className={styles.name}>{name || t('users.unnamed')}</div>
</div>
</div>
<Dropdown

View file

@ -0,0 +1,24 @@
@use '@/scss/underscore' as _;
.container {
display: flex;
align-items: center;
padding: _.unit(2);
margin-left: _.unit(4);
border-radius: 8px;
.image {
@include _.shimmering-animation;
width: 36px;
height: 36px;
margin-right: _.unit(2);
border-radius: 6px;
}
.name {
@include _.shimmering-animation;
width: 85px;
height: 20px;
color: var(--color-text);
}
}

View file

@ -0,0 +1,12 @@
import React from 'react';
import * as styles from './index.module.scss';
const UserInfoSkeleton = () => (
<div className={styles.container}>
<div className={styles.image} />
<div className={styles.name} />
</div>
);
export default UserInfoSkeleton;

View file

@ -16,7 +16,6 @@ const ApplicationName = ({ applicationId, isLink = false }: Props) => {
const { data } = useSWR<Application>(!isAdminConsole && `/api/applications/${applicationId}`);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
const name = (isAdminConsole ? 'Admin Console' : data?.name) || '-';
if (isLink && !isAdminConsole) {

View file

@ -4,7 +4,7 @@
padding: _.unit(6);
border-radius: 16px;
// Force dark theme on the code editor
background: #2d3132;
background: #34353f;
position: relative;
.copy {

View file

@ -1,3 +1,4 @@
import classNames from 'classnames';
import React, { ChangeEvent } from 'react';
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import { a11yDark as theme } from 'react-syntax-highlighter/dist/esm/styles/prism';
@ -6,19 +7,20 @@ import CopyToClipboard from '../CopyToClipboard';
import * as styles from './index.module.scss';
type Props = {
className?: string;
language?: string;
isReadonly?: boolean;
value?: string;
onChange?: (value: string) => void;
};
const CodeEditor = ({ language, isReadonly = false, value = '', onChange }: Props) => {
const CodeEditor = ({ className, language, isReadonly = false, value = '', onChange }: Props) => {
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
onChange?.(event.currentTarget.value);
};
return (
<div className={styles.container}>
<div className={classNames(styles.container, className)}>
<CopyToClipboard value={value} variant="icon" className={styles.copy} />
<div className={styles.editor}>
{/* SyntaxHighlighter is a readonly component, so a transparent <textarea> layer is needed

View file

@ -26,25 +26,14 @@
cursor: text;
.copyIcon {
display: flex;
align-items: center;
margin-left: _.unit(3);
color: var(--color-icon);
cursor: pointer;
> svg {
color: var(--color-icon);
}
}
}
&.icon {
.row {
.copyIcon {
> svg {
color: rgba(#fff, 0.8);
}
:hover {
color: #fff;
}
&:hover {
color: var(--color-text);
}
}
}

View file

@ -18,7 +18,7 @@ const UserName = ({ userId, isLink = false }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const isLoading = !data && !error;
const name = data?.name ?? t('users.unnamed');
const name = data?.name || t('users.unnamed');
if (isLoading) {
return null;

View file

@ -48,6 +48,7 @@ const ApiResources = () => {
<Button
title="admin_console.api_resources.create"
type="primary"
size="large"
icon={<Plus />}
onClick={() => {
setIsCreateFormOpen(true);

View file

@ -49,6 +49,7 @@ const Applications = () => {
icon={<Plus />}
title="admin_console.applications.create"
type="primary"
size="large"
onClick={() => {
navigate('/applications/create');
}}

View file

@ -10,7 +10,7 @@
}
.send {
margin-left: _.unit(1);
margin-left: _.unit(1.5);
margin-bottom: 1px;
}
}

View file

@ -78,5 +78,9 @@
.main {
flex: 1;
.codeEditor {
margin-bottom: _.unit(6);
}
}
}

View file

@ -206,6 +206,7 @@ const ConnectorDetails = () => {
</TabNav>
<div className={styles.main}>
<CodeEditor
className={styles.codeEditor}
language="json"
value={config}
onChange={(value) => {
@ -219,6 +220,7 @@ const ConnectorDetails = () => {
<div className={detailsStyles.footerMain}>
<Button
type="primary"
size="large"
title="admin_console.connector_details.save_changes"
isLoading={isSubmitting}
onClick={handleSave}

View file

@ -28,10 +28,16 @@ a.link {
display: flex;
align-items: center;
&:not(:last-child) {
padding-right: _.unit(1);
margin-right: _.unit(1);
&:not(:last-child)::after {
content: '';
width: 0;
height: 12px;
border-right: 1px solid var(--color-border);
margin: 0 _.unit(1);
}
svg {
margin-right: _.unit(1);
}
}
}

View file

@ -60,6 +60,7 @@ const Connectors = () => {
<Button
title="admin_console.connectors.create"
type="primary"
size="large"
icon={<Plus />}
onClick={() => {
setCreateType(ConnectorType.Social);

View file

@ -135,6 +135,7 @@ const SignInExperience = () => {
<Button
isLoading={isSubmitting}
type="primary"
size="large"
htmlType="submit"
title="general.save_changes"
/>

View file

@ -19,7 +19,7 @@
tbody {
tr {
td {
padding: _.unit(2);
padding: _.unit(3) _.unit(2);
font: var(--font-body-medium);
&:first-child {
@ -53,4 +53,19 @@
margin-left: _.unit(3);
}
}
.connectorId {
display: flex;
align-items: center;
font: var(--font-body-medium);
font-family: 'Roboto Mono', monospace;
line-height: 32px;
span {
max-width: 220px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
}
}

View file

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
import Button from '@/components/Button';
import CopyToClipboard from '@/components/CopyToClipboard';
import TableError from '@/components/Table/TableError';
import UnnamedTrans from '@/components/UnnamedTrans';
import useApi, { RequestError } from '@/hooks/use-api';
@ -87,6 +88,11 @@ const UserConnectors = ({ userId, connectors, onDelete }: Props) => {
{isLoading && <div>Loading</div>}
{displayConnectors && (
<table className={styles.table}>
<colgroup>
<col width="156px" />
<col />
<col width="110px" />
</colgroup>
<thead>
<tr>
<th>{t('user_details.connectors.connectors')}</th>
@ -102,25 +108,26 @@ const UserConnectors = ({ userId, connectors, onDelete }: Props) => {
onRetry={async () => mutate(undefined, true)}
/>
)}
{displayConnectors.map((connector) => (
<tr key={connector.id}>
{displayConnectors.map(({ id, userId = '', name, logo }) => (
<tr key={id}>
<td>
<div className={styles.connectorName}>
<div>
<img src={connector.logo} />
</div>
<img src={logo} />
<div className={styles.name}>
<UnnamedTrans resource={connector.name} />
<UnnamedTrans resource={name} />
</div>
</div>
</td>
<td>{connector.userId}</td>
<td className={styles.connectorId}>
<span>{userId || '-'}</span>
<CopyToClipboard variant="icon" value={userId} />
</td>
<td>
<Button
title="admin_console.user_details.connectors.remove"
type="plain"
onClick={() => {
void handleDelete(connector.id);
void handleDelete(id);
}}
/>
</td>

View file

@ -50,6 +50,7 @@ const Users = () => {
<CardTitle title="users.title" subtitle="users.subtitle" />
<Button
title="admin_console.users.create"
size="large"
type="primary"
icon={<Plus />}
onClick={() => {
@ -124,7 +125,7 @@ const Users = () => {
>
<td>
<ItemPreview
title={name ?? t('users.unnamed')}
title={name || t('users.unnamed')}
subtitle={conditionalString(username)}
icon={<img className={styles.avatar} src={avatar ?? getAvatarById(id)} />}
to={`/users/${id}`}