Skip to content

Commit

Permalink
Add individual photo page
Browse files Browse the repository at this point in the history
  • Loading branch information
ewhanson committed Jul 1, 2023
1 parent 2e3299c commit b6c3b82
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 39 deletions.
6 changes: 6 additions & 0 deletions e2e/viewer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ test("Can see photos in feed", async ({ page }) => {
const dateBadge = await page.getByText("Apr 6, 2023");
await expect(dateBadge).toBeVisible();
await expect(dateBadge).toHaveAttribute("title", /4\/6\/2023, \d{1,2}:55 PM/);

await page.getByRole("link", { name: "Brewing coffee" }).click();
const singlePhoto = await page.getByRole("img", {
name: "Shows Brewing Coffee",
});
await expect(singlePhoto).toBeVisible();
});

test("Cannot access uploader area", async ({ page }) => {
Expand Down
2 changes: 2 additions & 0 deletions ui/src/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { UploaderLogin } from "./components/routes/UploaderLogin.jsx";
import { UploaderDashboard } from "./components/routes/UploaderDashboard.jsx";
import { TagFeed } from "./components/routes/TagFeed.jsx";
import { WhatsNew } from "./components/routes/WhatsNew.jsx";
import { SinglePhoto } from "./components/routes/SinglePhoto.jsx";

export function App() {
const [authData, setAuthData] = useState({
Expand All @@ -33,6 +34,7 @@ export function App() {
<Home path={constants.ROUTES.HOME} />
<Login path={constants.ROUTES.LOGIN} />
<Feed path={constants.ROUTES.FEED} />
<SinglePhoto path={constants.ROUTES.PHOTO} />
<TagFeed path={constants.ROUTES.TAG} />
<About path={constants.ROUTES.ABOUT} />
<WhatsNew path={constants.ROUTES.WHATS_NEW} />
Expand Down
3 changes: 2 additions & 1 deletion ui/src/components/photoFeed/PhotoCard.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { constants } from "../../lib/constants.js";

export function PhotoCard({
id,
url,
description,
displayDate,
Expand All @@ -25,7 +26,7 @@ export function PhotoCard({
</div>
</div>
<h2 className="card-title">
{description}
<a href={constants.ROUTES.getPhotoRoute(id)}>{description}</a>
{isNew && <span className="badge badge-secondary">New</span>}
</h2>
<div className="card-actions">
Expand Down
1 change: 1 addition & 0 deletions ui/src/components/photoFeed/PhotoFeed.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function PhotoFeed({
return (
<PhotoCard
key={photo.id}
id={photo.id}
url={photo.url}
displayDate={photo.displayDate}
altDate={photo.altDate}
Expand Down
53 changes: 53 additions & 0 deletions ui/src/components/routes/SinglePhoto.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { MainComponentWrapper } from "../MainComponentWrapper.jsx";
import { PhotoCard } from "../photoFeed/PhotoCard.jsx";
import { useGetPhoto, useViewerAuthProtected } from "../../lib/customHooks.js";
import { LoadingSpinner } from "../LoadingSpinner.jsx";

export function SinglePhoto({ photoId }) {
const isViewer = useViewerAuthProtected();
if (!isViewer) return null;

const [photo, errorMessage, isFetching] = useGetPhoto(photoId);

if (errorMessage !== "") {
return (
<MainComponentWrapper>
<div className="alert alert-error shadow-lg max-w-lg">
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
className="stroke-current flex-shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
<span>Oops... {errorMessage}</span>
</div>
</div>
</MainComponentWrapper>
);
}

return (
<MainComponentWrapper>
{Object.entries(photo).length !== 0 && (
<PhotoCard
id={photo.id}
url={photo.url}
displayDate={photo.displayDate}
altDate={photo.altDate}
description={photo.description}
isNew={photo.isNew}
tags={photo.tags}
/>
)}
{isFetching && <LoadingSpinner />}
</MainComponentWrapper>
);
}
4 changes: 4 additions & 0 deletions ui/src/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const constants = {
HOME: "/",
LOGIN: "/login",
FEED: "/feed",
PHOTO: "/photos/:photoId",
ABOUT: "/about",
NOTIFICATIONS: "/signup",
WHATS_NEW: "/whats-new",
Expand All @@ -16,6 +17,9 @@ export const constants = {
getTagRoute: function (tagName) {
return "/tags/" + tagName;
},
getPhotoRoute: function (photoId) {
return "/photos/" + photoId;
},
},
ICONS: {
DOTS_HORIZONTAL: "dots_horizontal",
Expand Down
26 changes: 26 additions & 0 deletions ui/src/lib/customHooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { route } from "preact-router";
import { constants } from "./constants.js";
import Cookies from "cookies-js";
import { getUniqueArrayBy } from "./helpers.js";
import { getPhotoById } from "./pocketbase.js";

export const useViewerAuthProtected = () => {
const [authData] = useContext(AuthContext);
Expand Down Expand Up @@ -70,6 +71,31 @@ export const useInfiniteScroll = (callback, shouldStopExecution) => {
return [isFetching, setIsFetching];
};

export const useGetPhoto = (photoId) => {
const [photo, setPhoto] = useState({});
const [errorMessage, setErrorMessage] = useState("");
const [isFetching, setIsFetching] = useState(true);

useEffect(() => {
const getPhoto = async () => {
return await getPhotoById(photoId);
};

getPhoto()
.then((photo) => {
setPhoto(photo);
setErrorMessage("");
setIsFetching(false);
})
.catch((e) => {
setErrorMessage(e.message);
setIsFetching(false);
});
}, []);

return [photo, errorMessage, isFetching];
};

export const useGetPhotos = (getPhotos) => {
const [page, setPage] = useState(0);

Expand Down
78 changes: 40 additions & 38 deletions ui/src/lib/pocketbase.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,7 @@ export const getPhotosByTag = async (tagName, page, perPage = 10) => {
}

const photoData = photoResults.items.map((item) => {
return {
id: item.id,
description: item.description,
displayDate: getDisplayDateFromFormat(
item.dateTaken === "" ? item.created : item.dateTaken
),
altDate: getLocalDateFromFormat(
item.dateTaken === "" ? item.created : item.dateTaken
),
url: getPocketBaseFileUrl(item.id, item.file),
isNew: item.expand["photos_queue(photo)"] !== undefined,
tags:
item.expand["tags"]?.map((tag) => {
return {
id: tag.id,
name: tag.name,
};
}) ?? [],
};
return mapPhotoDataFromResults(item);
});

return {
Expand Down Expand Up @@ -111,25 +93,7 @@ export const getMainFeedPhotos = async (page, perPage = 10) => {
}

const photoData = photoResults.items.map((item) => {
return {
id: item.id,
description: item.description,
displayDate: getDisplayDateFromFormat(
item.dateTaken === "" ? item.created : item.dateTaken
),
altDate: getLocalDateFromFormat(
item.dateTaken === "" ? item.created : item.dateTaken
),
url: getPocketBaseFileUrl(item.id, item.file),
isNew: item.expand["photos_queue(photo)"] !== undefined,
tags:
item.expand["tags"]?.map((tag) => {
return {
id: tag.id,
name: tag.name,
};
}) ?? [],
};
return mapPhotoDataFromResults(item);
});

return {
Expand All @@ -144,6 +108,22 @@ export const getMainFeedPhotos = async (page, perPage = 10) => {
}
};

export const getPhotoById = async (id) => {
try {
const photoResult = await client.collection("photos").getOne(id, {
expand: "photos_queue(photo),tags",
fields:
"created,dateTaken,description,file,id,expand.photos_queue(photo).id,expand.tags.id,expand.tags.name",
});

return mapPhotoDataFromResults(photoResult);
} catch (e) {
throw e;
} finally {
await client.collection("users").authRefresh();
}
};

export const postPhoto = async (description, file, dateTime, tags) => {
const formData = new FormData();

Expand Down Expand Up @@ -220,3 +200,25 @@ export const getHasNewPhotos = async () => {
const parseTagsString = (tagsString) => {
return tagsString.split(",").map((item) => item.trim());
};

const mapPhotoDataFromResults = (item) => {
return {
id: item.id,
description: item.description,
displayDate: getDisplayDateFromFormat(
item.dateTaken === "" ? item.created : item.dateTaken
),
altDate: getLocalDateFromFormat(
item.dateTaken === "" ? item.created : item.dateTaken
),
url: getPocketBaseFileUrl(item.id, item.file),
isNew: item.expand["photos_queue(photo)"] !== undefined,
tags:
item.expand["tags"]?.map((tag) => {
return {
id: tag.id,
name: tag.name,
};
}) ?? [],
};
};

0 comments on commit b6c3b82

Please sign in to comment.