alot and reverse proxy
This commit is contained in:
+8
-1
@@ -2,11 +2,18 @@ const path = require('path');
|
|||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'export',
|
|
||||||
distDir: 'out',
|
distDir: 'out',
|
||||||
sassOptions: {
|
sassOptions: {
|
||||||
includePaths: [path.join(__dirname, 'styles')],
|
includePaths: [path.join(__dirname, 'styles')],
|
||||||
},
|
},
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/api/:path*',
|
||||||
|
destination: 'http://localhost:5000/:path*', // Proxy to Backend IMPORTANT TO SET THE CORRECT PORT
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import { setSession } from '../_helpers/auth';
|
|||||||
export default function Sessiondata() {
|
export default function Sessiondata() {
|
||||||
const pathname: string = usePathname().substring(1);
|
const pathname: string = usePathname().substring(1);
|
||||||
|
|
||||||
var username: string = localStorage.getItem('username') || '';
|
var username: string = '';
|
||||||
|
// if(typeof window !== 'undefined'){
|
||||||
|
username = localStorage.getItem('username') || '';
|
||||||
|
// }
|
||||||
|
|
||||||
if (pathname === 'register' || pathname === 'login') {
|
if (pathname === 'register' || pathname === 'login') {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function RegisterForm() {
|
|||||||
<input id="password" type="password" minLength={5} required></input>
|
<input id="password" type="password" minLength={5} required></input>
|
||||||
<input id="consent" type="checkbox" required />
|
<input id="consent" type="checkbox" required />
|
||||||
<label htmlFor="consent">
|
<label htmlFor="consent">
|
||||||
I have read and agree to Quiztimes'{' '}
|
I have read and agree to Tweakers'{' '}
|
||||||
<Link href={'/terms'}>Terms of Service</Link>
|
<Link href={'/terms'}>Terms of Service</Link>
|
||||||
</label>
|
</label>
|
||||||
<button type="submit" className={inter.className}>
|
<button type="submit" className={inter.className}>
|
||||||
@@ -72,7 +72,7 @@ function handleLogin(redirUrl: string | null) {
|
|||||||
.value,
|
.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch('https://quiztimes.nl/api/login', {
|
fetch('/api/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -111,7 +111,7 @@ async function handleRegister(redirUrl: string | null) {
|
|||||||
.value,
|
.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch('https://quiztimes.nl/api/register', {
|
fetch('/api/register', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -142,16 +142,55 @@ async function handleRegister(redirUrl: string | null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function setSession(setLoggedIn: boolean) {
|
export async function setSession(setLoggedIn: boolean) {
|
||||||
console.log('test');
|
|
||||||
if (setLoggedIn) {
|
if (setLoggedIn) {
|
||||||
await fetch('https://quiztimes.nl/api/jwt').then((data) => {
|
try {
|
||||||
console.log(data);
|
const response = await fetch('/api/jwt');
|
||||||
localStorage.setItem('username', 'Vincent');
|
if (!response.ok) {
|
||||||
});
|
throw new Error('Failed to fetch JWT');
|
||||||
return true;
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
localStorage.setItem('username', data.user);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching JWT:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function logout() {
|
||||||
|
await setSession(false);
|
||||||
|
//remove cookies from server
|
||||||
|
fetch('/api/logout', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function refreshToken() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/refreshtoken', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.text();
|
||||||
|
console.log(data);
|
||||||
|
if (!response.ok) {
|
||||||
|
setSession(false);
|
||||||
|
//redirect to login page
|
||||||
|
window.location.href = '/login?redir=' + window.location.pathname;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error refreshing token:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(refreshToken, 300000);
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
.BrowseField {
|
||||||
|
width: 1200px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: var(--off-white);
|
||||||
|
|
||||||
|
.Info {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between; /* Pushes elements to the ends */
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--brand);
|
||||||
|
padding-left: 20px;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--black);
|
||||||
|
font-size: 30px;
|
||||||
|
margin-right: 20px; /* Adjust the spacing between elements */
|
||||||
|
}
|
||||||
|
|
||||||
|
.headtitle{
|
||||||
|
color: var(--brand);
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 30px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headdescription{
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headcount{
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import styles from './browseBlock.module.scss'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
|
||||||
|
type BrowseBlockProps = {
|
||||||
|
title: string,
|
||||||
|
description: string,
|
||||||
|
postcount: number | string,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function BrowseBlock(BrowseBlockProps: BrowseBlockProps) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.BrowseField}>
|
||||||
|
<div className={styles.Info}>
|
||||||
|
<Link href={`/category/${BrowseBlockProps.title}`}><h2 className={styles.headtitle} >{BrowseBlockProps.title}</h2></Link>
|
||||||
|
<p className={styles.description}>{BrowseBlockProps.description}</p>
|
||||||
|
<p className={styles.count}>{BrowseBlockProps.postcount} posts</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FirsBlock(BrowseBlockProps: BrowseBlockProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles.BrowseField}>
|
||||||
|
<div className={styles.Info}>
|
||||||
|
<h2 className={styles.title} >{BrowseBlockProps.title}</h2>
|
||||||
|
<p className={styles.headdescription}>{BrowseBlockProps.description}</p>
|
||||||
|
<p className={styles.headcount}>{BrowseBlockProps.postcount}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5rem 22rem;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
float: left;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentGrid {
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--white);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
'use client'
|
||||||
|
import Link from 'next/link';
|
||||||
|
import styles from './page.module.scss';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { BrowseBlock, FirsBlock } from './browseBlock';
|
||||||
|
|
||||||
|
type Category = {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchCategories() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/home/categories');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch categories');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const categorieslist = data['categories']
|
||||||
|
console.log(data['categories'].length)
|
||||||
|
setCategories(categorieslist instanceof Array ? categorieslist : []);
|
||||||
|
setLoading(false); // Set loading to false once data is fetched
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching categories:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCategories();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<h1 className={styles.title}>Browse Categories</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.contentGrid}>
|
||||||
|
<FirsBlock
|
||||||
|
title="Name:"
|
||||||
|
description="Description:"
|
||||||
|
postcount="Post count:"
|
||||||
|
/>
|
||||||
|
{loading ? (
|
||||||
|
<p>Loading...</p>
|
||||||
|
) : categories.length == 0 ? (
|
||||||
|
<p>No categories found</p>
|
||||||
|
) : (
|
||||||
|
categories.map(category => (
|
||||||
|
<BrowseBlock
|
||||||
|
key={category.name} // Assuming each category has a unique name
|
||||||
|
title={category.name}
|
||||||
|
description={category.description}
|
||||||
|
postcount={category.count}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
.BrowseField {
|
||||||
|
width: 1200px;
|
||||||
|
height: 60px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background-color: var(--off-white);
|
||||||
|
|
||||||
|
.Info {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr 1fr auto; /* Setting a fixed width for the title column */
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--brand);
|
||||||
|
font-size: 30px;
|
||||||
|
white-space: nowrap; /* Prevent wrapping of text */
|
||||||
|
overflow: hidden; /* Hide overflowing text */
|
||||||
|
text-overflow: ellipsis; /* Show ellipsis for overflowed text */
|
||||||
|
width: 400px; /* Limit the maximum width of the title */
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--black);
|
||||||
|
font-size: 30px;
|
||||||
|
white-space: nowrap; /* Prevent wrapping of text */
|
||||||
|
overflow: hidden; /* Hide overflowing text */
|
||||||
|
text-overflow: ellipsis; /* Show ellipsis for overflowed text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
max-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.created {
|
||||||
|
width: 400px
|
||||||
|
}
|
||||||
|
|
||||||
|
.headtitle {
|
||||||
|
color: var(--brand);
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 30px;
|
||||||
|
width: 400px; /* Limit the maximum width of the title */
|
||||||
|
}
|
||||||
|
|
||||||
|
.headdescription {
|
||||||
|
font-weight: 600;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headcount {
|
||||||
|
font-weight: 600;
|
||||||
|
padding-right: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import styles from './browseBlock.module.scss'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
|
||||||
|
type BrowseBlockProps = {
|
||||||
|
title: string,
|
||||||
|
author: string,
|
||||||
|
reactions: number | string,
|
||||||
|
created: string,
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function BrowseBlock(BrowseBlockProps: BrowseBlockProps) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.BrowseField}>
|
||||||
|
<div className={styles.Info}>
|
||||||
|
<Link href={`/post/${BrowseBlockProps.id}`}><h2 className={styles.title} >{BrowseBlockProps.title}</h2></Link>
|
||||||
|
<p className={styles.description}>{BrowseBlockProps.author}</p>
|
||||||
|
<p className={styles.created}>{BrowseBlockProps.created}</p>
|
||||||
|
<p className={styles.count}>{BrowseBlockProps.reactions} 5 reactions</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FirsBlock(BrowseBlockProps: BrowseBlockProps) {
|
||||||
|
return (
|
||||||
|
<div className={styles.BrowseField}>
|
||||||
|
<div className={styles.Info}>
|
||||||
|
<h2 className={styles.headtitle} >{BrowseBlockProps.title}</h2>
|
||||||
|
<p className={styles.headdescription}>{BrowseBlockProps.author}</p>
|
||||||
|
<p className={styles.headcount}>{BrowseBlockProps.created}</p>
|
||||||
|
<p className={styles.headcount}>{BrowseBlockProps.reactions}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 5rem 22rem;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4rem;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
float: left;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.contentGrid{
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadMore {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 4rem 0 2rem;
|
||||||
|
background-color: var(--brand);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--white);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
'use client'
|
||||||
|
import Link from 'next/link';
|
||||||
|
import styles from './page.module.scss';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
import { BrowseBlock, FirsBlock } from './browseBlock';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
type Post = {
|
||||||
|
title: string;
|
||||||
|
author: string;
|
||||||
|
reactions: number;
|
||||||
|
date: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page({ params }: { params: { slug: string } }) {
|
||||||
|
|
||||||
|
const [posts, setPosts] = useState<Post[]>([]);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [categoryDescription, setCategoryDescription] = useState<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchPostsCategory() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/home/category/' + params.slug);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch categories');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const categorieslist = data['postarray']
|
||||||
|
console.log(data['postarray'].length)
|
||||||
|
setPosts(categorieslist instanceof Array ? categorieslist : []);
|
||||||
|
setLoading(false); // Set loading to false once data is fetched
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching categories:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCategoryDescription() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/home/category/details/' + params.slug);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch categories');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setCategoryDescription(data['description']);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching categories:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchCategoryDescription();
|
||||||
|
fetchPostsCategory();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//fetch description of category on /api/home/category/details/:slug
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<h1 className={styles.title}>{params.slug}</h1>
|
||||||
|
<p>{categoryDescription}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.contentGrid}>
|
||||||
|
<FirsBlock
|
||||||
|
title="Name:"
|
||||||
|
author="Creator:"
|
||||||
|
reactions="Reactions:"
|
||||||
|
created="Created:"
|
||||||
|
id="id"
|
||||||
|
/>
|
||||||
|
{loading ? (
|
||||||
|
<p>Loading...</p>
|
||||||
|
) : posts.length == 0 ? (
|
||||||
|
<p>No categories found</p>
|
||||||
|
) : (
|
||||||
|
posts.map(Post => (
|
||||||
|
<BrowseBlock
|
||||||
|
key={Post.title} // Assuming each category has a unique name
|
||||||
|
title={Post.title}
|
||||||
|
author={Post.author}
|
||||||
|
reactions={Post.reactions}
|
||||||
|
created={Post.date}
|
||||||
|
id={Post.id}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
+242
-6
@@ -1,11 +1,247 @@
|
|||||||
@use '@/styles/buttons';
|
@use '@/styles/buttons';
|
||||||
|
|
||||||
.main {
|
.banner {
|
||||||
padding-top: 5rem;
|
margin-top: -50px;
|
||||||
padding-left: 5rem;
|
height: 100vh;
|
||||||
|
padding: 0px 32px;
|
||||||
|
background: linear-gradient(45deg,
|
||||||
|
var(--almost-black) 0%,
|
||||||
|
var(--dark-gray) 100%);
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
margin: auto;
|
||||||
|
max-width: 1400px;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--brand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include buttons.button;
|
||||||
|
float: right;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.trending{
|
.demo {
|
||||||
width: 60%;
|
padding: 128px 32px;
|
||||||
margin: 0 auto;
|
background-color: var(--off-white);
|
||||||
|
color: var(--almost-black);
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 64px;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 1400px;
|
||||||
|
|
||||||
|
.action {
|
||||||
|
margin-top: 32px;
|
||||||
|
min-width: 384px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include buttons.button;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 16px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quoteContainer {
|
||||||
|
float: right;
|
||||||
|
border: 2px solid var(--almost-black);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 32px;
|
||||||
|
|
||||||
|
@media screen and (min-width: 600px) {
|
||||||
|
min-width: 512px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flashcards {
|
||||||
|
padding: 256px 32px;
|
||||||
|
background-color: var(--white);
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.customise {
|
||||||
|
position: relative;
|
||||||
|
padding: 128px 32px;
|
||||||
|
background-color: var(--almost-black);
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 64px;
|
||||||
|
margin: auto;
|
||||||
|
max-width: 1400px;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 4rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topics {
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 32px;
|
||||||
|
width: 600px;
|
||||||
|
height: 128px;
|
||||||
|
|
||||||
|
.choice {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
div {
|
||||||
|
border: 2px solid var(--white);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
p {
|
||||||
|
position: relative;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 600px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
display: block;
|
||||||
|
border: 2px solid var(--white);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
outline: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 32px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
width: 128px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid var(--white);
|
||||||
|
border-radius: 64px;
|
||||||
|
width: 48px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 4px;
|
||||||
|
border-radius: 64px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: var(--brand);
|
||||||
|
}
|
||||||
|
|
||||||
|
// input:checked + span {
|
||||||
|
// left: 28px;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.selector {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: var(--brand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.themes {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 48px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+269
-23
@@ -6,35 +6,281 @@ import Link from 'next/link';
|
|||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
const PostArray = [
|
|
||||||
{
|
|
||||||
title:'problems with running docker on arm processor',
|
|
||||||
content:'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl eget aliquam ultricies, nunc nisl ultricies nunc, vitae aliquam ni du tein kanker moederus',
|
|
||||||
author:'testuser1234567',
|
|
||||||
category:'Technology',
|
|
||||||
postId:'1',
|
|
||||||
date:'01-02-2023',
|
|
||||||
reactionCount:'69',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title:'problems with running docker on arm processor',
|
|
||||||
content:'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla euismod, nisl eget aliquam ultricies, nunc nisl ultricies nunc, vitae aliquam ni du tein kanker moederus',
|
|
||||||
author:'testuser1234567',
|
|
||||||
category:'Technology',
|
|
||||||
postId:'1',
|
|
||||||
date:'01-02-2023',
|
|
||||||
reactionCount:'69',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [selectedTheme, setSelectedTheme] = useState('black');
|
const [selectedTheme, setSelectedTheme] = useState('black');
|
||||||
|
|
||||||
|
const username = localStorage.getItem('username');
|
||||||
|
if (username) {
|
||||||
|
window.location.href = '/account';
|
||||||
|
}
|
||||||
|
|
||||||
|
const quotes = [
|
||||||
|
{
|
||||||
|
text: "<span>Forum</span> is a nice place to spend time.",
|
||||||
|
author: "Kaj, High School Student"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "<span>Forum</span> is one of the best forums.",
|
||||||
|
author: "Ruben, High School Student"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const [currentQuoteIndex, setCurrentQuoteIndex] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
// Switch to the next quote
|
||||||
|
setCurrentQuoteIndex((prevIndex) => (prevIndex + 1) % quotes.length);
|
||||||
|
}, 5000); // Change the interval as needed (in milliseconds)
|
||||||
|
|
||||||
|
return () => clearInterval(interval); // Cleanup the interval on component unmount
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<div className={styles.trending}>
|
<div className={styles.banner}>
|
||||||
<h2>Trending</h2>
|
<div className={styles.content}>
|
||||||
|
<motion.h1
|
||||||
|
initial={{ opacity: 0, y: '40px' }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5 }}
|
||||||
|
>
|
||||||
|
Ask an answer question <br />
|
||||||
|
with <span>Forum</span>
|
||||||
|
</motion.h1>
|
||||||
|
<motion.p
|
||||||
|
initial={{ opacity: 0, y: '40px' }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ duration: 0.5, delay: 0.1 }}
|
||||||
|
>
|
||||||
|
<Link href={'/register'}>Start today</Link>
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className={styles.demo}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<motion.div
|
||||||
|
className={styles.action}
|
||||||
|
variants={{
|
||||||
|
visible: {
|
||||||
|
transition: {
|
||||||
|
delayChildren: 0.2,
|
||||||
|
staggerChildren: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={{ once: true }}
|
||||||
|
>
|
||||||
|
<motion.h2
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, x: '-40px' },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
x: 0,
|
||||||
|
transition: { duration: 0.5 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Talk about your
|
||||||
|
<br />
|
||||||
|
favorite topics
|
||||||
|
</motion.h2>
|
||||||
|
<motion.div
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: { opacity: 1 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Link href={'/register'}>Start now</Link>
|
||||||
|
<p>*An account is required.</p>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
<div className={styles.quoteContainer}>
|
||||||
|
<h3 dangerouslySetInnerHTML={{ __html: `"${quotes[currentQuoteIndex].text}"` }}></h3>
|
||||||
|
<p>{quotes[currentQuoteIndex]["author"]}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* <div className={styles.flashcards}>
|
||||||
|
<p></p>
|
||||||
|
</div> */}
|
||||||
|
<motion.div
|
||||||
|
className={styles.customise}
|
||||||
|
variants={{
|
||||||
|
black: {
|
||||||
|
backgroundColor: 'var(--almost-black)',
|
||||||
|
color: 'var(--white)',
|
||||||
|
},
|
||||||
|
white: {
|
||||||
|
backgroundColor: 'var(--off-white)',
|
||||||
|
color: 'var(--black)',
|
||||||
|
},
|
||||||
|
brand: {
|
||||||
|
backgroundColor: 'var(--brand)',
|
||||||
|
color: 'var(--white)',
|
||||||
|
},
|
||||||
|
cozy: {
|
||||||
|
backgroundColor: 'var(--cozy)',
|
||||||
|
color: 'var(--almost-black)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
animate={selectedTheme}
|
||||||
|
>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.header}>
|
||||||
|
<h2>Hot topics</h2>
|
||||||
|
<div className={styles.topics}>
|
||||||
|
<div className={styles.choice}>
|
||||||
|
<motion.div
|
||||||
|
variants={{
|
||||||
|
black: {
|
||||||
|
borderColor: 'var(--white)',
|
||||||
|
},
|
||||||
|
white: {
|
||||||
|
borderColor: 'var(--black)',
|
||||||
|
},
|
||||||
|
brand: {
|
||||||
|
borderColor: 'var(--white)',
|
||||||
|
},
|
||||||
|
cozy: {
|
||||||
|
borderColor: 'var(--almost-black)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = '/topic/2';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>Topic 1</p> {/* TODO: make it update with the latest topic */}
|
||||||
|
</motion.div>
|
||||||
|
<motion.div
|
||||||
|
variants={{
|
||||||
|
black: {
|
||||||
|
borderColor: 'var(--white)',
|
||||||
|
},
|
||||||
|
white: {
|
||||||
|
borderColor: 'var(--black)',
|
||||||
|
},
|
||||||
|
brand: {
|
||||||
|
borderColor: 'var(--white)',
|
||||||
|
},
|
||||||
|
cozy: {
|
||||||
|
borderColor: 'var(--almost-black)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
//onclick go to other page
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = '/topic/2';
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>Topic 2</p> {/* TODO: make it update with the latest topic */}
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.selector}>
|
||||||
|
<h2>
|
||||||
|
Customise{' '}
|
||||||
|
<motion.span
|
||||||
|
variants={{
|
||||||
|
black: {
|
||||||
|
color: 'var(--brand)',
|
||||||
|
},
|
||||||
|
white: {
|
||||||
|
color: 'var(--brand)',
|
||||||
|
},
|
||||||
|
brand: {
|
||||||
|
color: 'var(--dark-gray)',
|
||||||
|
},
|
||||||
|
cozy: {
|
||||||
|
color: 'var(--white)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
your
|
||||||
|
</motion.span>{' '}
|
||||||
|
<br />
|
||||||
|
experience
|
||||||
|
</h2>
|
||||||
|
<p>With themes</p>
|
||||||
|
<motion.div
|
||||||
|
className={styles.themes}
|
||||||
|
variants={{
|
||||||
|
visible: {
|
||||||
|
transition: {
|
||||||
|
delayChildren: 0.2,
|
||||||
|
staggerChildren: 0.2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={{ once: true }}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--almost-black)',
|
||||||
|
}}
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: '10px' },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: { duration: 0.5 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onMouseOver={() => {
|
||||||
|
setSelectedTheme('black');
|
||||||
|
}}
|
||||||
|
></motion.div>
|
||||||
|
<motion.div
|
||||||
|
style={{ backgroundColor: 'var(--off-white)' }}
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: '10px' },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: { duration: 0.5 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onMouseOver={() => {
|
||||||
|
setSelectedTheme('white');
|
||||||
|
}}
|
||||||
|
></motion.div>
|
||||||
|
<motion.div
|
||||||
|
style={{ backgroundColor: 'var(--brand)' }}
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: '10px' },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: { duration: 0.5 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onMouseOver={() => {
|
||||||
|
setSelectedTheme('brand');
|
||||||
|
}}
|
||||||
|
></motion.div>
|
||||||
|
<motion.div
|
||||||
|
style={{ backgroundColor: 'var(--cozy)' }}
|
||||||
|
variants={{
|
||||||
|
hidden: { opacity: 0, y: '10px' },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: { duration: 0.5 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onMouseOver={() => {
|
||||||
|
setSelectedTheme('cozy');
|
||||||
|
}}
|
||||||
|
></motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
.main{
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.wrapper{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: var(--off-white);
|
||||||
|
border-radius: 16px;
|
||||||
|
|
||||||
|
.loading{
|
||||||
|
font-size: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topinfo{
|
||||||
|
width: 1200px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
|
h1{
|
||||||
|
font-size: 3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.metadata{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.author{
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--brand);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
p{
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p{
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content{
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
pre{
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: var(--black);
|
||||||
|
line-height: 1.5;
|
||||||
|
max-width: 1150px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottominfo{
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin: 4rem 0 2rem;
|
||||||
|
background-color: var(--brand);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
p{
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button:hover{
|
||||||
|
//chance cursor
|
||||||
|
cursor: pointer;
|
||||||
|
//change color
|
||||||
|
background-color: var(--brand-dark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comments{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
'use client'
|
||||||
|
import Link from 'next/link';
|
||||||
|
import styles from './page.module.scss';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
type Post = {
|
||||||
|
title: string;
|
||||||
|
author: string;
|
||||||
|
comments: [];
|
||||||
|
likes: number;
|
||||||
|
saves: number;
|
||||||
|
content: string;
|
||||||
|
category: string;
|
||||||
|
date: string;
|
||||||
|
viewcount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Page({ params }: { params: { slug: string } }) {
|
||||||
|
|
||||||
|
//fetch the data
|
||||||
|
const [post, setPost] = useState<Post>({ title: '', author: '', comments: [], likes: 0, saves: 0, content: '', category: '', date: '', viewcount: 0 });
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
useEffect(() => {
|
||||||
|
async function fetchPostsCategory() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/post/' + params.slug);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch categories');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setPost(data['post'][0]);
|
||||||
|
setLoading(false); // Set loading to false once data is fetched
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching categories:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPostsCategory();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function likePost() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/post/like/' + params.slug, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to like post');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
//set post likes to data.likes
|
||||||
|
setPost({ ...post, likes: data.likes });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error liking post:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function savePost() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/post/save/' + params.slug , {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to save post');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setPost({ ...post, saves: data.saves });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving post:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
{loading ? (
|
||||||
|
<p className={styles.loading}>Loading...</p>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<div className={styles.topinfo}>
|
||||||
|
<h1>{post.title}</h1>
|
||||||
|
<div className={styles.metadata}>
|
||||||
|
<Link href={'/' + post.author}><p className={styles.author}>{post.author}</p></Link>
|
||||||
|
<p>created on: {post.date}</p>
|
||||||
|
<p>{post.viewcount} views</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<pre>{post.content} to see how good it is </pre>
|
||||||
|
</div>
|
||||||
|
<div className={styles.bottominfo}>
|
||||||
|
<button onClick={likePost} className={styles.button}><p>likes: {post.likes}</p></button>
|
||||||
|
<button onClick={savePost} className={styles.button}><p>saves: {post.saves}</p></button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.comments}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ export const metadata: Metadata = {
|
|||||||
openGraph: {
|
openGraph: {
|
||||||
...openGraphMetadata,
|
...openGraphMetadata,
|
||||||
title: 'Create an account',
|
title: 'Create an account',
|
||||||
url: 'https://quiztimes.nl/register',
|
url: 'https://Forum.nl/register',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
$colors: (
|
$colors: (
|
||||||
brand: hsl(237, 100%, 70%),
|
brand: hsl(237, 100%, 70%),
|
||||||
|
brand-dark: hsl(237, 100%, 60%),
|
||||||
|
brand-light: hsl(237, 100%, 80%),
|
||||||
white: hsl(0, 0%, 100%),
|
white: hsl(0, 0%, 100%),
|
||||||
off-white: hsl(0, 0%, 96%),
|
off-white: hsl(0, 0%, 96%),
|
||||||
gray: hsl(0, 0%, 50%),
|
gray: hsl(0, 0%, 50%),
|
||||||
|
|||||||
Reference in New Issue
Block a user