feat: closes #7. Adds a verify screen
This commit is contained in:
parent
4dcac43e73
commit
2bca9ce939
89
app/root.tsx
89
app/root.tsx
|
@ -20,8 +20,8 @@ import { getProviders } from "~/data/providers.js";
|
|||
import { Sdk } from "@lumeweb/portal-sdk";
|
||||
import resources from "~/data/resources.js";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import {useEffect, useMemo, useState} from "react";
|
||||
import {PinningProcess} from "~/data/pinning.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { PinningProcess } from "~/data/pinning.js";
|
||||
|
||||
export const links: LinksFunction = () => [
|
||||
{ rel: "stylesheet", href: stylesheet },
|
||||
|
@ -38,9 +38,8 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body className="overflow-hidden">
|
||||
<body>
|
||||
{children}
|
||||
<Toaster />
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
|
@ -49,51 +48,59 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
|||
}
|
||||
|
||||
function App() {
|
||||
const sdk = useSdk();
|
||||
const providers = useMemo(() => getProviders(sdk as Sdk), [sdk]);
|
||||
useMemo(() => PinningProcess.setupSdk(sdk as Sdk), [sdk]);
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Refine
|
||||
authProvider={providers.auth}
|
||||
routerProvider={routerProvider}
|
||||
notificationProvider={notificationProvider}
|
||||
dataProvider={{
|
||||
default: providers.default,
|
||||
files: providers.files,
|
||||
}}
|
||||
resources={resources}
|
||||
options={{ disableTelemetry: true }}>
|
||||
<Outlet />
|
||||
</Refine>
|
||||
</QueryClientProvider>
|
||||
<>
|
||||
<Outlet />
|
||||
<Toaster />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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 [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}>
|
||||
<App />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Refine
|
||||
authProvider={providers.auth}
|
||||
routerProvider={routerProvider}
|
||||
notificationProvider={notificationProvider}
|
||||
dataProvider={{
|
||||
default: providers.default,
|
||||
files: providers.files,
|
||||
}}
|
||||
resources={resources}
|
||||
options={{ disableTelemetry: true }}>
|
||||
<App />
|
||||
</Refine>
|
||||
</QueryClientProvider>
|
||||
</SdkContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue