diff --git a/www/.gitignore b/www/.gitignore index ce0bdb0..b2d3ec8 100644 --- a/www/.gitignore +++ b/www/.gitignore @@ -42,3 +42,6 @@ next-env.d.ts # yarn .yarn + +# Sentry Config File +.env.sentry-build-plugin diff --git a/www/app/auth/actions.ts b/www/app/auth/actions.ts new file mode 100644 index 0000000..4a4a284 --- /dev/null +++ b/www/app/auth/actions.ts @@ -0,0 +1,47 @@ +'use server' + +import { revalidatePath } from 'next/cache' +import { redirect } from 'next/navigation' + +import { createClient } from '@/utils/supabase/server' + +export async function login(formData: FormData) { + const supabase = createClient() + + // type-casting here for convenience + // in practice, you should validate your inputs + const data = { + email: formData.get('email') as string, + password: formData.get('password') as string, + } + + const { error } = await supabase.auth.signInWithPassword(data) + + if (error) { + return "error" + // console.log(error) + // redirect('/error') + } + revalidatePath('/', 'layout') + redirect('/') +} + +export async function signup(formData: FormData) { + const supabase = createClient() + + // type-casting here for convenience + // in practice, you should validate your inputs + const data = { + email: formData.get('email') as string, + password: formData.get('password') as string, + } + + const { error } = await supabase.auth.signUp(data) + + if (error) { + redirect('/error') + } + + revalidatePath('/', 'layout') + redirect('/') +} diff --git a/www/app/auth/confirm/new/route.ts b/www/app/auth/confirm/new/route.ts new file mode 100644 index 0000000..14d2d2e --- /dev/null +++ b/www/app/auth/confirm/new/route.ts @@ -0,0 +1,28 @@ +import { type EmailOtpType } from '@supabase/supabase-js' +import { type NextRequest } from 'next/server' + +import { createClient } from '@/utils/supabase/server' +import { redirect } from 'next/navigation' + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url) + const token_hash = searchParams.get('token_hash') + const type = searchParams.get('type') as EmailOtpType | null + const next = searchParams.get('next') ?? '/' + + if (token_hash && type) { + const supabase = createClient() + + const { error } = await supabase.auth.verifyOtp({ + type, + token_hash, + }) + if (!error) { + // redirect user to specified redirect URL or root of app + redirect(next) + } + } + + // redirect the user to an error page with some instructions + redirect('/error') +} diff --git a/www/app/auth/confirm/reset/route.ts b/www/app/auth/confirm/reset/route.ts new file mode 100644 index 0000000..5d8c94f --- /dev/null +++ b/www/app/auth/confirm/reset/route.ts @@ -0,0 +1,30 @@ +import { type EmailOtpType } from '@supabase/supabase-js' +import { cookies } from 'next/headers' +import { NextRequest, NextResponse } from 'next/server' +// The client you created from the Server-Side Auth instructions +import { createClient } from '@/utils/supabase/server' + +export async function GET(request: NextRequest) { + const { searchParams } = new URL(request.url) + const token_hash = searchParams.get('token_hash') + const type = searchParams.get('type') as EmailOtpType | null + const next = searchParams.get('next') ?? '/' + const redirectTo = request.nextUrl.clone() + redirectTo.pathname = next + + if (token_hash && type) { + const supabase = createClient() + + const { error } = await supabase.auth.verifyOtp({ + type, + token_hash, + }) + if (!error) { + return NextResponse.redirect(redirectTo) + } + } + + // return the user to an error page with some instructions + redirectTo.pathname = '/auth/auth-code-error' + return NextResponse.redirect(redirectTo) +} diff --git a/www/app/auth/page.tsx b/www/app/auth/page.tsx index 991f0bf..ce41686 100644 --- a/www/app/auth/page.tsx +++ b/www/app/auth/page.tsx @@ -1,26 +1,25 @@ 'use client' -import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' +import { createClient } from '@/utils/supabase/client' import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; +import { redirect } from "next/navigation"; import Image from "next/image"; import icon from "@/public/bloomicon.jpg"; -import SignUp from '@/components/signUp'; -import SignIn from '@/components/signIn'; -import Forgot from '@/components/forgot' +import { SignIn, SignUp, Forgot } from '@/components/auth'; + +import { login, signup } from './actions' export default function Auth() { const [formType, setFormType] = useState('LOGIN'); - const supabase = createClientComponentClient() - const router = useRouter() + const supabase = createClient(); useEffect(() => { - supabase.auth.getSession().then(({ data: { session } }) => { - if (session) { // Can't access this page if you're logged in - router.push('/') + supabase.auth.getUser().then(({ data: { user } }) => { + if (user) { // Can't access this page if you're logged in + redirect('/') } }) @@ -29,11 +28,11 @@ export default function Auth() { console.log(event) } if (event == "PASSWORD_RECOVERY") { - router.push("/auth/reset") + redirect("/auth/reset") } }) - }, [router, supabase]) + }, [supabase]) return (
@@ -68,10 +67,10 @@ export default function Auth() { Your Aristotelian learning companion — here to help you follow your curiosity in whatever direction you like.

{formType === 'LOGIN' && ( - + )} {formType === 'SIGNUP' && ( - + )} {formType === 'FORGOT' && ( diff --git a/www/app/auth/reset/page.tsx b/www/app/auth/reset/page.tsx index 9920b95..d268401 100644 --- a/www/app/auth/reset/page.tsx +++ b/www/app/auth/reset/page.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; import icon from "@/public/bloomicon.jpg"; -import Reset from '@/components/reset' +import Reset from '@/components/auth/reset' export default function ResetPage() { diff --git a/www/app/global-error.tsx b/www/app/global-error.tsx new file mode 100644 index 0000000..9bda5fe --- /dev/null +++ b/www/app/global-error.tsx @@ -0,0 +1,23 @@ +"use client"; + +import * as Sentry from "@sentry/nextjs"; +import NextError from "next/error"; +import { useEffect } from "react"; + +export default function GlobalError({ error }: { error: Error & { digest?: string } }) { + useEffect(() => { + Sentry.captureException(error); + }, [error]); + + return ( + + + {/* `NextError` is the default Next.js error page component. Its type + definition requires a `statusCode` prop. However, since the App Router + does not expose status codes for errors, we simply pass 0 to render a + generic error message. */} + + + + ); +} \ No newline at end of file diff --git a/www/app/page.tsx b/www/app/page.tsx index 0bbcca0..ad46a4b 100644 --- a/www/app/page.tsx +++ b/www/app/page.tsx @@ -7,27 +7,23 @@ import darkBanner from "@/public/bloom2x1dark.svg"; import MessageBox from "@/components/messagebox"; import Thoughts from "@/components/thoughts"; import Sidebar from "@/components/sidebar"; - +import MarkdownWrapper from "@/components/markdownWrapper"; +import { DarkModeSwitch } from "react-toggle-dark-mode"; import { FaLightbulb, FaPaperPlane, FaBars } from "react-icons/fa"; -import { useRef, useEffect, useState, ElementRef } from "react"; - import Swal from "sweetalert2"; -import { useRouter } from "next/navigation"; + +import { useRef, useEffect, useState, ElementRef } from "react"; +import { redirect } from "next/navigation"; import { usePostHog } from "posthog-js/react"; -import Link from "next/link"; -import MarkdownWrapper from "@/components/markdownWrapper"; -import { DarkModeSwitch } from "react-toggle-dark-mode"; -import { Message, Conversation, API } from "@/utils/api"; -import { getId } from "@/utils/supabase"; -import { data } from "autoprefixer"; -import { Session } from "@supabase/supabase-js"; +import { API } from "@/utils/api"; +import { createClient } from "@/utils/supabase/client"; const URL = process.env.NEXT_PUBLIC_API_URL; export default function Home() { const [userId, setUserId] = useState(); - const [session, setSession] = useState(null); + // const [session, setSession] = useState(null); const [isThoughtsOpen, setIsThoughtsOpen] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false); @@ -37,7 +33,7 @@ export default function Home() { const [conversationId, setConversationId] = useState(); - const router = useRouter(); + const supabase = createClient(); const posthog = usePostHog(); const input = useRef>(null); //const input = useRef>(null); @@ -51,23 +47,21 @@ export default function Home() { useEffect(() => { (async () => { - const { userId, session } = await getId(); - setUserId(userId); - setSession(session); - setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches); - if (!session) { - Swal.fire({ + const { data: { user }, error } = await supabase.auth.getUser(); + // Check for an error or no user + if (!user || error) { + await Swal.fire({ title: "Notice: Bloombot now requires signing in for usage", text: "Due to surging demand for Bloom we are requiring users to stay signed in to user Bloom", icon: "warning", confirmButtonColor: "#3085d6", confirmButtonText: "Sign In", - }).then((res) => { - router.push("/auth"); - }); - } else { - posthog?.identify(userId, { email: session.user.email }); + }) + redirect("/auth"); } + setUserId(user.id); + setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches); + posthog?.identify(userId, { email: user.email }); })(); }, []); @@ -230,7 +224,7 @@ export default function Home() { isSidebarOpen={isSidebarOpen} setIsSidebarOpen={setIsSidebarOpen} api={new API({ url: URL!, userId: userId! })} - session={session} + // session={session} />