-
Notifications
You must be signed in to change notification settings - Fork 0
[COD-53] #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[COD-53] #59
Changes from all commits
a478a0a
93c2465
eb62192
675f8bc
fd37c35
7990623
ae42e87
be458fb
530ba48
6f449bf
bb09166
c36e041
3437e54
146d4ce
41f9d18
9ad4ef1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
import { useRef, useState } from "react"; | ||
import useSWR from "swr"; | ||
import * as yup from "yup"; | ||
|
||
const Form = ({ data, submit }) => { | ||
const { data: issuesData, error: issuesError } = useSWR(`/api/issues`); | ||
const [errors, setErrors] = useState([]); | ||
const [title, setTitle] = useState(data.title); | ||
const [description, setDescription] = useState(data.description); | ||
const [contentType, setContentType] = useState(data.content_type); | ||
const [imgUrl, setImgUrl] = useState(data.img_url); | ||
const [videoUrl, setVideoUrl] = useState(data.video_url); | ||
const issueIdRef = useRef(null); | ||
|
||
if (!issuesData && !issuesError) { | ||
return <h1>Loading...</h1>; | ||
} | ||
|
||
if (!issuesData) { | ||
return <h1>Not found</h1>; | ||
} | ||
|
||
// sets shape of object | ||
const dataSchema = yup.object().shape({ | ||
title: yup.string().min(1).required(), | ||
description: yup.string().required(), | ||
content_type: yup.string().required(), | ||
img_url: yup.string().url().required(), | ||
video_url: yup.string().url(), | ||
}); | ||
|
||
const handleSubmit = async (event) => { | ||
event.preventDefault(); | ||
|
||
// collect values from input fields | ||
const data = { | ||
title: title, | ||
description: description, | ||
content_type: contentType, | ||
img_url: imgUrl, | ||
video_url: videoUrl, | ||
relations: Number(issueIdRef.current.value), | ||
}; | ||
|
||
// when abortEarly:false -> collects errors from all fields | ||
// if abortEarly:true -> returns error from one field | ||
// path = id of the field | ||
try { | ||
await dataSchema.validate(data, { abortEarly: false }); | ||
submit(data); | ||
} catch (error) { | ||
setErrors(error.inner.map((el) => el.path)); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="p-20 w-full h-auto"> | ||
<form | ||
className="shadow w-4/5 h-auto p-8 mx-auto text-center bg-gray-100" | ||
onSubmit={handleSubmit} | ||
> | ||
{errors.length > 0 && ( | ||
<h1 className="text-center text-2xl mb-2 text-red-600"> | ||
Please fill all required fields | ||
</h1> | ||
)} | ||
<div> | ||
<div className="float-left w-1/4 mt-2 text-right"> | ||
<label htmlFor="title" className="inline-block p-4"> | ||
Title<span className="text-red-600">*</span> | ||
</label> | ||
</div> | ||
<div className="float-left w-3/4 mt-2"> | ||
<input | ||
type="text" | ||
id="title" | ||
name="title" | ||
value={title} | ||
onChange={(e) => setTitle(e.target.value)} | ||
className={`${ | ||
errors.includes("title") && "border-red-600" | ||
} w-3/4 p-4 border border-gray-600 rounded resize-y`} | ||
placeholder="Content title..." | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div> | ||
<div className="float-left w-1/4 mt-2 text-right"> | ||
<label htmlFor="description" className="inline-block p-4"> | ||
Description <span className="text-red-600">*</span> | ||
</label> | ||
</div> | ||
<div className="float-left w-3/4 mt-2"> | ||
<textarea | ||
type="text" | ||
id="description" | ||
name="description" | ||
value={description} | ||
onChange={(e) => setDescription(e.target.value)} | ||
className={`${ | ||
errors.includes("description") && "border-red-600" | ||
} w-3/4 h-40 p-4 border border-gray-600 rounded resize-y`} | ||
placeholder="Content description..." | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div> | ||
<div className="float-left w-1/4 mt-2 text-right"> | ||
<label htmlFor="content_type" className="inline-block p-4"> | ||
Content Type<span className="text-red-600">*</span> | ||
</label> | ||
</div> | ||
<div className="float-left w-3/4 mt-2"> | ||
<input | ||
type="text" | ||
id="content_type" | ||
name="content_type" | ||
value={contentType} | ||
onChange={(e) => setContentType(e.target.value)} | ||
className={`${ | ||
errors.includes("content_type") && "border-red-600" | ||
} w-3/4 p-4 border border-gray-600 rounded resize-y`} | ||
placeholder="Content type..." | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div> | ||
<div className="float-left w-1/4 mt-2 text-right"> | ||
<label htmlFor="img_url" className="inline-block p-4"> | ||
Image Url<span className="text-red-600">*</span> | ||
</label> | ||
</div> | ||
<div className="float-left w-3/4 mt-2"> | ||
<input | ||
type="text" | ||
id="img_url" | ||
name="img_url" | ||
value={imgUrl} | ||
onChange={(e) => setImgUrl(e.target.value)} | ||
className={`${ | ||
errors.includes("img_url") && "border-red-600" | ||
} w-3/4 p-4 border border-gray-600 rounded resize-y`} | ||
placeholder="Paste img url here..." | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div> | ||
<div className="float-left w-1/4 mt-2 text-right"> | ||
<label htmlFor="video_url" className="inline-block p-4"> | ||
Video Url<span className="text-red-600">*</span> | ||
</label> | ||
</div> | ||
<div className="float-left w-3/4 mt-2"> | ||
<input | ||
type="text" | ||
id="video_url" | ||
name="video_url" | ||
value={videoUrl} | ||
onChange={(e) => setVideoUrl(e.target.value)} | ||
className={`${ | ||
errors.includes("video_url") && "border-red-600" | ||
} w-3/4 p-4 border border-gray-600 rounded resize-y`} | ||
placeholder="Video url..." | ||
/> | ||
</div> | ||
</div> | ||
|
||
<div> | ||
<div className="float-left w-1/4 mt-2 text-right"> | ||
<label htmlFor="issues" className="inline-block p-4"> | ||
Choose an issue: | ||
</label> | ||
</div> | ||
<div className="float-left w-3/4 mt-2"> | ||
<select | ||
ref={issueIdRef} | ||
name="issues" | ||
id="issues" | ||
className="w-3/4 p-4 border border-gray-600 rounded resize-y" | ||
> | ||
{issuesData.data.map((issue) => { | ||
return ( | ||
<option key={issue.id} value={issue.id}> | ||
{issue.name} | ||
</option> | ||
); | ||
})} | ||
</select> | ||
</div> | ||
</div> | ||
<div> | ||
<input | ||
type="submit" | ||
value="Submit" | ||
className="border-2 cursor-pointer rounded-2xl text-white bg-blue-600 py-3 px-8 mt-2" | ||
/> | ||
</div> | ||
</form> | ||
</div> | ||
); | ||
}; | ||
|
||
export default Form; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,14 @@ | ||
// next.config.js | ||
module.exports = { | ||
images: { | ||
domains: ["picsum.photos", "dummyimage.com"], | ||
domains: [ | ||
"picsum.photos", | ||
"dummyimage.com", | ||
"images.unsplash.com", | ||
"insidethemagic-119e2.kxcdn.com", | ||
"saltinmycoffee.com", | ||
"breedingbusiness.com", | ||
"unsplash.com", | ||
], | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { useRouter } from "next/router"; | ||
import { useState } from "react"; | ||
import Form from "~/components/Form"; | ||
import Layout from "~/components/Layout/Layout"; | ||
|
||
const AddContentPage = () => { | ||
const router = useRouter(); | ||
const [error, setError] = useState(false); | ||
|
||
// send data obj to api/content | ||
const handleAddContentSubmit = async (data) => { | ||
setError(false); | ||
const response = await fetch("/api/content", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Accept: "application/json", | ||
}, | ||
body: JSON.stringify(data), | ||
}); | ||
|
||
// if response ok redirect to issue page | ||
if (response.ok) { | ||
router.push(`/issues/${data.relations}`); | ||
} else { | ||
setError(true); | ||
} | ||
}; | ||
|
||
//data obj for the form | ||
const data = { | ||
title: "", | ||
description: "", | ||
content_type: "", | ||
img_url: "", | ||
video_url: "", | ||
relations: "", | ||
}; | ||
Comment on lines
+30
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why declare this here? Why not declare it inside the component to save having to pass it in as a prop?I'm potentially missing something here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's because I use the same form for updating and adding the content. |
||
|
||
return ( | ||
<Layout> | ||
<div> | ||
<h1 className="text-center text-2xl px-32"> | ||
To add new content for mental health issues just fill out the form | ||
below and submit it | ||
</h1> | ||
{error && ( | ||
<h1 className="text-center text-2xl text-red-600"> | ||
Please check your form | ||
</h1> | ||
)} | ||
<Form data={data} submit={handleAddContentSubmit} /> | ||
</div> | ||
</Layout> | ||
); | ||
}; | ||
|
||
export default AddContentPage; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import sql from "~/lib/postgres"; | ||
|
||
const handler = async (req, res) => { | ||
const issuesTest = await sql`SELECT * FROM issues;`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can probably be removed, I presume you were using this to log the output somewhere or something like that. |
||
if (req.method === "GET") { | ||
return res.status(200).json({ data: await sql`SELECT * FROM content;` }); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My first thought on this is that I wonder if this can be done dynamically. Really not sure if that's possible so it'll be worth having a read of the documentation. My understanding is that these domains determine where an image can be hosted to be used in the
next/Image
component so I'm just wondering what would happen if an admin user uploaded an image that was hosted somewhere different.This might not even be a possibility, in which case you can ignore this whole comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about it too, but it seems like it's not possible yet. (or maybe I just missed something)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
check the comments here:
vercel/next.js#18311
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed yesterday, think you could just use normal
tags for anything uploaded by the user instead of trying to mess around with this.