feat: switch to nextjs (#7)

* feat: switch to nextjs

* chore: move everyting to main dir

* fix: blogpostgen

* chore: dot?

* chore: remove blogposts

* fix: everything

* chore: why

* chore: move to public

* chore: fetch

* chore: remove slash

* chore: aedfa

* feat: defenitive nextjs move
This commit is contained in:
2023-11-17 19:29:30 +01:00
committed by GitHub
parent fd1f6c9f15
commit da0982b5ff
50 changed files with 2928 additions and 1985 deletions

View File

@@ -0,0 +1,27 @@
import '../../_css/Root.css';
import React from 'react';
import { SiOsu } from 'react-icons/si';
import { FaDiscord, FaGithub, FaMastodon, FaTwitter, FaBlog } from 'react-icons/fa6';
import Link from "next/link";
import Image from 'next/image';
export default function Page() {
return (
<div>
<div className='aboutMeBox'>
{/* style={{ borderRadius: '70px' }} */}
<Link href='/collab'><Image src='/pfp.webp' alt='main profile picture' width='200' height='200' /></Link>
<p>A spanish hobbyist developer and osu! player</p>
<p>Stalk me on social media:</p>
<div className='icons'>
<Link href='https://github.com/SrIzan10'><FaGithub /></Link>
<Link href='/blog'><FaBlog /></Link>
<Link href='https://discord.com/users/703974042700611634'><FaDiscord /></Link>
<Link href='https://social.srizan.dev'><FaMastodon /></Link>
<Link href='https://twitter.com/itssrizan'><FaTwitter /></Link>
<Link href='https://osu.ppy.sh/users/25350735'><SiOsu /></Link>
</div>
</div>
</div>
)
}

View File

@@ -1,47 +1,36 @@
import { useParams } from "react-router-dom";
import { Helmet } from "react-helmet";
import { useEffect, useState } from "react";
import Head from "next/head";
import * as fs from 'node:fs/promises'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { atomDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
import { BlogNavBar } from "./BlogNavBar.tsx";
import '../css/BlogPost.css';
import BlogNavBar from "../../../_components/BlogNavBar";
import '../../../_css/BlogPost.css';
import React from "react";
import jsonDataArray from '../../../../../public/blogPosts.json';
import { redirect } from "next/navigation";
export function BlogPost() {
const id = Number(useParams().id);
if (isNaN(id)) {
// if it's not a number, redirect to the blog page
window.location.href = '/blog';
}
const [jsonData, setJsonData] = useState<BlogPostJSONResponse>({
export default async function Page({ params }: { params: { id: string } }) {
const id = parseInt(params.id);
if (Number.isNaN(id)) redirect('/blog')
let jsonData = {
id: 0,
title: '',
description: '',
date: '',
fileName: '',
fileContent: ''
});
}
useEffect(() => {
const fetchData = async () => {
const jsonFetch = await import('../../blogPosts.json');
const jsonDataArray = jsonFetch.default;
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
if (filteredPost) {
setJsonData(filteredPost);
} else {
document.location.href = '/blog';
}
};
fetchData();
}, [id]);
const filteredPost = jsonDataArray.filter((post) => post.id === id)[0];
if (filteredPost) {
jsonData = filteredPost;
} else {
redirect('/blog')
}
return (
<div>
<Helmet>
<Head>
<title>{jsonData.title}</title>
<meta name="description" content={jsonData.description} />
<meta name="og:title" content={jsonData.title} />
@@ -49,21 +38,19 @@ export function BlogPost() {
<meta name="og:type" content="article" />
<meta name="og:url" content={`https://srizan.dev/blog/${jsonData.id}`} />
<meta name="og:article:author" content="Sr Izan" />
</Helmet>
</Head>
<BlogNavBar title={jsonData.title} />
<div className={'blogPostContent'}>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={{
code(props) {
const { children, className, ...rest } = props
code({node, className, children, ...props}) {
const match = /language-(\w+)/.exec(className || '')
return match ? (
<SyntaxHighlighter
{...rest}
style={atomDark}
customStyle={{ backgroundColor: '#171717', outline: 'solid' }}
codeTagProps={{ className: 'codeHighlighter' }}
language={match[1]}
PreTag="div"
// eslint-disable-next-line react/no-children-prop
children={String(children).replace(/\n$/, '')}
/>
) : (
@@ -77,6 +64,7 @@ export function BlogPost() {
)
},
img(props) {
// eslint-disable-next-line jsx-a11y/alt-text
return <img {...props} style={{ maxWidth: '100%' }} />
}
}}>

View File

@@ -0,0 +1,30 @@
import '../../../app/_css/Blog.css';
import Head from 'next/head';
import blogPosts from '../../../../public/blogPosts.json'
import BlogPostCard from '../../../app/_components/BlogPostCard';
import BlogNavBar from '../../../app/_components/BlogNavBar';
import BlogRssDial from '@/app/_components/BlogRssDial';
function Blog() {
return (
<div>
<Head>
<title>Blog</title>
<meta name="theme-color" content="#0d0d0d" />
</Head>
<BlogNavBar />
<div className="blogPosts">
{blogPosts.map((post) => {
return (
<BlogPostCard {...post} key={post.id} />
);
})}
</div>
<div className="bottomRight">
<BlogRssDial />
</div>
</div>
);
}
export default Blog;

View File

@@ -1,7 +1,9 @@
import Image from "next/image";
export default function Collab() {
return (
<div>
<img src="/collab.webp" useMap="#image-map" />
<Image src="/collab.webp" useMap="#image-map" alt="Collab image" />
<map name="image-map">
<area target="_blank" alt="Sr Izan" title="Sr Izan" href="https://srizan.dev" coords="0,0,280,316" shape="rect" />

View File

@@ -1,8 +1,13 @@
import { useEffect, useState } from 'react';
import '../css/BlogNavBar.css'
import { Link } from 'react-router-dom';
// sadge
'use client';
export function BlogNavBar(props: Props) {
import { useEffect, useState } from 'react';
import '../_css/BlogNavBar.css'
import Link from 'next/link';
import Pfp from '../../../public/pfp.webp';
import Image from 'next/image';
export default function BlogNavBar(props: Props) {
const [isScrolled, setIsScrolled] = useState(false);
useEffect(() => {
@@ -26,7 +31,7 @@ export function BlogNavBar(props: Props) {
<img src="/pfp.webp" alt="main profile picture" height="50vh" />
<p>{props.title || 'Sr Izan\'s blog'}</p>
</div>
<Link to={props.title ? '/blog' : '/'} className="backHomeLink">Go back {props.title ? 'to posts' : 'home'}</Link>
<Link href={props.title ? '/blog' : '/'} className="backHomeLink">Go back {props.title ? 'to posts' : 'home'}</Link>
</div>
)
}

View File

@@ -1,12 +1,14 @@
"use client"
import Box from '@mui/material/Box';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Typography from '@mui/material/Typography';
import dayjs from 'dayjs'
import customParseFormat from 'dayjs/plugin/customParseFormat.js'
import '../css/BlogPostCard.css'
import '../_css/BlogPostCard.css'
import { Button, CardActions } from "@mui/material";
import {Link} from "react-router-dom";
import Link from "next/link";
dayjs.extend(customParseFormat)
export default function BlogPostCard(props: Props) {
@@ -25,7 +27,7 @@ export default function BlogPostCard(props: Props) {
</Typography>
</CardContent>
<CardActions className={'actions'}>
<Link to={`/blog/${props.id}`}><Button size="small">Read</Button></Link>
<Link href={`/blog/${props.id}`}><Button size="small">Read</Button></Link>
</CardActions>
</Box>
</Card>

View File

@@ -0,0 +1,44 @@
'use client'
import SpeedDial from "@mui/material/SpeedDial";
import SpeedDialAction from "@mui/material/SpeedDialAction";
import { FaRss, FaAtom } from "react-icons/fa6";
import { MdDataObject } from 'react-icons/md'
const actions = [
{ icon: <FaRss />, name: 'RSS' },
{ icon: <MdDataObject />, name: 'JSON' },
{ icon: <FaAtom />, name: 'Atom' }
];
export default function BlogRssDial() {
const handleChange = (event: string) => {
switch (event) {
case 'RSS':
window.location.href = '/blog/rss.xml'
break;
case 'JSON':
window.location.href = '/blog/feed.json'
break;
case 'Atom':
window.location.href = '/blog/atom.xml'
break;
}
}
return (
<SpeedDial
ariaLabel="SpeedDial basic example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<FaRss />}
>
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => handleChange(action.name)}
/>
))}
</SpeedDial>
)
}

View File

@@ -0,0 +1,56 @@
"use client";
import { CacheProvider } from "@emotion/react";
import createCache from "@emotion/cache";
import { useServerInsertedHTML } from "next/navigation";
import { ReactNode, useState } from "react";
import { ThemeProvider } from "@mui/material/styles";
import theme from "./theme";
export function RootStyleRegistry({
children,
}: {
children: ReactNode;
}) {
const [{ cache, flush }] = useState(() => {
const cache = createCache({ key: "my" });
cache.compat = true;
const prevInsert = cache.insert;
let inserted: string[] = [];
cache.insert = (...args) => {
const serialized = args[1];
if (cache.inserted[serialized.name] === undefined) {
inserted.push(serialized.name);
}
return prevInsert(...args);
};
const flush = () => {
const prevInserted = inserted;
inserted = [];
return prevInserted;
};
return { cache, flush };
});
useServerInsertedHTML(() => {
const names = flush();
if (names.length === 0) return null;
let styles = "";
for (const name of names) {
styles += cache.inserted[name];
}
return (
<style
data-emotion={`${cache.key} ${names.join(" ")}`}
dangerouslySetInnerHTML={{
__html: styles,
}}
/>
);
});
return (
<CacheProvider value={cache}>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</CacheProvider>
);
}

View File

@@ -0,0 +1,36 @@
import { Roboto } from 'next/font/google';
import { createTheme } from '@mui/material/styles';
const roboto = Roboto({
weight: ['300', '400', '500', '700'],
subsets: ['latin'],
display: 'swap',
});
// Check if window is defined before accessing its properties
const prefersDarkMode = typeof window !== 'undefined' ? window.matchMedia('(prefers-color-scheme: dark)') : null;
// Add event listener only if prefersDarkMode is defined
if (prefersDarkMode) {
prefersDarkMode.addEventListener('change', () => {
location.reload();
});
}
const theme = createTheme({
palette: {
mode: prefersDarkMode && prefersDarkMode.matches ? 'dark' : 'light',
background: {
default: prefersDarkMode && prefersDarkMode.matches ? '#0d0d0d' : '#fafafa',
paper: prefersDarkMode && prefersDarkMode.matches ? '#0d0d0d' : '#fafafa',
},
primary: {
main: '#646cff',
},
},
typography: {
fontFamily: roboto.style.fontFamily,
},
});
export default theme;

View File

@@ -7,16 +7,18 @@
line-height: 2;
}
code {
font-family: 'JetBrains Mono', monospace;
}
.codeHighlighter {
font-family: 'JetBrains Mono', monospace;
shadow: 0;
code .codeHighlighter {
font-family: var(--font-mono);
}
@media (max-width: 900px) {
.blogPostContent {
padding: 0;
}
}
@media (prefers-color-scheme: light) {
.codeHighlighter {
background-color: #FFF;
}
}

View File

@@ -1,7 +1,6 @@
.aboutMeBox {
width: 480px;
height: 360px;
position: absolute;
top: 50%;
left: 50%;
@@ -17,6 +16,11 @@
.icons * {
margin-right: 20px;
}
.icons svg {
/* change from 1em */
width: 1.5em;
height: 1.5em;
}
.icons *:last-child {
margin-right: 0;
@@ -26,4 +30,4 @@
.aboutMeBox {
width: 100%;
}
}
}

View File

@@ -13,6 +13,9 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
--font-mono: ui-monospace, 'JetBrains Mono', Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
}
a {

28
src/app/layout.tsx Normal file
View File

@@ -0,0 +1,28 @@
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'
import { RootStyleRegistry } from './_components/ThemeRegistry/EmotionRootStyleRegistry'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Sr Izan\'s corner for the net',
icons: { icon: '/pfp.webp' }
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<RootStyleRegistry>
{children}
</RootStyleRegistry>
</body>
</html>
)
}

3
src/blog/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

8
src/blog/.idea/blog.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

7
src/blog/.idea/discord.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="PROJECT_FILES" />
<option name="description" value="" />
</component>
</project>

8
src/blog/.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/blog.iml" filepath="$PROJECT_DIR$/.idea/blog.iml" />
</modules>
</component>
</project>

6
src/blog/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/../.." vcs="Git" />
</component>
</project>

View File

@@ -57,7 +57,7 @@ await glob('./src/blog/**/*.md').then(async (files) => {
}
})
fs.writeFileSync('./blogPosts.json', JSON.stringify(data))
fs.writeFileSync('./public/blogPosts.json', JSON.stringify(data))
fs.writeFileSync('./public/blog/feed.json', feed.json1())
fs.writeFileSync('./public/blog/rss.xml', feed.rss2())
fs.writeFileSync('./public/blog/atom.xml', feed.atom1())

View File

@@ -1,84 +0,0 @@
import React from 'react';
import MuiAlert, { AlertProps } from "@mui/material/Alert";
import Snackbar from "@mui/material/Snackbar";
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import CloseIcon from '@mui/icons-material/Close';
import Modal from '@mui/material/Modal';
import Box from '@mui/material/Box';
import Typography from "@mui/material/Typography";
const modalStyle = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
const Alert = React.forwardRef<HTMLDivElement, AlertProps>(function Alert(
props,
ref,
) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />;
});
export default function AnalyticsNotice() {
const [open, setOpen] = React.useState(window.localStorage.getItem('analyticsNotice') !== 'false');
const [modalOpen, setModalOpen] = React.useState(false);
const handleModalOpen = () => setModalOpen(true);
const handleClose = (_event?: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
setModalOpen(false);
window.localStorage.setItem('analyticsNotice', 'false');
};
const action = (
<React.Fragment>
<Button color="secondary" size="small" onClick={handleModalOpen} sx={{ color: 'white' }}>
WAIT WHAT?
</Button>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={handleClose}
>
<CloseIcon fontSize="small" sx={{ color: 'white' }}/>
</IconButton>
</React.Fragment>
);
return (
<div>
<Snackbar open={open} onClose={handleClose} action={action}>
<Alert severity="info" action={action}>hi this website uses <a href='https://umami.is' style={{ color: "white" }}><u>umami</u></a> for analytics</Alert>
</Snackbar>
<Modal
open={modalOpen}
onClose={() => handleClose()}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={modalStyle}>
<Typography id="modal-modal-title" variant="h6" component="h2">
Analytics
</Typography>
<Typography id="modal-modal-description" sx={{ mt: 2 }}>
Umami is used to track page visits. These aren't sold to anyone and are just to see what you guys like.
Endpoint is https://analytics.srizan.dev
</Typography>
</Box>
</Modal>
</div>
)
}

View File

@@ -1,53 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './pages/App.tsx'
import './css/index.css'
import { createBrowserRouter, RouterProvider } from "react-router-dom";
import { ThemeProvider, createTheme } from '@mui/material/styles';
import Blog from "./pages/Blog.tsx";
import { BlogPost } from "./components/BlogPost.tsx";
import AnalyticsNotice from "./components/AnalyticsNotice.tsx";
import Collab from './pages/Collab.tsx';
const router = createBrowserRouter([
{
path: "/",
element: <App />,
},
{
path: "/blog",
element: <Blog />,
},
{
path: "/blog/:id",
element: <BlogPost />
},
{
path: '/collab',
element: <Collab />
}
]);
const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
const darkTheme = createTheme({
palette: {
mode: prefersDarkMode.matches ? 'dark' : 'light',
background: {
default: prefersDarkMode.matches ? '#0d0d0d' : '#fafafa',
paper: prefersDarkMode.matches ? '#0d0d0d' : '#fafafa',
}
},
});
prefersDarkMode.addEventListener('change', () => {
location.reload()
});
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<ThemeProvider theme={darkTheme}>
<AnalyticsNotice />
<RouterProvider router={router} />
</ThemeProvider>
</React.StrictMode>,
)

View File

@@ -1,30 +0,0 @@
import '../css/App.css'
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faDiscord, faGithub, faMastodon, faTwitter } from '@fortawesome/free-brands-svg-icons'
import { faBlog, faCircle } from '@fortawesome/free-solid-svg-icons'
import { Link } from "react-router-dom";
function App() {
const [osuBeat, setOsuBeat] = React.useState(false)
return (
<div>
<div className='aboutMeBox'>
{/* style={{ borderRadius: '70px' }} */}
<Link to='/collab'><img src='/pfp.webp' alt='main profile picture' height='200px' /></Link>
<p>I'm a hobbyist developer and osu! player based on Spain who loves to open-source and to work on teams.</p>
<p>Stalk me on social media:</p>
<div className='icons'>
<a href='https://github.com/SrIzan10'><FontAwesomeIcon icon={faGithub} /></a>
<Link to='/blog'><FontAwesomeIcon icon={faBlog} /></Link>
<a href='https://discord.com/users/703974042700611634'><FontAwesomeIcon icon={faDiscord} /></a>
<a href='https://social.srizan.dev'><FontAwesomeIcon icon={faMastodon} /></a>
<a href='https://twitter.com/itssrizan'><FontAwesomeIcon icon={faTwitter} /></a>
<a href='https://osu.ppy.sh/users/25350735'><FontAwesomeIcon icon={faCircle} onMouseEnter={() => setOsuBeat(true)} onMouseLeave={() => setOsuBeat(false)} beatFade={osuBeat} /></a>
</div>
</div>
</div>
)
}
export default App

View File

@@ -1,66 +0,0 @@
import '../css/Blog.css';
import { Helmet } from 'react-helmet';
import blogPosts from '../../blogPosts.json'
import BlogPostCard from "../components/BlogPostCard.tsx";
import { BlogNavBar } from "../components/BlogNavBar.tsx";
import { faRss, faAtom } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import SpeedDial from '@mui/material/SpeedDial';
import SpeedDialAction from '@mui/material/SpeedDialAction';
import DataObject from "@mui/icons-material/DataObject";
const actions = [
{ icon: <FontAwesomeIcon icon={faRss} />, name: 'RSS' },
{ icon: <DataObject />, name: 'JSON' },
{ icon: <FontAwesomeIcon icon={faAtom} />, name: 'Atom' }
];
function Blog() {
const handleChange = (event: string) => {
switch (event) {
case 'RSS':
window.location.href = '/blog/rss.xml'
break;
case 'JSON':
window.location.href = '/blog/feed.json'
break;
case 'Atom':
window.location.href = '/blog/atom.xml'
break;
}
}
return (
<div>
<Helmet>
<title>Blog | Sr Izan's website</title>
<meta name="theme-color" content="#0d0d0d" />
</Helmet>
<BlogNavBar />
<div className="blogPosts">
{blogPosts.map((post) => {
return (
<BlogPostCard {...post} />
);
})}
</div>
<div className="bottomRight">
<SpeedDial
ariaLabel="SpeedDial basic example"
sx={{ position: 'absolute', bottom: 16, right: 16 }}
icon={<FontAwesomeIcon icon={faRss} />}
>
{actions.map((action) => (
<SpeedDialAction
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={() => handleChange(action.name)}
/>
))}
</SpeedDial>
</div>
</div>
);
}
export default Blog;

1
src/vite-env.d.ts vendored
View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />