diff --git a/backend/backend/main.py b/backend/backend/main.py index ead4171..76fb7dd 100644 --- a/backend/backend/main.py +++ b/backend/backend/main.py @@ -10,6 +10,7 @@ from jose import JWTError, jwt from passlib.context import CryptContext from psycopg import DataError, IntegrityError, AsyncConnection, sql +from psycopg.errors import UniqueViolation from psycopg_pool import AsyncConnectionPool from .models import (TokenData, ProposedWebsite, RegisteringStudentRequest, @@ -375,11 +376,15 @@ async def create_account(user_data: RegisteringUser, conn: AsyncConnection): async def register_full_account(user_data: RegisteringFullUserRequest, conn: AsyncConnection): async with conn.cursor() as cur: - await cur.execute(''' - insert into Full_Account (id, email, phone_number) - values (%(id)s, %(email)s, %(phone_number)s) - ''', {'id': user_data['id'], 'email': user_data['user'].email, 'phone_number': user_data['user'].phone_number}) - await conn.commit() + try: + await cur.execute(''' + insert into Full_Account (id, email, phone_number) + values (%(id)s, %(email)s, %(phone_number)s) + ''', {'id': user_data['id'], 'email': user_data['user'].email, 'phone_number': user_data['user'].phone_number}) + await conn.commit() + except UniqueViolation: + raise HTTPException( + status_code=400, detail='Phone number taken.') @app.post('/register') diff --git a/backend/tests/test_main.py b/backend/tests/test_main.py index b0f4fbd..207a3ba 100644 --- a/backend/tests/test_main.py +++ b/backend/tests/test_main.py @@ -41,6 +41,20 @@ async def test_register_administrator_with_same_email(test_db): assert res.json()['detail'] == 'User already exists.' +@pytest.mark.anyio +async def test_register_administrator_with_same_phone_number(test_db): + res = await register_administrator() + assert res.status_code == 200 + + administrator = deepcopy(d.registering_administrator_data) + administrator['username'] = 'different_username' + administrator['email'] = 'different_email@gmail.com' + + res = await register_administrator(administrator) + assert res.status_code == 400, res.text + assert res.json()['detail'] == 'Phone number taken.' + + @pytest.mark.anyio async def test_register_student(test_db): res = await register_administrator() diff --git a/frontend/package.json b/frontend/package.json index 14bb6e9..e29e39e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,7 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.49.3", "react-router-dom": "^6.21.3" }, "devDependencies": { diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 59a8aed..013a279 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -11,6 +11,9 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-hook-form: + specifier: ^7.49.3 + version: 7.49.3(react@18.2.0) react-router-dom: specifier: ^6.21.3 version: 6.21.3(react-dom@18.2.0)(react@18.2.0) @@ -1972,6 +1975,15 @@ packages: scheduler: 0.23.0 dev: false + /react-hook-form@7.49.3(react@18.2.0): + resolution: {integrity: sha512-foD6r3juidAT1cOZzpmD/gOKt7fRsDhXXZ0y28+Al1CHgX+AY1qIN9VSIIItXRq1dN68QrRwl1ORFlwjBaAqeQ==} + engines: {node: '>=18', pnpm: '8'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 + dependencies: + react: 18.2.0 + dev: false + /react-router-dom@6.21.3(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-kNzubk7n4YHSrErzjLK72j0B5i969GsuCGazRl3G6j1zqZBLjuSlYBdVdkDOgzGdPIffUOc9nmgiadTEVoq91g==} engines: {node: '>=14.0.0'} diff --git a/frontend/src/components/Root/Root.module.css b/frontend/src/components/Root/Root.module.css index dce34d0..e040927 100644 --- a/frontend/src/components/Root/Root.module.css +++ b/frontend/src/components/Root/Root.module.css @@ -6,3 +6,8 @@ margin: 0 0.5rem; } } + +.smallText { + color: #999; + font-size: 0.5rem; +} diff --git a/frontend/src/components/Root/Root.tsx b/frontend/src/components/Root/Root.tsx index 79ceaa8..203af94 100644 --- a/frontend/src/components/Root/Root.tsx +++ b/frontend/src/components/Root/Root.tsx @@ -24,6 +24,10 @@ function Root() {

By Lachlan Shoesmith

+

+ webdevcamp is not associated with Skill Samurai Rouse Hill or Skill + Samurai as a broader company. +

); diff --git a/frontend/src/index.css b/frontend/src/index.css index 0499343..08e6ce9 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -5,4 +5,6 @@ color-scheme: light dark; color: #dbdbdb; background-color: #222; + max-width: 480px; + margin: 0 auto; } diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index d51e51b..3d89db1 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -4,6 +4,7 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import Root from './components/Root/Root.tsx'; import ErrorPage from './routes/ErrorPage.tsx'; import Login from './routes/Login.tsx'; +import Register from './routes/Register.tsx'; import Home from './routes/Home.tsx'; import './index.css'; @@ -21,6 +22,10 @@ const router = createBrowserRouter([ path: '/login', element: , }, + { + path: '/register', + element: , + }, ], }, ]); diff --git a/frontend/src/routes/Login.tsx b/frontend/src/routes/Login.tsx index 252fcfa..14dc311 100644 --- a/frontend/src/routes/Login.tsx +++ b/frontend/src/routes/Login.tsx @@ -1,15 +1,64 @@ -import { Form } from 'react-router-dom'; import Button from '../components/Button/Button'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import { useState } from 'react'; -function login() { +type Inputs = { + username: string; + password: string; +}; + +export default function Login() { + const { + register, + formState: { errors }, + handleSubmit, + } = useForm(); + const [loginErrors, setLoginErrors] = useState(''); + + const onSubmit: SubmitHandler = async (data) => { + const res = await fetch('https://webdevcamp.fly.dev/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + const responseJSON = await res.json(); + const responseDetail: string = responseJSON.detail; + + if (!res.ok) { + setLoginErrors(responseDetail); + } else { + console.log(responseDetail); + } + }; return ( <>

Login Page

-
+ + + -
+ + {loginErrors &&

{loginErrors}

} ); } - -export default login; diff --git a/frontend/src/routes/Register.tsx b/frontend/src/routes/Register.tsx new file mode 100644 index 0000000..93244ee --- /dev/null +++ b/frontend/src/routes/Register.tsx @@ -0,0 +1,101 @@ +import Button from '../components/Button/Button'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import { useState } from 'react'; + +type Inputs = { + given_name: string; + family_name: string; + username: string; + hashed_password: string; + account_type: string; + email: string; + phone_number: string; +}; + +export default function Register() { + const { + register, + formState: { errors }, + handleSubmit, + } = useForm(); + const [registerErrors, setRegisterErrors] = useState(''); + + const onSubmit: SubmitHandler = async (data) => { + data.account_type = 'administrator'; + const res = await fetch('https://webdevcamp.fly.dev/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }); + + const responseJSON = await res.json(); + + if (!res.ok) { + setRegisterErrors(responseJSON.detail); + } else { + console.log(res.json()); + } + }; + return ( + <> +

Register new administrator

+
+ + + + + + + +
+ {registerErrors &&

{registerErrors}

} + + ); +}