diff --git a/package-lock.json b/package-lock.json index 75fbc8c..21885b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13633,9 +13633,9 @@ }, "dependencies": { "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", + "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", "requires": { "regenerator-runtime": "^0.13.4" } diff --git a/package.json b/package.json index 946fdaf..a8e943e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "scripts": { "start": "run-p --race dev:firebase dev:react", "dev:firebase": "firebase serve -p 4000", - "dev:react": "set PORT=3006 && react-scripts start", + "dev:react": "set PORT=3005 && react-scripts start", "build": "react-scripts build", "predeploy": "npm run build", "deploy": "cd functions && firebase deploy --only hosting:gorazdo", diff --git a/src/components/atoms/Avatar.js b/src/components/atoms/Avatar.js index 4b0d371..7de2b25 100644 --- a/src/components/atoms/Avatar.js +++ b/src/components/atoms/Avatar.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import styled from 'styled-components'; const StyledDiv = styled('div')` - width: ${(props) => props.theme.spacing(props.size)}px; - height: ${(props) => props.theme.spacing(props.size)}px; + width: ${(props) => props.theme.spacing(props.size)}; + height: ${(props) => props.theme.spacing(props.size)}; border-radius: 50%; border: 1px solid #000; background-size: cover; diff --git a/src/components/atoms/Badge.js b/src/components/atoms/Badge.js index 6989550..601ce98 100644 --- a/src/components/atoms/Badge.js +++ b/src/components/atoms/Badge.js @@ -14,8 +14,8 @@ const FONT_SIZES_DICTIONARY = { big: '18px', }; -const getHeight = (props) => SIZES_FACTORS[props.$size] * 8 + 'px'; -const getHalfHeight = (props) => SIZES_FACTORS[props.$size] * 4 + 'px'; +const getHeight = (props) => SIZES_FACTORS[props.$size] * 8; +const getHalfHeight = (props) => SIZES_FACTORS[props.$size] * 4; const BadgeBack = styled('div')` height: ${getHeight}; diff --git a/src/components/atoms/Button.js b/src/components/atoms/Button.js index 8f41bfe..8a5f33d 100644 --- a/src/components/atoms/Button.js +++ b/src/components/atoms/Button.js @@ -1,19 +1,12 @@ import React from 'react'; import styled from 'styled-components'; -const getButtonColor = (buttonThemeProperty, key = 'palette') => { - console.log(getButtonColor, buttonThemeProperty); - return (props) => { - console.log(props); - return props.theme.buttons[props[key]][buttonThemeProperty]; - }; -}; - const StyledButton = styled.button` border: 2px solid black; - padding: ${(props) => props.theme.spacing(1)} 0; + padding: ${(props) => props.theme.spacing(1, 3)}; + border-radius: ${(props) => props.theme.spacing(1)}; cursor: pointer; - background: transparent; + background: ${(props) => props.theme.palette.primary.main}; border: none; color: white; min-width: ${(props) => props.theme.spacing(4)}; @@ -33,9 +26,9 @@ const StyledButton = styled.button` props.theme.color('primary', 'main', (color) => color.lighten(0.2))}; } `; + export default StyledButton; export const Button = (props) => { - console.log(props); return ; }; diff --git a/src/components/atoms/Tag.js b/src/components/atoms/Tag.js index 668ea93..be08aae 100644 --- a/src/components/atoms/Tag.js +++ b/src/components/atoms/Tag.js @@ -7,15 +7,15 @@ import PropTypes from 'prop-types'; */ const TagBack = styled('div')` - height: ${(props) => props.theme.spacing(3) + 'px'}; - border-radius: ${(props) => props.theme.spacing(2) + 'px'}; + height: ${(props) => props.theme.spacing(3)}; + border-radius: ${(props) => props.theme.spacing(2)}; background: ${(props) => props.tagColor}; - padding-left: ${(props) => props.theme.spacing(1.5) + 'px'}; - padding-right: ${(props) => props.theme.spacing(1.5) + 'px'}; + padding-left: ${(props) => props.theme.spacing(1.5)}; + padding-right: ${(props) => props.theme.spacing(1.5)}; display: flex; justify-content: center; align-items: center; - margin-right: ${(props) => props.theme.spacing(1) + 'px'}; + margin-right: ${(props) => props.theme.spacing(1)}; `; const TagText = styled('span')` diff --git a/src/components/atoms/Text.js b/src/components/atoms/Text.js index d686d4f..1367833 100644 --- a/src/components/atoms/Text.js +++ b/src/components/atoms/Text.js @@ -5,7 +5,11 @@ import React, { useEffect, useLayoutEffect, } from 'react'; -import { LocaleContext, FALLBACK_LOCALE } from 'contexts/Locale'; +import { + LocaleContext, + FALLBACK_LOCALE, + FALLBACK_LOCALE_2, +} from 'contexts/Locale'; const extractLocale = (value, locale) => { if (typeof value === 'object') { @@ -15,6 +19,9 @@ const extractLocale = (value, locale) => { if (FALLBACK_LOCALE in value) { return value[FALLBACK_LOCALE]; } + if (FALLBACK_LOCALE_2 in value) { + return `[${FALLBACK_LOCALE_2}] ${value[FALLBACK_LOCALE_2]}`; + } return JSON.stringify(value); } return value; diff --git a/src/components/atoms/Typography.js b/src/components/atoms/Typography.js index 6400b03..a1f01d3 100644 --- a/src/components/atoms/Typography.js +++ b/src/components/atoms/Typography.js @@ -5,6 +5,7 @@ import styled from 'styled-components'; const StyledComponent = styled('span')` display: block; margin: 0; + margin-bottom: ${(props) => (props.gutterBottom ? '0.5em' : 0)}; padding: 0; color: ${(props) => props.theme.palette.text.primary}; font-size: ${(props) => props.theme.typography[props.variant].fontSize}; @@ -28,6 +29,7 @@ Typography.propTypes = { 'h5', 'h6', ]), + gutterBottom: PropTypes.bool, }; Typography.defaultProps = {}; diff --git a/src/components/molecules/CourseLink.js b/src/components/molecules/CourseLink.js new file mode 100644 index 0000000..c6d4f5c --- /dev/null +++ b/src/components/molecules/CourseLink.js @@ -0,0 +1,40 @@ +import React from 'react'; +import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { MdOpenInNew } from 'react-icons/md'; +import { Text } from 'components/atoms/Text'; + +const StyledAnchor = styled('a')` + margin: ${(props) => props.theme.spacing(1)}; + display: inline-block; + position: relative; + & > svg { + opacity: 0; + position: absolute; + bottom: 0; + transition: opacity 100ms; + margin-left: 0.3em; + } + &:hover > svg { + opacity: 0.6c; + } +`; + +export const CourseLink = ({ title, link }) => { + return ( + + + + + ); +}; + +CourseLink.propTypes = { + title: PropTypes.string, + link: PropTypes.string, +}; + +CourseLink.defaultProps = { + title: 'HTML Academy Javascript ', + link: 'https://htmlacademy.ru/courses', +}; diff --git a/src/components/molecules/CourseLink.stories.js b/src/components/molecules/CourseLink.stories.js new file mode 100644 index 0000000..c1b7363 --- /dev/null +++ b/src/components/molecules/CourseLink.stories.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { CourseLink } from './CourseLink'; + +export default { + title: 'Molecules/CourseLink', + component: CourseLink, + parameters: { + figma: + 'https://www.figma.com/file/oW95CY1Vcnb7PYICpP4By3/Gorazdo?node-id=129%3A2', + }, +}; + +const Template = (args) => ; + +export const Default = Template.bind({}); diff --git a/src/components/molecules/PanelArticles.js b/src/components/molecules/PanelArticles.js index 02cf267..0686251 100644 --- a/src/components/molecules/PanelArticles.js +++ b/src/components/molecules/PanelArticles.js @@ -1,13 +1,12 @@ import React from 'react'; import styled from 'styled-components'; import PropTypes from 'prop-types'; -import { Typography } from 'components/atoms/Typography'; import { Tag } from 'components/atoms/Tag'; -const SquarePicture = styled('div')` +const SquarePicture = styled('img')` height: 80px; width: 80px; - border-radius: ${(props) => props.theme.spacing(1) + 'px'}; + border-radius: ${(props) => props.theme.spacing(1)}; background: ${(props) => props.theme.palette.action.selected}; `; @@ -15,9 +14,9 @@ const PanelContainer = styled('div')` display: flex; height: 112px; width: 752px; - border-radius: ${(props) => props.theme.spacing(2) + 'px'}; - padding-left: ${(props) => props.theme.spacing(1) + 'px'}; - padding-right: ${(props) => props.theme.spacing(1) + 'px'}; + border-radius: ${(props) => props.theme.spacing(2)}; + padding-left: ${(props) => props.theme.spacing(1)}; + padding-right: ${(props) => props.theme.spacing(1)}; justify-content: space-around; align-items: center; background: ${(props) => props.theme.palette.action.hover}; @@ -39,7 +38,7 @@ const StyledTitle = styled('div')` const TagsContainer = styled('div')` display: flex; justify-content: space-between; - padding-top: ${(props) => props.theme.spacing(1) + 'px'}; + padding-top: ${(props) => props.theme.spacing(1)}; `; const Date = styled('span')` @@ -71,10 +70,10 @@ const COLOR_FACTORS = [ textColor: '#F15D23', }, ]; -export const PanelArticles = ({ title, colorTag }) => { +export const PanelArticles = ({ title, cover, alt, colorTag }) => { return ( - + {title} @@ -96,7 +95,6 @@ export const PanelArticles = ({ title, colorTag }) => { }; PanelArticles.propTypes = { - title: PropTypes.string, colorTag: PropTypes.arrayOf( PropTypes.shape({ color: PropTypes.string, @@ -108,6 +106,5 @@ PanelArticles.propTypes = { }; PanelArticles.defaultProps = { - title: 'Анонс новой версии Styled Components v5: Звериный оскал', colorTag: COLOR_FACTORS, }; diff --git a/src/components/molecules/UserTop.js b/src/components/molecules/UserTop.js index 08b93c2..e6d9bef 100644 --- a/src/components/molecules/UserTop.js +++ b/src/components/molecules/UserTop.js @@ -1,14 +1,12 @@ import React from 'react'; import styled from 'styled-components'; - import Box from 'components/atoms/Box'; import { Avatar } from 'components/atoms/Avatar'; import { Typography } from 'components/atoms/Typography'; import { Text } from 'components/atoms/Text'; const StyledBox = styled(Box)` - padding: ${(props) => props.theme.spacing(2)}; - padding-top: ${(props) => props.theme.spacing(6)}; + padding: ${(props) => props.theme.spacing(6)}; `; const Hgroup = styled('hgroup')` @@ -16,16 +14,20 @@ const Hgroup = styled('hgroup')` `; export const UserTop = ({ doc }) => { + // useEffect(() => { + // doc.ref.update(anna); + // }, []); + console.log({ userDocument: doc }); return ( - +
- +   - +
diff --git a/src/contexts/Locale.js b/src/contexts/Locale.js index b20d81d..48842cd 100644 --- a/src/contexts/Locale.js +++ b/src/contexts/Locale.js @@ -2,6 +2,7 @@ import React, { createContext } from 'react'; import { useSessionStorageState } from 'hooks/useSessionStorageState'; export const FALLBACK_LOCALE = 'en'; +export const FALLBACK_LOCALE_2 = 'ru'; export const AVAILABLE_LOCALES = ['en', 'ru']; export const LocaleContext = createContext(null); diff --git a/src/contexts/Theme.js b/src/contexts/Theme.js index 8a011ef..7218a6d 100644 --- a/src/contexts/Theme.js +++ b/src/contexts/Theme.js @@ -1,9 +1,13 @@ -import React, { createContext, useState } from 'react'; +import React, { createContext } from 'react'; import { ThemeProvider as StyledThemeProvider } from 'styled-components'; import dark from 'styles/themes/dark'; import light from 'styles/themes/light'; import { useSessionStorageState } from 'hooks/useSessionStorageState'; +import { + CssBaseline, + ThemeProvider as MUIThemeProvider, +} from '@material-ui/core'; const themes = { dark, light }; @@ -36,7 +40,12 @@ export const ThemeProvider = ({ children }) => { return ( - {children} + + + + {children} + + ); }; diff --git a/src/hooks/index.js b/src/hooks/index.js index edaf4af..6b82ddc 100644 --- a/src/hooks/index.js +++ b/src/hooks/index.js @@ -1,13 +1,5 @@ import { useEffect, useState, useReducer, useMemo } from 'react'; -const getFirebase = () => { - const isFirebaseAvailable = typeof firebase !== 'undefined'; - if (isFirebaseAvailable) { - return window.firebase; - } - return null; -}; - const getFirebaseAppInstance = () => { try { const isFirebaseAvailable = typeof firebase !== 'undefined'; @@ -18,7 +10,9 @@ const getFirebaseAppInstance = () => { } return null; } catch (error) { - console.error(error.message); + if (!error.message.includes('app/no-app')) { + console.error(error); + } return null; } }; @@ -36,7 +30,11 @@ export const useFirebaseApp = () => { const app = getFirebaseAppInstance(); const features = Array.from(app.container.providers.keys()); setFirebaseInstance(app); - console.log(`Firebase SDK loaded with ${features.join(', ')}`); + console.groupCollapsed( + `Firebase SDK loaded with ${features.length} modules` + ); + console.log(features); + console.groupEnd(); } catch (error) { console.error(error); setFirebaseInstance(null); @@ -157,3 +155,7 @@ const fetchData = async ({ aborted, dispatch, ref }) => { } } }; + +export const useCurrentUser = () => { + return useFirebase()?.auth()?.currentUser ?? null; +}; diff --git a/src/pages/admin/index.js b/src/pages/admin/index.js index 2d38e85..1555f81 100644 --- a/src/pages/admin/index.js +++ b/src/pages/admin/index.js @@ -13,7 +13,7 @@ export const Admin = () => { ); }; -export const FirebaseAuthContent = ({}) => { +export const FirebaseAuthContent = () => { const auth = window.firebase.auth; const user = window.firebase.auth().currentUser; const ref = useRef(); diff --git a/src/pages/anna_s.json b/src/pages/anna_s.json new file mode 100644 index 0000000..3c79b28 --- /dev/null +++ b/src/pages/anna_s.json @@ -0,0 +1,40 @@ +{ + "about": { + "en": "I’m a 27-year-old girl with a sociological degree and experience in banking analytics.\nFor two years I had been helping to sell bank products, hire people and prevent fraud in the best online Russian bank.", + "ru": "Лолкекчебурек" + }, + "content": [ + { + "items": [ + { + "title": { + "ru": "Построение аналитической платформы (Tinkoff Fintech)" + }, + "link": "https://fintech.tinkoff.ru/study/fintech/dwh/" + }, + { + "title": { "ru": "Learn Python 2 (Codeacademy)" }, + "link": "https://www.codecademy.com/learn/learn-python" + } + ], + "title": { "en": "Courses", "ru": "Курсы" }, + "type": "list-item" + }, + { + "items": [ + { + "title": { + "en": "Moscow State University", + "ru": "МГУ им М.В.Ломоносова" + } + } + ], + "title": { "en": "Education", "ru": "Образование" }, + "type": "list-item-full" + } + ], + "firstName": { "en": "Anna", "ru": "Анна" }, + "lastName": { "en": "Sitnikova", "ru": "Ситникова" }, + "name": "AnnaSitnikova", + "profession": { "en": "Data Analyst", "ru": "Аналитик данных" } +} diff --git a/src/pages/ekaterina.json b/src/pages/ekaterina.json new file mode 100644 index 0000000..4f48f83 --- /dev/null +++ b/src/pages/ekaterina.json @@ -0,0 +1,58 @@ +{ + "about": { + "en": "", + "ru": "Последние несколько лет работаю аналитиком в бэкенде. Пришло время освоить фронт ;)" + }, + "content": [ + { + "items": [ + { + "title": { "en": "System Analyst", "ru": "Системный аналитик" } + }, + { + "title": { "en": "Data Analyst", "ru": "Аналитик данных" } + }], + "title": { "en": "Experience", "ru": "Опыт работы" }, + "type": "list-item" + }, + { + "items": [ + { + "title": { "en": "Russian Technological University (MIREA)", "ru": "Российский технологический университет (МИРЭА)"}, + "qualification": {"en": "Master’s Degree in Computer science", "ru": "Специалет по направлению 'Информационные системы и технологии'"} + } + ], + "title": { "en": "Education", "ru": "Образование" }, + "type": "list-item-full" + }, + { + "items": [ + { + "title": { "en": "Attention to details", "ru": "Внимание к деталям"} + }, + { + "title": { "en": "Perseverance", "ru": "Настойчивость"} + }, + { + "title": { "en": "Patience", "ru": "Терпение"} + } + ], + "title": { "en": "Personal qualities", "ru": "Личные качества" }, + "type": "list-item-full" + }, + {"items": [ + { "title": ""}, + { "title": ""}, + { "title": ""} + ] + , + "title": { "en": "Hard skills", "ru": "Профессиональные навыки" }, + "type": "list-item-full" + } + ], + "firstName": { "en": "Ekaterina", "ru": "Екатерина" }, + "lastName": { "en": "Pichugina", "ru": "Пичугина" }, + "name": "EkaterinaPichugina", + "profession": { "en": "Junior Front-end Developer", "ru": "Junior Front-end Разработчик" } + } + \ No newline at end of file diff --git a/src/pages/nikolay.json b/src/pages/nikolay.json new file mode 100644 index 0000000..3ebda39 --- /dev/null +++ b/src/pages/nikolay.json @@ -0,0 +1,30 @@ +{ + "about": { + "en": "I am a newbie front-end developer. I don't have much experience, but a lot of desire and diligence. Soon, there will be many projects and activities. So stay tuned." + }, + "content": [ + { + "items": [ + { "title": "HTML Academy Javascript", "link": "https://htmlacademy.ru/courses"}, + { "title": "CSS+HTML", "link": "https://htmlacademy.ru/courses" }, + { "title": "Fundamentals of programming", "link": "https://www.udemy.com/course/ittensive-programmer-basic/" }, + { "title": "Basic React", "link": "https://reactjs.org/community/courses.html" } + ], + "title": { "en": "Courses", "ru": "Курсы" }, + "type": "list-item" + }, + { + "items": [ + { + "title": { "en": "Russian Presidential Academy of National Economy And Public Administration (RANEPA)", "ru": "Российская Академия Народного Хозяйства и Государственной Службы (РАНХиГС)"} + } + ], + "title": { "en": "Education", "ru": "Образование" }, + "type": "list-item-full" + } + ], + "firstName": { "en": "Nikolay", "ru": "Николай" }, + "lastName": { "en": "Nelidov", "ru": "Нелидов" }, + "name": "NikolayNelidov", + "profession": { "en": "Junior Front-end Developer", "ru": "Junior Front-end Разработчик" } +} diff --git a/src/pages/people/AddItemDrawer.js b/src/pages/people/AddItemDrawer.js new file mode 100644 index 0000000..e9264c8 --- /dev/null +++ b/src/pages/people/AddItemDrawer.js @@ -0,0 +1,195 @@ +import React, { useState } from 'react'; +import { + CardContent, + Typography, + Button, + Drawer, + TextField, + FormGroup, + MenuItem, +} from '@material-ui/core'; + +import { useCurrentUser } from 'hooks'; +import { postProcess } from './utils/postProcess'; + +const FORM_FIELDS_DICTIONARY = { + jobs: ['title', 'subtitle', 'cover', 'dateStart', 'dateEnd', 'description'], + education: [ + 'title', + 'subtitle', + 'cover', + 'dateStart', + 'dateEnd', + 'description', + ], + about: ['title', 'description'], + courses: ['title', 'subtitle', 'cover', 'description', 'dateEnd'], +}; + +// Anna Cover, Description +// Kolya поле для Tags (array) https://material-ui.com/components/autocomplete/ freeSolo creatable +// Katya зарендерить внутри секции эти items (используем готовые компоненты) + +export const AddItemDrawer = ({ userDoc, doc }) => { + const [type, setType] = useState('jobs'); + const [open, setOpen] = useState(false); + const me = useCurrentUser(); + const isMine = me?.uid === userDoc.get('uid'); + + const [formData, setFormData] = useState({}); + + const handleAdd = (event) => { + event.preventDefault(); + console.log('test'); + doc.ref + .collection('items') + .doc() + .set({ + type: type, + ...postProcess(formData), + }) + .then(function () { + console.log('Document successfully written!'); + }) + .catch(function (error) { + console.error('Error writing document: ', error); + }); + }; + + if (!isMine) { + return null; + } + return ( + <> + + setOpen(false)}> + + Edit section + {doc.id} + + + + setType(event.target.value)} + select + helperText="Название объекта item" + > + {Object.keys(FORM_FIELDS_DICTIONARY).map((key) => ( + + {key} + + ))} + + + + + + + + + ); +}; + +const Fields = ({ type, formData, setFormData }) => { + console.log(formData); + console.log(FORM_FIELDS_DICTIONARY[type]); + return FORM_FIELDS_DICTIONARY[type].map((fieldName) => ( + + )); +}; + +const Field = ({ name, formData, setFormData }) => { + if (name === 'title') { + return ( + { + setFormData({ ...formData, title: event.target.value }); + }} + variant="outlined" + label="Заголовок" + helperText="Название объекта item" + /> + ); + } + + if (name === 'subtitle') { + return ( + { + setFormData({ ...formData, subtitle: event.target.value }); + }} + label="Подзаголовок" + helperText="Название объекта item" + /> + ); + } + + if (name === 'dateStart') { + return ( + { + setFormData({ ...formData, dateStart: event.target.value }); + }} + label="Дата" + type="date" + helperText="Название объекта item" + /> + ); + } + + if (name === 'dateEnd') { + return ( + { + setFormData({ ...formData, dateEnd: event.target.value }); + }} + label="Дата" + type="date" + helperText="Название объекта item" + /> + ); + } + + return ( + + ); +}; diff --git a/src/pages/people/AddSection.js b/src/pages/people/AddSection.js new file mode 100644 index 0000000..196cbb2 --- /dev/null +++ b/src/pages/people/AddSection.js @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { Card, CardContent, InputAdornment } from '@material-ui/core'; +import { TextField, IconButton } from '@material-ui/core'; +import { MdSave } from 'react-icons/md'; + +export const AddSection = ({ doc }) => { + const [value, setValue] = useState('Hi!'); + const handleAdd = (event) => { + event.preventDefault(); + doc.ref + .collection('sections') + .doc() + .set({ + title: { + en: value, + ru: `ru: ${value}`, + }, + }) + .then(function () { + console.log('Document successfully written!'); + }) + .catch(function (error) { + console.error('Error writing document: ', error); + }); + }; + + return ( + + + setValue(event.target.value)} + variant="outlined" + label="Добавить новую секцию" + helperText="Ввести название секции" + InputProps={{ + endAdornment: ( + + + + + + ), + }} + /> + + + ); +}; diff --git a/src/pages/people/ContentItemMap.js b/src/pages/people/ContentItemMap.js new file mode 100644 index 0000000..6cc9cc6 --- /dev/null +++ b/src/pages/people/ContentItemMap.js @@ -0,0 +1,62 @@ +import React from 'react'; +import { Typography } from 'components/atoms/Typography'; +import { PanelArticles } from 'components/molecules/PanelArticles'; +import { Text } from 'components/atoms/Text'; +import { CourseLink } from 'components/molecules/CourseLink'; +import styled from 'styled-components'; + +const StyledSection = styled('section')` + margin-top: ${(props) => props.theme.spacing(5)}; + margin-bottom: ${(props) => props.theme.spacing(5)}; + padding-left: ${(props) => props.theme.spacing(6)}; + padding-right: ${(props) => props.theme.spacing(6)}; +`; + +export const ContentItemWrapper = (props) => { + const { title, children, component } = props; + const Component = component; + return ( + + + {title.en} + + {children} + + ); +}; + +ContentItemWrapper.defaultProps = { + component: 'div', +}; + +export const ContentItemMap = (props) => { + const { contentItem } = props; + const { type } = contentItem; + if (type === 'list-item') { + return ( + + {contentItem.items.map((item, index) => ( +
  • + +
  • + ))} +
    + ); + } + if (type === 'list-item-full') { + return ( + + {contentItem.items.map((item, index) => ( + } + colorTag={[]} + cover={item.cover ? item.cover : 'http://placekitten.com/50/50'} + alt={item.cover ? 'Cover' : 'No cover'} + /> + ))} + + ); + } + return 'Error: there is no such type'; +}; diff --git a/src/pages/people/ContentSections.js b/src/pages/people/ContentSections.js new file mode 100644 index 0000000..bb1a386 --- /dev/null +++ b/src/pages/people/ContentSections.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { useFirestoreRef } from 'hooks'; +import { List } from '@material-ui/core'; +import { useCollection } from 'react-firebase-hooks/firestore'; +import { Section } from './Section'; + +export const ContentSections = ({ userDoc }) => { + const ref = useFirestoreRef(() => userDoc.ref.collection('sections')); + const [collection] = useCollection(ref); + if (collection) { + return ( + + {collection.docs.map((doc) => ( +
    + ))} + + ); + } + return null; +}; diff --git a/src/pages/people/DeleteDocumentButton.js b/src/pages/people/DeleteDocumentButton.js new file mode 100644 index 0000000..56ab3cc --- /dev/null +++ b/src/pages/people/DeleteDocumentButton.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { useCurrentUser } from 'hooks'; +import { IconButton } from '@material-ui/core'; +import { MdDelete } from 'react-icons/md'; + +export const DeleteDocumentButton = ({ userDoc, targetDoc }) => { + const me = useCurrentUser(); + if (me?.uid !== userDoc.get('uid')) { + return null; + } + + return ( + { + targetDoc.ref + .delete() + .then(function () { + console.log('Document successfully deleted!'); + }) + .catch(function (error) { + console.error('Error removing document: ', error); + }); + }} + > + + + ); +}; diff --git a/src/pages/people/MainPortfolio.js b/src/pages/people/MainPortfolio.js new file mode 100644 index 0000000..cfefd9f --- /dev/null +++ b/src/pages/people/MainPortfolio.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { useCurrentUser } from 'hooks'; +import { UserTop } from 'components/molecules/UserTop'; +import { ContentItemMap } from './ContentItemMap'; +import { CardContent } from '@material-ui/core'; +import { AddSection } from './AddSection'; +import { ContentSections } from './ContentSections'; + +export const MainPortfolio = ({ doc }) => { + const me = useCurrentUser(); + return ( + + + {doc + .get('content') + ?.map((contentItem, index) => ( + + )) ?? 'No content'} + + {me?.uid === doc.get('uid') && } + + ); +}; diff --git a/src/pages/people/Section.js b/src/pages/people/Section.js new file mode 100644 index 0000000..6a89289 --- /dev/null +++ b/src/pages/people/Section.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { Card, CardContent, Typography, Divider } from '@material-ui/core'; +import { Text } from 'components/atoms/Text'; +import { DeleteDocumentButton } from './DeleteDocumentButton'; +import { SectionContent } from './SectionContent'; +import { AddItemDrawer } from './AddItemDrawer'; + +export const Section = ({ doc, userDoc }) => { + return ( + <> + + + + + + + + + + +
    + +
    + + ); +}; diff --git a/src/pages/people/SectionContent.js b/src/pages/people/SectionContent.js new file mode 100644 index 0000000..65d66d9 --- /dev/null +++ b/src/pages/people/SectionContent.js @@ -0,0 +1,18 @@ +import { useFirestoreRef } from 'hooks'; +import { useCollection } from 'react-firebase-hooks/firestore'; + +export const SectionContent = ({ doc, userDoc }) => { + const ref = useFirestoreRef(() => doc.ref.collection('items')); + const [collection, loading, error] = useCollection(ref); + + if (loading) { + return 'loading'; + } + if (error) { + return error.message; + } + if (collection.empty) { + return 'no data'; + } + return `Element count is ${collection.docs.length}`; +}; diff --git a/src/pages/people/index.js b/src/pages/people/index.js index 0db928e..90e15c7 100644 --- a/src/pages/people/index.js +++ b/src/pages/people/index.js @@ -3,7 +3,6 @@ import { useCollection } from 'react-firebase-hooks/firestore'; import { useFirestoreRef } from 'hooks'; import { Text } from 'components/atoms/Text'; import { Link } from 'react-router-dom'; -import { useWhyDidYouUpdate } from 'hooks/useWhyDidYouUpdate'; export const People = () => { const ref = useFirestoreRef((db) => db.collection('people')); diff --git a/src/pages/people/person.js b/src/pages/people/person.js index 4735d02..bb57115 100644 --- a/src/pages/people/person.js +++ b/src/pages/people/person.js @@ -2,10 +2,8 @@ import React from 'react'; import { useParams } from 'react-router'; import { useFirestoreRef } from 'hooks'; import { useCollection } from 'react-firebase-hooks/firestore'; -import Box from 'components/atoms/Box'; -import styled from 'styled-components'; -import { LinkIcon } from 'components/atoms/LinkIcon'; -import { UserTop } from 'components/molecules/UserTop'; +import { MainPortfolio } from './MainPortfolio'; +import { Typography } from '@material-ui/core'; export const Person = () => { const { name } = useParams(); @@ -13,91 +11,23 @@ export const Person = () => { const ref = useFirestoreRef((db) => db.collection('people').where('name', '==', name) ); - const [value, loading, error] = useCollection(ref); - + const [collection, loading, error] = useCollection(ref); return ( - - -
    - {error && Error: {JSON.stringify(error)}} - {loading && Collection: Loading...} - {value && value.docs.length === 0 && Collection: Empty...} - {value && ( -
    - {value.docs.map((doc) => ( - - -

    About

    -

    - I am a front-end developer with 10 years experience in web - development: from responsive web-sites to SPA and - micro-frontends. I strive to write pure, maintainable and well - documented code. Also I have got design skills and have been - working as a part-time UI/UX designer for 4 years and have a - passion to implement smooth animations to apps. Since 2019 I - have been conducting to several pet projects as an architect - and a full-stack developer. -

    -

    Highlights

    - - {data.map(rendererLambda)} - -
    - ))} -
    - )} -
    -
    +
    + {error && Error: {JSON.stringify(error)}} + {loading && Collection: Loading...} + {collection && collection.docs.length === 0 && ( + User 404 not found + )} + {collection && ( +
    + {collection.docs.map((doc) => ( + + + + ))} +
    + )} +
    ); }; - -const rendererLambda = (item) => ( - -); - -const data = [ - { - id: '123', - name: 'Game marketplace', - href: 'https://gm-trade.ru', - }, - { - id: 'asd', - name: 'Miro contest', - href: 'https://miro.com', - }, -]; - -const HightlightItem = (props) => ( - -

    {props.name}

    - -
    -); - -const HighlightContainer = styled('div')` - width: 336px; - height: 216px; - border-radius: 32px; - padding: 24px 40px; - margin-right: 24px; - background: #ccc; - display: flex; - flex-direction: column; - justify-content: space-between; -`; - -const HighlightsContainer = styled('div')` - display: flex; -`; - -const StyledBackground = styled('div')` - background-color: #5f75ee; - border-top-right-radius: ${(props) => props.theme.spacing(6)}; - border-bottom-right-radius: ${(props) => props.theme.spacing(6)}; - min-height: 40vh; - width: 40%; - flex-shrink: 0; - margin-right: ${(props) => props.theme.spacing(5)}; - margin-top: ${(props) => props.theme.spacing(3)}; -`; diff --git a/src/pages/people/utils/postProcess.js b/src/pages/people/utils/postProcess.js new file mode 100644 index 0000000..57b7249 --- /dev/null +++ b/src/pages/people/utils/postProcess.js @@ -0,0 +1,31 @@ +export const postProcessNullify = (formData) => { + // { a: 1, b: 2 } => [['a', 1], ['b', 2]] + const processedEntries = Object.entries(formData).map((pair) => { + const [key, value] = pair; + if (value === '') { + console.log('Поле', key, 'было заменено на', null); + return [key, null]; + } + return pair; + }); + + // [['a', 1], ['b', 2]] => { a: 1, b: 2 } + return Object.fromEntries(processedEntries); +}; + +export const postProcess = (formData) => { + // { a: 1, b: 2 }, => [['a', 1], ['b', 2]] + + const processedEntries = Object.entries(formData).filter((pair) => { + const [key, value] = pair; + if (value === '' || value === undefined) { + console.log( + `Поле ${key} не было сохранено на сервере из-за значения:`, + JSON.stringify(value) + ); + return false; + } + return true; + }); + return Object.fromEntries(processedEntries); +}; diff --git a/src/styles/themes/dark.js b/src/styles/themes/dark.js index cb5b532..924b8b5 100644 --- a/src/styles/themes/dark.js +++ b/src/styles/themes/dark.js @@ -1,7 +1,9 @@ import { color } from '../color'; -import { createMuiTheme } from '@material-ui/core'; +import { createMuiTheme } from '@material-ui/core/styles'; +import protoTheme from './proto'; const dark = createMuiTheme({ + ...protoTheme, name: 'dark', type: 'dark', palette: { diff --git a/src/styles/themes/light.js b/src/styles/themes/light.js index 40f83be..c8106f4 100644 --- a/src/styles/themes/light.js +++ b/src/styles/themes/light.js @@ -1,7 +1,9 @@ import { color } from '../color'; -import { createMuiTheme } from '@material-ui/core'; +import { createMuiTheme } from '@material-ui/core/styles'; +import protoTheme from './proto'; const light = createMuiTheme({ + ...protoTheme, name: 'light', type: 'light', palette: { diff --git a/src/styles/themes/proto.js b/src/styles/themes/proto.js index 986680c..062e9ad 100644 --- a/src/styles/themes/proto.js +++ b/src/styles/themes/proto.js @@ -1,5 +1,7 @@ import { createMuiTheme } from '@material-ui/core/styles'; -const MuiTheme = createMuiTheme(); +const MuiTheme = createMuiTheme({ + spacing: (gridSize) => gridSize * 8 + 'px', +}); export default MuiTheme;