Skip to content

Commit

Permalink
final commit before presentation, added PATCH routes and Edit views f…
Browse files Browse the repository at this point in the history
…or Posts and Events (admin only)
  • Loading branch information
dmostoller committed Mar 8, 2024
1 parent ab6b70c commit da20dd3
Show file tree
Hide file tree
Showing 14 changed files with 268 additions and 97 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ There are 5 tables: Users, Paintings, Comments, Posts, and Events.
There are two one-to-many relationships that form a many to many relationship: Users---<Comments>----Paintings.
The Posts and Events tables are only accesible by the admins and therefor are self contained.

Every resource has two Flask-Resful classes for routes, with every resource having GET, POST and DELETE routes. Paintings also has a PATCH route so that the paintings can be updated as needed.
Every resource has two Flask-Resful classes for routes, with every resource having GET, POST PATCH, and DELETE routes except users and comments.


### Future Expansions
I plan to add a few features and then deploy the site, those include:
- Enabling the direct upload of photos instead of just linking to already hosted files
- Add PATCH routes for all resources so everything can be updated (except comments)
- Fix the error handling of the login/signup forms so that bad entries dont break things (nonexistent user or a non-unique choice of username for example)
- Add PATCH route for user and a view to see or edit or delete the user.
- Fix the error handling of the login/signup forms
- Possibly allow the Admin to be able to delete comments from any user
- Dark Mode or just a darker theme
- Integrate Formik validation with EmailJS
5 changes: 5 additions & 0 deletions client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import AddEvent from './components/AddEvent.js';
import AddPainting from './components/AddPainting.js';
import EventDetail from './components/EventDetail.js';
import EditPainting from './components/EditPainting.js';
import EditPost from './components/EditPost.js';
import EditEvent from './components/EditEvent.js';


export default function App() {
const [pageToLoad, setPageToLoad] = useState("homepage")
Expand Down Expand Up @@ -64,7 +67,9 @@ export default function App() {
<Route path="/events" element={<EventsPage user={user} isAdmin={isAdmin}/>} />
<Route path="/events/new" element={<AddEvent/>} />
<Route path="/posts/:id" element={<PostDetail user={user} isAdmin={isAdmin}/>} />
<Route path="/posts/:id/edit" element={<EditPost/>} />
<Route path="/events/:id" element={<EventDetail user={user} isAdmin={isAdmin}/>} />
<Route path="/events/:id/edit" element={<EditEvent/>} />
<Route path="/contact" element={<ContactPage/>} />
<Route path="/posts/new" element={<AddPost/>} />
<Route path="/login" element={<LoginForm onLogin={handleLogin}/>} />
Expand Down
28 changes: 0 additions & 28 deletions client/src/components/AddEvent.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,31 +92,3 @@ function AddEvent() {
}

export default AddEvent



// const [formData, setFormData] = useState(initialState)

// function handleChange(e) {
// //console.log(e.target.value)
// setFormData({...formData, [e.target.name]: e.target.value})
// }

// function handleAddNewEvent(e){
// e.preventDefault()
// if(window.confirm("Are you sure you're ready to submit?")) {
// setFormData({...formData})
// fetch("/events/new", {
// method: "POST",
// headers: {
// "Content-Type": "application/json"
// },
// body: JSON.stringify({...formData})
// })
// .then((res) => res.json())
// .then((newEvent) => {
// navigate('/events')
// })
// setFormData(initialState)
// }
// }
2 changes: 1 addition & 1 deletion client/src/components/Comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function Comment({username, comment,key, id, date_added, comment_user_id, user,
<div className="author">{username}<div className="metadata"><span className="date">{date_added}</span></div></div>
<div className="text">{comment}</div>

{user && user.id == comment_user_id ?
{user && user.id === comment_user_id ?
<div className="actions">
<a onClick={handleDeleteComment} className="delete">Delete</a>
</div>
Expand Down
3 changes: 1 addition & 2 deletions client/src/components/CommentsList.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React, {useEffect, useState} from "react";
import {useParams, useNavigate} from "react-router-dom";
import Comment from "./Comment";
import CommentForm from "./CommentForm";

Expand All @@ -23,7 +22,7 @@ function CommentsList({user, painting_id}){
}

const commentsSection = comments
.filter(comment => comment.painting_id == painting_id)
.filter(comment => comment.painting_id === painting_id)
.map(comment => (
<Comment
key={comment.id}
Expand Down
98 changes: 98 additions & 0 deletions client/src/components/EditEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React, {useState, useEffect} from "react";
import {Link, useNavigate, useParams} from "react-router-dom";
import { useFormik } from "formik";
import * as yup from "yup";

function EditEvent() {
const navigate = useNavigate();
const [error, setError] = useState(null);
const [event, setEvent] = useState({})
const {id} = useParams();


useEffect(() => {
fetch(`/events/${id}`)
.then((res) => res.json())
.then((event) => setEvent(event))
}, [id]);


const formSchema = yup.object().shape({
name: yup.string().required("Must enter a title"),
venue: yup.string().required("Must enter a venue"),
location: yup.string().required("Must enter a location"),
details: yup.string().required("Must enter event details"),
image_url: yup.string().required("Must enter an image link"),
event_date: yup.date().required("Must enter a date"),
event_link: yup.string().required("Must enter an event link"),
})
const initValues = event

const formik = useFormik({
enableReinitialize: true,
initialValues: initValues,
validationSchema: formSchema,
onSubmit: (values) => {
fetch(`/events/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
}).then((res) => {
if(res.ok) {
res.json().then(event => {
navigate(`/events/${id}`)
})
} else {
res.json().then(error => setError(error.message))
}
})
},
})

return (
<>
{error && <h2 style={{color:'red', textAlign:'center'}}> {error} </h2>}
<div className="ui container">
<form style={{width:"60%", margin:"auto", padding:"25px"}} className="ui form" onSubmit={formik.handleSubmit}>
<div className="field">
<label>Add Event</label>
<input type="text" name="name" value={formik.values.name} placeholder="Event Name..." onChange={formik.handleChange}></input>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.name}</p>}
</div>
<div className="field">
<input type="text" name="venue" value={formik.values.venue} placeholder="Venue..." onChange={formik.handleChange}></input>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.venue}</p>}
</div>
<div className="field">
<input type="text" name="location" value={formik.values.location} placeholder="Location address..." onChange={formik.handleChange}></input>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.location}</p>}
</div>
<div className="field">
<input type="text" name="image_url" value={formik.values.image_url} placeholder="Image link..." onChange={formik.handleChange}></input>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.image_url}</p>}
</div>
<div className="field">
<input type="text" name="event_date" value={formik.values.event_date} placeholder="Event Date (MM/DD/YYYY)..." onChange={formik.handleChange}></input>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.event_date}</p>}
</div>
<div className="field">
<input type="text" name="event_link" value={formik.values.event_link} placeholder="Link to Event..." onChange={formik.handleChange}></input>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.event_link}</p>}
</div>
<div className="field">
<textarea type="text" rows="6" name="details" value={formik.values.details} placeholder="Event Details..." onChange={formik.handleChange}></textarea>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.details}</p>}
</div>
<div className="field">
<Link to="/" className="ui button small teal" >Back</Link>
<button style={{float: "right"}} className="ui button small teal" type="submit">Submit</button>
</div>
</form>
</div>
</>
)
}

export default EditEvent
42 changes: 21 additions & 21 deletions client/src/components/EditPainting.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,27 @@ function EditPainting({}) {
const initValues = painting

const formik = useFormik({
enableReinitialize: true,
initialValues: initValues,
validationSchema: formSchema,
onSubmit: (values) => {
fetch(`/paintings/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
}).then((res) => {
if(res.ok) {
res.json().then(painting => {
navigate(`/paintings/${id}`)
})
} else {
res.json().then(error => setError(error.message))
}
})
},
})
enableReinitialize: true,
initialValues: initValues,
validationSchema: formSchema,
onSubmit: (values) => {
fetch(`/paintings/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
}).then((res) => {
if(res.ok) {
res.json().then(painting => {
navigate(`/paintings/${id}`)
})
} else {
res.json().then(error => setError(error.message))
}
})
},
})

return (
<>
Expand Down
78 changes: 78 additions & 0 deletions client/src/components/EditPost.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, {useState, useEffect} from "react";
import {Link, useNavigate, useParams} from "react-router-dom";
import { useFormik } from "formik";
import * as yup from "yup";

function EditPost() {
const navigate = useNavigate();
const [error, setError] = useState(null);
const [post, setPost] = useState({})
const {id} = useParams();

useEffect(() => {
fetch(`/posts/${id}`)
.then((res) => res.json())
.then((post) => setPost(post))
}, [id]);

const formSchema = yup.object().shape({
title: yup.string()
.required("Must enter a title")
.min(2, 'name must be more than two characters'),
content: yup.string().required("Must enter content for your post"),
image_url: yup.string().required("Must add an image link"),
})

const initValues = post
const formik = useFormik({
enableReinitialize: true,
initialValues: initValues,
validationSchema: formSchema,
onSubmit: (values) => {
fetch(`/posts/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(values),
}).then((res) => {
if(res.ok) {
res.json().then(post => {
navigate(`/posts/${id}`)
})
} else {
res.json().then(error => setError(error.message))
}
})
},
})

return (
<>
{error && <h2 style={{color:'red', textAlign:'center'}}> {error} </h2>}
<div className="ui container">
<form style={{width:"60%", margin:"auto", padding:"25px"}} className="ui form" onSubmit={formik.handleSubmit}>
<div className="field">
<label>Add Post</label>
<input type="text" name="title" value={formik.values.title} placeholder="Post title..." onChange={formik.handleChange}></input>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.title}</p>}
</div>
<div className="field">
<input type="text" name="image_url" value={formik.values.image_url} placeholder="Image link..." onChange={formik.handleChange}></input>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.image_url}</p>}
</div>
<div className="field">
<textarea type="text" rows="6" name="content" value={formik.values.content} placeholder="Post content..." onChange={formik.handleChange}></textarea>
{formik.errors && <p style={{color:'red', textAlign:'center'}}>{formik.errors.content}</p>}
</div>
<div className="field">
<Link to="/" className="ui button small teal" >Back</Link>
<button style={{float: "right"}} className="ui button small teal" type="submit">Submit</button>
</div>
</form>
</div>
</>
)
}

export default EditPost
21 changes: 13 additions & 8 deletions client/src/components/EventDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,20 @@ function EventDetail({user, isAdmin}){
<div style={{padding: "10px"}}>
<Link to="/events" className="ui button small teal">Back</Link>
<a href={event.event_link} className="ui button small teal" target="_blank" rel="noopener noreferrer">Go To Event</a>
{ (user && isAdmin) ? (
<>
<button style={{float: "right"}}className="ui icon button small teal" onClick={handleDeleteEvent}>
<i class="trash icon" style={{visibility: "visible"}}></i>
{ user && isAdmin ? (
<>
<button style={{float: "right"}} className="right attached ui icon button small teal" onClick={handleDeleteEvent}>
<i class="trash icon" style={{visibility: "visible"}}></i>
Delete Event
</button>
</>
)
: <></>
}
<Link to={`/events/${id}/edit`} style={{float: "right"}} className="ui left attached button small teal">
<i className="edit icon" style={{visibility: "visible"}}></i>
Edit Event
</Link>
</>
)
: <></>
}
</div>
</div>
</div>
Expand Down
5 changes: 1 addition & 4 deletions client/src/components/Login.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import * as yup from "yup";

function LoginForm({ onLogin }) {
const [errors, setErrors] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();

const formSchema = yup.object().shape({
Expand Down Expand Up @@ -67,9 +66,7 @@ function LoginForm({ onLogin }) {
</div>
<div className="field">
<Link to="/" className="ui button small teal">Back</Link>
<button style={{float: "right"}} className="ui button small teal" type="submit">
{isLoading ? "Loading..." : "Login"}
</button>
<button style={{float: "right"}} className="ui button small teal" type="submit">Login</button>
</div>
<div>
{errors.map((err) => (
Expand Down
19 changes: 2 additions & 17 deletions client/src/components/Post.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
import React from "react";
import {Link, useNavigate } from "react-router-dom";


export default function Post ({id, title, content, image_url, date_added, isAdmin, deletePost}) {
// const navigate = useNavigate()

// const handleDeletePost = (post) => {
// if (window.confirm("Are you sure you want to delete this post?")) {
// fetch(`/posts/${id}`, {
// method: "DELETE"
// })
// .then(() => {
// deletePost(post)
// navigate('/')
// })
// }
// }
import {Link } from "react-router-dom";


export default function Post ({id, title, content, image_url, date_added}) {
return (
<div className="ui container fluid">
<div className="ui horizontal card fluid" style={{marginBottom: "15px"}}>
Expand Down
Loading

0 comments on commit da20dd3

Please sign in to comment.