From be8000114146ac030948319bbb20622e5b7720a1 Mon Sep 17 00:00:00 2001 From: Andrew Li Date: Thu, 3 Aug 2023 02:45:57 -0400 Subject: [PATCH] Add saving and loading of pieces layouts for gallery view --- README.md | 2 +- prisma/schema.prisma | 9 + src/components/Footer.tsx | 5 +- .../layouts/PiecesResponsiveGridRead.tsx | 55 +++++++ .../layouts/PiecesResponsiveGridWrite.tsx | 55 +++++++ src/env.mjs | 8 +- src/pages/index.tsx | 2 +- src/pages/pieces/index.tsx | 154 ++++++++++-------- src/server/api/root.ts | 2 + src/server/api/routers/layout.ts | 42 +++++ 10 files changed, 254 insertions(+), 80 deletions(-) create mode 100644 src/components/layouts/PiecesResponsiveGridRead.tsx create mode 100644 src/components/layouts/PiecesResponsiveGridWrite.tsx create mode 100644 src/server/api/routers/layout.ts diff --git a/README.md b/README.md index 9426672..402fcb0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This is a [T3 Stack](https://create.t3.gg/) project bootstrapped with `create-t3 ## Authors -This is made by Full Stack at Brown. Abcd +This is made by Full Stack at Brown. ## What's next? How do I make an app with this? diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 47f1cfb..1a13dc1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -98,3 +98,12 @@ model User { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model Layout { + id String @id @default(auto()) @map("_id") @db.ObjectId + name String @unique + layout Json + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} \ No newline at end of file diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index cd9650e..7b95f7f 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -3,14 +3,11 @@ import Link from "next/link"; import InstagramIcon from "@mui/icons-material/Instagram"; import LinkedInIcon from "@mui/icons-material/LinkedIn"; import YouTubeIcon from "@mui/icons-material/YouTube"; -import { Box, Container, Divider, Grid } from "@mui/material"; +import { Box, Container, Grid } from "@mui/material"; export default function Footer() { return ( <> - {/* */} + {pieces.map((piece) => { + return ( +
+ + + +
+ ); + })} + + ); +} diff --git a/src/components/layouts/PiecesResponsiveGridWrite.tsx b/src/components/layouts/PiecesResponsiveGridWrite.tsx new file mode 100644 index 0000000..f91f614 --- /dev/null +++ b/src/components/layouts/PiecesResponsiveGridWrite.tsx @@ -0,0 +1,55 @@ +import type { Content } from "@prisma/client"; + +import { + type Layout, + type Layouts, + Responsive, + WidthProvider, +} from "react-grid-layout"; +import "react-grid-layout/css/styles.css"; +import "react-resizable/css/styles.css"; + +const ResponsiveGridLayout = WidthProvider(Responsive); + +interface PiecesResponsiveGridWriteProps { + pieces: Content[]; +} + +export default function PiecesResponsiveGridWrite({ + pieces, +}: PiecesResponsiveGridWriteProps) { + const getLayouts = (): Layouts => { + const savedLayouts = localStorage.getItem("layout"); + return savedLayouts ? (JSON.parse(savedLayouts) as Layouts) : {}; + }; + + const handleLayoutChange = (layout: Layout[], layouts: Layouts) => { + localStorage.setItem("layout", JSON.stringify(layouts)); + }; + + return ( + + {pieces.map((piece) => { + return ( +
+ +
+ ); + })} +
+ ); +} diff --git a/src/env.mjs b/src/env.mjs index f30abd9..2d0bdf0 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -23,8 +23,8 @@ export const env = createEnv({ // Add `.min(1) on ID and SECRET if you want to make sure they're not empty GOOGLE_CLIENT_ID: z.string(), GOOGLE_CLIENT_SECRET: z.string(), - // UPLOADTHING_SECRET: z.string().min(1), - // UPLOADTHING_APP_ID: z.string().min(1), + UPLOADTHING_SECRET: z.string().min(1), + UPLOADTHING_APP_ID: z.string().min(1), }, /** @@ -45,8 +45,8 @@ export const env = createEnv({ GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, JWT_SECRET: process.env.JWT_SECRET, - // UPLOADTHING_SECRET: process.env.UPLOADTHING_SECRET, - // UPLOADTHING_APP_ID: process.env.UPLOADTHING_APP_ID, + UPLOADTHING_SECRET: process.env.UPLOADTHING_SECRET, + UPLOADTHING_APP_ID: process.env.UPLOADTHING_APP_ID, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. diff --git a/src/pages/index.tsx b/src/pages/index.tsx index e93fe0e..9ca1316 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -18,7 +18,7 @@ const Home: NextPage = () => {
{ + // States + const [openSuccessSnackbar, setOpenSuccessSnackbar] = useState(false); + const [successSnackbarMessage, setSuccessSnackbarMessage] = useState(""); + const [openErrorSnackbar, setOpenErrorSnackbar] = useState(false); + const [errorSnackbarMessage, setErrorSnackbarMessage] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); + const [isEditing, setIsEditing] = useState(false); + + // API Calls const { data: session } = useSession(); const { data: user } = api.user.getUser.useQuery( @@ -22,34 +34,38 @@ const Pieces: NextPage = () => { { refetchOnWindowFocus: false } ); - const [isEditing, setIsEditing] = useState(false); - - const [colScale, setColScale] = useState(12); - const maxPiecesPerRow = 3; - useEffect(() => { - const handleResize = () => { - if (window.innerWidth > 1200) { - setColScale(12 / maxPiecesPerRow); - } else if (window.innerWidth > 996) { - setColScale(10 / maxPiecesPerRow); - } else if (window.innerWidth > 768) { - setColScale(6 / maxPiecesPerRow); - } else if (window.innerWidth > 480) { - setColScale(4 / maxPiecesPerRow); - } else { - setColScale(2 / maxPiecesPerRow); - } - }; - window.addEventListener("resize", handleResize); - return () => window.removeEventListener("resize", handleResize); - }, []); + const { mutate: upsertLayout, isLoading: isUpsertingLayout } = + api.layout.upsertLayout.useMutation({ + onError(error) { + setOpenErrorSnackbar(true); + setErrorSnackbarMessage(error.message); + setErrorMessage(error.message); + }, + onSuccess() { + setOpenSuccessSnackbar(true); + setSuccessSnackbarMessage("Layout successfully saved!"); + setIsEditing(false); + setErrorMessage(""); + }, + }); /** * Handles saving of the grid layout */ const saveGridLayout = () => { - alert("Not implemented yet!"); - setIsEditing(false); + const layout = localStorage.getItem("layout"); + if (!layout) { + setErrorMessage("Local storage for grid layout is empty!"); + setOpenErrorSnackbar(true); + setErrorSnackbarMessage("Error saving layout!"); + return; + } + const layoutToSave = { + name: "pieces", + layout: JSON.parse(layout) as Layouts, + }; + + upsertLayout(layoutToSave); }; const { @@ -61,64 +77,62 @@ const Pieces: NextPage = () => { { refetchOnWindowFocus: false } ); - if (isLoading || !pieces) { + const { + data: layout, + isLoading: isLoadingLayout, + error: errorLayout, + } = api.layout.getLayout.useQuery( + { name: "pieces" }, + { refetchOnWindowFocus: false } + ); + + if (isLoading || isLoadingLayout || !pieces) { return ; } if (error) { return

Oh no... {error.message}

; } + if (errorLayout) { + return

Oh no... {errorLayout.message}

; + } return ( <> + {isUpsertingLayout && ( + + + + )} + setOpenSuccessSnackbar(false)} + message={successSnackbarMessage} + /> + setOpenErrorSnackbar(false)} + message={errorSnackbarMessage} + /> {isEditing && (

EDITING MODE

+ {errorMessage && ( + + )}
)}
- - {pieces.map((piece, idx) => { - return ( -
- {isEditing ? ( - - ) : ( - - - - )} -
- ); - })} -
+ {isEditing ? ( + + ) : ( + + )}
); diff --git a/src/server/api/root.ts b/src/server/api/root.ts index cd71b32..b7bdb4b 100644 --- a/src/server/api/root.ts +++ b/src/server/api/root.ts @@ -1,6 +1,7 @@ import { createTRPCRouter } from "@CarteBlanche/server/api/trpc"; import { userRouter } from "./routers/user"; import { contentRouter } from "./routers/content"; +import { layoutRouter } from "./routers/layout"; /** * This is the primary router for your server. @@ -10,6 +11,7 @@ import { contentRouter } from "./routers/content"; export const appRouter = createTRPCRouter({ user: userRouter, content: contentRouter, + layout: layoutRouter, }); // export type definition of API diff --git a/src/server/api/routers/layout.ts b/src/server/api/routers/layout.ts new file mode 100644 index 0000000..68db9d2 --- /dev/null +++ b/src/server/api/routers/layout.ts @@ -0,0 +1,42 @@ +import { + createTRPCRouter, + publicProcedure, +} from "@CarteBlanche/server/api/trpc"; +import { z } from "zod"; + +export const layoutRouter = createTRPCRouter({ + /** UPSERT GRID LAYOUT */ + upsertLayout: publicProcedure + .input( + z.object({ + name: z.string(), + layout: z.any(), + }) + ) + .mutation(({ input, ctx }) => { + return ctx.prisma.layout.upsert({ + where: { + name: input.name, + }, + update: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + layout: input.layout, + }, + create: { + name: input.name, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + layout: input.layout, + }, + }); + }), + + getLayout: publicProcedure + .input(z.object({ name: z.string() })) + .query(async ({ input, ctx }) => { + return ctx.prisma.layout.findUnique({ + where: { + name: input.name, + }, + }); + }), +});