commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
@@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: 'export',
|
||||
distDir: 'out',
|
||||
sassOptions: {
|
||||
includePaths: [path.join(__dirname, 'styles')],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
Generated
+6551
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "forum",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"framer-motion": "^10.16.15",
|
||||
"next": "14.0.3",
|
||||
"react": "^18",
|
||||
"react-country-flag": "^3.1.0",
|
||||
"react-dom": "^18",
|
||||
"sass": "^1.69.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 321 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 493 KiB |
@@ -0,0 +1,7 @@
|
||||
.main {
|
||||
|
||||
}
|
||||
|
||||
.content {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Metadata } from 'next';
|
||||
import { openGraphMetadata } from '@/app/_helpers/shared_metadata';
|
||||
import Link from 'next/link';
|
||||
import styles from './page.module.scss';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: '',
|
||||
openGraph: {
|
||||
...openGraphMetadata,
|
||||
title: '',
|
||||
},
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.content}></div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
.footer {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 64px 32px;
|
||||
background-color: var(--dark-gray);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 0 auto;
|
||||
max-width: 1400px;
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0 20px;
|
||||
|
||||
@media screen and (min-width: 1024px) {
|
||||
grid-template-columns: repeat(10, 1fr);
|
||||
}
|
||||
|
||||
.info,
|
||||
.spacer,
|
||||
.routeSection {
|
||||
margin-bottom: 64px;
|
||||
}
|
||||
|
||||
.info {
|
||||
grid-column: span 3;
|
||||
h4 {
|
||||
display: inline-block;
|
||||
font-size: 1.8rem;
|
||||
font-weight: bold;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--white);
|
||||
margin-top: 32px;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 16px;
|
||||
font-weight: bold;
|
||||
color: var(--brand);
|
||||
}
|
||||
}
|
||||
|
||||
.div {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.routeSection {
|
||||
grid-column: span 2;
|
||||
|
||||
h5 {
|
||||
margin-bottom: 32px;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
line-height: 24px;
|
||||
text-decoration: none;
|
||||
color: var(--white);
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copyright {
|
||||
hr {
|
||||
margin-top: 0;
|
||||
margin-bottom: 32px;
|
||||
border-color: var(--brand);
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.6rem;
|
||||
font-weight: normal;
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
'use client';
|
||||
|
||||
import { Metadata } from 'next';
|
||||
import { openGraphMetadata } from '@/app/_helpers/shared_metadata';
|
||||
import Link from 'next/link';
|
||||
import styles from './footer.module.scss';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
export default function Footer() {
|
||||
const pathname: string = usePathname().substring(1);
|
||||
var username: string = '';
|
||||
|
||||
if (pathname === 'register' || pathname === 'login') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.grid}>
|
||||
<div className={styles.info}>
|
||||
<h4>
|
||||
<Link href={`/`}>Forum name</Link>
|
||||
</h4>
|
||||
<p>Where curiosity thrives and knowledge grows.</p>
|
||||
<p>Share on</p>
|
||||
<div></div>
|
||||
</div>
|
||||
<div className={styles.spacer}></div>
|
||||
<div className={styles.routeSection}>
|
||||
<h5>Links</h5>
|
||||
<Link href={'/browse'}>Browse categories</Link>
|
||||
<Link href={'/create'}>Create posts</Link>
|
||||
<Link href={'/account'}>My account</Link>
|
||||
</div>
|
||||
<div className={styles.routeSection}>
|
||||
<h5>Forum name</h5>
|
||||
<Link href={'/contact'}>Contact</Link>
|
||||
<Link href={'/about'}>About</Link>
|
||||
<Link href={'/helpcenter'}>Helpcenter</Link>
|
||||
</div>
|
||||
<div className={styles.routeSection}>
|
||||
<h5>Policies</h5>
|
||||
<Link href={'/terms'}>Terms of Service</Link>
|
||||
<Link href={'/privacy'}>Privacy Policy</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.copyright}>
|
||||
<hr />
|
||||
<p>© {new Date().getFullYear()} forumname.nl. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
.header {
|
||||
position: fixed;
|
||||
z-index: 1001;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
padding: 0px 32px;
|
||||
background: linear-gradient(0, #0000, var(--almost-black));
|
||||
|
||||
.content {
|
||||
margin: auto;
|
||||
max-width: 1400px;
|
||||
|
||||
h4 {
|
||||
display: inline-block;
|
||||
font-size: 1.4rem;
|
||||
line-height: 50px;
|
||||
font-weight: bold;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
.sessionuser,
|
||||
.sessionbuttons {
|
||||
display: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
.sessionuser,
|
||||
.sessionbuttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sessionuser {
|
||||
float: right;
|
||||
transition: all 250ms;
|
||||
margin-right: -16px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
padding: 16px;
|
||||
padding-top: 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--white);
|
||||
|
||||
.username {
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
transition: color 250ms;
|
||||
line-height: 34px;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
text-transform: capitalize;
|
||||
color: var(--white);
|
||||
|
||||
&::after {
|
||||
content: '\25be';
|
||||
float: right;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
transition: opacity 250ms;
|
||||
visibility: hidden;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
color: var(--black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sessionbuttons {
|
||||
float: right;
|
||||
|
||||
.register,
|
||||
.login {
|
||||
transition: all 250ms ease;
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
margin-left: 16px;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
width: 128px;
|
||||
|
||||
line-height: 34px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.register {
|
||||
background-color: var(--brand);
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--brand),
|
||||
#000 15%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.login {
|
||||
border: 2px solid var(--brand);
|
||||
|
||||
&:hover {
|
||||
border: 2px solid color-mix(in srgb, var(--brand), #000 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import Link from 'next/link';
|
||||
import styles from './header.module.scss';
|
||||
import Sessiondata from './sessiondata';
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<div className={styles.content}>
|
||||
<h4>
|
||||
<Link href={`/`}>Forum name</Link>
|
||||
</h4>
|
||||
{/* <nav className={styles.nav}>
|
||||
<p>
|
||||
<Link href={`/`}></Link>
|
||||
</p>
|
||||
</nav> */}
|
||||
<Sessiondata />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import styles from './header.module.scss';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { setSession } from '../_helpers/auth';
|
||||
|
||||
export default function Sessiondata() {
|
||||
const pathname: string = usePathname().substring(1);
|
||||
|
||||
var username: string = localStorage.getItem('username') || '';
|
||||
|
||||
if (pathname === 'register' || pathname === 'login') {
|
||||
return null;
|
||||
} else if (username) {
|
||||
return (
|
||||
<div className={styles.sessionuser}>
|
||||
<p className={styles.username}>{username}</p>
|
||||
<div className={styles.dropdown}>
|
||||
<Link href={'/'}>My account</Link>
|
||||
<Link href={'/'} onClick={() => setSession(false)}>Logout</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={styles.sessionbuttons}>
|
||||
<Link href={'/register'} className={styles.register}>
|
||||
Sign up
|
||||
</Link>
|
||||
<Link href={'/login'} className={styles.login}>
|
||||
Login
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
'use client';
|
||||
import { Inter } from 'next/font/google';
|
||||
import Link from 'next/link';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export function LoginForm() {
|
||||
const searchParams = useSearchParams();
|
||||
var redirUrl = searchParams.get('redir');
|
||||
|
||||
if (localStorage.getItem('username')) {
|
||||
window.location.href = '/account';
|
||||
}
|
||||
|
||||
return (
|
||||
<form action={() => {}} onSubmit={() => handleLogin(redirUrl)}>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
required
|
||||
className={inter.className}
|
||||
></input>
|
||||
<label htmlFor="password">Password</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
required
|
||||
className={inter.className}
|
||||
></input>
|
||||
<span id="errorMessage"></span>
|
||||
<button type="submit" className={inter.className}>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export function RegisterForm() {
|
||||
const searchParams = useSearchParams();
|
||||
var redirUrl = searchParams.get('redir');
|
||||
|
||||
if (localStorage.getItem('username')) {
|
||||
window.location.href = '/account';
|
||||
}
|
||||
|
||||
return (
|
||||
<form action={() => {}} onSubmit={() => handleRegister(redirUrl)}>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input id="email" type="email" required></input>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input id="username" type="text" required></input>
|
||||
<label htmlFor="password">Password</label>
|
||||
<input id="password" type="password" minLength={5} required></input>
|
||||
<input id="consent" type="checkbox" required />
|
||||
<label htmlFor="consent">
|
||||
I have read and agree to Quiztimes'{' '}
|
||||
<Link href={'/terms'}>Terms of Service</Link>
|
||||
</label>
|
||||
<button type="submit" className={inter.className}>
|
||||
Sign up
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function handleLogin(redirUrl: string | null) {
|
||||
var data = {
|
||||
username: (document.getElementById('username') as HTMLInputElement)
|
||||
.value,
|
||||
password: (document.getElementById('password') as HTMLInputElement)
|
||||
.value,
|
||||
};
|
||||
|
||||
fetch('https://quiztimes.nl/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => {
|
||||
if (res.status === 200) {
|
||||
loginAndRedirect();
|
||||
} else {
|
||||
// display error message
|
||||
(
|
||||
document.getElementById('errorMessage') as HTMLSpanElement
|
||||
).innerHTML = 'Incorrect username or password';
|
||||
}
|
||||
});
|
||||
|
||||
const loginAndRedirect = async () => {
|
||||
// start session
|
||||
await setSession(true);
|
||||
|
||||
// redirect if set
|
||||
if (redirUrl !== null) {
|
||||
window.location.href = redirUrl;
|
||||
} else {
|
||||
window.location.href = '/account';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function handleRegister(redirUrl: string | null) {
|
||||
var data = {
|
||||
username: (document.getElementById('username') as HTMLInputElement)
|
||||
.value,
|
||||
email: (document.getElementById('email') as HTMLInputElement).value,
|
||||
password: (document.getElementById('password') as HTMLInputElement)
|
||||
.value,
|
||||
};
|
||||
|
||||
fetch('https://quiztimes.nl/api/register', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(data),
|
||||
}).then((res) => {
|
||||
if (res.status === 200) {
|
||||
loginAndRedirect();
|
||||
} else {
|
||||
// display error message
|
||||
(
|
||||
document.getElementById('errorMessage') as HTMLSpanElement
|
||||
).innerHTML = 'An error occurred';
|
||||
}
|
||||
});
|
||||
|
||||
const loginAndRedirect = async () => {
|
||||
// start session
|
||||
await setSession(true);
|
||||
|
||||
// redirect if set
|
||||
if (redirUrl !== null) {
|
||||
window.location.href = redirUrl;
|
||||
} else {
|
||||
window.location.href = '/account';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function setSession(setLoggedIn: boolean) {
|
||||
console.log('test');
|
||||
if (setLoggedIn) {
|
||||
await fetch('https://quiztimes.nl/api/jwt').then((data) => {
|
||||
console.log(data);
|
||||
localStorage.setItem('username', 'Vincent');
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
localStorage.clear();
|
||||
window.location.href = '/';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
export const openGraphMetadata = {
|
||||
title: 'Quiztimes',
|
||||
description: 'Quiztimes. Free Learning, Forever.',
|
||||
url: 'https://quiztimes.nl',
|
||||
siteName: 'Quiztimes',
|
||||
images: [
|
||||
{
|
||||
url: '/og.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'The quiztimes logo',
|
||||
},
|
||||
{
|
||||
url: '/og-twitter.png',
|
||||
width: 1024,
|
||||
height: 512,
|
||||
alt: 'The quiztimes logo',
|
||||
},
|
||||
],
|
||||
locale: 'en_UK',
|
||||
type: 'website',
|
||||
};
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,46 @@
|
||||
import '@/styles/root.scss';
|
||||
import '@/styles/reset.scss';
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import { Inter } from 'next/font/google';
|
||||
import Header from './_components/header';
|
||||
import Footer from './_components/footer';
|
||||
import { openGraphMetadata } from '@/app/_helpers/shared_metadata';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://Forum.nl'),
|
||||
title: {
|
||||
template: '%s | Forum',
|
||||
default: 'Forum',
|
||||
},
|
||||
description: 'Asking questions, finding answers. talk about anything and everything.',
|
||||
keywords: ['forum', 'questions', 'answers', 'knowledge'],
|
||||
authors: [
|
||||
{ name: 'Kaj van Schalkwijk', url: 'https://gitea.quiztimes.nl/kajvans' },
|
||||
{ name: 'Ruben jimmink', url: 'https://gitea.quiztimes.nl/kajvans' },
|
||||
],
|
||||
openGraph: {
|
||||
...openGraphMetadata,
|
||||
},
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: '#646CFF'
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={inter.className}>
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
.main {
|
||||
padding-top: 0;
|
||||
padding: 0 32px;
|
||||
height: 100vh;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
var(--almost-black) 0%,
|
||||
var(--dark-gray) 100%
|
||||
);
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin: auto;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
width: 500px;
|
||||
padding: 32px;
|
||||
background-color: var(--white);
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 32px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
input {
|
||||
transition: border 250ms;
|
||||
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
border: 2px solid var(--black);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
padding: 8px;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border: 2px solid var(--brand);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
transition: background-color 250ms;
|
||||
|
||||
display: block;
|
||||
margin-top: 32px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: var(--brand);
|
||||
|
||||
font-weight: bold;
|
||||
color: var(--white);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
cursor: pointer;
|
||||
background-color: color-mix(in srgb, var(--brand), #000 15%);
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin-top: 32px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: var(--black);
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--brand);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
margin-bottom: -16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: var(--red);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Metadata } from 'next';
|
||||
import { openGraphMetadata } from '@/app/_helpers/shared_metadata';
|
||||
import Link from 'next/link';
|
||||
import styles from './page.module.scss';
|
||||
import { LoginForm } from '@/app/_helpers/auth';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Login',
|
||||
openGraph: {
|
||||
...openGraphMetadata,
|
||||
title: 'Login',
|
||||
url: 'https://quiztimes.nl/login',
|
||||
},
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.content}>
|
||||
<h1>Welcome back!</h1>
|
||||
<LoginForm />
|
||||
<p>
|
||||
Don't have an account?{' '}
|
||||
<Link href="register">Sign up</Link>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
@use '@/styles/buttons';
|
||||
|
||||
.main {
|
||||
padding-top: 0;
|
||||
padding: 0 32px;
|
||||
height: 100vh;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
var(--almost-black) 0%,
|
||||
var(--dark-gray) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
|
||||
h1, h2 {
|
||||
text-align: center;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 32px;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
p, a {
|
||||
margin: 0 auto;
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
a {
|
||||
&:first-of-type {
|
||||
@include buttons.button;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
margin-top: 64px;
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Metadata } from 'next';
|
||||
import { openGraphMetadata } from '@/app/_helpers/shared_metadata';
|
||||
import Link from 'next/link';
|
||||
import styles from './not-found.module.scss';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Page not found',
|
||||
openGraph: {
|
||||
...openGraphMetadata,
|
||||
title: 'Page not found',
|
||||
},
|
||||
};
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.content}>
|
||||
<h1>Whoops! Looks like this page went on strike.</h1>
|
||||
<h2>It's either not available or doesn't exist at all</h2>
|
||||
<Link href={'/'}>Go Home</Link>
|
||||
{/* <p>Or</p>
|
||||
<Link href={'/helpcenter'}>Visit the Helpcenter</Link>
|
||||
<Link></Link> */}
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
@use '@/styles/buttons';
|
||||
|
||||
.main {
|
||||
padding-top: 5rem;
|
||||
padding-left: 5rem;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import Image from 'next/image';
|
||||
import styles from './page.module.scss';
|
||||
import Link from 'next/link';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function Home() {
|
||||
const [selectedTheme, setSelectedTheme] = useState('black');
|
||||
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Metadata } from 'next';
|
||||
import { openGraphMetadata } from '@/app/_helpers/shared_metadata';
|
||||
import Link from 'next/link';
|
||||
import styles from './page.module.scss';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Privacy Policy',
|
||||
openGraph: {
|
||||
...openGraphMetadata,
|
||||
title: 'Privacy Policy',
|
||||
},
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<article className={styles.content}>
|
||||
<h1>Privacy Policy</h1>
|
||||
<p>Last updated: [insert date]</p>
|
||||
<p>
|
||||
<strong>Forum</strong> (<strong>"we"</strong>{' '}
|
||||
or <strong>"us"</strong>) is committed to
|
||||
protecting the information of their users.
|
||||
</p>
|
||||
<h2>Information We Collect</h2>
|
||||
<ol>
|
||||
<li></li>
|
||||
</ol>
|
||||
</article>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
.main {
|
||||
padding-top: 0;
|
||||
padding: 0 32px;
|
||||
height: 100vh;
|
||||
background: linear-gradient(
|
||||
45deg,
|
||||
var(--almost-black) 0%,
|
||||
var(--dark-gray) 100%
|
||||
);
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
margin: auto;
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
width: 500px;
|
||||
padding: 32px;
|
||||
background-color: var(--white);
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 32px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
label[for='consent'] {
|
||||
display: inline-block;
|
||||
|
||||
a {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
color: var(--brand);
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
transition: border 250ms;
|
||||
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
border: 2px solid var(--black);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
padding: 8px;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border: 2px solid var(--brand);
|
||||
}
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
button {
|
||||
transition: background-color 250ms;
|
||||
|
||||
display: block;
|
||||
margin-top: 32px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: var(--brand);
|
||||
|
||||
font-weight: bold;
|
||||
color: var(--white);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
cursor: pointer;
|
||||
background-color: color-mix(in srgb, var(--brand), #000 15%);
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
margin-top: 32px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
color: var(--black);
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Metadata } from 'next';
|
||||
import { openGraphMetadata } from '@/app/_helpers/shared_metadata';
|
||||
import Link from 'next/link';
|
||||
import styles from './page.module.scss';
|
||||
import { Inter } from 'next/font/google';
|
||||
import { RegisterForm } from '../_helpers/auth';
|
||||
|
||||
const inter = Inter({ subsets: ['latin'] });
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Create an account',
|
||||
openGraph: {
|
||||
...openGraphMetadata,
|
||||
title: 'Create an account',
|
||||
url: 'https://quiztimes.nl/register',
|
||||
},
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<div className={styles.content}>
|
||||
<h1>Create an account</h1>
|
||||
<RegisterForm />
|
||||
<p>
|
||||
Already have an account? <Link href="login">Log in</Link>
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@mixin fly-in($distance: 50px) {
|
||||
@keyframes fly-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
66% {
|
||||
opacity: 0;
|
||||
transform: translateY($distance);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
@mixin button($theme: var(--brand)) {
|
||||
display: inline-block;
|
||||
transition: background-color 250ms ease;
|
||||
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
background-color: $theme;
|
||||
cursor: pointer;
|
||||
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
color: var(--white);
|
||||
|
||||
&:hover {
|
||||
background-color: color-mix(in srgb, $theme, #000 15%);
|
||||
}
|
||||
|
||||
&:visited {
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
@use "sass:meta";
|
||||
@use "./themes/lightmode";
|
||||
@use "./themes/darkmode";
|
||||
|
||||
$colors: (
|
||||
brand: hsl(237, 100%, 70%),
|
||||
white: hsl(0, 0%, 100%),
|
||||
off-white: hsl(0, 0%, 96%),
|
||||
gray: hsl(0, 0%, 50%),
|
||||
dark-gray: hsl(0, 0%, 14%),
|
||||
almost-black: hsl(0, 5%, 7%),
|
||||
black: hsl(0, 0%, 0%),
|
||||
red: hsl(350, 74%, 45%),
|
||||
green: hsl(149, 100%, 32%),
|
||||
cozy: hsl(39, 32%, 84%),
|
||||
);
|
||||
|
||||
:root {
|
||||
@each $name, $value in $colors {
|
||||
--#{$name}: #{$value};
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
@each $name, $value in meta.module-variables("lightmode") {
|
||||
--#{$name}: #{$value};
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@each $name, $value in meta.module-variables("darkmode") {
|
||||
--#{$name}: #{$value};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
dialog,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
sub,
|
||||
sup,
|
||||
tt,
|
||||
var,
|
||||
b,
|
||||
u,
|
||||
i,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
// ol,
|
||||
// ul {
|
||||
// list-style: none;
|
||||
// }
|
||||
// blockquote,
|
||||
// q {
|
||||
// quotes: none;
|
||||
// }
|
||||
blockquote:before,
|
||||
blockquote:after,
|
||||
q:before,
|
||||
q:after {
|
||||
content: "";
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
@use 'colors';
|
||||
|
||||
body {
|
||||
width: 100vw;
|
||||
background-color: var(--background);
|
||||
text-rendering: optimizeLegibility;
|
||||
color: var(--text);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
scroll-behavior: smooth;
|
||||
overscroll-behavior-y: none;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
padding-top: 50px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4; /* DIN A4 standard, Europe */
|
||||
margin: 12.7mm;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 210mm;
|
||||
/* height: 297mm; */
|
||||
height: 282mm;
|
||||
font-size: 11px;
|
||||
background: #fff;
|
||||
overflow: visible;
|
||||
}
|
||||
body {
|
||||
padding-top: 15mm;
|
||||
}
|
||||
header,
|
||||
footer,
|
||||
aside,
|
||||
nav,
|
||||
form {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
$text: var(--white);
|
||||
$text-secondary: var(--off-white);
|
||||
$background: var(--almost-black);
|
||||
$background-secondary: var(--dark-gray);
|
||||
@@ -0,0 +1,4 @@
|
||||
$text: var(--black);
|
||||
$text-secondary: var(--gray);
|
||||
$background: var(--white);
|
||||
$background-secondary: var(--off-white);
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"out/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user