244 lines
7.8 KiB
JavaScript
244 lines
7.8 KiB
JavaScript
|
import React, { useState, useEffect } from "react";
|
|||
|
import { useParams } from "react-router-dom";
|
|||
|
|
|||
|
const cataas = (endpoint) => "https://cataas.com" + endpoint;
|
|||
|
const bookmarker = (endpoint) => "http://localhost:4000/api/v1" + endpoint;
|
|||
|
|
|||
|
const Bookmarks = (props) => {
|
|||
|
// This gives us access to the :id in /u/:id
|
|||
|
const { id } = useParams();
|
|||
|
|
|||
|
// For standard headers in all Bookmarker requests
|
|||
|
const headers = {
|
|||
|
"Authorization": "Basic " + id,
|
|||
|
};
|
|||
|
|
|||
|
// The object describing the randomly fetched cat image
|
|||
|
const [randomCat, setRandomCat] = useState(null);
|
|||
|
|
|||
|
// The state of the CreateNewBookmark form
|
|||
|
const [formState, setFormState] = useState({
|
|||
|
category: "",
|
|||
|
notes: "",
|
|||
|
});
|
|||
|
|
|||
|
// The list of all categories that exist in the database
|
|||
|
const [categories, setCategories] = useState([]);
|
|||
|
|
|||
|
// The collection of which posts are being shown
|
|||
|
const [activeCategory, setActiveCategory] = useState(null);
|
|||
|
const [categoryCollection, setCategoryCollection] = useState([]);
|
|||
|
|
|||
|
// This gets run once and then never again, doing initial setup for our
|
|||
|
// component
|
|||
|
useEffect(() => {
|
|||
|
// Fetch and render a random cat picture
|
|||
|
showNewCat();
|
|||
|
|
|||
|
// Fetch the list of categories with more than one bookmark
|
|||
|
getAllCategories();
|
|||
|
}, []);
|
|||
|
|
|||
|
// When the active collection is changed, do this
|
|||
|
useEffect(() => {
|
|||
|
(async () => {
|
|||
|
// This should only be run if an activeCategory has been selected
|
|||
|
if (activeCategory != null) {
|
|||
|
const req = await fetch(
|
|||
|
bookmarker("/bookmark/category/" + activeCategory),
|
|||
|
{ headers }
|
|||
|
);
|
|||
|
|
|||
|
// Update the state with the resultant JSON object
|
|||
|
setCategoryCollection(await req.json());
|
|||
|
}
|
|||
|
})();
|
|||
|
}, [activeCategory]);
|
|||
|
|
|||
|
const showNewCat = async () => {
|
|||
|
// First, reset the cat picture. This will briefly rerender the
|
|||
|
// paragraph saying that the image is loading
|
|||
|
setRandomCat(null);
|
|||
|
|
|||
|
const req = await fetch(cataas("/cat?json=true"));
|
|||
|
setRandomCat(await req.json());
|
|||
|
};
|
|||
|
|
|||
|
const getAllCategories = async () => {
|
|||
|
const req = await fetch(
|
|||
|
bookmarker("/bookmark/categories"), { headers });
|
|||
|
setCategories(await req.json());
|
|||
|
};
|
|||
|
|
|||
|
const chooseCategory = (category) => {
|
|||
|
setActiveCategory(category);
|
|||
|
}
|
|||
|
|
|||
|
const bookmarkCat = async () => {
|
|||
|
const req = await fetch(bookmarker("/bookmark"), {
|
|||
|
method: "POST",
|
|||
|
headers: {...headers,
|
|||
|
"Content-Type": "application/json"
|
|||
|
},
|
|||
|
body: JSON.stringify({ ...formState, remote_id: randomCat.id })
|
|||
|
});
|
|||
|
|
|||
|
// Update the category list in case a new one was added
|
|||
|
await getAllCategories();
|
|||
|
|
|||
|
// Reset the form fields
|
|||
|
setFormState({
|
|||
|
category: "",
|
|||
|
notes: "",
|
|||
|
});
|
|||
|
|
|||
|
// Show a new cat
|
|||
|
await showNewCat();
|
|||
|
};
|
|||
|
|
|||
|
const removeBookmark = async (id) => {
|
|||
|
const req = await fetch(bookmarker("/bookmark/" + id), {
|
|||
|
headers,
|
|||
|
method: "DELETE",
|
|||
|
});
|
|||
|
|
|||
|
// Filter out the deleted bookmark and then reload the component
|
|||
|
setCategoryCollection(categoryCollection.filter(c => c.id != id));
|
|||
|
};
|
|||
|
|
|||
|
return (
|
|||
|
<div className = "main-container">
|
|||
|
<h2 className = "center-text"> Random Cat </h2>
|
|||
|
<div>
|
|||
|
<img className = "random-image center-box-inner"
|
|||
|
src = { randomCat != null
|
|||
|
? cataas(randomCat.url)
|
|||
|
: null
|
|||
|
}
|
|||
|
alt = { randomCat != null
|
|||
|
? randomCat.tags.join(" ")
|
|||
|
: "Loading cat..."
|
|||
|
} />
|
|||
|
<div className = "center-text">
|
|||
|
<button onClick = { showNewCat }>
|
|||
|
Show New Cat
|
|||
|
</button>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<div className = "form-container">
|
|||
|
<h3 className = "center-text">
|
|||
|
Like this cat? Bookmark them.
|
|||
|
</h3>
|
|||
|
<NewBookmarkForm
|
|||
|
submitCallback = { bookmarkCat }
|
|||
|
setter = { setFormState }
|
|||
|
state = { formState }/>
|
|||
|
</div>
|
|||
|
<h2> Browse your collections </h2>
|
|||
|
<SelectCategory
|
|||
|
categories = { categories }
|
|||
|
activeCategory = { activeCategory }
|
|||
|
callback = { chooseCategory }/>
|
|||
|
<div style = {{ height: 50 }}></div>
|
|||
|
{ activeCategory != null && categoryCollection.length > 0
|
|||
|
? <BookmarkCollection
|
|||
|
removeCallback = { removeBookmark }
|
|||
|
bookmarks = { categoryCollection } />
|
|||
|
: <></>
|
|||
|
}
|
|||
|
</div>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
// A reusable form component that takes a list of objects representing
|
|||
|
// input properties and then renders them dynamically
|
|||
|
const Form = (props) => {
|
|||
|
const updaterFactory = (key) => (e) => props.setter({
|
|||
|
...props.state,
|
|||
|
[key]: e.target.value,
|
|||
|
});
|
|||
|
|
|||
|
return (
|
|||
|
<form onSubmit={ e => e.preventDefault() }>
|
|||
|
{
|
|||
|
props.inputs.map((data, i) =>
|
|||
|
<div key = { i }>
|
|||
|
<p className = "form-label"> { data.label } </p>
|
|||
|
<input
|
|||
|
placeholder = { data.placeholder }
|
|||
|
value = { props.state[data.name] }
|
|||
|
onChange = { updaterFactory(data.name) }/>
|
|||
|
</div>
|
|||
|
)
|
|||
|
}
|
|||
|
<div className = "center-text">
|
|||
|
<button className = "form-submit-button"
|
|||
|
onClick = { props.submitCallback }>
|
|||
|
Submit
|
|||
|
</button>
|
|||
|
</div>
|
|||
|
</form>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
// Renders the Form component for our specific needs in the case of bookmarking
|
|||
|
// a cat photo
|
|||
|
const NewBookmarkForm = (props) => (
|
|||
|
<Form
|
|||
|
{ ...props }
|
|||
|
inputs = {[
|
|||
|
{
|
|||
|
name: "category",
|
|||
|
label: "Category name",
|
|||
|
placeholder: " Something really cool..."
|
|||
|
},
|
|||
|
{ name: "notes", label: "Notes", placeholder: "Any other notes" }
|
|||
|
]}/>
|
|||
|
);
|
|||
|
|
|||
|
// Renders a list of buttons, each representing a category. When the button
|
|||
|
// is pressed, the activeCategory will be updated to reflect the category it
|
|||
|
// represents
|
|||
|
const SelectCategory = ({ categories, activeCategory, callback }) => {
|
|||
|
return (
|
|||
|
<>
|
|||
|
{ categories.length > 0
|
|||
|
? categories.map((category, i) =>
|
|||
|
<button className = {
|
|||
|
category == activeCategory
|
|||
|
? "active"
|
|||
|
: ""
|
|||
|
}
|
|||
|
key = { i }
|
|||
|
onClick = { () => callback(category) }>
|
|||
|
{ category }
|
|||
|
</button>
|
|||
|
)
|
|||
|
: <p>Nothing to show</p>
|
|||
|
}
|
|||
|
</>
|
|||
|
);
|
|||
|
};
|
|||
|
|
|||
|
// Renders a list of photos followed by their notes
|
|||
|
const BookmarkCollection = ({
|
|||
|
bookmarks,
|
|||
|
removeCallback
|
|||
|
}) => bookmarks.map((b, i) =>
|
|||
|
<div key = { i }>
|
|||
|
<img className = "collection-image"
|
|||
|
src = { cataas("/cat/" + b.remote_id) } />
|
|||
|
<div className = "collection-item-caption">
|
|||
|
<button onClick = { () => removeCallback(b.id) }>
|
|||
|
🗑️
|
|||
|
</button>
|
|||
|
{ b.notes != null && b.notes.length > 0
|
|||
|
? <p className = "collection-note"> { b.notes } </p>
|
|||
|
: <></>
|
|||
|
}
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
);
|
|||
|
|
|||
|
export default Bookmarks;
|