created LoginPage, RegisterPage, next fonts, authService
This commit is contained in:
parent
9c45a0f227
commit
044c2870e7
@ -1 +1 @@
|
||||
# Nyanberg - Paradox Games Save Viewer
|
||||
# paradox-save-parser-frontend
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
|
||||
import { AgGridReact } from 'ag-grid-react'
|
||||
import styles from './page.module.css'
|
||||
import { AllCommunityModule, themeQuartz } from 'ag-grid-community'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { colorSchemeDark } from 'ag-grid-community'
|
||||
|
||||
export default function Home() {
|
||||
export default function BrowsePage() {
|
||||
const [agTheme, setAgTheme] = useState(themeQuartz)
|
||||
|
||||
useEffect(() => {
|
||||
@ -23,7 +23,7 @@ export default function Home() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.page} style={{ height: '500px' }}>
|
||||
<div style={{ height: '500px' }}>
|
||||
<AgGridReact
|
||||
theme={agTheme}
|
||||
modules={[AllCommunityModule]}
|
||||
@ -1,12 +1,13 @@
|
||||
:root {
|
||||
--background: #ffffff;
|
||||
--foreground: #171717;
|
||||
font-family: var(--font-ibm-plex-sans), monospace;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #0a0a0a;
|
||||
--foreground: #ededed;
|
||||
--foreground: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,35 +18,9 @@ body {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Hack';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
src: url('/fonts/hack-v3.003/Hack-Regular.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Hack';
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
src: url('/fonts/hack-v3.003/Hack-Bold.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Hack';
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
src: url('/fonts/hack-v3.003/Hack-Italic.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Hack';
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
src: url('/fonts/hack-v3.003/Hack-BoldItalic.ttf');
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--foreground);
|
||||
background: var(--background);
|
||||
font-family: 'Hack';
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
import type { Metadata } from 'next'
|
||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||
import './globals.css'
|
||||
import type { Metadata } from 'next'
|
||||
import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community'
|
||||
import Navbar from '@/components/Navbar'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create Next App',
|
||||
description: 'Generated by create next app',
|
||||
title: 'Home',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||
ModuleRegistry.registerModules([AllCommunityModule])
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Nyanberg - Paradox Games Save Viewer</title>
|
||||
<title>paradox-save-parser-frontend</title>
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
<body>
|
||||
<Navbar />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
98
src/app/login/page.tsx
Normal file
98
src/app/login/page.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { authService } from '@/services/authService'
|
||||
|
||||
export default function LoginPage() {
|
||||
const [formData, setFormData] = useState({
|
||||
email: '',
|
||||
password: '',
|
||||
stayLoggedIn: true,
|
||||
})
|
||||
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value, type, checked } = e.target
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: type === 'checkbox' ? checked : value,
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
console.log('Log in:', formData)
|
||||
const result = await authService.login(formData.email, formData.password)
|
||||
console.log('Logged in:', result)
|
||||
} catch (err: any) {
|
||||
console.error(err.message)
|
||||
setError(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mt-5" style={{ maxWidth: '500px' }}>
|
||||
<h2 className="mb-4">Login</h2>
|
||||
|
||||
{error && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="form-label">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="password" className="form-label">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-check mb-3">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
id="stayLoggedIn"
|
||||
name="stayLoggedIn"
|
||||
checked={formData.stayLoggedIn}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<label className="form-check-label" htmlFor="stayLoggedIn">
|
||||
Stay logged in
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="btn btn-primary w-100">
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
121
src/app/register/page.tsx
Normal file
121
src/app/register/page.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { authService } from '@/services/authService'
|
||||
|
||||
export default function RegisterPage() {
|
||||
const router = useRouter()
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
|
||||
const [error, setError] = useState('')
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[e.target.name]: e.target.value,
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
throw new Error('Passwords do not match.')
|
||||
}
|
||||
|
||||
console.log('Register', formData)
|
||||
const result = await authService.register(formData.name, formData.email, formData.password)
|
||||
console.log('Registered:', result)
|
||||
|
||||
router.push('/login') // Redirect after success
|
||||
} catch (err: any) {
|
||||
console.error(err.message)
|
||||
setError(err.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container mt-5" style={{ maxWidth: '400px' }}>
|
||||
<h2 className="mb-4 text-center">Register</h2>
|
||||
|
||||
{error && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="name" className="form-label">
|
||||
Nickname
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="name"
|
||||
name="name"
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="email" className="form-label">
|
||||
Email address
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-3">
|
||||
<label htmlFor="password" className="form-label">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="password"
|
||||
name="password"
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="confirmPassword" className="form-label">
|
||||
Confirm Password
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
className="form-control"
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleChange}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="btn btn-primary w-100">
|
||||
Register
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
27
src/components/Navbar.tsx
Normal file
27
src/components/Navbar.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import { font_Hack } from '@/utils/myFonts'
|
||||
|
||||
export default function Navbar() {
|
||||
return (
|
||||
<nav className={`navbar navbar-expand-lg navbar-dark bg-dark px-3 ${font_Hack.className}`}>
|
||||
<div className="container-fluid">
|
||||
{/* Left: Home Link */}
|
||||
<Link href="/" className="navbar-brand">
|
||||
Home
|
||||
</Link>
|
||||
|
||||
{/* Right: Login/Register Buttons */}
|
||||
<div className="d-flex gap-2 ms-auto">
|
||||
<Link href="/login" className="btn btn-outline-light rounded-pill px-4">
|
||||
Log in
|
||||
</Link>
|
||||
<Link href="/register" className="btn btn-primary rounded-pill px-4">
|
||||
Register
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
46
src/services/authService.ts
Normal file
46
src/services/authService.ts
Normal file
@ -0,0 +1,46 @@
|
||||
class AuthService {
|
||||
private API_BASE = '/api/auth'
|
||||
|
||||
private hashPassword(password: string) {
|
||||
// TODO: password hashing
|
||||
return password
|
||||
}
|
||||
|
||||
async login(email: string, password: string) {
|
||||
const response = await fetch(`${this.API_BASE}/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password: this.hashPassword(password),
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Login failed')
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
async register(name: string, email: string, password: string) {
|
||||
const response = await fetch(`${this.API_BASE}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
email,
|
||||
password: this.hashPassword(password),
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Registration failed')
|
||||
}
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
}
|
||||
|
||||
// Export a singleton instance
|
||||
export const authService = new AuthService()
|
||||
36
src/utils/myFonts.ts
Normal file
36
src/utils/myFonts.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import localFont from 'next/font/local'
|
||||
import { IBM_Plex_Sans } from 'next/font/google'
|
||||
|
||||
export const font_IbmPlexSans = IBM_Plex_Sans({
|
||||
subsets: ['latin'],
|
||||
weight: ['400', '500', '700'],
|
||||
display: 'swap',
|
||||
variable: '--font-ibm-plex-sans',
|
||||
})
|
||||
|
||||
export const font_Hack = localFont({
|
||||
src: [
|
||||
{
|
||||
path: '../../public/fonts/hack-v3.003/Hack-Regular.ttf',
|
||||
weight: '400',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: '../../public/fonts/hack-v3.003/Hack-Bold.ttf',
|
||||
weight: '700',
|
||||
style: 'normal',
|
||||
},
|
||||
{
|
||||
path: '../../public/fonts/hack-v3.003/Hack-Italic.ttf',
|
||||
weight: '400',
|
||||
style: 'italic',
|
||||
},
|
||||
{
|
||||
path: '../../public/fonts/hack-v3.003/Hack-BoldItalic.ttf',
|
||||
weight: '700',
|
||||
style: 'italic',
|
||||
},
|
||||
],
|
||||
display: 'swap',
|
||||
variable: '--font-hack',
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user