feat: update login/signup page

This commit is contained in:
AykutSarac 2023-10-03 20:35:20 +03:00
parent 8af1bef216
commit 0091a25efb
No known key found for this signature in database
9 changed files with 314 additions and 280 deletions

View File

@ -28,8 +28,8 @@ server {
try_files $uri /pricing.html;
}
location /reset-password {
try_files $uri /reset-password.html;
location /forgot-password {
try_files $uri /forgot-password.html;
}
location /sign-in {

View File

@ -16,7 +16,6 @@
"@emotion/react": "^11.11.1",
"@emotion/server": "^11.11.0",
"@mantine/core": "^6.0.17",
"@mantine/form": "^6.0.17",
"@mantine/hooks": "^6.0.17",
"@mantine/next": "^6.0.21",
"@mantine/prism": "^6.0.21",

View File

@ -6,5 +6,5 @@ Allow: /
# Block all crawlers for /accounts
User-agent: *
Disallow: /*?*
Disallow: /reset-password
Disallow: /forgot-password
Disallow: /widget

View File

@ -94,7 +94,7 @@ export const Navbar = () => {
radius="md"
className="hide-mobile"
>
Sign In
Login
</Button>
)}
<Button component={Link} href="/editor" prefetch={false} color="pink" radius="md">

View File

@ -0,0 +1,73 @@
import React from "react";
import Head from "next/head";
import Link from "next/link";
import { Button, Group, Paper, Stack, TextInput, Text, Anchor } from "@mantine/core";
import { toast } from "react-hot-toast";
import { Footer } from "src/layout/Footer";
import { Navbar } from "src/layout/Navbar";
import { supabase } from "src/lib/api/supabase";
const ForgotPassword = () => {
const [loading, setLoading] = React.useState(false);
const [email, setEmail] = React.useState("");
const [success, setSuccess] = React.useState(false);
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
supabase.auth
.resetPasswordForEmail(email)
.then(({ error }) => {
if (error) return toast.error(error.message);
setSuccess(true);
})
.finally(() => setLoading(false));
};
return (
<div>
<Head>
<title>Reset Password - JSON Crack</title>
<meta name="robots" content="noindex,nofollow" />
</Head>
<Navbar />
<Paper mx="auto" mt={70} maw={400} p="lg" withBorder>
<Text size="lg" weight={500}>
Reset Password
</Text>
<Paper pt="lg">
{success ? (
<Text>We&apos;ve sent an email to you, please check your inbox.</Text>
) : (
<form onSubmit={onSubmit}>
<Stack>
<TextInput
value={email}
onChange={e => setEmail(e.target.value)}
required
label="Email"
placeholder="hello@herowand.com"
radius="sm"
/>
</Stack>
<Group position="apart" mt="xl">
<Button color="dark" type="submit" radius="sm" loading={loading} fullWidth>
Reset Password
</Button>
</Group>
<Stack mt="lg" align="center">
<Anchor component={Link} prefetch={false} href="/sign-in" color="dark" size="xs">
Don&apos;t have an account? Sign Up
</Anchor>
</Stack>
</form>
)}
</Paper>
</Paper>
<Footer />
</div>
);
};
export default ForgotPassword;

View File

@ -1,83 +0,0 @@
import React from "react";
import Head from "next/head";
import Link from "next/link";
import { PaperProps, Button, Group, Paper, Stack, TextInput, Text, Anchor } from "@mantine/core";
import { toast } from "react-hot-toast";
import { Footer } from "src/layout/Footer";
import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
import { Navbar } from "src/layout/Navbar";
import { supabase } from "src/lib/api/supabase";
export function AuthenticationForm(props: PaperProps) {
const [loading, setLoading] = React.useState(false);
const [email, setEmail] = React.useState("");
const [success, setSuccess] = React.useState(false);
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
supabase.auth
.resetPasswordForEmail(email)
.then(({ error }) => {
if (error) return toast.error(error.message);
setSuccess(true);
})
.finally(() => setLoading(false));
};
return (
<Paper pt="lg" {...props}>
<Text size="lg" weight={500}>
Reset Password
</Text>
{success ? (
<Text>We&apos;ve sent an email to you, please check your inbox.</Text>
) : (
<form onSubmit={onSubmit}>
<Stack>
<TextInput
value={email}
onChange={e => setEmail(e.target.value)}
required
label="Email"
placeholder="hello@herowand.com"
size="md"
radius="sm"
/>
</Stack>
<Group position="apart" mt="xl">
<Button type="submit" radius="sm" size="md" loading={loading} fullWidth>
Reset Password
</Button>
</Group>
<Stack mt="lg" align="center">
<Anchor component={Link} prefetch={false} href="/sign-in" color="dark" size="xs">
Don&apos;t have an account? Sign Up
</Anchor>
</Stack>
</form>
)}
</Paper>
);
}
const ResetPassword = () => {
return (
<div>
<Head>
<title>Reset Password - JSON Crack</title>
<meta name="robots" content="noindex,nofollow" />
</Head>
<Navbar />
<Paper mx="auto" mt={70} maw={400} p="lg" withBorder>
<JSONCrackLogo />
<AuthenticationForm />
</Paper>
<Footer />
</div>
);
};
export default ResetPassword;

View File

@ -13,15 +13,12 @@ import {
Divider,
Anchor,
Stack,
Alert,
Center,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { useToggle, upperFirst } from "@mantine/hooks";
import { useSession } from "@supabase/auth-helpers-react";
import { toast } from "react-hot-toast";
import { AiOutlineGithub, AiOutlineGoogle } from "react-icons/ai";
import { Footer } from "src/layout/Footer";
import { JSONCrackLogo } from "src/layout/JsonCrackLogo";
import { Navbar } from "src/layout/Navbar";
import { supabase } from "src/lib/api/supabase";
import useUser from "src/store/useUser";
@ -29,59 +26,26 @@ import useUser from "src/store/useUser";
export function AuthenticationForm(props: PaperProps) {
const setSession = useUser(state => state.setSession);
const [loading, setLoading] = React.useState(false);
const [type, toggle] = useToggle<"login" | "register">(["login", "register"]);
const [done, setDone] = React.useState(false);
const form = useForm({
initialValues: {
email: "",
name: "",
password: "",
passwordAgain: "",
},
validate: {
email: val => (/^\S+@\S+$/.test(val) ? null : "Invalid email"),
password: val => (val.length <= 6 ? "Password should include at least 6 characters" : null),
passwordAgain: (val, { password }) =>
type === "register" && val !== password ? "Passwords doesn't match" : null,
},
const [userData, setUserData] = React.useState({
name: "",
email: "",
password: "",
});
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const validate = form.validate();
if (validate.hasErrors) return;
setLoading(true);
if (type === "login") {
supabase.auth
.signInWithPassword({
email: form.values.email,
password: form.values.password,
})
.then(({ data, error }) => {
if (error) return toast.error(error.message);
setSession(data.session);
})
.finally(() => setLoading(false));
} else {
supabase.auth
.signUp({
email: form.values.email,
password: form.values.password,
options: {
data: { name: form.values.name },
},
})
.then(({ error }) => {
if (error) return toast.error(error.message);
toast.success("Please check your inbox to confirm mail address!", { duration: 7000 });
setDone(true);
})
.finally(() => setLoading(false));
}
supabase.auth
.signInWithPassword({
email: userData.email,
password: userData.password,
})
.then(({ data, error }) => {
if (error) return toast.error(error.message);
setSession(data.session);
})
.finally(() => setLoading(false));
};
const handleLoginClick = (provider: "github" | "google") => {
@ -91,128 +55,68 @@ export function AuthenticationForm(props: PaperProps) {
});
};
if (done) {
return (
<Paper mih={100}>
<Text align="center" mt="lg">
Registration successul!
<br />
Please check your inbox for email confirmation.
</Text>
<Button radius="sm" size="md" mt="lg" onClick={() => window.location.reload()} fullWidth>
Back to login
</Button>
</Paper>
);
}
return (
<Paper {...props}>
<form onSubmit={onSubmit}>
<Stack>
<TextInput
required
label="Email"
placeholder="hello@jsoncrack.com"
value={userData.email}
onChange={event => setUserData(d => ({ ...d, email: event.target.value }))}
radius="md"
/>
<PasswordInput
required
label="Password"
placeholder=""
value={userData.password}
onChange={event => setUserData(d => ({ ...d, password: event.target.value }))}
radius="md"
/>
<Button color="dark" type="submit" radius="md" loading={loading}>
Sign in
</Button>
<Stack spacing="sm" mx="auto" align="center">
<Anchor
component={Link}
prefetch={false}
href="/forgot-password"
color="dark"
size="xs"
>
Forgot your password?
</Anchor>
</Stack>
</Stack>
</form>
<Divider my="lg" />
<Stack mb="md" mt="md">
<Button
radius="sm"
size="md"
radius="md"
leftIcon={<AiOutlineGoogle size="20" />}
onClick={() => handleLoginClick("google")}
color="red"
variant="outline"
>
Sign In with Google
Sign in with Google
</Button>
<Button
radius="sm"
size="md"
radius="md"
leftIcon={<AiOutlineGithub size="20" />}
onClick={() => handleLoginClick("github")}
color="dark"
variant="outline"
>
Sign In with GitHub
Sign in with GitHub
</Button>
</Stack>
<Divider label="Or continue with email" labelPosition="center" my="lg" />
<form onSubmit={onSubmit}>
<Stack>
{type === "register" && (
<TextInput
required
size="md"
label="Name"
placeholder="John Doe"
value={form.values.name}
onChange={event => form.setFieldValue("name", event.currentTarget.value)}
error={form.errors.name && "This field cannot be left blank"}
radius="sm"
/>
)}
<TextInput
required
label="Email address"
size="md"
placeholder="hello@jsoncrack.com"
value={form.values.email}
onChange={event => form.setFieldValue("email", event.currentTarget.value)}
error={form.errors.email && "Invalid email"}
radius="sm"
/>
<PasswordInput
required
label="Your Password"
size="md"
placeholder="*********"
value={form.values.password}
onChange={event => form.setFieldValue("password", event.currentTarget.value)}
error={form.errors.password && "Password should include at least 6 characters"}
radius="sm"
/>
{type === "register" && (
<PasswordInput
required
label="Validate Password"
placeholder="Your password"
value={form.values.passwordAgain}
onChange={event => form.setFieldValue("passwordAgain", event.currentTarget.value)}
error={form.errors.passwordAgain && "Passwords doesn't match"}
radius="sm"
size="md"
/>
)}
<Button type="submit" radius="sm" size="md" loading={loading}>
{upperFirst(type)}
</Button>
<Stack spacing="sm" mx="auto" align="center">
{type === "login" && (
<Anchor
component={Link}
prefetch={false}
href="/reset-password"
color="dark"
size="xs"
>
Forgot your password?
</Anchor>
)}
<Anchor
color="dark"
component="button"
type="button"
onClick={() => toggle()}
size="xs"
>
{type === "register"
? "Already have an account? Login"
: "Don't have an account? Sign Up"}
</Anchor>
</Stack>
</Stack>
</form>
</Paper>
);
}
@ -241,7 +145,7 @@ function ResetPassword(props: PaperProps) {
};
return (
<Paper radius="sm" {...props}>
<Paper radius="md" {...props}>
<Text size="lg" weight={500}>
Reset Password
</Text>
@ -253,14 +157,14 @@ function ResetPassword(props: PaperProps) {
onChange={e => setPassword(e.target.value)}
required
label="Password"
radius="sm"
radius="md"
/>
<PasswordInput
value={password2}
onChange={e => setPassword2(e.target.value)}
required
label="Validate Password"
radius="sm"
radius="md"
/>
</Stack>
@ -278,7 +182,6 @@ const SignIn = () => {
const { isReady, push, query } = useRouter();
const session = useSession();
const isPasswordReset = query?.type === "recovery" && !query?.error;
const [alertVisible, setAlertVisible] = React.useState(true);
React.useEffect(() => {
if (isReady && session && !isPasswordReset) {
@ -292,27 +195,15 @@ const SignIn = () => {
<title>Sign In - JSON Crack</title>
</Head>
<Navbar />
{alertVisible && (
<Alert
color="orange"
radius="md"
mt={30}
mx="auto"
w={700}
variant="outline"
withCloseButton
onClose={() => setAlertVisible(false)}
>
We have transitioned to a new database. If you&apos;ve been using JSON Crack for a while
and unable to login, kindly <strong>register a new account</strong> once more. (Rest
assured, your saved files remain intact.)
</Alert>
)}
<Paper shadow="md" mx="auto" maw={400} mt={50} p="lg" withBorder>
<JSONCrackLogo />
<Paper mt={50} shadow="xs" mx="auto" maw={400} p="lg" withBorder>
{isPasswordReset ? <ResetPassword /> : <AuthenticationForm />}
</Paper>
<Center my="xl">
<Anchor component={Link} prefetch={false} href="/sign-up" color="dark" fw="bold">
Don&apos;t have an account?
</Anchor>
</Center>
<Footer />
</div>
);

167
src/pages/sign-up.tsx Normal file
View File

@ -0,0 +1,167 @@
import React from "react";
import Head from "next/head";
import Link from "next/link";
import {
Anchor,
Button,
Center,
Divider,
Flex,
Paper,
PasswordInput,
Stack,
Text,
TextInput,
} from "@mantine/core";
import toast from "react-hot-toast";
import { AiOutlineGithub, AiOutlineGoogle } from "react-icons/ai";
import Layout from "src/layout/Layout";
import { supabase } from "src/lib/api/supabase";
const SignUp = () => {
const [loading, setLoading] = React.useState(false);
const [done, setDone] = React.useState(false);
const [userData, setUserData] = React.useState({
name: "",
email: "",
password: "",
});
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setLoading(true);
supabase.auth
.signUp({
email: userData.email,
password: userData.password,
options: {
data: { name: userData.name },
},
})
.then(({ error }) => {
if (error) return toast.error(error.message);
toast.success("Please check your inbox to confirm mail address!", { duration: 7000 });
setDone(true);
})
.finally(() => setLoading(false));
};
const handleLoginClick = (provider: "github" | "google") => {
supabase.auth.signInWithOAuth({
provider,
options: { redirectTo: "https://jsoncrack.com/editor" },
});
};
return (
<Layout>
<Head>
<title>JSON Crack | Sign Up</title>
</Head>
{done ? (
<Paper shadow="xs" mx="auto" maw={400} mt={50} p="lg" withBorder>
<Text align="center" mt="lg">
Registration successul!
<br />
Please check your inbox for email confirmation.
</Text>
<Anchor component={Link} href="/sign-in" underline={false}>
<Button color="dark" radius="md" mt="lg" fullWidth>
Back to login
</Button>
</Anchor>
</Paper>
) : (
<>
<Paper shadow="xs" mx="auto" maw={400} mt={50} p="lg" withBorder>
<form onSubmit={onSubmit}>
<Stack>
<TextInput
onChange={e => setUserData(d => ({ ...d, name: e.target.value }))}
required
label="Name"
placeholder="John Doe"
radius="md"
/>
<TextInput
onChange={e => setUserData(d => ({ ...d, email: e.target.value }))}
type="email"
required
label="Email"
placeholder="hello@jsoncrack.com"
radius="md"
/>
<PasswordInput
onChange={e => setUserData(d => ({ ...d, password: e.target.value }))}
min={6}
required
label="Password"
placeholder=""
radius="md"
/>
<Button color="dark" type="submit" loading={loading}>
Sign up for free
</Button>
<Divider label="OR CONTINUE WITH" labelPosition="center" />
<Flex gap="sm">
<Button
radius="md"
fullWidth
leftIcon={<AiOutlineGoogle size="20" />}
onClick={() => handleLoginClick("google")}
color="red"
variant="outline"
>
Google
</Button>
<Button
radius="md"
leftIcon={<AiOutlineGithub size="20" />}
onClick={() => handleLoginClick("github")}
color="dark"
variant="outline"
fullWidth
loading={loading}
>
GitHub
</Button>
</Flex>
<Divider mx={-20} />
<Text fz="xs" c="gray">
By signing up, you agree to our{" "}
<Anchor component={Link} href="/legal/terms" c="gray" fw={500}>
Terms of Service
</Anchor>{" "}
and{" "}
<Anchor component={Link} href="/legal/privacy" c="gray" fw={500}>
Privacy Policy
</Anchor>
. Need help?{" "}
<Anchor component={Link} href="mailto:contact@jsoncrack.com" c="gray" fw={500}>
Get in touch.
</Anchor>
</Text>
</Stack>
</form>
</Paper>
<Center my="xl">
<Anchor component={Link} prefetch={false} href="/sign-in" color="dark" fw="bold">
Already have an account?
</Anchor>
</Center>
</>
)}
</Layout>
);
};
export default SignUp;

View File

@ -1362,14 +1362,6 @@
react-remove-scroll "^2.5.5"
react-textarea-autosize "8.3.4"
"@mantine/form@^6.0.17":
version "6.0.19"
resolved "https://registry.npmjs.org/@mantine/form/-/form-6.0.19.tgz"
integrity sha512-5SFLZEzaBH7yKIDSDt1r9UiN4y7RkFvu+7J7CFPIQM+nTdXeGnugVFM8rASuZI7/FSYty/XoPY+Yymq3xDX+MQ==
dependencies:
fast-deep-equal "^3.1.3"
klona "^2.0.5"
"@mantine/hooks@^6.0.17":
version "6.0.21"
resolved "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.21.tgz"
@ -4358,11 +4350,6 @@ kld-polynomial@^0.3.0:
resolved "https://registry.npmjs.org/kld-polynomial/-/kld-polynomial-0.3.0.tgz"
integrity sha512-PEfxjQ6tsxL9DHBIhM2UZsSes0GI+OIMjbE0kj60jr80Biq/xXl1eGfnyzmfoackAMdKZtw2060L09HdjkPP5w==
klona@^2.0.5:
version "2.0.6"
resolved "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz"
integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==
language-subtag-registry@~0.3.2:
version "0.3.22"
resolved "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz"