0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

Merge pull request #872 from logto-io/charles-log-2508-upgrade-react-sdk-0.1.7-in-ac

fix(console): upgrade react-sdk and integrate session expired page
This commit is contained in:
Charles Zhao 2022-05-18 12:03:44 +08:00 committed by GitHub
commit 93b4966f59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 236 additions and 116 deletions

View file

@ -19,7 +19,7 @@
"devDependencies": {
"@fontsource/roboto-mono": "^4.5.7",
"@logto/phrases": "^0.1.0",
"@logto/react": "^0.1.5",
"@logto/react": "^0.1.7",
"@logto/schemas": "^0.1.0",
"@mdx-js/react": "^1.6.22",
"@parcel/core": "^2.5.0",

View file

@ -1,21 +0,0 @@
@use '@/scss/colors' as colors;
.light {
@include colors.light-theme;
}
.dark {
@include colors.dark-theme;
}
@media (prefers-color-scheme: light) {
body {
@include colors.light-theme;
}
}
@media (prefers-color-scheme: dark) {
body {
@include colors.dark-theme;
}
}

View file

@ -1,21 +1,15 @@
import { LogtoProvider, useLogto } from '@logto/react';
import { AppearanceMode, Setting } from '@logto/schemas';
import React, { useEffect } from 'react';
import { BrowserRouter, Navigate, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import useSWR, { SWRConfig } from 'swr';
import { LogtoProvider } from '@logto/react';
import React from 'react';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { SWRConfig } from 'swr';
import './scss/normalized.scss';
// eslint-disable-next-line import/no-unassigned-import
import '@fontsource/roboto-mono';
import * as styles from './App.module.scss';
import AppContent from './components/AppContent';
import { getPath } from './components/AppContent/components/Sidebar';
import { useSidebarMenuItems } from './components/AppContent/components/Sidebar/hook';
import ErrorBoundary from './components/ErrorBoundary';
import LogtoLoading from './components/LogtoLoading';
import Toast from './components/Toast';
import { themeStorageKey, logtoApiResource } from './consts';
import { RequestError } from './hooks/use-api';
import { logtoApiResource } from './consts';
import useSwrFetcher from './hooks/use-swr-fetcher';
import initI18n from './i18n/init';
import ApiResourceDetails from './pages/ApiResourceDetails';
@ -31,58 +25,13 @@ import Settings from './pages/Settings';
import SignInExperience from './pages/SignInExperience';
import UserDetails from './pages/UserDetails';
import Users from './pages/Users';
const isBasenameNeeded = process.env.NODE_ENV !== 'development' || process.env.PORT === '5002';
import { getBasename } from './utilities/app';
void initI18n();
const defaultTheme = localStorage.getItem(themeStorageKey) ?? AppearanceMode.SyncWithSystem;
const Main = () => {
const { isAuthenticated } = useLogto();
const location = useLocation();
const navigate = useNavigate();
const fetcher = useSwrFetcher();
const settingsFetcher = useSwrFetcher<Setting>();
const { data } = useSWR<Setting, RequestError>(
isAuthenticated && '/api/settings',
settingsFetcher
);
const sections = useSidebarMenuItems();
useEffect(() => {
const theme = data?.adminConsole.appearanceMode ?? defaultTheme;
const isFollowSystem = theme === AppearanceMode.SyncWithSystem;
const className = styles[theme] ?? '';
if (!isFollowSystem) {
document.body.classList.add(className);
}
return () => {
if (!isFollowSystem) {
document.body.classList.remove(className);
}
};
}, [data?.adminConsole.appearanceMode]);
useEffect(() => {
(async () => {
void initI18n(data?.adminConsole.language);
})();
}, [data?.adminConsole.language]);
useEffect(() => {
if (location.pathname === '/') {
navigate(getPath(sections?.[0]?.items[0]?.title ?? ''));
}
}, [location.pathname, navigate, sections]);
if (isAuthenticated && !sections?.length) {
return <LogtoLoading message="general.loading" />;
}
return (
<ErrorBoundary>
<SWRConfig value={{ fetcher }}>
@ -126,7 +75,7 @@ const Main = () => {
};
const App = () => (
<BrowserRouter basename={isBasenameNeeded ? '/console' : ''}>
<BrowserRouter basename={getBasename()}>
<LogtoProvider
config={{ endpoint: window.location.origin, appId: 'foo', resources: [logtoApiResource] }}
>

View file

@ -0,0 +1,53 @@
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="58" y="74" width="140" height="108" rx="12.1905" fill="#C9C5D0"/>
<mask id="mask0_3312_122828" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="58" y="74" width="140" height="108">
<rect x="58" y="74" width="140" height="108" rx="12.1905" fill="#CABEFF"/>
</mask>
<g mask="url(#mask0_3312_122828)">
<rect x="64.8008" y="142.2" width="128" height="0.4" transform="rotate(-90 64.8008 142.2)" fill="black"/>
<rect x="73" y="142" width="128" height="0.4" transform="rotate(-90 73 142)" fill="black"/>
<rect x="81" y="142" width="128" height="0.4" transform="rotate(-90 81 142)" fill="black"/>
<rect x="89" y="142" width="128" height="0.4" transform="rotate(-90 89 142)" fill="black"/>
<rect x="97" y="142" width="128" height="0.4" transform="rotate(-90 97 142)" fill="black"/>
<rect x="105" y="142" width="128" height="0.4" transform="rotate(-90 105 142)" fill="black"/>
<rect x="113" y="142" width="128" height="0.4" transform="rotate(-90 113 142)" fill="black"/>
<rect x="121" y="142" width="128" height="0.4" transform="rotate(-90 121 142)" fill="black"/>
<rect x="129" y="142" width="128" height="0.4" transform="rotate(-90 129 142)" fill="black"/>
<rect x="137" y="142" width="128" height="0.4" transform="rotate(-90 137 142)" fill="black"/>
<rect x="145" y="142" width="128" height="0.4" transform="rotate(-90 145 142)" fill="black"/>
<rect x="153" y="142" width="128" height="0.4" transform="rotate(-90 153 142)" fill="black"/>
<rect x="161" y="142" width="128" height="0.4" transform="rotate(-90 161 142)" fill="black"/>
<rect x="169" y="142" width="128" height="0.4" transform="rotate(-90 169 142)" fill="black"/>
<rect x="177" y="142" width="128" height="0.4" transform="rotate(-90 177 142)" fill="black"/>
<rect x="57" y="78" width="128" height="0.4" fill="black"/>
<rect x="57" y="86" width="128" height="0.4" fill="black"/>
<rect x="57" y="94" width="128" height="0.4" fill="black"/>
<rect x="57" y="102" width="128" height="0.4" fill="black"/>
<rect x="57" y="110" width="128" height="0.4" fill="black"/>
<rect x="57" y="118" width="128" height="0.4" fill="black"/>
<rect x="57" y="126" width="128" height="0.4" fill="black"/>
<rect x="57" y="134" width="128" height="0.4" fill="black"/>
</g>
<path d="M125.333 107.619C126.519 105.566 129.481 105.566 130.667 107.619L157.333 153.807C158.519 155.86 157.037 158.426 154.667 158.426H101.333C98.963 158.426 97.4815 155.86 98.6667 153.807L125.333 107.619Z" fill="#F4E560"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M128 119C125.791 119 124 120.791 124 123V135C124 137.209 125.791 139 128 139C130.209 139 132 137.209 132 135V123C132 120.791 130.209 119 128 119ZM128 143C125.791 143 124 144.791 124 147C124 149.209 125.791 151 128 151C130.209 151 132 149.209 132 147C132 144.791 130.209 143 128 143Z" fill="#444748"/>
<path d="M58 86C58 79.3726 63.3726 74 70 74H186C192.627 74 198 79.3726 198 86V94H58V86Z" fill="#78767F"/>
<circle cx="77" cy="84" r="3" fill="#E5E1EC"/>
<circle cx="87" cy="84" r="3" fill="#47464E"/>
<circle cx="97" cy="84" r="3" fill="#47464E"/>
<rect x="203.133" y="214.277" width="8" height="8" transform="rotate(-35.523 203.133 214.277)" fill="#C4C4C4"/>
<rect x="206" y="27.6482" width="8" height="8" rx="0.16" transform="rotate(-35.523 206 27.6482)" fill="#E5E1EC"/>
<g style="mix-blend-mode:multiply">
<rect x="172.453" y="171" width="16" height="16" transform="rotate(50.249 172.453 171)" fill="#C4C7C7"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M221.914 96.2086C221.914 96.0934 222.008 96 222.123 96H225.878C225.993 96 226.087 96.0934 226.087 96.2086V102.817L231.81 99.513C231.91 99.4554 232.037 99.4896 232.095 99.5894L233.972 102.841C234.03 102.941 233.996 103.069 233.896 103.126L228.172 106.431L233.896 109.735C233.995 109.793 234.03 109.92 233.972 110.02L232.094 113.272C232.037 113.372 231.909 113.406 231.81 113.348L226.087 110.044V116.653C226.087 116.768 225.993 116.861 225.878 116.861H222.123C222.008 116.861 221.914 116.768 221.914 116.653V110.044L216.191 113.348C216.091 113.406 215.963 113.372 215.906 113.272L214.028 110.02C213.971 109.92 214.005 109.793 214.105 109.735L219.828 106.431L214.104 103.126C214.005 103.069 213.97 102.941 214.028 102.841L215.906 99.5894C215.963 99.4896 216.091 99.4554 216.19 99.513L221.914 102.818V96.2086Z" fill="#E0E3E3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M94.3314 193.209C94.3314 193.093 94.4248 193 94.54 193H97.4606C97.5758 193 97.6692 193.093 97.6692 193.209V198.454L102.212 195.831C102.311 195.774 102.439 195.808 102.496 195.908L103.957 198.437C104.014 198.537 103.98 198.664 103.88 198.722L99.3378 201.344L103.88 203.967C103.98 204.025 104.014 204.152 103.956 204.252L102.496 206.781C102.439 206.881 102.311 206.915 102.211 206.858L97.6692 204.235V209.48C97.6692 209.595 97.5758 209.689 97.4606 209.689H94.54C94.4248 209.689 94.3314 209.595 94.3314 209.48V204.235L89.7888 206.858C89.689 206.915 89.5614 206.881 89.5038 206.781L88.0435 204.252C87.9859 204.152 88.0201 204.025 88.1199 203.967L92.6622 201.344L88.1196 198.722C88.0198 198.664 87.9856 198.537 88.0432 198.437L89.5035 195.908C89.5611 195.808 89.6887 195.774 89.7885 195.831L94.3314 198.454V193.209Z" fill="#E0E3E3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M52 62C56.4183 62 60 58.4183 60 54C60 49.5817 56.4183 46 52 46C47.5817 46 44 49.5817 44 54C44 58.4183 47.5817 62 52 62ZM52 58C54.2091 58 56 56.2091 56 54C56 51.7909 54.2091 50 52 50C49.7909 50 48 51.7909 48 54C48 56.2091 49.7909 58 52 58Z" fill="#C4C7C7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39 167C43.4183 167 47 163.418 47 159C47 154.582 43.4183 151 39 151C34.5817 151 31 154.582 31 159C31 163.418 34.5817 167 39 167ZM39 163C41.2091 163 43 161.209 43 159C43 156.791 41.2091 155 39 155C36.7909 155 35 156.791 35 159C35 161.209 36.7909 163 39 163Z" fill="#E0E3E3"/>
<path d="M153.212 48.3137C153.169 48.1981 153.267 48.0798 153.389 48.1006L168.611 50.7166C168.733 50.7375 168.786 50.8819 168.707 50.9766L158.83 62.8518C158.752 62.9465 158.6 62.9204 158.557 62.8048L153.212 48.3137Z" fill="#C9C5D0"/>
<path d="M117.818 59.5458L119.926 55.5412C121.091 53.3296 120.241 50.5929 118.03 49.4287V49.4287C115.818 48.2645 114.969 45.5278 116.133 43.3162V43.3162C117.298 41.1045 116.448 38.3678 114.237 37.2036V37.2036C112.025 36.0394 111.176 33.3027 112.34 31.0911L114.448 27.0865" stroke="#F7F8F8" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M117.818 59.5458L119.926 55.5412C121.091 53.3296 120.241 50.5929 118.03 49.4287V49.4287C115.818 48.2645 114.969 45.5278 116.133 43.3162V43.3162C117.298 41.1045 116.448 38.3678 114.237 37.2036V37.2036C112.025 36.0394 111.176 33.3027 112.34 31.0911L114.448 27.0865" stroke="#78767F" stroke-opacity="0.02" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M117.818 59.5458L119.926 55.5412C121.091 53.3296 120.241 50.5929 118.03 49.4287V49.4287C115.818 48.2645 114.969 45.5278 116.133 43.3162V43.3162C117.298 41.1045 116.448 38.3678 114.237 37.2036V37.2036C112.025 36.0394 111.176 33.3027 112.34 31.0911L114.448 27.0865" stroke="#5D34F2" stroke-opacity="0.11" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M29.7173 172.618C29.7394 172.496 29.8844 172.445 29.9783 172.524L35.6526 177.345C35.7465 177.425 35.7188 177.577 35.6028 177.618L28.5906 180.122C28.4746 180.163 28.3573 180.063 28.3794 179.942L29.7173 172.618Z" fill="#E5E1EC"/>
<rect x="75" y="220" width="80" height="4" rx="2" fill="#747778"/>
<rect x="167" y="220" width="19" height="4" rx="2" fill="#747778"/>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

View file

@ -27,13 +27,9 @@ type SidebarSection = {
items: SidebarItem[];
};
export const useSidebarMenuItems = (): SidebarSection[] | undefined => {
export const useSidebarMenuItems = (): SidebarSection[] => {
const { configs } = useAdminConsoleConfigs();
if (!configs) {
return;
}
return [
{
title: 'overview',
@ -41,7 +37,7 @@ export const useSidebarMenuItems = (): SidebarSection[] | undefined => {
{
Icon: Bolt,
title: 'get_started',
isHidden: configs.hideGetStarted,
isHidden: configs?.hideGetStarted,
},
{
Icon: BarGraph,

View file

@ -18,7 +18,7 @@ const Sidebar = () => {
return (
<div className={styles.sidebar}>
{sections?.map(({ title, items }) => (
{sections.map(({ title, items }) => (
<Section key={title} title={t(title)}>
{items.map(
({ title, Icon, isHidden, modal }) =>

View file

@ -1,4 +1,5 @@
@use '@/scss/underscore' as _;
@use '@/scss/colors' as colors;
.app {
position: absolute;
@ -25,3 +26,23 @@
margin: 0 auto;
}
}
.light {
@include colors.light-theme;
}
.dark {
@include colors.dark-theme;
}
@media (prefers-color-scheme: light) {
body {
@include colors.light-theme;
}
}
@media (prefers-color-scheme: dark) {
body {
@include colors.dark-theme;
}
}

View file

@ -1,15 +1,30 @@
import { useLogto } from '@logto/react';
import { AppearanceMode } from '@logto/schemas';
import React, { useEffect } from 'react';
import { Outlet, useHref } from 'react-router-dom';
import { Outlet, useHref, useLocation, useNavigate } from 'react-router-dom';
import { themeStorageKey } from '@/consts';
import useAdminConsoleConfigs from '@/hooks/use-configs';
import initI18n from '@/i18n/init';
import LogtoLoading from '../LogtoLoading';
import Sidebar from './components/Sidebar';
import SessionExpired from '../SessionExpired';
import Sidebar, { getPath } from './components/Sidebar';
import { useSidebarMenuItems } from './components/Sidebar/hook';
import Topbar from './components/Topbar';
import * as styles from './index.module.scss';
const defaultTheme = localStorage.getItem(themeStorageKey) ?? AppearanceMode.SyncWithSystem;
const AppContent = () => {
const { isAuthenticated, signIn } = useLogto();
const { isAuthenticated, error, signIn } = useLogto();
const href = useHref('/callback');
const { configs, error: configsError } = useAdminConsoleConfigs();
const isLoadingConfigs = !configs && !configsError;
const location = useLocation();
const navigate = useNavigate();
const sections = useSidebarMenuItems();
useEffect(() => {
if (!isAuthenticated) {
@ -17,10 +32,43 @@ const AppContent = () => {
}
}, [href, isAuthenticated, signIn]);
if (!isAuthenticated) {
useEffect(() => {
const theme = configs?.appearanceMode ?? defaultTheme;
const isFollowSystem = theme === AppearanceMode.SyncWithSystem;
const className = styles[theme] ?? '';
if (!isFollowSystem) {
document.body.classList.add(className);
}
return () => {
if (!isFollowSystem) {
document.body.classList.remove(className);
}
};
}, [configs?.appearanceMode]);
useEffect(() => {
(async () => {
void initI18n(configs?.language);
})();
}, [configs?.language]);
useEffect(() => {
// Navigate to the first menu item after configs are loaded.
if (configs && location.pathname === '/') {
navigate(getPath(sections[0]?.items[0]?.title ?? ''));
}
}, [location.pathname, configs, sections, navigate]);
if (!isAuthenticated || isLoadingConfigs) {
return <LogtoLoading message="general.loading" />;
}
if (error) {
return <SessionExpired />;
}
return (
<div className={styles.app}>
<Topbar />

View file

@ -3,6 +3,7 @@
.container {
display: flex;
flex-direction: column;
height: 100vh;
color: var(--color-text);
align-items: center;
overflow: hidden;

View file

@ -0,0 +1,28 @@
@use '@/scss/underscore' as _;
.container {
display: flex;
flex-direction: column;
height: 100vh;
background-color: var(--color-layer-1);
align-items: center;
overflow: hidden;
img {
height: 256px;
width: 256px;
margin: _.unit(42) 0 _.unit(6);
}
.title {
font: var(--font-title-large);
margin-bottom: _.unit(4);
}
.subtitle {
font: var(--font-body-medium);
margin-bottom: _.unit(6);
max-width: 470px;
text-align: center;
}
}

View file

@ -0,0 +1,33 @@
import { useLogto } from '@logto/react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useHref } from 'react-router-dom';
import WarningIcon from '@/assets/images/warning.svg';
import Button from '../Button';
import * as styles from './index.module.scss';
const SessionExpired = () => {
const { signIn } = useLogto();
const href = useHref('/callback');
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return (
<div className={styles.container}>
<img src={WarningIcon} />
<div className={styles.title}>{t('session_expire.title')}</div>
<div className={styles.subtitle}>{t('session_expire.subtitle')}</div>
<Button
size="large"
type="outline"
title="admin_console.session_expire.button"
onClick={() => {
void signIn(new URL(href, window.location.origin).toString());
}}
/>
</div>
);
};
export default SessionExpired;

View file

@ -1,10 +1,16 @@
import { useLogto } from '@logto/react';
import { AdminConsoleConfig, Setting } from '@logto/schemas';
import useSWR from 'swr';
import useApi, { RequestError } from './use-api';
const useAdminConsoleConfigs = () => {
const { data: settings, error, mutate } = useSWR<Setting, RequestError>('/api/settings');
const { isAuthenticated } = useLogto();
const {
data: settings,
error,
mutate,
} = useSWR<Setting, RequestError>(isAuthenticated && '/api/settings');
const api = useApi();
const updateConfigs = async (delta: Partial<AdminConsoleConfig>) => {

View file

@ -1,21 +1,11 @@
import { useHandleSignInCallback, useLogto } from '@logto/react';
import React, { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useHandleSignInCallback } from '@logto/react';
import React from 'react';
import LogtoLoading from '@/components/LogtoLoading';
import { getBasename } from '@/utilities/app';
const Callback = () => {
const { isAuthenticated, isLoading } = useLogto();
const navigate = useNavigate();
useHandleSignInCallback();
// TO-DO: Error handling
useEffect(() => {
if (isAuthenticated && !isLoading) {
navigate('/', { replace: true });
}
}, [isAuthenticated, isLoading, navigate]);
useHandleSignInCallback(getBasename());
return <LogtoLoading message="general.redirecting" />;
};

View file

@ -0,0 +1,5 @@
export const getBasename = (): string => {
const isBasenameNeeded = process.env.NODE_ENV !== 'development' || process.env.PORT === '5002';
return isBasenameNeeded ? '/console' : '';
};

View file

@ -506,6 +506,12 @@ const translation = {
appearance_dark: 'Dark mode',
saved: 'Saved!',
},
session_expire: {
title: 'Session Expired',
subtitle:
'Your session has expired and you have been disconnected. Click the button below to sign in admin console again.',
button: 'Sign in again',
},
},
};

View file

@ -501,6 +501,11 @@ const translation = {
appearance_dark: '深色模式',
saved: '已保存',
},
session_expire: {
title: '会话已过期',
subtitle: '由于会话过期,您已被登出. 请点击下方按钮重新登录到管理界面.',
button: '重新登录',
},
},
};

View file

@ -480,7 +480,7 @@ importers:
specifiers:
'@fontsource/roboto-mono': ^4.5.7
'@logto/phrases': ^0.1.0
'@logto/react': ^0.1.5
'@logto/react': ^0.1.7
'@logto/schemas': ^0.1.0
'@mdx-js/react': ^1.6.22
'@parcel/core': ^2.5.0
@ -535,7 +535,7 @@ importers:
devDependencies:
'@fontsource/roboto-mono': 4.5.7
'@logto/phrases': link:../phrases
'@logto/react': 0.1.5_react@17.0.2
'@logto/react': 0.1.7_react@17.0.2
'@logto/schemas': link:../schemas
'@mdx-js/react': 1.6.22_react@17.0.2
'@parcel/core': 2.5.0
@ -4395,11 +4395,11 @@ packages:
'@lezer/common': 0.15.12
dev: true
/@logto/browser/0.1.5:
resolution: {integrity: sha512-IKdovieQmI+iVcvLo55tIMtWlNqnnGA6KN+JBjSVXpfJNdo05saNlUaOzJc2AIAbR/dcDszNYogZIoAw3YO4BQ==}
/@logto/browser/0.1.7:
resolution: {integrity: sha512-toP/Fcqj+jWyaK25yFTxlFJ5UAErQr5mbL4vFHP+l9uR6UeLDLmGx9NuEVFKU52zuRIevGoZLRn5Q5XOGax9ig==}
requiresBuild: true
dependencies:
'@logto/js': 0.1.5
'@logto/js': 0.1.7
'@silverhand/essentials': 1.1.7
jose: 4.6.0
lodash.get: 4.4.2
@ -4407,8 +4407,8 @@ packages:
superstruct: 0.15.4
dev: true
/@logto/js/0.1.5:
resolution: {integrity: sha512-zf/ERfvxhbQ9JgLJ8xhFPcTtoeUJbiZHZ3iP6msGda5X/mdCABlSJLlZI6gtrf3DRKKv1K5Z/2NIDeCFfOMNNw==}
/@logto/js/0.1.7:
resolution: {integrity: sha512-APtl1L+mQsv5FG01rMpqXHdLKa8fratxRSO77h5lumapW2a5qGg4P1BAhkiWCt8Gb6fw2JzW9XpkXKdpVa4MHw==}
requiresBuild: true
dependencies:
'@silverhand/essentials': 1.1.7
@ -4419,13 +4419,13 @@ packages:
superstruct: 0.15.4
dev: true
/@logto/react/0.1.5_react@17.0.2:
resolution: {integrity: sha512-mPMiqE8IIvbnfgJiRGgyMHfqMpVKP/6lTA2N6Vxm0eBDEDjxl2anlfZe9Ey8Gk9L81UGC3/T5EFl/R22PZDqqQ==}
/@logto/react/0.1.7_react@17.0.2:
resolution: {integrity: sha512-zRg5uQZ0P1IdSA8nkMMRIY+BtRWF0rImS1Ec1Ku0LTWE315BdS/em0mVwDPqDcqlq8HMwyEorE19TXemnunK1Q==}
requiresBuild: true
peerDependencies:
react: '>=16.8.0'
dependencies:
'@logto/browser': 0.1.5
'@logto/browser': 0.1.7
'@silverhand/essentials': 1.1.7
react: 17.0.2
dev: true