fix: signout

This commit is contained in:
Henry
2024-04-22 22:45:12 +01:00
parent 227e76524b
commit 4e344a5ae1
7 changed files with 94 additions and 32 deletions

View File

@@ -2,7 +2,7 @@ import { Fragment } from "react";
import Image from "next/image"; import Image from "next/image";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
import { useTheme } from "~/providers/theme"; import { useTheme } from "~/providers/theme";
import createClient from "~/utils/supabase/client";
interface UserMenuProps { interface UserMenuProps {
imageUrl: string | undefined; imageUrl: string | undefined;
email: string; email: string;
@@ -15,6 +15,12 @@ function classNames(...classes: string[]): string {
export default function UserMenu({ imageUrl, email }: UserMenuProps) { export default function UserMenu({ imageUrl, email }: UserMenuProps) {
const { theme, switchTheme } = useTheme(); const { theme, switchTheme } = useTheme();
const handleLogout = async () => {
const db = createClient();
await db.auth.signOut();
};
return ( return (
<Menu as="div" className="relative inline-block w-full text-left"> <Menu as="div" className="relative inline-block w-full text-left">
<div> <div>
@@ -103,7 +109,7 @@ export default function UserMenu({ imageUrl, email }: UserMenuProps) {
<div className="light-border-600 border-t-[1px] p-1 dark:border-dark-600"> <div className="light-border-600 border-t-[1px] p-1 dark:border-dark-600">
<Menu.Item> <Menu.Item>
<button <button
// onClick={() => signOut({ callbackUrl: "/boards" })} onClick={handleLogout}
className=" flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400" className=" flex w-full items-center rounded-[5px] px-3 py-2 text-left text-xs hover:bg-light-200 dark:hover:bg-dark-400"
> >
Logout Logout

View File

@@ -1,12 +1,19 @@
import { Formik, Form, Field } from "formik"; import { Formik, Form, Field } from "formik";
import { api } from "~/utils/api"; import { api } from "~/utils/api";
import { useRouter } from "next/navigation";
interface FormValues { interface FormValues {
email: string; email: string;
} }
export function Login() { export function Login() {
const login = api.auth.login.useMutation(); const router = useRouter();
const login = api.auth.login.useMutation({
onSuccess: () => {
router.push("/verify");
},
});
return ( return (
<Formik <Formik

View File

@@ -12,7 +12,6 @@ import { api } from "~/utils/api";
const jakarta = Plus_Jakarta_Sans({ const jakarta = Plus_Jakarta_Sans({
subsets: ["latin"], subsets: ["latin"],
display: "swap", display: "swap",
variable: "--font-plus-jakarta-sans",
}); });
export const metadata = { export const metadata = {
@@ -23,7 +22,13 @@ export const metadata = {
const MyApp: AppType = ({ Component, pageProps }) => { const MyApp: AppType = ({ Component, pageProps }) => {
return ( return (
<main className={`${jakarta.variable} h-screen overflow-hidden font-sans`}> <>
<style jsx global>{`
html {
font-family: ${jakarta.style.fontFamily};
}
`}</style>
<main className="h-screen overflow-hidden font-sans">
<ThemeProvider> <ThemeProvider>
<ModalProvider> <ModalProvider>
<BoardProvider> <BoardProvider>
@@ -32,6 +37,7 @@ const MyApp: AppType = ({ Component, pageProps }) => {
</ModalProvider> </ModalProvider>
</ThemeProvider> </ThemeProvider>
</main> </main>
</>
); );
}; };

View File

@@ -1,36 +1,39 @@
import { type EmailOtpType } from '@supabase/supabase-js' import { type EmailOtpType } from "@supabase/supabase-js";
import type { NextApiRequest, NextApiResponse } from 'next' import type { NextApiRequest, NextApiResponse } from "next";
import createClient from '~/utils/supabase/api' import createClient from "~/utils/supabase/api";
function stringOrFirstString(item: string | string[] | undefined) { function stringOrFirstString(item: string | string[] | undefined) {
return Array.isArray(item) ? item[0] : item return Array.isArray(item) ? item[0] : item;
} }
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(
if (req.method !== 'GET') { req: NextApiRequest,
res.status(405).appendHeader('Allow', 'GET').end() res: NextApiResponse,
return ) {
if (req.method !== "GET") {
res.status(405).appendHeader("Allow", "GET").end();
return;
} }
const queryParams = req.query const queryParams = req.query;
const token_hash = stringOrFirstString(queryParams.token_hash) const token_hash = stringOrFirstString(queryParams.token_hash);
const type = stringOrFirstString(queryParams.type) const type = stringOrFirstString(queryParams.type);
let next = '/error' let next = "/error";
if (token_hash && type) { if (token_hash && type) {
const supabase = createClient(req, res) const supabase = createClient(req, res);
const { error } = await supabase.auth.verifyOtp({ const { error } = await supabase.auth.verifyOtp({
type: type as EmailOtpType, type: type as EmailOtpType,
token_hash, token_hash,
}) });
if (error) { if (error) {
console.error(error) console.error(error);
} else { } else {
next = stringOrFirstString(queryParams.next) || '/' next = stringOrFirstString(queryParams.next) ?? "/";
} }
} }
res.redirect(next) res.redirect(next);
} }

View File

@@ -0,0 +1,5 @@
import VerifyView from "~/views/auth/verify";
export default function VerifyPage() {
return <VerifyView />;
}

View File

@@ -4,13 +4,36 @@
* *
* We also create a few inference helpers for input and output types. * We also create a few inference helpers for input and output types.
*/ */
import { httpBatchLink, loggerLink } from "@trpc/client"; import { httpBatchLink, loggerLink, type TRPCLink } from "@trpc/client";
import { observable } from "@trpc/server/observable";
import { createTRPCNext } from "@trpc/next"; import { createTRPCNext } from "@trpc/next";
import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server";
import superjson from "superjson"; import superjson from "superjson";
import { type AppRouter } from "~/server/api/root"; import { type AppRouter } from "~/server/api/root";
const authLink: TRPCLink<AppRouter> = () => {
return ({ next, op }) => {
return observable((observer) => {
const unsubscribe = next(op).subscribe({
next(value) {
observer.next(value);
},
error(err) {
if (typeof window !== "undefined" && err.message === "UNAUTHORIZED") {
window.location.href = "/login";
}
observer.error(err);
},
complete() {
observer.complete();
},
});
return unsubscribe;
});
};
};
const getBaseUrl = () => { const getBaseUrl = () => {
if (typeof window !== "undefined") return ""; // browser should use relative url if (typeof window !== "undefined") return ""; // browser should use relative url
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url
@@ -32,6 +55,7 @@ export const api = createTRPCNext<AppRouter>({
process.env.NODE_ENV === "development" || process.env.NODE_ENV === "development" ||
(opts.direction === "down" && opts.result instanceof Error), (opts.direction === "down" && opts.result instanceof Error),
}), }),
authLink,
httpBatchLink({ httpBatchLink({
/** /**
* Transformer used for data de-serialization from the server. * Transformer used for data de-serialization from the server.

View File

@@ -0,0 +1,11 @@
import { createBrowserClient } from "@supabase/ssr";
import { type Database } from "~/types/database.types";
export default function createClient() {
const supabase = createBrowserClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
return supabase;
}