diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 17aa019..bf329a1 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -1,6 +1,7 @@ name: cicd env: + API_URL: ${{ secrets.API_URL }} NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }} CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }} diff --git a/.gitignore b/.gitignore index 92565ec..94a0d3a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,8 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# local env files -.env -.env*.local +# env files (can opt-in for commiting if needed) +.env* # vercel .vercel diff --git a/README.md b/README.md index c0a38dc..2abf1cb 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ A responsive disc golf disc search engine. ![image](https://github.com/cdleveille/discit/assets/1410481/9149c718-1810-43b3-9bf7-8e09e674aeb0) - ## See Also - [DiscIt API](https://github.com/cdleveille/discit-api) diff --git a/bun.lockb b/bun.lockb index b807941..1451071 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/next.config.mjs b/next.config.mjs index 66b2a7e..d4803ef 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,20 +1,11 @@ -/** @type {import('next').NextConfig} */ // @ts-check import withPWAInit from "@ducanh2912/next-pwa"; -const withPWA = withPWAInit({ - dest: "public", - cacheOnFrontEndNav: true, - aggressiveFrontEndNavCaching: true, - reloadOnOnline: true, - disable: process.env.NODE_ENV === "development", - workboxOptions: { - disableDevLogs: true - } -}); +const isDev = process.env.NODE_ENV === "development"; -export default withPWA({ +/** @type {import('next').NextConfig} */ +const nextConfig = { images: { remotePatterns: [ { @@ -26,5 +17,23 @@ export default withPWA({ hostname: "img.clerk.com" } ] + }, + experimental: { + reactCompiler: true + } +}; + +const withPWA = withPWAInit({ + dest: "public", + cacheOnFrontEndNav: true, + aggressiveFrontEndNavCaching: true, + reloadOnOnline: true, + disable: isDev, + workboxOptions: { + disableDevLogs: true } }); + +const config = isDev ? nextConfig : withPWA(nextConfig); + +export default config; diff --git a/package.json b/package.json index 4e86597..5080fdc 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "discit", - "version": "2.0.0", + "version": "2.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbo", "build": "next build", "start": "next start", "lint": "next lint" @@ -15,19 +15,24 @@ "@emotion/styled": "^11.11.5", "@mui/icons-material": "^5.15.19", "@mui/material": "^5.15.19", - "next": "^14.2.3", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "babel-plugin-react-compiler": "^0.0.0-experimental-938cd9a-20240601", + "next": "15.0.0-rc.0", + "react": "19.0.0-rc-f994737d14-20240522", + "react-dom": "19.0.0-rc-f994737d14-20240522", "react-hot-toast": "^2.4.1", "react-infinite-scroll-hook": "^4.1.1" }, "devDependencies": { - "@types/node": "20.14.2", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", + "@types/node": "^20", + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc", "eslint": "^8", - "eslint-config-next": "14.2.3", + "eslint-config-next": "15.0.0-rc.0", "prettier": "^3.3.1", - "typescript": "^5.4.5" + "typescript": "^5" + }, + "overrides": { + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc" } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 9afc93e..bf3cb1c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,29 +1,57 @@ +import type { Metadata, Viewport } from "next"; import "./globals.css"; import { Inter } from "next/font/google"; import { Toaster } from "react-hot-toast"; -import { ClerkProvider } from "@clerk/nextjs"; -import { METADATA, VIEWPORT } from "@constants"; +import { APP_INFO } from "@constants"; const inter = Inter({ subsets: ["latin"] }); -export default async function RootLayout({ +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode; }>) { return ( - - - - - {children} - - - + + + + {children} + + ); } -export const metadata = METADATA; -export const viewport = VIEWPORT; +export const metadata: Metadata = { + title: APP_INFO.title, + applicationName: APP_INFO.title, + description: APP_INFO.description, + authors: { name: APP_INFO.author.name, url: APP_INFO.author.url }, + publisher: APP_INFO.author.name, + manifest: "/manifest.json", + appleWebApp: { + capable: true, + statusBarStyle: "default", + title: APP_INFO.title + }, + openGraph: { + title: APP_INFO.title, + description: APP_INFO.description, + type: "website", + emails: APP_INFO.author.email, + url: APP_INFO.url, + images: { + url: "https://github.com/cdleveille/discit/assets/1410481/ce638b92-5142-4b1c-814a-5240e3cf8fde", + secureUrl: "https://github.com/cdleveille/discit/assets/1410481/ce638b92-5142-4b1c-814a-5240e3cf8fde", + type: "image/png", + alt: APP_INFO.title, + width: 256, + height: 256 + } + } +}; + +export const viewport: Viewport = { + themeColor: "#ffffff" +}; diff --git a/src/app/page.tsx b/src/app/page.tsx index ab01e6d..00bb841 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,4 @@ +import { ClerkProvider } from "@clerk/nextjs"; import { auth } from "@clerk/nextjs/server"; import { AppContextProvider, Controls, DiscGrid, Filters, Header, ScrollToTop } from "@components"; import { getBags, getDiscs } from "@services"; @@ -9,14 +10,16 @@ export default async function HomePage({ searchParams }: { searchParams: Record< const { disc, view } = searchParams; const [discs, bags = []] = await Promise.all([getDiscs(), userId ? getBags({ userId }) : []]); return ( - -
-
- - - - -
-
+ + +
+
+ + + + +
+
+
); } diff --git a/src/components/bag/BagAdd.tsx b/src/components/bag/BagAdd.tsx index ab2ad4c..44d44fb 100644 --- a/src/components/bag/BagAdd.tsx +++ b/src/components/bag/BagAdd.tsx @@ -18,7 +18,7 @@ export const BagAdd = ({ onClose }: BagAddProps) => { const { isLoading, error, setError, createBag } = useApi(); const { userId } = useAuth(); - const inputRef = useRef(); + const inputRef = useRef(null); useEffect(() => { const timeout = setTimeout(() => { diff --git a/src/components/bag/BagEdit.tsx b/src/components/bag/BagEdit.tsx index 8d444c9..b699d26 100644 --- a/src/components/bag/BagEdit.tsx +++ b/src/components/bag/BagEdit.tsx @@ -17,7 +17,7 @@ export const BagEdit = ({ bag, onClose }: BagEditProps) => { const { isLoading, error, setError, editBagName } = useApi(); const { userId } = useAuth(); - const inputRef = useRef(); + const inputRef = useRef(null); useEffect(() => { const timeout = setTimeout(() => { diff --git a/src/components/context/AppContextProvider.tsx b/src/components/context/AppContextProvider.tsx index 066dd07..fb20a0d 100644 --- a/src/components/context/AppContextProvider.tsx +++ b/src/components/context/AppContextProvider.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from "react"; import { BagAdd, BagDelete, BagEdit, DiscDetail, Modal, Settings, SignIn } from "@components"; import { INITIAL_FILTER_VALUES, INITIAL_FILTERS_ENABLED, View } from "@constants"; import { AppContext } from "@contexts"; -import { useApi, usePrevious, useQueryString } from "@hooks"; +import { usePrevious, useQueryString } from "@hooks"; import type { AppContextProviderProps, Bag, Disc, ModalProps, ViewOption } from "@types"; export const AppContextProvider = ({ @@ -26,7 +26,6 @@ export const AppContextProvider = ({ const [filtersEnabled, setFiltersEnabled] = useState(INITIAL_FILTERS_ENABLED); const [view, setView] = useState(initialView ?? View.SEARCH); - const { createBag, editBagName, deleteBag } = useApi(); const { updateQueryString } = useQueryString(); const { bags: bagsPrevious } = usePrevious({ bags }) ?? {}; diff --git a/src/components/disc/DiscDetail.tsx b/src/components/disc/DiscDetail.tsx index 6d34686..6455e0d 100644 --- a/src/components/disc/DiscDetail.tsx +++ b/src/components/disc/DiscDetail.tsx @@ -13,6 +13,7 @@ import RemoveIcon from "@mui/icons-material/Remove"; import { copyToClipboard, hexToRgba } from "@util"; import type { DiscDetailProps } from "@types"; + export const DiscDetail = ({ disc }: DiscDetailProps) => { const { isSignedIn } = useAuth(); const { filteredDiscs, selectedBag, showDiscDetailModal } = useAppContext(); diff --git a/src/components/misc/Modal.tsx b/src/components/misc/Modal.tsx index 3caf6d7..006b545 100644 --- a/src/components/misc/Modal.tsx +++ b/src/components/misc/Modal.tsx @@ -23,16 +23,10 @@ export const Modal = ({ children, open, onClose, showCloseBtn }: ModalProps) => const onRequestClose = () => setIsZoomed(false); return ( - +
-
+
{showCloseBtn && ( (value: T) => { - const ref = useRef(); + const ref = useRef(null); useEffect(() => { ref.current = value; }); diff --git a/src/services/api.ts b/src/services/api.ts index c43487c..9907e71 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -21,14 +21,16 @@ export const getDiscs = async () => requestJson({ path: "/disc", method: RequestMethod.GET, - tags: ["disc"] + tags: ["disc"], + cache: "force-cache" }); export const getBags = async ({ userId }: GetBagParams) => requestJson({ path: `/bag${userId ? `?user_id=${userId}` : ""}`, method: RequestMethod.GET, - tags: ["bag"] + tags: ["bag"], + cache: "force-cache" }); export const createBag = async ({ userId, bagName }: CreateBagParams) => { @@ -36,8 +38,7 @@ export const createBag = async ({ userId, bagName }: CreateBagParams) => { return requestJson({ path: "/bag/create", method: RequestMethod.POST, - body: { user_id: userId, name: bagName }, - cache: "no-cache" + body: { user_id: userId, name: bagName } }); }; @@ -46,8 +47,7 @@ export const editBagName = async ({ bagId, bagName }: EditBagNameParams) => { return requestJson({ path: "/bag/update-name", method: RequestMethod.PATCH, - body: { id: bagId, name: bagName }, - cache: "no-cache" + body: { id: bagId, name: bagName } }); }; @@ -56,8 +56,7 @@ export const addDiscToBag = async ({ bagId, discId }: AddDiscToBagParams) => { return requestJson({ path: "/bag/add-disc", method: RequestMethod.PATCH, - body: { id: bagId, disc_id: discId }, - cache: "no-cache" + body: { id: bagId, disc_id: discId } }); }; @@ -66,8 +65,7 @@ export const removeDiscFromBag = async ({ bagId, discId }: RemoveDiscFromBagPara return requestJson({ path: "/bag/remove-disc", method: RequestMethod.PATCH, - body: { id: bagId, disc_id: discId }, - cache: "no-cache" + body: { id: bagId, disc_id: discId } }); }; @@ -75,21 +73,20 @@ export const deleteBag = async ({ bagId }: DeleteBagParams) => { revalidateTag("bag"); return requestJson({ path: `/bag/delete/${bagId}`, - method: RequestMethod.DELETE, - cache: "no-cache" + method: RequestMethod.DELETE }); }; -const request = ({ path, method, body, tags, cache = "force-cache" }: RequestParams) => +const request = ({ path, method, body, tags, cache }: RequestParams) => fetch(`${config.API_URL}${path}`, { method, headers: { Authorization: `Bearer ${config.API_KEY}` }, body: JSON.stringify(body), next: { tags }, - cache + ...(cache && { cache }) }); -const requestJson = async ({ path, method, body, tags, cache = "force-cache" }: RequestParams) => { +const requestJson = async ({ path, method, body, tags, cache }: RequestParams) => { const res = await request({ path, method, body, tags, cache }); return res.json() as Promise; }; diff --git a/src/services/config.ts b/src/services/config.ts index 1e17dd8..d061b7d 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -1,7 +1,6 @@ import type { Config } from "@types"; export const config: Config = { - API_URL: process.env.API_URL || "https://discit-api.fly.dev", - API_KEY: process.env.API_KEY as string, - NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY as string + API_URL: process.env.API_URL as string, + API_KEY: process.env.API_KEY as string }; diff --git a/src/types/index.ts b/src/types/index.ts index a43aecf..21194f6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,7 +8,6 @@ export * from "./props"; export type Config = { API_URL: string; API_KEY: string; - NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: string; }; export type ViewOption = `${View}`; diff --git a/tsconfig.json b/tsconfig.json index 5a9c887..9d9e7e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "target": "ES2017", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, @@ -19,8 +20,7 @@ ], "paths": { "@*": ["./src/*"] - }, - "target": "ES2017" + } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], "exclude": ["node_modules"]