From 4b4cfcbd9760f81bacd2aa35698b0fc9cdb66739 Mon Sep 17 00:00:00 2001 From: Ramir Vorobyev Date: Mon, 4 Nov 2024 03:06:01 +0300 Subject: [PATCH] Frontend add finished --- frontend/public/vite.svg | 1 + frontend/src/app/index.scss | 40 ++++ frontend/src/app/index.tsx | 20 ++ frontend/src/app/providers/index.ts | 1 + frontend/src/app/providers/userProvider.tsx | 38 ++++ frontend/src/app/router/router.tsx | 22 ++ frontend/src/app/ui/app.tsx | 36 ++++ frontend/src/app/ui/style.module.scss | 4 + frontend/src/assets/react.svg | 1 + frontend/src/entities/index.ts | 1 + frontend/src/entities/user/api.ts | 24 +++ frontend/src/entities/user/index.ts | 2 + frontend/src/entities/user/model.ts | 14 ++ frontend/src/features/index.ts | 2 + frontend/src/features/like/api/api.ts | 24 +++ frontend/src/features/like/index.ts | 1 + frontend/src/features/report/api/api.ts | 13 ++ frontend/src/features/report/index.ts | 2 + frontend/src/features/report/ui/report.tsx | 21 ++ .../src/features/report/ui/style.module.scss | 12 ++ frontend/src/pages/auth/api/api.ts | 11 + frontend/src/pages/auth/model/types.ts | 19 ++ frontend/src/pages/auth/ui/auth.tsx | 189 ++++++++++++++++++ frontend/src/pages/auth/ui/style.module.scss | 55 +++++ frontend/src/pages/edit/ui/edit.tsx | 3 + frontend/src/pages/edit/ui/style.module.scss | 0 frontend/src/pages/feed/ui/feed.tsx | 141 +++++++++++++ frontend/src/pages/feed/ui/style.module.scss | 102 ++++++++++ frontend/src/pages/history/ui/history.tsx | 3 + .../src/pages/history/ui/style.module.scss | 0 frontend/src/pages/index.ts | 5 + .../src/shared/assets/fonts/le-murmure.otf | Bin 0 -> 76856 bytes frontend/src/shared/assets/index.ts | 20 ++ frontend/src/shared/assets/logo/2107.png | Bin 0 -> 54564 bytes frontend/src/shared/assets/logo/2107blue.png | Bin 0 -> 20298 bytes frontend/src/shared/assets/logo/2107pin.png | Bin 0 -> 20297 bytes frontend/src/shared/assets/svg/cancel.svg | 10 + frontend/src/shared/assets/svg/edit.svg | 6 + frontend/src/shared/assets/svg/feed.svg | 8 + frontend/src/shared/assets/svg/heart.svg | 4 + frontend/src/shared/assets/svg/history.svg | 4 + frontend/src/shared/clients.ts | 20 ++ frontend/src/shared/config.ts | 2 + frontend/src/shared/index.ts | 4 + frontend/src/shared/ui/button/index.tsx | 12 ++ .../src/shared/ui/button/style.module.scss | 27 +++ frontend/src/shared/ui/card/index.tsx | 73 +++++++ frontend/src/shared/ui/card/style.module.scss | 137 +++++++++++++ frontend/src/shared/ui/index.ts | 6 + .../src/shared/ui/input/img-input/index.tsx | 22 ++ .../ui/input/img-input/style.module.scss | 35 ++++ frontend/src/shared/ui/input/index.ts | 3 + frontend/src/shared/ui/input/input.tsx | 10 + .../src/shared/ui/input/style.module.scss | 27 +++ .../src/shared/ui/input/textarea/index.tsx | 10 + .../ui/input/textarea/style.module.scss | 23 +++ frontend/src/shared/ui/navbar/index.tsx | 28 +++ .../src/shared/ui/navbar/style.module.scss | 42 ++++ frontend/src/shared/ui/notify/index.ts | 20 ++ frontend/src/shared/ui/select/index.tsx | 21 ++ .../src/shared/ui/select/style.module.scss | 25 +++ frontend/src/vite-env.d.ts | 1 + frontend/src/widgets/crop/crop.tsx | 65 ++++++ frontend/src/widgets/crop/style.module.scss | 17 ++ frontend/src/widgets/crop/toImg.tsx | 74 +++++++ frontend/src/widgets/index.ts | 1 + 66 files changed, 1564 insertions(+) create mode 100644 frontend/public/vite.svg create mode 100644 frontend/src/app/index.scss create mode 100644 frontend/src/app/index.tsx create mode 100644 frontend/src/app/providers/index.ts create mode 100644 frontend/src/app/providers/userProvider.tsx create mode 100644 frontend/src/app/router/router.tsx create mode 100644 frontend/src/app/ui/app.tsx create mode 100644 frontend/src/app/ui/style.module.scss create mode 100644 frontend/src/assets/react.svg create mode 100644 frontend/src/entities/index.ts create mode 100644 frontend/src/entities/user/api.ts create mode 100644 frontend/src/entities/user/index.ts create mode 100644 frontend/src/entities/user/model.ts create mode 100644 frontend/src/features/index.ts create mode 100644 frontend/src/features/like/api/api.ts create mode 100644 frontend/src/features/like/index.ts create mode 100644 frontend/src/features/report/api/api.ts create mode 100644 frontend/src/features/report/index.ts create mode 100644 frontend/src/features/report/ui/report.tsx create mode 100644 frontend/src/features/report/ui/style.module.scss create mode 100644 frontend/src/pages/auth/api/api.ts create mode 100644 frontend/src/pages/auth/model/types.ts create mode 100644 frontend/src/pages/auth/ui/auth.tsx create mode 100644 frontend/src/pages/auth/ui/style.module.scss create mode 100644 frontend/src/pages/edit/ui/edit.tsx create mode 100644 frontend/src/pages/edit/ui/style.module.scss create mode 100644 frontend/src/pages/feed/ui/feed.tsx create mode 100644 frontend/src/pages/feed/ui/style.module.scss create mode 100644 frontend/src/pages/history/ui/history.tsx create mode 100644 frontend/src/pages/history/ui/style.module.scss create mode 100644 frontend/src/pages/index.ts create mode 100644 frontend/src/shared/assets/fonts/le-murmure.otf create mode 100644 frontend/src/shared/assets/index.ts create mode 100644 frontend/src/shared/assets/logo/2107.png create mode 100644 frontend/src/shared/assets/logo/2107blue.png create mode 100644 frontend/src/shared/assets/logo/2107pin.png create mode 100644 frontend/src/shared/assets/svg/cancel.svg create mode 100644 frontend/src/shared/assets/svg/edit.svg create mode 100644 frontend/src/shared/assets/svg/feed.svg create mode 100644 frontend/src/shared/assets/svg/heart.svg create mode 100644 frontend/src/shared/assets/svg/history.svg create mode 100644 frontend/src/shared/clients.ts create mode 100644 frontend/src/shared/config.ts create mode 100644 frontend/src/shared/index.ts create mode 100644 frontend/src/shared/ui/button/index.tsx create mode 100644 frontend/src/shared/ui/button/style.module.scss create mode 100644 frontend/src/shared/ui/card/index.tsx create mode 100644 frontend/src/shared/ui/card/style.module.scss create mode 100644 frontend/src/shared/ui/index.ts create mode 100644 frontend/src/shared/ui/input/img-input/index.tsx create mode 100644 frontend/src/shared/ui/input/img-input/style.module.scss create mode 100644 frontend/src/shared/ui/input/index.ts create mode 100644 frontend/src/shared/ui/input/input.tsx create mode 100644 frontend/src/shared/ui/input/style.module.scss create mode 100644 frontend/src/shared/ui/input/textarea/index.tsx create mode 100644 frontend/src/shared/ui/input/textarea/style.module.scss create mode 100644 frontend/src/shared/ui/navbar/index.tsx create mode 100644 frontend/src/shared/ui/navbar/style.module.scss create mode 100644 frontend/src/shared/ui/notify/index.ts create mode 100644 frontend/src/shared/ui/select/index.tsx create mode 100644 frontend/src/shared/ui/select/style.module.scss create mode 100644 frontend/src/vite-env.d.ts create mode 100644 frontend/src/widgets/crop/crop.tsx create mode 100644 frontend/src/widgets/crop/style.module.scss create mode 100644 frontend/src/widgets/crop/toImg.tsx create mode 100644 frontend/src/widgets/index.ts diff --git a/frontend/public/vite.svg b/frontend/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/frontend/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/app/index.scss b/frontend/src/app/index.scss new file mode 100644 index 0000000..ef1a27e --- /dev/null +++ b/frontend/src/app/index.scss @@ -0,0 +1,40 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700;1,900&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap'); + +@font-face { + font-family: "LeMurmure"; + src: url('../shared/assets/fonts/le-murmure.otf') format('opentype'); +} + +:root { + + --bg-main: radial-gradient(58.01% 47.04% at 50% 50%,rgb(99, 7, 33),rgb(19, 19, 19) 100%); + --font-system: Roboto; + --font-card: Roboto; + + --photo-form-size-w: 290px; + --photo-form-size-h: 552px; + + --bg-auth: linear-gradient(180.00deg, rgba(24, 24, 24, 0.69),rgba(10, 10, 10, 0.83) 100%),rgb(35, 35, 35); + --pink: rgb(255, 108, 166); + --white-pink: rgb(255, 198, 221); + --blue: rgb(113, 108, 255); + --white: rgb(255, 255, 255); + --bg-input: rgb(9, 9, 9); + --button-color: rgb(34, 31, 33); + --main-color: var(--white); +} + +body { + margin: 0; + font-family: var(--font-system); + +} + +#root { + height: 100vh; +} + \ No newline at end of file diff --git a/frontend/src/app/index.tsx b/frontend/src/app/index.tsx new file mode 100644 index 0000000..b51daf4 --- /dev/null +++ b/frontend/src/app/index.tsx @@ -0,0 +1,20 @@ +import { createRoot } from 'react-dom/client' +import {App} from './ui/app.tsx' +import './index.scss' +import UserProvider from './providers/userProvider.tsx' +import {ThemeProvider, ToasterComponent, ToasterProvider} from '@gravity-ui/uikit' +import '@gravity-ui/uikit/styles/fonts.css'; +import '@gravity-ui/uikit/styles/styles.css'; + +createRoot(document.getElementById('root')!).render( + + + + + + + + +) + + diff --git a/frontend/src/app/providers/index.ts b/frontend/src/app/providers/index.ts new file mode 100644 index 0000000..0bd4422 --- /dev/null +++ b/frontend/src/app/providers/index.ts @@ -0,0 +1 @@ +export * from './userProvider' \ No newline at end of file diff --git a/frontend/src/app/providers/userProvider.tsx b/frontend/src/app/providers/userProvider.tsx new file mode 100644 index 0000000..eb3c61a --- /dev/null +++ b/frontend/src/app/providers/userProvider.tsx @@ -0,0 +1,38 @@ +import { createContext, Dispatch, ReactNode, SetStateAction, useEffect, useState } from "react"; +import { getUser, User } from "../../entities"; + +interface IChildren { + children: ReactNode; +} + +interface Props { + user: User | undefined | null; + updateUser: () => void; + setUser: Dispatch>; +} + +export const UserContext = createContext({} as Props); + +export default function UserProvider({ children }: IChildren) { + + const [user, setUser] = useState(undefined); + + const updateUser = () => { + getUser().then(setUser) + console.log(user) + } + + useEffect(() => { + updateUser(); + }, []); + + useEffect(() => { + console.log(user) + }, [user]) + + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/frontend/src/app/router/router.tsx b/frontend/src/app/router/router.tsx new file mode 100644 index 0000000..c38c149 --- /dev/null +++ b/frontend/src/app/router/router.tsx @@ -0,0 +1,22 @@ +import { FocusUser} from "../../entities"; +import { Feed, Edit, History } from "../../pages"; +import { Page } from "../ui/app"; + +interface Props { + page: Page + focus: FocusUser +} + +export function Router({page, focus}: Props) { + const nowPage = () => { + switch (page) { + case 'feed': + return + case 'edit': + return + case 'history': + return + } + } + return nowPage() +} \ No newline at end of file diff --git a/frontend/src/app/ui/app.tsx b/frontend/src/app/ui/app.tsx new file mode 100644 index 0000000..d87b14e --- /dev/null +++ b/frontend/src/app/ui/app.tsx @@ -0,0 +1,36 @@ +import styles from './style.module.scss' +import { useContext, useState} from 'react' +import {Auth} from '../../pages' +import { UserContext } from '../providers' +import { editIcon, feedIcon, historyIcon, NavBar, NavIcon } from '../../shared' +import { Router } from '../router/router' +import { ReactNotifications } from 'react-notifications-component' + +export type Page = 'feed' | 'history' | 'edit' + +export function App() { + const {user} = useContext(UserContext) + const [page, setPage] = useState('feed') + + if (user === undefined) { + return <>Загрузка + } + else if (user === null) { + return + } + else { + + const buttons: NavIcon[] = [ + {src: editIcon, hook: () => setPage('edit'), active: page == 'edit'}, + {src: feedIcon, hook: () => setPage('feed'), active: page == 'feed'}, + {src: historyIcon, hook: () => setPage('history'), active: page == 'history'}, + ] + + return
+ + + +
+ } +} + diff --git a/frontend/src/app/ui/style.module.scss b/frontend/src/app/ui/style.module.scss new file mode 100644 index 0000000..c2c3a4e --- /dev/null +++ b/frontend/src/app/ui/style.module.scss @@ -0,0 +1,4 @@ +.main { + background: var(--bg-main); + height: 100svh; +} \ No newline at end of file diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/frontend/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/entities/index.ts b/frontend/src/entities/index.ts new file mode 100644 index 0000000..042420c --- /dev/null +++ b/frontend/src/entities/index.ts @@ -0,0 +1 @@ +export * from './user' \ No newline at end of file diff --git a/frontend/src/entities/user/api.ts b/frontend/src/entities/user/api.ts new file mode 100644 index 0000000..2623902 --- /dev/null +++ b/frontend/src/entities/user/api.ts @@ -0,0 +1,24 @@ +import { UserClient } from "../../shared"; +import { User } from "./model"; + +export async function getUser(): Promise { + return UserClient.get('').then( + response => { + return response.data + } + ).catch(error => { + console.log(error) + return null + }) +} + +export async function genKey (username: string): Promise { + return UserClient.post('/getauth', {username}).then( + response => { + return response.data + } + ).catch(error => { + console.log(error) + throw error + }) +} \ No newline at end of file diff --git a/frontend/src/entities/user/index.ts b/frontend/src/entities/user/index.ts new file mode 100644 index 0000000..ced9731 --- /dev/null +++ b/frontend/src/entities/user/index.ts @@ -0,0 +1,2 @@ +export * from './api' +export * from './model' \ No newline at end of file diff --git a/frontend/src/entities/user/model.ts b/frontend/src/entities/user/model.ts new file mode 100644 index 0000000..ca71059 --- /dev/null +++ b/frontend/src/entities/user/model.ts @@ -0,0 +1,14 @@ +export type User = { + name: string + surname: string + desc: string + literal: string + male: boolean + is_active: boolean + attachments: string[] + focus_user: FocusUser + focus_is_liked: boolean + verify: boolean +} + +export type FocusUser = Omit \ No newline at end of file diff --git a/frontend/src/features/index.ts b/frontend/src/features/index.ts new file mode 100644 index 0000000..96f43fd --- /dev/null +++ b/frontend/src/features/index.ts @@ -0,0 +1,2 @@ +export * from './like' +export * from './report' \ No newline at end of file diff --git a/frontend/src/features/like/api/api.ts b/frontend/src/features/like/api/api.ts new file mode 100644 index 0000000..3746738 --- /dev/null +++ b/frontend/src/features/like/api/api.ts @@ -0,0 +1,24 @@ +import { FocusUser, User } from "../../../entities"; +import { UserClient } from "../../../shared"; + +export async function likeSend (status: boolean): Promise { + return UserClient.post('/likes', {status}) + .then(response => { + return response.data; + }) + .catch(error => { + console.log(error) + throw error + }) +} + +export async function getLikes (): Promise { + return UserClient.get('/likes') + .then(response => { + return response.data; + }) + .catch(error => { + console.log(error) + throw error + }) +} diff --git a/frontend/src/features/like/index.ts b/frontend/src/features/like/index.ts new file mode 100644 index 0000000..cf7799a --- /dev/null +++ b/frontend/src/features/like/index.ts @@ -0,0 +1 @@ +export * from './api/api' \ No newline at end of file diff --git a/frontend/src/features/report/api/api.ts b/frontend/src/features/report/api/api.ts new file mode 100644 index 0000000..39503c1 --- /dev/null +++ b/frontend/src/features/report/api/api.ts @@ -0,0 +1,13 @@ +import { User } from "../../../entities"; +import { UserClient } from "../../../shared"; + +export async function reportSend (reason: string): Promise { + return UserClient.post('/likes/report', {reason}) + .then(response => { + return response.data + }) + .catch(error => { + console.log(error) + throw error + }) +} \ No newline at end of file diff --git a/frontend/src/features/report/index.ts b/frontend/src/features/report/index.ts new file mode 100644 index 0000000..ee5e524 --- /dev/null +++ b/frontend/src/features/report/index.ts @@ -0,0 +1,2 @@ +export * from './api/api' +export * from './ui/report' \ No newline at end of file diff --git a/frontend/src/features/report/ui/report.tsx b/frontend/src/features/report/ui/report.tsx new file mode 100644 index 0000000..d300416 --- /dev/null +++ b/frontend/src/features/report/ui/report.tsx @@ -0,0 +1,21 @@ +import styles from './style.module.scss' +import { Button, Sheet, TextInput } from "@gravity-ui/uikit" +import { ReactElement} from "react" + +interface Props { + is_open: boolean + close_hook: () => void + func_hook: () => void + content: string + content_hook: React.Dispatch> +} + +export function ModalReport({is_open, close_hook, func_hook, content, content_hook}: Props): ReactElement { + + return +
+ content_hook(e.target.value)} size="l" type="text" placeholder="Напишите сюда причину жалобы" autoFocus={true}> + +
+
+} \ No newline at end of file diff --git a/frontend/src/features/report/ui/style.module.scss b/frontend/src/features/report/ui/style.module.scss new file mode 100644 index 0000000..b47e1db --- /dev/null +++ b/frontend/src/features/report/ui/style.module.scss @@ -0,0 +1,12 @@ +.content { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; + width: 100%; + padding-bottom: 12px; + --g-sheet-content-padding: 10px; + --g-button-background-color: var(--pink); + --g-button-background-color-hover: var(--pink); + --g-text-body-font-weight: 400; +} \ No newline at end of file diff --git a/frontend/src/pages/auth/api/api.ts b/frontend/src/pages/auth/api/api.ts new file mode 100644 index 0000000..f736b12 --- /dev/null +++ b/frontend/src/pages/auth/api/api.ts @@ -0,0 +1,11 @@ +import { User } from "../../../entities"; +import { UserClient } from "../../../shared"; +import { UserRegister } from "../model/types"; + +export async function register(user: UserRegister, avatar: FormData): Promise { + return UserClient.post('', avatar, {params: user}).then(response => { + return response.data; + }).catch(error => { + throw error; + }); +} \ No newline at end of file diff --git a/frontend/src/pages/auth/model/types.ts b/frontend/src/pages/auth/model/types.ts new file mode 100644 index 0000000..b8a2093 --- /dev/null +++ b/frontend/src/pages/auth/model/types.ts @@ -0,0 +1,19 @@ +export enum Stage { + SUBINFO = 0, + NAME = 1, + PHOTO = 2, + ABOUT = 3 +} + +export enum Sex { + MALE = "Мужчина", + WOMAN = "Женщина", +} + +export interface UserRegister { + name: string + surname: string + literal: string + male: boolean + desc: string +} diff --git a/frontend/src/pages/auth/ui/auth.tsx b/frontend/src/pages/auth/ui/auth.tsx new file mode 100644 index 0000000..2691fd6 --- /dev/null +++ b/frontend/src/pages/auth/ui/auth.tsx @@ -0,0 +1,189 @@ +import styles from './style.module.scss' +import { ReactElement, useContext, useEffect, useState } from "react"; +import { Sex, Stage, UserRegister } from "../model/types"; +import {Input, Button, Select, Option, InputImg, TextArea, white2107, blue2107, pink2107 } from '../../../shared'; +import { CropWidget } from '../../../widgets'; +import { UserContext } from '../../../app/providers'; +import { register } from '../api/api'; + +export function Auth() { + const Literales: Option[] = [ + {key: "10k", value: "10К"}, + {key: "10c", value: "10С"}, + {key: "10u", value: "10Ю"}, + {key: "10y", value: "10У"}, + {key: "10j", value: "10Ж"}, + {key: "10i", value: "10И"}, + {key: "10t", value: "10Т"}, + {key: "10a", value: "10А"}, + {key: "10b", value: "10Б"}, + {key: "10g", value: "10Ж"}, + {key: "10o", value: "10О"}, + {key: "10f", value: "10Ф"}, + {key: "11k", value: "11К"}, + {key: "11c", value: "11С"}, + {key: "11y", value: "11Ю"}, + {key: "11j", value: "11Ж"}, + {key: "11i", value: "11И"}, + {key: "11t", value: "11Т"}, + {key: "11a", value: "11А"}, + {key: "11b", value: "11Б"}, + {key: "11g", value: "11Ж"}, + {key: "11o", value: "11О"}, + {key: "11f", value: "11Ф"}, + {key: "11p", value: "11П"}, + ] + + const {updateUser} = useContext(UserContext) + const [theme, setTheme] = useState<'pink' | 'blue' | null>(null) + const [stage, setStage] = useState(Stage.SUBINFO) + const [name, setName] = useState('') + const [surname, setSurname] = useState('') + const [sex, setSex] = useState('') + const [image, setImage] = useState(undefined) + const [desc, setDesc] = useState('') + const [literal, setLiteral] = useState(null) + + useEffect(() => { + switch (sex) { + case Sex.MALE: + setTheme('blue') + break + case Sex.WOMAN: + setTheme('pink') + break + default: + break + } + }, [sex]) + + const checkActive = ():boolean => { + if (stage == Stage.SUBINFO && literal && sex) { + return true + } else if (stage == Stage.NAME && name.length > 0 && surname.length > 0) { + return true + } else if (stage == Stage.PHOTO && image) { + return true + } else if (stage == Stage.ABOUT && desc.length >= 4) { + return true + } else { + return false; + } + } + + const Sexs: Option[] = [ + {key: "male", value: "Мужчина"}, + {key: "woman", value: "Женщина"} + ] + + const inputName = +
+
+ Имя + +
+
+ Фамилия + +
+
+ + const inputSex = +
+
+ Выберите пол + +
+
+ + const [isCropping, setCropping] = useState(true) + const startCropping = (img: string) => { + setImage(img) + setCropping(true) + } + const inputImg =
+
+ Ваше фото + +
+
+ + const inputAbout = +
+
+ О себе + +} \ No newline at end of file diff --git a/frontend/src/shared/ui/input/textarea/style.module.scss b/frontend/src/shared/ui/input/textarea/style.module.scss new file mode 100644 index 0000000..9062af1 --- /dev/null +++ b/frontend/src/shared/ui/input/textarea/style.module.scss @@ -0,0 +1,23 @@ +.textarea { + resize: none; + + font-size: 14px; + font-family: Roboto; + font-weight: 400; + width: 298px; + height: 150px; + padding: 8px; + padding-left: 10px; + border-radius: 12px; + border: 1px solid; + border-color: rgba(0, 0, 0, 0); + box-sizing: border-box; + box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); + background-color: var(--bg-input); + color: var(--main-color); + transition: border-color 0.2s ease; + outline: none; + &:focus { + border-color: var(--main-color); + } +} \ No newline at end of file diff --git a/frontend/src/shared/ui/navbar/index.tsx b/frontend/src/shared/ui/navbar/index.tsx new file mode 100644 index 0000000..f3d7f08 --- /dev/null +++ b/frontend/src/shared/ui/navbar/index.tsx @@ -0,0 +1,28 @@ +import styles from './style.module.scss' +import { ReactSVG } from 'react-svg' + +interface Props { + buttons: NavIcon[] +} + +export type NavIcon = { + src: string + hook: () => void + active: boolean +} + +export function NavBar({buttons}: Props) { + + const buttonsList = buttons.map(button => { + return + }) + + return +} \ No newline at end of file diff --git a/frontend/src/shared/ui/navbar/style.module.scss b/frontend/src/shared/ui/navbar/style.module.scss new file mode 100644 index 0000000..543cf46 --- /dev/null +++ b/frontend/src/shared/ui/navbar/style.module.scss @@ -0,0 +1,42 @@ +.navigation-bar { + display: flex; + position: absolute; + flex-direction: row; + width: 100%; + height: 64px; + top: 0; + justify-content: center; + gap: 82px; + + .icon-button { + background: none; + border: none; + padding: 0; + transition: all 3s ease; + + .icon svg { + width: 28px; + height: 28px; + opacity: 0.55; + transition: opacity 0.5s ease; + + path { + fill: white; + transition: fill 0.5s ease; + } + + rect { + fill: transparent; + } + } + + &[data-active="YES"] { + .icon svg { + opacity: 1; + path { + fill: var(--pink); + } + } + } + } +} \ No newline at end of file diff --git a/frontend/src/shared/ui/notify/index.ts b/frontend/src/shared/ui/notify/index.ts new file mode 100644 index 0000000..9e4a186 --- /dev/null +++ b/frontend/src/shared/ui/notify/index.ts @@ -0,0 +1,20 @@ +import { Store } from "react-notifications-component" + +interface Props { + title: string + content: string + type: "success" | "warning" +} + +export function addNotify({title, content, type}: Props) { + Store.addNotification({ + title: title, + message: content, + container: "top-center", + type: type, + dismiss: { + duration: 2000, + onScreen: true, + }, + }) +} \ No newline at end of file diff --git a/frontend/src/shared/ui/select/index.tsx b/frontend/src/shared/ui/select/index.tsx new file mode 100644 index 0000000..05ea8ea --- /dev/null +++ b/frontend/src/shared/ui/select/index.tsx @@ -0,0 +1,21 @@ +import { Sex } from '../../../pages/auth/model/types' +import styles from './style.module.scss' + +export type Option = { + key: string + value: string +} + +interface Props { + value: string + options: Option[] + title: string + hook: (value: string | Sex) => void +} + +export function Select({value, options, hook, title}: Props) { + return +} \ No newline at end of file diff --git a/frontend/src/shared/ui/select/style.module.scss b/frontend/src/shared/ui/select/style.module.scss new file mode 100644 index 0000000..b3eec8c --- /dev/null +++ b/frontend/src/shared/ui/select/style.module.scss @@ -0,0 +1,25 @@ +.select { + font-size: 14px; + font-family: Roboto; + font-weight: 400; + + width: 298px; + height: 38px; + //padding: 4px; + padding-left: 20px; + border-radius: 12px; + border: 1px solid; + border-color: rgba(0, 0, 0, 0); + box-sizing: border-box; + box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25); + background-color: var(--bg-input); + transition: color 1s ease; + color: var(--main-color); + transition: border-color 0.2s ease; + transition: background-color 1s ease; + outline: none; + + &:focus { + border-color: var(--main-color); + } +} \ No newline at end of file diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/frontend/src/widgets/crop/crop.tsx b/frontend/src/widgets/crop/crop.tsx new file mode 100644 index 0000000..56ff384 --- /dev/null +++ b/frontend/src/widgets/crop/crop.tsx @@ -0,0 +1,65 @@ +import { ReactElement, useState } from "react"; +import Cropper, { Area, Point } from "react-easy-crop"; +import { Button } from "../../shared"; +import getCroppedImg from "./toImg"; +import styles from './style.module.scss' + +interface Props { + img: string + setImg: (img: string | undefined) => void + onComplete: () => void +} + +export function CropWidget({img, setImg, onComplete}: Props): ReactElement { + const [zoom, setZoom] = useState(1.2) + const [crop, setCrop] = useState({x: 0, y: 0}) + const [area, setArea] = useState(null) + + const completeCrop = () => { + if (area) { + getCroppedImg(img, area).then((newImg) => { + if (newImg) { + setImg(newImg); + } + }) + } + onComplete(); + } + + const deleteCrop = () => { + setImg(undefined) + onComplete(); + } + + const onCropComplete = (croppedArea, croppedAreaPixels: Area) => { + setArea(croppedAreaPixels) + } + + const style = { + border: '2px dashed', + borderColor: 'var(--main-color)', + borderRadius: '8px' + } + + const cropper = + + return
+
+ {cropper} +
+
+
+
+} \ No newline at end of file diff --git a/frontend/src/widgets/crop/style.module.scss b/frontend/src/widgets/crop/style.module.scss new file mode 100644 index 0000000..ce9756f --- /dev/null +++ b/frontend/src/widgets/crop/style.module.scss @@ -0,0 +1,17 @@ +.main { + background: black; + height: 100vh; + .container-cropper { + height: 80%; + } + .buttons { + padding: 12px; + box-sizing: border-box; + gap: 16px; + display: flex; + width: 100%; + align-items: center; + flex-direction: column; + justify-content: space-between; + } +} \ No newline at end of file diff --git a/frontend/src/widgets/crop/toImg.tsx b/frontend/src/widgets/crop/toImg.tsx new file mode 100644 index 0000000..1b6e77e --- /dev/null +++ b/frontend/src/widgets/crop/toImg.tsx @@ -0,0 +1,74 @@ +import { Area } from 'react-easy-crop' + +const createImage = (url: string): Promise => + new Promise((resolve, reject) => { + const image = new Image(); + image.addEventListener("load", () => resolve(image)); + image.addEventListener("error", (error) => reject(error)); + image.setAttribute("crossOrigin", "anonymous"); // needed to avoid cross-origin issues on CodeSandbox + image.src = url; + }); + + function getRadianAngle(degreeValue: number) { + return (degreeValue * Math.PI) / 180; + } + + /** + * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop + * @param {File} image - Image File url + * @param {Object} pixelCrop - pixelCrop Object provided by react-easy-crop + * @param {number} rotation - optional rotation parameter + */ + export default async function getCroppedImg(imageSrc: string, pixelCrop: Area, rotation = 0) { + const image: HTMLImageElement = await createImage(imageSrc); + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + const maxSize = Math.max(image.width, image.height); + const safeArea = 2 * ((maxSize / 2) * Math.sqrt(2)); + + // set each dimensions to double largest dimension to allow for a safe area for the + // image to rotate in without being clipped by canvas context + canvas.width = safeArea; + canvas.height = safeArea; + + // translate canvas context to a central location on image to allow rotating around the center. + if (ctx) { + // translate canvas context to a central location on image to allow rotating around the center. + ctx.translate(safeArea / 2, safeArea / 2); + ctx.rotate(getRadianAngle(rotation)); + ctx.translate(-safeArea / 2, -safeArea / 2); + + // draw rotated image and store data. + ctx.drawImage( + image, + safeArea / 2 - image.width * 0.5, + safeArea / 2 - image.height * 0.5 + ); + + const data = ctx.getImageData(0, 0, safeArea, safeArea); + + // set canvas width to final desired crop size - this will clear existing context + canvas.width = pixelCrop.width; + canvas.height = pixelCrop.height; + + // paste generated rotate image with correct offsets for x,y crop values. + ctx.putImageData( + data, + 0 - safeArea / 2 + image.width * 0.5 - pixelCrop.x, + 0 - safeArea / 2 + image.height * 0.5 - pixelCrop.y + ); + } else { + return null + } + // As Base64 string + return canvas.toDataURL('image/jpeg'); + + // As a blob + // return new Promise((resolve) => { + // canvas.toBlob((file) => { + // console.log(file); + // resolve(URL.createObjectURL(file)); + // }, "image/jpeg"); + // }); + } \ No newline at end of file diff --git a/frontend/src/widgets/index.ts b/frontend/src/widgets/index.ts new file mode 100644 index 0000000..52aa084 --- /dev/null +++ b/frontend/src/widgets/index.ts @@ -0,0 +1 @@ +export * from './crop/crop' \ No newline at end of file