Skip to content

Commit

Permalink
Merge pull request #44 from crux-bphc/frontend-tagging
Browse files Browse the repository at this point in the history
Frontend tagging & search
  • Loading branch information
pnicto authored Aug 25, 2023
2 parents 4fe5d8c + 5f36019 commit e4572fe
Show file tree
Hide file tree
Showing 25 changed files with 880 additions and 113 deletions.
5 changes: 3 additions & 2 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@ services:
- ./frontend/.env.development
ports:
- 3000:3000
- 5555:5555
volumes:
- ./frontend/pages:/app/pages
- ./frontend/components:/app/components
- ./frontend/styles:/app/styles
- ./images/uploads:/app/uploads
- ./uploads:/app/uploads
db:
image: postgres:latest
container_name: snap-sorter-database-dev
restart: always
ports:
- 5432:5432
env_file:
- ./frontend/.env.development
volumes:
Expand Down
5 changes: 4 additions & 1 deletion docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ services:
env_file:
- ./frontend/.env.production
ports:
- 80:3000
- 3000:3000
volumes:
- ./frontend/pages:/app/pages
- ./frontend/components:/app/components
- ./frontend/styles:/app/styles
- ./uploads:/app/uploads
image: snap-sorter-frontend-prod
db:
image: postgres:latest
container_name: snap-sorter-database-prod
restart: always
ports:
- 5432:5432
env_file:
- ./frontend/.env.production
volumes:
Expand Down
34 changes: 23 additions & 11 deletions frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

#### Components

- [ ] Navbar
- [ ] Logo which takes to `search` page when logged in else to `login`
- [x] Navbar
- [x] Logo which takes to `search` page when logged in else to `login`
- [x] Links to each page (profile, search, announcements)
- [x] Make it dynamic when showing the pages i.e., do not display announcements route when on announcements page, show other routes only when logged in.
- [ ] Footer
Expand All @@ -20,7 +20,7 @@
- [x] Login page
- [x] Login button which initiates the oauth flow
- [x] Announcements
- [ ] Integrate with backend
- [x] Integrate with backend
- [x] Get data from announcements endpoint and display sorted by latest
- [x] Profile
- [x] Dropzone from mantine to collect images
Expand All @@ -42,7 +42,7 @@

#### Endpoints

- [ ] Announcements
- [x] Announcements
- Get announcements from DB
- [x] Store images
- Take the images and dump them in some folder called `/temp/images` as `studentuid_imagename.extension`
Expand All @@ -52,21 +52,22 @@
- Return images from the `Unknown schema joined with Dopy Image` table with tags
- [x] Auth
- Nextauth will do the needful (more deep dive on what exactly is happening might be needed)
- [ ] Update tags for a given image id
- [x] Update tags for a given image id
- Endpoint will be a `PATCH` request which takes image id and the updated tags and updates them in the db.

#### Database

- [x] Prisma or Drizzle with postgresql
- [ ] Announcement schema - {uuid, title, description, created at time stamp}
- [x] Announcement schema - {uuid, title, description, created at time stamp}
- [x] Upload Image - {uuid, fk to student uid, image path}
- [ ] Some Unknown schema to store tagged image - {fk to student uid, image id}
- [ ] Dopy Image - {uuid, image path, event name, tags}
- [x] Tag - {uuid, tag value, fk to image uids}
- [x] Event - {uuid, event name, fk to image uids}
- [x] Dopy Image - {uuid, image path, fk to event uid, fk to tag uids, fk to tagged student uids}

### Config & Misc

- [ ] Docker files
- [ ] Production
- [x] Docker files
- [x] Production
- [x] Development
- [x] Config
- [x] Prettier
Expand All @@ -78,7 +79,7 @@

### Development

1. Rename `.env.example` as `.env` or `.env.development` and add your `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` from google cloud console.
1. Rename `.env.example` as `.env.development` and add your `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` from google cloud console. Callback url on google cloud console is `http://localhost:3000/api/auth/callback/google`.
2. Run the following command and visit http://localhost:3000
```bash
docker compose -f docker-compose.dev.yml up
Expand All @@ -88,3 +89,14 @@
pnpm install
pnpm prisma generate
```

### Production

1. Rename `.env.example` as `.env`(for prisma's sake as of now) and `.env.production`.
2. Add your `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` from google cloud console. Callback url on google cloud console is `http://localhost:3000/api/auth/callback/google`. Add a secret to `NEXTAUTH_SECRET`.
3. Run the following command and visit http://localhost:3000
```bash
docker compose -f docker-compose.prod.yml up
```
4. As of now locally you have to run `npx --yes prisma migrate deploy` to migrate your schema to the database.
5. Because of how bind mounts work the `uploads` folder ownership needs to be changed. So run `chown -R 1001:1001 uploads` on the server/host.
87 changes: 67 additions & 20 deletions frontend/components/ImageWithModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,52 @@
import { Icon } from "@iconify/react";
import {
ActionIcon,
Alert,
Badge,
Button,
Group,
Image,
Modal,
Stack,
TextInput,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { useState } from "react";

type Props = {
id: string;
imageUrl: string;
tagsFromDatabase: string[];
};

export default function ImagePreviewModal({
id,
imageUrl,
tagsFromDatabase,
}: Props) {
const [opened, { open, close }] = useDisclosure(false);
const [tags, setTags] = useState(tagsFromDatabase);
const [newTag, setNewTag] = useState("");
const [updateStatus, setUpdateStatus] = useState("");

async function handleUpdateTags() {
console.log(tags);
const add = tags.filter((tag) => !tagsFromDatabase.includes(tag));
const remove = tagsFromDatabase.filter((tag) => !tags.includes(tag));

const response = await fetch("/api/updateTags", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
image: id,
add,
remove,
}),
});

setUpdateStatus(response.ok ? "success" : "failure");
}

return (
Expand All @@ -40,23 +61,17 @@ export default function ImagePreviewModal({
alt={imageUrl}
src={imageUrl}
onClick={open}
imageProps={{ onLoad: () => URL.revokeObjectURL(imageUrl) }}
className="cursor-pointer"
/>

<Modal centered opened={opened} onClose={close} size={"auto"}>
<section className="px-2 py-4 sm:flex sm:gap-x-2">
<Image
alt={imageUrl}
src={imageUrl}
onClick={open}
imageProps={{ onLoad: () => URL.revokeObjectURL(imageUrl) }}
/>
<Image alt={imageUrl} src={imageUrl} onClick={open} />
<div>
<Group position="center" className="mt-2">
{tags.map((tag, badgeIndex) => (
<Badge
className="m-1 capitalize"
className="m-1 normal-case"
key={badgeIndex}
rightSection={
<ActionIcon
Expand All @@ -75,22 +90,54 @@ export default function ImagePreviewModal({
))}
</Group>

<Group position="center" className="py-2">
<Stack align="center" className="my-2">
<TextInput
className="mt-4"
value={newTag}
placeholder="New tag"
onChange={(event) => setNewTag(event.currentTarget.value)}
/>
<Button
type="button"
disabled={newTag.length === 0}
onClick={() => setTags([...tags, newTag])}>
Create tag
</Button>
<Button type="button" onClick={handleUpdateTags}>
Update tags
</Button>
</Group>
<Group position="center" className="px-5" noWrap>
<Button
type="button"
disabled={newTag.length === 0}
onClick={() => {
if (!tags.includes(newTag)) {
setTags([...tags, newTag]);
}
setNewTag("");
}}>
Add tag
</Button>
<Button type="button" onClick={handleUpdateTags}>
Update tags
</Button>
</Group>
{updateStatus === "success" && (
<Alert
className="px-5 pt-5"
icon={<Icon icon="mdi:check-circle-outline" />}
title="Updated successfully!"
color="green"
radius="md"
withCloseButton
onClose={() => setUpdateStatus("")}>
{}
</Alert>
)}
{updateStatus === "failure" && (
<Alert
className="px-5 pt-5"
icon={<Icon icon="mdi:alert-circle-outline" />}
title="Tag update failed!"
color="red"
radius="md"
withCloseButton
onClose={() => setUpdateStatus("")}>
{}
</Alert>
)}
</Stack>
</div>
</section>
</Modal>
Expand Down
83 changes: 54 additions & 29 deletions frontend/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,74 @@
// Pitfalls:
// Since tailwind preflight is disabled some of these styles overwrite the default styles or make use of the default styles
// For example here 'px-0` is needed to remove the default unordered list padding
import { Anchor, Button } from "@mantine/core";
import { Button } from "@mantine/core";
import { signOut, useSession } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/router";

export default function Navbar() {
const { data: session } = useSession();
const router = useRouter();

const protectedRoutes = [
{
name: "profile",
path: "/app/profile",
},
{
name: "search",
path: "/app/search",
},
];

const unprotectedRoutes = [
{
name: "announcements",
path: "/announcements",
},
];

return (
<nav className="flex items-center justify-between px-10">
<nav className="flex items-center justify-center px-10 sm:justify-end">
{/* TODO: Add crux logo here for home route */}
<Anchor component={Link} href={"/"}>
Logo
</Anchor>
<ul className="flex list-none px-0">
<li className="mx-2">
<Button
component={Link}
href={"/announcements"}
className="capitalize">
announcements
{router.pathname !== "/login" && session?.user === undefined && (
<Button component={Link} href={"/"} className="mx-2 capitalize">
login
</Button>
</li>
)}
{unprotectedRoutes.map(
(route, index) =>
router.pathname !== route.path && (
<li key={index} className="mx-2">
<Button
component={Link}
href={route.path}
className="capitalize">
{route.name}
</Button>
</li>
)
)}
{session?.user && (
<>
<li className="mx-2">
<Button
component={Link}
href={"/app/profile"}
className=" capitalize">
profile
</Button>
</li>
<li className="mx-2">
<Button
component={Link}
href={"/app/search"}
className=" capitalize">
search
</Button>
</li>
{protectedRoutes.map(
(route, index) =>
router.pathname !== route.path && (
<li key={index} className="mx-2">
<Button
component={Link}
href={route.path}
className="capitalize">
{route.name}
</Button>
</li>
)
)}
<li className="mx-2">
<Button
onClick={() => signOut({ callbackUrl: "/login" })}
className=" capitalize">
className="capitalize">
logout
</Button>
</li>
Expand Down
5 changes: 4 additions & 1 deletion frontend/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
callbacks: {
session: ({ session, user }) => {
if (session?.user) session.user.id = user.id;
if (session?.user) {
session.user.id = user.id;
session.user.role = user.role;
}
return session;
},
},
Expand Down
Loading

0 comments on commit e4572fe

Please sign in to comment.