Merge pull request #58 from linkwarden/dev

Dev
This commit is contained in:
Daniel 2023-07-14 16:06:09 -04:00 committed by GitHub
commit 5e40f5dd44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 242 additions and 225 deletions

View File

@ -80,16 +80,16 @@ export default function ChangePassword({
value={newPassword} value={newPassword}
onChange={(e) => setNewPassword1(e.target.value)} onChange={(e) => setNewPassword1(e.target.value)}
type="password" type="password"
placeholder="*****************" placeholder="***********"
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />
<p className="text-sm text-sky-500">Re-enter New Password</p> <p className="text-sm text-sky-500">Confirm New Password</p>
<input <input
value={newPassword2} value={newPassword2}
onChange={(e) => setNewPassword2(e.target.value)} onChange={(e) => setNewPassword2(e.target.value)}
type="password" type="password"
placeholder="*****************" placeholder="***********"
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />

View File

@ -2,7 +2,6 @@ import useCollectionStore from "@/store/collections";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { import {
faFolder, faFolder,
faBox,
faHashtag, faHashtag,
faChartSimple, faChartSimple,
faChevronDown, faChevronDown,
@ -13,6 +12,7 @@ import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Disclosure, Transition } from "@headlessui/react"; import { Disclosure, Transition } from "@headlessui/react";
import Image from "next/image";
export default function Sidebar({ className }: { className?: string }) { export default function Sidebar({ className }: { className?: string }) {
const [tagDisclosure, setTagDisclosure] = useState<boolean>(() => { const [tagDisclosure, setTagDisclosure] = useState<boolean>(() => {
@ -53,55 +53,50 @@ export default function Sidebar({ className }: { className?: string }) {
<div <div
className={`bg-gray-100 h-screen w-64 xl:w-80 overflow-y-auto border-solid border-r-sky-100 px-2 border z-20 ${className}`} className={`bg-gray-100 h-screen w-64 xl:w-80 overflow-y-auto border-solid border-r-sky-100 px-2 border z-20 ${className}`}
> >
<p className="p-2 text-sky-500 font-bold text-2xl my-2 leading-4"> <div className="flex justify-center gap-2 mt-2">
Linkwarden <Link
</p> href="/dashboard"
<div className="flex flex-col gap-1">
<Link href="/dashboard">
<div
className={`${ className={`${
active === "/dashboard" active === "/dashboard"
? "bg-sky-200" ? "bg-sky-200"
: "hover:bg-slate-200 bg-gray-100" : "hover:bg-slate-200 bg-gray-100"
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`} } outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex justify-center flex-col items-center gap-1`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faChartSimple} icon={faChartSimple}
className={`w-6 h-6 drop-shadow text-sky-500`} className={`w-8 h-8 drop-shadow text-sky-500`}
/> />
<p className="text-sky-600">Dashboard</p> <p className="text-sky-600 text-xs font-semibold">Dashboard</p>
</div>
</Link> </Link>
<Link href="/links"> <Link
<div href="/links"
className={`${ className={`${
active === "/links" active === "/links"
? "bg-sky-200" ? "bg-sky-200"
: "hover:bg-slate-200 bg-gray-100" : "hover:bg-slate-200 bg-gray-100"
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`} } outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex justify-center flex-col items-center gap-1 w-full`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faLink} icon={faLink}
className={`w-6 h-6 drop-shadow text-sky-500`} className={`w-8 h-8 drop-shadow text-sky-500`}
/> />
<p className="text-sky-600">All Links</p> <p className="text-sky-600 text-xs font-semibold">All Links</p>
</div>
</Link> </Link>
<Link href="/collections"> <Link
<div href="/collections"
className={`${ className={`${
active === "/collections" ? "bg-sky-200" : "hover:bg-slate-200" active === "/collections" ? "bg-sky-200" : "hover:bg-slate-200"
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`} } outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex justify-center flex-col items-center gap-1 w-full`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faBox} icon={faFolder}
className={`w-6 h-6 drop-shadow text-sky-500`} className={`w-8 h-8 drop-shadow text-sky-500`}
/> />
<p className="text-sky-600">All Collections</p> <p className="text-sky-600 text-xs font-semibold">
</div> <span className="hidden xl:inline-block">All</span> Collections
</p>
</Link> </Link>
</div> </div>

View File

@ -20,14 +20,22 @@ export default function AuthRedirect({ children }: Props) {
if (!router.pathname.startsWith("/public")) { if (!router.pathname.startsWith("/public")) {
if ( if (
status === "authenticated" && status === "authenticated" &&
(router.pathname === "/login" || router.pathname === "/register") (router.pathname === "/login" ||
router.pathname === "/register" ||
router.pathname === "/confirmation" ||
router.pathname === "/forgot")
) { ) {
router.push("/").then(() => { router.push("/").then(() => {
setRedirect(false); setRedirect(false);
}); });
} else if ( } else if (
status === "unauthenticated" && status === "unauthenticated" &&
!(router.pathname === "/login" || router.pathname === "/register") !(
router.pathname === "/login" ||
router.pathname === "/register" ||
router.pathname === "/confirmation" ||
router.pathname === "/forgot"
)
) { ) {
router.push("/login").then(() => { router.push("/login").then(() => {
setRedirect(false); setRedirect(false);

View File

@ -5,8 +5,6 @@ import { createTransport } from "nodemailer";
export default async function sendVerificationRequest( export default async function sendVerificationRequest(
params: SendVerificationRequestParams params: SendVerificationRequestParams
) { ) {
console.log(params);
const { identifier, url, provider, theme } = params; const { identifier, url, provider, theme } = params;
const { host } = new URL(url); const { host } = new URL(url);
const transport = createTransport(provider.server); const transport = createTransport(provider.server);

View File

@ -19,7 +19,7 @@ const addMemberToCollection = async (
// member can't be empty // member can't be empty
memberUsername.trim() !== "" && memberUsername.trim() !== "" &&
// member can't be the owner // member can't be the owner
memberUsername.trim() !== ownerUsername memberUsername.trim().toLowerCase() !== ownerUsername.toLowerCase()
) { ) {
// Lookup, get data/err, list ... // Lookup, get data/err, list ...
const user = await getPublicUserDataByUsername( const user = await getPublicUserDataByUsername(
@ -40,7 +40,7 @@ const addMemberToCollection = async (
}); });
} }
} else if (checkIfMemberAlreadyExists) toast.error("User already exists."); } else if (checkIfMemberAlreadyExists) toast.error("User already exists.");
else if (memberUsername.trim() === ownerUsername) else if (memberUsername.trim().toLowerCase() === ownerUsername.toLowerCase())
toast.error("You are already the collection owner."); toast.error("You are already the collection owner.");
}; };

View File

@ -27,7 +27,6 @@
"@types/nodemailer": "^6.4.8", "@types/nodemailer": "^6.4.8",
"@types/react": "18.2.14", "@types/react": "18.2.14",
"@types/react-dom": "18.2.6", "@types/react-dom": "18.2.6",
"bcrypt": "^5.1.0", "bcrypt": "^5.1.0",
"colorthief": "^2.4.0", "colorthief": "^2.4.0",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",

View File

@ -10,6 +10,8 @@ import { Adapter } from "next-auth/adapters";
import sendVerificationRequest from "@/lib/api/sendVerificationRequest"; import sendVerificationRequest from "@/lib/api/sendVerificationRequest";
import { Provider } from "next-auth/providers"; import { Provider } from "next-auth/providers";
let email;
const providers: Provider[] = [ const providers: Provider[] = [
CredentialsProvider({ CredentialsProvider({
type: "credentials", type: "credentials",
@ -63,6 +65,7 @@ if (process.env.EMAIL_SERVER && process.env.EMAIL_FROM)
from: process.env.EMAIL_FROM, from: process.env.EMAIL_FROM,
maxAge: 600, maxAge: 600,
sendVerificationRequest(params) { sendVerificationRequest(params) {
email = params.identifier;
sendVerificationRequest(params); sendVerificationRequest(params);
}, },
}) })
@ -76,6 +79,7 @@ export const authOptions: AuthOptions = {
providers, providers,
pages: { pages: {
signIn: "/login", signIn: "/login",
verifyRequest: "/confirmation",
}, },
callbacks: { callbacks: {
session: async ({ session, token }: { session: Session; token: JWT }) => { session: async ({ session, token }: { session: Session; token: JWT }) => {

View File

@ -1,7 +1,7 @@
import useCollectionStore from "@/store/collections"; import useCollectionStore from "@/store/collections";
import { import {
faBox,
faEllipsis, faEllipsis,
faFolder,
faPlus, faPlus,
faSort, faSort,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
@ -36,7 +36,7 @@ export default function Collections() {
<div className="flex gap-3 items-end"> <div className="flex gap-3 items-end">
<div className="flex gap-2"> <div className="flex gap-2">
<FontAwesomeIcon <FontAwesomeIcon
icon={faBox} icon={faFolder}
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-500 drop-shadow" className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-500 drop-shadow"
/> />
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold"> <p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold">

View File

@ -1,23 +1,24 @@
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import React from "react"; import React from "react";
export default function EmailConfirmaion({ email }: { email: string }) { export default function EmailConfirmaion() {
return ( return (
<div className="overflow-y-auto py-2 fixed top-0 bottom-0 right-0 left-0 bg-gray-500 bg-opacity-10 backdrop-blur-sm flex items-center fade-in z-30"> <div className="overflow-y-auto py-2 fixed top-0 bottom-0 right-0 left-0 bg-gray-500 bg-opacity-10 backdrop-blur-sm flex items-center fade-in z-30">
<div className="mx-auto p-3 rounded-xl border border-sky-100 shadow-lg bg-gray-100 text-sky-800"> <div className="mx-auto p-3 text-center rounded-xl border border-sky-100 shadow-lg bg-gray-100 text-sky-800">
<p className="text-center text-2xl mb-2">Please check your email</p> <p className="text-center text-2xl mb-2">Please check your email</p>
<p>A sign in link has been sent to your email address.</p> <p>A sign in link has been sent to your email address.</p>
<div <p>You can safely close this page.</p>
{/* <div
onClick={() => onClick={() =>
signIn("email", { signIn("email", {
email, email: email,
redirect: false, redirect: false,
}) })
} }
className="mx-auto font-semibold mt-2 cursor-pointer w-fit" className="mx-auto font-semibold mt-2 cursor-pointer w-fit"
> >
Resend? Resend?
</div> </div> */}
</div> </div>
</div> </div>
); );

View File

@ -1,6 +1,6 @@
import EmailConfirmaion from "@/components/Modal/EmailConfirmaion";
import SubmitButton from "@/components/SubmitButton"; import SubmitButton from "@/components/SubmitButton";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react"; import { useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
@ -11,7 +11,6 @@ interface FormData {
export default function Forgot() { export default function Forgot() {
const [submitLoader, setSubmitLoader] = useState(false); const [submitLoader, setSubmitLoader] = useState(false);
const [showConfirmation, setShowConfirmation] = useState(false);
const [form, setForm] = useState<FormData>({ const [form, setForm] = useState<FormData>({
email: "", email: "",
@ -23,20 +22,16 @@ export default function Forgot() {
const load = toast.loading("Sending login link..."); const load = toast.loading("Sending login link...");
const res = await signIn("email", { await signIn("email", {
email: form.email, email: form.email,
callbackUrl: window.location.origin, callbackUrl: "/",
}); });
setShowConfirmation(true);
toast.dismiss(load); toast.dismiss(load);
setSubmitLoader(false); setSubmitLoader(false);
if (!res?.ok) { toast.success("Login link sent.");
toast.error("Invalid login.");
}
} else { } else {
toast.error("Please fill out all the fields."); toast.error("Please fill out all the fields.");
} }
@ -44,30 +39,35 @@ export default function Forgot() {
return ( return (
<> <>
{showConfirmation && form.email ? ( <div className="p-5 mt-10 mx-auto flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100">
<EmailConfirmaion email={form.email} /> <div className="flex flex-col gap-2 sm:flex-row justify-between items-center mb-5">
) : undefined} <Image
<p className="text-xl font-bold text-center text-sky-500 mt-10 mb-3"> src="/linkwarden.png"
Linkwarden width={1694}
</p> height={483}
<div className="p-5 mx-auto flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100"> alt="Linkwarden"
<div className="my-5 text-center"> className="h-12 w-fit"
<p className="text-3xl font-bold text-sky-500">Password reset</p> />
<div className="text-center sm:text-right">
<p className="text-3xl font-bold text-sky-500">Password Reset</p>
</div>
</div> </div>
<p className="text-sm text-sky-500 w-fit font-semibold">Email</p> <div>
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">Email</p>
<input <input
type="text" type="text"
placeholder="johnny@example.com" placeholder="johnny@example.com"
value={form.email} value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })} onChange={(e) => setForm({ ...form, email: e.target.value })}
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />
<p className="text-md text-gray-500 mt-1">
<p className="text-md text-gray-500"> Make sure to change your password in the profile settings
Make sure to change your password in the profile settings afterwards. afterwards.
</p> </p>
</div>
<SubmitButton <SubmitButton
onClick={loginUser} onClick={loginUser}
@ -75,12 +75,12 @@ export default function Forgot() {
className="mt-2 w-full text-center" className="mt-2 w-full text-center"
loading={submitLoader} loading={submitLoader}
/> />
</div> <div className="flex items-baseline gap-1 justify-center">
<div className="flex items-baseline gap-1 justify-center my-3">
<Link href={"/login"} className="block text-sky-500 font-bold"> <Link href={"/login"} className="block text-sky-500 font-bold">
Go back Go back
</Link> </Link>
</div> </div>
</div>
</> </>
); );
} }

View File

@ -1,5 +1,6 @@
import SubmitButton from "@/components/SubmitButton"; import SubmitButton from "@/components/SubmitButton";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react"; import { useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
@ -45,20 +46,27 @@ export default function Login() {
return ( return (
<> <>
<p className="text-xl font-bold text-center text-sky-500 mt-10 mb-3"> <div className="p-5 my-10 mx-auto flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100">
Linkwarden <div className="text-right flex flex-col gap-2 sm:flex-row justify-between items-center mb-5">
</p> <Image
<div className="p-5 mx-auto flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100"> src="/linkwarden.png"
<div className="my-5 text-center"> width={1694}
height={483}
alt="Linkwarden"
className="h-12 w-fit"
/>
<div className="text-center sm:text-right">
<p className="text-3xl font-bold text-sky-500">Welcome back</p> <p className="text-3xl font-bold text-sky-500">Welcome back</p>
<p className="text-md font-semibold text-sky-400"> <p className="text-md font-semibold text-sky-400">
Sign in to your account Sign in to your account
</p> </p>
</div> </div>
</div>
<p className="text-sm text-sky-500 w-fit font-semibold"> <div>
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
Username Username
{EmailProvider ? " or Email" : undefined} {EmailProvider ? "/Email" : undefined}
</p> </p>
<input <input
@ -66,39 +74,44 @@ export default function Login() {
placeholder="johnny" placeholder="johnny"
value={form.username} value={form.username}
onChange={(e) => setForm({ ...form, username: e.target.value })} onChange={(e) => setForm({ ...form, username: e.target.value })}
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />
</div>
<p className="text-sm text-sky-500 w-fit font-semibold">Password</p> <div>
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
Password
</p>
<input <input
type="password" type="password"
placeholder="*****************" placeholder="***********"
value={form.password} value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })} onChange={(e) => setForm({ ...form, password: e.target.value })}
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />
{EmailProvider && (
<div className="w-fit ml-auto mt-1">
<Link href={"/forgot"} className="text-gray-500 font-semibold">
Forgot Password?
</Link>
</div>
)}
</div>
<SubmitButton <SubmitButton
onClick={loginUser} onClick={loginUser}
label="Login" label="Login"
className="mt-2 w-full text-center" className="mt-2 w-full text-center"
loading={submitLoader} loading={submitLoader}
/> />
</div> <div className="flex items-baseline gap-1 justify-center">
<div className="flex items-baseline gap-1 justify-center my-3">
<p className="w-fit text-gray-500">New here?</p> <p className="w-fit text-gray-500">New here?</p>
<Link href={"/register"} className="block text-sky-500 font-bold"> <Link href={"/register"} className="block text-sky-500 font-bold">
Sign Up Sign Up
</Link> </Link>
</div> </div>
{EmailProvider && (
<div className="flex items-baseline gap-1 justify-center mb-3">
<p className="w-fit text-gray-500">Forgot your password?</p>
<Link href={"/forgot"} className="block text-sky-500 font-bold">
Send login link
</Link>
</div> </div>
)}
</> </>
); );
} }

View File

@ -63,9 +63,9 @@ export default function PublicCollections() {
})} })}
</div> </div>
<p className="text-center font-bold text-gray-500"> {/* <p className="text-center font-bold text-gray-500">
List created with <span className="text-sky-500">Linkwarden.</span> List created with <span className="text-sky-500">Linkwarden.</span>
</p> </p> */}
</div> </div>
) : ( ) : (
<></> <></>

View File

@ -3,7 +3,7 @@ import { useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import SubmitButton from "@/components/SubmitButton"; import SubmitButton from "@/components/SubmitButton";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import EmailConfirmaion from "@/components/Modal/EmailConfirmaion"; import Image from "next/image";
const EmailProvider = process.env.NEXT_PUBLIC_EMAIL_PROVIDER; const EmailProvider = process.env.NEXT_PUBLIC_EMAIL_PROVIDER;
@ -17,7 +17,6 @@ type FormData = {
export default function Register() { export default function Register() {
const [submitLoader, setSubmitLoader] = useState(false); const [submitLoader, setSubmitLoader] = useState(false);
const [showConfirmation, setShowConfirmation] = useState(false);
const [form, setForm] = useState<FormData>({ const [form, setForm] = useState<FormData>({
name: "", name: "",
@ -50,7 +49,7 @@ export default function Register() {
const sendConfirmation = async () => { const sendConfirmation = async () => {
await signIn("email", { await signIn("email", {
email: form.email, email: form.email,
redirect: false, callbackUrl: "/",
}); });
}; };
@ -76,10 +75,7 @@ export default function Register() {
setSubmitLoader(false); setSubmitLoader(false);
if (response.ok) { if (response.ok) {
if (form.email) { if (form.email) await sendConfirmation();
await sendConfirmation();
setShowConfirmation(true);
}
toast.success( toast.success(
EmailProvider EmailProvider
@ -99,90 +95,111 @@ export default function Register() {
return ( return (
<> <>
{showConfirmation && form.email ? ( <div className="p-5 mx-auto my-10 flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100">
<EmailConfirmaion email={form.email} /> <div className="flex flex-col gap-2 sm:flex-row justify-between items-center mb-5">
) : undefined} <Image
<p className="text-xl font-bold text-center my-10 mb-3 text-sky-500"> src="/linkwarden.png"
Linkwarden width={1694}
</p> height={483}
<div className="p-5 mx-auto flex flex-col gap-3 justify-between sm:w-[28rem] w-80 bg-slate-50 rounded-md border border-sky-100"> alt="Linkwarden"
<div className="my-5 text-center"> className="h-12 w-fit"
/>
<div className="text-center sm:text-right">
<p className="text-3xl font-bold text-sky-500">Get started</p> <p className="text-3xl font-bold text-sky-500">Get started</p>
<p className="text-md font-semibold text-sky-400"> <p className="text-md font-semibold text-sky-400">
Create a new account Create a new account
</p> </p>
</div> </div>
</div>
<p className="text-sm text-sky-500 w-fit font-semibold">Display Name</p> <div>
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
Display Name
</p>
<input <input
type="text" type="text"
placeholder="Johnny" placeholder="Johnny"
value={form.name} value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })} onChange={(e) => setForm({ ...form, name: e.target.value })}
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />
</div>
<p className="text-sm text-sky-500 w-fit font-semibold">Username</p> <div>
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
Username
</p>
<input <input
type="text" type="text"
placeholder="john" placeholder="john"
value={form.username} value={form.username}
onChange={(e) => setForm({ ...form, username: e.target.value })} onChange={(e) => setForm({ ...form, username: e.target.value })}
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />
</div>
{EmailProvider ? ( {EmailProvider ? (
<> <div>
<p className="text-sm text-sky-500 w-fit font-semibold">Email</p> <p className="text-sm text-sky-500 w-fit font-semibold mb-1">
Email
</p>
<input <input
type="email" type="email"
placeholder="johnny@example.com" placeholder="johnny@example.com"
value={form.email} value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })} onChange={(e) => setForm({ ...form, email: e.target.value })}
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />
</> </div>
) : undefined} ) : undefined}
<p className="text-sm text-sky-500 w-fit font-semibold">Password</p> <div className="flex item-center gap-5">
<div>
<input <p className="text-sm text-sky-500 w-fit font-semibold mb-1">
type="password" Password
placeholder="*****************"
value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })}
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/>
<p className="text-sm text-sky-500 w-fit font-semibold">
Re-enter Password
</p> </p>
<input <input
type="password" type="password"
placeholder="*****************" placeholder="***********"
value={form.password}
onChange={(e) => setForm({ ...form, password: e.target.value })}
className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/>
</div>
<div>
<p className="text-sm text-sky-500 w-fit font-semibold mb-1">
Confirm Password
</p>
<input
type="password"
placeholder="***********"
value={form.passwordConfirmation} value={form.passwordConfirmation}
onChange={(e) => onChange={(e) =>
setForm({ ...form, passwordConfirmation: e.target.value }) setForm({ ...form, passwordConfirmation: e.target.value })
} }
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100" className="w-full rounded-md p-2 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
/> />
</div>
</div>
<SubmitButton <SubmitButton
onClick={registerUser} onClick={registerUser}
label="Sign Up" label="Sign Up"
className="mt-2 w-full text-center" className="mt-2 w-full text-center"
loading={submitLoader} loading={submitLoader}
/> />
</div> <div className="flex items-baseline gap-1 justify-center">
<div className="flex items-baseline gap-1 justify-center my-3">
<p className="w-fit text-gray-500">Already have an account?</p> <p className="w-fit text-gray-500">Already have an account?</p>
<Link href={"/login"} className="block w-min text-sky-500 font-bold"> <Link href={"/login"} className="block text-sky-500 font-bold">
Login Login
</Link> </Link>
</div> </div>
</div>
</> </>
); );
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 645 B

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 190 KiB

BIN
public/linkwarden.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,19 +1 @@
{ {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}