add: creating profile page logic

This commit is contained in:
Aidar Shaikhutdin @makeweb.space 2023-05-05 13:00:15 +06:00
parent 24278eb9b6
commit 084b1b9993
19 changed files with 305 additions and 16 deletions

View File

@ -2,10 +2,14 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#ffffff" />
<title>Aura Web App</title>
<link rel="apple-touch-icon" sizes="180x180" href="/src/assets/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/src/assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/src/assets/favicon-16x16.png">
<link rel="manifest" href="/src/assets/site.webmanifest">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<title>AURA</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

39
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"react": "^18.2.0",
"react-circular-progressbar": "^2.1.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.0"
},
@ -23,7 +24,8 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"typescript": "^5.0.2",
"vite": "^4.3.2"
"vite": "^4.3.2",
"vite-plugin-copy": "^0.1.6"
}
},
"node_modules/@ampproject/remapping": {
@ -2551,6 +2553,14 @@
"node": ">=0.10.0"
}
},
"node_modules/react-circular-progressbar": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.1.0.tgz",
"integrity": "sha512-xp4THTrod4aLpGy68FX/k1Q3nzrfHUjUe5v6FsdwXBl3YVMwgeXYQKDrku7n/D6qsJA9CuunarAboC2xCiKs1g==",
"peerDependencies": {
"react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@ -2963,6 +2973,18 @@
}
}
},
"node_modules/vite-plugin-copy": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/vite-plugin-copy/-/vite-plugin-copy-0.1.6.tgz",
"integrity": "sha512-bqIaefZOE2Jx8P5wJuHKL5GzCERa/pcwdUQWaocyTNXgalN2xkxXH7LmqRJ34V2OlKF2F9E/zj0zITS7U6PpUg==",
"dev": true,
"dependencies": {
"fast-glob": "^3.2.7"
},
"engines": {
"node": ">=14.8.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -4724,6 +4746,12 @@
"loose-envify": "^1.1.0"
}
},
"react-circular-progressbar": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-circular-progressbar/-/react-circular-progressbar-2.1.0.tgz",
"integrity": "sha512-xp4THTrod4aLpGy68FX/k1Q3nzrfHUjUe5v6FsdwXBl3YVMwgeXYQKDrku7n/D6qsJA9CuunarAboC2xCiKs1g==",
"requires": {}
},
"react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@ -4968,6 +4996,15 @@
"rollup": "^3.21.0"
}
},
"vite-plugin-copy": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/vite-plugin-copy/-/vite-plugin-copy-0.1.6.tgz",
"integrity": "sha512-bqIaefZOE2Jx8P5wJuHKL5GzCERa/pcwdUQWaocyTNXgalN2xkxXH7LmqRJ34V2OlKF2F9E/zj0zITS7U6PpUg==",
"dev": true,
"requires": {
"fast-glob": "^3.2.7"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -11,6 +11,7 @@
},
"dependencies": {
"react": "^18.2.0",
"react-circular-progressbar": "^2.1.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.0"
},
@ -25,6 +26,7 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"typescript": "^5.0.2",
"vite": "^4.3.2"
"vite": "^4.3.2",
"vite-plugin-copy": "^0.1.6"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
src/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/assets/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/assets/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@ -0,0 +1,67 @@
import { useEffect, useState } from 'react'
import ProcessItem from "./ProcessItem"
interface Task {
(): Promise<void>
}
type ProcessItem = {
task: Task
label: string
}
type ProcessFlowProps = {
items: ProcessItem[]
onDone: () => void
}
enum ProcessStatus {
Idle,
Pending,
Done,
}
const createChaining = (tasks: Task[], callback: (idx: number) => void) => {
return tasks.reduce((chain, task, idx) => {
return chain.then(task).then(()=> callback(idx))
}, Promise.resolve())
}
const getMultiplier = (currentIdx: number, length: number): number => {
return Math.max(length - (currentIdx + 1) - 1, 0)
}
const calculateTop = (currentIdx: number, length: number): number => {
const itemHeight = 56
return getMultiplier(currentIdx, length) * itemHeight
}
function ProcessFlow({ items, onDone }: ProcessFlowProps): JSX.Element {
const [status, setStatus] = useState(ProcessStatus.Idle)
const [doneTaskIdx, setDoneTaskIdx] = useState(-1)
const tasks = items.map(({ task }) => task)
useEffect(() => {
if (status !== ProcessStatus.Idle) return
setStatus(ProcessStatus.Pending)
createChaining(tasks, setDoneTaskIdx)
.then(() => setStatus(ProcessStatus.Done))
.then(() => onDone())
}, [status, tasks, onDone])
const toItems = ({ label }: ProcessItem, idx: number): JSX.Element => {
return <ProcessItem
key={idx}
label={label}
top={calculateTop(doneTaskIdx, items.length)}
isDone={idx <= doneTaskIdx}
/>
}
return (
<div className='process-items mt-24'>
{items.map(toItems)}
</div>
)
}
export default ProcessFlow

View File

@ -0,0 +1,23 @@
type ProcessItemProps = {
top: number
label: string
isDone: boolean
}
function ProcessItem({ top, label, isDone }: ProcessItemProps): JSX.Element {
return (
<div className='process-item' style={{ top: top }}>
<div className='process-item__pick'>
{
isDone
? <div className='process-item__icon'>&#9989;</div>
: <div className="process-item__loader"><span></span></div>
}
</div>
<div className='process-item__label'>{label}</div>
</div>
)
}
export default ProcessItem

View File

@ -1,19 +1,44 @@
import { useEffect } from "react"
import { useState } from "react"
import { useNavigate } from "react-router-dom"
import { CircularProgressbar, buildStyles } from 'react-circular-progressbar'
import ProcessFlow from "./ProcessFlow"
import Title from "../Title"
import routes from "../../routes"
import './styles.css'
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
function CreateProfilePage(): JSX.Element {
const navigate = useNavigate()
useEffect(() => {
const timerId = setTimeout(() => navigate(routes.client.emailEnter()), 3000)
return () => clearTimeout(timerId)
}, [navigate])
const [progress, setProgress] = useState(0)
const processItems = [
{ task: () => sleep(3300).then(() => setProgress(35)), label: 'Zodiac data analysis' },
{ task: () => sleep(2550).then(() => setProgress(61)), label: 'Drawing Wallpapers' },
{ task: () => sleep(3789).then(() => setProgress(98)), label: 'Preparing results' },
]
const handleDone = () => Promise.resolve()
.then(() => setProgress(100))
.then(() => sleep(1000))
.then(() => navigate(routes.client.emailEnter()))
return (
<section className='page'>
<Title variant="h2" className="mt-24">Creating your profile</Title>
<div className="progressbar">
<CircularProgressbar
value={progress}
text={`${progress}%`}
strokeWidth={4}
styles={buildStyles({
strokeLinecap: 'butt',
textSize: '12px',
pathColor: '#000',
textColor: '#000',
pathTransitionDuration: 1,
})}
/>
</div>
<ProcessFlow items={processItems} onDone={handleDone} />
</section>
)
}

View File

@ -0,0 +1,91 @@
.progressbar {
width: 100%;
max-width: 250px;
margin: 24px auto;
}
.process-items {
overflow: hidden;
}
.process-item {
display: flex;
font-size: 24px;
font-weight: 400;
margin-bottom: 24px;
line-height: 32px;
position: relative;
transition: top .4s ease-in-out;
}
.process-item__pick {
position: relative;
margin-right: 15px;
width: 32px;
height: 32px;
clear: both;
}
.process-item__icon {
font-size: 32px;
}
.process-item__loader {
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
width: 32px;
height: 32px;
animation: loader-1-1 4.8s linear infinite;
}
@keyframes loader-1-1 {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.process-item__loader span {
display: block;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
height: 32px;
width: 32px;
clip: rect(0, 32px, 32px, 16px);
animation: loader-1-2 1.2s linear infinite;
}
@keyframes loader-1-2 {
0% { transform: rotate(0deg); }
100% { transform: rotate(220deg); }
}
.process-item__loader span::after {
position: absolute;
box-sizing: border-box;
content: "";
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
height: 32px;
width: 32px;
clip: rect(0, 32px, 32px, 16px);
border: 3px solid #000;
border-radius: 50%;
animation: loader-1-3 1.2s cubic-bezier(0.770, 0.000, 0.175, 1.000) infinite;
}
@keyframes loader-1-3 {
0% { transform: rotate(-140deg); }
50% { transform: rotate(-160deg); }
100% { transform: rotate(140deg); }
}

View File

@ -25,11 +25,8 @@ function SubscriptionPage(): JSX.Element {
<>
<UserHeader email={userEmail} />
<section className='page'>
<Title variant='h3'>
Your personalized Aries Wallpaper has been created! Find your happiness now and get an additional individual horoscope based on your energies.
</Title>
<Countdown start={10}/>
<CallToAction />
<Countdown start={10}/>
<Payment items={paymentItems} currency={currency} locale={locale}/>
<MainButton label='Get access' onClick={handleClick} />
<Policy links={links}>

View File

@ -1,5 +1,6 @@
import ReactDOM from 'react-dom/client'
import init from './init'
import 'react-circular-progressbar/dist/styles.css'
import './fonts.css'
import './index.css'

View File

@ -1,7 +1,21 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { copy } from 'vite-plugin-copy'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
build: {
manifest: false,
},
plugins: [
react(),
copy([
{ src: 'src/assets/favicon.ico', dest: 'dist' },
{ src: 'src/assets/browserconfig.xml', dest: 'dist' },
{ src: 'src/assets/mstile-150x150.png', dest: 'dist' },
{ src: 'src/assets/android-chrome-192x192.png', dest: 'dist/assets' },
{ src: 'src/assets/android-chrome-512x512.png', dest: 'dist/assets' },
{ src: 'src/assets/android-chrome-512x512.png', dest: 'dist/assets' },
], { hook: 'writeBundle' }),
],
})