fix: refactor, emails list, breath circle
This commit is contained in:
parent
806a5d3050
commit
041112bdaa
@ -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 (
|
||||
<div className={styles['outer-circle']}>
|
||||
|
||||
@ -10,8 +10,8 @@
|
||||
|
||||
.inner-circle {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
background-color: #69a8e7;
|
||||
border-radius: 100%;
|
||||
display: flex;
|
||||
|
||||
@ -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<string>('');
|
||||
const [date, setDate] = useState<string | IDate>('');
|
||||
const [compatCategory, setCompatCategory] = useState(2);
|
||||
@ -25,13 +27,19 @@ function CompatibilityPage(): JSX.Element {
|
||||
const { data } = useApiCall<AICompatCategories.CompatCategory[]>(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<HTMLInputElement>) => {
|
||||
@ -58,7 +66,7 @@ function CompatibilityPage(): JSX.Element {
|
||||
value={name}
|
||||
placeholder={t('name')}
|
||||
onValid={handleValidName}
|
||||
onInvalid={() => setIsDisabled(true)}
|
||||
onInvalid={() => setIsDisabledName(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles['input-container__date-container']}>
|
||||
@ -67,7 +75,7 @@ function CompatibilityPage(): JSX.Element {
|
||||
value={getDateAsString(date)}
|
||||
inputClassName={styles['date-input']}
|
||||
onValid={handleValidDate}
|
||||
onInvalid={() => setIsDisabled(true)}
|
||||
onInvalid={() => setIsDisabledDate(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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<HTMLDivElement[]>([]);
|
||||
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 (
|
||||
<div className={styles.container}>
|
||||
<span className={styles['title']}>{t('people_joined_today', { countPeoples: <strong>{countUsers}</strong> })}</span>
|
||||
<div className={styles['emails-container']}>
|
||||
{emails.map(({email, price}, idx) => (
|
||||
<div ref={(el: HTMLDivElement) => itemsRef.current[idx] = el} key={idx}>
|
||||
<EmailItem email={email} price={price} />
|
||||
</div>
|
||||
<div className={styles['email-item']} ref={(el: HTMLDivElement) => itemsRef.current[idx] = el} key={idx}>
|
||||
<EmailItem email={email} price={price} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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<HTMLElement> = (e) => {
|
||||
const X = e.clientX
|
||||
const Y = e.clientY
|
||||
updateCoordinates(X, Y)
|
||||
handleNext()
|
||||
}
|
||||
|
||||
const touchStart: TouchEventHandler<HTMLElement> = (e) => {
|
||||
const X = e.touches[0].clientX
|
||||
const Y = e.touches[0].clientY
|
||||
updateCoordinates(X, Y)
|
||||
handleNext()
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={`${styles.page} page`} onMouseDown={handleNext} onTouchStart={handleNext}>
|
||||
<section className={`${styles.page} page`} onMouseDown={mouseDown} onTouchStart={touchStart}>
|
||||
<div className={styles.content}>
|
||||
<Title variant='h4' className={styles.title}>{t('touch_screen')}</Title>
|
||||
</div>
|
||||
|
||||
@ -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 (
|
||||
<div onClick={click} className={`${styles.container} ${value === 9 ? styles.popular : ''}`}>
|
||||
<div onClick={click} className={compatClassName()}>
|
||||
{removeAfterDot(_price.format())}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -11,6 +11,12 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #30bf52;
|
||||
border-color: #30bf52;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.popular {
|
||||
border-color: #30bf52;
|
||||
}
|
||||
|
||||
@ -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 (
|
||||
<div className={`${styles.container}`}>
|
||||
{prices.map((price, idx) => (
|
||||
<PriceItem key={idx} value={price.value} id={price.id} click={click} />
|
||||
<PriceItem active={price.id === activeItem} key={idx} value={price.value} id={price.id} click={click} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -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 {
|
||||
<EmailsList />
|
||||
</div>
|
||||
<div className={styles['price-list-container']}>
|
||||
<PriceList click={handleNext} />
|
||||
<PriceList activeItem={selectedPrice} click={handleNext} />
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
.emails-list-container {
|
||||
width: 40%;
|
||||
min-width: 200px;
|
||||
height: 130px;
|
||||
/* height: 136px; */
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
|
||||
@ -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())
|
||||
}
|
||||
|
||||
|
||||
@ -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<typeof reducer>
|
||||
|
||||
28
src/store/payment.ts
Normal file
28
src/store/payment.ts
Normal file
@ -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<Partial<IPayment>>) {
|
||||
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
|
||||
Loading…
Reference in New Issue
Block a user