mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(ui): add support for fullscreen profile layout (#2622)
This commit is contained in:
parent
61f00449da
commit
9ef395f668
7 changed files with 150 additions and 125 deletions
|
@ -2,6 +2,7 @@ import { SignInMode } from '@logto/schemas';
|
|||
import { useEffect } from 'react';
|
||||
import { Route, Routes, BrowserRouter, Navigate } from 'react-router-dom';
|
||||
|
||||
import AppBoundary from './containers/AppBoundary';
|
||||
import AppContent from './containers/AppContent';
|
||||
import LoadingLayerProvider from './containers/LoadingLayerProvider';
|
||||
import usePageContext from './hooks/use-page-context';
|
||||
|
@ -15,6 +16,7 @@ import ErrorPage from './pages/ErrorPage';
|
|||
import ForgotPassword from './pages/ForgotPassword';
|
||||
import Passcode from './pages/Passcode';
|
||||
import PasswordRegisterWithUsername from './pages/PasswordRegisterWithUsername';
|
||||
import Profile from './pages/Profile';
|
||||
import Register from './pages/Register';
|
||||
import ResetPassword from './pages/ResetPassword';
|
||||
import SecondaryRegister from './pages/SecondaryRegister';
|
||||
|
@ -57,60 +59,66 @@ const App = () => {
|
|||
const isSignInOnly = experienceSettings.signInMode === SignInMode.SignIn;
|
||||
|
||||
return (
|
||||
<Provider value={context}>
|
||||
<AppContent>
|
||||
<BrowserRouter>
|
||||
<BrowserRouter>
|
||||
<Provider value={context}>
|
||||
<AppBoundary>
|
||||
<Routes>
|
||||
<Route path="/" element={<Navigate replace to="/sign-in" />} />
|
||||
<Route path="/sign-in/consent" element={<Consent />} />
|
||||
<Route
|
||||
path="/unknown-session"
|
||||
element={<ErrorPage message="error.invalid_session" />}
|
||||
/>
|
||||
|
||||
<Route element={<LoadingLayerProvider />}>
|
||||
{/* Sign-in */}
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
<Route element={<AppContent />}>
|
||||
<Route path="/" element={<Navigate replace to="/sign-in" />} />
|
||||
<Route path="/sign-in/consent" element={<Consent />} />
|
||||
<Route
|
||||
path="/sign-in"
|
||||
element={isRegisterOnly ? <Navigate replace to="/register" /> : <SignIn />}
|
||||
path="/unknown-session"
|
||||
element={<ErrorPage message="error.invalid_session" />}
|
||||
/>
|
||||
<Route path="/sign-in/social/:connector" element={<SocialSignIn />} />
|
||||
<Route path="/sign-in/:method" element={<SecondarySignIn />} />
|
||||
<Route path="/sign-in/:method/password" element={<SignInPassword />} />
|
||||
|
||||
{/* Register */}
|
||||
<Route
|
||||
path="/register"
|
||||
element={isSignInOnly ? <Navigate replace to="/sign-in" /> : <Register />}
|
||||
/>
|
||||
<Route
|
||||
path="/register/username/password"
|
||||
element={<PasswordRegisterWithUsername />}
|
||||
/>
|
||||
<Route path="/register/:method" element={<SecondaryRegister />} />
|
||||
<Route element={<LoadingLayerProvider />}>
|
||||
{/* Sign-in */}
|
||||
<Route
|
||||
path="/sign-in"
|
||||
element={isRegisterOnly ? <Navigate replace to="/register" /> : <SignIn />}
|
||||
/>
|
||||
<Route path="/sign-in/social/:connector" element={<SocialSignIn />} />
|
||||
<Route path="/sign-in/:method" element={<SecondarySignIn />} />
|
||||
<Route path="/sign-in/:method/password" element={<SignInPassword />} />
|
||||
|
||||
{/* Forgot password */}
|
||||
<Route path="/forgot-password/reset" element={<ResetPassword />} />
|
||||
<Route path="/forgot-password/:method" element={<ForgotPassword />} />
|
||||
{/* Register */}
|
||||
<Route
|
||||
path="/register"
|
||||
element={isSignInOnly ? <Navigate replace to="/sign-in" /> : <Register />}
|
||||
/>
|
||||
<Route
|
||||
path="/register/username/password"
|
||||
element={<PasswordRegisterWithUsername />}
|
||||
/>
|
||||
<Route path="/register/:method" element={<SecondaryRegister />} />
|
||||
|
||||
{/* Continue set up missing profile */}
|
||||
<Route path="/continue/email-or-sms/:method" element={<ContinueWithEmailOrPhone />} />
|
||||
<Route path="/continue/:method" element={<Continue />} />
|
||||
{/* Forgot password */}
|
||||
<Route path="/forgot-password/reset" element={<ResetPassword />} />
|
||||
<Route path="/forgot-password/:method" element={<ForgotPassword />} />
|
||||
|
||||
{/* Social sign-in pages */}
|
||||
<Route path="/callback/:connector" element={<Callback />} />
|
||||
<Route path="/social/register/:connector" element={<SocialRegister />} />
|
||||
<Route path="/social/landing/:connector" element={<SocialLanding />} />
|
||||
{/* Continue set up missing profile */}
|
||||
<Route
|
||||
path="/continue/email-or-sms/:method"
|
||||
element={<ContinueWithEmailOrPhone />}
|
||||
/>
|
||||
<Route path="/continue/:method" element={<Continue />} />
|
||||
|
||||
{/* Always keep route path with param as the last one */}
|
||||
<Route path="/:type/:method/passcode-validation" element={<Passcode />} />
|
||||
{/* Social sign-in pages */}
|
||||
<Route path="/callback/:connector" element={<Callback />} />
|
||||
<Route path="/social/register/:connector" element={<SocialRegister />} />
|
||||
<Route path="/social/landing/:connector" element={<SocialLanding />} />
|
||||
|
||||
{/* Always keep route path with param as the last one */}
|
||||
<Route path="/:type/:method/passcode-validation" element={<Passcode />} />
|
||||
</Route>
|
||||
|
||||
<Route path="*" element={<ErrorPage />} />
|
||||
</Route>
|
||||
|
||||
<Route path="*" element={<ErrorPage />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</AppContent>
|
||||
</Provider>
|
||||
</AppBoundary>
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
22
packages/ui/src/containers/AppBoundary/index.module.scss
Normal file
22
packages/ui/src/containers/AppBoundary/index.module.scss
Normal file
|
@ -0,0 +1,22 @@
|
|||
@use '@/scss/colors' as colors;
|
||||
@use '@/scss/underscore' as _;
|
||||
|
||||
body {
|
||||
&.light {
|
||||
@include colors.light;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
@include colors.dark;
|
||||
}
|
||||
}
|
||||
|
||||
:global(body.mobile) {
|
||||
--max-width: 360px;
|
||||
background: var(--color-bg-body);
|
||||
}
|
||||
|
||||
:global(body.desktop) {
|
||||
--max-width: 400px;
|
||||
background: var(--color-bg-float-base);
|
||||
}
|
48
packages/ui/src/containers/AppBoundary/index.tsx
Normal file
48
packages/ui/src/containers/AppBoundary/index.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { conditionalString } from '@silverhand/essentials';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useCallback, useContext, useEffect } from 'react';
|
||||
|
||||
import Toast from '@/components/Toast';
|
||||
import useColorTheme from '@/hooks/use-color-theme';
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
import useTheme from '@/hooks/use-theme';
|
||||
|
||||
import ConfirmModalProvider from '../ConfirmModalProvider';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const AppBoundary = ({ children }: Props) => {
|
||||
// Set Primary Color
|
||||
useColorTheme();
|
||||
const theme = useTheme();
|
||||
const { platform, toast, setToast } = useContext(PageContext);
|
||||
|
||||
// Set Theme Mode
|
||||
useEffect(() => {
|
||||
document.body.classList.remove(conditionalString(styles.light), conditionalString(styles.dark));
|
||||
document.body.classList.add(conditionalString(styles[theme]));
|
||||
}, [theme]);
|
||||
|
||||
// Apply Platform Style
|
||||
useEffect(() => {
|
||||
document.body.classList.remove('desktop', 'mobile');
|
||||
document.body.classList.add(platform === 'mobile' ? 'mobile' : 'desktop');
|
||||
}, [platform]);
|
||||
|
||||
// Prevent internal eventListener rebind
|
||||
const hideToast = useCallback(() => {
|
||||
setToast('');
|
||||
}, [setToast]);
|
||||
|
||||
return (
|
||||
<ConfirmModalProvider>
|
||||
<Toast message={toast} callback={hideToast} />
|
||||
{children}
|
||||
</ConfirmModalProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppBoundary;
|
|
@ -1,6 +1,4 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
@use '@/scss/colors' as colors;
|
||||
@use '@/scss/fonts' as fonts;
|
||||
|
||||
/* Preview Settings */
|
||||
.preview {
|
||||
|
@ -13,30 +11,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* Foundation */
|
||||
body {
|
||||
--radius: 8px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: auto;
|
||||
word-break: break-word;
|
||||
@include colors.static;
|
||||
|
||||
&.light {
|
||||
@include colors.light;
|
||||
}
|
||||
|
||||
&.dark {
|
||||
@include colors.dark;
|
||||
}
|
||||
|
||||
@include fonts.fonts;
|
||||
}
|
||||
|
||||
/* Main Layout */
|
||||
.container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
color: var(--color-type-primary);
|
||||
overflow: auto;
|
||||
@include _.flex_column(center, normal);
|
||||
}
|
||||
|
@ -51,14 +29,6 @@ body {
|
|||
}
|
||||
|
||||
:global(body.mobile) {
|
||||
--max-width: 360px;
|
||||
background: var(--color-bg-body);
|
||||
|
||||
.container {
|
||||
background: var(--color-bg-body);
|
||||
font: var(--font-body-2);
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
|
@ -69,14 +39,6 @@ body {
|
|||
}
|
||||
|
||||
:global(body.desktop) {
|
||||
--max-width: 400px;
|
||||
background: var(--color-bg-float-base);
|
||||
|
||||
.container {
|
||||
background: var(--color-bg-float-base);
|
||||
font: var(--font-body-2);
|
||||
}
|
||||
|
||||
.main {
|
||||
width: 640px;
|
||||
min-height: 640px;
|
||||
|
|
|
@ -1,52 +1,21 @@
|
|||
import { conditionalString } from '@silverhand/essentials';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, useCallback, useContext } from 'react';
|
||||
import { useContext } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
||||
import Toast from '@/components/Toast';
|
||||
import ConfirmModalProvider from '@/containers/ConfirmModalProvider';
|
||||
import useColorTheme from '@/hooks/use-color-theme';
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
import useTheme from '@/hooks/use-theme';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
export type Props = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
const AppContent = ({ children }: Props) => {
|
||||
const theme = useTheme();
|
||||
const { toast, platform, setToast } = useContext(PageContext);
|
||||
|
||||
// Prevent internal eventListener rebind
|
||||
const hideToast = useCallback(() => {
|
||||
setToast('');
|
||||
}, [setToast]);
|
||||
|
||||
// Set Primary Color
|
||||
useColorTheme();
|
||||
|
||||
// Set Theme Mode
|
||||
useEffect(() => {
|
||||
document.body.classList.remove(conditionalString(styles.light), conditionalString(styles.dark));
|
||||
document.body.classList.add(conditionalString(styles[theme]));
|
||||
}, [theme]);
|
||||
|
||||
// Apply Platform Style
|
||||
useEffect(() => {
|
||||
document.body.classList.remove('desktop', 'mobile');
|
||||
document.body.classList.add(platform === 'mobile' ? 'mobile' : 'desktop');
|
||||
}, [platform]);
|
||||
const AppContent = () => {
|
||||
const { platform } = useContext(PageContext);
|
||||
|
||||
return (
|
||||
<ConfirmModalProvider>
|
||||
<div className={styles.container}>
|
||||
{platform === 'web' && <div className={styles.placeHolder} />}
|
||||
<main className={styles.main}>{children}</main>
|
||||
{platform === 'web' && <div className={styles.placeHolder} />}
|
||||
<Toast message={toast} callback={hideToast} />
|
||||
</div>
|
||||
</ConfirmModalProvider>
|
||||
<div className={styles.container}>
|
||||
{platform === 'web' && <div className={styles.placeHolder} />}
|
||||
<main className={styles.main}>
|
||||
<Outlet />
|
||||
</main>
|
||||
{platform === 'web' && <div className={styles.placeHolder} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
5
packages/ui/src/pages/Profile/index.tsx
Normal file
5
packages/ui/src/pages/Profile/index.tsx
Normal file
|
@ -0,0 +1,5 @@
|
|||
const Profile = () => {
|
||||
return <>Profile works!</>;
|
||||
};
|
||||
|
||||
export default Profile;
|
|
@ -1,7 +1,18 @@
|
|||
@use '@/scss/colors' as colors;
|
||||
@use '@/scss/fonts' as fonts;
|
||||
|
||||
body {
|
||||
@include colors.static;
|
||||
@include fonts.fonts;
|
||||
|
||||
--radius: 8px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
word-break: break-word;
|
||||
color: var(--color-type-primary);
|
||||
font: var(--font-body-2);
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
Loading…
Reference in a new issue