Base markup has been added
This commit is contained in:
parent
1fdc0aae1f
commit
3b52ed4989
2
Makefile
2
Makefile
@ -5,7 +5,7 @@ install-local:
|
||||
npm install
|
||||
|
||||
start:
|
||||
npm run dev
|
||||
npm run dev -- --host
|
||||
|
||||
build:
|
||||
npm run build
|
||||
|
||||
@ -10,6 +10,6 @@
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
118
package-lock.json
generated
118
package-lock.json
generated
@ -9,11 +9,13 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
@ -929,6 +931,20 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.0.tgz",
|
||||
"integrity": "sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/history": {
|
||||
"version": "4.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
||||
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
|
||||
@ -961,6 +977,27 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-router": {
|
||||
"version": "5.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
|
||||
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-router-dom": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
|
||||
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/scheduler": {
|
||||
"version": "0.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
|
||||
@ -2535,6 +2572,36 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.0.tgz",
|
||||
"integrity": "sha512-hTm6KKNpj9SDG4syIWRjCU219O0RZY8RUPobCFt9p+PlF7nnkRgMoh2DieTKvw3F3Mw6zg565HGnSv8BuoY5oQ==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.11.0.tgz",
|
||||
"integrity": "sha512-Q3mK1c/CYoF++J6ZINz7EZzwlgSOZK/kc7lxIA7PhtWhKju4KfF1WHqlx0kVCIFJAWztuYVpXZeljEbds8z4Og==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.6.0",
|
||||
"react-router": "6.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
@ -3512,6 +3579,17 @@
|
||||
"fastq": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"@remix-run/router": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.0.tgz",
|
||||
"integrity": "sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w=="
|
||||
},
|
||||
"@types/history": {
|
||||
"version": "4.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
|
||||
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
|
||||
@ -3544,6 +3622,27 @@
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-router": {
|
||||
"version": "5.1.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
|
||||
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-router-dom": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
|
||||
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/react": "*",
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"@types/scheduler": {
|
||||
"version": "0.16.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
|
||||
@ -4640,6 +4739,23 @@
|
||||
"integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
|
||||
"dev": true
|
||||
},
|
||||
"react-router": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.0.tgz",
|
||||
"integrity": "sha512-hTm6KKNpj9SDG4syIWRjCU219O0RZY8RUPobCFt9p+PlF7nnkRgMoh2DieTKvw3F3Mw6zg565HGnSv8BuoY5oQ==",
|
||||
"requires": {
|
||||
"@remix-run/router": "1.6.0"
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.11.0.tgz",
|
||||
"integrity": "sha512-Q3mK1c/CYoF++J6ZINz7EZzwlgSOZK/kc7lxIA7PhtWhKju4KfF1WHqlx0kVCIFJAWztuYVpXZeljEbds8z4Og==",
|
||||
"requires": {
|
||||
"@remix-run/router": "1.6.0",
|
||||
"react-router": "6.11.0"
|
||||
}
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
|
||||
@ -11,11 +11,13 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.1",
|
||||
"@typescript-eslint/parser": "^5.57.1",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
|
||||
42
src/App.css
42
src/App.css
@ -1,42 +0,0 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
24
src/App.tsx
24
src/App.tsx
@ -1,24 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<h1>Let's start!</h1>
|
||||
<h2>What's your date of birth?</h2>
|
||||
<button>Next</button>
|
||||
<footer>
|
||||
<p>By continuing, you agree to our
|
||||
<a href='https://aura.wit.life/terms'>EULA</a>
|
||||
and
|
||||
<a href='https://aura.wit.life/privacy'>Privacy Notice</a>.
|
||||
Have a question? Reach our support team
|
||||
<a href='https://aura.wit.life/'>here</a>
|
||||
</p>
|
||||
<small>For entertaiment purposes only</small>
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
BIN
src/assets/media/sf-mono-bold.otf
Normal file
BIN
src/assets/media/sf-mono-bold.otf
Normal file
Binary file not shown.
BIN
src/assets/media/sf-pro-text-bold.woff
Normal file
BIN
src/assets/media/sf-pro-text-bold.woff
Normal file
Binary file not shown.
BIN
src/assets/media/sf-pro-text-medium.woff
Normal file
BIN
src/assets/media/sf-pro-text-medium.woff
Normal file
Binary file not shown.
BIN
src/assets/media/sf-pro-text-regular.woff
Normal file
BIN
src/assets/media/sf-pro-text-regular.woff
Normal file
Binary file not shown.
BIN
src/assets/media/sf-pro-text-semibold.woff
Normal file
BIN
src/assets/media/sf-pro-text-semibold.woff
Normal file
Binary file not shown.
31
src/components/App/index.tsx
Normal file
31
src/components/App/index.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import BirthdayPage from '../BirthdayPage'
|
||||
import BirthtimePage from '../BirthtimePage'
|
||||
import CreateProfilePage from '../CreateProfilePage'
|
||||
import NotFoundPage from '../NotFoundPage'
|
||||
import Header from '../Header'
|
||||
import routes from '../../routes'
|
||||
import './styles.css'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className='container'>
|
||||
<Header />
|
||||
<main className='content'>
|
||||
<section className='page'>
|
||||
<Routes>
|
||||
<Route path={routes.client.root()} element={
|
||||
<Navigate to={routes.client.birthday()} />
|
||||
} />
|
||||
<Route path={routes.client.birthday()} element={<BirthdayPage />} />
|
||||
<Route path={routes.client.birthtime()} element={<BirthtimePage />} />
|
||||
<Route path={routes.client.createProfile()} element={<CreateProfilePage />} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
25
src/components/App/styles.css
Normal file
25
src/components/App/styles.css
Normal file
@ -0,0 +1,25 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1 1;
|
||||
overflow: hidden;
|
||||
padding: 15px 32px;
|
||||
}
|
||||
15
src/components/BackButton/index.tsx
Normal file
15
src/components/BackButton/index.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import './styles.css'
|
||||
|
||||
function BackButton({ className, ...props }: React.ButtonHTMLAttributes<HTMLDivElement>): JSX.Element {
|
||||
const combinedClassNames = ['back-btn', className].filter(Boolean).join(' ')
|
||||
return (
|
||||
<div className={combinedClassNames} {...props}>
|
||||
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.7676 12.3203H5.76758" stroke="black" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
<path d="M12.7676 19.3203L5.76758 12.3203L12.7676 5.32031" stroke="black" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BackButton
|
||||
9
src/components/BackButton/styles.css
Normal file
9
src/components/BackButton/styles.css
Normal file
@ -0,0 +1,9 @@
|
||||
.back-btn {
|
||||
display: flex;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
left: 28px;
|
||||
cursor: pointer;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
27
src/components/BirthdayPage/index.tsx
Normal file
27
src/components/BirthdayPage/index.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import routes from '../../routes'
|
||||
import Policy from '../Policy'
|
||||
import Purposes from '../Purposes'
|
||||
import Title from '../Title'
|
||||
import DateControl from '../DateControl'
|
||||
import MainButton from '../MainButton'
|
||||
import './styles.css'
|
||||
|
||||
function BirthdayPage(): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
const handleNext = () => navigate(routes.client.birthtime())
|
||||
return (
|
||||
<>
|
||||
<Title variant='h3' className='mt-24'>Let's start!</Title>
|
||||
<Title variant='h2'>What's your date of birth?</Title>
|
||||
<DateControl />
|
||||
<MainButton label='Next' onClick={handleNext} />
|
||||
<footer className='footer'>
|
||||
<Policy />
|
||||
<Purposes />
|
||||
</footer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BirthdayPage
|
||||
5
src/components/BirthdayPage/styles.css
Normal file
5
src/components/BirthdayPage/styles.css
Normal file
@ -0,0 +1,5 @@
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
21
src/components/BirthtimePage/index.tsx
Normal file
21
src/components/BirthtimePage/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import Title from "../Title"
|
||||
import MainButton from "../MainButton"
|
||||
import TimeControl from "../TimeControl"
|
||||
import routes from "../../routes"
|
||||
import './styles.css'
|
||||
|
||||
function BirthtimePage(): JSX.Element {
|
||||
const navigate = useNavigate();
|
||||
const handleNext = () => navigate(routes.client.createProfile())
|
||||
return (
|
||||
<>
|
||||
<Title variant="h2" className="mt-24">What time were you born?</Title>
|
||||
<p className="description">We use NASA data to determine the exact position of the planets in the sky at the time of your birth to create wallpapers that are just right for you.</p>
|
||||
<TimeControl />
|
||||
<MainButton label='Next' onClick={handleNext} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default BirthtimePage
|
||||
7
src/components/BirthtimePage/styles.css
Normal file
7
src/components/BirthtimePage/styles.css
Normal file
@ -0,0 +1,7 @@
|
||||
.description {
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 140%;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
11
src/components/CreateProfilePage/index.tsx
Normal file
11
src/components/CreateProfilePage/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import Title from "../Title"
|
||||
|
||||
function CreateProfilePage(): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Title variant="h2" className="mt-24">Creating your profile</Title>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateProfilePage
|
||||
33
src/components/DateControl/index.tsx
Normal file
33
src/components/DateControl/index.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import './styles.css'
|
||||
|
||||
function DateControl(): JSX.Element {
|
||||
return (
|
||||
<div className='date-control'>
|
||||
<div className='date-control__container'>
|
||||
<div className="date-control__field">
|
||||
<h3 className='date-control__field-label'>Year</h3>
|
||||
<label className="date-control__input">
|
||||
<input type="number" placeholder=" " max="4" />
|
||||
<p className="date-control__input-placeholder">YYYY</p>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-control__field">
|
||||
<h3 className='date-control__field-label'>Month</h3>
|
||||
<label className="date-control__input">
|
||||
<input type="text" placeholder=" " />
|
||||
<p className="date-control__input-placeholder">MM</p>
|
||||
</label>
|
||||
</div>
|
||||
<div className="date-control__field">
|
||||
<h3 className='date-control__field-label'>Day</h3>
|
||||
<label className="date-control__input">
|
||||
<input type="text" placeholder=" " />
|
||||
<p className="date-control__input-placeholder">DD</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DateControl
|
||||
99
src/components/DateControl/styles.css
Normal file
99
src/components/DateControl/styles.css
Normal file
@ -0,0 +1,99 @@
|
||||
.date-control {
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.date-control__container {
|
||||
grid-gap: 12px;
|
||||
background-color: #fff;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(3,1fr);
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.date-control__field-label {
|
||||
color: #6b7baa;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
margin: 0 0 6px 6px;
|
||||
}
|
||||
|
||||
.date-control__input {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
position: relative;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.date-control__input > input {
|
||||
appearance: none;
|
||||
border-radius: 8px;
|
||||
color: #121620;
|
||||
font-size: 16px;
|
||||
height: 48px;
|
||||
line-height: 18px;
|
||||
max-width: 400px;
|
||||
min-width: 96px;
|
||||
outline: none;
|
||||
padding: 12px 12px 5px;
|
||||
transition: border-color .3s ease;
|
||||
width: 100%;
|
||||
background: #eff2fd;
|
||||
border: 2px solid #dee5f9;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.date-control__input > input:focus {
|
||||
border-color: #066fde;
|
||||
transition-delay: .1s;
|
||||
}
|
||||
|
||||
.date-control__input-placeholder {
|
||||
color: #6b7baa;
|
||||
font-size: 16px;
|
||||
left: 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
transition: top .3s ease,color .3s ease,font-size .3s ease;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.date-control__input input:focus + .date-control__input-placeholder,
|
||||
.date-control__input input:not(:placeholder-shown) + .date-control__input-placeholder {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
top: 12px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.date-control__field-select {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-bottom: 0;
|
||||
appearance: none;
|
||||
border-radius: 8px;
|
||||
color: #121620;
|
||||
height: 48px;
|
||||
line-height: 18px;
|
||||
max-width: 400px;
|
||||
min-width: 96px;
|
||||
outline: none;
|
||||
padding: 12px 12px 5px;
|
||||
transition: border-color .3s ease;
|
||||
background: #eff2fd;
|
||||
border: 2px solid #dee5f9;
|
||||
padding-top: 5px;
|
||||
}
|
||||
BIN
src/components/Header/icon.png
Normal file
BIN
src/components/Header/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
40
src/components/Header/index.tsx
Normal file
40
src/components/Header/index.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate, useLocation } from 'react-router-dom'
|
||||
import routes, { isNotEntrypoint } from '../../routes'
|
||||
import BackButton from '../BackButton'
|
||||
import iconUrl from './icon.png'
|
||||
import './styles.css'
|
||||
|
||||
function Header(): JSX.Element {
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const [initialPath, setInitialPath] = useState<string | null>(null);
|
||||
const [isNavigated, setIsNavigated] = useState<boolean>(false);
|
||||
const showBackButton = isNotEntrypoint(location.pathname)
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialPath) {
|
||||
setInitialPath(location.pathname)
|
||||
}
|
||||
if (initialPath && location.pathname !== initialPath && !isNavigated) {
|
||||
setIsNavigated(true)
|
||||
}
|
||||
}, [location.pathname, initialPath, isNavigated])
|
||||
|
||||
const goBack = () => {
|
||||
if (initialPath && isNotEntrypoint(initialPath) && !isNavigated) {
|
||||
navigate(routes.client.root())
|
||||
} else {
|
||||
navigate(-1)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="header">
|
||||
{ showBackButton ? <BackButton className="pa" onClick={goBack} /> : null }
|
||||
<img src={iconUrl} alt="logo" width="40" height="40" />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
10
src/components/Header/styles.css
Normal file
10
src/components/Header/styles.css
Normal file
@ -0,0 +1,10 @@
|
||||
.header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background: #eff1fd;
|
||||
height: 50px;
|
||||
min-height: 50px;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
13
src/components/MainButton/index.tsx
Normal file
13
src/components/MainButton/index.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import './styles.css'
|
||||
|
||||
type ButtonProps = {
|
||||
label: string;
|
||||
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
|
||||
function MainButton({ className, label, ...props}: ButtonProps): JSX.Element {
|
||||
const combinedClassNames = ['main-btn', className].filter(Boolean).join(' ')
|
||||
return <button className={combinedClassNames} {...props}>{label}</button>
|
||||
}
|
||||
|
||||
export default MainButton
|
||||
22
src/components/MainButton/styles.css
Normal file
22
src/components/MainButton/styles.css
Normal file
@ -0,0 +1,22 @@
|
||||
.main-btn {
|
||||
align-items: center;
|
||||
background: #000;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
justify-content: center;
|
||||
line-height: 20px;
|
||||
max-width: 400px;
|
||||
min-height: 60px;
|
||||
min-width: 250px;
|
||||
padding: 12px 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main-btn:disabled {
|
||||
opacity: 50%;
|
||||
}
|
||||
10
src/components/NotFoundPage/index.tsx
Normal file
10
src/components/NotFoundPage/index.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
function NotFoundPage() {
|
||||
return (
|
||||
<>
|
||||
<h1>Oops!</h1>
|
||||
<p>Sorry, an unexpected error has occurred.</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default NotFoundPage
|
||||
39
src/components/Policy/index.tsx
Normal file
39
src/components/Policy/index.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { ReactNode } from 'react'
|
||||
import './styles.css'
|
||||
|
||||
enum PolicyContentType {
|
||||
Text = 'text',
|
||||
Link = 'link',
|
||||
}
|
||||
|
||||
type PolicyContent = {
|
||||
type: PolicyContentType
|
||||
content: string
|
||||
href?: string
|
||||
}
|
||||
|
||||
function Policy(): JSX.Element {
|
||||
const text: PolicyContent[] = [
|
||||
{ type: PolicyContentType.Text, content: 'By continuing, you agree to our ' },
|
||||
{ type: PolicyContentType.Link, content: 'EULA', href: 'https://aura.wit.life/terms' },
|
||||
{ type: PolicyContentType.Text, content: ' and ' },
|
||||
{ type: PolicyContentType.Link, content: 'Privacy Notice', href: 'https://aura.wit.life/privacy' },
|
||||
{ type: PolicyContentType.Text, content: '. Have a question? Reach our support team ' },
|
||||
{ type: PolicyContentType.Link, content: 'here', href: 'https://aura.wit.life/' },
|
||||
]
|
||||
const toElement = (item: PolicyContent, idx: number) => {
|
||||
switch (item.type) {
|
||||
case 'text':
|
||||
return item.content
|
||||
case 'link':
|
||||
return <a key={idx} href={item.href} target="_blank" rel="noreferrer nofollow">{item.content}</a>
|
||||
default:
|
||||
throw new Error(`Unknown type: ${item.type}`)
|
||||
}
|
||||
}
|
||||
const content = text.map<ReactNode>(toElement)
|
||||
|
||||
return <div className="policy"><p>{ content }</p></div>
|
||||
}
|
||||
|
||||
export default Policy
|
||||
20
src/components/Policy/styles.css
Normal file
20
src/components/Policy/styles.css
Normal file
@ -0,0 +1,20 @@
|
||||
.policy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.policy p,
|
||||
.policy a {
|
||||
color: #121620;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.policy a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
7
src/components/Purposes/index.tsx
Normal file
7
src/components/Purposes/index.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import './styles.css'
|
||||
|
||||
function Purposes(): JSX.Element {
|
||||
return <small className="purposes">For entertaiment purposes only</small>
|
||||
}
|
||||
|
||||
export default Purposes
|
||||
6
src/components/Purposes/styles.css
Normal file
6
src/components/Purposes/styles.css
Normal file
@ -0,0 +1,6 @@
|
||||
.purposes {
|
||||
color: #8e8e93;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
33
src/components/TimeControl/index.tsx
Normal file
33
src/components/TimeControl/index.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
function TimeControl(): JSX.Element {
|
||||
return (
|
||||
<div className='date-control'>
|
||||
<div className='date-control__container'>
|
||||
<div className="date-control__field">
|
||||
<select className="date-control__field-select">
|
||||
{Array.from(Array(12).keys()).map((hour) => (
|
||||
<option key={hour} value={hour + 1}>{hour + 1}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="date-control__field">
|
||||
<select className="date-control__field-select">
|
||||
{Array.from(Array(60).keys()).map((minute) => {
|
||||
const formattedMinute = String(minute).padStart(2, '0');
|
||||
return (
|
||||
<option key={minute} value={minute}>{formattedMinute}</option>
|
||||
);
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<div className="date-control__field">
|
||||
<select className="date-control__field-select">
|
||||
<option value="AM">AM</option>
|
||||
<option value="PM">PM</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimeControl
|
||||
17
src/components/Title/index.tsx
Normal file
17
src/components/Title/index.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import { PropsWithChildren, HTMLAttributes } from 'react'
|
||||
import './styles.css'
|
||||
|
||||
type TitleProps = PropsWithChildren<{
|
||||
variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
||||
} & HTMLAttributes<HTMLElement>>
|
||||
|
||||
|
||||
function Title({ children, variant, className, ...props }: TitleProps): JSX.Element {
|
||||
const combinedClassNames = ['title', className].filter(Boolean).join(' ')
|
||||
const Tag = variant ?? 'h1'
|
||||
return (
|
||||
<Tag className={combinedClassNames} {...props}>{ children }</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
export default Title
|
||||
6
src/components/Title/styles.css
Normal file
6
src/components/Title/styles.css
Normal file
@ -0,0 +1,6 @@
|
||||
.title {
|
||||
letter-spacing: .2px;
|
||||
line-height: 150%;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
38
src/fonts.css
Normal file
38
src/fonts.css
Normal file
@ -0,0 +1,38 @@
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: SF Pro Text;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(./assets/media/sf-pro-text-regular.woff) format("woff")
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: SF Pro Text;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url(./assets/media/sf-pro-text-medium.woff) format("woff")
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: SF Pro Text;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url(./assets/media/sf-pro-text-semibold.woff) format("woff")
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: SF Pro Text;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url(./assets/media/sf-pro-text-bold.woff) format("woff")
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-display: swap;
|
||||
font-family: "sf mono bold, sans-serif";
|
||||
font-style: normal;
|
||||
src: url(./assets/media/sf-mono-bold.otf) format("opentype")
|
||||
}
|
||||
137
src/index.css
137
src/index.css
@ -1,69 +1,94 @@
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: SF Pro Text, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
place-items: center;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
h4 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
button,h4 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
vertical-align: initial;
|
||||
}
|
||||
|
||||
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body,html {
|
||||
height: 100%;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.height-initial {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
ol,ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div[class^=divider] {
|
||||
font-family: SF Pro Text Regular,sans-serif
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-color: #cde8f9 #fff;
|
||||
scrollbar-width: auto;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #cde8f9;
|
||||
border: 3px solid #fff;
|
||||
border-radius: 10px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
a,button,div,input,select,textarea {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.mt-24 {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.pa {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
15
src/init.tsx
Normal file
15
src/init.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import App from './components/App'
|
||||
|
||||
const init = async () => {
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
)
|
||||
}
|
||||
|
||||
export default init
|
||||
21
src/main.ts
Normal file
21
src/main.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import init from './init'
|
||||
import './fonts.css'
|
||||
import './index.css'
|
||||
|
||||
const getRootElement = (id: string): HTMLElement => {
|
||||
const root = document.getElementById(id)
|
||||
|
||||
if (root) return root;
|
||||
|
||||
const element = document.createElement('div')
|
||||
element.id = id
|
||||
document.body.appendChild(element)
|
||||
|
||||
return element
|
||||
}
|
||||
|
||||
const vdom = await init()
|
||||
const rootElement = getRootElement('root')
|
||||
|
||||
ReactDOM.createRoot(rootElement).render(vdom)
|
||||
10
src/main.tsx
10
src/main.tsx
@ -1,10 +0,0 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
24
src/routes.ts
Normal file
24
src/routes.ts
Normal file
@ -0,0 +1,24 @@
|
||||
const host = '';
|
||||
const prefix = 'api/v1';
|
||||
|
||||
const routes = {
|
||||
client: {
|
||||
root: () => [host, ''].join('/'),
|
||||
email: () => [host, 'email'].join('/'),
|
||||
birthday: () => [host, 'birthday'].join('/'),
|
||||
birthtime: () => [host, 'birthtime'].join('/'),
|
||||
subscription: () => [host, 'subscription'].join('/'),
|
||||
createProfile: () => [host, 'profile', 'create'].join('/'),
|
||||
},
|
||||
server: {
|
||||
locales: () => [host, prefix, 'locales.json'].join('/'),
|
||||
translations: () => [host, prefix, 't.json'].join('/'),
|
||||
userRegistration: () => [host, prefix, 'user', 'registration.json'].join('/'),
|
||||
},
|
||||
}
|
||||
|
||||
export const entrypoints = [routes.client.root(), routes.client.birthday()]
|
||||
export const isEntrypoint = (path: string) => entrypoints.includes(path)
|
||||
export const isNotEntrypoint = (path: string) => !isEntrypoint(path)
|
||||
|
||||
export default routes
|
||||
Loading…
Reference in New Issue
Block a user