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

feat(console): early bird gift (#3177)

This commit is contained in:
Xiao Yijun 2023-02-23 15:07:11 +08:00 committed by GitHub
parent 794e270dc5
commit e69790fd17
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 271 additions and 57 deletions

View file

@ -0,0 +1,14 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_331_72604)">
<path d="M20.3332 19.2223C20.3332 20.4446 19.3332 21.4446 18.1109 21.4446H5.88873C4.6665 21.4446 3.6665 20.4446 3.6665 19.2223V9.77789C3.6665 8.55566 4.6665 7.55566 5.88873 7.55566H18.1109C19.3332 7.55566 20.3332 8.55566 20.3332 9.77789V19.2223Z" fill="#FFB95A"/>
<path d="M22 8.11114C22 9.33336 21 10.3334 19.7778 10.3334H4.22222C3 10.3334 2 9.33336 2 8.11114C2 6.88892 3 5.88892 4.22222 5.88892H19.7778C21 5.88892 22 6.88892 22 8.11114Z" fill="#FFB95A"/>
<path d="M3.6665 10.3333H20.3332V11.4444H3.6665V10.3333Z" fill="#EB9918"/>
<path d="M12.5556 3.66675H11.4445C10.5239 3.66675 9.77783 4.41286 9.77783 5.33341V21.4445H14.2223V5.33341C14.2223 4.41341 13.4762 3.66675 12.5556 3.66675Z" fill="#DD3730"/>
<path d="M10.8891 5.88893C11.5002 5.88893 11.5908 5.60226 11.0897 5.25171L7.35522 2.63726C6.85411 2.28671 6.19689 2.43393 5.89356 2.96448L4.77356 4.92448C4.47022 5.45504 4.72245 5.88893 5.33356 5.88893H10.8891ZM13.1113 5.88893C12.5002 5.88893 12.4097 5.60226 12.9108 5.25171L16.6458 2.63726C17.1463 2.28671 17.8041 2.43393 18.1074 2.96448L19.2274 4.92393C19.5302 5.45504 19.278 5.88893 18.6669 5.88893H13.1113Z" fill="#DD3730"/>
</g>
<defs>
<clipPath id="clip0_331_72604">
<rect width="20" height="20" fill="white" transform="translate(2 2)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,16 @@
@use '@/scss/underscore' as _;
.title {
white-space: normal;
}
.content {
margin-top: _.unit(-5);
.description {
font: var(--font-body-2);
color: var(--color-text-secondary);
margin-bottom: _.unit(6);
}
}

View file

@ -0,0 +1,43 @@
import { useTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import ModalLayout from '@/components/ModalLayout';
import * as modalStyles from '@/scss/modal.module.scss';
import Reservation from '../../Reservation';
import * as styles from './index.module.scss';
type Props = {
isOpen: boolean;
onClose: () => void;
};
const GiftModal = ({ isOpen, onClose }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return (
<ReactModal
shouldCloseOnOverlayClick
isOpen={isOpen}
className={modalStyles.content}
overlayClassName={modalStyles.overlay}
onRequestClose={onClose}
>
<ModalLayout
isTitleWordWrapEnabled
title="cloud.gift.title"
className={styles.content}
onClose={onClose}
>
<div className={styles.description}>{t('cloud.gift.description')}</div>
<Reservation
title="cloud.gift.reserve_title"
description="cloud.gift.reserve_description"
reservationButtonTitle="cloud.gift.book_button"
/>
</ModalLayout>
</ReactModal>
);
};
export default GiftModal;

View file

@ -0,0 +1,31 @@
import { useState } from 'react';
import Gift from '@/assets/images/gift.svg';
import IconButton from '@/components/IconButton';
import GiftModal from './GiftModal';
const EarlyBirdGift = () => {
const [isGiftOpen, setIsGiftOpen] = useState(false);
return (
<>
<IconButton
size="large"
onClick={() => {
setIsGiftOpen(true);
}}
>
<Gift />
</IconButton>
<GiftModal
isOpen={isGiftOpen}
onClose={() => {
setIsGiftOpen(false);
}}
/>
</>
);
};
export default EarlyBirdGift;

View file

@ -0,0 +1,31 @@
@use '@/scss/underscore' as _;
.reservation {
width: 100%;
padding: _.unit(3) _.unit(4);
border: 1px solid var(--color-divider);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: space-between;
.reservationInfo {
display: flex;
align-items: center;
.reservationIcon {
margin-right: _.unit(4);
flex-shrink: 0;
}
.reservationTitle {
font: var(--font-title-3);
}
.reservationDescription {
font: var(--font-body-2);
color: var(--color-text-secondary);
padding-right: _.unit(4);
}
}
}

View file

@ -0,0 +1,45 @@
import type { AdminConsoleKey } from '@logto/phrases';
import { useTranslation } from 'react-i18next';
import Calendar from '@/assets/images/calendar.svg';
import { reservationLink } from '@/cloud/constants';
import Button from '@/components/Button';
import { buildUrl } from '@/utils/url';
import * as styles from './index.module.scss';
type Props = {
title: AdminConsoleKey;
description: AdminConsoleKey;
reservationButtonTitle: AdminConsoleKey;
};
const Reservation = ({ title, description, reservationButtonTitle }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return (
<div className={styles.reservation}>
<div className={styles.reservationInfo}>
<Calendar className={styles.reservationIcon} />
<div>
<div className={styles.reservationTitle}>{t(title)}</div>
<div className={styles.reservationDescription}>{t(description)}</div>
</div>
</div>
<Button
type="outline"
title={reservationButtonTitle}
onClick={() => {
const bookLink = buildUrl(reservationLink, {
// Note: month format is YYYY-MM
month: new Date().toISOString().slice(0, 7),
});
window.open(bookLink, '_blank');
}}
/>
</div>
);
};
export default Reservation;

View file

@ -0,0 +1 @@
export const reservationLink = 'https://calendly.com/logto/30min';

View file

@ -24,32 +24,3 @@
width: 100%;
margin: _.unit(8) 0;
}
.reservation {
width: 100%;
padding: _.unit(3) _.unit(4);
border: 1px solid var(--color-divider);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: space-between;
.reservationInfo {
display: flex;
align-items: center;
.reservationIcon {
margin-right: _.unit(4);
flex-shrink: 0;
}
.reservationTitle {
font: var(--font-title-3);
}
.reservationDescription {
font: var(--font-body-2);
color: var(--color-text-secondary);
}
}
}

View file

@ -4,15 +4,14 @@ import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import AirPlay from '@/assets/images/air-play.svg';
import Calendar from '@/assets/images/calendar.svg';
import GetStarted from '@/assets/images/get-started.svg';
import ActionBar from '@/cloud/components/ActionBar';
import Reservation from '@/cloud/components/Reservation';
import * as pageLayout from '@/cloud/scss/layout.module.scss';
import Button from '@/components/Button';
import Divider from '@/components/Divider';
import OverlayScrollbar from '@/components/OverlayScrollbar';
import { AppEndpointsContext } from '@/containers/AppEndpointsProvider';
import { buildUrl } from '@/utils/url';
import { CloudPage } from '../../types';
import { getCloudPagePathname } from '../../utils';
@ -49,30 +48,12 @@ const Congrats = () => {
}}
/>
<Divider className={styles.divider} />
<div className={styles.reservation}>
<div className={styles.reservationInfo}>
<Calendar className={styles.reservationIcon} />
<div>
<div className={styles.reservationTitle}>{t('cloud.congrats.reserve_title')}</div>
<div className={styles.reservationDescription}>
{t('cloud.congrats.reserve_description')}
</div>
</div>
</div>
<Button
type="outline"
title="cloud.congrats.book_button"
onClick={() => {
const bookLink = buildUrl('https://calendly.com/logto/30min', {
// Note: month format is YYYY-MM
month: new Date().toISOString().slice(0, 7),
});
window.open(bookLink, '_blank');
}}
<Reservation
title="cloud.congrats.reserve_title"
description="cloud.congrats.reserve_description"
reservationButtonTitle="cloud.congrats.book_button"
/>
</div>
</div>
</OverlayScrollbar>
<ActionBar step={4}>
<Button

View file

@ -5,6 +5,9 @@
.title {
color: var(--color-text);
}
.titleEllipsis {
@include _.text-ellipsis;
}

View file

@ -10,18 +10,27 @@ type Props = {
title: AdminConsoleKey | ReactElement<typeof DangerousRaw>;
subtitle?: AdminConsoleKey | ReactElement<typeof DangerousRaw>;
size?: 'small' | 'medium' | 'large';
isWordWrapEnabled?: boolean;
className?: string;
};
/**
* Always use this component to render CardTitle, with built-in i18n support.
*/
const CardTitle = ({ title, subtitle, size = 'large', className }: Props) => {
const CardTitle = ({
title,
subtitle,
size = 'large',
isWordWrapEnabled = false,
className,
}: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return (
<div className={classNames(styles.container, styles[size], className)}>
<div className={styles.title}>{typeof title === 'string' ? t(title) : title}</div>
<div className={classNames(styles.title, !isWordWrapEnabled && styles.titleEllipsis)}>
{typeof title === 'string' ? t(title) : title}
</div>
{subtitle && (
<div className={styles.subtitle}>
{typeof subtitle === 'string' ? t(subtitle) : subtitle}

View file

@ -18,6 +18,7 @@ type Props = {
onClose?: () => void;
className?: string;
size?: 'medium' | 'large' | 'xlarge';
isTitleWordWrapEnabled?: boolean;
};
const ModalLayout = ({
@ -28,11 +29,12 @@ const ModalLayout = ({
onClose,
className,
size = 'medium',
isTitleWordWrapEnabled = false,
}: Props) => {
return (
<Card className={classNames(styles.container, styles[size])}>
<div className={styles.header}>
<CardTitle title={title} subtitle={subtitle} />
<CardTitle title={title} subtitle={subtitle} isWordWrapEnabled={isTitleWordWrapEnabled} />
{onClose && (
<IconButton
onClick={() => {

View file

@ -2,7 +2,9 @@ import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import Logo from '@/assets/images/logo.svg';
import EarlyBirdGift from '@/cloud/components/EarlyBirdGift';
import Spacer from '@/components/Spacer';
import { isCloud } from '@/consts/cloud';
import GetStartedProgress from '@/pages/GetStarted/components/GetStartedProgress';
import UserInfo from '../UserInfo';
@ -22,6 +24,7 @@ const Topbar = ({ className }: Props) => {
<div className={styles.text}>{t('title')}</div>
<Spacer />
<GetStartedProgress />
{isCloud && <EarlyBirdGift />}
<UserInfo />
</div>
);

View file

@ -59,6 +59,14 @@ const cloud = {
discord_link: 'discord channel', // UNTRANSLATED
enter_admin_console: 'Enter Admin Console', // UNTRANSLATED
},
gift: {
title: 'Secure early credit for Logto SaaS join the front-runners now !', // UNTRANSLATED
description: 'Book a one-on-one session with our team for early credit and use case sharing.', // UNTRANSLATED
reserve_title: 'Reserve your time with Logto team', // UNTRANSLATED
reserve_description:
'You can also save the calendar link. Were always available to assist you.', // UNTRANSLATED
book_button: 'Book', // UNTRANSLATED
},
};
export default cloud;

View file

@ -59,6 +59,14 @@ const cloud = {
discord_link: 'discord channel',
enter_admin_console: 'Enter Admin Console',
},
gift: {
title: 'Secure early credit for Logto SaaS join the front-runners now !',
description: 'Book a one-on-one session with our team for early credit and use case sharing.',
reserve_title: 'Reserve your time with Logto team',
reserve_description:
'You can also save the calendar link. Were always available to assist you.',
book_button: 'Book',
},
};
export default cloud;

View file

@ -59,6 +59,14 @@ const cloud = {
discord_link: 'discord channel', // UNTRANSLATED
enter_admin_console: 'Enter Admin Console', // UNTRANSLATED
},
gift: {
title: 'Secure early credit for Logto SaaS join the front-runners now !', // UNTRANSLATED
description: 'Book a one-on-one session with our team for early credit and use case sharing.', // UNTRANSLATED
reserve_title: 'Reserve your time with Logto team', // UNTRANSLATED
reserve_description:
'You can also save the calendar link. Were always available to assist you.', // UNTRANSLATED
book_button: 'Book', // UNTRANSLATED
},
};
export default cloud;

View file

@ -59,6 +59,14 @@ const cloud = {
discord_link: 'discord channel', // UNTRANSLATED
enter_admin_console: 'Enter Admin Console', // UNTRANSLATED
},
gift: {
title: 'Secure early credit for Logto SaaS join the front-runners now !', // UNTRANSLATED
description: 'Book a one-on-one session with our team for early credit and use case sharing.', // UNTRANSLATED
reserve_title: 'Reserve your time with Logto team', // UNTRANSLATED
reserve_description:
'You can also save the calendar link. Were always available to assist you.', // UNTRANSLATED
book_button: 'Book', // UNTRANSLATED
},
};
export default cloud;

View file

@ -59,6 +59,14 @@ const cloud = {
discord_link: 'discord channel', // UNTRANSLATED
enter_admin_console: 'Enter Admin Console', // UNTRANSLATED
},
gift: {
title: 'Secure early credit for Logto SaaS join the front-runners now !', // UNTRANSLATED
description: 'Book a one-on-one session with our team for early credit and use case sharing.', // UNTRANSLATED
reserve_title: 'Reserve your time with Logto team', // UNTRANSLATED
reserve_description:
'You can also save the calendar link. Were always available to assist you.', // UNTRANSLATED
book_button: 'Book', // UNTRANSLATED
},
};
export default cloud;

View file

@ -59,6 +59,14 @@ const cloud = {
discord_link: 'discord channel', // UNTRANSLATED
enter_admin_console: 'Enter Admin Console', // UNTRANSLATED
},
gift: {
title: 'Secure early credit for Logto SaaS join the front-runners now !', // UNTRANSLATED
description: 'Book a one-on-one session with our team for early credit and use case sharing.', // UNTRANSLATED
reserve_title: 'Reserve your time with Logto team', // UNTRANSLATED
reserve_description:
'You can also save the calendar link. Were always available to assist you.', // UNTRANSLATED
book_button: 'Book', // UNTRANSLATED
},
};
export default cloud;

View file

@ -59,6 +59,14 @@ const cloud = {
discord_link: 'discord channel', // UNTRANSLATED
enter_admin_console: 'Enter Admin Console', // UNTRANSLATED
},
gift: {
title: 'Secure early credit for Logto SaaS join the front-runners now !', // UNTRANSLATED
description: 'Book a one-on-one session with our team for early credit and use case sharing.', // UNTRANSLATED
reserve_title: 'Reserve your time with Logto team', // UNTRANSLATED
reserve_description:
'You can also save the calendar link. Were always available to assist you.', // UNTRANSLATED
book_button: 'Book', // UNTRANSLATED
},
};
export default cloud;

View file

@ -59,6 +59,14 @@ const cloud = {
discord_link: 'discord channel', // UNTRANSLATED
enter_admin_console: 'Enter Admin Console', // UNTRANSLATED
},
gift: {
title: 'Secure early credit for Logto SaaS join the front-runners now !', // UNTRANSLATED
description: 'Book a one-on-one session with our team for early credit and use case sharing.', // UNTRANSLATED
reserve_title: 'Reserve your time with Logto team', // UNTRANSLATED
reserve_description:
'You can also save the calendar link. Were always available to assist you.', // UNTRANSLATED
book_button: 'Book', // UNTRANSLATED
},
};
export default cloud;