Compare commits

..

No commits in common. "bug/refine-remix-vite" and "master" have entirely different histories.

14 changed files with 674 additions and 7654 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2024 Hammer Technologies LLC Copyright (c) 2024 LumeWeb
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:

View File

@ -64,36 +64,34 @@ 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)} >
> <Checkbox
<Checkbox {...checkboxProps}
{...checkboxProps} id={id}
id={id} aria-invalid={errorId ? true : undefined}
aria-invalid={errorId ? true : undefined} aria-describedby={errorId}
aria-describedby={errorId} checked={input.value === checkedValue}
checked={input.value === checkedValue} onCheckedChange={(state) => {
onCheckedChange={(state) => { input.change(state.valueOf() ? checkedValue : "")
input.change(state.valueOf() ? checkedValue : "") inputProps.onCheckedChange?.(state)
inputProps.onCheckedChange?.(state) }}
}} onFocus={(event) => {
onFocus={(event) => { input.focus()
input.focus() inputProps.onFocus?.(event)
inputProps.onFocus?.(event) }}
}} onBlur={(event) => {
onBlur={(event) => { input.blur()
input.blur() inputProps.onBlur?.(event)
inputProps.onBlur?.(event) }}
}} 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>
) )
} }
@ -110,7 +108,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-[12px] text-destructive-foreground"> <li key={e} className="text-[10px] text-foreground-destructive">
{e} {e}
</li> </li>
))} ))}

View File

@ -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

View File

@ -1,36 +0,0 @@
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" }
}
}

BIN
app/images/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -3,22 +3,19 @@ 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 (
@ -30,23 +27,18 @@ export function Layout({ children }: { children: React.ReactNode }) {
<Links /> <Links />
</head> </head>
<body> <body>
<Refine {children}
authProvider={authProvider}
routerProvider={routerProvider}
>
{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>;
} }

View File

@ -1,13 +1,80 @@
import Login from "./login" 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"
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() {
const isLogged = false return (
<div className="p-10 h-screen relative overflow-clip">
if (isLogged) { <header>
window.location.href = "/dashboard" <img src={logoPng} alt="Lume logo" className="h-10"></img>
} else { </header>
window.location.href = "/login" <form className="w-full p-2 max-w-md space-y-4 mt-12 bg-background">
} <h2 className="text-3xl font-bold !mb-12">Welcome back! 🎉</h2>
<Field
return isLogged ? <div>Dashboard</div> : <Login /> inputProps={{ name: "email" }}
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>
)
} }

View File

@ -1,179 +0,0 @@
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. Didnt 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>
)
}

View File

@ -1,92 +0,0 @@
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>
)
}

View File

@ -1,157 +0,0 @@
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>
)
}

View File

@ -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 72% 51%; --destructive-foreground: 0 0% 98%;
--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: 241 90% 82%; --ring: 0 0% 3.9%;
--radius: 5px; --radius: 5px;
} }

7675
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{ {
"name": "lume-portal-dashboard", "name": "",
"private": true, "private": true,
"sideEffects": false, "sideEffects": false,
"type": "module", "type": "module",
@ -14,14 +14,10 @@
"@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",
@ -29,8 +25,7 @@
"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",

View File

@ -7,6 +7,7 @@ export default defineConfig({
remix({ remix({
ssr: false, ssr: false,
ignoredRouteFiles: ["**/*.css"], ignoredRouteFiles: ["**/*.css"],
}), }),
tsconfigPaths(), tsconfigPaths(),
], ],