feat: attention page buttons, special welcome offer

This commit is contained in:
gofnnp 2023-09-23 22:16:27 +04:00
parent deb354e29b
commit 5ee5385ef9
8 changed files with 213 additions and 109 deletions

BIN
public/your-friends.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -5,6 +5,7 @@ import routes from '@/routes'
import styles from './styles.module.css' import styles from './styles.module.css'
// import CheckboxWithText from '../CheckboxWithText' // import CheckboxWithText from '../CheckboxWithText'
import SpecialWelcomeOffer from '../SpecialWelcomeOffer' import SpecialWelcomeOffer from '../SpecialWelcomeOffer'
import MainButton from '../MainButton'
// import MainButton from '../MainButton' // import MainButton from '../MainButton'
interface AttentionPageProps { interface AttentionPageProps {
@ -29,9 +30,11 @@ function AttentionPage({ isOpenModal, onCloseSpecialOffer }: AttentionPageProps)
<img className={styles.icon} src="/stop-icon.png" alt="stop" /> <img className={styles.icon} src="/stop-icon.png" alt="stop" />
<Title variant='h2'>{t('aura.attention.title')}</Title> <Title variant='h2'>{t('aura.attention.title')}</Title>
<p className={styles.text}>{t('aura.warming_up.body')}</p> <p className={styles.text}>{t('aura.warming_up.body')}</p>
<div className={styles['checkbox-container']}> <div className={styles['buttons-container']}>
{/* <CheckboxWithText text={t('not_ready_for_information')} onChange={onChangeCheckbox} /> */} {/* <CheckboxWithText text={t('not_ready_for_information')} onChange={onChangeCheckbox} /> */}
<Title variant='h2' className={styles.button} onClick={handleNext}>{t('aura.warming_up.button')}</Title> {/* <Title variant='h2' className={styles.button} onClick={handleNext}>{t('aura.warming_up.button')}</Title> */}
<MainButton onClick={handleNext}>{t('aura.warmin_good.button')}</MainButton>
<MainButton onClick={handleNext}>{t('aura.warmin_bad.button')}</MainButton>
</div> </div>
</section> </section>
) )

View File

@ -17,9 +17,12 @@
font-weight: 700; font-weight: 700;
} }
.checkbox-container { .buttons-container {
width: 80%; width: 100%;
margin: 64px auto 0; margin: 64px auto 0;
display: flex;
flex-direction: column;
gap: 13px;
} }
.button { .button {

View File

@ -1,60 +1,84 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from "react";
import { useNavigate, useLocation } from 'react-router-dom' import { useNavigate, useLocation } from "react-router-dom";
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next";
import routes, { hasCrossButton, hasNavigation, isNotEntrypoint } from '@/routes' import routes, {
import BackButton from './BackButton' hasCrossButton,
import iconUrl from './icon.png' hasNavigation,
import menuUrl from './menu.png' isNotEntrypoint,
import styles from './styles.module.css' } from "@/routes";
import BackButton from "./BackButton";
import iconUrl from "./icon.png";
import menuUrl from "./menu.png";
import styles from "./styles.module.css";
type HeaderProps = { type HeaderProps = {
openMenu?: () => void openMenu?: () => void;
showBack?: boolean showBack?: boolean;
showCross?: boolean showCross?: boolean;
classCross?: CSSModuleClasses | string classCross?: CSSModuleClasses | string;
clickCross?: () => void clickCross?: () => void;
} };
function Header({ openMenu, showBack, showCross, classCross, clickCross = () => {undefined}, ...props }: HeaderProps & React.HTMLAttributes<HTMLDivElement>): JSX.Element { function Header({
const { t } = useTranslation() openMenu,
const navigate = useNavigate() showBack,
const location = useLocation() showCross = true,
classCross,
clickCross = () => {
undefined;
},
...props
}: HeaderProps & React.HTMLAttributes<HTMLDivElement>): JSX.Element {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const [initialPath, setInitialPath] = useState<string | null>(null); const [initialPath, setInitialPath] = useState<string | null>(null);
const [isNavigated, setIsNavigated] = useState<boolean>(false); const [isNavigated, setIsNavigated] = useState<boolean>(false);
const showBackButton = isNotEntrypoint(location.pathname) const showBackButton = isNotEntrypoint(location.pathname);
const showMenuButton = hasNavigation(location.pathname) const showMenuButton = hasNavigation(location.pathname);
const showCrossButton = hasCrossButton(location.pathname) const showCrossButton = hasCrossButton(location.pathname);
useEffect(() => { useEffect(() => {
if (!initialPath) { if (!initialPath) {
setInitialPath(location.pathname) setInitialPath(location.pathname);
} }
if (initialPath && location.pathname !== initialPath && !isNavigated) { if (initialPath && location.pathname !== initialPath && !isNavigated) {
setIsNavigated(true) setIsNavigated(true);
} }
}, [location.pathname, initialPath, isNavigated]) }, [location.pathname, initialPath, isNavigated]);
const goBack = () => { const goBack = () => {
if (initialPath && isNotEntrypoint(initialPath) && !isNavigated) { if (initialPath && isNotEntrypoint(initialPath) && !isNavigated) {
navigate(routes.client.root()) navigate(routes.client.root());
} else { } else {
navigate(-1) navigate(-1);
} }
} };
return ( return (
<header className={styles.header} {...props}> <header className={styles.header} {...props}>
{ (showBackButton || showBack) ? <BackButton className='pa' onClick={goBack} /> : null } {showBackButton || showBack ? (
<div className={styles['header__logo-container']}> <BackButton className="pa" onClick={goBack} />
) : null}
<div className={styles["header__logo-container"]}>
<img src={iconUrl} alt="logo" width="40" height="40" /> <img src={iconUrl} alt="logo" width="40" height="40" />
<span className={styles["header__title"]}>{t('app_name')}</span> <span className={styles["header__title"]}>{t("app_name")}</span>
</div> </div>
{(showCrossButton || showCross) ? <img className={`${styles.cross} ${classCross || ''}`} src="/cross.png" alt="Cross" onClick={clickCross} /> : null} {showCrossButton && showCross ? (
{showMenuButton ? <div className={styles["header__menu-btn"]} onClick={openMenu}> <img
<img src={menuUrl} alt="menu" width="40" height="40" /> className={`${styles.cross} ${classCross || ""}`}
</div> : null} src="/cross.png"
alt="Cross"
onClick={clickCross}
/>
) : null}
{showMenuButton ? (
<div className={styles["header__menu-btn"]} onClick={openMenu}>
<img src={menuUrl} alt="menu" width="40" height="40" />
</div>
) : null}
</header> </header>
) );
} }
export default Header export default Header;

View File

@ -1,52 +1,86 @@
import { useNavigate } from 'react-router-dom' import { useNavigate } from "react-router-dom";
import { useTranslation } from 'react-i18next' import { useTranslation } from "react-i18next";
import Title from '../Title' import Title from "../Title";
import routes from '@/routes' import routes from "@/routes";
import styles from './styles.module.css' import styles from "./styles.module.css";
import ModalTop from '../ModalTop' import ModalTop from "../ModalTop";
import Header from '../Header' import Header from "../Header";
import { useCallback } from 'react' import { useCallback } from "react";
import { useDispatch } from 'react-redux' import { useDispatch, useSelector } from "react-redux";
import { actions } from '@/store' import { actions, selectors } from "@/store";
import MainButton from "../MainButton";
interface ModalTopProps { interface ModalTopProps {
open: boolean open: boolean;
onClose?: () => void onClose?: () => void;
} }
function SpecialWelcomeOffer({ open, onClose }: ModalTopProps): JSX.Element { function SpecialWelcomeOffer({ open, onClose }: ModalTopProps): JSX.Element {
const { t } = useTranslation() const { t } = useTranslation();
const navigate = useNavigate() const navigate = useNavigate();
const dispatch = useDispatch() const dispatch = useDispatch();
const updateSelectedPrice = useCallback((selectedPrice: number | null) => { const selectedPrice = useSelector(selectors.selectSelectedPrice);
dispatch(actions.payment.update({ const halfPrice = (Math.round(selectedPrice || 0) / 2).toFixed(2);
selectedPrice const updateIsDiscount = useCallback((isDiscount: boolean) => {
})) dispatch(
}, [dispatch]) actions.payment.update({
isDiscount
})
);
}, [dispatch]);
const handleNext = () => { const handleNext = () => {
updateSelectedPrice(1) updateIsDiscount(true);
navigate(routes.client.emailEnter()) navigate(routes.client.paymentMethod());
} };
return ( return (
<> <>
{open ? ( {open ? (
<ModalTop open={open} onClose={onClose || handleNext}> <ModalTop open={open} onClose={onClose || handleNext}>
<Header showBack={false} showCross={true} clickCross={onClose || handleNext} /> <Header
showBack={false}
showCross={false}
clickCross={onClose || handleNext}
/>
<div className={styles.content}> <div className={styles.content}>
<span className={styles['welcome-offer']}>{t('special_welcome_offer')}</span> {/* <span className={styles['welcome-offer']}>{t('special_welcome_offer')}</span> */}
<Title variant='h1'>{t('get_100_off')}</Title> <img src="/your-friends.png" alt="Your friends" />
<div className={styles['discount-container']}> <Title variant="h2" className={styles["your-friends"]}>
<span className={styles['red-price']}>$9.99</span> {t("au.friends.window")}
<span className={styles['price']}>$0.00</span> </Title>
<Title variant="h2" className={styles["get-50-only"]}>
{t("au.get50.only")}
</Title>
<div className={styles["discount-container"]}>
{Number(halfPrice) > 0 &&
<>
<span className={styles["red-price"]}>${selectedPrice}</span>{" "}
<span className={styles["price"]}>${halfPrice}</span>
</>
}
{!Number(halfPrice) && <span className={styles["free-trial"]}>{t('au.free_trial_web.7_14')}</span>}
</div> </div>
<button className={styles.button} onClick={onClose || handleNext}>{t('get_discount')}</button> <MainButton
className={styles["button-green"]}
onClick={handleNext}
>
$ {halfPrice} {t("au.try_for.button")}
</MainButton>
<MainButton
// disabled
className={styles["button-black"]}
onClick={() => {
console.log("click");
}}
>
<img className={styles["button-icon"]} src="/leo.png" alt="Leo" />
{t("au.more_llc.button")}
</MainButton>
</div> </div>
</ModalTop> </ModalTop>
): null} ) : null}
</> </>
) );
} }
export default SpecialWelcomeOffer export default SpecialWelcomeOffer;

View File

@ -1,40 +1,73 @@
.content { .content {
padding: 0 24px 64px 24px; padding: 0 24px 64px 24px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
padding-top: 28px; padding-top: 28px;
} }
.welcome-offer { .welcome-offer {
color: #717171; color: #717171;
font-weight: 500; font-weight: 500;
} }
.discount-container { .discount-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 16px; gap: 16px;
font-size: 24px; font-size: 32px;
font-weight: 600; font-weight: 600;
margin-bottom: 32px;
} }
.red-price { .red-price {
color: red; color: red;
text-decoration: line-through; text-decoration: line-through;
} }
.button { .button-green {
background-color: #69e573; background-color: #18d136;
color: #fff; font-weight: 600;
font-size: 21px;
/* color: #fff;
border-radius: 26px; border-radius: 26px;
width: 100%; width: 100%;
max-width: 300px; max-width: 300px;
padding: 12px 0; padding: 12px 0;
border: none; border: none;
font-size: 14px; font-size: 14px;
margin-top: 16px; margin-top: 16px; */
} }
.button-black {
font-weight: 600;
font-size: 21px;
position: relative;
}
.your-friends {
width: 80%;
font-weight: 600;
margin-bottom: 8px;
}
.get-50-only {
margin-bottom: 0;
font-weight: 700;
color: #ff003d;
}
.button-icon {
position: absolute;
width: 48px;
top: 50%;
left: 13px;
transform: translateY(-50%);
}
.free-trial {
font-size: 22px;
font-weight: 600;
}

View File

@ -4,7 +4,7 @@ import user, { actions as userActions, selectUser } from './user'
import form, { actions as formActions, selectors as formSelectors } from './form' import form, { actions as formActions, selectors as formSelectors } from './form'
import aura, { actions as auraActions } from './aura' import aura, { actions as auraActions } from './aura'
import siteConfig, { selectHome, actions as siteConfigActions } from './siteConfig' import siteConfig, { selectHome, actions as siteConfigActions } from './siteConfig'
import payment, { actions as paymentActions } from './payment' import payment, { actions as paymentActions, selectIsDiscount } from './payment'
import subscriptionPlans, { actions as subscriptionPlasActions, selectPlanById } from './subscriptionPlan' import subscriptionPlans, { actions as subscriptionPlasActions, selectPlanById } from './subscriptionPlan'
import status, { actions as userStatusActions, selectStatus } from './status' import status, { actions as userStatusActions, selectStatus } from './status'
import compatibility, { actions as compatibilityActions } from './compatibility' import compatibility, { actions as compatibilityActions } from './compatibility'
@ -44,6 +44,7 @@ export const selectors = {
selectUserCallbacksDescription, selectUserCallbacksDescription,
selectUserCallbacksPrevStat, selectUserCallbacksPrevStat,
selectHome, selectHome,
selectIsDiscount,
...formSelectors, ...formSelectors,
} }
export type RootState = ReturnType<typeof reducer> export type RootState = ReturnType<typeof reducer>

View File

@ -1,28 +1,34 @@
import { createSlice, createSelector } from '@reduxjs/toolkit' import { createSlice, createSelector } from "@reduxjs/toolkit";
import type { PayloadAction } from '@reduxjs/toolkit' import type { PayloadAction } from "@reduxjs/toolkit";
interface IPayment { interface IPayment {
selectedPrice: number | null selectedPrice: number | null;
isDiscount: boolean;
} }
const initialState: IPayment = { const initialState: IPayment = {
selectedPrice: null selectedPrice: null,
} isDiscount: false,
};
const paymentSlice = createSlice({ const paymentSlice = createSlice({
name: 'payment', name: "payment",
initialState, initialState,
reducers: { reducers: {
update(state, action: PayloadAction<Partial<IPayment>>) { update(state, action: PayloadAction<Partial<IPayment>>) {
return { ...state, ...action.payload } return { ...state, ...action.payload };
}, },
}, },
extraReducers: (builder) => builder.addCase('reset', () => initialState), extraReducers: (builder) => builder.addCase("reset", () => initialState),
}) });
export const { actions } = paymentSlice export const { actions } = paymentSlice;
export const selectSelectedPrice = createSelector( export const selectSelectedPrice = createSelector(
(state: { payment: IPayment }) => state.payment.selectedPrice, (state: { payment: IPayment }) => state.payment.selectedPrice,
(payment) => payment (payment) => payment
) );
export default paymentSlice.reducer export const selectIsDiscount = createSelector(
(state: { payment: IPayment }) => state.payment.isDiscount,
(payment) => payment
);
export default paymentSlice.reducer;