diff --git a/packages/console/src/components/CopyToClipboard/index.module.scss b/packages/console/src/components/CopyToClipboard/index.module.scss index dcba6f3ee..8ec2a48cd 100644 --- a/packages/console/src/components/CopyToClipboard/index.module.scss +++ b/packages/console/src/components/CopyToClipboard/index.module.scss @@ -29,8 +29,12 @@ flex: 1; overflow: hidden; text-overflow: ellipsis; - } + &.wrapContent { + text-overflow: unset; + word-break: break-all; + } + } .copyToolTipAnchor { margin-left: _.unit(2); diff --git a/packages/console/src/components/CopyToClipboard/index.tsx b/packages/console/src/components/CopyToClipboard/index.tsx index 11cc07f36..90728fed5 100644 --- a/packages/console/src/components/CopyToClipboard/index.tsx +++ b/packages/console/src/components/CopyToClipboard/index.tsx @@ -20,6 +20,7 @@ type Props = { variant?: 'text' | 'contained' | 'border' | 'icon'; hasVisibilityToggle?: boolean; size?: 'default' | 'small'; + isWordWrapAllowed?: boolean; }; type CopyState = TFuncKey<'translation', 'admin_console.general'>; @@ -30,6 +31,7 @@ function CopyToClipboard({ hasVisibilityToggle, variant = 'contained', size = 'default', + isWordWrapAllowed = false, }: Props) { const copyIconReference = useRef(null); const [copyState, setCopyState] = useState('copy'); @@ -73,7 +75,11 @@ function CopyToClipboard({ }} >
- {variant !== 'icon' &&
{displayValue}
} + {variant !== 'icon' && ( +
+ {displayValue} +
+ )} {hasVisibilityToggle && ( tbody > tr > td { + border: unset; + } + } +} + + +.column { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/DnsRecordsTable/index.tsx b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/DnsRecordsTable/index.tsx new file mode 100644 index 000000000..c7f654aff --- /dev/null +++ b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/DnsRecordsTable/index.tsx @@ -0,0 +1,76 @@ +import { type DomainDnsRecords } from '@logto/schemas'; + +import CopyToClipboard from '@/components/CopyToClipboard'; +import DynamicT from '@/components/DynamicT'; +import { Ring } from '@/components/Spinner'; +import Table from '@/components/Table'; + +import * as styles from './index.module.scss'; + +type Props = { + records: DomainDnsRecords; +}; + +function DnsRecordsTable({ records }: Props) { + return ( +
+
+ +
+
+ {records.length === 0 ? ( +
+ + +
+ ) : ( + false} + columns={[ + { + title: , + dataIndex: 'type', + colSpan: 2, + render: ({ type }) =>
{type}
, + }, + { + title: , + dataIndex: 'name', + colSpan: 7, + render: ({ name }) => ( + + ), + }, + { + title: , + dataIndex: 'value', + colSpan: 7, + render: ({ value }) => ( + + ), + }, + ]} + /> + )} + + + ); +} + +export default DnsRecordsTable; diff --git a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/Step/index.module.scss b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/Step/index.module.scss new file mode 100644 index 000000000..a116587bb --- /dev/null +++ b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/Step/index.module.scss @@ -0,0 +1,65 @@ +@use '@/scss/underscore' as _; + +.step { + .header { + display: flex; + align-items: center; + + .status { + flex-shrink: 0; + } + + .title { + font: var(--font-label-2); + margin-left: _.unit(5); + } + + .tip { + margin-left: _.unit(0.5); + } + } + + .contentContainer { + position: relative; + padding: _.unit(2) 0 _.unit(6) _.unit(10); + } + + &:not(:last-child) { + .contentContainer::before { + content: ''; + position: absolute; + display: block; + border-left: 1px dashed var(--color-divider); + top: _.unit(1); + bottom: _.unit(1); + transform: translateX(_.unit(-7.5)); + } + } +} + +.stepIcon { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + border-radius: 10px; + background-color: var(--color-surface-variant); + color: var(--color-text-link); + font: var(--font-label-3); + + .icon { + flex-shrink: 0; + width: 12px; + height: 12px; + color: var(--color-white); + } + + &.finished { + background-color: var(--color-on-success-container); + } + + &.loading { + background-color: unset; + } +} diff --git a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/Step/index.tsx b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/Step/index.tsx new file mode 100644 index 000000000..8c418a459 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/components/Step/index.tsx @@ -0,0 +1,71 @@ +import { type AdminConsoleKey } from '@logto/phrases'; +import { DomainStatus } from '@logto/schemas'; +import classNames from 'classnames'; +import { type ReactNode } from 'react'; + +import Success from '@/assets/images/success.svg'; +import Tip from '@/assets/images/tip.svg'; +import DynamicT from '@/components/DynamicT'; +import IconButton from '@/components/IconButton'; +import { Ring } from '@/components/Spinner'; +import ToggleTip from '@/components/Tip/ToggleTip'; + +import * as styles from './index.module.scss'; + +type Props = { + step: number; + title: AdminConsoleKey; + tip?: AdminConsoleKey; + domainStatus: DomainStatus; + children?: ReactNode; +}; + +const domainStatusToStep: Record = { + [DomainStatus.Error]: 0, + [DomainStatus.PendingVerification]: 1, + [DomainStatus.PendingSsl]: 2, + [DomainStatus.Active]: 3, +}; + +function Step({ step, title, tip, domainStatus, children }: Props) { + const domainStatusStep = domainStatusToStep[domainStatus]; + + const isPending = step > domainStatusStep; + const isLoading = step === domainStatusStep; + const isFinished = step < domainStatusStep; + + return ( +
+
+
+ {isPending && step} + {isLoading && } + {isFinished && } +
+
+ +
+ {tip && ( + } + horizontalAlign="start" + > + + + + + )} +
+
{children}
+
+ ); +} + +export default Step; diff --git a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/index.module.scss b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/index.module.scss new file mode 100644 index 000000000..f69a1d97a --- /dev/null +++ b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/index.module.scss @@ -0,0 +1,6 @@ +@use '@/scss/underscore' as _; + +.container { + border-top: 1px solid var(--color-divider); + padding: _.unit(5) _.unit(6) 0; +} diff --git a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/index.tsx b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/index.tsx new file mode 100644 index 000000000..768561076 --- /dev/null +++ b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/components/ActivationProcess/index.tsx @@ -0,0 +1,69 @@ +import { + DomainStatus, + type Domain, + type DomainDnsRecords, + type DomainDnsRecord, +} from '@logto/schemas'; + +import { isDomainStatus } from '../../utils'; + +import DnsRecordsTable from './components/DnsRecordsTable'; +import Step from './components/Step'; +import * as styles from './index.module.scss'; + +type Props = { + customDomain: Domain; +}; + +const isSetupSslDnsRecord = ({ type, name }: DomainDnsRecord) => + type.toUpperCase() === 'TXT' && name.includes('_acme-challenge'); + +function ActivationProcess({ customDomain }: Props) { + const { dnsRecords, status } = customDomain; + + // TODO @xiaoyijun Remove this type assertion when the LOG-6276 issue is done by @wangsijie + const typedDomainStatus = isDomainStatus(status) ? status : DomainStatus.Error; + + const { verifyDomainDnsRecord, setupSslDnsRecord } = dnsRecords.reduce<{ + verifyDomainDnsRecord: DomainDnsRecords; + setupSslDnsRecord: DomainDnsRecords; + }>( + (result, record) => + isSetupSslDnsRecord(record) + ? { + ...result, + setupSslDnsRecord: [...result.setupSslDnsRecord, record], + } + : { + ...result, + verifyDomainDnsRecord: [...result.verifyDomainDnsRecord, record], + }, + { + verifyDomainDnsRecord: [], + setupSslDnsRecord: [], + } + ); + + return ( +
+ + + + + + +
+ ); +} + +export default ActivationProcess; diff --git a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/index.tsx b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/index.tsx index a175bd325..874514a29 100644 --- a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/index.tsx +++ b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/components/CustomDomain/index.tsx @@ -1,5 +1,6 @@ -import { type Domain } from '@logto/schemas'; +import { type Domain, DomainStatus } from '@logto/schemas'; +import ActivationProcess from './components/ActivationProcess'; import CustomDomainHeader from './components/CustomDomainHeader'; import * as styles from './index.module.scss'; @@ -12,7 +13,9 @@ function CustomDomain({ customDomain, onDeleteCustomDomain }: Props) { return (
- {/* TODO @xiaoyijun add custom domain active process content */} + {customDomain.status !== DomainStatus.Active && ( + + )}
); } diff --git a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/index.tsx b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/index.tsx index 7ea6726c3..cfdfae31a 100644 --- a/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/index.tsx +++ b/packages/console/src/pages/TenantSettings/tabs/TenantDomainSettings/index.tsx @@ -4,7 +4,8 @@ import useSWR from 'swr'; import FormCard from '@/components/FormCard'; import FormField from '@/components/FormField'; -import { type RequestError } from '@/hooks/use-api'; +import useApi, { type RequestError } from '@/hooks/use-api'; +import useSwrFetcher from '@/hooks/use-swr-fetcher'; import AddDomainForm from './components/AddDomainForm'; import CustomDomain from './components/CustomDomain'; @@ -12,8 +13,13 @@ import DefaultDomain from './components/DefaultDomain'; import * as styles from './index.module.scss'; function TenantDomainSettings() { - // Todo: @xiaoyijun setup the auto refresh interval for the domains when implementing the active domain process. - const { data, error, mutate } = useSWR('api/domains'); + const api = useApi(); + const fetcher = useSwrFetcher(api); + const { data, error, mutate } = useSWR('api/domains', fetcher, { + // Note: check the custom domain status every 10 seconds. + refreshInterval: 10_000, + }); + const isLoading = !data && !error; /** * Note: we can only create a custom domain, and we don't have a default id for it, so the first element of the array is the custom domain.