diff --git a/web/public/fonts/jetbrains_mono/JetBrainsMono-Italic.woff2 b/web/public/fonts/jetbrains_mono/JetBrainsMono-Italic.woff2 new file mode 100644 index 0000000000..d60c270e8c Binary files /dev/null and b/web/public/fonts/jetbrains_mono/JetBrainsMono-Italic.woff2 differ diff --git a/web/public/fonts/jetbrains_mono/JetBrainsMono-Medium.woff2 b/web/public/fonts/jetbrains_mono/JetBrainsMono-Medium.woff2 new file mode 100644 index 0000000000..669d04cdf2 Binary files /dev/null and b/web/public/fonts/jetbrains_mono/JetBrainsMono-Medium.woff2 differ diff --git a/web/public/fonts/jetbrains_mono/JetBrainsMono-MediumItalic.woff2 b/web/public/fonts/jetbrains_mono/JetBrainsMono-MediumItalic.woff2 new file mode 100644 index 0000000000..80cfd15e00 Binary files /dev/null and b/web/public/fonts/jetbrains_mono/JetBrainsMono-MediumItalic.woff2 differ diff --git a/web/public/fonts/jetbrains_mono/JetBrainsMono-Regular.woff2 b/web/public/fonts/jetbrains_mono/JetBrainsMono-Regular.woff2 new file mode 100644 index 0000000000..40da427651 Binary files /dev/null and b/web/public/fonts/jetbrains_mono/JetBrainsMono-Regular.woff2 differ diff --git a/web/public/fonts/jetbrains_mono/JetBrainsMono-SemiBold.woff2 b/web/public/fonts/jetbrains_mono/JetBrainsMono-SemiBold.woff2 new file mode 100644 index 0000000000..5ead7b0d6f Binary files /dev/null and b/web/public/fonts/jetbrains_mono/JetBrainsMono-SemiBold.woff2 differ diff --git a/web/public/fonts/jetbrains_mono/JetBrainsMono-SemiBoldItalic.woff2 b/web/public/fonts/jetbrains_mono/JetBrainsMono-SemiBoldItalic.woff2 new file mode 100644 index 0000000000..c5dd294b47 Binary files /dev/null and b/web/public/fonts/jetbrains_mono/JetBrainsMono-SemiBoldItalic.woff2 differ diff --git a/web/src/pages/PlaygroundPage/PlaygroundPage.tsx b/web/src/pages/PlaygroundPage/PlaygroundPage.tsx new file mode 100644 index 0000000000..4ebd77d138 --- /dev/null +++ b/web/src/pages/PlaygroundPage/PlaygroundPage.tsx @@ -0,0 +1,93 @@ +import { m } from '../../paraglide/messages'; +import { Card } from '../../shared/components/Card/Card'; +import { CodeCard } from '../../shared/defguard-ui/components/CodeCard/CodeCard'; +import { Divider } from '../../shared/defguard-ui/components/Divider/Divider'; +import { ThemeSpacing } from '../../shared/defguard-ui/types'; +import './style.scss'; +import { Controls } from '../../shared/components/Controls/Controls'; +import { LoadingStep } from '../../shared/components/LoadingStep/LoadingStep'; +import { ActionableSection } from '../../shared/defguard-ui/components/ActionableSection/ActionableSection'; +import { ActionableSectionVariant } from '../../shared/defguard-ui/components/ActionableSection/types'; +import { Button } from '../../shared/defguard-ui/components/Button/Button'; +import { SizedBox } from '../../shared/defguard-ui/components/SizedBox/SizedBox'; +import testIconSrc from './assets/actionable-test1.png'; + +export const PlaygroundPage = () => { + return ( +
+ + + + + + + + + + + +
+ ); +}; + +const LoadingStepsTest = () => { + return ( + <> + +
+ + + + + + +
+
+ +
+ + + + +
+
+ +
+ + + + + + +
+
+
+
+ +
+
+ + ); +}; diff --git a/web/src/pages/PlaygroundPage/assets/actionable-test1.png b/web/src/pages/PlaygroundPage/assets/actionable-test1.png new file mode 100644 index 0000000000..7b5e9c03b1 Binary files /dev/null and b/web/src/pages/PlaygroundPage/assets/actionable-test1.png differ diff --git a/web/src/pages/PlaygroundPage/style.scss b/web/src/pages/PlaygroundPage/style.scss new file mode 100644 index 0000000000..0dbdf52dc1 --- /dev/null +++ b/web/src/pages/PlaygroundPage/style.scss @@ -0,0 +1,15 @@ +#playground-page { + width: 100%; + min-height: 100dvh; + display: flex; + flex-flow: column; + align-items: center; + justify-content: flex-start; + row-gap: var(--spacing-2xl); + box-sizing: border-box; + padding: var(--spacing-4xl); + + .card { + max-width: 670px; + } +} diff --git a/web/src/pages/UsersOverviewPage/UsersTable.tsx b/web/src/pages/UsersOverviewPage/UsersTable.tsx index cd85f1bf74..ea9b3f5099 100644 --- a/web/src/pages/UsersOverviewPage/UsersTable.tsx +++ b/web/src/pages/UsersOverviewPage/UsersTable.tsx @@ -139,9 +139,6 @@ export const UsersTable = ({ users }: Props) => { enableSorting: true, sortingFn: 'text', minSize: 250, - meta: { - flex: true, - }, cell: (info) => { const rowData = info.row.original; return ( diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts index 1055a695e8..adaabe675f 100644 --- a/web/src/routeTree.gen.ts +++ b/web/src/routeTree.gen.ts @@ -10,9 +10,9 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as SnackbarRouteImport } from './routes/snackbar' +import { Route as PlaygroundRouteImport } from './routes/playground' import { Route as ConsentRouteImport } from './routes/consent' import { Route as AuthRouteImport } from './routes/auth' -import { Route as AppLoaderRouteImport } from './routes/app-loader' import { Route as AuthorizedRouteImport } from './routes/_authorized' import { Route as R404RouteImport } from './routes/404' import { Route as AuthIndexRouteImport } from './routes/auth/index' @@ -56,6 +56,11 @@ const SnackbarRoute = SnackbarRouteImport.update({ path: '/snackbar', getParentRoute: () => rootRouteImport, } as any) +const PlaygroundRoute = PlaygroundRouteImport.update({ + id: '/playground', + path: '/playground', + getParentRoute: () => rootRouteImport, +} as any) const ConsentRoute = ConsentRouteImport.update({ id: '/consent', path: '/consent', @@ -66,11 +71,6 @@ const AuthRoute = AuthRouteImport.update({ path: '/auth', getParentRoute: () => rootRouteImport, } as any) -const AppLoaderRoute = AppLoaderRouteImport.update({ - id: '/app-loader', - path: '/app-loader', - getParentRoute: () => rootRouteImport, -} as any) const AuthorizedRoute = AuthorizedRouteImport.update({ id: '/_authorized', getParentRoute: () => rootRouteImport, @@ -279,9 +279,9 @@ const AuthorizedDefaultLocationsLocationIdEditRoute = export interface FileRoutesByFullPath { '/404': typeof R404Route - '/app-loader': typeof AppLoaderRoute '/auth': typeof AuthRouteWithChildren '/consent': typeof ConsentRoute + '/playground': typeof PlaygroundRoute '/snackbar': typeof SnackbarRoute '/auth/callback': typeof AuthCallbackRoute '/auth/loading': typeof AuthLoadingRoute @@ -320,8 +320,8 @@ export interface FileRoutesByFullPath { } export interface FileRoutesByTo { '/404': typeof R404Route - '/app-loader': typeof AppLoaderRoute '/consent': typeof ConsentRoute + '/playground': typeof PlaygroundRoute '/snackbar': typeof SnackbarRoute '/auth/callback': typeof AuthCallbackRoute '/auth/loading': typeof AuthLoadingRoute @@ -362,9 +362,9 @@ export interface FileRoutesById { __root__: typeof rootRouteImport '/404': typeof R404Route '/_authorized': typeof AuthorizedRouteWithChildren - '/app-loader': typeof AppLoaderRoute '/auth': typeof AuthRouteWithChildren '/consent': typeof ConsentRoute + '/playground': typeof PlaygroundRoute '/snackbar': typeof SnackbarRoute '/_authorized/_default': typeof AuthorizedDefaultRouteWithChildren '/auth/callback': typeof AuthCallbackRoute @@ -406,9 +406,9 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/404' - | '/app-loader' | '/auth' | '/consent' + | '/playground' | '/snackbar' | '/auth/callback' | '/auth/loading' @@ -447,8 +447,8 @@ export interface FileRouteTypes { fileRoutesByTo: FileRoutesByTo to: | '/404' - | '/app-loader' | '/consent' + | '/playground' | '/snackbar' | '/auth/callback' | '/auth/loading' @@ -488,9 +488,9 @@ export interface FileRouteTypes { | '__root__' | '/404' | '/_authorized' - | '/app-loader' | '/auth' | '/consent' + | '/playground' | '/snackbar' | '/_authorized/_default' | '/auth/callback' @@ -532,9 +532,9 @@ export interface FileRouteTypes { export interface RootRouteChildren { R404Route: typeof R404Route AuthorizedRoute: typeof AuthorizedRouteWithChildren - AppLoaderRoute: typeof AppLoaderRoute AuthRoute: typeof AuthRouteWithChildren ConsentRoute: typeof ConsentRoute + PlaygroundRoute: typeof PlaygroundRoute SnackbarRoute: typeof SnackbarRoute } @@ -547,6 +547,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof SnackbarRouteImport parentRoute: typeof rootRouteImport } + '/playground': { + id: '/playground' + path: '/playground' + fullPath: '/playground' + preLoaderRoute: typeof PlaygroundRouteImport + parentRoute: typeof rootRouteImport + } '/consent': { id: '/consent' path: '/consent' @@ -561,13 +568,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthRouteImport parentRoute: typeof rootRouteImport } - '/app-loader': { - id: '/app-loader' - path: '/app-loader' - fullPath: '/app-loader' - preLoaderRoute: typeof AppLoaderRouteImport - parentRoute: typeof rootRouteImport - } '/_authorized': { id: '/_authorized' path: '' @@ -946,9 +946,9 @@ const AuthRouteWithChildren = AuthRoute._addFileChildren(AuthRouteChildren) const rootRouteChildren: RootRouteChildren = { R404Route: R404Route, AuthorizedRoute: AuthorizedRouteWithChildren, - AppLoaderRoute: AppLoaderRoute, AuthRoute: AuthRouteWithChildren, ConsentRoute: ConsentRoute, + PlaygroundRoute: PlaygroundRoute, SnackbarRoute: SnackbarRoute, } export const routeTree = rootRouteImport diff --git a/web/src/routes/app-loader.tsx b/web/src/routes/app-loader.tsx deleted file mode 100644 index 48c77f3fe3..0000000000 --- a/web/src/routes/app-loader.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router'; -import { AppLoaderPage } from '../pages/AppLoaderPage/AppLoaderPage'; - -export const Route = createFileRoute('/app-loader')({ - component: AppLoaderPage, -}); diff --git a/web/src/routes/playground.tsx b/web/src/routes/playground.tsx new file mode 100644 index 0000000000..735377c9d7 --- /dev/null +++ b/web/src/routes/playground.tsx @@ -0,0 +1,6 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { PlaygroundPage } from '../pages/PlaygroundPage/PlaygroundPage'; + +export const Route = createFileRoute('/playground')({ + component: PlaygroundPage, +}); diff --git a/web/src/shared/components/LoadingStep/LoadingStep.tsx b/web/src/shared/components/LoadingStep/LoadingStep.tsx new file mode 100644 index 0000000000..8c663445a4 --- /dev/null +++ b/web/src/shared/components/LoadingStep/LoadingStep.tsx @@ -0,0 +1,80 @@ +import clsx from 'clsx'; +import './style.scss'; +import { useMemo } from 'react'; +import { AppText } from '../../defguard-ui/components/AppText/AppText'; +import { Fold } from '../../defguard-ui/components/Fold/Fold'; +import { Icon, IconKind } from '../../defguard-ui/components/Icon'; +import { LoaderSpinner } from '../../defguard-ui/components/LoaderSpinner/LoaderSpinner'; +import { SizedBox } from '../../defguard-ui/components/SizedBox/SizedBox'; +import { TextStyle, ThemeSpacing, ThemeVariable } from '../../defguard-ui/types'; +import { isPresent } from '../../defguard-ui/utils/isPresent'; +import type { LoadingStepProps } from './types'; + +type ComponentVariant = 'loading' | 'success' | 'error' | 'default'; + +export const LoadingStep = ({ + title, + children, + error, + errorMessage, + success, + loading, +}: LoadingStepProps) => { + const variant = useMemo((): ComponentVariant => { + if (success) return 'success'; + if (error) return 'error'; + if (loading) return 'loading'; + return 'default'; + }, [error, success, loading]); + + return ( +
+
+
+ {variant === 'default' && ( + + )} + {loading && } + {success && ( + + )} + {error && ( + + )} +
+

{title}

+
+
+
+ {isPresent(children) && variant === 'error' && ( +
+ + {isPresent(errorMessage) && ( + <> + + {errorMessage} + + + + )} + {children} + + +
+ )} +
+
+ ); +}; diff --git a/web/src/shared/components/LoadingStep/style.scss b/web/src/shared/components/LoadingStep/style.scss new file mode 100644 index 0000000000..21548bf311 --- /dev/null +++ b/web/src/shared/components/LoadingStep/style.scss @@ -0,0 +1,87 @@ +.loading-step { + &.variant-default { + .title { + font: var(--t-body-sm-400); + color: var(--fg-muted); + } + } + + &.variant-loading { + .title { + font: var(--t-body-sm-500); + color: var(--fg-default); + } + } + + &.variant-success { + .title { + font: var(--t-body-sm-400); + color: var(--fg-success); + } + } + + &.variant-error { + .title { + font: var(--t-body-sm-500); + color: var(--fg-default); + } + } + + & > .main-track { + display: grid; + grid-template-columns: 20px 1fr; + grid-template-rows: 1fr; + align-items: center; + column-gap: var(--spacing-md); + + & > .icon-container { + width: 20px; + height: 20px; + } + + .title { + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + @include animate(color); + } + } + + & > .content-track { + position: relative; + display: grid; + grid-template-columns: 10px 1fr; + grid-template-rows: 1fr; + column-gap: 22px; + + & > .content-bar { + grid-column: 1 / 2; + content: ' '; + width: 100%; + box-sizing: border-box; + border-right: var(--border-1) solid var(--border-disabled); + } + + & > .content { + grid-column: 2 / 3; + } + } +} + +.loading-step:not(:last-child) { + padding-bottom: var(--spacing-xs); + + .main-track { + padding-bottom: var(--spacing-xs); + } + + .content-bar { + border-color: var(--border-disabled); + } + + & > .content-track { + min-height: var(--spacing-2xl); + } +} diff --git a/web/src/shared/components/LoadingStep/types.ts b/web/src/shared/components/LoadingStep/types.ts new file mode 100644 index 0000000000..8ff212622b --- /dev/null +++ b/web/src/shared/components/LoadingStep/types.ts @@ -0,0 +1,12 @@ +import type { PropsWithChildren } from 'react'; + +export type LoadingStepConfig = { + title: string; + error?: boolean; + errorMessage?: string; + loading?: boolean; + success?: boolean; + testId?: string; +}; + +export type LoadingStepProps = LoadingStepConfig & PropsWithChildren; diff --git a/web/src/shared/components/SelectionSection/SelectionSection.tsx b/web/src/shared/components/SelectionSection/SelectionSection.tsx index cab190856e..eeb108e2db 100644 --- a/web/src/shared/components/SelectionSection/SelectionSection.tsx +++ b/web/src/shared/components/SelectionSection/SelectionSection.tsx @@ -43,7 +43,7 @@ export const SelectionSection = ({ }); } return res; - }, [options, onlySelected, selection, search.trim]); + }, [options, onlySelected, selection, search]); const handleSelectAll = useCallback(() => { const allSelected = selection.size === options.length; diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui index 07d6826a40..e46c660d05 160000 --- a/web/src/shared/defguard-ui +++ b/web/src/shared/defguard-ui @@ -1 +1 @@ -Subproject commit 07d6826a407e6aeb3a7362e88cb90605ca4763b2 +Subproject commit e46c660d05c390372d6bbcf3201a7eb4ce2b51cf