Skip to content

Commit b8f5ef0

Browse files
committed
Basic Implementation of Login/Registration UI.
1 parent 9ad2958 commit b8f5ef0

File tree

10 files changed

+533
-26585
lines changed

10 files changed

+533
-26585
lines changed

package-lock.json

Lines changed: 47 additions & 26575 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
"graphql-scalars": "1.7.0",
2222
"graphql-tag": "2.11.0",
2323
"react": "17.0.1",
24+
"react-animate-height": "^2.0.23",
2425
"react-dom": "17.0.1",
2526
"react-router-dom": "5.2.0",
2627
"react-scripts": "4.0.1",
28+
"react-transition-group": "^4.4.1",
2729
"typescript": "4.1.3",
2830
"web-vitals": "1.1.0"
2931
},
@@ -79,6 +81,7 @@
7981
"@tailwindcss/postcss7-compat": "2.0.2",
8082
"@types/classnames": "2.2.11",
8183
"@types/react-router-dom": "5.1.7",
84+
"@types/react-transition-group": "^4.4.0",
8285
"apollo": "2.32.1",
8386
"autoprefixer": "9.8.6",
8487
"concurrently": "5.3.0",

src/App.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1+
import { useState } from "react";
12
import { Home } from "./pages/Home";
3+
import { Login } from "./pages/Login";
24
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
35
import { ApolloProvider } from "@apollo/client";
46
import { client } from "./client";
57

68
function App() {
9+
const [loggedIn, setLoggedIn] = useState(false);
10+
711
return (
812
<ApolloProvider client={client}>
913
<Router>
1014
<Switch>
15+
<Route path="/login" exact>
16+
<Login />
17+
</Route>
1118
<Route path="/" exact>
1219
<Home />
1320
</Route>
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { FC, useEffect, useState } from "react";
2+
import { View } from "../../pages/Login";
3+
import { Transition } from "@headlessui/react";
4+
5+
interface LoginViewProps {
6+
changeView: React.Dispatch<React.SetStateAction<View>>;
7+
setNotificationShowing: React.Dispatch<React.SetStateAction<boolean>>;
8+
}
9+
10+
export const ForgotPasswordView: FC<LoginViewProps> = ({
11+
changeView,
12+
setNotificationShowing,
13+
}) => {
14+
return (
15+
<div className="space-y-6">
16+
<div>
17+
<label
18+
htmlFor="email"
19+
className="block text-sm font-medium text-gray-700"
20+
>
21+
Email address
22+
</label>
23+
<div className="mt-1">
24+
<input
25+
id="email"
26+
name="email"
27+
type="email"
28+
autoComplete="email"
29+
className="transition duration-500 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
30+
/>
31+
</div>
32+
</div>
33+
34+
<div>
35+
<button
36+
onClick={() => {
37+
changeView(View.Login);
38+
setNotificationShowing(true);
39+
}}
40+
className="transition w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-cyan-600 hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyans-500"
41+
>
42+
Reset Password
43+
</button>
44+
</div>
45+
46+
<div className="text-right">
47+
<div className="text-sm">
48+
<a
49+
onClick={() => changeView(View.Login)}
50+
className="font-medium text-cyan-600 hover:text-cyan-500 cursor-pointer"
51+
>
52+
Back to Login
53+
</a>
54+
</div>
55+
</div>
56+
</div>
57+
);
58+
};
59+
60+
interface ResetNotificationProps {
61+
showing: boolean;
62+
setShowing: React.Dispatch<React.SetStateAction<boolean>>;
63+
}
64+
65+
export const ResetNotification: FC<ResetNotificationProps> = ({
66+
showing,
67+
setShowing,
68+
}: ResetNotificationProps) => {
69+
useEffect(() => {
70+
if (showing) {
71+
setTimeout(() => {
72+
setShowing(false);
73+
}, 2500);
74+
}
75+
});
76+
77+
return (
78+
<Transition
79+
show={showing}
80+
enter="transition duration-500 ease-out"
81+
enterFrom="opacity-0"
82+
enterTo="opacity-100"
83+
leave="transition duration-500"
84+
leaveFrom="opacity-100"
85+
leaveTo="opacity-0"
86+
>
87+
<div className="fixed inset-0 flex items-end justify-center px-4 py-6 pointer-events-none sm:p-6 sm:items-start sm:justify-end">
88+
<div
89+
x-data="{ show: true }"
90+
x-show="show"
91+
x-description="Notification panel, show/hide based on alert state."
92+
className="max-w-sm w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden"
93+
>
94+
<div className="p-4">
95+
<div className="flex items-start">
96+
<div className="flex-shrink-0">
97+
<svg
98+
className="h-6 w-6 text-green-400"
99+
x-description="Heroicon name: check-circle"
100+
xmlns="http://www.w3.org/2000/svg"
101+
fill="none"
102+
viewBox="0 0 24 24"
103+
stroke="currentColor"
104+
aria-hidden="true"
105+
>
106+
<path
107+
stroke-linecap="round"
108+
stroke-linejoin="round"
109+
stroke-width="2"
110+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
111+
></path>
112+
</svg>
113+
</div>
114+
<div className="ml-3 w-0 flex-1 pt-0.5">
115+
<p className="ztext-sm font-medium text-gray-900">
116+
Reset Email Sent!
117+
</p>
118+
<p className="mt-1 text-sm text-gray-500">
119+
Please check your inbox to reset your password.
120+
</p>
121+
</div>
122+
<div className="ml-4 flex-shrink-0 flex">
123+
<button
124+
onClick={() => setShowing(false)}
125+
className="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
126+
>
127+
<span className="sr-only">Close</span>
128+
<svg
129+
className="h-5 w-5"
130+
x-description="Heroicon name: x"
131+
xmlns="http://www.w3.org/2000/svg"
132+
viewBox="0 0 20 20"
133+
fill="currentColor"
134+
aria-hidden="true"
135+
>
136+
<path
137+
fill-rule="evenodd"
138+
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
139+
clip-rule="evenodd"
140+
></path>
141+
</svg>
142+
</button>
143+
</div>
144+
</div>
145+
</div>
146+
</div>
147+
</div>
148+
</Transition>
149+
);
150+
};

src/components/Login/LoginView.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { FC, useState } from "react";
2+
import { Link } from "react-router-dom";
3+
4+
import { View } from "../../pages/Login";
5+
6+
interface LoginViewProps {
7+
changeView: React.Dispatch<React.SetStateAction<View>>;
8+
}
9+
10+
export const LoginView: FC<LoginViewProps> = ({ changeView }) => {
11+
const [email, setEmail] = useState("[email protected]");
12+
const [password, setPassword] = useState("supersecretpass");
13+
14+
return (
15+
<div className="space-y-6">
16+
<div>
17+
<label
18+
htmlFor="email"
19+
className="block text-sm font-medium text-gray-700"
20+
>
21+
Email address
22+
</label>
23+
<div className="mt-1">
24+
<input
25+
id="email"
26+
name="email"
27+
type="email"
28+
autoComplete="email"
29+
autoFocus={false}
30+
value={email}
31+
onChange={e => setEmail(e.target.value)}
32+
className="transition duration-500 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
33+
/>
34+
</div>
35+
</div>
36+
37+
<div>
38+
<label
39+
htmlFor="password"
40+
className="block text-sm font-medium text-gray-700"
41+
>
42+
Password
43+
</label>
44+
<div className="mt-1">
45+
<input
46+
id="password"
47+
name="password"
48+
type="password"
49+
autoComplete="current-password"
50+
value={password}
51+
onChange={e => setPassword(e.target.value)}
52+
className="transition duration-500 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
53+
/>
54+
</div>
55+
</div>
56+
57+
<div className="flex items-center justify-between">
58+
<div className="flex items-center">
59+
<input
60+
id="remember_me"
61+
name="remember_me"
62+
type="checkbox"
63+
className="h-4 w-4 text-cyan-600 focus:ring-cyan-500 border-gray-300 rounded"
64+
/>
65+
<label
66+
htmlFor="remember_me"
67+
className="ml-2 block text-sm text-gray-900"
68+
>
69+
Remember me
70+
</label>
71+
</div>
72+
73+
<div className="text-sm">
74+
<a
75+
onClick={() => changeView(View.ForgotPassword)}
76+
className="font-medium text-cyan-600 hover:text-cyan-500 cursor-pointer"
77+
>
78+
Forgot your password?
79+
</a>
80+
</div>
81+
</div>
82+
83+
<div>
84+
<Link to="/">
85+
<button className="transition w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-cyan-600 hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyans-500 disabled:opacity-50">
86+
Sign in
87+
</button>
88+
</Link>
89+
</div>
90+
</div>
91+
);
92+
};

src/components/Login/RegisterView.tsx

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { FC, useState } from "react";
2+
import { Link } from "react-router-dom";
3+
4+
import { View } from "../../pages/Login";
5+
6+
interface LoginViewProps {
7+
changeView: React.Dispatch<React.SetStateAction<View>>;
8+
}
9+
10+
export const RegisterView: FC<LoginViewProps> = ({ changeView }) => {
11+
const [email, setEmail] = useState("");
12+
const [name, setName] = useState("");
13+
const [password, setPassword] = useState("");
14+
15+
return (
16+
<div className="space-y-6">
17+
<div>
18+
<label
19+
htmlFor="name"
20+
className="block text-sm font-medium text-gray-700"
21+
>
22+
Full Name
23+
</label>
24+
<div className="mt-1">
25+
<input
26+
id="full-name"
27+
name="full-name"
28+
type="text"
29+
autoFocus={false}
30+
value={name}
31+
onChange={e => setName(e.target.value)}
32+
className="transition duration-500 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
33+
/>
34+
</div>
35+
</div>
36+
<div>
37+
<label
38+
htmlFor="email"
39+
className="block text-sm font-medium text-gray-700"
40+
>
41+
Email address
42+
</label>
43+
<div className="mt-1">
44+
<input
45+
id="email"
46+
name="email"
47+
type="email"
48+
autoFocus={false}
49+
value={email}
50+
onChange={e => setEmail(e.target.value)}
51+
className="transition duration-500 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
52+
/>
53+
</div>
54+
</div>
55+
<div>
56+
<label
57+
htmlFor="password"
58+
className="block text-sm font-medium text-gray-700"
59+
>
60+
Password
61+
</label>
62+
<div className="mt-1">
63+
<input
64+
id="password"
65+
name="password"
66+
type="password"
67+
autoComplete="current-password"
68+
value={password}
69+
onChange={e => setPassword(e.target.value)}
70+
className="transition duration-500 appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
71+
/>
72+
</div>
73+
</div>
74+
<div>
75+
<Link to="/">
76+
<button className="transition w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-cyan-600 hover:bg-cyan-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyans-500 disabled:opacity-50">
77+
Sign Up
78+
</button>
79+
</Link>
80+
</div>
81+
82+
<div className="text-right">
83+
<div className="text-sm">
84+
<a
85+
onClick={() => changeView(View.Login)}
86+
className="font-medium text-cyan-600 hover:text-cyan-500 cursor-pointer"
87+
>
88+
Back to Login
89+
</a>
90+
</div>
91+
</div>
92+
</div>
93+
);
94+
};

src/components/Login/styles.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.view-enter {
2+
opacity: 0;
3+
transform: scale(0.7);
4+
}
5+
.view-enter-active {
6+
opacity: 1;
7+
transform: translateX(0);
8+
transition: opacity 300ms, transform 300ms;
9+
transition-delay: 1000ms;
10+
}
11+
.view-exit {
12+
opacity: 1;
13+
}
14+
.view-exit-active {
15+
opacity: 0;
16+
transform: scale(0.9);
17+
transition: opacity 300ms, transform 300ms;
18+
}

0 commit comments

Comments
 (0)