diff --git a/ui/src/app/profile/page.tsx b/ui/src/app/profile/page.tsx
new file mode 100644
index 0000000..e892a07
--- /dev/null
+++ b/ui/src/app/profile/page.tsx
@@ -0,0 +1,5 @@
+import React from 'react';
+
+export default function Page() {
+ return <>>;
+}
diff --git a/ui/src/components/Topbar/LoginModal.tsx b/ui/src/components/Topbar/LoginModal.tsx
new file mode 100644
index 0000000..7adde5a
--- /dev/null
+++ b/ui/src/components/Topbar/LoginModal.tsx
@@ -0,0 +1,260 @@
+'use client';
+
+import { login, register, refreshLoggedIn } from '@/api/auth';
+import { User } from '@/api/auth.types';
+import {
+ Modal,
+ Container,
+ Title,
+ Anchor,
+ Paper,
+ TextInput,
+ Button,
+ PasswordInput,
+ Group,
+ Checkbox,
+ Text
+} from '@mantine/core';
+import { useForm } from '@mantine/form';
+import { notifications } from '@mantine/notifications';
+
+interface LoginModalProps {
+ type?: string;
+ toggle: any;
+ setUser: (user: User) => void;
+ setRefreshId: (id: NodeJS.Timeout) => void;
+}
+
+export function LoginModal({ type, toggle, setUser, setRefreshId }: LoginModalProps) {
+ function passwordValidator(value: string) {
+ if (value.trim().length < 10) {
+ return 'Password must be at least 10 characters';
+ }
+ if (value.trim().length >= 128) {
+ return 'Password must be at most 128 characters';
+ }
+ if (!/(\d)/.test(value)) {
+ return 'Password must contain at least one number';
+ }
+ if (!/[a-z]/.test(value)) {
+ return 'Password must contain at least one lowercase letter';
+ }
+ if (!/[A-Z]/.test(value)) {
+ return 'Password must contain at least one uppercase letter';
+ }
+ if (!/[!@#$%^&*]/.test(value)) {
+ return 'Password must contain at least one special character';
+ }
+ return null;
+ }
+
+ function emailValidator(value: string) {
+ if (value.trim().length == 0) {
+ return 'Email is required';
+ }
+ if (!/^\S+@\S+$/.test(value)) {
+ return 'Invalid email';
+ }
+ return null;
+ }
+
+ const registerForm = useForm({
+ initialValues: {
+ firstName: '',
+ lastName: '',
+ email: '',
+ password: ''
+ },
+ validate: {
+ firstName: (value) => (value.trim().length > 0 ? null : 'First name is required'),
+ lastName: (value) => (value.trim().length > 0 ? null : 'Last name is required'),
+ email: emailValidator,
+ password: passwordValidator
+ }
+ });
+
+ const loginForm = useForm({
+ initialValues: {
+ email: '',
+ password: '',
+ remember: false
+ }
+ });
+
+ const resetForm = useForm({
+ initialValues: {
+ email: ''
+ }
+ });
+
+ function onClose() {
+ toggle(undefined);
+ registerForm.reset();
+ resetForm.reset();
+ if (!loginForm.values.remember) {
+ loginForm.reset();
+ }
+ }
+
+ return (
+
+ {type == 'reset' ? (
+
+ Reset password
+
+ Enter your email and we will send you a link to reset your password.{' '}
+ toggle('login')}>
+ Go Back
+
+
+
+
+
+
+ ) : type == 'register' ? (
+
+ Create account
+
+ Already have an account?{' '}
+ toggle('login')}>
+ Sign in
+
+
+
+
+
+
+
+ ) : (
+
+ Welcome back!
+
+ Do not have an account yet?{' '}
+ toggle('register')}>
+ Create account
+
+
+
+
+
+
+
+ )}
+
+ );
+}
diff --git a/ui/src/components/Topbar/headerItems.ts b/ui/src/components/Topbar/headerItems.ts
new file mode 100644
index 0000000..024a65e
--- /dev/null
+++ b/ui/src/components/Topbar/headerItems.ts
@@ -0,0 +1,41 @@
+export interface HeaderItem {
+ name: string;
+ link: string;
+ role?: string;
+}
+
+export const headerItems: HeaderItem[] = [
+ {
+ name: 'Races',
+ link: '/races'
+ },
+ {
+ name: 'Classes',
+ link: '/classes'
+ },
+ {
+ name: 'Feats',
+ link: '/feats'
+ },
+ {
+ name: 'Options & Features',
+ link: '/options'
+ },
+ {
+ name: 'Backgrounds',
+ link: '/backgrounds'
+ },
+ {
+ name: 'Items',
+ link: '/items'
+ },
+ {
+ name: 'Spells',
+ link: '/spells'
+ },
+ {
+ name: 'Management',
+ link: '/management',
+ role: 'admin'
+ }
+];
diff --git a/ui/src/components/Topbar/index.tsx b/ui/src/components/Topbar/index.tsx
index 7efb7bb..3d31c94 100644
--- a/ui/src/components/Topbar/index.tsx
+++ b/ui/src/components/Topbar/index.tsx
@@ -3,73 +3,14 @@
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import './topbar.css';
-import {
- Anchor,
- Avatar,
- Button,
- Card,
- Checkbox,
- Container,
- Grid,
- Group,
- Menu,
- Modal,
- Paper,
- PasswordInput,
- Text,
- TextInput,
- Title,
- UnstyledButton
-} from '@mantine/core';
+import { Avatar, Button, Card, Grid, Group, Menu, Text, UnstyledButton } from '@mantine/core';
import Cookies from 'js-cookie';
import { useEffect, useState } from 'react';
-import { useForm } from '@mantine/form';
-import { login, register, logout, me, refreshLoggedIn } from '@/api/auth';
+import { logout, me, refreshLoggedIn } from '@/api/auth';
import { User } from '@/api/auth.types';
import { useToggle } from '@mantine/hooks';
-import { notifications } from '@mantine/notifications';
-
-interface HeaderItem {
- name: string;
- link: string;
- role?: string;
-}
-
-const headerItems: HeaderItem[] = [
- {
- name: 'Races',
- link: '/races'
- },
- {
- name: 'Classes',
- link: '/classes'
- },
- {
- name: 'Feats',
- link: '/feats'
- },
- {
- name: 'Options & Features',
- link: '/options'
- },
- {
- name: 'Backgrounds',
- link: '/backgrounds'
- },
- {
- name: 'Items',
- link: '/items'
- },
- {
- name: 'Spells',
- link: '/spells'
- },
- {
- name: 'Management',
- link: '/management',
- role: 'admin'
- }
-];
+import { LoginModal } from './LoginModal';
+import { HeaderItem, headerItems } from './headerItems';
export default function Topbar() {
const pathName = usePathname();
@@ -145,17 +86,11 @@ export default function Topbar() {
-
+
+
+
);
}
-
-interface LoginModalProps {
- type?: string;
- toggle: any;
- setUser: (user: User) => void;
- setRefreshId: (id: NodeJS.Timeout) => void;
-}
-
-function LoginModal({ type, toggle, setUser, setRefreshId }: LoginModalProps) {
- function passwordValidator(value: string) {
- if (value.trim().length < 10) {
- return 'Password must be at least 10 characters';
- }
- if (value.trim().length >= 128) {
- return 'Password must be at most 128 characters';
- }
- if (!/(\d)/.test(value)) {
- return 'Password must contain at least one number';
- }
- if (!/[a-z]/.test(value)) {
- return 'Password must contain at least one lowercase letter';
- }
- if (!/[A-Z]/.test(value)) {
- return 'Password must contain at least one uppercase letter';
- }
- if (!/[!@#$%^&*]/.test(value)) {
- return 'Password must contain at least one special character';
- }
- return null;
- }
-
- function emailValidator(value: string) {
- if (value.trim().length == 0) {
- return 'Email is required';
- }
- if (!/^\S+@\S+$/.test(value)) {
- return 'Invalid email';
- }
- return null;
- }
-
- const registerForm = useForm({
- initialValues: {
- firstName: '',
- lastName: '',
- email: '',
- password: ''
- },
- validate: {
- firstName: (value) => (value.trim().length > 0 ? null : 'First name is required'),
- lastName: (value) => (value.trim().length > 0 ? null : 'Last name is required'),
- email: emailValidator,
- password: passwordValidator
- }
- });
-
- const loginForm = useForm({
- initialValues: {
- email: '',
- password: '',
- remember: false
- }
- });
-
- const resetForm = useForm({
- initialValues: {
- email: ''
- }
- });
-
- function onClose() {
- toggle(undefined);
- registerForm.reset();
- resetForm.reset();
- if (!loginForm.values.remember) {
- loginForm.reset();
- }
- }
-
- return (
-
- {type == 'reset' ? (
-
- Reset password
-
- Enter your email and we will send you a link to reset your password.{' '}
- toggle('login')}>
- Go Back
-
-
-
-
-
-
- ) : type == 'register' ? (
-
- Create account
-
- Already have an account?{' '}
- toggle('login')}>
- Sign in
-
-
-
-
-
-
-
- ) : (
-
- Welcome back!
-
- Do not have an account yet?{' '}
- toggle('register')}>
- Create account
-
-
-
-
-
-
-
- )}
-
- );
-}