feat: add special welcome offer page

This commit is contained in:
gofnnp 2023-08-17 05:13:43 +04:00
parent 9ce9a90031
commit 1832036710
12 changed files with 217 additions and 18 deletions

View File

@ -1,6 +1,6 @@
import { useState } from 'react'
import {
Routes, Route, Navigate, Outlet, useLocation
Routes, Route, Navigate, Outlet, useLocation, useNavigate
} from 'react-router-dom'
import { useAuth } from '../../auth'
import { useSelector } from 'react-redux'
@ -25,14 +25,22 @@ import AttentionPage from '../AttentionPage'
import FeedbackPage from '../FeedbackPage'
function App(): JSX.Element {
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false)
const navigate = useNavigate()
const closeSpecialOffer = () => {
setIsSpecialOfferOpen(false)
navigate(routes.client.emailEnter())
}
return (
<Routes>
<Route element={<Layout />}>
<Route element={<Layout setIsSpecialOfferOpen={setIsSpecialOfferOpen} />}>
<Route path={routes.client.root()} element={<MainPage />} />
<Route path={routes.client.birthday()} element={<BirthdayPage />} />
<Route path={routes.client.didYouKnow()} element={<DidYouKnowPage />} />
<Route path={routes.client.freePeriodInfo()} element={<FreePeriodInfoPage />} />
<Route path={routes.client.attention()} element={<AttentionPage />} />
<Route path={routes.client.attention()} element={<AttentionPage isOpenModal={isSpecialOfferOpen} onCloseSpecialOffer={closeSpecialOffer} />} />
<Route path={routes.client.feedback()} element={<FeedbackPage />} />
<Route path={routes.client.birthtime()} element={<BirthtimePage />} />
<Route path={routes.client.createProfile()} element={<SkipStep />} />
@ -49,14 +57,19 @@ function App(): JSX.Element {
)
}
function Layout(): JSX.Element {
interface LayoutProps {
setIsSpecialOfferOpen: React.Dispatch<React.SetStateAction<boolean>>
}
function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element {
const location = useLocation()
const showNavbar = hasNavigation(location.pathname)
const showFooter = hasNoFooter(location.pathname)
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false)
const changeIsSpecialOfferOpen = () => setIsSpecialOfferOpen(true)
return (
<div className='container'>
<Header openMenu={() => setIsMenuOpen(true)}/>
<Header openMenu={() => setIsMenuOpen(true)} clickCross={changeIsSpecialOfferOpen}/>
<main className='content'><Outlet /></main>
{ showFooter ? <Footer color={showNavbar ? 'black' : 'white'}/> : null }
{ showNavbar ? <Navbar isOpen={isMenuOpen} closeMenu={() => setIsMenuOpen(false)} /> : null}

View File

@ -4,8 +4,14 @@ import Title from '../Title'
import routes from '../../routes'
import styles from './styles.module.css'
import CheckboxWithText from '../CheckboxWithText'
import SpecialWelcomeOffer from '../SpecialWelcomeOffer'
function AttentionPage(): JSX.Element {
interface AttentionPageProps {
isOpenModal: boolean
onCloseSpecialOffer?: () => void
}
function AttentionPage({ isOpenModal, onCloseSpecialOffer }: AttentionPageProps): JSX.Element {
const { t } = useTranslation()
const navigate = useNavigate()
const handleNext = () => navigate(routes.client.feedback())
@ -18,6 +24,7 @@ function AttentionPage(): JSX.Element {
return (
<section className={`${styles.page} page`}>
<SpecialWelcomeOffer open={isOpenModal} onClose={onCloseSpecialOffer} />
<img className={styles.icon} src="/stop-icon.png" alt="stop" />
<Title variant='h2'>{t('attention')}</Title>
<p className={styles.text}>{t('attention_page_text')}</p>

View File

@ -7,17 +7,24 @@ import styles from './styles.module.css'
import { useSelector } from 'react-redux'
import { selectors } from '../../store'
import { getZodiacSignByDate } from '../../zodiac-sign'
import SpecialWelcomeOffer from '../SpecialWelcomeOffer'
import { useState } from 'react'
function DidYouKnowPage(): JSX.Element {
const { t } = useTranslation()
const navigate = useNavigate()
const handleNext = () => navigate(routes.client.freePeriodInfo())
const [isOpenModal, setIsOpenModal] = useState(false)
const handleSpecialOffer = () => {
setIsOpenModal(true)
}
const birthdate = useSelector(selectors.selectBirthdate)
const zodiacSign = getZodiacSignByDate(birthdate)
return (
<section className={`${styles.page} page`}>
<SpecialWelcomeOffer open={isOpenModal} />
<div className={styles.content}>
<Title variant='h1'>{t('did_you_know')}</Title>
<p className={styles.zodiacInfo}>
@ -28,7 +35,7 @@ function DidYouKnowPage(): JSX.Element {
<MainButton onClick={handleNext}>
{t('learn_about_my_energy')}
</MainButton>
<span className={styles.skip}>{t('skip_for_now')}</span>
<span className={styles.skip} onClick={handleSpecialOffer}>{t('skip_for_now')}</span>
</footer>
</section>
)

View File

@ -1,17 +1,20 @@
import { useState, useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import routes, { hasNavigation, isNotEntrypoint } from '../../routes'
import routes, { hasCrossButton, hasNavigation, isNotEntrypoint } from '../../routes'
import BackButton from './BackButton'
import iconUrl from './icon.png'
import menuUrl from './menu.png'
import './styles.css'
import styles from './styles.module.css'
type HeaderProps = {
openMenu: () => void
openMenu?: () => void
showBack?: boolean
showCross?: boolean
clickCross?: () => void
}
function Header({ openMenu }: HeaderProps): JSX.Element {
function Header({ openMenu, showBack, showCross, clickCross = () => {undefined}, ...props }: HeaderProps & React.HTMLAttributes<HTMLDivElement>): JSX.Element {
const { t } = useTranslation()
const navigate = useNavigate()
const location = useLocation()
@ -19,6 +22,7 @@ function Header({ openMenu }: HeaderProps): JSX.Element {
const [isNavigated, setIsNavigated] = useState<boolean>(false);
const showBackButton = isNotEntrypoint(location.pathname)
const showMenuButton = hasNavigation(location.pathname)
const showCrossButton = hasCrossButton(location.pathname)
useEffect(() => {
if (!initialPath) {
@ -38,11 +42,12 @@ function Header({ openMenu }: HeaderProps): JSX.Element {
}
return (
<header className="header">
{ showBackButton ? <BackButton className="pa" onClick={goBack} /> : null }
<header className={styles.header} {...props}>
{ (showBackButton || showBack) ? <BackButton className='pa' onClick={goBack} /> : null }
<img src={iconUrl} alt="logo" width="40" height="40" />
<span className="header__title">{t('app_name')}</span>
{showMenuButton ? <div className="header__menu-btn" onClick={openMenu}>
<span className={styles["header__title"]}>{t('app_name')}</span>
{(showCrossButton || showCross) ? <img className={styles.cross} 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>

View File

@ -23,11 +23,16 @@
text-transform: uppercase;
}
.header__menu-btn {
.header__menu-btn, .cross {
position: absolute;
top: 5px;
top: 33%;
right: 28px;
width: 40px;
height: 40px;
cursor: pointer;
}
.cross {
height: 33%;
width: auto;
}

View File

@ -0,0 +1,26 @@
import { ReactNode } from 'react'
import styles from './styles.module.css'
import Header from '../Header'
interface ModalProps {
children: ReactNode
open?: boolean
onClose?: () => void
}
function ModalTop({ open, children, onClose }: ModalProps): JSX.Element {
const handleClose = (event: React.MouseEvent) => {
if (event.target !== event.currentTarget) return
onClose?.()
}
if (!open) return <></>
return (
<div className={styles.modal} onClick={handleClose}>
<div className={styles['modal-content']}>
{children}
</div>
</div>
)
}
export default ModalTop

View File

@ -0,0 +1,44 @@
.modal {
background: rgb(14 14 14 / 80%);
backdrop-filter: blur(6px);
height: 100vh;
overflow: hidden;
position: fixed;
top: 0;
width: 100vw;
z-index: 2000;
animation: appearanceBackground .6s ease-in;
}
.modal-content {
border-radius: 0 0 16px 16px;
background-color: #fff;
animation: appearance 0.6s ease-in;
}
header {
position: absolute;
left: 0;
}
@keyframes appearance {
0% {
margin-top: -100%;
}
100% {
margin-top: 0;
}
}
@keyframes appearanceBackground {
0% {
background: transparent;
backdrop-filter: blur(0px);
}
100% {
background: rgb(14 14 14 / 80%);
backdrop-filter: blur(6px);
}
}

View File

@ -0,0 +1,42 @@
import { useNavigate } from 'react-router-dom'
import { useTranslation } from 'react-i18next'
import Title from '../Title'
import routes from '../../routes'
import styles from './styles.module.css'
import ModalTop from '../ModalTop'
import Header from '../Header'
interface ModalTopProps {
open: boolean
onClose?: () => void
}
function SpecialWelcomeOffer({ open, onClose }: ModalTopProps): JSX.Element {
const { t } = useTranslation()
const navigate = useNavigate()
const handleNext = () => {
navigate(routes.client.emailEnter())
}
return (
<>
{open ? (
<ModalTop open={open} onClose={onClose || handleNext}>
<Header showBack={true} showCross={true} clickCross={onClose || handleNext} />
<div className={styles.content}>
<span className={styles['welcome-offer']}>{t('special_welcome_offer')}</span>
<Title variant='h1'>{t('get_100_off')}</Title>
<div className={styles['discount-container']}>
<span className={styles['red-price']}>$9.99</span>
<span className={styles['price']}>$0.00</span>
</div>
<button className={styles.button} onClick={onClose || handleNext}>{t('get_discount')}</button>
</div>
</ModalTop>
): null}
</>
)
}
export default SpecialWelcomeOffer

View File

@ -0,0 +1,40 @@
.content {
padding: 0 24px 64px 24px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding-top: 28px;
}
.welcome-offer {
color: #717171;
font-weight: 500;
}
.discount-container {
display: flex;
flex-direction: row;
gap: 16px;
font-size: 24px;
font-weight: 600;
}
.red-price {
color: red;
text-decoration: line-through;
}
.button {
background-color: #78d180;
color: #fff;
border-radius: 26px;
width: 100%;
max-width: 300px;
padding: 12px 0;
border: none;
font-size: 14px;
margin-top: 16px;
}

View File

@ -62,5 +62,8 @@ export default {
attention_page_text: "There are significant deviations in your energy, this is a fairly rare occurrence and few people have encountered something like this. Therefore, we cannot provide you with a full report as you may not be prepared for this.",
not_ready_for_information: "I understand that I may not be ready for this information.",
feedback: "I couldn`t believe me eyes when I saw what was revealed about my energy; it was truly shocking. Everything that was mentioned really happened in my life, and I understood the reasons and especially who was behind it. Everything was very beneficial and spot on, without any excess or unnecessary details. Thanks for the assistance; this analysis greatly helped me in my life. I recommend it to everyone. Special thanks to Aura!",
get_100_off: "Get 100% off - Today only",
special_welcome_offer: "Special welcome offer!",
get_discount: "Get discount",
},
}

View File

@ -55,6 +55,9 @@ export const withNavigationRoutes = [routes.client.wallpaper()]
export const hasNavigation = (path: string) => withNavigationRoutes.includes(path)
export const hasNoNavigation = (path: string) => !hasNavigation(path)
export const withCrossButtonRoutes = [routes.client.attention()]
export const hasCrossButton = (path: string) => withCrossButtonRoutes.includes(path)
export const withoutFooterRoutes = [
routes.client.didYouKnow(),
routes.client.freePeriodInfo(),

View File

@ -2,18 +2,21 @@ import { combineReducers, configureStore, createAction } from '@reduxjs/toolkit'
import token, { actions as tokenActions, selectToken } from './token'
import user, { actions as userActions, selectUser } from './user'
import form, { actions as formActions, selectors as formSelectors } from './form'
import aura, { actions as auraActions } from './aura'
import subscriptionPlans, { actions as subscriptionPlasActions, selectPlanById } from './subscriptionPlan'
import status, { actions as userStatusActions, selectStatus } from './status'
import { loadStore, backupStore } from './storageHelper'
import { selectAuraCoordinates } from './aura'
const preloadedState = loadStore()
export const reducer = combineReducers({ token, user, form, status, subscriptionPlans })
export const reducer = combineReducers({ token, user, form, status, subscriptionPlans, aura })
export const actions = {
token: tokenActions,
user: userActions,
form: formActions,
status: userStatusActions,
subscriptionPlan: subscriptionPlasActions,
aura: auraActions,
reset: createAction('reset'),
}
export const selectors = {
@ -21,6 +24,7 @@ export const selectors = {
selectUser,
selectStatus,
selectPlanById,
selectAuraCoordinates,
...formSelectors,
}
export type RootState = ReturnType<typeof reducer>