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 { 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,51 +48,59 @@ 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 (
|
||||||
<QueryClientProvider client={queryClient}>
|
<>
|
||||||
<Refine
|
<Outlet />
|
||||||
authProvider={providers.auth}
|
<Toaster />
|
||||||
routerProvider={routerProvider}
|
</>
|
||||||
notificationProvider={notificationProvider}
|
|
||||||
dataProvider={{
|
|
||||||
default: providers.default,
|
|
||||||
files: providers.files,
|
|
||||||
}}
|
|
||||||
resources={resources}
|
|
||||||
options={{ disableTelemetry: true }}>
|
|
||||||
<Outlet />
|
|
||||||
</Refine>
|
|
||||||
</QueryClientProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
const [portalUrl, setPortalUrl] = useState(import.meta.env.VITE_PORTAL_URL);
|
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);
|
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 (
|
return (
|
||||||
<SdkContextProvider sdk={sdk}>
|
<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>
|
</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