feat: closes #7. Adds a verify screen

This commit is contained in:
Juan Di Toro 2024-03-26 13:22:18 +01:00
parent 4dcac43e73
commit 2bca9ce939
2 changed files with 145 additions and 41 deletions

View File

@ -20,8 +20,8 @@ import { getProviders } from "~/data/providers.js";
import { Sdk } from "@lumeweb/portal-sdk"; import { Sdk } from "@lumeweb/portal-sdk";
import resources from "~/data/resources.js"; import resources from "~/data/resources.js";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {useEffect, useMemo, useState} from "react"; import { useEffect, useMemo, useState } from "react";
import {PinningProcess} from "~/data/pinning.js"; import { PinningProcess } from "~/data/pinning.js";
export const links: LinksFunction = () => [ export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet }, { rel: "stylesheet", href: stylesheet },
@ -38,9 +38,8 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Meta /> <Meta />
<Links /> <Links />
</head> </head>
<body className="overflow-hidden"> <body>
{children} {children}
<Toaster />
<ScrollRestoration /> <ScrollRestoration />
<Scripts /> <Scripts />
</body> </body>
@ -49,10 +48,45 @@ export function Layout({ children }: { children: React.ReactNode }) {
} }
function App() { function App() {
const sdk = useSdk();
const providers = useMemo(() => getProviders(sdk as Sdk), [sdk]);
useMemo(() => PinningProcess.setupSdk(sdk as Sdk), [sdk]);
return ( return (
<>
<Outlet />
<Toaster />
</>
);
}
export default function Root() {
const [portalUrl, setPortalUrl] = useState(import.meta.env.VITE_PORTAL_URL);
const sdk = Sdk.create(portalUrl);
const providers = useMemo(() => getProviders(sdk as Sdk), [sdk]);
useEffect(() => {
if (sdk) {
PinningProcess.setupSdk(sdk as Sdk);
}
}, [sdk]);
useEffect(() => {
if (!portalUrl) {
fetch("/api/meta")
.then((response) => response.json())
.then((data) => {
setPortalUrl(`https://${data.domain}`);
})
.catch((error) => {
console.error("Failed to fetch portal url:", error);
});
}
}, [portalUrl]);
if (!portalUrl) {
return <p>Loading...</p>;
}
return (
<SdkContextProvider sdk={sdk}>
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Refine <Refine
authProvider={providers.auth} authProvider={providers.auth}
@ -64,36 +98,9 @@ function App() {
}} }}
resources={resources} resources={resources}
options={{ disableTelemetry: true }}> options={{ disableTelemetry: true }}>
<Outlet /> <App />
</Refine> </Refine>
</QueryClientProvider> </QueryClientProvider>
);
}
export default function Root() {
const [portalUrl, setPortalUrl] = useState(import.meta.env.VITE_PORTAL_URL);
useEffect(() => {
if (!portalUrl) {
fetch('/api/meta')
.then(response => response.json())
.then(data => {
setPortalUrl(`https://${data.domain}`);
})
.catch((error: any) => {
console.error('Failed to fetch portal url:', error);
});
}
}, [portalUrl]);
if (!portalUrl) {
return <p>Loading...</p>;
}
const sdk = Sdk.create(portalUrl);
return (
<SdkContextProvider sdk={sdk}>
<App />
</SdkContextProvider> </SdkContextProvider>
); );
} }

97
app/routes/verify.tsx Normal file
View File

@ -0,0 +1,97 @@
import type { MetaFunction } from "@remix-run/node";
import { Link, useSearchParams } from "@remix-run/react";
import { Button } from "~/components/ui/button";
import logoPng from "~/images/lume-logo.png?url";
import lumeColorLogoPng from "~/images/lume-color-logo.png?url";
import discordLogoPng from "~/images/discord-logo.png?url";
import lumeBgPng from "~/images/lume-bg-image.png?url";
import { Field } from "~/components/forms";
import { getFormProps, useForm } from "@conform-to/react";
import { z } from "zod";
import { getZodConstraint, parseWithZod } from "@conform-to/zod";
import { ToastAction } from "~/components/ui/toast";
import { useGo, useNotification } from "@refinedev/core";
import { useEffect } from "react";
import { useMutation, useQuery } from "@tanstack/react-query";
export const meta: MetaFunction = () => {
return [{ title: "Verify Email" }];
};
export default function Verify() {
const go = useGo();
const {open} = useNotification();
const [searchParams] = useSearchParams();
const token = searchParams.get("token");
const exchangeToken = useQuery({
queryKey: ["exchange-token", token],
queryFn: () => {
// TODO: api call to exchange token goes here
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 5000);
});
},
});
const verifyAgain = useMutation({
mutationFn: () => {
// TODO: api call to verify again goes here
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, 5000);
});
},
onMutate() {
open?.({
type: "success",
message: "Email sent",
description: "Please check your email inbox and click the link",
})
}
});
useEffect(() => {
if (exchangeToken.isSuccess) {
go({ to: "/dashboard" });
}
}, [go, exchangeToken.isSuccess]);
return (
<div className="p-10 h-screen relative">
<header>
<img src={logoPng} alt="Lume logo" className="h-10" />
</header>
<main className="flex flex-col items-center justify-center h-full">
<h1 className="text-2xl">
{exchangeToken.isLoading
? "Verifying your email." : null}
{exchangeToken.isSuccess && !exchangeToken.isLoading ? "Your email has been verified" : null}
{exchangeToken.isError && !exchangeToken.isLoading ? "Something went wrong, please try again" : null}
</h1>
{exchangeToken.isError ? (
<div>
<p className="opacity-60">{exchangeToken.error.message}</p>
<Button onClick={() => {
verifyAgain.mutate()
}}>
Send verification email again
</Button>
</div>
) : null}
{exchangeToken.isSuccess ? <p className="opacity-60">Redirecting to your dashboard</p> : null}
</main>
<div className="fixed inset-0 -z-10 overflow-clip">
<img
src={lumeBgPng}
alt="Lume background"
className="absolute top-0 left-0 right-0 object-cover z-[-1]"
/>
</div>
</div>
);
}