fix: refactor, emails list, breath circle

This commit is contained in:
gofnnp 2023-08-29 02:34:59 +04:00
parent 806a5d3050
commit 041112bdaa
14 changed files with 170 additions and 54 deletions

View File

@ -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']}>

View File

@ -10,8 +10,8 @@
.inner-circle {
position: relative;
width: 200px;
height: 200px;
width: 240px;
height: 240px;
background-color: #69a8e7;
border-radius: 100%;
display: flex;

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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>
)

View File

@ -11,6 +11,12 @@
font-weight: 600;
}
.active {
background-color: #30bf52;
border-color: #30bf52;
color: #fff;
}
.popular {
border-color: #30bf52;
}

View File

@ -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>
)

View File

@ -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>
</>

View File

@ -21,7 +21,7 @@
.emails-list-container {
width: 40%;
min-width: 200px;
height: 130px;
/* height: 136px; */
margin-top: 48px;
}

View File

@ -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())
}

View File

@ -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
View 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