Skip to content

[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

Merged
merged 16 commits into from
Aug 12, 2021
2 changes: 1 addition & 1 deletion components/CategorySelection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const CategoriesSelection = () => {
if (!categories) {
return <div>Not found.</div>;
}

return (
<div className="mt-16 md:mt-32 mb-4 mx-10 flex flex-col flex-grow items-center justify-center">
<div>
Expand All @@ -38,7 +39,6 @@ const CategoriesSelection = () => {
id={id}
title={convertSlugToTitle(slug)}
url={urlLookup[slug]}

link={`/categories/${slug}`}
/>
))}
Expand Down
2 changes: 1 addition & 1 deletion components/Content/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Content = ({
<ContentCard
key={id}
isLiked={favorites.includes(id)}
imgSrc={img_url ?? "https://picsum.photos/100"}
imgSrc={img_url ? img_url : "https://picsum.photos/100"}
imgAlt={title}
contentType={content_type}
title={title}
Expand Down
207 changes: 207 additions & 0 deletions components/Form/index.js
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;
10 changes: 9 additions & 1 deletion next.config.js
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",
],
Comment on lines +4 to +12
Copy link
Collaborator

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.

Copy link
Collaborator Author

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)

Copy link
Collaborator Author

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

Copy link
Collaborator

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.

},
};
58 changes: 58 additions & 0 deletions pages/add-content.js
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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.
So I need to pass the data object as a prop to the form.


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;
1 change: 1 addition & 0 deletions pages/api/content/index.js
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;`;
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;` });
}
Expand Down