mirror of
https://github.com/logto-io/logto.git
synced 2025-03-10 22:22:45 -05:00
feat(ui): app notification (#999)
* feat(ui): app notification app notification * feat(ui): remove session storage remove session storage
This commit is contained in:
parent
3c37739107
commit
f4e380f0b1
9 changed files with 91 additions and 51 deletions
|
@ -72,7 +72,6 @@ const translation = {
|
|||
social_create_account: 'No account? You can create a new account and bind.',
|
||||
social_bind_account: 'Already have an account? Sign in to bind it with your social identity.',
|
||||
social_bind_with_existing: 'We find a related account, you can bind it directly.',
|
||||
demo_message: 'Use the Admin username and password to sign in this demo.',
|
||||
},
|
||||
error: {
|
||||
username_password_mismatch: 'Username and password do not match.',
|
||||
|
|
|
@ -72,7 +72,6 @@ const translation = {
|
|||
social_create_account: 'No account? You can create a new account and bind.',
|
||||
social_bind_account: 'Already have an account? Sign in to bind it with your social identity.',
|
||||
social_bind_with_existing: 'We find a related account, you can bind it directly.',
|
||||
demo_message: '请使用 admin 用户名和密码登录',
|
||||
},
|
||||
error: {
|
||||
username_password_mismatch: '用户名和密码不匹配。',
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
.overlay {
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.notification {
|
||||
padding: _.unit(3) _.unit(4);
|
||||
font: var(--font-body);
|
||||
|
@ -15,16 +10,13 @@
|
|||
max-width: 520px;
|
||||
margin: 0 auto _.unit(2);
|
||||
box-shadow: var(--shadow-1);
|
||||
@include _.flex_row;
|
||||
|
||||
&:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
@include _.flex_row;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--color-outline);
|
||||
width: 20px;
|
||||
|
@ -42,23 +34,8 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
:global(body.mobile) {
|
||||
.overlay {
|
||||
top: _.unit(6);
|
||||
left: _.unit(5);
|
||||
right: _.unit(5);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
:global(body.desktop) {
|
||||
.overlay {
|
||||
top: _.unit(-6);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
.link {
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
|
|
|
@ -1,37 +1,28 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactModal, { Props as ModalProps } from 'react-modal';
|
||||
|
||||
import InfoIcon from '@/assets/icons/info-icon.svg';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type Props = ModalProps & {
|
||||
type Props = {
|
||||
className?: string;
|
||||
message: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
const Notification = ({ className, message, onClose, overlayClassName, ...rest }: Props) => {
|
||||
const Notification = ({ className, message, onClose }: Props) => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' });
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className={classNames(styles.notification, className)}
|
||||
overlayClassName={classNames(styles.overlay, overlayClassName)}
|
||||
ariaHideApp={false}
|
||||
parentSelector={() => document.querySelector('main') ?? document.body}
|
||||
onRequestClose={onClose}
|
||||
{...rest}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<InfoIcon className={styles.icon} />
|
||||
<div className={styles.message}>{message}</div>
|
||||
<a className={styles.link} onClick={onClose}>
|
||||
{t('action.got_it')}
|
||||
</a>
|
||||
</div>
|
||||
</ReactModal>
|
||||
<div className={classNames(styles.notification, className)}>
|
||||
<InfoIcon className={styles.icon} />
|
||||
<div className={styles.message}>{message}</div>
|
||||
<a className={styles.link} onClick={onClose}>
|
||||
{t('action.got_it')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
29
packages/ui/src/containers/AppNotification/index.module.scss
Normal file
29
packages/ui/src/containers/AppNotification/index.module.scss
Normal file
|
@ -0,0 +1,29 @@
|
|||
@use '@/scss/underscore' as _;
|
||||
|
||||
|
||||
.appNotification {
|
||||
position: absolute;
|
||||
top: _.unit(6);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
|
||||
:global(body.mobile) {
|
||||
.appNotification {
|
||||
top: _.unit(6);
|
||||
left: _.unit(5);
|
||||
right: _.unit(5);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
:global(body.desktop) {
|
||||
.appNotification {
|
||||
top: _.unit(-6);
|
||||
transform: translate(-50%, -100%);
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
}
|
||||
}
|
24
packages/ui/src/containers/AppNotification/index.test.tsx
Normal file
24
packages/ui/src/containers/AppNotification/index.test.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { render, fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { appNotificationStorageKey } from '@/utils/session-storage';
|
||||
|
||||
import AppNotification from '.';
|
||||
|
||||
describe('AppNotification', () => {
|
||||
it('render properly', () => {
|
||||
const message = 'This is a notification message';
|
||||
sessionStorage.setItem(appNotificationStorageKey, message);
|
||||
const { queryByText, getByText } = render(<AppNotification />);
|
||||
|
||||
expect(queryByText(message)).not.toBeNull();
|
||||
|
||||
const closeLink = getByText('action.got_it');
|
||||
|
||||
expect(closeLink).not.toBeNull();
|
||||
|
||||
fireEvent.click(closeLink);
|
||||
|
||||
expect(queryByText(message)).toBeNull();
|
||||
});
|
||||
});
|
|
@ -1,21 +1,31 @@
|
|||
import { Nullable } from '@silverhand/essentials';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Notification from '@/components/Notification';
|
||||
import { getAppNotificationInfo, clearAppNotificationInfo } from '@/utils/session-storage';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
const AppNotification = () => {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'main_flow' });
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [notification, setNotification] = useState<Nullable<string>>(null);
|
||||
|
||||
const onClose = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
setNotification(null);
|
||||
clearAppNotificationInfo();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setIsOpen(true);
|
||||
const notification = getAppNotificationInfo();
|
||||
setNotification(notification);
|
||||
}, []);
|
||||
|
||||
return <Notification isOpen={isOpen} message={t('description.demo_message')} onClose={onClose} />;
|
||||
if (!notification) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Notification className={styles.appNotification} message={notification} onClose={onClose} />
|
||||
);
|
||||
};
|
||||
|
||||
export default AppNotification;
|
||||
|
|
|
@ -3,6 +3,7 @@ import classNames from 'classnames';
|
|||
import React, { useContext } from 'react';
|
||||
|
||||
import BrandingHeader from '@/components/BrandingHeader';
|
||||
import AppNotification from '@/containers/AppNotification';
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
|
||||
import * as styles from './index.module.scss';
|
||||
|
@ -35,6 +36,7 @@ const SignIn = () => {
|
|||
socialConnectors={experienceSettings.socialConnectors}
|
||||
/>
|
||||
<CreateAccountLink primarySignInMethod={experienceSettings.primarySignInMethod} />
|
||||
<AppNotification />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
9
packages/ui/src/utils/session-storage.ts
Normal file
9
packages/ui/src/utils/session-storage.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const appNotificationStorageKey = 'logto:client:notification';
|
||||
|
||||
export const getAppNotificationInfo = () => {
|
||||
return sessionStorage.getItem(appNotificationStorageKey);
|
||||
};
|
||||
|
||||
export const clearAppNotificationInfo = () => {
|
||||
sessionStorage.removeItem(appNotificationStorageKey);
|
||||
};
|
Loading…
Add table
Reference in a new issue