From 1832036710725de1d32b3f0e78efd056dd5bb1ce Mon Sep 17 00:00:00 2001 From: gofnnp Date: Thu, 17 Aug 2023 05:13:43 +0400 Subject: [PATCH] feat: add special welcome offer page --- src/components/App/index.tsx | 23 +++++++--- src/components/AttentionPage/index.tsx | 9 +++- src/components/DidYouKnowPage/index.tsx | 9 +++- src/components/Header/index.tsx | 21 +++++---- .../Header/{styles.css => styles.module.css} | 9 +++- src/components/ModalTop/index.tsx | 26 +++++++++++ src/components/ModalTop/styles.module.css | 44 +++++++++++++++++++ src/components/SpecialWelcomeOffer/index.tsx | 42 ++++++++++++++++++ .../SpecialWelcomeOffer/styles.module.css | 40 +++++++++++++++++ src/locales/dev.ts | 3 ++ src/routes.ts | 3 ++ src/store/index.ts | 6 ++- 12 files changed, 217 insertions(+), 18 deletions(-) rename src/components/Header/{styles.css => styles.module.css} (86%) create mode 100644 src/components/ModalTop/index.tsx create mode 100644 src/components/ModalTop/styles.module.css create mode 100644 src/components/SpecialWelcomeOffer/index.tsx create mode 100644 src/components/SpecialWelcomeOffer/styles.module.css diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index 510f1a3..a7b401a 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -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(false) + const navigate = useNavigate() + + const closeSpecialOffer = () => { + setIsSpecialOfferOpen(false) + navigate(routes.client.emailEnter()) + } + return ( - }> + }> } /> } /> } /> } /> - } /> + } /> } /> } /> } /> @@ -49,14 +57,19 @@ function App(): JSX.Element { ) } -function Layout(): JSX.Element { +interface LayoutProps { + setIsSpecialOfferOpen: React.Dispatch> +} + +function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element { const location = useLocation() const showNavbar = hasNavigation(location.pathname) const showFooter = hasNoFooter(location.pathname) const [isMenuOpen, setIsMenuOpen] = useState(false) + const changeIsSpecialOfferOpen = () => setIsSpecialOfferOpen(true) return (
-
setIsMenuOpen(true)}/> +
setIsMenuOpen(true)} clickCross={changeIsSpecialOfferOpen}/>
{ showFooter ?
: null } { showNavbar ? setIsMenuOpen(false)} /> : null} diff --git a/src/components/AttentionPage/index.tsx b/src/components/AttentionPage/index.tsx index 984096f..5a808bc 100644 --- a/src/components/AttentionPage/index.tsx +++ b/src/components/AttentionPage/index.tsx @@ -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 (
+ stop {t('attention')}

{t('attention_page_text')}

diff --git a/src/components/DidYouKnowPage/index.tsx b/src/components/DidYouKnowPage/index.tsx index e2eb4b6..fab3327 100644 --- a/src/components/DidYouKnowPage/index.tsx +++ b/src/components/DidYouKnowPage/index.tsx @@ -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 (
+
{t('did_you_know')}

@@ -28,7 +35,7 @@ function DidYouKnowPage(): JSX.Element { {t('learn_about_my_energy')} - {t('skip_for_now')} + {t('skip_for_now')}

) diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index bd1a88c..4a1ba52 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -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): 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(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 ( -
- { showBackButton ? : null } +
+ { (showBackButton || showBack) ? : null } logo - {t('app_name')} - {showMenuButton ?
+ {t('app_name')} + {(showCrossButton || showCross) ? Cross : null} + {showMenuButton ?
menu
: null}
diff --git a/src/components/Header/styles.css b/src/components/Header/styles.module.css similarity index 86% rename from src/components/Header/styles.css rename to src/components/Header/styles.module.css index 38848d1..e41c6b5 100644 --- a/src/components/Header/styles.css +++ b/src/components/Header/styles.module.css @@ -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; +} diff --git a/src/components/ModalTop/index.tsx b/src/components/ModalTop/index.tsx new file mode 100644 index 0000000..79190c8 --- /dev/null +++ b/src/components/ModalTop/index.tsx @@ -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 ( +
+
+ {children} +
+
+ ) +} + +export default ModalTop diff --git a/src/components/ModalTop/styles.module.css b/src/components/ModalTop/styles.module.css new file mode 100644 index 0000000..1516891 --- /dev/null +++ b/src/components/ModalTop/styles.module.css @@ -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); + } +} \ No newline at end of file diff --git a/src/components/SpecialWelcomeOffer/index.tsx b/src/components/SpecialWelcomeOffer/index.tsx new file mode 100644 index 0000000..4ddde0a --- /dev/null +++ b/src/components/SpecialWelcomeOffer/index.tsx @@ -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 ? ( + +
+
+ {t('special_welcome_offer')} + {t('get_100_off')} +
+ $9.99 + $0.00 +
+ +
+ + ): null} + + ) +} + +export default SpecialWelcomeOffer diff --git a/src/components/SpecialWelcomeOffer/styles.module.css b/src/components/SpecialWelcomeOffer/styles.module.css new file mode 100644 index 0000000..a59f1d2 --- /dev/null +++ b/src/components/SpecialWelcomeOffer/styles.module.css @@ -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; +} \ No newline at end of file diff --git a/src/locales/dev.ts b/src/locales/dev.ts index 23617b4..29f7f3f 100644 --- a/src/locales/dev.ts +++ b/src/locales/dev.ts @@ -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", }, } diff --git a/src/routes.ts b/src/routes.ts index f8fad5b..52251e8 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -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(), diff --git a/src/store/index.ts b/src/store/index.ts index 9824419..5bbb6a3 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -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