Skip to content

Commit

Permalink
Add option to create new admin users
Browse files Browse the repository at this point in the history
  • Loading branch information
frankbille committed Sep 5, 2024
1 parent 037f38c commit 57fcd5a
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 2 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ jobs:
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
Expand Down
4 changes: 4 additions & 0 deletions public/i18n/da/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"Add JID Code": "Tilføj JID kode",
"Can't add your own location": "Kan ikke tilføje din egen lokation",
"Checking JID Code": "Checker JID kode",
"Email": "Email",
"Enter JID Code": "Indtast JID kode",
"Fewer JID codes than last year": "\uFE0E {{difference}} færre JID koder ({{percentage}}%)",
"Fewer unique JID codes than last year": "\uFE0E {{difference}} færre unikke JID koder ({{percentage}}%)",
Expand All @@ -20,7 +21,10 @@
"Name": "Navn",
"No country with the code": "Intet land med landekoden '{{code}}'",
"Participants": "Deltagere",
"Password": "Kodeord",
"Pin code": "Pinkode",
"Register": "Registrer",
"Repeat password": "Gentag kodeord",
"Same as last year": "Samme som sidste år",
"Show": "Vis",
"Total JID codes found": "Total antal JID kode",
Expand Down
4 changes: 4 additions & 0 deletions public/i18n/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"Add JID Code": "Add JID Code",
"Can't add your own location": "Can't add your own location",
"Checking JID Code": "Checking JID Code",
"Email": "Email",
"Enter JID Code": "Enter JID Code",
"Fewer JID codes than last year": "\uFE0E {{difference}} fewer JID codes ({{percentage}}%)",
"Fewer unique JID codes than last year": "\uFE0E {{difference}} fewer unique JID codes ({{percentage}}%)",
Expand All @@ -20,7 +21,10 @@
"Name": "Name",
"No country with the code": "No country with the code '{{code}}'",
"Participants": "Participants",
"Password": "Password",
"Pin code": "Pin code",
"Register": "Register",
"Repeat password": "Repeat password",
"Same as last year": "Same as last year",
"Show": "Show",
"Total JID codes found": "Total JID codes found",
Expand Down
2 changes: 2 additions & 0 deletions src/admin/AdminTopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export default function AdminTopNav(props: {
className="menu menu-sm dropdown-content mt-3 z-[1] p-2 drop-shadow-md bg-primary rounded-box w-52 text-primary-content font-bold">
{!adminTopNavFragment.authenticatedAdmin?.id &&
<li><Link to="/admin/login">{t("Login")}</Link></li>}
{!adminTopNavFragment.authenticatedAdmin?.id &&
<li><Link to="/admin/register">{t("Register")}</Link></li>}
{adminTopNavFragment.authenticatedAdmin?.id &&
<li><Link to="/admin">{t("Locations")}</Link></li>}
{adminTopNavFragment.authenticatedAdmin?.id &&
Expand Down
125 changes: 125 additions & 0 deletions src/admin/register/AdminRegister.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import {useMutation, useSuspenseQuery} from "@apollo/client";

import AdminTopNav from "../AdminTopNav.tsx";
import {AdminRegisterQuery, RegisterAdminMutation} from "./AdminRegisterQueries.tsx";
import {Form, Link} from "react-router-dom";
import {useState} from "react";
import {useTranslation} from "react-i18next";

export default function AdminRegister() {
const {t} = useTranslation()

const {data, refetch} = useSuspenseQuery(AdminRegisterQuery);
const [registerAdminMutation] = useMutation(RegisterAdminMutation)

const [registered, setRegistered] = useState<boolean>(false);

const [name, setName] = useState<string>("");
const [email, setEmail] = useState<string>("");
const [emailValid, setEmailValid] = useState<boolean>(true);
const [password, setPassword] = useState<string>("");
const [repeatPassword, setRepeatPassword] = useState<string>("");

const updateEmail = (field: HTMLInputElement) => {
setEmail(field.value)
setEmailValid(field.value ? field.validity.valid : true)
}

const formValid = name && email && password && password === repeatPassword;

const register = async () => {
if (formValid) {
const {data: mutationResult} = await registerAdminMutation({
variables: {
name,
email,
password,
}
})

if (mutationResult?.createAdmin?.id) {
setRegistered(true);
}
}
};

return (
<>
<AdminTopNav adminTopNavFragment={data} onLogOut={refetch}/>
<main>
<div className="flex flex-col items-center">
{registered &&
<div>
<div>
{t("Registration successful.")}
</div>
<div>
<Link to="/admin/login">{t("Go to login")}</Link>
</div>
</div>
}

{!registered &&
<Form onSubmit={register}>
<div className="items-end flex flex-col gap-4 w-64">
<div className="form-control w-full">
<div className="label">
<span className="label-text">{t("Name")}</span>
</div>
<input type="text" name="name" placeholder={t("Name")} required
value={name}
onChange={e => setName(e.target.value)}
className="input input-bordered input-primary w-full"/>
</div>
<div className="form-control w-full">
<div className="label">
<span className="label-text">{t("Email")}</span>
</div>
<input type="email" name="email" placeholder={t("Email")} required
value={email}
onChange={e => updateEmail(e.target)}
className={"input input-bordered input-primary w-full" + (emailValid ? "" : " input-error")}/>
{!emailValid &&
<div className="label">
<span className="label-text-alt">{t("Enter a valid email address")}</span>
</div>
}
</div>
<div className="form-control w-full">
<div className="label">
<span className="label-text">{t("Password")}</span>
</div>
<input type="password" name="password" placeholder={t("Password")} required
value={password}
onChange={e => setPassword(e.target.value)}
className="input input-bordered input-primary w-full"/>
</div>
<div className="form-control w-full">
<div className="label">
<span className="label-text">{t("Repeat password")}</span>
</div>
<input type="password" name="repeatPassword" placeholder={t("Repeat password")}
required
value={repeatPassword}
onChange={e => setRepeatPassword(e.target.value)}
className="input input-bordered input-primary w-full"/>
{repeatPassword && password != repeatPassword &&
<div className="label">
<span className="label-text-alt">{t("Passwords must be the same")}</span>
</div>
}
</div>
<div>
<button type="submit"
className={`btn btn-primary ${formValid ? "" : "btn-disabled"}`}>
{t("Register")}
</button>
</div>
</div>
</Form>
}
</div>
</main>
</>
);
}
15 changes: 15 additions & 0 deletions src/admin/register/AdminRegisterQueries.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {gql} from "../../graphql";

export const AdminRegisterQuery = gql(/* GraphQL */ `
query AdminRegisterQuery {
...AdminTopNavFragment
}
`)

export const RegisterAdminMutation = gql(/* GraphQL */ `
mutation RegisterAdminMutation($name: String!, $email: String!, $password: String!) {
createAdmin(input: {name: $name, email: $email, password: $password}) {
id
}
}
`);
10 changes: 10 additions & 0 deletions src/graphql/__autogenerated/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const documents = {
"\n query AdminLoginQuery {\n authenticatedAdmin {\n id\n }\n ...AdminTopNavFragment\n }\n": types.AdminLoginQueryDocument,
"\n mutation AuthenticateAdminMutation($email: String!, $password: String!) {\n authenticateAdmin(email: $email, password: $password)\n }\n": types.AuthenticateAdminMutationDocument,
"\n query AdminOverviewQuery {\n authenticatedAdmin {\n id\n }\n locations {\n id\n name\n code {\n value\n }\n year\n participants {\n id\n }\n jidCodeStats {\n count\n uniqueCount\n uniqueCountryCount\n }\n }\n ...AdminTopNavFragment\n }\n": types.AdminOverviewQueryDocument,
"\n query AdminRegisterQuery {\n ...AdminTopNavFragment\n }\n": types.AdminRegisterQueryDocument,
"\n mutation RegisterAdminMutation($name: String!, $email: String!, $password: String!) {\n createAdmin(input: {name: $name, email: $email, password: $password}) {\n id\n }\n }\n": types.RegisterAdminMutationDocument,
"\n query ServerVersionQuery {\n serverVersion\n }\n": types.ServerVersionQueryDocument,
"\n fragment MapOverview on JidCodeStats {\n countryStats {\n country\n uniqueCount\n }\n }\n": types.MapOverviewFragmentDoc,
"\n query VerifyLocationCode($locationCode: String!, $noLocationCode: Boolean!) {\n locationByCode(code: $locationCode) @skip(if: $noLocationCode) {\n id\n }\n ...AdminTopNavFragment\n }\n": types.VerifyLocationCodeDocument,
Expand Down Expand Up @@ -88,6 +90,14 @@ export function gql(source: "\n mutation AuthenticateAdminMutation($email: St
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(source: "\n query AdminOverviewQuery {\n authenticatedAdmin {\n id\n }\n locations {\n id\n name\n code {\n value\n }\n year\n participants {\n id\n }\n jidCodeStats {\n count\n uniqueCount\n uniqueCountryCount\n }\n }\n ...AdminTopNavFragment\n }\n"): (typeof documents)["\n query AdminOverviewQuery {\n authenticatedAdmin {\n id\n }\n locations {\n id\n name\n code {\n value\n }\n year\n participants {\n id\n }\n jidCodeStats {\n count\n uniqueCount\n uniqueCountryCount\n }\n }\n ...AdminTopNavFragment\n }\n"];
/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(source: "\n query AdminRegisterQuery {\n ...AdminTopNavFragment\n }\n"): (typeof documents)["\n query AdminRegisterQuery {\n ...AdminTopNavFragment\n }\n"];
/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(source: "\n mutation RegisterAdminMutation($name: String!, $email: String!, $password: String!) {\n createAdmin(input: {name: $name, email: $email, password: $password}) {\n id\n }\n }\n"): (typeof documents)["\n mutation RegisterAdminMutation($name: String!, $email: String!, $password: String!) {\n createAdmin(input: {name: $name, email: $email, password: $password}) {\n id\n }\n }\n"];
/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
Loading

0 comments on commit 57fcd5a

Please sign in to comment.