Compare commits
6 Commits
master
...
bug/refine
Author | SHA1 | Date |
---|---|---|
Juan Di Toro | 8ba1302c6b | |
Juan Di Toro | ec402fc38c | |
Juan Di Toro | 4b9ea7f472 | |
Juan Di Toro | 51f212c7b4 | |
Juan Di Toro | 393fda9710 | |
Derrick Hammer | eb82f68bd8 |
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024 LumeWeb
|
Copyright (c) 2024 Hammer Technologies LLC
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ export const FieldCheckbox = ({
|
||||||
const id = inputProps.id ?? fallbackId
|
const id = inputProps.id ?? fallbackId
|
||||||
const errorId = errors?.length ? `${id}-error` : undefined
|
const errorId = errors?.length ? `${id}-error` : undefined
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div
|
<div
|
||||||
className={cn("space-x-2 flex items-center text-primary-2", className)}
|
className={cn("space-x-2 flex items-center text-primary-2", className)}
|
||||||
>
|
>
|
||||||
|
@ -88,10 +89,11 @@ export const FieldCheckbox = ({
|
||||||
type="button"
|
type="button"
|
||||||
/>
|
/>
|
||||||
<Label {...labelProps} htmlFor={id} />
|
<Label {...labelProps} htmlFor={id} />
|
||||||
|
</div>
|
||||||
<div className="min-h-[32px] px-4 pb-3 pt-1">
|
<div className="min-h-[32px] px-4 pb-3 pt-1">
|
||||||
{errorId ? <ErrorList id={errorId} errors={errors} /> : null}
|
{errorId ? <ErrorList id={errorId} errors={errors} /> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +110,7 @@ export function ErrorList({
|
||||||
return (
|
return (
|
||||||
<ul id={id} className="flex flex-col gap-1">
|
<ul id={id} className="flex flex-col gap-1">
|
||||||
{errorsToRender.map((e) => (
|
{errorsToRender.map((e) => (
|
||||||
<li key={e} className="text-[10px] text-foreground-destructive">
|
<li key={e} className="text-[12px] text-destructive-foreground">
|
||||||
{e}
|
{e}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { cn } from "~/utils"
|
||||||
const Checkbox = React.forwardRef<
|
const Checkbox = React.forwardRef<
|
||||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> & {
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> & {
|
||||||
className: string
|
className?: string
|
||||||
}
|
}
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<CheckboxPrimitive.Root
|
<CheckboxPrimitive.Root
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { AuthProvider } from "@refinedev/core"
|
||||||
|
import type {
|
||||||
|
AuthActionResponse,
|
||||||
|
CheckResponse,
|
||||||
|
OnErrorResponse
|
||||||
|
} from "@refinedev/core/dist/interfaces"
|
||||||
|
|
||||||
|
export const authProvider: AuthProvider = {
|
||||||
|
login: async (params: any) => {
|
||||||
|
return { success: true } satisfies AuthActionResponse
|
||||||
|
},
|
||||||
|
logout: async (params: any) => {
|
||||||
|
return { success: true } satisfies AuthActionResponse
|
||||||
|
},
|
||||||
|
check: async (params?: any) => {
|
||||||
|
return { authenticated: true } satisfies CheckResponse
|
||||||
|
},
|
||||||
|
onError: async (error: any) => {
|
||||||
|
return { logout: true } satisfies OnErrorResponse
|
||||||
|
},
|
||||||
|
register: async (params: any) => {
|
||||||
|
return { success: true } satisfies AuthActionResponse
|
||||||
|
},
|
||||||
|
forgotPassword: async (params: any) => {
|
||||||
|
return { success: true } satisfies AuthActionResponse
|
||||||
|
},
|
||||||
|
updatePassword: async (params: any) => {
|
||||||
|
return { success: true } satisfies AuthActionResponse
|
||||||
|
},
|
||||||
|
getPermissions: async (params: any) => {
|
||||||
|
return { success: true } satisfies AuthActionResponse
|
||||||
|
},
|
||||||
|
getIdentity: async (params: any) => {
|
||||||
|
return { id: "1", fullName: "John Doe", avatar: "https://via.placeholder.com/150" }
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
28
app/root.tsx
28
app/root.tsx
|
@ -3,19 +3,22 @@ import {
|
||||||
Meta,
|
Meta,
|
||||||
Outlet,
|
Outlet,
|
||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration
|
||||||
} from "@remix-run/react";
|
} from "@remix-run/react"
|
||||||
|
|
||||||
import stylesheet from "./tailwind.css?url";
|
import stylesheet from "./tailwind.css?url"
|
||||||
import { LinksFunction } from "@remix-run/node";
|
import { 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 routerProvider from "@refinedev/remix-router";
|
||||||
|
import { authProvider } from "./data/auth-provider"
|
||||||
|
|
||||||
export const links: LinksFunction = () => [
|
export const links: LinksFunction = () => [
|
||||||
{ rel: "stylesheet", href: stylesheet },
|
{ rel: "stylesheet", href: stylesheet }
|
||||||
// { rel: "stylesheet", href: manropeStylesheet },
|
// { rel: "stylesheet", href: manropeStylesheet },
|
||||||
];
|
]
|
||||||
|
|
||||||
export function Layout({ children }: { children: React.ReactNode }) {
|
export function Layout({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
|
@ -27,18 +30,23 @@ export function Layout({ children }: { children: React.ReactNode }) {
|
||||||
<Links />
|
<Links />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<Refine
|
||||||
|
authProvider={authProvider}
|
||||||
|
routerProvider={routerProvider}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
|
</Refine>
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return <Outlet />;
|
return <Outlet />
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HydrateFallback() {
|
export function HydrateFallback() {
|
||||||
return <p>Loading...</p>;
|
return <p>Loading...</p>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,80 +1,13 @@
|
||||||
import type { MetaFunction } from "@remix-run/node"
|
import Login from "./login"
|
||||||
import { Link } 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, FieldCheckbox } from "~/components/forms"
|
|
||||||
|
|
||||||
export const meta: MetaFunction = () => {
|
|
||||||
return [
|
|
||||||
{ title: "New Remix SPA" },
|
|
||||||
{ name: "description", content: "Welcome to Remix (SPA Mode)!" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
return (
|
const isLogged = false
|
||||||
<div className="p-10 h-screen relative overflow-clip">
|
|
||||||
<header>
|
if (isLogged) {
|
||||||
<img src={logoPng} alt="Lume logo" className="h-10"></img>
|
window.location.href = "/dashboard"
|
||||||
</header>
|
} else {
|
||||||
<form className="w-full p-2 max-w-md space-y-4 mt-12 bg-background">
|
window.location.href = "/login"
|
||||||
<h2 className="text-3xl font-bold !mb-12">Welcome back! 🎉</h2>
|
}
|
||||||
<Field
|
|
||||||
inputProps={{ name: "email" }}
|
return isLogged ? <div>Dashboard</div> : <Login />
|
||||||
labelProps={{ children: "Email" }}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
inputProps={{ name: "password", type: "password" }}
|
|
||||||
labelProps={{ children: "Password" }}
|
|
||||||
/>
|
|
||||||
<FieldCheckbox
|
|
||||||
inputProps={{ name: "rememberMe" }}
|
|
||||||
labelProps={{ children: "Remember Me" }}
|
|
||||||
/>
|
|
||||||
<Button className="w-full h-14">Login</Button>
|
|
||||||
<p className="text-input-placeholder">
|
|
||||||
Forgot your password?{" "}
|
|
||||||
<Link
|
|
||||||
to="/sign-up"
|
|
||||||
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
|
|
||||||
>
|
|
||||||
Reset Password
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
<Button className="w-full h-14" variant={"outline"}>
|
|
||||||
Create an Account
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
<img src={lumeBgPng} alt="Lume background" className="absolute top-0 right-0 md:w-2/3 object-cover z-[-1]"></img>
|
|
||||||
<footer className="absolute bottom-5">
|
|
||||||
<ul className="flex flex-row">
|
|
||||||
<li>
|
|
||||||
<Link to="https://discord.lumeweb.com">
|
|
||||||
<Button
|
|
||||||
variant={"link"}
|
|
||||||
className="flex flex-row gap-x-2 text-input-placeholder"
|
|
||||||
>
|
|
||||||
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
|
|
||||||
Connect with us
|
|
||||||
</Button>
|
|
||||||
</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>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,179 @@
|
||||||
|
import type { MetaFunction } from "@remix-run/node"
|
||||||
|
import { Link, useLocation } from "@remix-run/react"
|
||||||
|
import { z } from "zod"
|
||||||
|
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, FieldCheckbox } from "~/components/forms"
|
||||||
|
import { getFormProps, useForm } from "@conform-to/react"
|
||||||
|
import { getZodConstraint, parseWithZod } from "@conform-to/zod"
|
||||||
|
|
||||||
|
export const meta: MetaFunction = () => {
|
||||||
|
return [
|
||||||
|
{ title: "Login" },
|
||||||
|
{ name: "description", content: "Welcome to Lume!" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
const location = useLocation()
|
||||||
|
const hash = location.hash
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-10 h-screen relative overflow-clip">
|
||||||
|
<header>
|
||||||
|
<img src={logoPng} alt="Lume logo" className="h-10" />
|
||||||
|
</header>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{hash === "" && <LoginForm />}
|
||||||
|
{hash === "#otp" && <OtpForm />}
|
||||||
|
|
||||||
|
<footer className="my-5">
|
||||||
|
<ul className="flex flex-row">
|
||||||
|
<li>
|
||||||
|
<Link to="https://discord.lumeweb.com">
|
||||||
|
<Button
|
||||||
|
variant={"link"}
|
||||||
|
className="flex flex-row gap-x-2 text-input-placeholder"
|
||||||
|
>
|
||||||
|
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
|
||||||
|
Connect with us
|
||||||
|
</Button>
|
||||||
|
</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({
|
||||||
|
email: z.string().email(),
|
||||||
|
password: z.string(),
|
||||||
|
rememberMe: z.boolean()
|
||||||
|
})
|
||||||
|
|
||||||
|
const LoginForm = () => {
|
||||||
|
const [form, fields] = useForm({
|
||||||
|
id: "login",
|
||||||
|
constraint: getZodConstraint(LoginSchema),
|
||||||
|
onValidate({ formData }) {
|
||||||
|
return parseWithZod(formData, { schema: LoginSchema })
|
||||||
|
},
|
||||||
|
shouldValidate: "onSubmit"
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="w-full p-2 max-w-md space-y-3 mt-12 bg-background"
|
||||||
|
{...getFormProps(form)}
|
||||||
|
>
|
||||||
|
<h2 className="text-3xl font-bold !mb-12">Welcome back! 🎉</h2>
|
||||||
|
<Field
|
||||||
|
inputProps={{ name: fields.email.name }}
|
||||||
|
labelProps={{ children: "Email" }}
|
||||||
|
errors={fields.email.errors}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
inputProps={{ name: fields.password.name, type: "password" }}
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Reset Password
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
<Link to="/sign-up" className="block">
|
||||||
|
<Button type="button" className="w-full h-14" variant={"outline"}>
|
||||||
|
Create an Account
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const OtpSchema = z.object({
|
||||||
|
otp: z.string().length(6, { message: "OTP must be 6 characters" })
|
||||||
|
})
|
||||||
|
|
||||||
|
const OtpForm = () => {
|
||||||
|
// TODO: Add support for resending the OTP
|
||||||
|
const [form, fields] = useForm({
|
||||||
|
id: "otp",
|
||||||
|
constraint: getZodConstraint(OtpSchema),
|
||||||
|
onValidate({ formData }) {
|
||||||
|
return parseWithZod(formData, { schema: OtpSchema })
|
||||||
|
},
|
||||||
|
shouldValidate: "onSubmit"
|
||||||
|
})
|
||||||
|
const valid = false // TODO: some sort of logic to verify user is on OTP state validly
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
location.hash = ""
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className="w-full p-2 max-w-md mt-12 bg-background"
|
||||||
|
{...getFormProps(form)}
|
||||||
|
>
|
||||||
|
<span className="block !mb-8 space-y-2">
|
||||||
|
<h2 className="text-3xl font-bold">Check your inbox</h2>
|
||||||
|
<p className="text-input-placeholder">
|
||||||
|
We will need the six digit confirmation code you received in your
|
||||||
|
email in order to verify your account and get started. Didn’t receive
|
||||||
|
a code?{" "}
|
||||||
|
<Button type="button" variant={"link"} className="text-md h-0">
|
||||||
|
Resend now →
|
||||||
|
</Button>
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
<Field
|
||||||
|
inputProps={{ name: fields.otp.name }}
|
||||||
|
labelProps={{ children: "Confirmation Code" }}
|
||||||
|
errors={fields.otp.errors}
|
||||||
|
/>
|
||||||
|
<Button className="w-full h-14">Verify</Button>
|
||||||
|
<p className="text-input-placeholder w-full text-left">
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
|
||||||
|
>
|
||||||
|
← Back to Login
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
import type { MetaFunction } from "@remix-run/node"
|
||||||
|
import { Link } 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"
|
||||||
|
|
||||||
|
export const meta: MetaFunction = () => {
|
||||||
|
return [{ title: "Sign Up" }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const RecoverPasswordSchema = z
|
||||||
|
.object({
|
||||||
|
email: z.string().email(),
|
||||||
|
})
|
||||||
|
export default function RecoverPassword() {
|
||||||
|
const [form, fields] = useForm({
|
||||||
|
id: "sign-up",
|
||||||
|
constraint: getZodConstraint(RecoverPasswordSchema),
|
||||||
|
onValidate({ formData }) {
|
||||||
|
return parseWithZod(formData, { schema: RecoverPasswordSchema })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-10 h-screen relative">
|
||||||
|
<header>
|
||||||
|
<img src={logoPng} alt="Lume logo" className="h-10" />
|
||||||
|
</header>
|
||||||
|
<form
|
||||||
|
className="w-full p-2 max-w-md space-y-4 mt-12 bg-background"
|
||||||
|
{...getFormProps(form)}
|
||||||
|
>
|
||||||
|
<span className="!mb-12 space-y-2">
|
||||||
|
<h2 className="text-3xl font-bold">Reset your password</h2>
|
||||||
|
</span>
|
||||||
|
<Field
|
||||||
|
inputProps={{ name: fields.email.name }}
|
||||||
|
labelProps={{ children: "Email Address" }}
|
||||||
|
errors={fields.email.errors}
|
||||||
|
/>
|
||||||
|
<Button className="w-full h-14">Create Account</Button>
|
||||||
|
<p className="text-input-placeholder w-full text-left">
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
|
||||||
|
>
|
||||||
|
← Back to Login
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<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>
|
||||||
|
<footer className="my-5">
|
||||||
|
<ul className="flex flex-row">
|
||||||
|
<li>
|
||||||
|
<Link to="https://discord.lumeweb.com">
|
||||||
|
<Button
|
||||||
|
variant={"link"}
|
||||||
|
className="flex flex-row gap-x-2 text-input-placeholder"
|
||||||
|
>
|
||||||
|
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
|
||||||
|
Connect with us
|
||||||
|
</Button>
|
||||||
|
</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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
import type { MetaFunction } from "@remix-run/node"
|
||||||
|
import { Link } 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, FieldCheckbox } from "~/components/forms"
|
||||||
|
import { getFormProps, useForm } from "@conform-to/react"
|
||||||
|
import { z } from "zod"
|
||||||
|
import { getZodConstraint, parseWithZod } from "@conform-to/zod"
|
||||||
|
|
||||||
|
export const meta: MetaFunction = () => {
|
||||||
|
return [{ title: "Sign Up" }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const SignUpSchema = z
|
||||||
|
.object({
|
||||||
|
email: z.string().email(),
|
||||||
|
password: z
|
||||||
|
.string()
|
||||||
|
.min(8, { message: "Password must be at least 8 characters" }),
|
||||||
|
confirmPassword: z
|
||||||
|
.string()
|
||||||
|
.min(8, { message: "Password must be at least 8 characters" }),
|
||||||
|
termsOfService: z.boolean({
|
||||||
|
required_error: "You must agree to the terms of service"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.superRefine((data, ctx) => {
|
||||||
|
if (data.password !== data.confirmPassword) {
|
||||||
|
return ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["confirmPassword"],
|
||||||
|
message: "Passwords do not match"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
export default function SignUp() {
|
||||||
|
const [form, fields] = useForm({
|
||||||
|
id: "sign-up",
|
||||||
|
constraint: getZodConstraint(SignUpSchema),
|
||||||
|
onValidate({ formData }) {
|
||||||
|
return parseWithZod(formData, { schema: SignUpSchema })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-10 h-screen relative">
|
||||||
|
<header>
|
||||||
|
<img src={logoPng} alt="Lume logo" className="h-10" />
|
||||||
|
</header>
|
||||||
|
<form
|
||||||
|
className="w-full p-2 max-w-md space-y-4 mt-12 bg-background"
|
||||||
|
{...getFormProps(form)}
|
||||||
|
>
|
||||||
|
<span className="!mb-12 space-y-2">
|
||||||
|
<h2 className="text-3xl font-bold">All roads lead to Lume</h2>
|
||||||
|
<p className="text-input-placeholder">
|
||||||
|
🤘 Get 50 GB free storage and download for free,{" "}
|
||||||
|
<b
|
||||||
|
className="text-primar
|
||||||
|
y-2"
|
||||||
|
>
|
||||||
|
forever
|
||||||
|
</b>
|
||||||
|
.{" "}
|
||||||
|
</p>
|
||||||
|
</span>
|
||||||
|
<Field
|
||||||
|
inputProps={{ name: fields.email.name }}
|
||||||
|
labelProps={{ children: "Email" }}
|
||||||
|
errors={fields.email.errors}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
inputProps={{ name: fields.password.name, type: "password" }}
|
||||||
|
labelProps={{ children: "Password" }}
|
||||||
|
errors={fields.password.errors}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
inputProps={{ name: fields.confirmPassword.name, type: "password" }}
|
||||||
|
labelProps={{ children: "Confirm Password" }}
|
||||||
|
errors={fields.confirmPassword.errors}
|
||||||
|
/>
|
||||||
|
<FieldCheckbox
|
||||||
|
inputProps={{ name: fields.termsOfService.name, form: form.id }}
|
||||||
|
labelProps={{
|
||||||
|
children: (
|
||||||
|
<span>
|
||||||
|
I agree to the
|
||||||
|
<Link
|
||||||
|
to="/terms-of-service"
|
||||||
|
className="text-primary-1 text-md hover:underline hover:underline-offset-4 mx-1"
|
||||||
|
>
|
||||||
|
Terms of Service
|
||||||
|
</Link>
|
||||||
|
and
|
||||||
|
<Link
|
||||||
|
to="/privacy-policy"
|
||||||
|
className="text-primary-1 text-md hover:underline hover:underline-offset-4 mx-1"
|
||||||
|
>
|
||||||
|
Privacy Policy
|
||||||
|
</Link>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
errors={fields.termsOfService.errors}
|
||||||
|
/>
|
||||||
|
<Button className="w-full h-14">Create Account</Button>
|
||||||
|
<p className="text-input-placeholder w-full text-right">
|
||||||
|
Already have an account?{" "}
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="text-primary-1 text-md hover:underline hover:underline-offset-4"
|
||||||
|
>
|
||||||
|
Login here instead →
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<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>
|
||||||
|
<footer className="my-5">
|
||||||
|
<ul className="flex flex-row">
|
||||||
|
<li>
|
||||||
|
<Link to="https://discord.lumeweb.com">
|
||||||
|
<Button
|
||||||
|
variant={"link"}
|
||||||
|
className="flex flex-row gap-x-2 text-input-placeholder"
|
||||||
|
>
|
||||||
|
<img className="h-5" src={discordLogoPng} alt="Discord Logo" />
|
||||||
|
Connect with us
|
||||||
|
</Button>
|
||||||
|
</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>
|
||||||
|
)
|
||||||
|
}
|
|
@ -29,12 +29,12 @@
|
||||||
--accent-foreground: 0 0% 9%;
|
--accent-foreground: 0 0% 9%;
|
||||||
|
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 0 0% 98%;
|
--destructive-foreground: 0 72% 51%;
|
||||||
|
|
||||||
--border: 240 50% 17%;
|
--border: 240 50% 17%;
|
||||||
--input: 240 50% 17%;
|
--input: 240 50% 17%;
|
||||||
--input-placeholder: 241 21% 42%;
|
--input-placeholder: 241 21% 42%;
|
||||||
--ring: 0 0% 3.9%;
|
--ring: 241 90% 82%;
|
||||||
|
|
||||||
--radius: 5px;
|
--radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "",
|
"name": "lume-portal-dashboard",
|
||||||
"private": true,
|
"private": true,
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -14,10 +14,14 @@
|
||||||
"@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-20240306231947",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@refinedev/cli": "^2.16.1",
|
||||||
|
"@refinedev/core": "^4.47.2",
|
||||||
|
"@refinedev/remix-router": "^3.0.0",
|
||||||
"@remix-run/node": "^2.8.0",
|
"@remix-run/node": "^2.8.0",
|
||||||
"@remix-run/react": "^2.8.0",
|
"@remix-run/react": "^2.8.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
@ -25,7 +29,8 @@
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.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",
|
||||||
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@remix-run/dev": "^2.8.0",
|
"@remix-run/dev": "^2.8.0",
|
||||||
|
|
|
@ -7,7 +7,6 @@ export default defineConfig({
|
||||||
remix({
|
remix({
|
||||||
ssr: false,
|
ssr: false,
|
||||||
ignoredRouteFiles: ["**/*.css"],
|
ignoredRouteFiles: ["**/*.css"],
|
||||||
|
|
||||||
}),
|
}),
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue