Added logo and frontend part of login and signup modals
This commit is contained in:
parent
2bbe65ce3e
commit
7255e22315
@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@headlessui/react": "^1.7.2",
|
||||||
"@heroicons/react": "^2.0.11",
|
"@heroicons/react": "^2.0.11",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
13
client/pnpm-lock.yaml
generated
13
client/pnpm-lock.yaml
generated
@ -1,6 +1,7 @@
|
|||||||
lockfileVersion: 5.4
|
lockfileVersion: 5.4
|
||||||
|
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@headlessui/react': ^1.7.2
|
||||||
'@heroicons/react': ^2.0.11
|
'@heroicons/react': ^2.0.11
|
||||||
'@types/react': ^18.0.17
|
'@types/react': ^18.0.17
|
||||||
'@types/react-dom': ^18.0.6
|
'@types/react-dom': ^18.0.6
|
||||||
@ -18,6 +19,7 @@ specifiers:
|
|||||||
vite: ^3.1.0
|
vite: ^3.1.0
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@headlessui/react': 1.7.2_biqbaboplfbrettd7655fr4n2y
|
||||||
'@heroicons/react': 2.0.11_react@18.2.0
|
'@heroicons/react': 2.0.11_react@18.2.0
|
||||||
clsx: 1.2.1
|
clsx: 1.2.1
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
@ -323,6 +325,17 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/@headlessui/react/1.7.2_biqbaboplfbrettd7655fr4n2y:
|
||||||
|
resolution: {integrity: sha512-snLv2lxwsf2HNTOBNgHYdvoYZ3ChJE8QszPi1d/hl9js8KrFrUulTaQBfSyPbJP5BybVreWh9DxCgz9S0Z6hKQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16 || ^17 || ^18
|
||||||
|
react-dom: ^16 || ^17 || ^18
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@heroicons/react/2.0.11_react@18.2.0:
|
/@heroicons/react/2.0.11_react@18.2.0:
|
||||||
resolution: {integrity: sha512-bASjOgSSaYj8HqXWsOqaBiB6ZLalE/g90WYGgZ5lPm4KCCG7wPXntY4kzHf5NrLh6UBAcnPwvbiw1Ne9GYfJtw==}
|
resolution: {integrity: sha512-bASjOgSSaYj8HqXWsOqaBiB6ZLalE/g90WYGgZ5lPm4KCCG7wPXntY4kzHf5NrLh6UBAcnPwvbiw1Ne9GYfJtw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
BIN
client/src/assets/images/logo.png
Normal file
BIN
client/src/assets/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
22
client/src/components/FormField.tsx
Normal file
22
client/src/components/FormField.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import Input from "./Input";
|
||||||
|
|
||||||
|
interface FormFieldProps extends React.ComponentPropsWithoutRef<"input"> {
|
||||||
|
label: string;
|
||||||
|
bottomElement?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FormField = ({ label, bottomElement, ...inputProps }: FormFieldProps) => {
|
||||||
|
return (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<label htmlFor={inputProps.id} className="font-semibold text-sm">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<br />
|
||||||
|
<Input {...inputProps} />
|
||||||
|
{bottomElement}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FormField;
|
38
client/src/components/InlineLink.tsx
Normal file
38
client/src/components/InlineLink.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
|
||||||
|
export interface InlineLinkProps
|
||||||
|
extends React.ComponentPropsWithoutRef<"span"> {
|
||||||
|
to: string;
|
||||||
|
external?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InlineLink: FC<InlineLinkProps> = ({
|
||||||
|
to,
|
||||||
|
external,
|
||||||
|
className,
|
||||||
|
...rest
|
||||||
|
}) => {
|
||||||
|
if (external === true) {
|
||||||
|
return (
|
||||||
|
<a href={to}>
|
||||||
|
<span
|
||||||
|
className={clsx("text-violet-400 cursor-pointer text-sm", className)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink to={to}>
|
||||||
|
<span
|
||||||
|
className={clsx("text-violet-400 cursor-pointer text-sm", className)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InlineLink;
|
129
client/src/components/LoginModal.tsx
Normal file
129
client/src/components/LoginModal.tsx
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import React, { FC } from "react";
|
||||||
|
import { Dialog } from "@headlessui/react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { Tab } from "@headlessui/react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import FormField from "./FormField";
|
||||||
|
import Button from "./Button";
|
||||||
|
import InlineLink from "./InlineLink";
|
||||||
|
import logo from "../assets/images/logo.png";
|
||||||
|
|
||||||
|
export interface LoginModelProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => any;
|
||||||
|
defaultPage?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginModal: FC<LoginModelProps> = ({ defaultPage, isOpen, onClose }) => {
|
||||||
|
return createPortal(
|
||||||
|
<Dialog open={isOpen} onClose={onClose} className="relative z-50">
|
||||||
|
<div className="bg-black/80 fixed inset-0 flex items-center justify-center">
|
||||||
|
<Dialog.Panel className="bg-zinc-900 text-gray-100 w-[420px] rounded-md py-12 px-6">
|
||||||
|
<div className="flex flex-row items-center justify-center">
|
||||||
|
<Dialog.Title className="text-xl">
|
||||||
|
<img src={logo} className="inline w-12 h-12" /> Log in to
|
||||||
|
twitch-clone
|
||||||
|
</Dialog.Title>
|
||||||
|
</div>
|
||||||
|
<Tab.Group defaultIndex={defaultPage}>
|
||||||
|
<Tab.List className="space-x-4 border-b border-b-neutral-100/40 mt-4">
|
||||||
|
<Tab>
|
||||||
|
{({ selected }) => (
|
||||||
|
<LoginModalTab selected={selected}>Log In</LoginModalTab>
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
<Tab>
|
||||||
|
{({ selected }) => (
|
||||||
|
<LoginModalTab selected={selected}>Sign Up</LoginModalTab>
|
||||||
|
)}
|
||||||
|
</Tab>
|
||||||
|
</Tab.List>
|
||||||
|
<Tab.Panels className="mt-4">
|
||||||
|
<Tab.Panel className="space-y-4">
|
||||||
|
<FormField
|
||||||
|
id="login-username"
|
||||||
|
label="Username"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="login-password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
bottomElement={
|
||||||
|
<InlineLink to="#" className="block mt-2">
|
||||||
|
Trouble logging in?
|
||||||
|
</InlineLink>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Button className="bg-violet-500 w-full font-semibold py-2 text-sm">
|
||||||
|
Log In
|
||||||
|
</Button>
|
||||||
|
</Tab.Panel>
|
||||||
|
<Tab.Panel className="space-y-4">
|
||||||
|
<p className="text-sm">
|
||||||
|
Creating an account allows you to participate in chat, follow
|
||||||
|
your favorite channels, and broadcast from your own channel.
|
||||||
|
</p>
|
||||||
|
<FormField
|
||||||
|
id="signup-username"
|
||||||
|
label="Username"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="signup-password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="signup-confirm-password"
|
||||||
|
label="Confirm Password"
|
||||||
|
type="password"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
id="signup-email"
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
className="py-2 px-2 outline-2 w-full"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-center">
|
||||||
|
By clicking Sign Up, you are agreeing to twitch-clone's{" "}
|
||||||
|
<InlineLink to="https://tosdr.org/en/service/200" external>
|
||||||
|
Terms of Service
|
||||||
|
</InlineLink>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<Button className="bg-violet-500 w-full font-semibold py-2 text-sm">
|
||||||
|
Sign Up
|
||||||
|
</Button>
|
||||||
|
</Tab.Panel>
|
||||||
|
</Tab.Panels>
|
||||||
|
</Tab.Group>
|
||||||
|
</Dialog.Panel>
|
||||||
|
</div>
|
||||||
|
</Dialog>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface LoginModalTabProps extends React.ComponentPropsWithoutRef<"p"> {
|
||||||
|
selected: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginModalTab: FC<LoginModalTabProps> = ({ selected, ...rest }) => {
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className={clsx(
|
||||||
|
"font-semibold p-1",
|
||||||
|
selected && "text-violet-400 border-b-2 border-b-violet-400"
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginModal;
|
@ -1,16 +1,31 @@
|
|||||||
import { TvIcon, UserIcon } from "@heroicons/react/24/outline";
|
import { TvIcon, UserIcon } from "@heroicons/react/24/outline";
|
||||||
import { FC } from "react";
|
import { FC, useState } from "react";
|
||||||
import Button from "./Button";
|
import Button from "./Button";
|
||||||
import Input from "./Input";
|
import Input from "./Input";
|
||||||
|
import LoginModal from "./LoginModal";
|
||||||
|
import logo from "../assets/images/logo.png";
|
||||||
|
|
||||||
const NavBar: FC = () => {
|
const NavBar: FC = () => {
|
||||||
|
const [showLogin, setShowLogin] = useState(false);
|
||||||
|
const [showTab, setShowTab] = useState(0);
|
||||||
|
|
||||||
|
const showLoginTab = () => {
|
||||||
|
setShowTab(0);
|
||||||
|
setShowLogin(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showSignupTab = () => {
|
||||||
|
setShowTab(1);
|
||||||
|
setShowLogin(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="bg-zinc-800 w-screen font-semibold">
|
<nav className="bg-zinc-800 w-screen font-semibold">
|
||||||
<div className="flex flex-row justify-between items-center mx-2">
|
<div className="flex flex-row justify-between items-center mx-2">
|
||||||
<div className="basis-1/4">
|
<div className="basis-1/4">
|
||||||
<ul className="flex flex-row space-x-9 items-center">
|
<ul className="flex flex-row space-x-8 items-center">
|
||||||
<li>
|
<li>
|
||||||
<TvIcon className="w-6 h-6" />
|
<img src={logo} className="w-8 h-8" />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p className="text-lg">Browse</p>
|
<p className="text-lg">Browse</p>
|
||||||
@ -25,12 +40,18 @@ const NavBar: FC = () => {
|
|||||||
<div className="basis-1/4">
|
<div className="basis-1/4">
|
||||||
<ul className="justify-end flex flex-row space-x-3 items-center">
|
<ul className="justify-end flex flex-row space-x-3 items-center">
|
||||||
<li>
|
<li>
|
||||||
<Button className="text-sm px-3 py-2 bg-neutral-700">
|
<Button
|
||||||
|
className="text-sm px-3 py-2 bg-neutral-700"
|
||||||
|
onClick={showLoginTab}
|
||||||
|
>
|
||||||
Log In
|
Log In
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Button className="text-sm px-3 py-2 bg-violet-500">
|
<Button
|
||||||
|
className="text-sm px-3 py-2 bg-violet-500"
|
||||||
|
onClick={showSignupTab}
|
||||||
|
>
|
||||||
Sign Up
|
Sign Up
|
||||||
</Button>
|
</Button>
|
||||||
</li>
|
</li>
|
||||||
@ -42,6 +63,11 @@ const NavBar: FC = () => {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<LoginModal
|
||||||
|
isOpen={showLogin}
|
||||||
|
defaultPage={showTab}
|
||||||
|
onClose={() => setShowLogin(false)}
|
||||||
|
/>
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user