add: creating profile page logic
10
index.html
@ -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
@ -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",
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/assets/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
src/assets/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 438 KiB |
BIN
src/assets/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
9
src/assets/browserconfig.xml
Normal 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>
|
||||
BIN
src/assets/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
src/assets/favicon.ico
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/mstile-150x150.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
19
src/assets/site.webmanifest
Normal 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"
|
||||
}
|
||||
67
src/components/CreateProfilePage/ProcessFlow.tsx
Normal 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
|
||||
23
src/components/CreateProfilePage/ProcessItem.tsx
Normal 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'>✅</div>
|
||||
: <div className="process-item__loader"><span></span></div>
|
||||
}
|
||||
</div>
|
||||
<div className='process-item__label'>{label}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProcessItem
|
||||
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
91
src/components/CreateProfilePage/styles.css
Normal 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); }
|
||||
}
|
||||
@ -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}>
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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' }),
|
||||
],
|
||||
})
|
||||
|
||||