Finished MUI for the most part

This commit is contained in:
QkoSad
2024-09-27 21:43:06 +03:00
parent be03889ff3
commit 9bc2015426
29 changed files with 1762 additions and 4102 deletions
+1224 -3148
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -5,8 +5,8 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.14.3", "@mui/icons-material": "^6.1.1",
"@mui/material": "^5.14.4", "@mui/material": "^6.1.1",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
-584
View File
@@ -1,584 +0,0 @@
/* Global Styles */
:root {
--primary-color: #17a2b8;
--dark-color: #343a40;
--light-color: #f4f4f4;
--danger-color: #dc3545;
--success-color: #28a745;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Raleway", sans-serif;
font-size: 1rem;
line-height: 1.6;
background-color: #fff;
color: #333;
}
a {
color: var(--primary-color);
text-decoration: none;
}
ul {
list-style: none;
}
img {
width: 100%;
}
/* Utilities */
.container {
max-width: 1100px;
margin: auto;
overflow: hidden;
padding: 0 2rem;
margin-top: 6rem;
margin-bottom: 3rem;
}
/* Text Styles*/
.x-large {
font-size: 4rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.large {
font-size: 3rem;
line-height: 1.2;
margin-bottom: 1rem;
}
.lead {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.text-center {
text-align: center;
}
.text-primary {
color: var(--primary-color);
}
.text-dark {
color: var(--dark-color);
}
/* Padding */
.p {
padding: 0.5rem;
}
.p-1 {
padding: 1rem;
}
.p-2 {
padding: 2rem;
}
.p-3 {
padding: 3rem;
}
.py {
padding: 0.5rem 0;
}
.py-1 {
padding: 1rem 0;
}
.py-2 {
padding: 2rem 0;
}
.py-3 {
padding: 3rem 0;
}
/* Margin */
.m {
margin: 0.5rem;
}
.m-1 {
margin: 1rem;
}
.m-2 {
margin: 2rem;
}
.m-3 {
margin: 3rem;
}
.my {
margin: 0.5rem 0;
}
.my-1 {
margin: 1rem 0;
}
.my-2 {
margin: 2rem 0;
}
.my-3 {
margin: 3rem 0;
}
.btn {
display: inline-block;
background: var(--light-color);
color: #333;
padding: 0.4rem 1.3rem;
font-size: 1rem;
border: none;
cursor: pointer;
margin-right: 0.5rem;
transition: opacity 0.2s ease-in;
outline: none;
}
.badge {
font-size: 0.8rem;
padding: 0.1rem;
text-align: center;
margin: 0.3rem;
background: var(--light-color);
color: #333;
}
.alert {
padding: 0.8rem;
margin: 1rem 0;
opacity: 0.9;
background: var(--light-color);
color: #333;
}
.btn-primary,
.bg-primary,
.badge-primary,
.alert-primary {
background: var(--primary-color);
color: #fff;
}
.btn-light,
.bg-light,
.badge-light,
.alert-light {
background: var(--light-color);
color: #333;
}
.btn-dark,
.bg-dark,
.badge-dark,
.alert-dark {
background: var(--dark-color);
color: #fff;
}
.btn-danger,
.bg-danger,
.badge-danger,
.alert-danger {
background: var(--danger-color);
color: #fff;
}
.btn-success,
.bg-success,
.badge-success,
.alert-success {
background: var(--success-color);
color: #fff;
}
.btn-white,
.bg-white,
.badge-white,
.alert-white {
background: #fff;
color: #333;
border: #ccc solid 1px;
}
.btn:hover {
opacity: 0.8;
}
.bg-light,
.badge-light {
border: #ccc solid 1px;
}
.round-img {
border-radius: 50%;
}
.line {
height: 1px;
background: #ccc;
margin: 1.5rem 0;
}
/* Overlay */
.dark-overlay {
background-color: rgba(0, 0, 0, 0.7);
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Forms */
.form .form-group {
margin: 1.2rem 0;
}
.form .form-text {
display: block;
margin-top: 0.3rem;
color: #888;
}
.form input[type="text"],
.form input[type="email"],
.form input[type="password"],
.form input[type="date"],
.form select,
.form textarea {
display: block;
width: 100%;
padding: 0.4rem;
font-size: 1.2rem;
border: 1px solid #ccc;
}
.form input[type="submit"],
button {
font: inherit;
}
.form .social-input {
display: flex;
}
.form .social-input i {
padding: 0.5rem;
width: 4rem;
}
.form .social-input i.fa-twitter {
color: #38a1f3;
}
.form .social-input i.fa-facebook {
color: #3b5998;
}
.form .social-input i.fa-instagram {
color: #3f729b;
}
.form .social-input i.fa-youtube {
color: #c4302b;
}
.form .social-input i.fa-linkedin {
color: #0077b5;
}
.table th,
.table td {
padding: 1rem;
text-align: left;
}
.table th {
background: var(--light-color);
}
/* Navbar */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.7rem 2rem;
position: fixed;
z-index: 1;
width: 100%;
top: 0;
border-bottom: solid 1px var(--primary-color);
opacity: 0.9;
}
.navbar ul {
display: flex;
}
.navbar a {
color: #fff;
padding: 0.45rem;
margin: 0 0.25rem;
}
.navbar a:hover {
color: var(--primary-color);
}
.navbar .welcome span {
margin-right: 0.6rem;
}
/* Landing Page */
.landing {
position: relative;
background: url("./img/vimCheatSheet.jpg") no-repeat center center/cover;
height: 100vh;
}
.landing-inner {
color: #fff;
height: 100%;
width: 80%;
margin: auto;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
/* Profiles Page */
.profile {
display: grid;
grid-template-columns: 2fr 4fr 2fr;
align-items: center;
grid-gap: 2rem;
padding: 1rem;
line-height: 1.8;
margin-bottom: 1rem;
}
/* Profile Page */
.profile-grid {
display: grid;
grid-template-areas:
"top top"
"about about"
"exp edu"
"github github";
grid-gap: 1rem;
}
.profile-top {
grid-area: top;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.profile-top img {
width: 250px;
}
.profile-top .icons a {
color: #fff;
margin: 0 0.3rem;
}
.profile-top .icons a:hover {
color: var(--dark-color);
}
.profile-about {
grid-area: about;
text-align: center;
}
.profile-about .skills {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.profile-exp {
grid-area: exp;
}
.profile-edu {
grid-area: edu;
}
.profile-exp h2,
.profile-edu h2 {
margin-bottom: 1rem;
}
.profile-exp > div,
.profile-edu > div {
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: #ccc 1px dotted;
}
.profile-exp > div:last-child,
.profile-edu > div:last-child {
border: 0;
}
.profile-exp p,
.profile-edu p {
margin: 0.5rem 0;
}
.profile-github {
grid-area: github;
}
.profile-github .repo {
display: flex;
}
.profile-github .repo > div:first-child {
flex: 7;
flex-basis: 70%;
}
.profile-github > div:last-child {
flex: 3;
flex-basis: 20%;
}
/* Posts Page */
.post-form .post-form-header {
background: var(--primary-color);
padding: 0.5rem;
}
.post {
display: grid;
grid-template-columns: 1fr 4fr;
grid-gap: 2rem;
align-items: center;
}
.post > div:first-child {
text-align: center;
}
.post img {
width: 100px;
}
.post .comment-count {
background: var(--light-color);
color: var(--primary-color);
padding: 0.1rem 0.2rem;
border-radius: 5px;
font-size: 0.8rem;
}
.post .post-date {
color: #aaa;
font-size: 0.8rem;
margin-bottom: 0.5rem;
}
/* Mobile Styles */
@media (max-width: 700px) {
.container {
margin-top: 8rem;
}
.hide-sm {
display: none;
}
/* Text Styles */
.x-large {
font-size: 3rem;
}
.large {
font-size: 2rem;
}
.lead {
font-size: 1rem;
}
/* Navbar */
.navbar {
display: block;
text-align: center;
}
.navbar ul {
text-align: center;
justify-content: center;
}
.navbar h1 {
margin-bottom: 1rem;
}
.navbar .welcome {
display: none;
}
/* Profiles Page */
.profile {
grid-template-columns: 1fr;
text-align: center;
}
.profile ul {
display: none;
}
/* Profile Page */
.profile-top img,
.profile img {
width: 200px;
margin: auto;
}
.profile-grid {
grid-template-areas:
"top"
"about"
"exp"
"edu"
"github";
}
.profile-about .skills {
flex-direction: column;
}
.dash-buttons a {
display: block;
width: 100%;
margin-bottom: 0.2rem;
}
.post {
grid-template-columns: 1fr;
}
.post a,
.post button {
padding: 0.3rem 0.4rem;
}
}
.alert-wrapper {
position: fixed;
top: 4rem;
right: 2rem;
display: inline-block;
}
-16
View File
@@ -1,16 +0,0 @@
.datePicker :hover {
border-color: "#212121";
}
.datePicker :focus {
border-color: "#1976d2";
border-width: "2px";
}
.datePicker{
color:"red";
background:"red";
}
.colormebaby{
background-color:"red";
color:"red"
}
+6 -6
View File
@@ -7,7 +7,7 @@ import * as React from "react";
import Avatar from "@mui/material/Avatar"; import Avatar from "@mui/material/Avatar";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid2";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import LockOutlinedIcon from "@mui/icons-material/LockOutlined"; import LockOutlinedIcon from "@mui/icons-material/LockOutlined";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
@@ -54,7 +54,7 @@ export default function SignUp() {
</Typography> </Typography>
<Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}> <Box component="form" noValidate onSubmit={handleSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid size={{ xs: 12 }}>
<TextField <TextField
name="name" name="name"
required required
@@ -64,7 +64,7 @@ export default function SignUp() {
autoFocus autoFocus
/> />
</Grid> </Grid>
<Grid item xs={12}> <Grid size={{ xs: 12 }}>
<TextField <TextField
required required
fullWidth fullWidth
@@ -73,7 +73,7 @@ export default function SignUp() {
name="email" name="email"
/> />
</Grid> </Grid>
<Grid item xs={12}> <Grid size={{ xs: 12 }}>
<TextField <TextField
required required
fullWidth fullWidth
@@ -83,7 +83,7 @@ export default function SignUp() {
id="password" id="password"
/> />
</Grid> </Grid>
<Grid item xs={12}> <Grid size={{ xs: 12 }}>
<TextField <TextField
required required
fullWidth fullWidth
@@ -103,7 +103,7 @@ export default function SignUp() {
Sign Up Sign Up
</Button> </Button>
<Grid container justifyContent="flex-end"> <Grid container justifyContent="flex-end">
<Grid item> <Grid>
Already have an account? <Link to={"/login"}>Sign in</Link> Already have an account? <Link to={"/login"}>Sign in</Link>
</Grid> </Grid>
</Grid> </Grid>
+10 -8
View File
@@ -6,6 +6,7 @@ import Experience from "./Experience";
import Education from "./Education"; import Education from "./Education";
import { getCurrentProfile, deleteAccount } from "../../actions/profile"; import { getCurrentProfile, deleteAccount } from "../../actions/profile";
import { Box, Button, Typography } from "@mui/material"; import { Box, Button, Typography } from "@mui/material";
import { compose } from "redux";
const Dashboard = () => { const Dashboard = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -19,13 +20,14 @@ const Dashboard = () => {
const profile = useAppSelector((state) => state.profile.profile); const profile = useAppSelector((state) => state.profile.profile);
return ( return (
<Box <Box
component="main" sx={{
justifyContent="center" justifyContent: "center",
flexDirection="column" flexDirection: "column",
minHeight="50vh" minHeight: "50vh",
display="flex" display: "flex",
alignItems="center" alignItems: "center",
gap="1rem" gap: "1rem",
}}
> >
<Typography variant="h3" component="h2"> <Typography variant="h3" component="h2">
Dashboard{" "} Dashboard{" "}
@@ -38,7 +40,7 @@ const Dashboard = () => {
<DashboardActions /> <DashboardActions />
<Experience experience={profile.experience} /> <Experience experience={profile.experience} />
<Education education={profile.education} /> <Education education={profile.education} />
<Box sx={{ marginTop: "10vh" }}> <Box sx={{ marginTop: "2vh" }}>
<Button <Button
variant="contained" variant="contained"
color="error" color="error"
@@ -4,7 +4,12 @@ import { Link } from "react-router-dom";
const DashboardActions = () => { const DashboardActions = () => {
return ( return (
<Box> <Box
sx={{
display: { xs: "grid", md: "flex" },
rowGap: { xs: "5px" },
}}
>
<Button <Button
component={Link} component={Link}
to="/edit-profile" to="/edit-profile"
+13 -12
View File
@@ -21,19 +21,26 @@ const Education = ({ education }: { education: EducationType[] }) => {
const educations = education.map((edu) => ( const educations = education.map((edu) => (
<TableRow key={edu._id}> <TableRow key={edu._id}>
<TableCell <TableCell
// style={{ sx={{
// wordWrap: "break-word", textWrap: "wrap",
// }} wordBreak: "break-word",
}}
> >
{edu.school} {edu.school}
</TableCell> </TableCell>
<TableCell <TableCell
// style={{ wordWrap: "break-word" }} sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
> >
{edu.degree} {edu.degree}
</TableCell> </TableCell>
<TableCell <TableCell
// style={{ wordWrap: "break-word" }} sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
> >
{formatDate(edu.from)} - {edu.to ? formatDate(edu.to) : "Now"} {formatDate(edu.from)} - {edu.to ? formatDate(edu.to) : "Now"}
</TableCell> </TableCell>
@@ -56,13 +63,7 @@ const Education = ({ education }: { education: EducationType[] }) => {
return ( return (
<Box> <Box>
<Typography variant="h2">Education Credentials</Typography> <Typography variant="h2">Education Credentials</Typography>
<Table <Table>
// style={{
// tableLayout: "fixed",
// width: "50%",
// alignSelf: "left",
// }}
>
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell>School</TableCell> <TableCell>School</TableCell>
+22 -3
View File
@@ -20,9 +20,28 @@ const Experience = ({ experience }: { experience: ExperienceType[] }) => {
if (!experience) return <></>; if (!experience) return <></>;
const experiences = experience.map((exp) => ( const experiences = experience.map((exp) => (
<TableRow key={exp._id}> <TableRow key={exp._id}>
<TableCell>{exp.company}</TableCell> <TableCell
<TableCell>{exp.title}</TableCell> sx={{
<TableCell> textWrap: "wrap",
wordBreak: "break-word",
}}
>
{exp.company}
</TableCell>
<TableCell
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
{exp.title}
</TableCell>
<TableCell
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
{formatDate(exp.from)} - {exp.to ? formatDate(exp.to) : "Now"} {formatDate(exp.from)} - {exp.to ? formatDate(exp.to) : "Now"}
</TableCell> </TableCell>
<TableCell> <TableCell>
-1
View File
@@ -148,7 +148,6 @@ const Navbar = () => {
Logout Logout
</a> </a>
</MenuItem> </MenuItem>
,
</div> </div>
) : ( ) : (
guestLinks.map((el) => ( guestLinks.map((el) => (
+9 -2
View File
@@ -1,5 +1,6 @@
import { Box, Button, Container, TextField, Typography } from "@mui/material"; import { Box, Button, Container, TextField, Typography } from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { createAlert } from "../../actions/alert";
import { addComment } from "../../actions/post"; import { addComment } from "../../actions/post";
import { useAppDispatch } from "../../utils/hooks"; import { useAppDispatch } from "../../utils/hooks";
@@ -18,8 +19,14 @@ const CommentForm = ({ postId }: { postId: string }) => {
gap="1rem" gap="1rem"
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
dispatch(addComment(postId, { text })); if (text.length > 250) {
setText(""); dispatch(
createAlert("Comment longer than 250 characters", "danger"),
);
} else {
dispatch(addComment(postId, { text }));
setText("");
}
}} }}
> >
<TextField <TextField
+6 -1
View File
@@ -40,7 +40,12 @@ const CommentItem = ({
</Button> </Button>
)} )}
<CardContent> <CardContent>
<Typography variant="subtitle1">{text}</Typography> <Typography
variant="subtitle1"
sx={{ textWrap: "wrap", wordBreak: "break-word" }}
>
{text}
</Typography>
</CardContent> </CardContent>
</Card> </Card>
</Container> </Container>
+10 -4
View File
@@ -1,12 +1,12 @@
import { Box, Button, Container, TextField, Typography } from "@mui/material"; import { Box, Button, Container, TextField, Typography } from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { createAlert } from "../../actions/alert";
import { addPost } from "../../actions/post"; import { addPost } from "../../actions/post";
import { useAppDispatch } from "../../utils/hooks"; import { useAppDispatch } from "../../utils/hooks";
const PostForm = () => { const PostForm = () => {
const [text, setText] = useState(""); const [text, setText] = useState("");
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const handleSumbit = {};
return ( return (
<Container> <Container>
<Typography>Say Something...</Typography> <Typography>Say Something...</Typography>
@@ -18,8 +18,12 @@ const PostForm = () => {
sx={{ mt: 3, mb: 3 }} sx={{ mt: 3, mb: 3 }}
onSubmit={(e) => { onSubmit={(e) => {
e.preventDefault(); e.preventDefault();
dispatch(addPost({ text })); if (text.length > 250) {
setText(""); dispatch(createAlert("Post longer than 250 characters", "danger"));
} else {
dispatch(addPost({ text }));
setText("");
}
}} }}
> >
<TextField <TextField
@@ -27,7 +31,9 @@ const PostForm = () => {
label="Create a post" label="Create a post"
fullWidth fullWidth
value={text} value={text}
onChange={(e) => setText(e.target.value)} onChange={(e) => {
return setText(e.target.value);
}}
multiline multiline
rows={3} rows={3}
required required
+35 -40
View File
@@ -6,14 +6,12 @@ import { Post } from "../../types";
import { useAppDispatch, useAppSelector } from "../../utils/hooks"; import { useAppDispatch, useAppSelector } from "../../utils/hooks";
import { import {
Avatar, Avatar,
Box,
Button, Button,
Card, Card,
CardContent, CardContent,
CardHeader, CardHeader,
Container, Container,
Table,
TableCell,
TableRow,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import ThumbDownIcon from "@mui/icons-material/ThumbDown"; import ThumbDownIcon from "@mui/icons-material/ThumbDown";
@@ -36,45 +34,42 @@ const PostItem = ({
subheader={formatDate(date)} subheader={formatDate(date)}
/> />
<CardContent> <CardContent>
<Typography variant="subtitle1">{text}</Typography> <Typography
variant="subtitle1"
sx={{ textWrap: "wrap", wordBreak: "break-word" }}
>
{text}
</Typography>
</CardContent> </CardContent>
</Card> </Card>
<Table> <Box
<TableRow> sx={{
<TableCell> display: "flex",
<Button onClick={async () => await dispatch(addLike(_id))}> justifyContent: "space-evenly",
<ThumbUpIcon /> alignItems: "center",
</Button> }}
</TableCell> >
<TableCell> <Button onClick={async () => await dispatch(addLike(_id))}>
<Typography display="inline-flex" variant="button"> <ThumbUpIcon />
{likes.length} </Button>
</Typography> <Typography display="inline-flex" variant="button">
</TableCell> {likes.length}
<TableCell> </Typography>
<Button onClick={async () => await dispatch(removeLike(_id))}> <Button onClick={async () => await dispatch(removeLike(_id))}>
<ThumbDownIcon /> <ThumbDownIcon />
</Button> </Button>
</TableCell> <Button component={Link} to={`/posts/${_id}`}>
<TableCell> <Typography>Comments </Typography>
<Button component={Link} to={`/posts/${_id}`}> </Button>
<Typography>Comments </Typography> <Typography display="inline-flex" variant="button">
</Button> {comments.length}
</TableCell> </Typography>
<TableCell> {!auth.loading && auth.user !== null && user === auth.user._id && (
<Typography display="inline-flex" variant="button"> <Button onClick={async () => await dispatch(deletePost(_id))}>
{comments.length} <DeleteIcon />
</Typography> </Button>
</TableCell> )}
<TableCell> </Box>
{!auth.loading && auth.user !== null && user === auth.user._id && (
<Button onClick={async () => await dispatch(deletePost(_id))}>
<DeleteIcon />
</Button>
)}
</TableCell>
</TableRow>
</Table>
</Container> </Container>
); );
}; };
@@ -1,15 +1,8 @@
import styled from "@emotion/styled"; import styled from "@emotion/styled";
import { import { Button, Box, Container, TextField, Typography } from "@mui/material";
Button,
Box,
Container,
TextField,
Typography,
Checkbox,
FormControlLabel,
} from "@mui/material";
import React, { useState } from "react"; import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { createAlert } from "../../actions/alert";
import { addEducation } from "../../actions/profile"; import { addEducation } from "../../actions/profile";
import { useAppDispatch } from "../../utils/hooks"; import { useAppDispatch } from "../../utils/hooks";
@@ -51,6 +44,21 @@ const AddEducation = () => {
const onChange = ( const onChange = (
event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>, event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
) => setFormData({ ...formData, [event.target.name]: event.target.value }); ) => setFormData({ ...formData, [event.target.name]: event.target.value });
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (formData.degree.length > 50) {
dispatch(createAlert("Degree is longer 50 characters", "danger"));
} else if (formData.fieldofstudy.length > 50) {
dispatch(createAlert("Field of study is longer 50 characters", "danger"));
} else if (formData.description.length > 250) {
dispatch(
createAlert("Description name is longer 250 characters", "danger"),
);
} else if (formData.school.length > 50) {
dispatch(createAlert("School name is longer 50 characters", "danger"));
} else
await dispatch(addEducation(formData)).then(() => navigate("/dashboard"));
};
return ( return (
<Container maxWidth="sm"> <Container maxWidth="sm">
@@ -68,12 +76,7 @@ const AddEducation = () => {
noValidate noValidate
maxWidth="500px" maxWidth="500px"
sx={{ mt: 3 }} sx={{ mt: 3 }}
onSubmit={async (e) => { onSubmit={onSubmit}
e.preventDefault();
await dispatch(addEducation(formData)).then(() =>
navigate("/dashboard"),
);
}}
> >
<TextField <TextField
name="school" name="school"
@@ -10,6 +10,7 @@ import {
import { Container } from "@mui/system"; import { Container } from "@mui/system";
import React, { useState } from "react"; import React, { useState } from "react";
import { Link, useNavigate } from "react-router-dom"; import { Link, useNavigate } from "react-router-dom";
import { createAlert } from "../../actions/alert";
import { addExperience } from "../../actions/profile"; import { addExperience } from "../../actions/profile";
import { useAppDispatch } from "../../utils/hooks"; import { useAppDispatch } from "../../utils/hooks";
@@ -69,9 +70,30 @@ const AddExperience = () => {
sx={{ mt: 3 }} sx={{ mt: 3 }}
onSubmit={async (e) => { onSubmit={async (e) => {
e.preventDefault(); e.preventDefault();
await dispatch(addExperience(formData)).then(() => if (formData.description.length > 250) {
navigate("/dashboard"), dispatch(
); createAlert("Job description is longer 250 characters", "danger"),
);
} else if (formData.location.length > 50) {
dispatch(
createAlert("Location is longer than 50 characters", "danger"),
);
} else if (formData.title.length > 50) {
dispatch(
createAlert("Job title is longer than 50 characters", "danger"),
);
} else if (formData.company.length > 50) {
dispatch(
createAlert(
"Company name is longer than 50 characters",
"danger",
),
);
} else {
await dispatch(addExperience(formData)).then(() =>
navigate("/dashboard"),
);
}
}} }}
> >
<TextField <TextField
@@ -2,16 +2,16 @@ import {
Button, Button,
Box, Box,
Container, Container,
CssBaseline,
Grid,
InputLabel, InputLabel,
MenuItem, MenuItem,
Select, Select,
TextField, TextField,
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import React, { Fragment, useState, useEffect } from "react"; import Grid from "@mui/material/Grid2";
import React, { useState, useEffect } from "react";
import { Link, useMatch, useNavigate } from "react-router-dom"; import { Link, useMatch, useNavigate } from "react-router-dom";
import { createAlert } from "../../actions/alert";
import { createProfile, getCurrentProfile } from "../../actions/profile"; import { createProfile, getCurrentProfile } from "../../actions/profile";
import { useAppDispatch, useAppSelector } from "../../utils/hooks"; import { useAppDispatch, useAppSelector } from "../../utils/hooks";
@@ -86,28 +86,52 @@ const ProfileForm = () => {
const linkedin = data.get("linkedin") as string; const linkedin = data.get("linkedin") as string;
const instagram = data.get("instagram") as string; const instagram = data.get("instagram") as string;
const status = data.get("status") as string; const status = data.get("status") as string;
await dispatch( if (facebook?.length > 100) {
createProfile( dispatch(createAlert("Facebook link is longer 100 characters", "danger"));
{ } else if (linkedin?.length > 100) {
website, dispatch(createAlert("LinkedIn link is longer 100 characters", "danger"));
location, } else if (youtube?.length > 100) {
skills, dispatch(createAlert("Youtube link is longer 100 characters", "danger"));
githubusername, } else if (instagram?.length > 100) {
company, dispatch(
bio, createAlert("Instagram link is longer 100 characters", "danger"),
twitter, );
facebook, } else if (website?.length > 100) {
youtube, dispatch(createAlert("Website link is longer 100 characters", "danger"));
linkedin, } else if (skills.length > 100) {
instagram, dispatch(createAlert("Skills is longer 100 characters", "danger"));
status, } else if (location?.length > 50) {
}, dispatch(createAlert("Location is longer 100 characters", "danger"));
editing, } else if (githubusername?.length > 50) {
), dispatch(
).then((res) => { createAlert("Github username is longer 50 characters", "danger"),
console.log(res); );
if (!editing) navigate("/dashboard"); } else if (company?.length > 50) {
}); dispatch(createAlert("Company name is longer 50 characters", "danger"));
} else if (bio?.length > 250) {
dispatch(createAlert("Bio name is longer 250 characters", "danger"));
} else
await dispatch(
createProfile(
{
website,
location,
skills,
githubusername,
company,
bio,
twitter,
facebook,
youtube,
linkedin,
instagram,
status,
},
editing,
),
).then(() => {
if (!editing) navigate("/dashboard");
});
}; };
return ( return (
@@ -123,48 +147,48 @@ const ProfileForm = () => {
<Typography variant="body2">* = required field</Typography> <Typography variant="body2">* = required field</Typography>
<Box component="form" noValidate onSubmit={onSubmit} sx={{ mt: 3 }}> <Box component="form" noValidate onSubmit={onSubmit} sx={{ mt: 3 }}>
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField name="company" fullWidth label="Company" autoFocus /> <TextField name="company" fullWidth label="Company" autoFocus />
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<Typography paddingY="1rem"> <Typography paddingY="1rem">
Could be your own company or one you work for Could be your own company or one you work for
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField name="website" fullWidth label="Website" /> <TextField name="website" fullWidth label="Website" />
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<Typography paddingY="1rem"> <Typography paddingY="1rem">
Could be your own a or a company website Could be your own a or a company website
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField name="location" fullWidth label="Location" /> <TextField name="location" fullWidth label="Location" />
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<Typography paddingY="1rem"> <Typography paddingY="1rem">
City & state suggest(eg. Boston MA) City & state suggest(eg. Boston MA)
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField name="skills" required fullWidth label="Skills" /> <TextField name="skills" required fullWidth label="Skills" />
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<Typography> <Typography>
Please use comma separeted values (eg. HTML, CSS, JavaScript, PHP) Please use comma separeted values (eg. HTML, CSS, JavaScript, PHP)
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField name="githubUser" fullWidth label="Github Username" /> <TextField name="githubUser" fullWidth label="Github Username" />
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<Typography> <Typography>
If you want your latest repositories, add a Github link and If you want your latest repositories, add a Github link and
include your username include your username
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<InputLabel id="status">Status *</InputLabel> <InputLabel id="status">Status *</InputLabel>
<Select <Select
fullWidth fullWidth
@@ -187,15 +211,15 @@ const ProfileForm = () => {
<MenuItem value="Other">Other</MenuItem> <MenuItem value="Other">Other</MenuItem>
</Select> </Select>
</Grid> </Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<Typography paddingTop="2.3rem"> <Typography paddingTop="2.3rem">
Select Profesional Status Select Profesional Status
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={12}> <Grid size={{ xs: 12 }}>
<Typography>Tell us a little about yourself</Typography> <Typography>Tell us a little about yourself</Typography>
</Grid> </Grid>
<Grid item xs={12}> <Grid size={{ xs: 12 }}>
<TextField <TextField
name="bio" name="bio"
multiline multiline
@@ -204,7 +228,7 @@ const ProfileForm = () => {
label="A short bio of yourself" label="A short bio of yourself"
/> />
</Grid> </Grid>
<Grid item xs={12}> <Grid size={{ xs: 12 }}>
<Button <Button
variant="contained" variant="contained"
onClick={() => toggleSocialInputs(!displaySocialInputs)} onClick={() => toggleSocialInputs(!displaySocialInputs)}
@@ -214,33 +238,33 @@ const ProfileForm = () => {
</Grid> </Grid>
{displaySocialInputs ? ( {displaySocialInputs ? (
<> <>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField fullWidth name="twitter" label="Twitter URL" /> <TextField fullWidth name="twitter" label="Twitter URL" />
</Grid> </Grid>
<Grid item xs={6}></Grid> <Grid size={{ xs: 6 }}></Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField fullWidth name="facebook" label="FaceBook URL" /> <TextField fullWidth name="facebook" label="FaceBook URL" />
</Grid> </Grid>
<Grid item xs={6}></Grid> <Grid size={{ xs: 6 }}></Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField fullWidth name="youtube" label="YouTube URL" /> <TextField fullWidth name="youtube" label="YouTube URL" />
</Grid> </Grid>
<Grid item xs={6}></Grid> <Grid size={{ xs: 6 }}></Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField fullWidth name="linkedin" label="Linkedin URL" /> <TextField fullWidth name="linkedin" label="Linkedin URL" />
</Grid> </Grid>
<Grid item xs={6}></Grid> <Grid size={{ xs: 6 }}></Grid>
<Grid item xs={6}> <Grid size={{ xs: 6 }}>
<TextField fullWidth name="instagram" label="Instagram URL" /> <TextField fullWidth name="instagram" label="Instagram URL" />
</Grid> </Grid>
</> </>
) : null} ) : null}
<Grid item xs={8}> <Grid size={{ xs: 8 }}>
<Button variant="contained" type="submit"> <Button variant="contained" type="submit">
Submit Changes Submit Changes
</Button> </Button>
</Grid> </Grid>
<Grid item xs={2}> <Grid size={{ xs: 2 }}>
<Button variant="outlined"> <Button variant="outlined">
<Link <Link
style={{ color: "inherit", textDecoration: "none" }} style={{ color: "inherit", textDecoration: "none" }}
+53 -24
View File
@@ -9,7 +9,8 @@ import ProfileGithub from "./ProfileGithub";
import { getProfileById } from "../../actions/profile"; import { getProfileById } from "../../actions/profile";
import { useAppDispatch, useAppSelector } from "../../utils/hooks"; import { useAppDispatch, useAppSelector } from "../../utils/hooks";
import { EducationType, ExperienceType } from "../../types"; import { EducationType, ExperienceType } from "../../types";
import { Box, Button, Card, Paper, Typography } from "@mui/material"; import { Box, Button, Card, Container, Paper, Typography } from "@mui/material";
import Grid from "@mui/material/Grid2";
const Profile = () => { const Profile = () => {
const profile = useAppSelector((state) => state.profile.profile); const profile = useAppSelector((state) => state.profile.profile);
@@ -25,26 +26,54 @@ const Profile = () => {
}, [dispatch, id]); }, [dispatch, id]);
return ( return (
<Box display="flex" flexDirection="column" alignItems="center"> <>
{profile === null ? ( {profile === null ? (
<Spinner /> <Spinner />
) : ( ) : (
<> <Paper sx={{ display: "grid", justifyItems: "center", marginY: "5px" }}>
<Button component={Link} to="/profiles"> <div>
Back To Profiles <Button
</Button> variant="contained"
{auth.isAuthenticated && component={Link}
auth.loading === false && to="/profiles"
auth.user !== null && sx={{
auth.user._id === profile.user._id && ( width: "70px",
<Button component={Link} to="/edit-profile"> margin: "5px",
Edit Profile }}
</Button> >
)} Back
<Paper elevation={9} sx={{ width: "90%" }}> </Button>
<ProfileTop profile={profile} /> {auth.isAuthenticated &&
<ProfileAbout profile={profile} /> auth.loading === false &&
<Box> auth.user !== null &&
auth.user._id === profile.user._id && (
<Button
component={Link}
to="/edit-profile"
variant="outlined"
sx={{
width: "70px",
}}
>
Edit
</Button>
)}
</div>
<Grid
container
spacing={2}
rowSpacing={"30px"}
sx={{
marginX: "20%",
}}
>
<Grid size={{ sm: 12, lg: 4 }}>
<ProfileTop profile={profile} />
</Grid>
<Grid size={{ sm: 12, lg: 8 }}>
<ProfileAbout profile={profile} />
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
<Typography variant="h2">Experience</Typography> <Typography variant="h2">Experience</Typography>
{profile.experience.length > 0 ? ( {profile.experience.length > 0 ? (
<> <>
@@ -58,8 +87,8 @@ const Profile = () => {
) : ( ) : (
<Typography variant="h4">No experience credentials</Typography> <Typography variant="h4">No experience credentials</Typography>
)} )}
</Box> </Grid>
<Box> <Grid size={6}>
<Typography variant="h2">Education</Typography> <Typography variant="h2">Education</Typography>
{profile.education.length > 0 ? ( {profile.education.length > 0 ? (
<> <>
@@ -73,14 +102,14 @@ const Profile = () => {
) : ( ) : (
<Typography variant="h4">No education credentials</Typography> <Typography variant="h4">No education credentials</Typography>
)} )}
</Box> </Grid>
{profile.githubusername && ( {profile.githubusername && (
<ProfileGithub username={profile.githubusername} /> <ProfileGithub username={profile.githubusername} />
)} )}
</Paper> </Grid>
</> </Paper>
)} )}
</Box> </>
); );
}; };
+20 -9
View File
@@ -1,4 +1,4 @@
import { Box, Typography } from "@mui/material"; import { Box, List, ListItem, Typography } from "@mui/material";
import React, { Fragment } from "react"; import React, { Fragment } from "react";
import { ProfileType } from "../../types"; import { ProfileType } from "../../types";
@@ -11,21 +11,32 @@ const ProfileAbout = ({
}: { }: {
profile: ProfileType; profile: ProfileType;
}) => ( }) => (
<Box> <Box sx={{ textWrap: "wrap", maxWidth: "100%" }}>
{bio && ( {bio && (
<> <>
<Typography>{name.trim().split(" ")[0]}s Bio</Typography> <Typography color="info" variant="h5">
<Typography>{bio}</Typography> <b>{name.trim().split(" ")[0]}'s Bio</b>
</Typography>
<Typography
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
{bio}
</Typography>
</> </>
)} )}
<Typography>Skill Set</Typography> <Typography color="info" variant="h5">
<Box> <b>Skill Set</b>
</Typography>
<List>
{skills.map((skill, index) => ( {skills.map((skill, index) => (
<Box key={index}> <ListItem key={index}>
<Typography>{skill}</Typography> <Typography>{skill}</Typography>
</Box> </ListItem>
))} ))}
</Box> </List>
</Box> </Box>
); );
@@ -1,4 +1,4 @@
import { Box, Typography } from "@mui/material"; import { Paper, Typography } from "@mui/material";
import React from "react"; import React from "react";
import { EducationType } from "../../types"; import { EducationType } from "../../types";
import formatDate from "../../utils/formatDate"; import formatDate from "../../utils/formatDate";
@@ -8,15 +8,49 @@ const ProfileEducation = ({
}: { }: {
education: EducationType; education: EducationType;
}) => ( }) => (
<Box> <Paper elevation={12} sx={{ margin: "10px", padding: "4px" }}>
<Typography variant="h3">{school}</Typography> <Typography
<Typography> variant="h4"
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
{school}
</Typography>
<Typography
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
{formatDate(from)} - {to ? formatDate(to) : "Now"} {formatDate(from)} - {to ? formatDate(to) : "Now"}
</Typography> </Typography>
<Typography>Degree: {degree}</Typography> <Typography
<Typography>Field Of Study: {fieldofstudy}</Typography> sx={{
<Typography>Description: {description}</Typography> textWrap: "wrap",
</Box> wordBreak: "break-word",
}}
>
<b>Degree:</b> {degree}
</Typography>
<Typography
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
<b>Field Of Study:</b> {fieldofstudy}
</Typography>
<Typography
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
<b>Description:</b> {description}
</Typography>
</Paper>
); );
export default ProfileEducation; export default ProfileEducation;
@@ -1,4 +1,4 @@
import { Box, Typography } from "@mui/material"; import { Paper, Typography } from "@mui/material";
import React from "react"; import React from "react";
import { ExperienceType } from "../../types"; import { ExperienceType } from "../../types";
import formatDate from "../../utils/formatDate"; import formatDate from "../../utils/formatDate";
@@ -8,15 +8,43 @@ const ProfileExperience = ({
}: { }: {
experience: ExperienceType; experience: ExperienceType;
}) => ( }) => (
<Box> <Paper elevation={12} sx={{ margin: "10px", padding: "4px" }}>
<Typography variant="h3">{company}</Typography> <Typography
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
<b>{company}</b>
</Typography>
<Typography> <Typography>
{formatDate(from)} - {to ? formatDate(to) : "Now"} {formatDate(from)} - {to ? formatDate(to) : "Now"}
</Typography> </Typography>
<Typography>Position: {title}</Typography> <Typography
<Typography>Location: {location}</Typography> sx={{
<Typography>Description: {description}</Typography> textWrap: "wrap",
</Box> wordBreak: "break-word",
}}
>
<b>Position:</b> {title}
</Typography>
<Typography
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
<b>Location:</b> {location}
</Typography>
<Typography
sx={{
textWrap: "wrap",
wordBreak: "break-word",
}}
>
<b>Description:</b> {description}
</Typography>
</Paper>
); );
export default ProfileExperience; export default ProfileExperience;
@@ -13,10 +13,10 @@ const ProfileGithub = ({ username }: { username: string }) => {
}, [dispatch, username]); }, [dispatch, username]);
return ( return (
<div className="profile-github"> <div>
<h2 className="text-primary my-1">Github Repos</h2> <h2>Github Repos</h2>
{repos.map((repo) => ( {repos.map((repo) => (
<div key={repo.id} className="repo bg-white p-1 my-1"> <div key={repo.id}>
<div> <div>
<h4> <h4>
<a href={repo.html_url} target="_blank" rel="noopener noreferrer"> <a href={repo.html_url} target="_blank" rel="noopener noreferrer">
@@ -27,13 +27,9 @@ const ProfileGithub = ({ username }: { username: string }) => {
</div> </div>
<div> <div>
<ul> <ul>
<li className="badge badge-primary"> <li>Stars: {repo.stargazers_count}</li>
Stars: {repo.stargazers_count} <li>Watchers: {repo.watchers_count}</li>
</li> <li>Forks: {repo.forks_count}</li>
<li className="badge badge-dark">
Watchers: {repo.watchers_count}
</li>
<li className="badge badge-light">Forks: {repo.forks_count}</li>
</ul> </ul>
</div> </div>
</div> </div>
+8 -7
View File
@@ -1,3 +1,4 @@
import { BorderAll } from "@mui/icons-material";
import { Avatar, Box, Card, CardHeader, Typography } from "@mui/material"; import { Avatar, Box, Card, CardHeader, Typography } from "@mui/material";
import React from "react"; import React from "react";
import { ProfileType } from "../../types"; import { ProfileType } from "../../types";
@@ -14,19 +15,18 @@ const ProfileTop = ({
}: { }: {
profile: ProfileType; profile: ProfileType;
}) => { }) => {
console.log(avatar)
return ( return (
<> <>
<img className="round-img my-1" src={avatar} alt="" /> <img src={avatar} alt="" />
<Typography variant="h1">{name}</Typography> <Typography variant="h2">{name}</Typography>
<Typography> <Typography sx={{ textWrap: "wrap" }}>
{status} {company ? <span> at {company}</span> : null} {status} {company ? <span> at {company}</span> : null}
</Typography> </Typography>
<Typography>{location ? <span>{location}</span> : null}</Typography> {location ? <Typography>{location}</Typography> : null}
<Box> <Box>
{website ? ( {website ? (
<a href={website} target="_blank" rel="noopener noreferrer"> <a href={website} target="_blank" rel="noopener noreferrer">
<i className="fas fa-globe fa-2x" /> <i>{website}</i>
</a> </a>
) : null} ) : null}
{social {social
@@ -38,8 +38,9 @@ const ProfileTop = ({
href={value} href={value}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
style={{ display: "block" }}
> >
<i className={`fab fa-${key} fa-2x`}></i> <i>{value}</i>
</a> </a>
)) ))
: null} : null}
+14 -11
View File
@@ -22,18 +22,21 @@ const ProfileItem = ({
profile: ProfileType; profile: ProfileType;
}) => { }) => {
return ( return (
<Paper sx={{ padding: "100px" }}> <Paper sx={{ width: "250px", paddingX: "10px" }}>
<img src={avatar} alt="" /> <img src={avatar} alt="" />
<Box> <Typography color="info">
<Typography>{name}</Typography> <b>{name}</b>
<Typography> </Typography>
{status} {company && <span> at {company}</span>} <Typography>
</Typography> {status} {company && <span> at {company}</span>}
<Typography>{location && <span>{location}</span>}</Typography> </Typography>
<Button component={Link} to={`/profile/${_id}`}> <Typography>{location && <span>{location}</span>}</Typography>
View Profile <Button component={Link} to={`/profile/${_id}`} variant="outlined">
</Button> View Profile
</Box> </Button>
<Typography color="info">
<b>Skills:</b>
</Typography>
<List> <List>
{skills.slice(0, 4).map((skill, index) => ( {skills.slice(0, 4).map((skill, index) => (
<ListItemText key={index}>{skill}</ListItemText> <ListItemText key={index}>{skill}</ListItemText>
+27 -22
View File
@@ -17,28 +17,33 @@ const Profiles = () => {
const { profiles, loading } = useAppSelector((state) => state.profile); const { profiles, loading } = useAppSelector((state) => state.profile);
return ( return (
<Container maxWidth="sm"> <Container>
<Box display="flex" flexDirection="column" alignItems="center"> {loading ? (
{loading ? ( <Spinner />
<Spinner /> ) : (
) : ( <>
<> <Typography variant="h2">Developers</Typography>
<Typography variant="h2">Developers</Typography> <Typography variant="h6">
<Typography variant="h6"> Browse and connect with developers
Browse and connect with developers </Typography>
</Typography> <Box
<Box display="flex" flexDirection="row"> sx={{
{profiles.length > 0 && Array.isArray(profiles) ? ( display: "flex",
profiles.map((profile) => ( justifyContent: "left",
<ProfileItem key={profile._id} profile={profile} /> gap: "10px",
)) flexWrap: "wrap",
) : ( }}
<Typography>No profiles found...</Typography> >
)} {profiles.length > 0 && Array.isArray(profiles) ? (
</Box> profiles.map((profile) => (
</> <ProfileItem key={profile._id} profile={profile} />
)} ))
</Box> ) : (
<Typography>No profiles found...</Typography>
)}
</Box>
</>
)}
</Container> </Container>
); );
}; };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 MiB

-1
View File
@@ -2,7 +2,6 @@ import React from "react";
import ReactDOM from "react-dom/client"; import ReactDOM from "react-dom/client";
import App from "./App"; import App from "./App";
import "./App2.css";
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement, document.getElementById("root") as HTMLElement,
); );
+96 -106
View File
@@ -1,41 +1,41 @@
import express from 'express'; import express from "express";
import axios from 'axios'; import axios from "axios";
import config from 'config'; import config from "config";
import auth from '../../middleware/auth'; import auth from "../../middleware/auth";
import { check, validationResult } from 'express-validator'; import { check, validationResult } from "express-validator";
// bring in normalize to give us a proper url, regardless of what user entered // bring in normalize to give us a proper url, regardless of what user entered
import normalize from 'normalize-url'; import normalize from "normalize-url";
import checkObjectId from '../../middleware/checkObjectId'; import checkObjectId from "../../middleware/checkObjectId";
import Profile from '../../models/Profile'; import Profile from "../../models/Profile";
import User from '../../models/User'; import User from "../../models/User";
import Post from '../../models/Post'; import Post from "../../models/Post";
import { isUserId } from '../../utils'; import { isUserId } from "../../utils";
const router = express.Router(); const router = express.Router();
// @route GET api/profile/me // @route GET api/profile/me
// @desc Get current users profile // @desc Get current users profile
// @access Private // @access Private
router.get('/me', auth, async (req, res) => { router.get("/me", auth, async (req, res) => {
try { try {
if (isUserId(req)) { if (isUserId(req)) {
const profile = await Profile.findOne({ const profile = await Profile.findOne({
user: req.user.id user: req.user.id,
}).populate('user', ['name', 'avatar']); }).populate("user", ["name", "avatar"]);
if (!profile) { if (!profile) {
return res.status(400).json({ msg: 'There is no profile for this user' }); return res
.status(400)
.json({ msg: "There is no profile for this user" });
} }
res.json(profile); res.json(profile);
} }
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) res.status(500).send("Server Error");
console.error(err.message);
res.status(500).send('Server Error');
} }
}); });
@@ -43,10 +43,10 @@ router.get('/me', auth, async (req, res) => {
// @desc Create or update user profile // @desc Create or update user profile
// @access Private // @access Private
router.post( router.post(
'/', "/",
auth, auth,
check('status', 'Status is required').notEmpty(), check("status", "Status is required").notEmpty(),
check('skills', 'Skills is required').notEmpty(), check("skills", "Skills is required").notEmpty(),
async (req, res) => { async (req, res) => {
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
@@ -71,17 +71,23 @@ router.post(
const profileFields = { const profileFields = {
user: req.user.id, user: req.user.id,
website: website:
website && website !== '' website && website !== ""
? normalize(website, { forceHttps: true }) ? normalize(website, { forceHttps: true })
: '', : "",
skills: Array.isArray(skills) skills: Array.isArray(skills)
? skills ? skills
: skills.split(',').map((skill: string) => ' ' + skill.trim()), : skills.split(",").map((skill: string) => " " + skill.trim()),
...rest ...rest,
}; };
// Build socialFields object // Build socialFields object
const socialFields: { [key: string]: any } = { youtube, twitter, instagram, linkedin, facebook }; const socialFields: { [key: string]: any } = {
youtube,
twitter,
instagram,
linkedin,
facebook,
};
// normalize social fields to ensure valid url // normalize social fields to ensure valid url
for (const [key, value] of Object.entries(socialFields)) { for (const [key, value] of Object.entries(socialFields)) {
@@ -96,33 +102,29 @@ router.post(
let profile = await Profile.findOneAndUpdate( let profile = await Profile.findOneAndUpdate(
{ user: req.user.id }, { user: req.user.id },
{ $set: profileFields }, { $set: profileFields },
{ new: true, upsert: true, setDefaultsOnInsert: true } { new: true, upsert: true, setDefaultsOnInsert: true },
); );
return res.json(profile); return res.json(profile);
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) return res.status(500).send("Server Error");
console.error(err.message);
return res.status(500).send('Server Error');
} }
} }
} },
); );
// @route GET api/profile // @route GET api/profile
// @desc Get all profiles // @desc Get all profiles
// @access Public // @access Public
router.get('/', async (req, res) => { router.get("/", async (req, res) => {
try { try {
const profiles = await Profile.find().populate('user', ['name', 'avatar']); const profiles = await Profile.find().populate("user", ["name", "avatar"]);
res.json(profiles); res.json(profiles);
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) res.status(500).send("Server Error");
console.error(err.message);
res.status(500).send('Server Error');
} }
}); });
@@ -130,31 +132,29 @@ router.get('/', async (req, res) => {
// @desc Get profile by user ID // @desc Get profile by user ID
// @access Public // @access Public
router.get( router.get(
'/user/:user_id', "/user/:user_id",
checkObjectId('user_id'), checkObjectId("user_id"),
async ({ params: { user_id } }, res) => { async ({ params: { user_id } }, res) => {
try { try {
const profile = await Profile.findOne({ const profile = await Profile.findOne({
user: user_id user: user_id,
}).populate('user', ['name', 'avatar']); }).populate("user", ["name", "avatar"]);
if (!profile) return res.status(400).json({ msg: 'Profile not found' }); if (!profile) return res.status(400).json({ msg: "Profile not found" });
return res.json(profile); return res.json(profile);
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) return res.status(500).json({ msg: "Server error" });
console.error(err.message);
return res.status(500).json({ msg: 'Server error' });
} }
} },
); );
// @route DELETE api/profile // @route DELETE api/profile
// @desc Delete profile, user & posts // @desc Delete profile, user & posts
// @access Private // @access Private
router.delete('/', auth, async (req, res) => { router.delete("/", auth, async (req, res) => {
try { try {
// Remove user posts // Remove user posts
// Remove profile // Remove profile
@@ -163,16 +163,14 @@ router.delete('/', auth, async (req, res) => {
await Promise.all([ await Promise.all([
Post.deleteMany({ user: req.user.id }), Post.deleteMany({ user: req.user.id }),
Profile.findOneAndRemove({ user: req.user.id }), Profile.findOneAndRemove({ user: req.user.id }),
User.findOneAndRemove({ _id: req.user.id }) User.findOneAndRemove({ _id: req.user.id }),
]); ]);
res.json({ msg: 'User deleted' }); res.json({ msg: "User deleted" });
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) res.status(500).send("Server Error");
console.error(err.message);
res.status(500).send('Server Error');
} }
}); });
@@ -180,11 +178,11 @@ router.delete('/', auth, async (req, res) => {
// @desc Add profile experience // @desc Add profile experience
// @access Private // @access Private
router.put( router.put(
'/experience', "/experience",
auth, auth,
check('title', 'Title is required').notEmpty(), check("title", "Title is required").notEmpty(),
check('company', 'Company is required').notEmpty(), check("company", "Company is required").notEmpty(),
check('from', 'From date is required and needs to be from the past') check("from", "From date is required and needs to be from the past")
.notEmpty() .notEmpty()
.custom((value, { req }) => (req.body.to ? value < req.body.to : true)), .custom((value, { req }) => (req.body.to ? value < req.body.to : true)),
async (req, res) => { async (req, res) => {
@@ -205,26 +203,24 @@ router.put(
res.json(profile); res.json(profile);
} }
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) res.status(500).send("Server Error");
console.error(err.message);
res.status(500).send('Server Error');
} }
} },
); );
// @route DELETE api/profile/experience/:exp_id // @route DELETE api/profile/experience/:exp_id
// @desc Delete experience from profile // @desc Delete experience from profile
// @access Private // @access Private
router.delete('/experience/:exp_id', auth, async (req, res) => { router.delete("/experience/:exp_id", auth, async (req, res) => {
try { try {
if (isUserId(req)) { if (isUserId(req)) {
const foundProfile = await Profile.findOne({ user: req.user.id }); const foundProfile = await Profile.findOne({ user: req.user.id });
if (foundProfile) { if (foundProfile) {
foundProfile.experience = foundProfile.experience.filter( foundProfile.experience = foundProfile.experience.filter(
(exp: any) => exp._id.toString() !== req.params.exp_id (exp: any) => exp._id.toString() !== req.params.exp_id,
); );
await foundProfile.save(); await foundProfile.save();
@@ -232,11 +228,9 @@ router.delete('/experience/:exp_id', auth, async (req, res) => {
return res.status(200).json(foundProfile); return res.status(200).json(foundProfile);
} }
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) return res.status(500).json({ msg: "Server error" });
console.error(err.message);
return res.status(500).json({ msg: 'Server error' });
} }
}); });
@@ -244,12 +238,12 @@ router.delete('/experience/:exp_id', auth, async (req, res) => {
// @desc Add profile education // @desc Add profile education
// @access Private // @access Private
router.put( router.put(
'/education', "/education",
auth, auth,
check('school', 'School is required').notEmpty(), check("school", "School is required").notEmpty(),
check('degree', 'Degree is required').notEmpty(), check("degree", "Degree is required").notEmpty(),
check('fieldofstudy', 'Field of study is required').notEmpty(), check("fieldofstudy", "Field of study is required").notEmpty(),
check('from', 'From date is required and needs to be from the past') check("from", "From date is required and needs to be from the past")
.notEmpty() .notEmpty()
.custom((value, { req }) => (req.body.to ? value < req.body.to : true)), .custom((value, { req }) => (req.body.to ? value < req.body.to : true)),
async (req, res) => { async (req, res) => {
@@ -269,62 +263,58 @@ router.put(
res.json(profile); res.json(profile);
} }
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) res.status(500).send("Server Error");
console.error(err.message);
res.status(500).send('Server Error');
} }
} },
); );
// @route DELETE api/profile/education/:edu_id // @route DELETE api/profile/education/:edu_id
// @desc Delete education from profile // @desc Delete education from profile
// @access Private // @access Private
router.delete('/education/:edu_id', auth, async (req, res) => { router.delete("/education/:edu_id", auth, async (req, res) => {
try { try {
if (isUserId(req)) { if (isUserId(req)) {
const foundProfile = await Profile.findOne({ user: req.user.id }); const foundProfile = await Profile.findOne({ user: req.user.id });
if (foundProfile) { if (foundProfile) {
foundProfile.education = foundProfile.education.filter( foundProfile.education = foundProfile.education.filter(
(edu: any) => edu._id.toString() !== req.params.edu_id (edu: any) => edu._id.toString() !== req.params.edu_id,
); );
await foundProfile.save(); await foundProfile.save();
} }
return res.status(200).json(foundProfile); return res.status(200).json(foundProfile);
} }
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) return res.status(500).json({ msg: "Server error" });
console.error(err.message);
return res.status(500).json({ msg: 'Server error' });
} }
}); });
// @route GET api/profile/github/:username // @route GET api/profile/github/:username
// @desc Get user repos from Github // @desc Get user repos from Github
// @access Public // @access Public
router.get('/github/:username', async (req, res) => { router.get("/github/:username", async (req, res) => {
console.log(config.get("githubToken"));
console.log(req.params.username);
try { try {
const uri = encodeURI( const uri = encodeURI(
`https://api.github.com/users/${req.params.username}/repos?per_page=5&sort=created:asc` `https://api.github.com/users/${req.params.username}/repos?per_page=5&sort=created:asc`,
); );
const headers = { const headers = {
'user-agent': 'node.js', "user-agent": "node.js",
Authorization: `token ${config.get('githubToken')}` Authorization: `token ${config.get("githubToken")}`,
}; };
const gitHubResponse = await axios.get(uri, { headers }); const gitHubResponse = await axios.get(uri, { headers });
return res.json(gitHubResponse.data); return res.json(gitHubResponse.data);
} catch (err: unknown) { } catch (err: unknown) {
if (typeof err === 'string') if (typeof err === "string") console.error(err);
console.error(err) else if (err instanceof Error) console.error(err.message);
else if (err instanceof Error) return res.status(404).json({ msg: "No Github profile found" });
console.error(err.message);
return res.status(404).json({ msg: 'No Github profile found' });
} }
}); });
module.exports = router module.exports = router;