diff --git a/src/components/BreathCircle/index.tsx b/src/components/BreathCircle/index.tsx index 0461cd5..02bde89 100644 --- a/src/components/BreathCircle/index.tsx +++ b/src/components/BreathCircle/index.tsx @@ -11,6 +11,7 @@ function BreathCircle(): JSX.Element { const navigate = useNavigate() const [text, setText] = useState(t('')) const [render, setRender] = useState(false) + const [counter, setCounter] = useState(0) useEffect(() => { @@ -29,10 +30,13 @@ function BreathCircle(): JSX.Element { return sleep(4000) }) .then(() => { - handleNext() + setCounter((prevState) => prevState + 1) + if (counter === 3) { + handleNext() + } setRender((prevState) => !prevState) }) - }, [t, render, navigate]) + }, [t, render, navigate, counter]) return (
diff --git a/src/components/BreathCircle/styles.module.css b/src/components/BreathCircle/styles.module.css index 57e49ab..dd493ba 100644 --- a/src/components/BreathCircle/styles.module.css +++ b/src/components/BreathCircle/styles.module.css @@ -10,8 +10,8 @@ .inner-circle { position: relative; - width: 200px; - height: 200px; + width: 240px; + height: 240px; background-color: #69a8e7; border-radius: 100%; display: flex; diff --git a/src/components/Compatibility/index.tsx b/src/components/Compatibility/index.tsx index 135efe0..add1a12 100644 --- a/src/components/Compatibility/index.tsx +++ b/src/components/Compatibility/index.tsx @@ -10,7 +10,9 @@ import { AICompatCategories, useApi, useApiCall } from "@/api"; function CompatibilityPage(): JSX.Element { const { t, i18n } = useTranslation(); - const [isDisabled, setIsDisabled] = useState(false); + const [isDisabled, setIsDisabled] = useState(true); + const [isDisabledName, setIsDisabledName] = useState(true); + const [isDisabledDate, setIsDisabledDate] = useState(true); const [name, setName] = useState(''); const [date, setDate] = useState(''); const [compatCategory, setCompatCategory] = useState(2); @@ -25,13 +27,19 @@ function CompatibilityPage(): JSX.Element { const { data } = useApiCall(loadData) const handleValidName = (name: string) => { - setIsDisabled(name === ''); + setIsDisabledName(!name.length); setName(name) + checkAllDisabled() } const handleValidDate = (date: IDate | string) => { - setIsDisabled(date === ''); + setIsDisabledDate(date === ''); setDate(date) + checkAllDisabled() + } + + const checkAllDisabled = () => { + setIsDisabled(isDisabledName || isDisabledDate); } const changeCompatCategory = (event: React.ChangeEvent) => { @@ -58,7 +66,7 @@ function CompatibilityPage(): JSX.Element { value={name} placeholder={t('name')} onValid={handleValidName} - onInvalid={() => setIsDisabled(true)} + onInvalid={() => setIsDisabledName(true)} />
@@ -67,7 +75,7 @@ function CompatibilityPage(): JSX.Element { value={getDateAsString(date)} inputClassName={styles['date-input']} onValid={handleValidDate} - onInvalid={() => setIsDisabled(true)} + onInvalid={() => setIsDisabledDate(true)} />
diff --git a/src/components/EmailsList/index.tsx b/src/components/EmailsList/index.tsx index 6cb100d..29604d4 100644 --- a/src/components/EmailsList/index.tsx +++ b/src/components/EmailsList/index.tsx @@ -4,25 +4,45 @@ import styles from './styles.module.css' import { useTranslation } from 'react-i18next' import { useEffect, useRef, useState } from 'react' -const emails: IEmailItem[] = [] +// const getEmails = (): IEmailItem[] => { +// const emails: IEmailItem[] = [] + +// for (let i = 0; i < 2; i++) { +// const subList = [] +// for (let j = 0; j < 4; j++) { +// subList.push({ +// email: getRandomName(), +// price: 9 +// }) +// } +// subList[getRandomArbitrary(0, 4)].price = [5, 13.67][getRandomArbitrary(0, 2)] +// emails.push(...subList) +// } -for (let i = 0; i < 25; i++) { - const sublist = [] - for (let j = 0; j < 4; j++) { - sublist.push({ +// return emails +// } + +const getEmails = (): IEmailItem[] => { + const emails: IEmailItem[] = [] + + for (let index = 0; index < 5; index++) { + emails.push({ email: getRandomName(), - price: 9 + price: [9, 9, 9, 9, 9, 5, 13.67][getRandomArbitrary(0, 7)] }) + } - sublist[getRandomArbitrary(0, 4)].price = [5, 13.67][getRandomArbitrary(0, 2)] - emails.push(...sublist) + + return emails } + function EmailsList(): JSX.Element { const { t } = useTranslation() const [countUsers, setCountUsers] = useState(752) + const [emails, setEmails] = useState(getEmails()) + const [elementIdx, setElementIdx] = useState(0) const itemsRef = useRef([]); - let elementIdx = 0 useEffect(() => { const randomDelay = getRandomArbitrary(3000, 5000) @@ -30,28 +50,39 @@ function EmailsList(): JSX.Element { setCountUsers((prevState) => prevState + 1) }, randomDelay) return () => clearTimeout(countUsersTimeOut) - }, [countUsers, elementIdx]) + }, [countUsers]) useEffect(() => { + const randomDelay = getRandomArbitrary(500, 5000) + const itemsRefInterval = setInterval(() => { - + if (itemsRef.current[elementIdx - 1]) { + itemsRef.current[elementIdx - 1].remove() + } if (itemsRef.current[elementIdx]?.style) { - itemsRef.current[elementIdx].className = styles.hidden + itemsRef.current[elementIdx].classList.add(styles.hidden) } - elementIdx++ - }, 3000) - return () => clearTimeout(itemsRefInterval) - }, [elementIdx]) - + setEmails((prevState) => { + const array = prevState.slice(0) + array.push({ + email: getRandomName(), + price: [9, 9, 9, 9, 9, 5, 13.67][getRandomArbitrary(0, 7)] + }) + return array + }) + setElementIdx((prevState) => prevState + 1) + }, randomDelay) + return () => clearInterval(itemsRefInterval) + }, [emails, elementIdx]) return (
{t('people_joined_today', { countPeoples: {countUsers} })}
{emails.map(({email, price}, idx) => ( -
itemsRef.current[idx] = el} key={idx}> - -
+
itemsRef.current[idx] = el} key={idx}> + +
))}
diff --git a/src/components/EmailsList/styles.module.css b/src/components/EmailsList/styles.module.css index 8d2d43b..caa1086 100644 --- a/src/components/EmailsList/styles.module.css +++ b/src/components/EmailsList/styles.module.css @@ -13,22 +13,28 @@ font-size: 14px; font-weight: 500; color: #494747; + text-align: center; } .emails-container { width: 100%; - flex-grow: 3; + height: 108px; padding: 8px 0; display: flex; - flex-direction: column; + flex-direction: column-reverse; align-items: center; overflow: hidden; gap: 10px; } -.hidden { - transition: all 1s ease; - margin-top: -26px; - opacity: 0; - will-change: margin-top, opacity; +.email-item { + margin-top: 0; + opacity: 1; + will-change: margin-top, opacity; +} + +.hidden { + transition: all .5s ease; + margin-bottom: -26px; + opacity: 0; } diff --git a/src/components/FreePeriodInfoPage/index.tsx b/src/components/FreePeriodInfoPage/index.tsx index 959506d..c186bf1 100644 --- a/src/components/FreePeriodInfoPage/index.tsx +++ b/src/components/FreePeriodInfoPage/index.tsx @@ -3,7 +3,7 @@ import { useTranslation } from 'react-i18next' import Title from '../Title' import routes from '@/routes' import styles from './styles.module.css' -import { useCallback } from 'react' +import { MouseEventHandler, TouchEventHandler, useCallback } from 'react' import { useDispatch } from 'react-redux' import { actions } from '@/store' @@ -19,15 +19,27 @@ function FreePeriodInfoPage(): JSX.Element { } })) }, [dispatch]) - const handleNext = (e: any) => { - const X = e.clientX || Math.round(e.touches[0].clientX) - const Y = e.clientY || Math.round(e.touches[0].clientY) - updateCoordinates(X, Y) + + const handleNext = () => { navigate(routes.client.createProfile()) } + const mouseDown: MouseEventHandler = (e) => { + const X = e.clientX + const Y = e.clientY + updateCoordinates(X, Y) + handleNext() + } + + const touchStart: TouchEventHandler = (e) => { + const X = e.touches[0].clientX + const Y = e.touches[0].clientY + updateCoordinates(X, Y) + handleNext() + } + return ( -
+
{t('touch_screen')}
diff --git a/src/components/PriceItem/index.tsx b/src/components/PriceItem/index.tsx index 95adcb1..9279bb9 100644 --- a/src/components/PriceItem/index.tsx +++ b/src/components/PriceItem/index.tsx @@ -22,15 +22,21 @@ const removeAfterDot = (value: string): string => { } interface PriceItemProps { - click: () => void + active: boolean + click: () => void } -function PriceItem({ id, value, click }: IPrice & PriceItemProps): JSX.Element { - console.log(id); +function PriceItem({ id, value, active, click }: IPrice & PriceItemProps): JSX.Element { const _price = new Price(roundToWhole(value), currency, locale) + const compatClassName = () => { + const isPopular = id === 3 + const isActive = active + return `${styles.container} ${isPopular ? styles.popular : ''} ${isActive ? styles.active : ''}` + } + return ( -
+
{removeAfterDot(_price.format())}
) diff --git a/src/components/PriceItem/styles.module.css b/src/components/PriceItem/styles.module.css index 6e67676..b432182 100644 --- a/src/components/PriceItem/styles.module.css +++ b/src/components/PriceItem/styles.module.css @@ -11,6 +11,12 @@ font-weight: 600; } +.active { + background-color: #30bf52; + border-color: #30bf52; + color: #fff; +} + .popular { border-color: #30bf52; } diff --git a/src/components/PriceList/index.tsx b/src/components/PriceList/index.tsx index cd17d65..4e450e1 100644 --- a/src/components/PriceList/index.tsx +++ b/src/components/PriceList/index.tsx @@ -26,16 +26,16 @@ const prices: IPrice[] = [ ] interface PriceListProps { - click: () => void + activeItem: number | null + click: () => void } -function PriceList({click}: PriceListProps): JSX.Element { - +function PriceList({click, activeItem}: PriceListProps): JSX.Element { return (
{prices.map((price, idx) => ( - + ))}
) diff --git a/src/components/PriceListPage/index.tsx b/src/components/PriceListPage/index.tsx index a964ec6..3b58358 100644 --- a/src/components/PriceListPage/index.tsx +++ b/src/components/PriceListPage/index.tsx @@ -12,11 +12,12 @@ import PriceList from '../PriceList' function PriceListPage(): JSX.Element { const { t } = useTranslation() const navigate = useNavigate() + const selectedPrice = useSelector(selectors.selectSelectedPrice) + const email = useSelector(selectors.selectEmail) const handleNext = () => { navigate(routes.client.breath()) } - return ( <> @@ -28,7 +29,7 @@ function PriceListPage(): JSX.Element {
- +
diff --git a/src/components/PriceListPage/styles.module.css b/src/components/PriceListPage/styles.module.css index c92e3e5..d5713bd 100644 --- a/src/components/PriceListPage/styles.module.css +++ b/src/components/PriceListPage/styles.module.css @@ -21,7 +21,7 @@ .emails-list-container { width: 40%; min-width: 200px; - height: 130px; + /* height: 136px; */ margin-top: 48px; } diff --git a/src/components/SpecialWelcomeOffer/index.tsx b/src/components/SpecialWelcomeOffer/index.tsx index 4b42420..e699a74 100644 --- a/src/components/SpecialWelcomeOffer/index.tsx +++ b/src/components/SpecialWelcomeOffer/index.tsx @@ -5,6 +5,9 @@ import routes from '@/routes' import styles from './styles.module.css' import ModalTop from '../ModalTop' import Header from '../Header' +import { useCallback } from 'react' +import { useDispatch } from 'react-redux' +import { actions } from '@/store' interface ModalTopProps { open: boolean @@ -14,7 +17,14 @@ interface ModalTopProps { function SpecialWelcomeOffer({ open, onClose }: ModalTopProps): JSX.Element { const { t } = useTranslation() const navigate = useNavigate() + const dispatch = useDispatch() + const updateSelectedPrice = useCallback((selectedPrice: number | null) => { + dispatch(actions.payment.update({ + selectedPrice + })) + }, [dispatch]) const handleNext = () => { + updateSelectedPrice(1) navigate(routes.client.emailEnter()) } diff --git a/src/store/index.ts b/src/store/index.ts index 5bbb6a3..caa7b88 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -3,13 +3,15 @@ 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 payment, { actions as paymentActions } from './payment' 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' +import { selectSelectedPrice } from './payment' const preloadedState = loadStore() -export const reducer = combineReducers({ token, user, form, status, subscriptionPlans, aura }) +export const reducer = combineReducers({ token, user, form, status, subscriptionPlans, aura, payment }) export const actions = { token: tokenActions, user: userActions, @@ -17,6 +19,7 @@ export const actions = { status: userStatusActions, subscriptionPlan: subscriptionPlasActions, aura: auraActions, + payment: paymentActions, reset: createAction('reset'), } export const selectors = { @@ -25,6 +28,7 @@ export const selectors = { selectStatus, selectPlanById, selectAuraCoordinates, + selectSelectedPrice, ...formSelectors, } export type RootState = ReturnType diff --git a/src/store/payment.ts b/src/store/payment.ts new file mode 100644 index 0000000..8cf8135 --- /dev/null +++ b/src/store/payment.ts @@ -0,0 +1,28 @@ +import { createSlice, createSelector } from '@reduxjs/toolkit' +import type { PayloadAction } from '@reduxjs/toolkit' + +interface IPayment { + selectedPrice: number | null +} + +const initialState: IPayment = { + selectedPrice: null +} + +const paymentSlice = createSlice({ + name: 'payment', + initialState, + reducers: { + update(state, action: PayloadAction>) { + return { ...state, ...action.payload } + }, + }, + extraReducers: (builder) => builder.addCase('reset', () => initialState), +}) + +export const { actions } = paymentSlice +export const selectSelectedPrice = createSelector( + (state: { payment: IPayment }) => state.payment.selectedPrice, + (payment) => payment +) +export default paymentSlice.reducer