From 8cf1f1f577eaecdec085a017256822f9f6e0a8f8 Mon Sep 17 00:00:00 2001 From: Ruidy Date: Tue, 2 Jun 2020 22:49:13 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8F=97=20Folder=20Structure=20and=20Gener?= =?UTF-8?q?al=20Refactoring=20(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: AddEducation folder * refactor: AddExperience folder * refactor: separate Dashboard Buttons, Experience section * refactor: add Dashboard Education section * refactor: Developers folder * refactor: EditProfile folder * refactor: use React.memo * refactor: Post folder * refactor: Posts folder * refactor: Profile folder * refactor: delete useless store/auth folder --- src/pages/AddEducation.tsx | 188 ----------- src/pages/AddEducation/Form.tsx | 109 ++++++ src/pages/AddEducation/index.tsx | 113 +++++++ src/pages/AddExperience.tsx | 189 ----------- src/pages/AddExperience/Form.tsx | 108 ++++++ src/pages/AddExperience/index.tsx | 115 +++++++ src/pages/Dashboard.tsx | 146 -------- src/pages/Dashboard/Buttons.tsx | 24 ++ src/pages/Dashboard/EducationSection.tsx | 49 +++ src/pages/Dashboard/ExperienceSection.tsx | 51 +++ src/pages/Dashboard/index.tsx | 84 +++++ .../Developers/Profile.tsx} | 6 +- .../{Developers.tsx => Developers/index.tsx} | 16 +- src/pages/EditProfile.tsx | 319 ------------------ src/pages/EditProfile/Form.tsx | 211 ++++++++++++ src/pages/EditProfile/index.tsx | 147 ++++++++ src/pages/Landing.tsx | 2 +- src/pages/NotFound.tsx | 2 +- src/pages/Post.tsx | 115 ------- src/pages/Post/Comments.tsx | 26 ++ src/pages/Post/Display.tsx | 24 ++ src/pages/Post/Form.tsx | 28 ++ src/pages/Post/index.tsx | 84 +++++ src/pages/Posts/Form.tsx | 22 ++ src/pages/Posts/Item.tsx | 41 +++ src/pages/{Posts.tsx => Posts/index.tsx} | 64 +--- src/pages/Profile.tsx | 239 ------------- src/pages/Profile/About.tsx | 26 ++ src/pages/Profile/Education.tsx | 38 +++ src/pages/Profile/Experience.tsx | 34 ++ src/pages/Profile/Github.tsx | 50 +++ src/pages/Profile/Top.tsx | 68 ++++ src/pages/Profile/index.tsx | 69 ++++ src/static/img/education.svg | 1 + src/static/img/experience.svg | 1 + src/static/img/github.svg | 1 + src/store/auth/index.ts | 36 -- 37 files changed, 1555 insertions(+), 1291 deletions(-) delete mode 100644 src/pages/AddEducation.tsx create mode 100644 src/pages/AddEducation/Form.tsx create mode 100644 src/pages/AddEducation/index.tsx delete mode 100644 src/pages/AddExperience.tsx create mode 100644 src/pages/AddExperience/Form.tsx create mode 100644 src/pages/AddExperience/index.tsx delete mode 100644 src/pages/Dashboard.tsx create mode 100644 src/pages/Dashboard/Buttons.tsx create mode 100644 src/pages/Dashboard/EducationSection.tsx create mode 100644 src/pages/Dashboard/ExperienceSection.tsx create mode 100644 src/pages/Dashboard/index.tsx rename src/{components/DevProfile.tsx => pages/Developers/Profile.tsx} (87%) rename src/pages/{Developers.tsx => Developers/index.tsx} (69%) delete mode 100644 src/pages/EditProfile.tsx create mode 100644 src/pages/EditProfile/Form.tsx create mode 100644 src/pages/EditProfile/index.tsx delete mode 100644 src/pages/Post.tsx create mode 100644 src/pages/Post/Comments.tsx create mode 100644 src/pages/Post/Display.tsx create mode 100644 src/pages/Post/Form.tsx create mode 100644 src/pages/Post/index.tsx create mode 100644 src/pages/Posts/Form.tsx create mode 100644 src/pages/Posts/Item.tsx rename src/pages/{Posts.tsx => Posts/index.tsx} (51%) delete mode 100644 src/pages/Profile.tsx create mode 100644 src/pages/Profile/About.tsx create mode 100644 src/pages/Profile/Education.tsx create mode 100644 src/pages/Profile/Experience.tsx create mode 100644 src/pages/Profile/Github.tsx create mode 100644 src/pages/Profile/Top.tsx create mode 100644 src/pages/Profile/index.tsx create mode 100644 src/static/img/education.svg create mode 100644 src/static/img/experience.svg create mode 100644 src/static/img/github.svg delete mode 100644 src/store/auth/index.ts diff --git a/src/pages/AddEducation.tsx b/src/pages/AddEducation.tsx deleted file mode 100644 index bdfb2c8..0000000 --- a/src/pages/AddEducation.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import React, {FC, useState, FormEvent} from 'react'; -// Routing -import {Link} from 'react-router-dom'; -import Routes from '../constants/routes'; -// Redux -import {WithFirebaseProps} from 'react-redux-firebase'; -import {enhance} from '../store/firebase'; -// Style -import FormHeader from '../components/FormHeader'; -import Alert from '../components/Alert'; -// Typing -import Dev from '../models/Dev'; -import User from '../models/User'; -import IAlert, {formAlert} from '../types/Alert'; -import Education from '../types/Education'; -import {parseDate} from '../types/TimePeriod'; -// Form -import useForm from '../hooks'; - -interface FormData { - school: string; - degree: string; - field: string; - from: string; - to: string; - current: boolean; - description: string; -} - -interface IProps extends Dev, WithFirebaseProps {} -/** - * Form to add an Education step to Profile - */ -const AddEducation: FC = ({firebase, educations}) => { - const [alert, setAlert] = useState(formAlert); - - const initFormData: FormData = { - school: '', - degree: '', - field: '', - from: '', - to: '', - current: false, - description: '', - }; - const {formData, handleChange, handleCheckboxesChange, resetForm} = useForm< - FormData - >(initFormData); - - const isDisabled: boolean = formData.school === '' || formData.degree === ''; - - const handleSubmit = (e: FormEvent): void => { - e.preventDefault(); - const makeEducation = ({ - school, - degree, - from, - field, - to, - current, - description, - }: FormData): Education => { - if (current) to = 'Current'; - const newEdu: Education = { - id: educations.length, - school, - degree, - field, - from: parseDate(from), - to: parseDate(to), - description, - }; - return newEdu; - }; - const newEdu = makeEducation(formData); - - try { - firebase.updateProfile( - {educations: [...educations, newEdu]}, - {useSet: true, merge: true}, - ); - setAlert({ - show: true, - color: 'success', - text: - 'Profile successfully updated. You may continue or go back to your dashboard.', - }); - resetForm(); - } catch (err) { - setAlert({...alert, show: true}); - } - }; - - return ( -
- - -
-
- -
-
- -
-
- -
-
-

From Date

- -
-
-

To Date

- -
-
-

- {' '} - Current School -

-
-
- -
- {alert.show && } - - - Go Back - - -
- ); -}; - -export default enhance(AddEducation); diff --git a/src/pages/AddEducation/Form.tsx b/src/pages/AddEducation/Form.tsx new file mode 100644 index 0000000..802b95a --- /dev/null +++ b/src/pages/AddEducation/Form.tsx @@ -0,0 +1,109 @@ +import React, {FC} from 'react'; +// Routing +import {Link} from 'react-router-dom'; +import Routes from '../../constants/routes'; + +import {IEducationForm} from '.'; + +interface IProps { + isDisabled: boolean; + formData: IEducationForm; + handleSubmit: (e: React.FormEvent) => void; + handleChange: ( + e: React.ChangeEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => void; + handleCheckboxesChange: (e: React.ChangeEvent) => void; +} + +const AddEducationForm: FC = ({ + handleSubmit, + formData, + handleChange, + handleCheckboxesChange, + isDisabled, +}) => ( +
+
+ +
+
+ +
+
+ +
+
+

From Date

+ +
+
+

To Date

+ +
+
+

+ {' '} + Current School +

+
+
+ +
+ + + Go Back + +
+); + +export default AddEducationForm; diff --git a/src/pages/AddEducation/index.tsx b/src/pages/AddEducation/index.tsx new file mode 100644 index 0000000..418fd21 --- /dev/null +++ b/src/pages/AddEducation/index.tsx @@ -0,0 +1,113 @@ +import React, {FC, useState, FormEvent} from 'react'; +// Redux +import {WithFirebaseProps} from 'react-redux-firebase'; +import {enhance} from '../../store/firebase'; +// Style +import FormHeader from '../../components/FormHeader'; +import Alert from '../../components/Alert'; +// Typing +import Dev from '../../models/Dev'; +import User from '../../models/User'; +import IAlert, {formAlert} from '../../types/Alert'; +import Education from '../../types/Education'; +import {parseDate} from '../../types/TimePeriod'; +// Form +import useForm from '../../hooks'; +import AddEducationForm from './Form'; + +export interface IEducationForm { + school: string; + degree: string; + field: string; + from: string; + to: string; + current: boolean; + description: string; +} + +interface IProps extends Dev, WithFirebaseProps {} +/** + * Form to add an Education step to Profile + */ +const AddEducation: FC = ({firebase, educations}) => { + const [alert, setAlert] = useState(formAlert); + + const initFormData: IEducationForm = { + school: '', + degree: '', + field: '', + from: '', + to: '', + current: false, + description: '', + }; + const {formData, handleChange, handleCheckboxesChange, resetForm} = useForm< + IEducationForm + >(initFormData); + + const isDisabled: boolean = formData.school === '' || formData.degree === ''; + + const handleSubmit = (e: FormEvent): void => { + e.preventDefault(); + const makeEducation = ({ + school, + degree, + from, + field, + to, + current, + description, + }: IEducationForm): Education => { + if (current) to = 'Current'; + const newEdu: Education = { + id: educations.length, + school, + degree, + field, + from: parseDate(from), + to: parseDate(to), + description, + }; + return newEdu; + }; + const newEdu = makeEducation(formData); + + try { + firebase.updateProfile( + {educations: [...educations, newEdu]}, + {useSet: true, merge: true}, + ); + setAlert({ + show: true, + color: 'success', + text: + 'Profile successfully updated. You may continue or go back to your dashboard.', + }); + resetForm(); + } catch (err) { + setAlert({...alert, show: true}); + } + }; + + return ( +
+ + + {alert.show && } + +
+ ); +}; + +export default enhance(AddEducation); diff --git a/src/pages/AddExperience.tsx b/src/pages/AddExperience.tsx deleted file mode 100644 index 7382cab..0000000 --- a/src/pages/AddExperience.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import React, {FC, useState, FormEvent} from 'react'; -// Routing -import {Link} from 'react-router-dom'; -import Routes from '../constants/routes'; -// Redux -import {WithFirebaseProps} from 'react-redux-firebase'; -import {enhance} from '../store/firebase'; -// Style -import FormHeader from '../components/FormHeader'; -import Alert from '../components/Alert'; -// Typing -import Dev from '../models/Dev'; -import User from '../models/User'; -import IAlert, {formAlert} from '../types/Alert'; -import Experience from '../types/Experience'; -import {parseDate} from '../types/TimePeriod'; -// Form -import useForm from '../hooks'; - -interface FormData { - position: string; - company: string; - location: string; - from: string; - to: string; - current: boolean; - description: string; -} - -interface IProps extends Dev, WithFirebaseProps {} - -/** - * Form to add an Experience step to Profile - */ -const AddExperience: FC = ({firebase, experiences}) => { - const [alert, setAlert] = useState(formAlert); - - const initFormData: FormData = { - position: '', - company: '', - location: '', - from: '', - to: '', - current: false, - description: '', - }; - const {formData, handleChange, handleCheckboxesChange, resetForm} = useForm< - FormData - >(initFormData); - - const isDisabled: boolean = - formData.position === '' || formData.company === ''; - - const handleSubmit = (e: FormEvent): void => { - e.preventDefault(); - const makeExperience = ({ - position, - company, - from, - location, - to, - current, - description, - }: FormData): Experience => { - if (current) to = 'Current'; - const newExp: Experience = { - id: experiences.length, - position, - company, - location, - from: parseDate(from), - to: parseDate(to), - description, - }; - return newExp; - }; - const newExp = makeExperience(formData); - - try { - firebase.updateProfile( - {experiences: [...experiences, newExp]}, - {useSet: true, merge: true}, - ); - setAlert({ - show: true, - color: 'success', - text: - 'Profile successfully updated. You may continue or go back to your dashboard.', - }); - resetForm(); - } catch (err) { - setAlert({...alert, show: true}); - } - }; - - return ( -
- - -
-
- -
-
- -
-
- -
-
-

From Date

- -
-
-

To Date

- -
-
-

- {' '} - Current Job -

-
-
- -
- {alert.show && } - - - Go Back - - -
- ); -}; - -export default enhance(AddExperience); diff --git a/src/pages/AddExperience/Form.tsx b/src/pages/AddExperience/Form.tsx new file mode 100644 index 0000000..14fb93a --- /dev/null +++ b/src/pages/AddExperience/Form.tsx @@ -0,0 +1,108 @@ +import React, {FC} from 'react'; +// Routing +import {Link} from 'react-router-dom'; +import Routes from '../../constants/routes'; + +import {IExperienceForm} from '.'; + +interface IProps { + isDisabled: boolean; + formData: IExperienceForm; + handleSubmit: (e: React.FormEvent) => void; + handleChange: ( + e: React.ChangeEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => void; + handleCheckboxesChange: (e: React.ChangeEvent) => void; +} + +const AddExperienceForm: FC = ({ + handleSubmit, + formData, + handleChange, + handleCheckboxesChange, + isDisabled, +}) => ( +
+
+ +
+
+ +
+
+ +
+
+

From Date

+ +
+
+

To Date

+ +
+
+

+ {' '} + Current Job +

+
+
+ +
+ + + Go Back + +
+); + +export default AddExperienceForm; diff --git a/src/pages/AddExperience/index.tsx b/src/pages/AddExperience/index.tsx new file mode 100644 index 0000000..26b5312 --- /dev/null +++ b/src/pages/AddExperience/index.tsx @@ -0,0 +1,115 @@ +import React, {FC, useState, FormEvent} from 'react'; +// Redux +import {WithFirebaseProps} from 'react-redux-firebase'; +import {enhance} from '../../store/firebase'; +// Style +import FormHeader from '../../components/FormHeader'; +import Alert from '../../components/Alert'; +// Typing +import Dev from '../../models/Dev'; +import User from '../../models/User'; +import IAlert, {formAlert} from '../../types/Alert'; +import Experience from '../../types/Experience'; +import {parseDate} from '../../types/TimePeriod'; +// Form +import useForm from '../../hooks'; +import AddExperienceForm from './Form'; + +export interface IExperienceForm { + position: string; + company: string; + location: string; + from: string; + to: string; + current: boolean; + description: string; +} + +interface IProps extends Dev, WithFirebaseProps {} + +/** + * Form to add an Experience step to Profile + */ +const AddExperience: FC = ({firebase, experiences}) => { + const [alert, setAlert] = useState(formAlert); + + const initFormData: IExperienceForm = { + position: '', + company: '', + location: '', + from: '', + to: '', + current: false, + description: '', + }; + const {formData, handleChange, handleCheckboxesChange, resetForm} = useForm< + IExperienceForm + >(initFormData); + + const isDisabled: boolean = + formData.position === '' || formData.company === ''; + + const handleSubmit = (e: FormEvent): void => { + e.preventDefault(); + const makeExperience = ({ + position, + company, + from, + location, + to, + current, + description, + }: IExperienceForm): Experience => { + if (current) to = 'Current'; + const newExp: Experience = { + id: experiences.length, + position, + company, + location, + from: parseDate(from), + to: parseDate(to), + description, + }; + return newExp; + }; + const newExp = makeExperience(formData); + + try { + firebase.updateProfile( + {experiences: [...experiences, newExp]}, + {useSet: true, merge: true}, + ); + setAlert({ + show: true, + color: 'success', + text: + 'Profile successfully updated. You may continue or go back to your dashboard.', + }); + resetForm(); + } catch (err) { + setAlert({...alert, show: true}); + } + }; + + return ( +
+ + + {alert.show && } + +
+ ); +}; + +export default enhance(AddExperience); diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx deleted file mode 100644 index ddbf17b..0000000 --- a/src/pages/Dashboard.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React, {FC, MouseEvent} from 'react'; -// Redux -import {WithFirebaseProps} from 'react-redux-firebase'; -import {enhance} from '../store/firebase'; -// Routing -import {Link} from 'react-router-dom'; -import Routes from '../constants/routes'; -// Style -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import { - faUserCircle, - faGraduationCap, - faUserSlash, -} from '@fortawesome/free-solid-svg-icons'; -import {faBlackTie} from '@fortawesome/free-brands-svg-icons'; -import Header from '../components/Header'; -// Types -import Dev from '../models/Dev'; -import User from '../models/User'; -import Experience from '../types/Experience'; -import {getTimePeriod} from '../types/TimePeriod'; -import Education from '../types/Education'; - -interface IProps extends Dev, WithFirebaseProps {} -/** - * Main page from which a Dev can peek and edit its own profile. - */ -const Dashboard: FC = ({ - firebase, - displayName, - experiences, - educations, -}) => { - /** turns account to inactive then logs user out */ - const deleteAccount = () => { - firebase.updateProfile({isActive: false}, {useSet: true, merge: true}); - firebase.logout(); - }; - - /** - * - * @param id key of the entry to remove - * @param entries array of credential educations - */ - const deleteEduEntry = (id: number, entries: Education[]) => ( - e: MouseEvent, - ) => { - firebase.updateProfile({ - educations: entries.filter((e: Education) => e.id !== id), - }); - }; - /** - * - * @param id key of the entry to remove - * @param entries array of credential experiences - */ - const deleteExpEntry = (id: number, entries: Experience[]) => ( - e: MouseEvent, - ) => { - firebase.updateProfile({ - experiences: entries.filter((e: Experience) => e.id !== id), - }); - }; - - return ( -
-
-
- - Edit Profile - - - Add Experience - - - Add Education - -
- -

Experience Credentials

- - - - - - - - - - - {experiences?.map((exp: Experience) => ( - - - - - - - ))} - -
CompanyTitleYears
{exp.company}{exp.position}{getTimePeriod(exp.from, exp.to)} - -
- -

Education Credentials

- - - - - - - - - - - {educations?.map((edu: Education, i: number) => ( - - - - - - - ))} - -
SchoolDegreeYears
{edu.school}{edu.degree}{getTimePeriod(edu.from, edu.to)} - -
-
- -
-
- ); -}; - -export default enhance(Dashboard); diff --git a/src/pages/Dashboard/Buttons.tsx b/src/pages/Dashboard/Buttons.tsx new file mode 100644 index 0000000..13bd5c0 --- /dev/null +++ b/src/pages/Dashboard/Buttons.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +// Routing +import {Link} from 'react-router-dom'; +import Routes from '../../constants/routes'; +// Styling +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faUserCircle, faGraduationCap} from '@fortawesome/free-solid-svg-icons'; +import {faBlackTie} from '@fortawesome/free-brands-svg-icons'; + +const DashBoardButtons = () => ( +
+ + Edit Profile + + + Add Experience + + + Add Education + +
+); + +export default DashBoardButtons; diff --git a/src/pages/Dashboard/EducationSection.tsx b/src/pages/Dashboard/EducationSection.tsx new file mode 100644 index 0000000..ee1c668 --- /dev/null +++ b/src/pages/Dashboard/EducationSection.tsx @@ -0,0 +1,49 @@ +import React, {FC} from 'react'; +// Typing +import Education from '../../types/Education'; + +import {getTimePeriod} from '../../types/TimePeriod'; + +interface IProps { + educations: Education[]; + handleDelete: ( + id: number, + entries: Education[], + ) => (e: React.MouseEvent) => void; +} +const DashboardEducationSection: FC = ({educations, handleDelete}) => { + return ( + <> +

Education Credentials

+ + + + + + + + + + + {educations?.map((edu: Education, i: number) => ( + + + + + + + ))} + +
SchoolDegreeYears
{edu.school}{edu.degree}{getTimePeriod(edu.from, edu.to)} + +
+ + ); +}; + +export default DashboardEducationSection; diff --git a/src/pages/Dashboard/ExperienceSection.tsx b/src/pages/Dashboard/ExperienceSection.tsx new file mode 100644 index 0000000..20e4678 --- /dev/null +++ b/src/pages/Dashboard/ExperienceSection.tsx @@ -0,0 +1,51 @@ +import React, {FC} from 'react'; +// Typing +import Experience from '../../types/Experience'; +import {getTimePeriod} from '../../types/TimePeriod'; + +interface IProps { + experiences: Experience[]; + handleDelete: ( + id: number, + entries: Experience[], + ) => (e: React.MouseEvent) => void; +} +const DashboardExperienceSection: FC = ({ + experiences, + handleDelete, +}) => { + return ( + <> +

Experience Credentials

+ + + + + + + + + + + {experiences?.map((exp: Experience) => ( + + + + + + + ))} + +
CompanyTitleYears
{exp.company}{exp.position}{getTimePeriod(exp.from, exp.to)} + +
+ + ); +}; + +export default DashboardExperienceSection; diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx new file mode 100644 index 0000000..175edb8 --- /dev/null +++ b/src/pages/Dashboard/index.tsx @@ -0,0 +1,84 @@ +import React, {FC, MouseEvent} from 'react'; +// Redux +import {WithFirebaseProps} from 'react-redux-firebase'; +import {enhance} from '../../store/firebase'; +// Style +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faUserSlash} from '@fortawesome/free-solid-svg-icons'; + +import DashBoardButtons from './Buttons'; +import Header from '../../components/Header'; +// Types +import Dev from '../../models/Dev'; +import User from '../../models/User'; +import Experience from '../../types/Experience'; +import Education from '../../types/Education'; +import DashboardExperienceSection from './ExperienceSection'; +import DashboardEducationSection from './EducationSection'; + +interface IProps extends Dev, WithFirebaseProps {} +/** + * Main page from which a Dev can peek and edit its own profile. + */ +const Dashboard: FC = ({ + firebase, + displayName, + experiences, + educations, +}) => { + /** turns account to inactive then logs user out */ + const deleteAccount = () => { + firebase.updateProfile({isActive: false}, {useSet: true, merge: true}); + firebase.logout(); + }; + + /** + * + * @param id key of the entry to remove + * @param entries array of credential educations + */ + const deleteEduEntry = (id: number, entries: Education[]) => ( + e: MouseEvent, + ) => { + firebase.updateProfile({ + educations: entries.filter((e: Education) => e.id !== id), + }); + }; + /** + * + * @param id key of the entry to remove + * @param entries array of credential experiences + */ + const deleteExpEntry = (id: number, entries: Experience[]) => ( + e: MouseEvent, + ) => { + firebase.updateProfile({ + experiences: entries.filter((e: Experience) => e.id !== id), + }); + }; + + return ( +
+
+ + + + + + +
+ +
+
+ ); +}; + +export default enhance(Dashboard); diff --git a/src/components/DevProfile.tsx b/src/pages/Developers/Profile.tsx similarity index 87% rename from src/components/DevProfile.tsx rename to src/pages/Developers/Profile.tsx index 7006c06..c3c34c8 100644 --- a/src/components/DevProfile.tsx +++ b/src/pages/Developers/Profile.tsx @@ -5,8 +5,8 @@ import {Link} from 'react-router-dom'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {faCheck} from '@fortawesome/free-solid-svg-icons'; // Typing -import {DevSummary, getDescription} from '../models/Dev'; -import Routes from '../constants/routes'; +import {DevSummary, getDescription} from '../../models/Dev'; +import Routes from '../../constants/routes'; /** * Present a dev profile succintly. Redirect to dev profile on click. @@ -41,4 +41,4 @@ const DevProfile: FC = ({ ); -export default DevProfile; +export default React.memo(DevProfile); diff --git a/src/pages/Developers.tsx b/src/pages/Developers/index.tsx similarity index 69% rename from src/pages/Developers.tsx rename to src/pages/Developers/index.tsx index 2706821..b967d01 100644 --- a/src/pages/Developers.tsx +++ b/src/pages/Developers/index.tsx @@ -3,15 +3,19 @@ import React, {FC} from 'react'; import {compose} from 'redux'; import {connect} from 'react-redux'; import {firestoreConnect} from 'react-redux-firebase'; -import {RootState} from '../store'; +import {RootState} from '../../store'; // Style -import Header from '../components/Header'; -import DevProfile from '../components/DevProfile'; -import {DevSummary} from '../models/Dev'; +import DevProfile from './Profile'; +import Header from '../../components/Header'; + +import {DevSummary} from '../../models/Dev'; + +import Collections from '../../constants/collections'; interface IProps { developers: DevSummary[]; } + /** * Developers list page */ @@ -31,8 +35,8 @@ const Developers: FC = ({developers}) => ( ); export default compose( - firestoreConnect(() => ['users']), // or { collection: 'users' } - connect((state: RootState, props) => ({ + firestoreConnect(() => [Collections.USERS]), + connect((state: RootState) => ({ developers: state.firestore.ordered.users, })), )(Developers); diff --git a/src/pages/EditProfile.tsx b/src/pages/EditProfile.tsx deleted file mode 100644 index 3fe5b5e..0000000 --- a/src/pages/EditProfile.tsx +++ /dev/null @@ -1,319 +0,0 @@ -import React, {FC, useState} from 'react'; -import {Link} from 'react-router-dom'; -import Routes from '../constants/routes'; -// Redux -import {enhance} from '../store/firebase'; -import {WithFirebaseProps} from 'react-redux-firebase'; -// Style -import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; -import { - faTwitter, - faFacebook, - faYoutube, - faLinkedin, - faInstagram, -} from '@fortawesome/free-brands-svg-icons'; -import FormHeader from '../components/FormHeader'; -import Alert from '../components/Alert'; -import Statuses from '../constants/statuses'; -// Form -import useForm from '../hooks'; -import getGithubRepos from '../services/github'; -// Typing -import Dev from '../models/Dev'; -import User from '../models/User'; -import Links, {parseLink, getGithubLink} from '../types/Links'; -import IAlert, {formAlert} from '../types/Alert'; - -interface FormData { - status: string; - company: string; - website: string; - location: string; - skills: string; - github: string; - bio: string; - facebook: string; - linkedin: string; - instagram: string; - twitter: string; - youtube: string; -} - -interface IProps extends Dev, WithFirebaseProps {} - -/** - * Form to update dev's personal information. - */ -const EditProfile: FC = ({ - firebase, - status, - skills, - company, - links, - location, - bio, - github, -}) => { - const [showLinks, setShowLinks] = useState(false); - const [alert, setAlert] = useState(formAlert); - - const initFormData = { - status: status ?? 'Developer', - company: company, - location: location ?? '', - bio: bio ?? '', - skills: skills?.toString() ?? '', - website: links?.website ?? '', - github: github ?? '', - facebook: links?.facebook ?? '', - linkedin: links?.linkedin ?? '', - instagram: links?.instagram ?? '', - twitter: links?.twitter ?? '', - youtube: links?.youtube ?? '', - }; - - const {formData, handleChange} = useForm(initFormData); - - /** construct profile object from formData */ - const makeProfile = async ({ - status, - company, - location, - bio, - website, - instagram, - facebook, - linkedin, - twitter, - github, - youtube, - skills, - }: FormData) => { - const newLinks: Links = { - website: parseLink(website), - instagram: parseLink(instagram), - facebook: parseLink(facebook), - linkedin: parseLink(linkedin), - twitter: parseLink(twitter), - github: getGithubLink(github), - youtube: parseLink(youtube), - }; - const newSkills: string[] = skills?.split(','); - const newRepos = await getGithubRepos(github); - return { - status, - company, - location, - bio, - github, - links: newLinks, - skills: newSkills, - repos: newRepos, - }; - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - const updatedDev = await makeProfile(formData); - try { - firebase.updateProfile(updatedDev, {useSet: true, merge: true}); - setAlert({ - show: true, - color: 'success', - text: - 'Profile successfully updated. You may go back to your dashboard.', - }); - } catch (err) { - console.error(err); - setAlert({...alert, show: true}); - } - }; - - const isDisabled: boolean = formData.status === '' || formData.skills === ''; - - const toggleSocialLinks = () => setShowLinks(!showLinks); - return ( -
- - -
-
- - - Give us an idea of where you are at in your career - -
-
- - - Could be your own company or one you work for - -
-
- - - Could be your own or a company website - -
-
- - - City & state suggested (eg. Boston, MA) - -
-
- - - Please use comma separated values (eg. HTML,CSS,JavaScript,PHP) - -
-
- - - If you want your latest repos and a Github link, include your - username - -
-
- - Tell us a little about yourself -
- -
- - Optional -
- - {showLinks && ( - <> -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - )} - {alert.show && } - - - Go Back - - -
- ); -}; - -export default enhance(EditProfile); diff --git a/src/pages/EditProfile/Form.tsx b/src/pages/EditProfile/Form.tsx new file mode 100644 index 0000000..9df3481 --- /dev/null +++ b/src/pages/EditProfile/Form.tsx @@ -0,0 +1,211 @@ +import React, {FC} from 'react'; +// Routing +import {Link} from 'react-router-dom'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import { + faFacebook, + faInstagram, + faLinkedin, + faTwitter, + faYoutube, +} from '@fortawesome/free-brands-svg-icons'; +import {IProfileForm} from '.'; + +import Routes from '../../constants/routes'; +import Statuses from '../../constants/statuses'; + +interface IProps { + showLinks: boolean; + isDisabled: boolean; + formData: IProfileForm; + handleSubmit: (e: React.FormEvent) => void; + handleChange: ( + e: React.ChangeEvent< + HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement + >, + ) => void; + toggleSocialLinks: () => void; +} + +const EditProfileForm: FC = ({ + showLinks, + handleSubmit, + formData, + handleChange, + isDisabled, + toggleSocialLinks, +}) => ( +
+
+ + + Give us an idea of where you are at in your career + +
+
+ + + Could be your own company or one you work for + +
+
+ + + Could be your own or a company website + +
+
+ + + City & state suggested (eg. Boston, MA) + +
+
+ + + Please use comma separated values (eg. HTML,CSS,JavaScript,PHP) + +
+
+ + + If you want your latest repos and a Github link, include your username + +
+
+ + Tell us a little about yourself +
+ +
+ + Optional +
+ + {showLinks && ( + <> +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + )} + + + + Go Back + +
+); + +export default EditProfileForm; diff --git a/src/pages/EditProfile/index.tsx b/src/pages/EditProfile/index.tsx new file mode 100644 index 0000000..9c10d3d --- /dev/null +++ b/src/pages/EditProfile/index.tsx @@ -0,0 +1,147 @@ +import React, {FC, useState} from 'react'; +// Redux +import {enhance} from '../../store/firebase'; +import {WithFirebaseProps} from 'react-redux-firebase'; +// Style +import FormHeader from '../../components/FormHeader'; +import Alert from '../../components/Alert'; +// Form +import useForm from '../../hooks'; +import getGithubRepos from '../../services/github'; +// Typing +import Dev from '../../models/Dev'; +import User from '../../models/User'; +import Links, {parseLink, getGithubLink} from '../../types/Links'; +import IAlert, {formAlert} from '../../types/Alert'; +import EditProfileForm from './Form'; + +export interface IProfileForm { + status: string; + company: string; + website: string; + location: string; + skills: string; + github: string; + bio: string; + facebook: string; + linkedin: string; + instagram: string; + twitter: string; + youtube: string; +} + +interface IProps extends Dev, WithFirebaseProps {} + +/** + * Form to update dev's personal information. + */ +const EditProfile: FC = ({ + firebase, + status, + skills, + company, + links, + location, + bio, + github, +}) => { + const [showLinks, setShowLinks] = useState(false); + const [alert, setAlert] = useState(formAlert); + + const initFormData = { + status: status ?? 'Developer', + company: company, + location: location ?? '', + bio: bio ?? '', + skills: skills?.toString() ?? '', + website: links?.website ?? '', + github: github ?? '', + facebook: links?.facebook ?? '', + linkedin: links?.linkedin ?? '', + instagram: links?.instagram ?? '', + twitter: links?.twitter ?? '', + youtube: links?.youtube ?? '', + }; + + const {formData, handleChange} = useForm(initFormData); + + /** construct profile object from formData */ + const makeProfile = async ({ + status, + company, + location, + bio, + website, + instagram, + facebook, + linkedin, + twitter, + github, + youtube, + skills, + }: IProfileForm) => { + const newLinks: Links = { + website: parseLink(website), + instagram: parseLink(instagram), + facebook: parseLink(facebook), + linkedin: parseLink(linkedin), + twitter: parseLink(twitter), + github: getGithubLink(github), + youtube: parseLink(youtube), + }; + const newSkills: string[] = skills?.split(','); + const newRepos = await getGithubRepos(github); + return { + status, + company, + location, + bio, + github, + links: newLinks, + skills: newSkills, + repos: newRepos, + }; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const updatedDev = await makeProfile(formData); + try { + firebase.updateProfile(updatedDev, {useSet: true, merge: true}); + setAlert({ + show: true, + color: 'success', + text: + 'Profile successfully updated. You may go back to your dashboard.', + }); + } catch (err) { + console.error(err); + setAlert({...alert, show: true}); + } + }; + + const isDisabled: boolean = formData.status === '' || formData.skills === ''; + + const toggleSocialLinks = () => setShowLinks(!showLinks); + + return ( +
+ + + {alert.show && } + +
+ ); +}; + +export default enhance(EditProfile); diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx index e02a2e4..b96503b 100644 --- a/src/pages/Landing.tsx +++ b/src/pages/Landing.tsx @@ -28,4 +28,4 @@ const Landing: FC = () => ( ); -export default Landing; +export default React.memo(Landing); diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx index c4383bb..9da1cd7 100644 --- a/src/pages/NotFound.tsx +++ b/src/pages/NotFound.tsx @@ -25,4 +25,4 @@ const NotFound: FC = () => ( ); -export default NotFound; +export default React.memo(NotFound); diff --git a/src/pages/Post.tsx b/src/pages/Post.tsx deleted file mode 100644 index 4c5b967..0000000 --- a/src/pages/Post.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import React, {FC} from 'react'; -// Routing -import {useParams, Link} from 'react-router-dom'; -import Routes from '../constants/routes'; -// Redux -import {compose} from '@reduxjs/toolkit'; -import {connect, useSelector} from 'react-redux'; -import {firestoreConnect, WithFirestoreProps} from 'react-redux-firebase'; -import {RootState} from '../store'; -import {selectProfile} from '../store/firebase'; -import Collections from '../constants/collections'; -// Typing -import Post from '../models/Post'; -import Comment from '../types/Comment'; -// Form -import useForm from '../hooks'; - -interface IProps extends WithFirestoreProps { - post: Post; -} - -/** - * Display a Post and the related comments. Shows a form to add a comment. - */ -const PostPage: FC = ({post, firestore}) => { - const {avatarUrl, displayName} = useSelector(selectProfile); - const newComment: Comment = { - name: displayName ?? 'error', - avatarUrl: avatarUrl ?? 'error', - text: '', - }; - - const {formData, handleChange, resetForm} = useForm(newComment); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - firestore - .update(`${Collections.POSTS}/${post.id}`, { - comments: [...post.comments, formData], - }) - .then(() => resetForm()) - .catch(err => console.error(err)); - }; - - return ( -
- - Back To Posts - - -
-
- - {post.name} -

{post.name}

- -
-
-

{post.text}

-
-
- -
-
-

Leave A Comment

-
-
- - -
-
- -
- {post.comments?.map((c: Comment, i: number) => ( -
- -
-

{c.text}

-
-
- ))} -
-
- ); -}; - -/** - * Container to fetch id params from thr URI and pass it to Profile page - */ -const PostPageContainer: FC = () => { - const {id} = useParams(); - const Component = compose( - firestoreConnect(() => [`${Collections.POSTS}/${id}`]), - connect(({firestore: {data}}: RootState) => ({ - post: data.posts && {...data.posts[id], id}, - })), - )(PostPage); - - return ; -}; - -export default PostPageContainer; diff --git a/src/pages/Post/Comments.tsx b/src/pages/Post/Comments.tsx new file mode 100644 index 0000000..9a946ca --- /dev/null +++ b/src/pages/Post/Comments.tsx @@ -0,0 +1,26 @@ +import React, {FC} from 'react'; +import Comment from '../../types/Comment'; + +interface IProps { + comments: Comment[]; +} + +const PostComments: FC = ({comments}) => ( +
+ {comments?.map(({name, avatarUrl, text}: Comment, i: number) => ( +
+ +
+

{text}

+
+
+ ))} +
+); + +export default PostComments; diff --git a/src/pages/Post/Display.tsx b/src/pages/Post/Display.tsx new file mode 100644 index 0000000..6cd7656 --- /dev/null +++ b/src/pages/Post/Display.tsx @@ -0,0 +1,24 @@ +import React, {FC} from 'react'; +// Routing +import {Link} from 'react-router-dom'; +import Routes from '../../constants/routes'; + +import Post from '../../models/Post'; + +const PostDisplay: FC<{post: Post}> = ({ + post: {name, userID, avatarUrl, text}, +}) => ( +
+
+ + {name} +

{name}

+ +
+
+

{text}

+
+
+); + +export default PostDisplay; diff --git a/src/pages/Post/Form.tsx b/src/pages/Post/Form.tsx new file mode 100644 index 0000000..020f91c --- /dev/null +++ b/src/pages/Post/Form.tsx @@ -0,0 +1,28 @@ +import React, {FC} from 'react'; + +interface IProps { + handleSubmit: (e: React.FormEvent) => void; + handleChange: (e: React.ChangeEvent) => void; + text: string; +} + +const PostForm: FC = ({text, handleChange, handleSubmit}) => ( +
+
+

Leave A Comment

+
+
+ + +
+
+); + +export default PostForm; diff --git a/src/pages/Post/index.tsx b/src/pages/Post/index.tsx new file mode 100644 index 0000000..e915efe --- /dev/null +++ b/src/pages/Post/index.tsx @@ -0,0 +1,84 @@ +import React, {FC} from 'react'; +// Routing +import {useParams, Link} from 'react-router-dom'; +import Routes from '../../constants/routes'; +// Redux +import {compose} from '@reduxjs/toolkit'; +import {connect, useSelector} from 'react-redux'; +import {firestoreConnect, WithFirestoreProps} from 'react-redux-firebase'; +import {RootState} from '../../store'; +import {selectProfile} from '../../store/firebase'; +import Collections from '../../constants/collections'; +// Typing +import Post from '../../models/Post'; +import Comment from '../../types/Comment'; +// Form +import useForm from '../../hooks'; + +import PostForm from './Form'; +import PostComments from './Comments'; +import PostDisplay from './Display'; + +interface IProps extends WithFirestoreProps { + post: Post; +} + +/** + * Display a Post and the related comments. Shows a form to add a comment. + */ +const PostComponent: FC = ({post, firestore}) => { + const {avatarUrl, displayName} = useSelector(selectProfile); + const newComment: Comment = { + name: displayName ?? 'error', + avatarUrl: avatarUrl ?? 'error', + text: '', + }; + + const {formData, handleChange, resetForm} = useForm(newComment); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + firestore + .update(`${Collections.POSTS}/${post.id}`, { + comments: [...post.comments, formData], + }) + .then(() => resetForm()) + .catch(err => console.error(err)); + }; + + return ( +
+ + Back To Posts + + + + + + + +
+ ); +}; + +/** + * Container to fetch id params from thr URI and pass it to Profile page + */ +const PostPage: FC = () => { + const {id} = useParams(); + const Component = compose( + firestoreConnect(() => [`${Collections.POSTS}/${id}`]), + connect(({firestore: {data}}: RootState) => ({ + post: data.posts && {...data.posts[id], id}, + })), + )(PostComponent); + + return ; +}; + +export default PostPage; diff --git a/src/pages/Posts/Form.tsx b/src/pages/Posts/Form.tsx new file mode 100644 index 0000000..8b685bd --- /dev/null +++ b/src/pages/Posts/Form.tsx @@ -0,0 +1,22 @@ +import React, {FC} from 'react'; + +interface IProps { + text: string; + handleSubmit: (event: React.FormEvent) => void; + handleChange: (event: React.ChangeEvent) => void; +} + +const PostsForm: FC = ({text, handleSubmit, handleChange}) => ( +
+