0
Fork 0
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:
Charles Zhao 2022-12-16 22:09:38 +08:00 committed by GitHub
parent 61f00449da
commit 9ef395f668
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 150 additions and 125 deletions

View file

@ -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>
);
};

View 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);
}

View 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;

View file

@ -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;

View file

@ -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>
);
};

View file

@ -0,0 +1,5 @@
const Profile = () => {
return <>Profile works!</>;
};
export default Profile;

View file

@ -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);
}
* {