Compare commits

..

No commits in common. "1732c1805951a666e46c22d9b16eb6db8bed5001" and "260b41b29b9543f3e2acd133f8cdcaed258555bd" have entirely different histories.

9 changed files with 320 additions and 20487 deletions

View File

@ -1,166 +1,37 @@
import type {AuthProvider} from "@refinedev/core" import { AuthProvider } from "@refinedev/core"
import type { import type {
AuthActionResponse, AuthActionResponse,
CheckResponse, CheckResponse,
IdentityResponse, OnErrorResponse
OnErrorResponse // @ts-ignore
// @ts-ignore
} from "@refinedev/core/dist/interfaces/bindings/auth" } from "@refinedev/core/dist/interfaces/bindings/auth"
import {Sdk} from "@lumeweb/portal-sdk";
import Cookies from 'universal-cookie';
import type {AccountInfoResponse} from "@lumeweb/portal-sdk";
export type AuthFormRequest = { export const authProvider: AuthProvider = {
email: string; login: async (params: any) => {
password: string; return { success: true } satisfies AuthActionResponse
rememberMe: boolean; },
redirectTo?: string; logout: async (params: any) => {
} return { success: true } satisfies AuthActionResponse
},
export type RegisterFormRequest = { check: async (params?: any) => {
email: string; return { authenticated: true } satisfies CheckResponse
password: string; },
firstName: string; onError: async (error: any) => {
lastName: string; return { logout: true } satisfies OnErrorResponse
} },
register: async (params: any) => {
export type Identity = { return { success: true } satisfies AuthActionResponse
id: string; },
firstName: string; forgotPassword: async (params: any) => {
lastName: string; return { success: true } satisfies AuthActionResponse
email: string; },
} updatePassword: async (params: any) => {
return { success: true } satisfies AuthActionResponse
export class PortalAuthProvider implements RequiredAuthProvider { },
private sdk: Sdk; getPermissions: async (params: any) => {
return { success: true } satisfies AuthActionResponse
constructor(apiUrl: string) { },
this.sdk = Sdk.create(apiUrl); getIdentity: async (params: any) => {
return { id: "1", fullName: "John Doe", avatar: "https://via.placeholder.com/150" }
const methods: Array<keyof AuthProvider> = [ }
'login',
'logout',
'check',
'onError',
'register',
'forgotPassword',
'updatePassword',
'getPermissions',
'getIdentity',
];
methods.forEach((method) => {
this[method] = this[method]?.bind(this) as any;
});
}
async login(params: AuthFormRequest): Promise<AuthActionResponse> {
const cookies = new Cookies();
const ret = await this.sdk.account().login({
email: params.email,
password: params.password,
})
let redirectTo: string | undefined;
if (ret) {
cookies.set('jwt', this.sdk.account().jwtToken, {path: '/'});
redirectTo = params.redirectTo;
if (!redirectTo) {
redirectTo = ret ? "/dashboard" : "/login";
}
}
return {
success: ret,
redirectTo,
};
}
async logout(params: any): Promise<AuthActionResponse> {
let ret = await this.sdk.account().logout();
if (ret) {
const cookies = new Cookies();
cookies.remove('jwt');
}
return {success: ret, redirectTo: "/login"};
}
async check(params?: any): Promise<CheckResponse> {
const cookies = new Cookies();
const jwtCookie = cookies.get('jwt');
if (jwtCookie) {
this.sdk.setAuthToken(jwtCookie);
}
const ret = await this.sdk.account().ping();
if (!ret) {
cookies.remove('jwt');
}
return {authenticated: ret, redirectTo: ret ? undefined : "/login"};
}
async onError(error: any): Promise<OnErrorResponse> {
return {logout: true};
}
async register(params: RegisterFormRequest): Promise<AuthActionResponse> {
const ret = await this.sdk.account().register({
email: params.email,
password: params.password,
first_name: params.firstName,
last_name: params.lastName,
});
return {success: ret, redirectTo: ret ? "/dashboard" : undefined};
}
async forgotPassword(params: any): Promise<AuthActionResponse> {
return {success: true};
}
async updatePassword(params: any): Promise<AuthActionResponse> {
return {success: true};
}
async getPermissions(params?: Record<string, any>): Promise<AuthActionResponse> {
return {success: true};
}
async getIdentity(params?: Identity): Promise<IdentityResponse> {
const ret = await this.sdk.account().info();
if (!ret) {
return {identity: null};
}
const acct = ret as AccountInfoResponse;
return {
id: acct.id,
firstName: acct.first_name,
lastName: acct.last_name,
email: acct.email,
};
}
public static create(apiUrl: string): AuthProvider {
return new PortalAuthProvider(apiUrl);
}
}
interface RequiredAuthProvider extends AuthProvider {
login: AuthProvider['login'];
logout: AuthProvider['logout'];
check: AuthProvider['check'];
onError: AuthProvider['onError'];
register: AuthProvider['register'];
forgotPassword: AuthProvider['forgotPassword'];
updatePassword: AuthProvider['updatePassword'];
getPermissions: AuthProvider['getPermissions'];
getIdentity: AuthProvider['getIdentity'];
} }

View File

@ -12,7 +12,7 @@ import type { LinksFunction } from "@remix-run/node";
// Supports weights 200-800 // Supports weights 200-800
import '@fontsource-variable/manrope'; import '@fontsource-variable/manrope';
import {Refine} from "@refinedev/core"; import {Refine} from "@refinedev/core";
import {PortalAuthProvider} from "~/data/auth-provider.js"; import {authProvider} from "~/data/auth-provider.js";
import routerProvider from "@refinedev/remix-router"; import routerProvider from "@refinedev/remix-router";
export const links: LinksFunction = () => [ export const links: LinksFunction = () => [
@ -40,7 +40,7 @@ export function Layout({children}: { children: React.ReactNode }) {
export default function App() { export default function App() {
return ( return (
<Refine <Refine
authProvider={PortalAuthProvider.create("https://alpha.pinner.xyz")} authProvider={authProvider}
routerProvider={routerProvider} routerProvider={routerProvider}
> >
<Outlet/> <Outlet/>

View File

@ -1,24 +1,25 @@
import {useGo, useIsAuthenticated} from "@refinedev/core"; import Login from "./login";
import {useEffect} from "react"; import { useGo, useIsAuthenticated } from "@refinedev/core";
import { useEffect } from "react";
export default function Index() { export default function Index() {
const {isLoading, data} = useIsAuthenticated(); const { isLoading, data } = useIsAuthenticated();
const go = useGo(); const go = useGo();
useEffect(() => { useEffect(() => {
if (!isLoading) { if (!isLoading && data?.authenticated) {
if (data?.authenticated) { go({ to: "/dashboard", type: "replace" });
go({to: "/dashboard", type: "replace"});
} else {
go({to: "/login", type: "replace"});
}
}
}, [isLoading, data]);
if (isLoading) {
return <>Checking Login Status</> || null;
} }
}, [isLoading, data]);
return (<>Redirecting</>) || null; if (isLoading) {
return <>Checking Login Status</> || null;
}
if (data?.authenticated) {
return <>Redirecting</> || null;
}
return <Login />;
} }

View File

@ -1,69 +1,71 @@
import { GeneralLayout } from "~/components/general-layout"; import { GeneralLayout } from "~/components/general-layout"
import { import {
CloudIcon, CloudIcon,
CloudDownloadIcon, CloudDownloadIcon,
CloudUploadSolidIcon, CloudUploadSolidIcon
} from "~/components/icons"; } from "~/components/icons"
import { UpgradeAccountBanner } from "~/components/upgrade-account-banner"; import { UpgradeAccountBanner } from "~/components/upgrade-account-banner"
import { UsageCard } from "~/components/usage-card"; import { UsageCard } from "~/components/usage-card"
import { UsageChart } from "~/components/usage-chart"; import { UsageChart } from "~/components/usage-chart"
import { Authenticated } from "@refinedev/core";
export default function Dashboard() { export default function Dashboard() {
const isLogged = true
if (!isLogged) {
window.location.href = "/login"
}
return ( return (
<Authenticated key="dashboard" v3LegacyAuthProviderCompatible> <GeneralLayout>
<GeneralLayout> <h1 className="font-bold mb-4 text-3xl">Dashboard</h1>
<h1 className="font-bold mb-4 text-3xl">Dashboard</h1> <UpgradeAccountBanner />
<UpgradeAccountBanner /> <h2 className="font-bold mb-8 mt-10 text-2xl">Current Usage</h2>
<h2 className="font-bold mb-8 mt-10 text-2xl">Current Usage</h2> <div className="grid grid-cols-2 gap-8">
<div className="grid grid-cols-2 gap-8"> <UsageCard
<UsageCard label="Storage"
label="Storage" currentUsage={120}
currentUsage={120} monthlyUsage={130}
monthlyUsage={130} icon={<CloudIcon className="text-ring" />}
icon={<CloudIcon className="text-ring" />} />
/> <UsageCard
<UsageCard label="Download"
label="Download" currentUsage={2}
currentUsage={2} monthlyUsage={10}
monthlyUsage={10} icon={<CloudDownloadIcon className="text-ring" />}
icon={<CloudDownloadIcon className="text-ring" />} />
/> <UsageCard
<UsageCard label="Upload"
label="Upload" currentUsage={5}
currentUsage={5} monthlyUsage={15}
monthlyUsage={15} icon={<CloudUploadSolidIcon className="text-ring" />}
icon={<CloudUploadSolidIcon className="text-ring" />} />
/> </div>
</div> <h2 className="font-bold mb-8 mt-10 text-2xl">Historical Usage</h2>
<h2 className="font-bold mb-8 mt-10 text-2xl">Historical Usage</h2> <div className="grid gap-8 grid-cols-2">
<div className="grid gap-8 grid-cols-2"> <UsageChart
<UsageChart dataset={[
dataset={[ { x: "3/2", y: "50" },
{ x: "3/2", y: "50" }, { x: "3/3", y: "10" },
{ x: "3/3", y: "10" }, { x: "3/4", y: "20" }
{ x: "3/4", y: "20" }, ]}
]} label="Storage"
label="Storage" />
/> <UsageChart
<UsageChart dataset={[
dataset={[ { x: "3/2", y: "50" },
{ x: "3/2", y: "50" }, { x: "3/3", y: "10" },
{ x: "3/3", y: "10" }, { x: "3/4", y: "20" }
{ x: "3/4", y: "20" }, ]}
]} label="Download"
label="Download" />
/> <UsageChart
<UsageChart dataset={[
dataset={[ { x: "3/2", y: "50" },
{ x: "3/2", y: "50" }, { x: "3/3", y: "10" },
{ x: "3/3", y: "10" }, { x: "3/4", y: "20" }
{ x: "3/4", y: "20" }, ]}
]} label="Upload"
label="Upload" />
/> </div>
</div> </GeneralLayout>
</GeneralLayout> )
</Authenticated>
);
} }

View File

@ -1,215 +1,179 @@
import type {MetaFunction} from "@remix-run/node" import type { MetaFunction } from "@remix-run/node"
import {Link, useLocation} from "@remix-run/react" import { Link, useLocation } from "@remix-run/react"
import {z} from "zod" import { z } from "zod"
import {Button} from "~/components/ui/button" import { Button } from "~/components/ui/button"
import logoPng from "~/images/lume-logo.png?url" import logoPng from "~/images/lume-logo.png?url"
import lumeColorLogoPng from "~/images/lume-color-logo.png?url" import lumeColorLogoPng from "~/images/lume-color-logo.png?url"
import discordLogoPng from "~/images/discord-logo.png?url" import discordLogoPng from "~/images/discord-logo.png?url"
import lumeBgPng from "~/images/lume-bg-image.png?url" import lumeBgPng from "~/images/lume-bg-image.png?url"
import {Field, FieldCheckbox} from "~/components/forms" import { Field, FieldCheckbox } from "~/components/forms"
import {getFormProps, useForm} from "@conform-to/react" import { getFormProps, useForm } from "@conform-to/react"
import {getZodConstraint, parseWithZod} from "@conform-to/zod" import { getZodConstraint, parseWithZod } from "@conform-to/zod"
import {useGo, useIsAuthenticated, useLogin, useParsed} from "@refinedev/core";
import {AuthFormRequest} from "~/data/auth-provider.js";
import {useEffect} from "react";
export const meta: MetaFunction = () => { export const meta: MetaFunction = () => {
return [ return [
{title: "Login"}, { title: "Login" },
{name: "description", content: "Welcome to Lume!"} { name: "description", content: "Welcome to Lume!" }
] ]
}
type LoginParams = {
to: string;
} }
export default function Login() { export default function Login() {
const location = useLocation() const location = useLocation()
const {isLoading: isAuthLoading, data: authData} = useIsAuthenticated(); const hash = location.hash
const hash = location.hash
const go = useGo();
const parsed = useParsed<LoginParams>()
useEffect(() => { return (
if (!isAuthLoading) { <div className="p-10 h-screen relative">
if (authData?.authenticated) { <header>
let to = "/dashboard"; <img src={logoPng} alt="Lume logo" className="h-10" />
if(parsed.params?.to){ </header>
to = parsed.params.to <div className="fixed inset-0 -z-10 overflow-clip">
} <img
src={lumeBgPng}
alt="Lume background"
className="absolute top-0 right-0 md:w-2/3 object-cover z-[-1]"
/>
</div>
go({to, type: "push"}); {hash === "" && <LoginForm />}
} {hash === "#otp" && <OtpForm />}
}
}, [isAuthLoading, authData]);
return ( <footer className="my-5">
<div className="p-10 h-screen relative"> <ul className="flex flex-row">
<header> <li>
<img src={logoPng} alt="Lume logo" className="h-10"/> <Link to="https://discord.lumeweb.com">
</header> <Button
<div className="fixed inset-0 -z-10 overflow-clip"> variant={"link"}
<img className="flex flex-row gap-x-2 text-input-placeholder"
src={lumeBgPng} >
alt="Lume background" <img className="h-5" src={discordLogoPng} alt="Discord Logo" />
className="absolute top-0 right-0 md:w-2/3 object-cover z-[-1]" Connect with us
/> </Button>
</div> </Link>
</li>
{hash === "" && <LoginForm/>} <li>
{hash === "#otp" && <OtpForm/>} <Link to="https://lumeweb.com">
<Button
<footer className="my-5"> variant={"link"}
<ul className="flex flex-row"> className="flex flex-row gap-x-2 text-input-placeholder"
<li> >
<Link to="https://discord.lumeweb.com"> <img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
<Button Connect with us
variant={"link"} </Button>
className="flex flex-row gap-x-2 text-input-placeholder" </Link>
> </li>
<img className="h-5" src={discordLogoPng} alt="Discord Logo"/> </ul>
Connect with us </footer>
</Button> </div>
</Link> )
</li>
<li>
<Link to="https://lumeweb.com">
<Button
variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo"/>
Connect with us
</Button>
</Link>
</li>
</ul>
</footer>
</div>
)
} }
const LoginSchema = z.object({ const LoginSchema = z.object({
email: z.string().email(), email: z.string().email(),
password: z.string(), password: z.string(),
rememberMe: z.boolean() rememberMe: z.boolean()
}) })
const LoginForm = () => { const LoginForm = () => {
const login = useLogin<AuthFormRequest>() const [form, fields] = useForm({
const parsed = useParsed<LoginParams>() id: "login",
const [form, fields] = useForm({ constraint: getZodConstraint(LoginSchema),
id: "login", onValidate({ formData }) {
constraint: getZodConstraint(LoginSchema), return parseWithZod(formData, { schema: LoginSchema })
onValidate({formData}) { },
return parseWithZod(formData, {schema: LoginSchema}) shouldValidate: "onSubmit"
}, })
shouldValidate: "onSubmit",
onSubmit(e) {
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget).entries()); return (
login.mutate({ <form
email: data.email.toString(), className="w-full p-2 max-w-md space-y-3 mt-12 bg-background"
password: data.password.toString(), {...getFormProps(form)}
rememberMe: data.rememberMe.toString() === "on", >
redirectTo: parsed.params?.to <h2 className="text-3xl font-bold !mb-12">Welcome back! 🎉</h2>
}); <Field
} inputProps={{ name: fields.email.name }}
}) labelProps={{ children: "Email" }}
errors={fields.email.errors}
return ( />
<form <Field
className="w-full p-2 max-w-md space-y-3 mt-12 bg-background" inputProps={{ name: fields.password.name, type: "password" }}
{...getFormProps(form)} labelProps={{ children: "Password" }}
errors={fields.password.errors}
/>
<FieldCheckbox
inputProps={{ name: fields.rememberMe.name, form: form.id }}
labelProps={{ children: "Remember Me" }}
errors={fields.rememberMe.errors}
/>
<Button className="w-full h-14">Login</Button>
<p className="inline-block text-input-placeholder">
Forgot your password?{" "}
<Link
to="/reset-password"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
> >
<h2 className="text-3xl font-bold !mb-12">Welcome back! 🎉</h2> Reset Password
<Field </Link>
inputProps={{name: fields.email.name}} </p>
labelProps={{children: "Email"}} <Link to="/sign-up" className="block">
errors={fields.email.errors} <Button type="button" className="w-full h-14" variant={"outline"}>
/> Create an Account
<Field </Button>
inputProps={{name: fields.password.name, type: "password"}} </Link>
labelProps={{children: "Password"}} </form>
errors={fields.password.errors} )
/>
<FieldCheckbox
inputProps={{name: fields.rememberMe.name, form: form.id}}
labelProps={{children: "Remember Me"}}
errors={fields.rememberMe.errors}
/>
<Button className="w-full h-14">Login</Button>
<p className="inline-block text-input-placeholder">
Forgot your password?{" "}
<Link
to="/reset-password"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
>
Reset Password
</Link>
</p>
<Link to="/register" className="block">
<Button type="button" className="w-full h-14" variant={"outline"}>
Create an Account
</Button>
</Link>
</form>
)
} }
const OtpSchema = z.object({ const OtpSchema = z.object({
otp: z.string().length(6, {message: "OTP must be 6 characters"}) otp: z.string().length(6, { message: "OTP must be 6 characters" })
}) })
const OtpForm = () => { const OtpForm = () => {
// TODO: Add support for resending the OTP // TODO: Add support for resending the OTP
const [form, fields] = useForm({ const [form, fields] = useForm({
id: "otp", id: "otp",
constraint: getZodConstraint(OtpSchema), constraint: getZodConstraint(OtpSchema),
onValidate({formData}) { onValidate({ formData }) {
return parseWithZod(formData, {schema: OtpSchema}) return parseWithZod(formData, { schema: OtpSchema })
}, },
shouldValidate: "onSubmit" shouldValidate: "onSubmit"
}) })
const valid = false // TODO: some sort of logic to verify user is on OTP state validly const valid = false // TODO: some sort of logic to verify user is on OTP state validly
if (!valid) { if (!valid) {
location.hash = "" location.hash = ""
return null return null
} }
return ( return (
<form <form
className="w-full p-2 max-w-md mt-12 bg-background" className="w-full p-2 max-w-md mt-12 bg-background"
{...getFormProps(form)} {...getFormProps(form)}
> >
<span className="block !mb-8 space-y-2"> <span className="block !mb-8 space-y-2">
<h2 className="text-3xl font-bold">Check your inbox</h2> <h2 className="text-3xl font-bold">Check your inbox</h2>
<p className="text-input-placeholder"> <p className="text-input-placeholder">
We will need the six digit confirmation code you received in your We will need the six digit confirmation code you received in your
email in order to verify your account and get started. Didnt receive email in order to verify your account and get started. Didnt receive
a code?{" "} a code?{" "}
<Button type="button" variant={"link"} className="text-md h-0"> <Button type="button" variant={"link"} className="text-md h-0">
Resend now Resend now
</Button> </Button>
</p> </p>
</span> </span>
<Field <Field
inputProps={{name: fields.otp.name}} inputProps={{ name: fields.otp.name }}
labelProps={{children: "Confirmation Code"}} labelProps={{ children: "Confirmation Code" }}
errors={fields.otp.errors} errors={fields.otp.errors}
/> />
<Button className="w-full h-14">Verify</Button> <Button className="w-full h-14">Verify</Button>
<p className="text-input-placeholder w-full text-left"> <p className="text-input-placeholder w-full text-left">
<Link <Link
to="/login" to="/login"
className="text-primary-1 text-md hover:underline hover:underline-offset-4" className="text-primary-1 text-md hover:underline hover:underline-offset-4"
> >
Back to Login Back to Login
</Link> </Link>
</p> </p>
</form> </form>
) )
} }

View File

@ -1,36 +1,31 @@
import type { MetaFunction } from "@remix-run/node"; import type { MetaFunction } from "@remix-run/node"
import { Link } from "@remix-run/react"; import { Link } from "@remix-run/react"
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button"
import logoPng from "~/images/lume-logo.png?url"; import logoPng from "~/images/lume-logo.png?url"
import lumeColorLogoPng from "~/images/lume-color-logo.png?url"; import lumeColorLogoPng from "~/images/lume-color-logo.png?url"
import discordLogoPng from "~/images/discord-logo.png?url"; import discordLogoPng from "~/images/discord-logo.png?url"
import lumeBgPng from "~/images/lume-bg-image.png?url"; import lumeBgPng from "~/images/lume-bg-image.png?url"
import { Field } from "~/components/forms"; import { Field } from "~/components/forms"
import { getFormProps, useForm } from "@conform-to/react"; import { getFormProps, useForm } from "@conform-to/react"
import { z } from "zod"; import { z } from "zod"
import { getZodConstraint, parseWithZod } from "@conform-to/zod"; import { getZodConstraint, parseWithZod } from "@conform-to/zod"
export const meta: MetaFunction = () => { export const meta: MetaFunction = () => {
return [{ title: "Sign Up" }]; return [{ title: "Sign Up" }]
}; }
const RecoverPasswordSchema = z.object({ const RecoverPasswordSchema = z
email: z.string().email(), .object({
}); email: z.string().email(),
})
export default function RecoverPassword() { export default function RecoverPassword() {
const [form, fields] = useForm({ const [form, fields] = useForm({
id: "sign-up", id: "sign-up",
constraint: getZodConstraint(RecoverPasswordSchema), constraint: getZodConstraint(RecoverPasswordSchema),
onValidate({ formData }) { onValidate({ formData }) {
return parseWithZod(formData, { schema: RecoverPasswordSchema }); return parseWithZod(formData, { schema: RecoverPasswordSchema })
}, }
}); })
// TODO: another detail is the reset password has no screen to either accept a new pass or
// just say an email has been sent.. if i were to generate a pass for them. imho i think
// a screen that just says a password reset email has been sent would be good, then a separate
// route to accept the reset token and send that to the api when would then trigger a new email
// with the pass.
return ( return (
<div className="p-10 h-screen relative"> <div className="p-10 h-screen relative">
@ -39,7 +34,8 @@ export default function RecoverPassword() {
</header> </header>
<form <form
className="w-full p-2 max-w-md space-y-4 mt-12 bg-background" className="w-full p-2 max-w-md space-y-4 mt-12 bg-background"
{...getFormProps(form)}> {...getFormProps(form)}
>
<span className="!mb-12 space-y-2"> <span className="!mb-12 space-y-2">
<h2 className="text-3xl font-bold">Reset your password</h2> <h2 className="text-3xl font-bold">Reset your password</h2>
</span> </span>
@ -48,11 +44,12 @@ export default function RecoverPassword() {
labelProps={{ children: "Email Address" }} labelProps={{ children: "Email Address" }}
errors={fields.email.errors} errors={fields.email.errors}
/> />
<Button className="w-full h-14">Reset Password</Button> <Button className="w-full h-14">Create Account</Button>
<p className="text-input-placeholder w-full text-left"> <p className="text-input-placeholder w-full text-left">
<Link <Link
to="/login" to="/login"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"> className="text-primary-1 text-md hover:underline hover:underline-offset-4"
>
Back to Login Back to Login
</Link> </Link>
</p> </p>
@ -70,7 +67,8 @@ export default function RecoverPassword() {
<Link to="https://discord.lumeweb.com"> <Link to="https://discord.lumeweb.com">
<Button <Button
variant={"link"} variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"> className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={discordLogoPng} alt="Discord Logo" /> <img className="h-5" src={discordLogoPng} alt="Discord Logo" />
Connect with us Connect with us
</Button> </Button>
@ -80,7 +78,8 @@ export default function RecoverPassword() {
<Link to="https://lumeweb.com"> <Link to="https://lumeweb.com">
<Button <Button
variant={"link"} variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"> className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" /> <img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us Connect with us
</Button> </Button>
@ -89,5 +88,5 @@ export default function RecoverPassword() {
</ul> </ul>
</footer> </footer>
</div> </div>
); )
} }

View File

@ -9,17 +9,13 @@ import { Field, FieldCheckbox } from "~/components/forms"
import { getFormProps, useForm } from "@conform-to/react" import { getFormProps, useForm } from "@conform-to/react"
import { z } from "zod" import { z } from "zod"
import { getZodConstraint, parseWithZod } from "@conform-to/zod" import { getZodConstraint, parseWithZod } from "@conform-to/zod"
import {useLogin, useRegister} from "@refinedev/core";
import {AuthFormRequest, RegisterFormRequest} from "~/data/auth-provider.js";
export const meta: MetaFunction = () => { export const meta: MetaFunction = () => {
return [{ title: "Sign Up" }]; return [{ title: "Sign Up" }]
}; }
const RegisterSchema = z const SignUpSchema = z
.object({ .object({
firstName: z.string().min(1),
lastName: z.string().min(1),
email: z.string().email(), email: z.string().email(),
password: z password: z
.string() .string()
@ -28,49 +24,28 @@ const RegisterSchema = z
.string() .string()
.min(8, { message: "Password must be at least 8 characters" }), .min(8, { message: "Password must be at least 8 characters" }),
termsOfService: z.boolean({ termsOfService: z.boolean({
required_error: "You must agree to the terms of service", required_error: "You must agree to the terms of service"
}), })
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {
if (data.password !== data.confirmPassword) { if (data.password !== data.confirmPassword) {
return ctx.addIssue({ return ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
path: ["confirmPassword"], path: ["confirmPassword"],
message: "Passwords do not match", message: "Passwords do not match"
}); })
} }
return true; return true
}); })
export default function Register() { export default function SignUp() {
const register = useRegister<RegisterFormRequest>()
const login = useLogin<AuthFormRequest>();
const [form, fields] = useForm({ const [form, fields] = useForm({
id: "register", id: "sign-up",
constraint: getZodConstraint(RegisterSchema), constraint: getZodConstraint(SignUpSchema),
onValidate({ formData }) { onValidate({ formData }) {
return parseWithZod(formData, { schema: RegisterSchema }); return parseWithZod(formData, { schema: SignUpSchema })
}, }
onSubmit(e) { })
e.preventDefault();
const data = Object.fromEntries(new FormData(e.currentTarget).entries());
register.mutate({
email: data.email.toString(),
password: data.password.toString(),
firstName: data.firstName.toString(),
lastName: data.lastName.toString(),
}, {
onSuccess: () => {
login.mutate({
email: data.email.toString(),
password: data.password.toString(),
rememberMe: false,
})
}
})
}
});
return ( return (
<div className="p-10 h-screen relative"> <div className="p-10 h-screen relative">
@ -79,33 +54,21 @@ export default function Register() {
</header> </header>
<form <form
className="w-full p-2 max-w-md space-y-4 mt-12 bg-background" className="w-full p-2 max-w-md space-y-4 mt-12 bg-background"
{...getFormProps(form)}> {...getFormProps(form)}
>
<span className="!mb-12 space-y-2"> <span className="!mb-12 space-y-2">
<h2 className="text-3xl font-bold">All roads lead to Lume</h2> <h2 className="text-3xl font-bold">All roads lead to Lume</h2>
<p className="text-input-placeholder"> <p className="text-input-placeholder">
🤘 Get 50 GB free storage and download for free,{" "} 🤘 Get 50 GB free storage and download for free,{" "}
<b <b
className="text-primar className="text-primar
y-2"> y-2"
>
forever forever
</b> </b>
.{" "} .{" "}
</p> </p>
</span> </span>
<div className="flex gap-4">
<Field
className="flex-1"
inputProps={{ name: fields.firstName.name }}
labelProps={{ children: "First Name" }}
errors={fields.firstName.errors}
/>
<Field
className="flex-1"
inputProps={{ name: fields.lastName.name }}
labelProps={{ children: "Last Name" }}
errors={fields.lastName.errors}
/>
</div>
<Field <Field
inputProps={{ name: fields.email.name }} inputProps={{ name: fields.email.name }}
labelProps={{ children: "Email" }} labelProps={{ children: "Email" }}
@ -129,17 +92,19 @@ export default function Register() {
I agree to the I agree to the
<Link <Link
to="/terms-of-service" to="/terms-of-service"
className="text-primary-1 text-md hover:underline hover:underline-offset-4 mx-1"> className="text-primary-1 text-md hover:underline hover:underline-offset-4 mx-1"
>
Terms of Service Terms of Service
</Link> </Link>
and and
<Link <Link
to="/privacy-policy" to="/privacy-policy"
className="text-primary-1 text-md hover:underline hover:underline-offset-4 mx-1"> className="text-primary-1 text-md hover:underline hover:underline-offset-4 mx-1"
>
Privacy Policy Privacy Policy
</Link> </Link>
</span> </span>
), )
}} }}
errors={fields.termsOfService.errors} errors={fields.termsOfService.errors}
/> />
@ -148,7 +113,8 @@ export default function Register() {
Already have an account?{" "} Already have an account?{" "}
<Link <Link
to="/login" to="/login"
className="text-primary-1 text-md hover:underline hover:underline-offset-4"> className="text-primary-1 text-md hover:underline hover:underline-offset-4"
>
Login here instead Login here instead
</Link> </Link>
</p> </p>
@ -166,7 +132,8 @@ export default function Register() {
<Link to="https://discord.lumeweb.com"> <Link to="https://discord.lumeweb.com">
<Button <Button
variant={"link"} variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"> className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={discordLogoPng} alt="Discord Logo" /> <img className="h-5" src={discordLogoPng} alt="Discord Logo" />
Connect with us Connect with us
</Button> </Button>
@ -176,7 +143,8 @@ export default function Register() {
<Link to="https://lumeweb.com"> <Link to="https://lumeweb.com">
<Button <Button
variant={"link"} variant={"link"}
className="flex flex-row gap-x-2 text-input-placeholder"> className="flex flex-row gap-x-2 text-input-placeholder"
>
<img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" /> <img className="h-5" src={lumeColorLogoPng} alt="Lume Logo" />
Connect with us Connect with us
</Button> </Button>
@ -185,5 +153,5 @@ export default function Register() {
</ul> </ul>
</footer> </footer>
</div> </div>
); )
} }

19971
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
"@conform-to/react": "^1.0.2", "@conform-to/react": "^1.0.2",
"@conform-to/zod": "^1.0.2", "@conform-to/zod": "^1.0.2",
"@fontsource-variable/manrope": "^5.0.19", "@fontsource-variable/manrope": "^5.0.19",
"@lumeweb/portal-sdk": "0.0.0-20240314110748", "@lumeweb/portal-sdk": "^0.0.0-20240306231947",
"@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5",
@ -39,7 +39,6 @@
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-cookie": "^7.1.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"tailwind-merge": "^2.2.1", "tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",