created LoginPage, RegisterPage, next fonts, authService

This commit is contained in:
Altair-sh 2025-05-22 15:45:55 +05:00
parent 9c45a0f227
commit 044c2870e7
11 changed files with 344 additions and 41 deletions

View File

@ -1 +1 @@
# Nyanberg - Paradox Games Save Viewer # paradox-save-parser-frontend

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1,11 +1,11 @@
'use client' 'use client'
import { AgGridReact } from 'ag-grid-react' import { AgGridReact } from 'ag-grid-react'
import styles from './page.module.css'
import { AllCommunityModule, themeQuartz } from 'ag-grid-community' import { AllCommunityModule, themeQuartz } from 'ag-grid-community'
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { colorSchemeDark } from 'ag-grid-community' import { colorSchemeDark } from 'ag-grid-community'
export default function Home() { export default function BrowsePage() {
const [agTheme, setAgTheme] = useState(themeQuartz) const [agTheme, setAgTheme] = useState(themeQuartz)
useEffect(() => { useEffect(() => {
@ -23,7 +23,7 @@ export default function Home() {
}, []) }, [])
return ( return (
<div className={styles.page} style={{ height: '500px' }}> <div style={{ height: '500px' }}>
<AgGridReact <AgGridReact
theme={agTheme} theme={agTheme}
modules={[AllCommunityModule]} modules={[AllCommunityModule]}

View File

@ -1,12 +1,13 @@
:root { :root {
--background: #ffffff; --background: #ffffff;
--foreground: #171717; --foreground: #171717;
font-family: var(--font-ibm-plex-sans), monospace;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--background: #0a0a0a; --background: #0a0a0a;
--foreground: #ededed; --foreground: #ffffff;
} }
} }
@ -17,35 +18,9 @@ body {
padding: 10px; 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 { body {
color: var(--foreground); color: var(--foreground);
background: var(--background); background: var(--background);
font-family: 'Hack';
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }

View File

@ -1,24 +1,24 @@
import type { Metadata } from 'next' import 'bootstrap/dist/css/bootstrap.min.css'
import './globals.css' import './globals.css'
import type { Metadata } from 'next'
import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community' import { ModuleRegistry, AllCommunityModule } from 'ag-grid-community'
import Navbar from '@/components/Navbar'
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Create Next App', title: 'Home',
description: 'Generated by create next app',
} }
export default function RootLayout({ export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
children,
}: Readonly<{
children: React.ReactNode
}>) {
ModuleRegistry.registerModules([AllCommunityModule]) ModuleRegistry.registerModules([AllCommunityModule])
return ( return (
<html lang="en"> <html lang="en">
<head> <head>
<title>Nyanberg - Paradox Games Save Viewer</title> <title>paradox-save-parser-frontend</title>
</head> </head>
<body>{children}</body> <body>
<Navbar />
{children}
</body>
</html> </html>
) )
} }

98
src/app/login/page.tsx Normal file
View 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
View 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
View 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>
)
}

View 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
View 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',
})