fix: signout
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,15 +22,22 @@ 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`}>
|
<>
|
||||||
<ThemeProvider>
|
<style jsx global>{`
|
||||||
<ModalProvider>
|
html {
|
||||||
<BoardProvider>
|
font-family: ${jakarta.style.fontFamily};
|
||||||
<Component {...pageProps} />
|
}
|
||||||
</BoardProvider>
|
`}</style>
|
||||||
</ModalProvider>
|
<main className="h-screen overflow-hidden font-sans">
|
||||||
</ThemeProvider>
|
<ThemeProvider>
|
||||||
</main>
|
<ModalProvider>
|
||||||
|
<BoardProvider>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</BoardProvider>
|
||||||
|
</ModalProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</main>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/pages/verify/index.tsx
Normal file
5
src/pages/verify/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import VerifyView from "~/views/auth/verify";
|
||||||
|
|
||||||
|
export default function VerifyPage() {
|
||||||
|
return <VerifyView />;
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|||||||
11
src/utils/supabase/client.ts
Normal file
11
src/utils/supabase/client.ts
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user