diff --git a/Makefile b/Makefile index 20a283a..d98c0a2 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ install-local: npm install start: - npm run dev + npm run dev -- --host build: npm run build diff --git a/index.html b/index.html index 9a7e241..100cf7a 100644 --- a/index.html +++ b/index.html @@ -10,6 +10,6 @@
- + diff --git a/package-lock.json b/package-lock.json index c117fb9..54e3445 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3a24d5b..65a41ce 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -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; -} diff --git a/src/App.tsx b/src/App.tsx deleted file mode 100644 index c5fea90..0000000 --- a/src/App.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useState } from 'react' -import './App.css' - -function App() { - return ( - <> -

Let's start!

-

What's your date of birth?

- - - - ) -} - -export default App diff --git a/src/assets/media/sf-mono-bold.otf b/src/assets/media/sf-mono-bold.otf new file mode 100644 index 0000000..41317f4 Binary files /dev/null and b/src/assets/media/sf-mono-bold.otf differ diff --git a/src/assets/media/sf-pro-text-bold.woff b/src/assets/media/sf-pro-text-bold.woff new file mode 100644 index 0000000..e97b40c Binary files /dev/null and b/src/assets/media/sf-pro-text-bold.woff differ diff --git a/src/assets/media/sf-pro-text-medium.woff b/src/assets/media/sf-pro-text-medium.woff new file mode 100644 index 0000000..e40fbdc Binary files /dev/null and b/src/assets/media/sf-pro-text-medium.woff differ diff --git a/src/assets/media/sf-pro-text-regular.woff b/src/assets/media/sf-pro-text-regular.woff new file mode 100644 index 0000000..45de7e4 Binary files /dev/null and b/src/assets/media/sf-pro-text-regular.woff differ diff --git a/src/assets/media/sf-pro-text-semibold.woff b/src/assets/media/sf-pro-text-semibold.woff new file mode 100644 index 0000000..1e5ba7c Binary files /dev/null and b/src/assets/media/sf-pro-text-semibold.woff differ diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx new file mode 100644 index 0000000..cccec4e --- /dev/null +++ b/src/components/App/index.tsx @@ -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 ( +
+
+
+
+ + + } /> + } /> + } /> + } /> + } /> + +
+
+
+ ) +} + +export default App diff --git a/src/components/App/styles.css b/src/components/App/styles.css new file mode 100644 index 0000000..932079e --- /dev/null +++ b/src/components/App/styles.css @@ -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; +} diff --git a/src/components/BackButton/index.tsx b/src/components/BackButton/index.tsx new file mode 100644 index 0000000..4e9f22e --- /dev/null +++ b/src/components/BackButton/index.tsx @@ -0,0 +1,15 @@ +import './styles.css' + +function BackButton({ className, ...props }: React.ButtonHTMLAttributes): JSX.Element { + const combinedClassNames = ['back-btn', className].filter(Boolean).join(' ') + return ( +
+ + + + +
+ ) +} + +export default BackButton diff --git a/src/components/BackButton/styles.css b/src/components/BackButton/styles.css new file mode 100644 index 0000000..7b7e7ba --- /dev/null +++ b/src/components/BackButton/styles.css @@ -0,0 +1,9 @@ +.back-btn { + display: flex; + width: 30px; + height: 30px; + left: 28px; + cursor: pointer; + justify-content: center; + align-items: center; +} diff --git a/src/components/BirthdayPage/index.tsx b/src/components/BirthdayPage/index.tsx new file mode 100644 index 0000000..9364231 --- /dev/null +++ b/src/components/BirthdayPage/index.tsx @@ -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 ( + <> + Let's start! + What's your date of birth? + + +
+ + +
+ + ) +} + +export default BirthdayPage diff --git a/src/components/BirthdayPage/styles.css b/src/components/BirthdayPage/styles.css new file mode 100644 index 0000000..4de9053 --- /dev/null +++ b/src/components/BirthdayPage/styles.css @@ -0,0 +1,5 @@ +.footer { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/src/components/BirthtimePage/index.tsx b/src/components/BirthtimePage/index.tsx new file mode 100644 index 0000000..0aa2da7 --- /dev/null +++ b/src/components/BirthtimePage/index.tsx @@ -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 ( + <> + What time were you born? +

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.

+ + + + ) +} + +export default BirthtimePage diff --git a/src/components/BirthtimePage/styles.css b/src/components/BirthtimePage/styles.css new file mode 100644 index 0000000..b34d8d5 --- /dev/null +++ b/src/components/BirthtimePage/styles.css @@ -0,0 +1,7 @@ +.description { + font-size: 20px; + font-weight: 400; + line-height: 140%; + margin-bottom: 24px; + text-align: center; +} diff --git a/src/components/CreateProfilePage/index.tsx b/src/components/CreateProfilePage/index.tsx new file mode 100644 index 0000000..cfb75f7 --- /dev/null +++ b/src/components/CreateProfilePage/index.tsx @@ -0,0 +1,11 @@ +import Title from "../Title" + +function CreateProfilePage(): JSX.Element { + return ( + <> + Creating your profile + + ) +} + +export default CreateProfilePage diff --git a/src/components/DateControl/index.tsx b/src/components/DateControl/index.tsx new file mode 100644 index 0000000..ba433f5 --- /dev/null +++ b/src/components/DateControl/index.tsx @@ -0,0 +1,33 @@ +import './styles.css' + +function DateControl(): JSX.Element { + return ( +
+
+
+

Year

+ +
+
+

Month

+ +
+
+

Day

+ +
+
+
+ ) +} + +export default DateControl diff --git a/src/components/DateControl/styles.css b/src/components/DateControl/styles.css new file mode 100644 index 0000000..5abfaa7 --- /dev/null +++ b/src/components/DateControl/styles.css @@ -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; +} diff --git a/src/components/Header/icon.png b/src/components/Header/icon.png new file mode 100644 index 0000000..f03e262 Binary files /dev/null and b/src/components/Header/icon.png differ diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx new file mode 100644 index 0000000..c3ddb5e --- /dev/null +++ b/src/components/Header/index.tsx @@ -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(null); + const [isNavigated, setIsNavigated] = useState(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 ( +
+ { showBackButton ? : null } + logo +
+ ) +} + +export default Header diff --git a/src/components/Header/styles.css b/src/components/Header/styles.css new file mode 100644 index 0000000..7474cc4 --- /dev/null +++ b/src/components/Header/styles.css @@ -0,0 +1,10 @@ +.header { + align-items: center; + display: flex; + justify-content: center; + background: #eff1fd; + height: 50px; + min-height: 50px; + position: relative; + width: 100%; +} diff --git a/src/components/MainButton/index.tsx b/src/components/MainButton/index.tsx new file mode 100644 index 0000000..1fffb47 --- /dev/null +++ b/src/components/MainButton/index.tsx @@ -0,0 +1,13 @@ +import './styles.css' + +type ButtonProps = { + label: string; +} & React.ButtonHTMLAttributes; + + +function MainButton({ className, label, ...props}: ButtonProps): JSX.Element { + const combinedClassNames = ['main-btn', className].filter(Boolean).join(' ') + return +} + +export default MainButton diff --git a/src/components/MainButton/styles.css b/src/components/MainButton/styles.css new file mode 100644 index 0000000..38c7c16 --- /dev/null +++ b/src/components/MainButton/styles.css @@ -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%; +} diff --git a/src/components/NotFoundPage/index.tsx b/src/components/NotFoundPage/index.tsx new file mode 100644 index 0000000..843fed0 --- /dev/null +++ b/src/components/NotFoundPage/index.tsx @@ -0,0 +1,10 @@ +function NotFoundPage() { + return ( + <> +

Oops!

+

Sorry, an unexpected error has occurred.

+ + ) +} + +export default NotFoundPage diff --git a/src/components/Policy/index.tsx b/src/components/Policy/index.tsx new file mode 100644 index 0000000..94d7282 --- /dev/null +++ b/src/components/Policy/index.tsx @@ -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 {item.content} + default: + throw new Error(`Unknown type: ${item.type}`) + } + } + const content = text.map(toElement) + + return

{ content }

+} + +export default Policy diff --git a/src/components/Policy/styles.css b/src/components/Policy/styles.css new file mode 100644 index 0000000..d44b2e2 --- /dev/null +++ b/src/components/Policy/styles.css @@ -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; +} diff --git a/src/components/Purposes/index.tsx b/src/components/Purposes/index.tsx new file mode 100644 index 0000000..9aaf069 --- /dev/null +++ b/src/components/Purposes/index.tsx @@ -0,0 +1,7 @@ +import './styles.css' + +function Purposes(): JSX.Element { + return For entertaiment purposes only +} + +export default Purposes diff --git a/src/components/Purposes/styles.css b/src/components/Purposes/styles.css new file mode 100644 index 0000000..32f613e --- /dev/null +++ b/src/components/Purposes/styles.css @@ -0,0 +1,6 @@ +.purposes { + color: #8e8e93; + font-size: 12px; + line-height: 18px; + margin-top: 6px; +} diff --git a/src/components/TimeControl/index.tsx b/src/components/TimeControl/index.tsx new file mode 100644 index 0000000..194ce79 --- /dev/null +++ b/src/components/TimeControl/index.tsx @@ -0,0 +1,33 @@ +function TimeControl(): JSX.Element { + return ( +
+
+
+ +
+
+ +
+
+ +
+
+
+ ) +} + +export default TimeControl diff --git a/src/components/Title/index.tsx b/src/components/Title/index.tsx new file mode 100644 index 0000000..38b27c5 --- /dev/null +++ b/src/components/Title/index.tsx @@ -0,0 +1,17 @@ +import { PropsWithChildren, HTMLAttributes } from 'react' +import './styles.css' + +type TitleProps = PropsWithChildren<{ + variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' +} & HTMLAttributes> + + +function Title({ children, variant, className, ...props }: TitleProps): JSX.Element { + const combinedClassNames = ['title', className].filter(Boolean).join(' ') + const Tag = variant ?? 'h1' + return ( + { children } + ) +} + +export default Title diff --git a/src/components/Title/styles.css b/src/components/Title/styles.css new file mode 100644 index 0000000..e27f9dd --- /dev/null +++ b/src/components/Title/styles.css @@ -0,0 +1,6 @@ +.title { + letter-spacing: .2px; + line-height: 150%; + margin-bottom: 24px; + text-align: center; +} diff --git a/src/fonts.css b/src/fonts.css new file mode 100644 index 0000000..8760270 --- /dev/null +++ b/src/fonts.css @@ -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") +} diff --git a/src/index.css b/src/index.css index 2c3fac6..0cdb979 100644 --- a/src/index.css +++ b/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; } diff --git a/src/init.tsx b/src/init.tsx new file mode 100644 index 0000000..771abb7 --- /dev/null +++ b/src/init.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import { BrowserRouter } from 'react-router-dom' +import App from './components/App' + +const init = async () => { + return ( + + + + + + ) +} + +export default init diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..1210f23 --- /dev/null +++ b/src/main.ts @@ -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) diff --git a/src/main.tsx b/src/main.tsx deleted file mode 100644 index 91c03f3..0000000 --- a/src/main.tsx +++ /dev/null @@ -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( - - - , -) diff --git a/src/routes.ts b/src/routes.ts new file mode 100644 index 0000000..8bd8480 --- /dev/null +++ b/src/routes.ts @@ -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