-
Notifications
You must be signed in to change notification settings - Fork 17
Add new bar menu #117
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
Open
nihodi
wants to merge
52
commits into
cybernetisk:development
Choose a base branch
from
nihodi:bar-menu
base: development
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Add new bar menu #117
Changes from all commits
Commits
Show all changes
52 commits
Select commit
Hold shift + click to select a range
7b112ed
convert prismaClient.js to TypeScript
nihodi 6dd323c
WIP menu
nihodi 3c92793
require being logged in to access /volunteering/menu
nihodi 05492ba
move GET /menu/products endpoint to /menu (because it makes more sense)
nihodi 4b1520f
use prisma's autogenerated type definition for MenuProduct instead of…
nihodi a50dbd5
add auth checks to menu API
nihodi 6e6f8bb
separate product and category related functions into their own files
nihodi 86e7f9c
remove support for uncategorized items
nihodi 7b64128
switch to a much cleaner Grid based layout
nihodi 5d01d78
move `NewProduct` and `createProduct` to product.tsx
nihodi 6d21137
add validation to category name inputs
nihodi 2858d09
add validation to the rest of the inputs
nihodi 2db8590
change active and glutenfree to be booleans in the database schema
nihodi 65658f1
add input for gluten free
nihodi 39e25ee
fix product validation
nihodi d302e80
add spinner when updating/creating new product
nihodi ac76e26
disable "Create" button when input is invalid
nihodi d50e8a1
add spinner when creating/updating category
nihodi f8a137f
add API endpoint for deleting products
nihodi 666b1be
add button for deleting products
nihodi d67cefa
redesign customer-facing menu (`/escape/menu` page)
nihodi c572128
add display for gluten-free products
nihodi 9171dd8
add responsivity to bar menu
nihodi b6e1866
convert escape menu to be server-side
nihodi c685e7b
redo menu grid column sizes
nihodi 7fbf534
rename `MenuProduct.active` column to `hidden`
nihodi 6539ee0
add checkbox input to hide/un-hide products
nihodi 7690f19
only display non-hidden items on the menu
nihodi 4b029d1
add some comments
nihodi 46e9d78
add basic API validation
nihodi 93ab40f
default setting `priceVolunteer` to `price` of the product due to uni…
nihodi 5b801d3
fix API validation breaking POST /escape/products endpoint
nihodi 8b9b8b3
align buttons better
nihodi 1be22db
extract DeletionConfirmationDialog into a generic component
nihodi e6dad7b
add ability to delete categories
nihodi 5544bf8
wrap all relevant API returns with `auth.verify()`
nihodi c71adee
rename all `authCheck`s to `auth` for consistency
nihodi 0887c8e
return HTTP `201 Created` (instead of just `200 Ok`) for relevant API…
nihodi ec7d5a1
wrap missing API return with `auth.verify()`
nihodi 5586298
add server-side check to only permit users with the `admin` role to c…
nihodi 58c6253
extract API utility types into their own files/directory
nihodi ab94b59
even more `auth.verify()`
nihodi 1741305
remove `console.log` in `DeletionConfirmationDialog.tsx`
nihodi ca0fe7f
only show validation error when creating a new menu category after us…
nihodi c90b8c9
extract large onClick method into its own function
nihodi d0312d9
add database error checking for menu API endpoints
nihodi 942195b
actually use transaction
nihodi fdda652
fix validation immediately validating `Category name` field after cre…
nihodi 0587b96
add link to escape menu to the `AppBar`
nihodi e8fd7bf
require `admin` role to load `MenuEditPage`
nihodi b1211c6
move link to menu editor from the /volunteering page to /board
nihodi ed00d43
improve grid column spacing and text wrapping in the menu
nihodi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,245 @@ | ||
| // This file contains react components for individual categories, and relevant utility functions. | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
| import { Button, Grid, Input, Stack, TextField, Typography } from "@mui/material"; | ||
| import { MenuCategory } from "@prisma/client"; | ||
|
|
||
| import { NewProduct, Product } from "@/app/(pages)/(main)/board/menu/product"; | ||
| import { styled } from "@mui/system"; | ||
| import CircularProgress from "@mui/material/CircularProgress"; | ||
| import { DeletionConfirmationDialog } from "@/app/components/input/DeletionConfirmationDialog"; | ||
| import { MenuCategoryCreate, MenuCategoryWithProducts } from "@/app/api/utils/types/MenuCategoryTypes"; | ||
|
|
||
|
|
||
| // Updates a given category. | ||
| function updateCategory(category: MenuCategoryWithProducts, newAttributes: Partial<MenuCategory>): Promise<Response> { | ||
|
|
||
| return fetch("/api/v2/escape/menu/categories", { | ||
| method: "PATCH", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify({...category, ...newAttributes, ...{menu_products: undefined}}) // set menu_products to undefined, because trying to PATCH the category with products makes Prisma angry | ||
| }); | ||
| } | ||
|
|
||
| // Creates a given category. | ||
| function createCategory(category: MenuCategoryCreate): Promise<Response> { | ||
| return fetch("/api/v2/escape/menu/categories", { | ||
| method: "POST", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| body: JSON.stringify(category) | ||
| }); | ||
| } | ||
|
|
||
| function deleteCategory(categoryId: number): Promise<Response> { | ||
| return fetch(`/api/v2/escape/menu/categories/${ categoryId }`, { | ||
| method: "DELETE", | ||
| }); | ||
| } | ||
|
|
||
| // a <Textfield> with larger text. Equivalent font-size to <h2> | ||
| const LargeTextField = styled(TextField)({ | ||
| "& .MuiOutlinedInput-input": { | ||
| fontSize: "3.75rem", | ||
| } | ||
| }) | ||
|
|
||
| export function Category( | ||
| props: { | ||
| category: MenuCategoryWithProducts, | ||
| onUpdate: () => void // called when the category has been updated in the database. | ||
| } | ||
| ) { | ||
|
|
||
| let [categoryName, setCategoryName] = useState<string>(props.category.name); | ||
|
|
||
| // validate category name. Category name cannot be empty. | ||
| const categoryNameInvalid: boolean = categoryName.trim() === ""; | ||
|
|
||
| // these are used to disable the UPDATE button when user has not inputted anything yet. | ||
| let [hasBeenUpdated, setHasBeenUpdated] = useState<boolean>(false); | ||
| let [isFirst, setIsFirst] = useState<boolean>(true); | ||
|
|
||
| // is used for the spinner inside the UPDATE button | ||
| let [isUpdating, setIsUpdating] = useState<boolean>(false); | ||
|
|
||
|
|
||
| let [deleteDialogOpen, setDeleteDialogOpen] = useState<boolean>(false); | ||
| let [isDeleting, setIsDeleting] = useState<boolean>(false); | ||
|
|
||
|
|
||
| useEffect(() => { | ||
| // useEffect is always run once at the start. | ||
| if (isFirst) { | ||
| setIsFirst(false); | ||
| return; | ||
| } | ||
|
|
||
| // this happens when categoryName has been updated for the first time. | ||
| setHasBeenUpdated(true); | ||
| }, [categoryName]); | ||
|
|
||
| return ( | ||
| <Stack> | ||
| <Grid container spacing={ 2 } columns={ 10 }> | ||
|
|
||
| <Grid item xs={ 8 }> | ||
| <LargeTextField | ||
| type="text" | ||
| value={ categoryName } | ||
| onChange={ (e) => { | ||
| setCategoryName(e.target.value) | ||
| } } | ||
|
|
||
| required | ||
| label="Category Name" | ||
| placeholder="Category Name" | ||
| error={ categoryNameInvalid } | ||
| helperText={ categoryNameInvalid ? "Name must not be empty" : "" } | ||
|
|
||
| ></LargeTextField> | ||
| </Grid> | ||
|
|
||
| <Grid item xs={ 1 } display="flex" alignItems="center"> | ||
| <Button // UPDATE button | ||
| disabled={ !hasBeenUpdated || categoryNameInvalid || isUpdating } | ||
| onClick={ () => { | ||
| setIsUpdating(true); | ||
| updateCategory( | ||
| props.category, | ||
| {name: categoryName} | ||
| ).then(() => { | ||
| setHasBeenUpdated(false); | ||
| setIsFirst(true); | ||
| setIsUpdating(false); | ||
| props.onUpdate(); | ||
| }); | ||
| } | ||
| } | ||
| > | ||
| { | ||
| isUpdating ? <CircularProgress/> : <>Update</> // show spinner when updating is in progress | ||
| } | ||
| </Button> | ||
| </Grid> | ||
|
|
||
| <Grid item xs={ 1 } display="flex" alignItems="center"> | ||
| <Button | ||
| color="error" | ||
| onClick={ () => { | ||
| setDeleteDialogOpen(true); | ||
| } } | ||
| > | ||
| Delete | ||
| </Button> | ||
| </Grid> | ||
| </Grid> | ||
|
|
||
|
|
||
| <Grid container spacing={ 2 } columns={ 10 }> | ||
|
|
||
| <Grid item xs={ 2 }> | ||
| <Typography variant="h5">Name</Typography> | ||
| </Grid> | ||
| <Grid item xs={ 2 }> | ||
| <Typography variant="h5">Price</Typography> | ||
| </Grid> | ||
| <Grid item xs={ 2 }> | ||
| <Typography variant="h5">Volume (cL)</Typography> | ||
| </Grid> | ||
| <Grid item xs={ 3 }> | ||
| <div></div> | ||
| </Grid> | ||
|
|
||
| { | ||
| props.category.menu_products.map((item) => ( | ||
| <Product product={ item } key={ item.id } onUpdate={ props.onUpdate }></Product> | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| <NewProduct onUpdate={ props.onUpdate } categoryId={ props.category.id }></NewProduct> | ||
| </Grid> | ||
|
|
||
| <DeletionConfirmationDialog | ||
| open={ deleteDialogOpen } | ||
| onClose={ () => setDeleteDialogOpen(false) } | ||
| onDelete={ () => { | ||
| setIsDeleting(true); | ||
| deleteCategory(props.category.id).then(() => { | ||
| props.onUpdate(); | ||
| }); | ||
| } } | ||
| showSpinner={ isDeleting } | ||
| title="Delete category?" | ||
| > | ||
| Are you sure you want to delete this category? | ||
| </DeletionConfirmationDialog> | ||
| </Stack> | ||
| ) | ||
| } | ||
|
|
||
| export function NewCategory(props: { onUpdate: () => void }) { | ||
| let [categoryName, setCategoryName] = useState<string>(""); | ||
| let [isCreating, setIsCreating] = useState<boolean>(false); | ||
|
|
||
| let [hasBeenUpdated, setHasBeenUpdated] = useState<boolean>(false); | ||
| let [isFirst, setIsFirst] = useState<boolean>(true); | ||
|
|
||
| useEffect(() => { | ||
| if (isFirst) { | ||
| setIsFirst(false); | ||
| return; | ||
| } | ||
|
|
||
| setHasBeenUpdated(true); | ||
| }, [categoryName]); | ||
|
|
||
|
|
||
| const invalid = categoryName.trim() === ""; | ||
|
|
||
| // called when user clicks the CREATE button | ||
| const createNewCategory = () => { | ||
| setIsCreating(true); | ||
| createCategory( | ||
| {name: categoryName} | ||
| ).then(() => { | ||
| setCategoryName(""); | ||
| setIsCreating(false); | ||
| setIsFirst(true); | ||
| setHasBeenUpdated(false); | ||
| props.onUpdate(); | ||
| }); | ||
| }; | ||
|
|
||
| return ( | ||
| <Stack justifyContent="space-between" spacing={ 2 }> | ||
| <Typography variant="h3">New Category</Typography> | ||
|
|
||
| <TextField | ||
| type="text" | ||
| value={ categoryName } | ||
| placeholder="Category Name" | ||
| label="Category Name" | ||
| onChange={ (e) => setCategoryName(e.target.value) } | ||
| style={ {fontSize: "3.75rem"} } | ||
|
|
||
| required | ||
| error={ invalid && hasBeenUpdated} | ||
| helperText={ invalid && hasBeenUpdated ? "Name must not be empty" : "" } | ||
| ></TextField> | ||
|
|
||
| <Button | ||
|
|
||
| disabled={ invalid || !hasBeenUpdated } | ||
|
|
||
| onClick={ createNewCategory } | ||
| >{ isCreating ? <CircularProgress/> : <>Create</> }</Button> | ||
|
|
||
|
|
||
| </Stack> | ||
| ) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| "use client"; | ||
|
|
||
| import { Stack } from "@mui/material"; | ||
| import { useEffect, useState } from "react"; | ||
| import authWrapper from "@/app/middleware/authWrapper"; | ||
| import { Category, NewCategory } from "@/app/(pages)/(main)/board/menu/category"; | ||
| import { MenuCategoryWithProducts } from "@/app/api/utils/types/MenuCategoryTypes"; | ||
|
|
||
| // require login | ||
| export default authWrapper(MenuEditPage, "admin"); | ||
|
|
||
| function MenuEditPage() { | ||
| const [menuCategories, setMenuCategories] = useState<MenuCategoryWithProducts[]>([]); | ||
|
|
||
| useEffect(() => { | ||
| refetchMenu(); | ||
| }, []); | ||
|
|
||
| const refetchMenu = () => fetchMenu().then(menu => setMenuCategories(menu)); | ||
|
|
||
| return ( | ||
| <Stack spacing={ 10 }> | ||
| { | ||
| menuCategories.map((item) => { | ||
| return ( | ||
| <Category | ||
| category={ item } | ||
| key={ item.id } | ||
| onUpdate={ | ||
| refetchMenu // refetch the menu when somthing has been updated | ||
| } | ||
| ></Category> | ||
| ); | ||
| }) | ||
| } | ||
|
|
||
| <NewCategory onUpdate={ refetchMenu }></NewCategory> | ||
| </Stack> | ||
| ) | ||
| } | ||
|
|
||
| async function fetchMenu(): Promise<MenuCategoryWithProducts[]> { | ||
| const menu = await fetch("/api/v2/escape/menu"); | ||
| return await menu.json(); | ||
| } | ||
|
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.