mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat: demo app dev panel (#6105)
This commit is contained in:
parent
01558bbddf
commit
f78b1768ef
23 changed files with 383 additions and 73 deletions
5
.changeset/few-moose-sniff.md
Normal file
5
.changeset/few-moose-sniff.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@logto/demo-app": minor
|
||||
---
|
||||
|
||||
add dev panel
|
|
@ -33,7 +33,7 @@
|
|||
"@logto/language-kit": "workspace:^1.1.0",
|
||||
"@logto/phrases": "workspace:^1.11.0",
|
||||
"@logto/phrases-experience": "workspace:^1.6.1",
|
||||
"@logto/react": "^3.0.8",
|
||||
"@logto/react": "^3.0.11",
|
||||
"@logto/schemas": "workspace:^1.17.0",
|
||||
"@logto/shared": "workspace:^3.1.1",
|
||||
"@mdx-js/mdx": "^3.0.1",
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
"@logto/core-kit": "workspace:^2.4.0",
|
||||
"@logto/language-kit": "workspace:^1.1.0",
|
||||
"@logto/phrases": "workspace:^1.10.0",
|
||||
"@logto/react": "^3.0.8",
|
||||
"@logto/react": "^3.0.11",
|
||||
"@logto/schemas": "workspace:^1.15.0",
|
||||
"@parcel/core": "2.9.3",
|
||||
"@parcel/transformer-sass": "2.9.3",
|
||||
|
@ -37,6 +37,7 @@
|
|||
"eslint": "^8.56.0",
|
||||
"i18next": "^22.4.15",
|
||||
"i18next-browser-languagedetector": "^8.0.0",
|
||||
"jose": "^5.0.0",
|
||||
"lint-staged": "^15.0.0",
|
||||
"parcel": "2.9.3",
|
||||
"postcss": "^8.4.31",
|
||||
|
|
|
@ -3,40 +3,113 @@
|
|||
@use '@logto/core-kit/scss/console-themes' as themes;
|
||||
|
||||
.app {
|
||||
input {
|
||||
background-color: var(--color-layer-1);
|
||||
font: var(--font-body-2);
|
||||
color: var(--color-text);
|
||||
padding: 0 _.unit(3);
|
||||
height: 36px;
|
||||
border: 1px solid var(--color-border);
|
||||
outline: 3px solid transparent;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline-block;
|
||||
user-select: none;
|
||||
border: 1px solid var(--color-border);
|
||||
background-color: var(--color-layer-1);
|
||||
border-radius: 8px;
|
||||
padding: _.unit(3) _.unit(6);
|
||||
font: var(--font-label-2);
|
||||
color: var(--color-text);
|
||||
transition: background ease-in-out 0.2s;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: var(--color-hover);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 3px solid var(--color-focused-variant);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
background: var(--color-layer-1);
|
||||
border-radius: 16px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 640px;
|
||||
height: 640px;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
img {
|
||||
margin-top: _.unit(25);
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: _.unit(6);
|
||||
color: var(--color-neutral-10);
|
||||
font: var(--font-title-2);
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: _.unit(1);
|
||||
font: var(--font-body-2);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
&.congrats {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
width: 640px;
|
||||
height: 640px;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.title {
|
||||
margin-top: _.unit(6);
|
||||
}
|
||||
|
||||
.text {
|
||||
margin-top: _.unit(1);
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top: _.unit(8);
|
||||
}
|
||||
}
|
||||
|
||||
&.devPanel {
|
||||
max-width: 800px;
|
||||
width: 25vw;
|
||||
position: fixed;
|
||||
left: _.unit(2);
|
||||
top: _.unit(2);
|
||||
padding: _.unit(4);
|
||||
gap: _.unit(3);
|
||||
|
||||
.item {
|
||||
margin: _.unit(2) 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: _.unit(1);
|
||||
}
|
||||
|
||||
.button {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
margin-top: _.unit(25);
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.infoCard {
|
||||
margin-top: _.unit(4);
|
||||
padding: _.unit(4);
|
||||
|
@ -61,25 +134,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
user-select: none;
|
||||
margin-top: _.unit(8);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
padding: _.unit(3) _.unit(6);
|
||||
font: var(--font-label-2);
|
||||
color: var(--color-text);
|
||||
transition: background ease-in-out 0.2s;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: var(--color-hover);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 3px solid var(--color-focused-variant);
|
||||
}
|
||||
}
|
||||
|
||||
.continue {
|
||||
margin-top: _.unit(12);
|
||||
|
@ -121,6 +175,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--color-neutral-10);
|
||||
margin: _.unit(3);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
@include themes.light;
|
||||
|
|
|
@ -1,28 +1,41 @@
|
|||
import type { IdTokenClaims } from '@logto/react';
|
||||
import { LogtoProvider, useLogto, Prompt, UserScope } from '@logto/react';
|
||||
import { type IdTokenClaims, LogtoProvider, useLogto, type Prompt } from '@logto/react';
|
||||
import { demoAppApplicationId } from '@logto/schemas';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import '@/scss/normalized.scss';
|
||||
|
||||
import * as styles from './App.module.scss';
|
||||
import Callback from './Callback';
|
||||
import DevPanel from './DevPanel';
|
||||
import congratsDark from './assets/congrats-dark.svg';
|
||||
import congrats from './assets/congrats.svg';
|
||||
import initI18n from './i18n/init';
|
||||
import { getLocalData, setLocalData } from './utils';
|
||||
|
||||
void initI18n();
|
||||
|
||||
const Main = () => {
|
||||
const params = new URL(window.location.href).searchParams;
|
||||
const { isAuthenticated, isLoading, getIdTokenClaims, signIn, signOut } = useLogto();
|
||||
const [user, setUser] = useState<Pick<IdTokenClaims, 'sub' | 'username'>>();
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'demo_app' });
|
||||
const isInCallback = Boolean(new URL(window.location.href).searchParams.get('code'));
|
||||
const isInCallback = Boolean(params.get('code'));
|
||||
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
const [congratsIcon, setCongratsIcon] = useState<string>(isDarkMode ? congratsDark : congrats);
|
||||
const [showDevPanel, setShowDevPanel] = useState(getLocalData('ui').showDevPanel ?? false);
|
||||
const error = params.get('error');
|
||||
const errorDescription = params.get('error_description');
|
||||
|
||||
const toggleDevPanel = useCallback(() => {
|
||||
setShowDevPanel((previous) => {
|
||||
setLocalData('ui', { showDevPanel: !previous });
|
||||
return !previous;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isInCallback || isLoading) {
|
||||
if (isInCallback || isLoading || error) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -43,7 +56,7 @@ const Main = () => {
|
|||
extraParams: Object.fromEntries(new URLSearchParams(window.location.search).entries()),
|
||||
});
|
||||
}
|
||||
}, [getIdTokenClaims, isAuthenticated, isInCallback, isLoading, signIn, user]);
|
||||
}, [error, getIdTokenClaims, isAuthenticated, isInCallback, isLoading, signIn, user]);
|
||||
|
||||
useEffect(() => {
|
||||
const onThemeChange = (event: MediaQueryListEvent) => {
|
||||
|
@ -64,13 +77,37 @@ const Main = () => {
|
|||
return <Callback />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className={styles.app}>
|
||||
<div className={styles.error}>
|
||||
<p>
|
||||
Error occurred: {error}
|
||||
<br />
|
||||
{errorDescription}
|
||||
</p>
|
||||
<button
|
||||
className={styles.button}
|
||||
onClick={() => {
|
||||
setLocalData('config', {});
|
||||
window.location.assign('/demo-app');
|
||||
}}
|
||||
>
|
||||
Reset config and retry
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated || !user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.app}>
|
||||
<div className={styles.card}>
|
||||
{showDevPanel && <DevPanel />}
|
||||
<div className={[styles.card, styles.congrats].join(' ')}>
|
||||
{congratsIcon && <img src={congratsIcon} alt="Congrats" />}
|
||||
<div className={styles.title}>{t('title')}</div>
|
||||
<div className={styles.text}>{t('subtitle')}</div>
|
||||
|
@ -99,19 +136,36 @@ const Main = () => {
|
|||
>
|
||||
{t('sign_out')}
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className={styles.button}
|
||||
onClick={toggleDevPanel}
|
||||
onKeyDown={({ key }) => {
|
||||
if (key === 'Enter' || key === ' ') {
|
||||
toggleDevPanel();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{showDevPanel ? 'Close' : 'Open'} dev panel
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const config = getLocalData('config');
|
||||
|
||||
return (
|
||||
<LogtoProvider
|
||||
config={{
|
||||
endpoint: window.location.origin,
|
||||
appId: demoAppApplicationId,
|
||||
prompt: [Prompt.Login, Prompt.Consent],
|
||||
scopes: [UserScope.Organizations, UserScope.OrganizationRoles],
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
prompt: config.prompt ? (config.prompt.split(' ') as Prompt[]) : [],
|
||||
scopes: config.scope ? config.scope.split(' ') : [],
|
||||
resources: config.resource ? config.resource.split(' ') : [],
|
||||
}}
|
||||
>
|
||||
<Main />
|
||||
|
|
114
packages/demo-app/src/DevPanel.tsx
Normal file
114
packages/demo-app/src/DevPanel.tsx
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { useLogto } from '@logto/react';
|
||||
import { decodeJwt } from 'jose';
|
||||
import { useCallback, useState, type FormEventHandler } from 'react';
|
||||
|
||||
import * as styles from './App.module.scss';
|
||||
import { getLocalData, setLocalData } from './utils';
|
||||
|
||||
const safeDecodeJwt = (token: string) => {
|
||||
try {
|
||||
return decodeJwt(token);
|
||||
} catch {
|
||||
return token;
|
||||
}
|
||||
};
|
||||
|
||||
const DevPanel = () => {
|
||||
const config = getLocalData('config');
|
||||
const [showSaved, setShowSaved] = useState(false);
|
||||
const { getAccessToken, getIdTokenClaims, fetchUserInfo } = useLogto();
|
||||
|
||||
const submitConfig: FormEventHandler<HTMLFormElement> = useCallback((event) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
setLocalData('config', data);
|
||||
setShowSaved(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setShowSaved(false);
|
||||
}, 500);
|
||||
}, []);
|
||||
|
||||
const requestToken: FormEventHandler<HTMLFormElement> = useCallback(
|
||||
async (event) => {
|
||||
event.preventDefault();
|
||||
const formData = new FormData(event.currentTarget);
|
||||
const data = Object.fromEntries(formData.entries());
|
||||
const token = await getAccessToken(
|
||||
data.resource ? String(data.resource) : undefined,
|
||||
data.organizationId ? String(data.organizationId) : undefined
|
||||
);
|
||||
console.log(token ? safeDecodeJwt(token) : 'No token');
|
||||
},
|
||||
[getAccessToken]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={[styles.card, styles.devPanel].join(' ')}>
|
||||
<form onSubmit={submitConfig}>
|
||||
<div className={styles.title}>Logto config</div>
|
||||
<div className={styles.item}>
|
||||
<div className={styles.text}>Prompt</div>
|
||||
<input name="prompt" defaultValue={config.prompt} type="text" />
|
||||
</div>
|
||||
<div className={styles.item}>
|
||||
<div className={styles.text}>Scope</div>
|
||||
<input name="scope" defaultValue={config.scope} type="text" />
|
||||
</div>
|
||||
<div className={styles.item}>
|
||||
<div className={styles.text}>Resource (space delimited)</div>
|
||||
<input name="resource" defaultValue={config.resource} type="text" />
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<div className={styles.text}>Sign out to apply changes.</div>
|
||||
<button type="submit" className={styles.button}>
|
||||
{showSaved ? 'Saved' : 'Save'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<form onSubmit={requestToken}>
|
||||
<div className={styles.title}>Refresh token grant</div>
|
||||
<div className={styles.item}>
|
||||
<div className={styles.text}>Resource</div>
|
||||
<input name="resource" type="text" />
|
||||
</div>
|
||||
<div className={styles.item}>
|
||||
<div className={styles.text}>Organization ID</div>
|
||||
<input name="organizationId" type="text" />
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<div className={styles.text}>See console for the result.</div>
|
||||
<button type="submit" className={styles.button}>
|
||||
Request token
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div>
|
||||
<div className={styles.title}>User info</div>
|
||||
<div className={styles.text}>See console for the result.</div>
|
||||
<p>
|
||||
<button
|
||||
className={styles.button}
|
||||
onClick={async () => {
|
||||
console.log(await getIdTokenClaims());
|
||||
}}
|
||||
>
|
||||
Get ID token claims
|
||||
</button>
|
||||
</p>
|
||||
<p>
|
||||
<button
|
||||
className={styles.button}
|
||||
onClick={async () => {
|
||||
console.log(await fetchUserInfo());
|
||||
}}
|
||||
>
|
||||
Fetch user info
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default DevPanel;
|
70
packages/demo-app/src/utils.ts
Normal file
70
packages/demo-app/src/utils.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Prompt, UserScope } from '@logto/react';
|
||||
import { z } from 'zod';
|
||||
|
||||
type LocalLogtoConfig = {
|
||||
prompt?: string;
|
||||
scope?: string;
|
||||
resource?: string;
|
||||
};
|
||||
|
||||
const localLogtoConfigGuard = z.object({
|
||||
prompt: z.string(),
|
||||
scope: z.string(),
|
||||
resource: z.string(),
|
||||
}) satisfies z.ZodType<LocalLogtoConfig>;
|
||||
|
||||
type LocalUiConfig = {
|
||||
showDevPanel?: boolean;
|
||||
};
|
||||
|
||||
const localUiConfigGuard = z.object({
|
||||
showDevPanel: z.boolean(),
|
||||
}) satisfies z.ZodType<LocalUiConfig>;
|
||||
|
||||
type Key = 'config' | 'ui';
|
||||
|
||||
const keyPrefix = 'logto:demo-app:dev:';
|
||||
|
||||
type KeyToType = {
|
||||
config: LocalLogtoConfig;
|
||||
ui: LocalUiConfig;
|
||||
};
|
||||
|
||||
const keyToGuard: Readonly<{
|
||||
[K in Key]: z.ZodType<KeyToType[K]>;
|
||||
}> = Object.freeze({
|
||||
config: localLogtoConfigGuard,
|
||||
ui: localUiConfigGuard,
|
||||
});
|
||||
|
||||
const keyToDefault = Object.freeze({
|
||||
config: {
|
||||
prompt: [Prompt.Login, Prompt.Consent].join(' '),
|
||||
scope: [UserScope.Organizations, UserScope.OrganizationRoles].join(' '),
|
||||
},
|
||||
ui: {},
|
||||
} satisfies Record<Key, unknown>);
|
||||
|
||||
const safeJsonParse = (value: string): unknown => {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const safeZodParse = (guard: z.ZodType<unknown>, value: unknown) => {
|
||||
const result = guard.safeParse(value);
|
||||
return result.success ? result.data : {};
|
||||
};
|
||||
|
||||
export const getLocalData = <K extends Key>(key: K): KeyToType[K] => {
|
||||
const result = keyToGuard[key].safeParse(
|
||||
safeJsonParse(localStorage.getItem(`${keyPrefix}${key}`) ?? '')
|
||||
);
|
||||
return result.success ? result.data : keyToDefault[key];
|
||||
};
|
||||
|
||||
export const setLocalData = (key: Key, value: unknown) => {
|
||||
localStorage.setItem(`${keyPrefix}${key}`, JSON.stringify(safeZodParse(keyToGuard[key], value)));
|
||||
};
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: 'Sie haben sich erfolgreich bei der Live-Vorschau angemeldet!',
|
||||
subtitle: 'Hier sind Ihre Anmeldeinformationen:',
|
||||
username: 'Benutzername: ',
|
||||
user_id: 'Benutzer ID: ',
|
||||
sign_out: 'Abmeldung von der Live-Vorschau',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const demo_app = {
|
||||
title: "You've successfully signed in the live preview!",
|
||||
subtitle: 'Here is your log in information:',
|
||||
subtitle: 'Here is your user information:',
|
||||
username: 'Username: ',
|
||||
user_id: 'User ID: ',
|
||||
sign_out: 'Sign out the live preview',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: '¡Ha iniciado sesión correctamente en la vista previa en vivo!',
|
||||
subtitle: 'Aquí está su información de inicio de sesión:',
|
||||
username: 'Nombre de usuario: ',
|
||||
user_id: 'ID de usuario: ',
|
||||
sign_out: 'Cerrar sesión en la vista previa en vivo',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: "Vous vous êtes connecté avec succès à l'aperçu en direct !",
|
||||
subtitle: 'Voici vos informations de connexion :',
|
||||
username: "Nom d'utilisateur :",
|
||||
user_id: "ID de l'utilisateur :",
|
||||
sign_out: "Se déconnecter de l'aperçu en direct",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: "Hai effettuato l'accesso anteprima live con successo!",
|
||||
subtitle: 'Ecco le tue informazioni di accesso:',
|
||||
username: 'Nome utente: ',
|
||||
user_id: 'ID utente: ',
|
||||
sign_out: "Esci dall'anteprima live",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: 'ライブプレビューへのサインインに成功しました!',
|
||||
subtitle: 'あなたのログイン情報は以下の通りです:',
|
||||
username: 'ユーザー名:',
|
||||
user_id: 'ユーザーID:',
|
||||
sign_out: 'ライブプレビューからサインアウトする',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: 'Live Preview에 성공적으로 로그인했습니다!',
|
||||
subtitle: '여기 로그인 정보가 있어요:',
|
||||
username: '사용자 이름: ',
|
||||
user_id: '사용자 ID: ',
|
||||
sign_out: 'Live Preview에서 로그아웃',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: 'Pomyślnie zalogowałeś się do podglądu na żywo!',
|
||||
subtitle: 'Oto twoje informacje logowania:',
|
||||
username: 'Nazwa użytkownika: ',
|
||||
user_id: 'ID użytkownika: ',
|
||||
sign_out: 'Wyloguj się z podglądu na żywo',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: "You've successfully signed in the live preview!",
|
||||
subtitle: 'Aqui estão suas informações de login:',
|
||||
username: 'Nome de usuário: ',
|
||||
user_id: 'ID do usuário: ',
|
||||
sign_out: 'Sair da Visualização ao Vivo',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: 'Iniciou sessão com sucesso na pré-visualização ao vivo!',
|
||||
subtitle: 'Aqui estão as suas informações de login:',
|
||||
username: 'Utilizador: ',
|
||||
user_id: 'ID de utilizador: ',
|
||||
sign_out: 'Terminar sessão na visualização ao vivo',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: 'Вы успешно вошли в живой просмотр!',
|
||||
subtitle: 'Вот ваши данные для входа:',
|
||||
username: 'Имя пользователя: ',
|
||||
user_id: 'Идентификатор пользователя: ',
|
||||
sign_out: 'Выйти из живого просмотра',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: 'Canlı önizlemede başarıyla oturum açtınız!',
|
||||
subtitle: 'Sisteme giriş bilgileriniz:',
|
||||
username: 'Kullanıcı Adı: ',
|
||||
user_id: 'Kullanıcı Kimliği: ',
|
||||
sign_out: 'Canlı Önizlemeyi Kapat',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: '你已成功登录实时预览!',
|
||||
subtitle: '以下是本次登录的用户信息:',
|
||||
username: '用户名:',
|
||||
user_id: '用户 ID:',
|
||||
sign_out: '退出实时预览',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: '你已成功登錄實時預覽!',
|
||||
subtitle: '以下是本次登錄的用戶信息:',
|
||||
username: '用戶名:',
|
||||
user_id: '用戶 ID:',
|
||||
sign_out: '退出實時預覽',
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const demo_app = {
|
||||
title: '你已成功登錄實時預覽!',
|
||||
subtitle: '以下是本次登錄的用戶信息:',
|
||||
username: '用戶名:',
|
||||
user_id: '用戶 ID:',
|
||||
sign_out: '退出實時預覽',
|
||||
|
|
|
@ -2876,8 +2876,8 @@ importers:
|
|||
specifier: workspace:^1.6.1
|
||||
version: link:../phrases-experience
|
||||
'@logto/react':
|
||||
specifier: ^3.0.8
|
||||
version: 3.0.8(react@18.2.0)
|
||||
specifier: ^3.0.11
|
||||
version: 3.0.11(react@18.2.0)
|
||||
'@logto/schemas':
|
||||
specifier: workspace:^1.17.0
|
||||
version: link:../schemas
|
||||
|
@ -3471,8 +3471,8 @@ importers:
|
|||
specifier: workspace:^1.10.0
|
||||
version: link:../phrases
|
||||
'@logto/react':
|
||||
specifier: ^3.0.8
|
||||
version: 3.0.8(react@18.2.0)
|
||||
specifier: ^3.0.11
|
||||
version: 3.0.11(react@18.2.0)
|
||||
'@logto/schemas':
|
||||
specifier: workspace:^1.15.0
|
||||
version: link:../schemas
|
||||
|
@ -3515,6 +3515,9 @@ importers:
|
|||
i18next-browser-languagedetector:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
jose:
|
||||
specifier: ^5.0.0
|
||||
version: 5.2.4
|
||||
lint-staged:
|
||||
specifier: ^15.0.0
|
||||
version: 15.0.2
|
||||
|
@ -5242,12 +5245,15 @@ packages:
|
|||
resolution: {integrity: sha512-yDWSZMI2Qo/xoYU92tnwSP/gnSvq8+CLK5DqD/4brO42QJa7xjt7eA+HSyuMmSUrKffY2nP3riU81gs+nR8DkA==}
|
||||
engines: {node: ^18.12.0}
|
||||
|
||||
'@logto/browser@2.2.10':
|
||||
resolution: {integrity: sha512-y6NauaxctqpfApccP6uFVmpg/vG1OhsDVLD4Pdpzbmj3whl63Nb17yxSTQHt4eYNKmSZJ2SzudAnMnVEYD91iQ==}
|
||||
'@logto/browser@2.2.13':
|
||||
resolution: {integrity: sha512-7fyenm6f2xSzZc8GHFpAL38rxAXf1hQH/ySSow8QHjgypF0pz9zy9bRSG1oVFgsMrFWYf73dPk7V2ACQ5Tujww==}
|
||||
|
||||
'@logto/client@2.6.6':
|
||||
resolution: {integrity: sha512-QT7jMnzEIWHBNrf9/M8p1OErRBbbNZjoekXGji5aZCyUh975hh8+GEBL21HV71FT3H/5Cq4Gf1GzUbAIW3izMA==}
|
||||
|
||||
'@logto/client@2.7.0':
|
||||
resolution: {integrity: sha512-8mj+757befwQJ4M0h4f3fQwUB1lruDkyLNTyRFOQxFfDMN1QeD70B4nMnz6iTX/Gi09HnIskmj9MHWrbvH6LSQ==}
|
||||
|
||||
'@logto/cloud@0.2.5-a7eedce':
|
||||
resolution: {integrity: sha512-FFjkGjqUgn9PCZnSuCODm2FcjqBm4JfPxfHCiXlOkUjeUhTJLrj7C0gjKzSQ/B6IaWri4EXN/meuqi5z/AMIPg==}
|
||||
engines: {node: ^20.9.0}
|
||||
|
@ -5255,11 +5261,14 @@ packages:
|
|||
'@logto/js@4.1.1':
|
||||
resolution: {integrity: sha512-+RgthBvDw30UojirtAjZeHNfOwDQVURmpjcIBYTIf6afx5F5jJq8b1D/eaFbrCFrmXmatkT2iN7X8kYHui86WQ==}
|
||||
|
||||
'@logto/js@4.1.3':
|
||||
resolution: {integrity: sha512-TIYrVSyD0c1mEt3fU9NKbWRTblujs3Ct/DYgNzBLzE/tmSVvwM6Z4JxnwZZ3xyfnL8dC4UfSQRlc2gjSMzKUGw==}
|
||||
|
||||
'@logto/node@2.4.7':
|
||||
resolution: {integrity: sha512-AlANeqY1NIt93EBcRzrTmyAVHXOHpszTJK+qe1ok50rmZlTmX2p7yQvrg0/Ehwf/+4Rla5vooAR+HIFMaOmPpQ==}
|
||||
|
||||
'@logto/react@3.0.8':
|
||||
resolution: {integrity: sha512-p3pV4rX4g8ZwHQ159mxI+pP3Bwome47dNEmP1hI8/10WqdIPXGYTnfYn5c2l4Y2DyslYyK3ur2Sy4i4K6ept9A==}
|
||||
'@logto/react@3.0.11':
|
||||
resolution: {integrity: sha512-JyOOf7zZOEg7fTRldfi9SGwwbuk3qJTlIKld+GQ1yGWIXilI2JyyY2XQraZWpHQLW8KtQFgRWQ/fUKV0bideFQ==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
|
||||
|
@ -14951,9 +14960,9 @@ snapshots:
|
|||
'@silverhand/essentials': 2.9.1
|
||||
tiny-cookie: 2.4.1
|
||||
|
||||
'@logto/browser@2.2.10':
|
||||
'@logto/browser@2.2.13':
|
||||
dependencies:
|
||||
'@logto/client': 2.6.6
|
||||
'@logto/client': 2.7.0
|
||||
'@silverhand/essentials': 2.9.1
|
||||
js-base64: 3.7.5
|
||||
|
||||
|
@ -14964,6 +14973,13 @@ snapshots:
|
|||
camelcase-keys: 7.0.2
|
||||
jose: 5.2.4
|
||||
|
||||
'@logto/client@2.7.0':
|
||||
dependencies:
|
||||
'@logto/js': 4.1.3
|
||||
'@silverhand/essentials': 2.9.1
|
||||
camelcase-keys: 7.0.2
|
||||
jose: 5.2.4
|
||||
|
||||
'@logto/cloud@0.2.5-a7eedce(zod@3.22.4)':
|
||||
dependencies:
|
||||
'@silverhand/essentials': 2.9.1
|
||||
|
@ -14976,15 +14992,20 @@ snapshots:
|
|||
'@silverhand/essentials': 2.9.1
|
||||
camelcase-keys: 7.0.2
|
||||
|
||||
'@logto/js@4.1.3':
|
||||
dependencies:
|
||||
'@silverhand/essentials': 2.9.1
|
||||
camelcase-keys: 7.0.2
|
||||
|
||||
'@logto/node@2.4.7':
|
||||
dependencies:
|
||||
'@logto/client': 2.6.6
|
||||
'@silverhand/essentials': 2.9.1
|
||||
js-base64: 3.7.5
|
||||
|
||||
'@logto/react@3.0.8(react@18.2.0)':
|
||||
'@logto/react@3.0.11(react@18.2.0)':
|
||||
dependencies:
|
||||
'@logto/browser': 2.2.10
|
||||
'@logto/browser': 2.2.13
|
||||
'@silverhand/essentials': 2.9.1
|
||||
react: 18.2.0
|
||||
|
||||
|
|
Loading…
Reference in a new issue