feat: signin
This commit is contained in:
parent
276451f3bf
commit
0a98a0b365
|
|
@ -85,7 +85,7 @@ const Header = ({ user, route }: Props) => {
|
||||||
{user ? (
|
{user ? (
|
||||||
<div className="dropdown dropdown-end">
|
<div className="dropdown dropdown-end">
|
||||||
<div tabIndex={0} className="btn btn-ghost rounded-full p-0">
|
<div tabIndex={0} className="btn btn-ghost rounded-full p-0">
|
||||||
<div className="rounded-full w-10 h-10 m-1 inline-flex justify-center items-center bg-white text-3xl">
|
<div className="rounded-full w-10 h-10 m-1 inline-flex justify-center items-center bg-primary-content text-primary text-3xl">
|
||||||
{user.icon ?? user.username[0]}
|
{user.icon ?? user.username[0]}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { ActionFunction, LinksFunction, MetaFunction } from "remix";
|
import type { ActionFunction, LinksFunction, MetaFunction } from "remix";
|
||||||
import { useActionData, json, Link, useSearchParams, Form } from "remix";
|
import { useActionData, json, Link, useSearchParams, Form } from "remix";
|
||||||
import { login, createUserSession, register } from "~/utils/session.server";
|
import { login, createUserSession } from "~/utils/session.server";
|
||||||
import Header from "../components/Header";
|
import Header from "../components/Header";
|
||||||
|
|
||||||
export const links: LinksFunction = () => {
|
export const links: LinksFunction = () => {
|
||||||
|
|
@ -104,7 +104,7 @@ export default function Login() {
|
||||||
type="text"
|
type="text"
|
||||||
id="username-input"
|
id="username-input"
|
||||||
name="username"
|
name="username"
|
||||||
className="input"
|
className="input input-bordered"
|
||||||
defaultValue={actionData?.fields?.username}
|
defaultValue={actionData?.fields?.username}
|
||||||
aria-invalid={Boolean(actionData?.fieldErrors?.username)}
|
aria-invalid={Boolean(actionData?.fieldErrors?.username)}
|
||||||
aria-describedby={
|
aria-describedby={
|
||||||
|
|
@ -143,7 +143,7 @@ export default function Login() {
|
||||||
<input
|
<input
|
||||||
id="password-input"
|
id="password-input"
|
||||||
name="password"
|
name="password"
|
||||||
className="input"
|
className="input input-bordered"
|
||||||
defaultValue={actionData?.fields?.password}
|
defaultValue={actionData?.fields?.password}
|
||||||
type="password"
|
type="password"
|
||||||
aria-invalid={
|
aria-invalid={
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import type { ActionFunction, LinksFunction, MetaFunction } from "remix";
|
import type { ActionFunction, LinksFunction, MetaFunction } from "remix";
|
||||||
import { useActionData, json, Link, useSearchParams, Form } from "remix";
|
import { useActionData, json, Link, useSearchParams, Form } from "remix";
|
||||||
import { db } from "~/utils/db.server";
|
import { db } from "~/utils/db.server";
|
||||||
import { login, createUserSession, register } from "~/utils/session.server";
|
import { createUserSession, register } from "~/utils/session.server";
|
||||||
|
import Header from "../components/Header";
|
||||||
|
|
||||||
export const links: LinksFunction = () => {
|
export const links: LinksFunction = () => {
|
||||||
return [];
|
return [];
|
||||||
|
|
@ -9,8 +10,8 @@ export const links: LinksFunction = () => {
|
||||||
|
|
||||||
export const meta: MetaFunction = () => {
|
export const meta: MetaFunction = () => {
|
||||||
return {
|
return {
|
||||||
title: "Explit | Login",
|
title: "Explit | Sign in",
|
||||||
description: "Login to track and split your expenses!",
|
description: "Sign in to track and split your expenses!",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -26,6 +27,18 @@ function validatePassword(password: unknown) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateConfirmPassword(confirmPassword: unknown, password: string) {
|
||||||
|
if (typeof confirmPassword !== "string" || confirmPassword !== password) {
|
||||||
|
return `Passwords must match`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateIcon(icon: unknown) {
|
||||||
|
if (typeof icon !== "string" || icon.length > 2) {
|
||||||
|
return `Icons must be a single character, e.g. "A" or "😎"`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateTeamId(teamId: unknown) {
|
function validateTeamId(teamId: unknown) {
|
||||||
if (typeof teamId !== "string" || teamId.length < 1) {
|
if (typeof teamId !== "string" || teamId.length < 1) {
|
||||||
return "You must indicate an arbitrary team ID";
|
return "You must indicate an arbitrary team ID";
|
||||||
|
|
@ -37,11 +50,14 @@ type ActionData = {
|
||||||
fieldErrors?: {
|
fieldErrors?: {
|
||||||
username: string | undefined;
|
username: string | undefined;
|
||||||
password: string | undefined;
|
password: string | undefined;
|
||||||
|
teamId: string | undefined;
|
||||||
|
confirmPassword: string | undefined;
|
||||||
|
icon: string | undefined;
|
||||||
};
|
};
|
||||||
fields?: {
|
fields?: {
|
||||||
loginType: string;
|
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
teamId?: string;
|
teamId?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
};
|
};
|
||||||
|
|
@ -51,73 +67,55 @@ const badRequest = (data: ActionData) => json(data, { status: 400 });
|
||||||
|
|
||||||
export const action: ActionFunction = async ({ request }) => {
|
export const action: ActionFunction = async ({ request }) => {
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
const loginType = form.get("loginType");
|
|
||||||
const username = form.get("username");
|
const username = form.get("username");
|
||||||
const password = form.get("password");
|
const password = form.get("password");
|
||||||
const icon = form.get("icon");
|
const confirmPassword = form.get("confirmPassword");
|
||||||
|
const icon =
|
||||||
|
form.get("icon") ??
|
||||||
|
(typeof username === "string" ? username[0] : undefined);
|
||||||
const teamId = form.get("teamId");
|
const teamId = form.get("teamId");
|
||||||
const redirectTo = form.get("redirectTo") || "/expenses";
|
const redirectTo = form.get("redirectTo") || "/expenses";
|
||||||
if (
|
if (
|
||||||
typeof loginType !== "string" ||
|
|
||||||
typeof username !== "string" ||
|
typeof username !== "string" ||
|
||||||
typeof password !== "string" ||
|
typeof password !== "string" ||
|
||||||
(loginType === "register" &&
|
typeof confirmPassword !== "string" ||
|
||||||
(typeof icon !== "string" ||
|
typeof teamId !== "string" ||
|
||||||
typeof teamId !== "string" ||
|
typeof redirectTo !== "string"
|
||||||
typeof redirectTo !== "string"))
|
|
||||||
) {
|
) {
|
||||||
return badRequest({
|
return badRequest({
|
||||||
formError: `Form not submitted correctly.`,
|
formError: `Form not submitted correctly.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = { loginType, username, password, teamId };
|
const fields = { username, password, confirmPassword, teamId };
|
||||||
const fieldErrors = {
|
const fieldErrors = {
|
||||||
username: validateUsername(username),
|
username: validateUsername(username),
|
||||||
password: validatePassword(password),
|
password: validatePassword(password),
|
||||||
teamId: loginType === "register" && validateTeamId(teamId),
|
confirmPassword: validateConfirmPassword(confirmPassword, password),
|
||||||
|
icon: validateIcon(icon),
|
||||||
|
teamId: validateTeamId(teamId),
|
||||||
};
|
};
|
||||||
if (Object.values(fieldErrors).some(Boolean))
|
if (Object.values(fieldErrors).some(Boolean))
|
||||||
return badRequest({ fieldErrors, fields });
|
return badRequest({ fieldErrors, fields });
|
||||||
|
|
||||||
switch (loginType) {
|
const userExists = await db.user.findFirst({
|
||||||
case "login": {
|
where: { username },
|
||||||
const user = await login({ username, password });
|
});
|
||||||
if (!user) {
|
if (userExists) {
|
||||||
return badRequest({
|
console.error(userExists);
|
||||||
fields,
|
return badRequest({
|
||||||
formError: `Username/Password combination is incorrect`,
|
fields,
|
||||||
});
|
formError: `User with username ${username} already exists`,
|
||||||
}
|
});
|
||||||
return createUserSession(user.id, redirectTo);
|
|
||||||
}
|
|
||||||
case "register": {
|
|
||||||
const userExists = await db.user.findFirst({
|
|
||||||
where: { username },
|
|
||||||
});
|
|
||||||
if (userExists) {
|
|
||||||
console.error(userExists);
|
|
||||||
return badRequest({
|
|
||||||
fields,
|
|
||||||
formError: `User with username ${username} already exists`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const user = await register({ username, password, icon, teamId });
|
|
||||||
if (!user) {
|
|
||||||
return badRequest({
|
|
||||||
fields,
|
|
||||||
formError: `Something went wrong trying to create a new user.`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return createUserSession(user.id, redirectTo);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return badRequest({
|
|
||||||
fields,
|
|
||||||
formError: `Login type invalid`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
const user = await register({ username, password, icon, teamId });
|
||||||
|
if (!user) {
|
||||||
|
return badRequest({
|
||||||
|
fields,
|
||||||
|
formError: `Something went wrong trying to create a new user.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return createUserSession(user.id, redirectTo);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
|
|
@ -125,143 +123,294 @@ export default function Login() {
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<>
|
||||||
<div className="content" data-light="">
|
<Header />
|
||||||
<h1>Login</h1>
|
<div className="container mx-auto min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<Form
|
<div className="card bg-base-200 w-full shadow-lg max-w-lg">
|
||||||
method="post"
|
<div className="card-body w-full">
|
||||||
aria-describedby={
|
<h1 className="card-title">Sign In</h1>
|
||||||
actionData?.formError ? "form-error-message" : undefined
|
<Form
|
||||||
}
|
method="post"
|
||||||
>
|
className="mt-5"
|
||||||
<input
|
aria-describedby={
|
||||||
type="hidden"
|
actionData?.formError ? "form-error-message" : undefined
|
||||||
name="redirectTo"
|
}
|
||||||
value={searchParams.get("redirectTo") ?? undefined}
|
>
|
||||||
/>
|
|
||||||
<fieldset>
|
|
||||||
<legend className="sr-only">Login or Register?</legend>
|
|
||||||
<label>
|
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="hidden"
|
||||||
name="loginType"
|
name="redirectTo"
|
||||||
value="login"
|
value={searchParams.get("redirectTo") ?? undefined}
|
||||||
defaultChecked={
|
/>
|
||||||
!actionData?.fields?.loginType ||
|
<div className="form-control mb-3">
|
||||||
actionData?.fields?.loginType === "login"
|
<label htmlFor="username-input" className="label">
|
||||||
}
|
<span className="label-text">Username</span>
|
||||||
/>{" "}
|
</label>
|
||||||
Login
|
<input
|
||||||
</label>
|
type="text"
|
||||||
<label>
|
id="username-input"
|
||||||
<input
|
name="username"
|
||||||
type="radio"
|
className={`input input-bordered${
|
||||||
name="loginType"
|
Boolean(actionData?.fieldErrors?.username)
|
||||||
value="register"
|
? " input-error"
|
||||||
defaultChecked={actionData?.fields?.loginType === "register"}
|
: ""
|
||||||
/>{" "}
|
}`}
|
||||||
Register
|
defaultValue={actionData?.fields?.username}
|
||||||
</label>
|
aria-invalid={Boolean(actionData?.fieldErrors?.username)}
|
||||||
</fieldset>
|
aria-describedby={
|
||||||
<div>
|
actionData?.fieldErrors?.username
|
||||||
<label htmlFor="username-input">Username</label>
|
? "username-error"
|
||||||
<input
|
: undefined
|
||||||
type="text"
|
}
|
||||||
id="username-input"
|
/>
|
||||||
name="username"
|
{actionData?.fieldErrors?.username && (
|
||||||
defaultValue={actionData?.fields?.username}
|
<div className="alert alert-error mt-2" role="alert">
|
||||||
aria-invalid={Boolean(actionData?.fieldErrors?.username)}
|
<div className="flex-1">
|
||||||
aria-describedby={
|
<svg
|
||||||
actionData?.fieldErrors?.username ? "username-error" : undefined
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
}
|
fill="none"
|
||||||
/>
|
viewBox="0 0 24 24"
|
||||||
{actionData?.fieldErrors?.username ? (
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
<p
|
>
|
||||||
className="form-validation-error"
|
<path
|
||||||
role="alert"
|
strokeLinecap="round"
|
||||||
id="username-error"
|
strokeLinejoin="round"
|
||||||
>
|
strokeWidth="2"
|
||||||
{actionData?.fieldErrors.username}
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
</p>
|
></path>
|
||||||
) : null}
|
</svg>
|
||||||
|
<label id="username-error">
|
||||||
|
{actionData?.fieldErrors.username}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-control mb-3">
|
||||||
|
<label htmlFor="icon-input" className="label">
|
||||||
|
<span className="label-text">Icon</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="icon-input"
|
||||||
|
name="icon"
|
||||||
|
className={`input input-bordered${
|
||||||
|
Boolean(actionData?.fieldErrors?.icon)
|
||||||
|
? " input-warning"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
defaultValue={actionData?.fields?.icon}
|
||||||
|
aria-invalid={Boolean(actionData?.fieldErrors?.icon)}
|
||||||
|
aria-describedby={
|
||||||
|
actionData?.fieldErrors?.icon ? "icon-error" : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text-alt">
|
||||||
|
Icon defaults to inital letter of username, can be a single
|
||||||
|
character or emoji
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
{actionData?.fieldErrors?.icon && (
|
||||||
|
<div className="alert alert-error mt-2" role="alert">
|
||||||
|
<div className="flex-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<label id="icon-error">
|
||||||
|
{actionData?.fieldErrors.icon}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-control mb-3">
|
||||||
|
<label htmlFor="password-input" className="label">
|
||||||
|
<span className="label-text">Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="password-input"
|
||||||
|
name="password"
|
||||||
|
className={`input input-bordered${
|
||||||
|
Boolean(actionData?.fieldErrors?.password)
|
||||||
|
? " input-error"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
defaultValue={actionData?.fields?.password}
|
||||||
|
type="password"
|
||||||
|
aria-invalid={
|
||||||
|
Boolean(actionData?.fieldErrors?.password) || undefined
|
||||||
|
}
|
||||||
|
aria-describedby={
|
||||||
|
actionData?.fieldErrors?.password
|
||||||
|
? "password-error"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{actionData?.fieldErrors?.password && (
|
||||||
|
<div className="alert alert-error mt-2" role="alert">
|
||||||
|
<div className="flex-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<label id="password-error">
|
||||||
|
{actionData?.fieldErrors.password}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-control mb-3">
|
||||||
|
<label htmlFor="confirmPassword-input" className="label">
|
||||||
|
<span className="label-text">Confirm password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="confirmPassword-input"
|
||||||
|
name="confirmPassword"
|
||||||
|
className={`input input-bordered${
|
||||||
|
Boolean(actionData?.fieldErrors?.confirmPassword)
|
||||||
|
? " input-error"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
defaultValue={actionData?.fields?.confirmPassword}
|
||||||
|
type="password"
|
||||||
|
aria-invalid={
|
||||||
|
Boolean(actionData?.fieldErrors?.confirmPassword) ||
|
||||||
|
undefined
|
||||||
|
}
|
||||||
|
aria-describedby={
|
||||||
|
actionData?.fieldErrors?.confirmPassword
|
||||||
|
? "confirmPassword-error"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{actionData?.fieldErrors?.confirmPassword && (
|
||||||
|
<div className="alert alert-error mt-2" role="alert">
|
||||||
|
<div className="flex-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<label id="confirmPassword-error">
|
||||||
|
{actionData?.fieldErrors.confirmPassword}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="form-control mb-3">
|
||||||
|
<label htmlFor="teamId-input" className="label">
|
||||||
|
<span className="label-text">Team</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="teamId-input"
|
||||||
|
name="teamId"
|
||||||
|
className={`input input-bordered${
|
||||||
|
Boolean(actionData?.fieldErrors?.teamId)
|
||||||
|
? " input-error"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
defaultValue={actionData?.fields?.teamId}
|
||||||
|
aria-invalid={Boolean(actionData?.fieldErrors?.teamId)}
|
||||||
|
aria-describedby={
|
||||||
|
actionData?.fieldErrors?.teamId ? "teamid-error" : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{actionData?.fieldErrors?.teamId && (
|
||||||
|
<div className="alert alert-error mt-2" role="alert">
|
||||||
|
<div className="flex-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<label id="teamid-error">
|
||||||
|
{actionData?.fieldErrors.teamId}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{actionData?.formError && (
|
||||||
|
<div className="alert alert-error mt-5" role="alert">
|
||||||
|
<div className="flex-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<label id="form-error-message">
|
||||||
|
{actionData?.formError}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-center max-w-xs mx-auto mt-10">
|
||||||
|
<button type="submit" className="btn btn-primary btn-block">
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<label htmlFor="username-input">Team ID</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="team-id-input"
|
|
||||||
name="teamId"
|
|
||||||
defaultValue={actionData?.fields?.teamId}
|
|
||||||
aria-hidden={actionData?.fields?.loginType === "login"}
|
|
||||||
aria-invalid={Boolean(actionData?.fieldErrors?.teamId)}
|
|
||||||
aria-describedby={
|
|
||||||
actionData?.fieldErrors?.teamId ? "teamId-error" : undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{actionData?.fieldErrors?.teamId ? (
|
|
||||||
<p
|
|
||||||
className="form-validation-error"
|
|
||||||
role="alert"
|
|
||||||
id="teamId-error"
|
|
||||||
>
|
|
||||||
{actionData?.fieldErrors.teamId}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label htmlFor="username-input">Icon (letter or emoji)</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
maxLength={1}
|
|
||||||
aria-hidden={actionData?.fields?.loginType === "login"}
|
|
||||||
id="icon-input"
|
|
||||||
name="icon"
|
|
||||||
defaultValue={actionData?.fields?.icon}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label htmlFor="password-input">Password</label>
|
|
||||||
<input
|
|
||||||
id="password-input"
|
|
||||||
name="password"
|
|
||||||
defaultValue={actionData?.fields?.password}
|
|
||||||
type="password"
|
|
||||||
aria-invalid={
|
|
||||||
Boolean(actionData?.fieldErrors?.password) || undefined
|
|
||||||
}
|
|
||||||
aria-describedby={
|
|
||||||
actionData?.fieldErrors?.password ? "password-error" : undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{actionData?.fieldErrors?.password ? (
|
|
||||||
<p
|
|
||||||
className="form-validation-error"
|
|
||||||
role="alert"
|
|
||||||
id="password-error"
|
|
||||||
>
|
|
||||||
{actionData?.fieldErrors.password}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<div id="form-error-message">
|
|
||||||
{actionData?.formError ? (
|
|
||||||
<p className="form-validation-error" role="alert">
|
|
||||||
{actionData?.formError}
|
|
||||||
</p>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
<button type="submit" className="button">
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="links">
|
<ul className="menu px-3 menu-horizontal rounded-box max-w-xs mx-auto flex items-center justify-evenly">
|
||||||
<ul>
|
<li>
|
||||||
<li>
|
<Link to="/" className="btn btn-outline btn-accent">
|
||||||
<Link to="/">Home</Link>
|
Home
|
||||||
</li>
|
</Link>
|
||||||
</ul>
|
</li>
|
||||||
</div>
|
<li>
|
||||||
</div>
|
<Link to="/login" className="btn btn-outline btn-accent">
|
||||||
|
Login
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue