Skip to content

Commit

Permalink
Refactored verification flows and close
Browse files Browse the repository at this point in the history
  • Loading branch information
stefnnn committed Nov 10, 2020
1 parent 1ecc8dc commit 78605f5
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 188 deletions.
9 changes: 2 additions & 7 deletions components/CheckLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,12 @@ export default function CheckLogin({
// skip query if user is already defined
skip: user ? true : false,
onCompleted: (data) => {
setLoading(false);
setUser(data?.me); // could be undefined!
setLoading(false);
},
});
useEffect(() => {
let mounted = true;
// if we skip above query, because user is already loaded
if (user && mounted) setLoading(false);
return () => {
mounted = false;
};
if (user) setLoading(false);
}, [user]);

if (loading) {
Expand Down
7 changes: 6 additions & 1 deletion components/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import { Flex, Box, Heading, Text, Link as A } from "rebass";
import Head from "next/head";
import { useUser } from "state/user";
import { LoginForm } from "pages/user/login";
import { ReactNode } from "react";
import React, { ReactNode } from "react";
import CheckLogin from "./CheckLogin";
import Link from "next/link";
import { FlexProps } from "rebass";
import { Role } from "graphql/types";
import { Footer } from "components/Footer";
import { TopBar } from "./TopBar";
import IconClose from "../public/images/icon_close.svg";
import { Spinner } from "theme-ui";

export const Page: React.FC<{
children?: React.ReactNode;
Expand Down Expand Up @@ -170,6 +171,10 @@ export const Container: React.FC<FlexProps> = (props) => {
);
};

export const Loading: React.FC = () => (
<Spinner color="gray" size={20} mr={3} />
);

export const ErrorPage: React.FC = (props) => (
<Page heading="Fehler">
<Heading as="h2">Oh je, es ist ein Fehler aufgetreten</Heading>
Expand Down
5 changes: 3 additions & 2 deletions components/Schools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ShowField } from "./Users";
import { cantonNames } from "../util/cantons";
import { useState, ReactElement } from "react";
import { School } from "@prisma/client";
import { Loading } from "components/Page";
import {
useSchoolsWithMembersQuery,
useSetSchoolMutation,
Expand Down Expand Up @@ -49,7 +50,7 @@ export const Schools: React.FC = () => {
return <Text>Error loading data: {schoolsQuery.error.message}</Text>;
}
if (schoolsQuery.loading) {
return <Text>Loading data</Text>;
return <Loading />;
}
return (
<>
Expand Down Expand Up @@ -138,7 +139,7 @@ export const SelectSchool: React.FC = () => {
);
}
if (!schools) {
return <p>Loading...</p>;
return <Loading />;
}

const options = schools?.reduce(
Expand Down
2 changes: 1 addition & 1 deletion graphql/resolvers/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ export async function sendVerificationEmail(
process.env.NODE_ENV !== "production" ? process.env.NODE_ENV : ""
}`;
const token = await createVerificationToken(db, email);
const url = `${process.env.BASE_URL}user/login?t=${token}&p=${purpose}`;
const url = `${process.env.BASE_URL}user/verify?t=${token}&p=${purpose}`;
const subjects: Record<string, string> = {
verification: "voty: Bitte Email bestätigen",
reset: "voty: Passwort zurücksetzen?",
Expand Down
190 changes: 13 additions & 177 deletions pages/user/login.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
import { useRouter } from "next/router";
import { AppPage } from "components/Page";
import { gql, useMutation, useApolloClient } from "@apollo/client";
import { useState, useEffect, ReactElement } from "react";
import { Text, Box, Button, Heading, Flex } from "rebass";
import { Grid } from "theme-ui";
import { Label, Input } from "@rebass/forms";
import { gql, useMutation } from "@apollo/client";
import { useState, ReactElement } from "react";
import { Text, Button, Heading, Flex } from "rebass";
import { QForm, ErrorBox } from "components/Form";
import CheckLogin from "components/CheckLogin";
import { usePageEvent, trackEvent } from "util/stats";
import {
useSetAccessToken,
useUser,
useSetUser,
SessionUser,
} from "../../state/user";
import { useSetAccessToken, useUser, useSetUser } from "../../state/user";
import { useQueryParam } from "util/hooks";
import {
Role,
useLoginMutation,
useCheckVerificationMutation,
useEmailVerificationMutation,
} from "graphql/types";
import VerifyPage from "./verify";

export const LOGIN = gql`
mutation login($email: String!, $password: String!) {
Expand Down Expand Up @@ -81,12 +74,9 @@ export default function Login(): ReactElement {
}

// purpose: verification, reset, login
// this needs to stay in for a while, so that old links continue to work
if (token && purpose) {
return (
<AppPage heading="Anmelden" onClose={() => void router.push("/")}>
<CheckToken token={token} purpose={purpose} />
</AppPage>
);
return <VerifyPage />;
} else {
return (
<AppPage heading="Anmelden" onClose={() => void router.push("/")}>
Expand Down Expand Up @@ -184,7 +174,7 @@ export function LoginForm(): ReactElement {
);
}

function VerificationForm({ email }: { email: string }): ReactElement {
export function VerificationForm({ email }: { email: string }): ReactElement {
usePageEvent({ category: "Login", action: "NotVerified" });
const [mailSent, setMailSent] = useState(false);
const [error, setError] = useState("");
Expand Down Expand Up @@ -221,22 +211,17 @@ function VerificationForm({ email }: { email: string }): ReactElement {
);
}

function getStartpage(role?: string) {
let page = "";
export function getStartpage(role?: string): string {
switch (role) {
case Role.Teacher:
page = "/teacher";
break;
return "/teacher";
case Role.Student:
page = "/student";
break;
return "/student";
case Role.Admin:
page = "/admin";
break;
return "/admin";
default:
page = "/";
return "/";
}
return page;
}

function AfterLogin() {
Expand All @@ -259,79 +244,6 @@ function AfterLogin() {
}
}

function CheckToken({ token, purpose }: { token: string; purpose: string }) {
const setUser = useSetUser();
const setAccessToken = useSetAccessToken();
const [error, setError] = useState("");
const [tempUser, setTempUser] = useState<SessionUser | undefined | null>();
const router = useRouter();
const client = useApolloClient();
const [doVerification] = useCheckVerificationMutation({
onCompleted: (data) => {
if (data.checkVerification && data.checkVerification.token) {
setTempUser(data.checkVerification.user);
setAccessToken(data.checkVerification.token);
}
},
onError(error) {
setError("Dieser Email-Link ist leider nicht mehr gültig!");
console.error(error.message);
},
});

useEffect(() => {
// first logout current user, as doVerification will auto-login user based on token
void client.clearStore();
setAccessToken("");
setUser(undefined);
void doVerification({ variables: { token } });
}, []);

const isTeacher = tempUser?.role === Role.Teacher;
// token verification succeded, we have a session & user
if (tempUser !== undefined) {
// login -> go straight back
if (purpose === "login") {
setUser(tempUser);
}
if (purpose === "verification") {
trackEvent({ category: "Login", action: "EmailVerified" });
return (
<Box>
<Text mb={4}>
Super, Deine Email-Adresse ist nun bestätigt.{" "}
{isTeacher
? "Dein Konto für Lehrpersonen ist nun eröffnet und Du bist bereits angemeldet."
: ""}
</Text>
<Button onClick={() => router.push(getStartpage(tempUser?.role))}>
{isTeacher
? "Weiter geht's zur Auswahl Deiner Schule"
: "Weiter geht's"}
</Button>
</Box>
);
}
if (purpose === "reset") {
return <PasswordResetForm />;
}
}

if (error) {
return (
<>
<Heading as="h2">Fehler</Heading>
<Text mb={4}>{error}</Text>
<Button as="a" href="/user/login">
zurück
</Button>
</>
);
}

return <Text>Überprüfen</Text>;
}

function RequestReset({ onCancel }: { email: string; onCancel: () => void }) {
const [mailSent, setMailSent] = useState(false);
const [error, setError] = useState("");
Expand Down Expand Up @@ -392,79 +304,3 @@ function RequestReset({ onCancel }: { email: string; onCancel: () => void }) {
</>
);
}

function PasswordResetForm() {
usePageEvent({ category: "Login", action: "PasswordRequest" });
const user = useUser();
const setUser = useSetUser();
const setAccessToken = useSetAccessToken();
const [password, setPassword] = useState("");
const [password2, setPassword2] = useState("");
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const router = useRouter();

const [doChangePassword] = useMutation(CHANGE_PASSWORD, {
onCompleted({ changePassword }) {
if (changePassword && changePassword.token) {
setUser(changePassword.user);
setAccessToken(changePassword.token);
}
setSuccess(true);
},
onError() {
setError("Es ist ein Fehler aufgetreten.");
},
});

async function checkPasswords(pw1: string, pw2: string) {
if (pw1 !== pw2) {
setError("Die beiden Passwörter stimmen nicht überein…");
}
return doChangePassword({ variables: { password } });
}
if (success) {
return (
<>
<Heading as="h2">Passwort geändert</Heading>
<Text mb={4}>Super, das hat geklappt.</Text>
<Button onClick={() => router.push(getStartpage(user?.role))}>
Weiter geht&apos;s
</Button>
</>
);
}
return (
<>
<Heading as="h2">Passwort ändern</Heading>
<Grid gap={2} columns={[0, 0, "2fr 3fr"]}>
<Label alignSelf="center">Neues Passwort:</Label>
<Input
autoCapitalize="none"
value={password}
name="password"
type="password"
onChange={(event: React.FormEvent<HTMLInputElement>) =>
setPassword(event.currentTarget.value)
}
/>
<Label alignSelf="center">Password wiederholen:</Label>
<Input
value={password2}
name="password2"
type="password"
onChange={(event: React.FormEvent<HTMLInputElement>) =>
setPassword2(event.currentTarget.value)
}
/>
<Button
onClick={() => checkPasswords(password, password2)}
sx={{ gridColumn: [0, 0, 2] }}
>
Passwort ändern
</Button>
<ErrorBox error={error} />
</Grid>
</>
);
}
Loading

0 comments on commit 78605f5

Please sign in to comment.