From d01d801c25c6f166c200ac7490d291fabf57847b Mon Sep 17 00:00:00 2001 From: "Felipe Torres (fforres)" Date: Wed, 10 Apr 2024 08:54:05 -0700 Subject: [PATCH] Creating (#26) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AuthProvider usa las sugerencias de supabase para authenticación general (no específicas de NextJS) https://supabase.com/docs/guides/auth/sessions#can-i-use-http-only-cookies-to-store-the-access-and-refresh-tokens https://supabase.com/docs/guides/auth/sessions#sign-out-and-scopes image `` refresca la cookie en cada instancia que se carga (o cada que updatea la sesión mediante el event handler de `onAuthStateChange`. `WIN:` Como está en el Layout, cada ruta recibe su propia instancia de Auth, (Y asi seguimos una de las sugerencias del uso de NextJS al menos 🙏🏼 ) --- app/(transition)/(root)/events/[id]/page.tsx | 20 +-- app/(transition)/(root)/login/page.tsx | 28 +---- app/(transition)/(root)/page.tsx | 8 +- app/(transition)/layout.tsx | 44 ++----- middleware.ts | 19 --- package-lock.json | 123 ++++++++++++------- package.json | 8 +- src/api/ApolloClient.tsx | 18 --- src/api/ApolloClientForRSC.tsx | 28 +++++ src/api/ApolloWrapper.tsx | 1 + src/api/gql/graphql.ts | 51 +++++++- src/api/gql/schema.gql | 61 ++++++++- src/components/Navbar/MainNav.tsx | 31 +---- src/components/Navbar/MobileNav.tsx | 38 ++---- src/components/nav.tsx | 51 ++------ src/components/ui/button.tsx | 1 + src/utils/supabase/AuthProvider/index.tsx | 66 ++++++++++ src/utils/supabase/client.ts | 48 ++++++-- src/utils/supabase/middleware.ts | 64 ---------- src/utils/supabase/server.ts | 38 ------ 20 files changed, 360 insertions(+), 386 deletions(-) delete mode 100644 middleware.ts delete mode 100644 src/api/ApolloClient.tsx create mode 100644 src/api/ApolloClientForRSC.tsx create mode 100644 src/utils/supabase/AuthProvider/index.tsx delete mode 100644 src/utils/supabase/middleware.ts delete mode 100644 src/utils/supabase/server.ts diff --git a/app/(transition)/(root)/events/[id]/page.tsx b/app/(transition)/(root)/events/[id]/page.tsx index 218be9f..367ad46 100644 --- a/app/(transition)/(root)/events/[id]/page.tsx +++ b/app/(transition)/(root)/events/[id]/page.tsx @@ -1,5 +1,4 @@ -import { gql } from "@apollo/client"; -import { getApolloClient } from "@/api/ApolloClient"; +import { getApolloClientForRSC } from "@/api/ApolloClientForRSC"; import { MapPinIcon } from "@heroicons/react/24/outline"; import { Attendees } from "@/components/Event/Attendees/Attendees"; import { Hero } from "@/components/Event/Hero/Hero"; @@ -11,30 +10,23 @@ import { EventType, PageProps } from "./types"; import { GetEventDocument } from "./getEvent.generated"; export default async function Event({ searchParams }: PageProps) { - const c = getApolloClient(); + const c = getApolloClientForRSC(); const { id } = searchParams; const { data, error } = await c.query({ query: GetEventDocument, variables: { - input: id - } + input: id, + }, }); if (error) return

Ocurrió un error cargando el evento

; - + const { event } = data; if (!event) return

No pudimos encontrar el evento que estás buscando

; - const { - address, - community, - description, - name, - startDateTime, - users, - } = event; + const { address, community, description, name, startDateTime, users } = event; const eventDate = new Date(startDateTime).toLocaleString(); diff --git a/app/(transition)/(root)/login/page.tsx b/app/(transition)/(root)/login/page.tsx index 6e3e00c..b2ae3a2 100644 --- a/app/(transition)/(root)/login/page.tsx +++ b/app/(transition)/(root)/login/page.tsx @@ -4,42 +4,24 @@ import { NextPage } from "next"; import { redirect } from "next/navigation"; import { useEffect, useState } from "react"; -import { Session } from "@supabase/supabase-js"; import { Auth } from "@supabase/auth-ui-react"; import { ThemeSupa } from "@supabase/auth-ui-shared"; -import { createClient } from "@/utils/supabase/client"; +import { supabaseClient } from "@/utils/supabase/client"; import { useTheme } from "next-themes"; - -const supabase = createClient(); +import { useIsLoggedIn } from "@/utils/supabase/AuthProvider"; const AuthPage: NextPage = () => { - const [session, setSession] = useState(null); const { resolvedTheme } = useTheme(); const [url, setUrl] = useState(); - useEffect(() => { const url = new URL(window.location.href); url.pathname = "/"; url.hash = ""; setUrl(url.toString()); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - supabase.auth.getSession().then(async ({ data: { session } }) => { - setSession(session); - await supabase.auth.startAutoRefresh(); - }); - - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange(async (_event, session) => { - await supabase.auth.startAutoRefresh(); - setSession(session); - }); - - return () => subscription.unsubscribe(); }, []); - if (session) { + const isLoggedIn = useIsLoggedIn(); + if (isLoggedIn) { redirect("/"); } @@ -55,7 +37,7 @@ const AuthPage: NextPage = () => { ({ query: FetchExampleEventsDocument, }); @@ -18,13 +18,13 @@ export default async function Home() {
-

RSC

+

RSC

{variable.data?.events?.map((event) => (
{event.id}
))}
-

Client fetching

+

Client fetching

diff --git a/app/(transition)/layout.tsx b/app/(transition)/layout.tsx index d571ce9..ad57a18 100644 --- a/app/(transition)/layout.tsx +++ b/app/(transition)/layout.tsx @@ -1,46 +1,16 @@ "use client"; import { AnimatePresence, LazyMotion, domAnimation } from "framer-motion"; -import React, { useEffect, useState } from "react"; +import React from "react"; -import { createClient } from "@/utils/supabase/client"; +import { AuthProvider } from "@/utils/supabase/AuthProvider"; export default function Layout({ children }: { children: React.ReactNode }) { - const supabase = createClient(); - - useEffect(() => { - const start = async () => { - const { data } = await supabase.auth.getSession(); - const accessToken = data?.session?.access_token; - - const localStorageKey = process.env.NEXT_PUBLIC_TOKEN_STORAGE_KEY; - if (!accessToken || !localStorageKey) { - return; - } - - window.localStorage.setItem( - process.env.NEXT_PUBLIC_TOKEN_STORAGE_KEY!, - accessToken, - ); - console.log({ accessToken }); - }; - - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange((_event, session) => { - // TODO: handle token - }); - - start().catch((e) => { - console.error(e); - }); - - return () => subscription.unsubscribe(); - }, []); - return ( - - {children} - + + + {children} + + ); } diff --git a/middleware.ts b/middleware.ts deleted file mode 100644 index 01abe41..0000000 --- a/middleware.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type NextRequest } from 'next/server' -import { updateSession } from '@/utils/supabase/middleware' - -export async function middleware(request: NextRequest) { - return await updateSession(request) -} - -export const config = { - matcher: [ - /* - * Match all request paths except for the ones starting with: - * - _next/static (static files) - * - _next/image (image optimization files) - * - favicon.ico (favicon file) - * Feel free to modify this pattern to include more paths. - */ - '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)', - ], -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0da9361..f17726d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,8 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@apollo/client": "^3.9.0-alpha.4", - "@apollo/experimental-nextjs-app-support": "^0.5.1", + "@apollo/client": "^3.9.11", + "@apollo/experimental-nextjs-app-support": "^0.10.0", "@auth0/nextjs-auth0": "^2.6.3", "@heroicons/react": "^2.0.18", "@next/third-parties": "^14.0.4", @@ -20,7 +20,6 @@ "@radix-ui/react-slot": "^1.0.2", "@supabase/auth-ui-react": "^0.4.7", "@supabase/auth-ui-shared": "^0.1.8", - "@supabase/ssr": "^0.1.0", "@supabase/supabase-js": "^2.41.1", "@types/node": "20.4.1", "@types/react": "18.2.14", @@ -28,12 +27,14 @@ "autoprefixer": "10.4.14", "class-variance-authority": "^0.7.0", "classnames": "^2.3.2", + "cookies-next": "^4.1.1", "easymde": "^2.18.0", "eslint-config-next": "13.4.9", "framer-motion": "^10.12.18", "graphiql": "^3.0.6", "graphql": "^16.8.1", "graphql-request": "^6.1.0", + "js-cookie": "^3.0.5", "lucide-react": "^0.265.0", "next": "^14.0.3", "next-themes": "^0.2.1", @@ -58,6 +59,7 @@ "@next/eslint-plugin-next": "^13.5.6", "@tailwindcss/typography": "^0.5.10", "@types/jest": "^29.5.11", + "@types/js-cookie": "^3.0.6", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", "eslint": "^8.44.0", @@ -106,9 +108,9 @@ } }, "node_modules/@apollo/client": { - "version": "3.9.0-alpha.5", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.9.0-alpha.5.tgz", - "integrity": "sha512-KUwJxpNxfsnPZ/+pKx25VKEPnlKoGVrtZoMH/wOJ0CaYpodrY8y3Laa92z/K//YcaHt5RAdedg8I1JrfmzIMFw==", + "version": "3.9.11", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.9.11.tgz", + "integrity": "sha512-H7e9m7cRcFO93tokwzqrsbnfKorkpV24xU30hFH5u2g6B+c1DMo/ouyF/YrBPdrTzqxQCjTUmds/FLmJ7626GA==", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", "@wry/caches": "^1.0.0", @@ -118,7 +120,7 @@ "hoist-non-react-statics": "^3.3.2", "optimism": "^0.18.0", "prop-types": "^15.7.2", - "rehackt": "0.0.3", + "rehackt": "0.0.6", "response-iterator": "^0.2.6", "symbol-observable": "^4.0.0", "ts-invariant": "^0.10.3", @@ -147,17 +149,28 @@ } } }, - "node_modules/@apollo/experimental-nextjs-app-support": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@apollo/experimental-nextjs-app-support/-/experimental-nextjs-app-support-0.5.2.tgz", - "integrity": "sha512-qUopCHDocCBfL+XDhuPNwiWrJHf7rc75bdDBF2TxZyYwW32wo3vPPac6bGWrYy8lgkJ8K8Z5O56655WQOynFTg==", + "node_modules/@apollo/client-react-streaming": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@apollo/client-react-streaming/-/client-react-streaming-0.10.0.tgz", + "integrity": "sha512-iZ2jYghRS71xFv6O3Js5Ojrrmk4SnIEKwPRKIswQyAtqjHrfvUTyXCDzxrhPcGQe/y7su/XcE7Xp0kOp7yTnlg==", "dependencies": { - "server-only": "^0.0.1", - "superjson": "^1.12.2", + "superjson": "^1.12.2 || ^2.0.0", "ts-invariant": "^0.10.3" }, "peerDependencies": { - "@apollo/client": ">=3.8.0-rc || ^3.8.0 || >=3.9.0-alpha || >=3.9.0-beta || >=3.9.0-rc", + "@apollo/client": "^3.9.6", + "react": "^18" + } + }, + "node_modules/@apollo/experimental-nextjs-app-support": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@apollo/experimental-nextjs-app-support/-/experimental-nextjs-app-support-0.10.0.tgz", + "integrity": "sha512-S3mfZRnAAAaKwA8RNckS4TWYLX5utpmRTwG3WGFtpooYx8QQG8xft0p0a9eTQ53Jrw3nSMJc/wOOsT/5noMCQg==", + "dependencies": { + "@apollo/client-react-streaming": "0.10.0" + }, + "peerDependencies": { + "@apollo/client": "^3.9.6", "next": "^13.4.1 || ^14.0.0", "react": "^18" } @@ -5027,18 +5040,6 @@ "ws": "^8.14.2" } }, - "node_modules/@supabase/ssr": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.1.0.tgz", - "integrity": "sha512-bIVrkqjAK5G3KjkIMKYKtAOlCgRRplEWjrlyRyXSOYtgDieiOhk2ZyNAPsEOa1By9OZVxuX5eAW1fitdnuxayw==", - "dependencies": { - "cookie": "^0.5.0", - "ramda": "^0.29.0" - }, - "peerDependencies": { - "@supabase/supabase-js": "^2.33.1" - } - }, "node_modules/@supabase/storage-js": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.5.5.tgz", @@ -5128,6 +5129,11 @@ "@types/tern": "*" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -5191,6 +5197,12 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true + }, "node_modules/@types/js-yaml": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", @@ -6739,6 +6751,29 @@ "node": ">= 0.6" } }, + "node_modules/cookies-next": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/cookies-next/-/cookies-next-4.1.1.tgz", + "integrity": "sha512-20QaN0iQSz87Os0BhNg9M71eM++gylT3N5szTlhq2rK6QvXn1FYGPB4eAgU4qFTunbQKhD35zfQ95ZWgzUy3Cg==", + "dependencies": { + "@types/cookie": "^0.6.0", + "@types/node": "^16.10.2", + "cookie": "^0.6.0" + } + }, + "node_modules/cookies-next/node_modules/@types/node": { + "version": "16.18.96", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.96.tgz", + "integrity": "sha512-84iSqGXoO+Ha16j8pRZ/L90vDMKX04QTYMTfYeE1WrjWaZXuchBehGUZEpNgx7JnmlrIHdnABmpjrQjhCnNldQ==" + }, + "node_modules/cookies-next/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/copy-anything": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", @@ -9341,6 +9376,14 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -11673,15 +11716,6 @@ } ] }, - "node_modules/ramda": { - "version": "0.29.1", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", - "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" - } - }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -11876,9 +11910,9 @@ } }, "node_modules/rehackt": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.0.3.tgz", - "integrity": "sha512-aBRHudKhOWwsTvCbSoinzq+Lej/7R8e8UoPvLZo5HirZIIBLGAgdG7SL9QpdcBoQ7+3QYPi3lRLknAzXBlhZ7g==", + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.0.6.tgz", + "integrity": "sha512-l3WEzkt4ntlEc/IB3/mF6SRgNHA6zfQR7BlGOgBTOmx7IJJXojDASav+NsgXHFjHn+6RmwqsGPFgZpabWpeOdw==", "peerDependencies": { "@types/react": "*", "react": "*" @@ -12213,11 +12247,6 @@ "upper-case-first": "^2.0.2" } }, - "node_modules/server-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", - "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==" - }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -12652,14 +12681,14 @@ } }, "node_modules/superjson": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.13.3.tgz", - "integrity": "sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", + "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", "dependencies": { "copy-anything": "^3.0.2" }, "engines": { - "node": ">=10" + "node": ">=16" } }, "node_modules/supports-color": { diff --git a/package.json b/package.json index 8e03e12..851e5d6 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "node": ">=18.17.0" }, "dependencies": { - "@apollo/client": "^3.9.0-alpha.4", - "@apollo/experimental-nextjs-app-support": "^0.5.1", + "@apollo/client": "^3.9.11", + "@apollo/experimental-nextjs-app-support": "^0.10.0", "@auth0/nextjs-auth0": "^2.6.3", "@heroicons/react": "^2.0.18", "@next/third-parties": "^14.0.4", @@ -29,7 +29,6 @@ "@radix-ui/react-slot": "^1.0.2", "@supabase/auth-ui-react": "^0.4.7", "@supabase/auth-ui-shared": "^0.1.8", - "@supabase/ssr": "^0.1.0", "@supabase/supabase-js": "^2.41.1", "@types/node": "20.4.1", "@types/react": "18.2.14", @@ -37,12 +36,14 @@ "autoprefixer": "10.4.14", "class-variance-authority": "^0.7.0", "classnames": "^2.3.2", + "cookies-next": "^4.1.1", "easymde": "^2.18.0", "eslint-config-next": "13.4.9", "framer-motion": "^10.12.18", "graphiql": "^3.0.6", "graphql": "^16.8.1", "graphql-request": "^6.1.0", + "js-cookie": "^3.0.5", "lucide-react": "^0.265.0", "next": "^14.0.3", "next-themes": "^0.2.1", @@ -67,6 +68,7 @@ "@next/eslint-plugin-next": "^13.5.6", "@tailwindcss/typography": "^0.5.10", "@types/jest": "^29.5.11", + "@types/js-cookie": "^3.0.6", "@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/parser": "^5.61.0", "eslint": "^8.44.0", diff --git a/src/api/ApolloClient.tsx b/src/api/ApolloClient.tsx deleted file mode 100644 index bcec2c4..0000000 --- a/src/api/ApolloClient.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; -import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc"; - -const { getClient } = registerApolloClient(() => { - return new ApolloClient({ - cache: new InMemoryCache(), - link: new HttpLink({ - // this needs to be an absolute url, as relative urls cannot be used in SSR - uri: process.env.NEXT_PUBLIC_JSCL_API_URL, - fetch, - // you can disable result caching here if you want to - // (this does not work if you are rendering your page with `export const dynamic = "force-static"`) - // fetchOptions: { cache: "no-store" }, - }), - }); -}); - -export const getApolloClient = getClient; diff --git a/src/api/ApolloClientForRSC.tsx b/src/api/ApolloClientForRSC.tsx new file mode 100644 index 0000000..5116fad --- /dev/null +++ b/src/api/ApolloClientForRSC.tsx @@ -0,0 +1,28 @@ +import { COOKIE_NAME } from "@/utils/supabase/client"; +import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; +import { registerApolloClient } from "@apollo/experimental-nextjs-app-support/rsc"; +import { cookies } from "next/headers"; + +const { getClient } = registerApolloClient(() => { + const cookieValue = cookies().get(COOKIE_NAME)?.value ?? ""; + const headers = cookieValue + ? { + headers: { + cookie: `${COOKIE_NAME}=${cookieValue}`, + }, + } + : {}; + console.log("cookieValue", COOKIE_NAME, cookieValue); + console.log("headers", headers); + return new ApolloClient({ + cache: new InMemoryCache(), + link: new HttpLink({ + // this needs to be an absolute url, as relative urls cannot be used in SSR + uri: process.env.NEXT_PUBLIC_JSCL_API_URL, + // fetch, + ...headers, + }), + }); +}); + +export const getApolloClientForRSC = getClient; diff --git a/src/api/ApolloWrapper.tsx b/src/api/ApolloWrapper.tsx index 6d607be..c1fb11f 100644 --- a/src/api/ApolloWrapper.tsx +++ b/src/api/ApolloWrapper.tsx @@ -14,6 +14,7 @@ function makeClient() { // you can disable result caching here if you want to // (this does not work if you are rendering your page with `export const dynamic = "force-static"`) fetchOptions: { cache: "no-store" }, + credentials: "same-origin", // you can override the default `fetchOptions` on a per query basis // via the `context` property on the options passed as a second argument // to an Apollo Client data fetching hook, e.g.: diff --git a/src/api/gql/graphql.ts b/src/api/gql/graphql.ts index 6b48f6f..66a1c17 100644 --- a/src/api/gql/graphql.ts +++ b/src/api/gql/graphql.ts @@ -250,6 +250,8 @@ export type Mutation = { editTicket: Ticket; /** Enqueue images to import */ enqueueGoogleAlbumImport: Scalars['Boolean']['output']; + /** Create a purchase order */ + payForPurchaseOrder: PurchaseOrder; /** Redeem a ticket */ redeemUserTicket: UserTicket; /** Kickoff the email validation flow. This flow will links an email to a user, create a company if it does not exist, and allows filling data for that email's position */ @@ -327,6 +329,11 @@ export type MutationEnqueueGoogleAlbumImportArgs = { }; +export type MutationPayForPurchaseOrderArgs = { + input: PayForPurchaseOrderInput; +}; + + export type MutationRedeemUserTicketArgs = { userTicketId: Scalars['String']['input']; }; @@ -369,6 +376,11 @@ export type MyTicketsSearchInput = { status: InputMaybe; }; +export type PayForPurchaseOrderInput = { + currencyID: Scalars['String']['input']; + purchaseOrderId: Scalars['String']['input']; +}; + /** Representation of a TicketPrice */ export type Price = { amount: Scalars['Int']['output']; @@ -376,6 +388,12 @@ export type Price = { id: Scalars['ID']['output']; }; +export type PricingInputField = { + currencyId: Scalars['String']['input']; + /** The price. But in cents, so for a $10 ticket, you'd pass 1000 (or 10_00), or for 1000 chilean pesos, you'd pass 1000_00 */ + value_in_cents: Scalars['Int']['input']; +}; + /** Representation of a payment log entry */ export type PublicFinanceEntryRef = { createdAt: Scalars['DateTime']['output']; @@ -388,9 +406,12 @@ export type PublicFinanceEntryRef = { /** Representation of a Purchase Order */ export type PurchaseOrder = { + currency: Maybe; + finalPrice: Maybe; id: Scalars['ID']['output']; + paymentLink: Maybe; + status: Maybe; tickets: Array; - totalAmount: Maybe; }; export type PurchaseOrderInput = { @@ -398,6 +419,13 @@ export type PurchaseOrderInput = { ticketId: Scalars['String']['input']; }; +export enum PurchaseOrderStatusEnum { + Cancelled = 'cancelled', + NotRequired = 'not_required', + Paid = 'paid', + Unpaid = 'unpaid' +} + export type Query = { /** Get a list of communities. Filter by name, id, or status */ communities: Array; @@ -588,11 +616,15 @@ export type Ticket = { endDateTime: Maybe; eventId: Scalars['String']['output']; id: Scalars['ID']['output']; + /** Whether or not the ticket is free */ + isFree: Scalars['Boolean']['output']; + /** Whether or not the ticket has an unlimited quantity. This is reserved for things loike online events. */ + isUnlimited: Scalars['Boolean']['output']; name: Scalars['String']['output']; prices: Maybe>; /** The number of tickets available for this ticket type */ quantity: Maybe; - requiresApproval: Maybe; + requiresApproval: Scalars['Boolean']['output']; startDateTime: Scalars['DateTime']['output']; status: TicketTemplateStatus; visibility: TicketTemplateVisibility; @@ -600,6 +632,7 @@ export type Ticket = { export enum TicketApprovalStatus { Approved = 'approved', + NotRequired = 'not_required', Pending = 'pending', Rejected = 'rejected' } @@ -611,35 +644,40 @@ export type TicketClaimInput = { }; export type TicketCreateInput = { - currencyId: InputMaybe; description: InputMaybe; endDateTime: InputMaybe; eventId: Scalars['String']['input']; + /** If the ticket is free, the price submitted will be ignored. */ + isFree: Scalars['Boolean']['input']; name: Scalars['String']['input']; - price: InputMaybe; + prices: InputMaybe>; quantity: InputMaybe; requiresApproval: InputMaybe; startDateTime: Scalars['DateTime']['input']; status: InputMaybe; + /** If provided, quantity must not be passed. This is for things like online events where there is no limit to the amount of tickets that can be sold. */ + unlimitedTickets: Scalars['Boolean']['input']; visibility: InputMaybe; }; export type TicketEditInput = { - currencyId: InputMaybe; description: InputMaybe; endDateTime: InputMaybe; eventId: InputMaybe; name: InputMaybe; - price: InputMaybe; + prices: InputMaybe; quantity: InputMaybe; requiresApproval: InputMaybe; startDateTime: InputMaybe; status: InputMaybe; ticketId: Scalars['String']['input']; + /** If provided, quantity must not be passed. This is for things like online events where there is no limit to the amount of tickets that can be sold. */ + unlimitedTickets: InputMaybe; visibility: InputMaybe; }; export enum TicketPaymentStatus { + Cancelled = 'cancelled', NotRequired = 'not_required', Paid = 'paid', Unpaid = 'unpaid' @@ -652,6 +690,7 @@ export enum TicketRedemptionStatus { export enum TicketStatus { Active = 'active', + Expired = 'expired', Inactive = 'inactive' } diff --git a/src/api/gql/schema.gql b/src/api/gql/schema.gql index 04a93f1..2547319 100644 --- a/src/api/gql/schema.gql +++ b/src/api/gql/schema.gql @@ -250,6 +250,9 @@ type Mutation { """Enqueue images to import""" enqueueGoogleAlbumImport(input: EnqueueGoogleAlbumImportInput!): Boolean! + """Create a purchase order""" + payForPurchaseOrder(input: PayForPurchaseOrderInput!): PurchaseOrder! + """Redeem a ticket""" redeemUserTicket(userTicketId: String!): UserTicket! @@ -282,6 +285,11 @@ input MyTicketsSearchInput { status: TicketStatus } +input PayForPurchaseOrderInput { + currencyID: String! + purchaseOrderId: String! +} + """Representation of a TicketPrice""" type Price { amount: Int! @@ -289,6 +297,15 @@ type Price { id: ID! } +input PricingInputField { + currencyId: String! + + """ + The price. But in cents, so for a $10 ticket, you'd pass 1000 (or 10_00), or for 1000 chilean pesos, you'd pass 1000_00 + """ + value_in_cents: Int! +} + """Representation of a payment log entry""" type PublicFinanceEntryRef { createdAt: DateTime! @@ -301,9 +318,12 @@ type PublicFinanceEntryRef { """Representation of a Purchase Order""" type PurchaseOrder { + currency: AllowedCurrency + finalPrice: Float id: ID! + paymentLink: String + status: PurchaseOrderStatusEnum tickets: [UserTicket!]! - totalAmount: Float } input PurchaseOrderInput { @@ -311,6 +331,13 @@ input PurchaseOrderInput { ticketId: String! } +enum PurchaseOrderStatusEnum { + cancelled + not_required + paid + unpaid +} + type Query { """Get a list of communities. Filter by name, id, or status""" communities(id: String, name: String, status: CommnunityStatus): [Community!]! @@ -446,12 +473,20 @@ type Ticket { endDateTime: DateTime eventId: String! id: ID! + + """Whether or not the ticket is free""" + isFree: Boolean! + + """ + Whether or not the ticket has an unlimited quantity. This is reserved for things loike online events. + """ + isUnlimited: Boolean! name: String! prices: [Price!] """The number of tickets available for this ticket type""" quantity: Int - requiresApproval: Boolean + requiresApproval: Boolean! startDateTime: DateTime! status: TicketTemplateStatus! visibility: TicketTemplateVisibility! @@ -459,6 +494,7 @@ type Ticket { enum TicketApprovalStatus { approved + not_required pending rejected } @@ -472,35 +508,47 @@ input TicketClaimInput { } input TicketCreateInput { - currencyId: String description: String endDateTime: DateTime eventId: String! + + """If the ticket is free, the price submitted will be ignored.""" + isFree: Boolean! name: String! - price: Int + prices: [PricingInputField!] quantity: Int requiresApproval: Boolean startDateTime: DateTime! status: TicketTemplateStatus + + """ + If provided, quantity must not be passed. This is for things like online events where there is no limit to the amount of tickets that can be sold. + """ + unlimitedTickets: Boolean! visibility: TicketTemplateVisibility } input TicketEditInput { - currencyId: String description: String endDateTime: DateTime eventId: String name: String - price: Int + prices: PricingInputField quantity: Int requiresApproval: Boolean startDateTime: DateTime status: TicketTemplateStatus ticketId: String! + + """ + If provided, quantity must not be passed. This is for things like online events where there is no limit to the amount of tickets that can be sold. + """ + unlimitedTickets: Boolean visibility: TicketTemplateVisibility } enum TicketPaymentStatus { + cancelled not_required paid unpaid @@ -513,6 +561,7 @@ enum TicketRedemptionStatus { enum TicketStatus { active + expired inactive } diff --git a/src/components/Navbar/MainNav.tsx b/src/components/Navbar/MainNav.tsx index 7acfafb..6de44aa 100644 --- a/src/components/Navbar/MainNav.tsx +++ b/src/components/Navbar/MainNav.tsx @@ -3,40 +3,17 @@ import Link from "next/link"; import { NavBarProps } from "./types"; import { NavbarItem } from "./NavbarItem"; import { buttonVariants } from "../ui/button"; -import { useEffect, useState } from "react"; -import { User } from "@supabase/supabase-js"; -import { createClient } from "@/utils/supabase/client"; +import { useIsAuthReady, useIsLoggedIn } from "@/utils/supabase/AuthProvider"; export function MainNav({ items }: NavBarProps) { - const supabase = createClient(); - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const getUser = async () => { - const { data } = await supabase.auth.getSession(); - setLoading(false); - setUser(data?.session?.user ?? null); - }; - - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange((_event, session) => { - setUser(session?.user ?? null); - }); - - // eslint-disable-next-line @typescript-eslint/no-floating-promises - getUser(); - - return () => subscription.unsubscribe(); - }, []); - + const user = useIsLoggedIn(); + const isReady = useIsAuthReady(); return (