Skip to content
Snippets Groups Projects
Commit 7e477401 authored by Marc's avatar Marc
Browse files

feat!: migrate to NextJS

parent 2239f56b
No related branches found
No related tags found
1 merge request!1Migrate to NextJS
Showing with 234 additions and 198 deletions
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
settings: {
react: {
version: 'detect',
},
},
parser: '@typescript-eslint/parser', // allows ESLint to understand TypeScript
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
extends: [
'plugin:react/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
plugins: ['@typescript-eslint'],
rules: {
'no-console': 'off',
'object-shorthand': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'react/react-in-jsx-scope': 'off',
'react/no-unescaped-entities': 'off',
},
};
{
"root": true,
"extends": [
"next",
"prettier",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"plugins": ["@typescript-eslint", "prettier"]
}
......@@ -4,27 +4,41 @@
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
/dist
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# custom
/public/.gitlab-ci.yml
# Resume
public/MarcLi-CV.pdf
public/MarcLi-Resume.pdf
# resume
/public/MarcLi-CV.pdf
/public/MarcLi-Resume.pdf
# The Docker image that will be used to deploy your app
image: alpine:3.17
pages:
script:
- mkdir .public
- cp -r * .public
- mv .public public
artifacts:
paths:
# The folder that contains the files to be exposed at the Page URL
- public
rules:
# This ensures that only pushes to the according branch will trigger
# a pages deploy
- if: $CI_COMMIT_REF_NAME == "gh-pages"
......@@ -10,21 +10,41 @@
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
/dist
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
# custom
/public/.gitlab-ci.yml
# resume
/public/MarcLi-CV.pdf
/public/MarcLi-Resume.pdf
......@@ -3,10 +3,10 @@ FRONT_DIR := ./
DOCKER := docker
DC := docker-compose
all: up logs
all: up
up:
$(DC) up --build --detach
$(DC) up --build
exec:
$(DOCKER) exec -it $(NAME) sh
......
......@@ -4,9 +4,9 @@ services:
webapp:
image: node:20.6-alpine3.18
container_name: portfolio_front
working_dir: "/frontend"
working_dir: "/portfolio"
volumes:
- ./:/frontend
- ./:/portfolio
command: "sh -c 'yarn install && yarn run dev'"
# command: "sh -c 'sleep infinity'"
ports:
......
// Use type safe message keys with `next-intl`
type Messages = typeof import('./locales/en.json');
declare interface IntlMessages extends Messages {}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- HTML Meta Tags -->
<title>Marc Li</title>
<meta name="title" content="Marc Li" />
<meta
name="description"
content="Hi, I'm a Software Engineer, and here you can learn more about me and download my resume!"
/>
<!-- Open Graph / Facebook -->
<meta property="og:url" content="https://marcli.com/" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Marc Li" />
<meta
property="og:description"
content="Hi, I'm a Software Engineer, and here you can learn more about me and download my resume!"
/>
<meta property="og:image" content="/meta-preview.png" />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content="https://marcli.com/" />
<meta property="twitter:title" content="Marc Li" />
<meta
property="twitter:description"
content="Hi, I'm a Software Engineer, and here you can learn more about me and download my resume!"
/>
<meta property="twitter:image" content="/meta-preview.png" />
<link rel="manifest" href="/manifest.json" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<!-- Google tag (gtag.js) -->
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-6N9TQ7R4S6"
></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-6N9TQ7R4S6');
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
File moved
File moved
// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNextIntl = require('next-intl/plugin')();
/** @type {import('next').NextConfig} */
const nextConfig = {};
module.exports = withNextIntl(nextConfig);
{
"name": "portfolio",
"version": "2.0.0",
"version": "3.0.0",
"private": true,
"scripts": {
"dev": "vite --host",
"build": "tsc --skipLibCheck && vite build",
"serve": "vite preview --host",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"prepare": "husky install"
},
"dependencies": {
"@emotion/react": "^11.11.1",
"@mantine/core": "^6.0.20",
"@mantine/hooks": "^6.0.20",
"axios": "^1.5.0",
"i18next": "^23.5.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^13.2.2",
"react-router-dom": "^6.15.0",
"zustand": "^4.4.1"
"classnames": "^2.3.2",
"next": "14.0.3",
"next-intl": "^3.1.0",
"next-themes": "^0.2.1",
"react": "^18",
"react-dom": "^18",
"zustand": "^4.4.6"
},
"devDependencies": {
"@commitlint/cli": "^17.7.1",
"@commitlint/config-conventional": "^17.7.0",
"@types/axios": "^0.14.0",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"@typescript-eslint/parser": "^6.6.0",
"@vitejs/plugin-react": "^4.0.4",
"autoprefixer": "^10.4.15",
"eslint": "^8.49.0",
"@commitlint/cli": "^18.4.2",
"@commitlint/config-conventional": "^18.4.2",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.12.0",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.0.3",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-react": "^7.33.2",
"husky": "^8.0.0",
"postcss": "^8.4.29",
"prettier": "^3.0.3",
"prettier-plugin-tailwindcss": "^0.5.4",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-eslint": "^1.8.1"
"eslint-plugin-prettier": "^5.0.1",
"husky": "^8.0.3",
"postcss": "^8",
"prettier": "^3.1.0",
"prettier-plugin-tailwindcss": "^0.5.7",
"tailwindcss": "^3.3.0",
"typescript": "^5"
}
}
import React, { FC } from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { MantineProvider } from '@mantine/core';
import '~/plugins/axios';
import '~/plugins/i18n';
import Page from './components/Page';
import Home from '~/views/Home';
import NotFound from '~/views/NotFound';
const App: FC = () => {
const basename = location.hostname === 'mli42.gitlab.io' ? '/portfolio' : '/';
return (
<MantineProvider>
<BrowserRouter basename={basename}>
<Routes>
<Route path="/" element={<Page View={Home} title="Welcome" />} />
<Route path="*" element={<Page View={NotFound} title="404" />} />
</Routes>
</BrowserRouter>
</MantineProvider>
);
};
export default App;
import { notFound } from 'next/navigation';
export default function Page() {
notFound();
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('./tailwind-preflight.css');
@layer base {
body {
@apply font-sourceCodePro
}
}
import type { Metadata } from 'next';
import { Source_Code_Pro } from 'next/font/google';
import './globals.css';
import './waves.css';
import { locales } from '@/i18n';
import { notFound } from 'next/navigation';
import classNames from 'classnames';
import { Locale } from '@/types/i18n';
import { NextIntlClientProvider, useMessages } from 'next-intl';
import Provider from './themeProvider';
import Navbar from '@/components/Organisms/Navbar';
const sourceCodePro = Source_Code_Pro({
subsets: ['latin'],
variable: '--font-source-code-pro',
});
const title = 'Marc Li';
const description =
"Hi, I'm a Software Engineer, and here you can learn more about me and download my resume!";
const siteURL = 'https://marcli.com';
type RootLayoutProps = {
children: React.ReactNode;
params: Record<string, string>;
};
export const metadata: Metadata = {
metadataBase: new URL(siteURL),
title: {
default: title,
template: `%s | ${title}`,
},
description,
manifest: '/manifest.json',
openGraph: {
type: 'website',
url: siteURL,
title,
description,
siteName: title,
images: [{ url: '/meta-preview.png' }],
},
twitter: {
card: 'summary_large_image',
},
};
export default function RootLayout({
children,
params: { locale },
}: RootLayoutProps) {
if (!locales.includes(locale as Locale)) notFound();
const messages = useMessages();
return (
<html lang={locale} suppressHydrationWarning>
<body
className={classNames(
'text-dark dark:bg-dark dark:text-gray-300',
sourceCodePro.variable
)}
>
<NextIntlClientProvider locale={locale} messages={messages}>
<Provider>
<Navbar />
{children}
</Provider>
</NextIntlClientProvider>
</body>
</html>
);
}
import Link from 'next/link';
export default async function NotFound() {
return (
<div>
<h2>Not Found</h2>
<p>Could not find requested resource</p>
<Link href="/">Return Home</Link>
</div>
);
}
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '~/components/Atoms/Button';
import AppLink from '~/components/Atoms/AppLink';
import Footer from '~/components/Organisms/Footer';
import useColorModeStore from '~/stores/colorMode.store';
import useI18nStore from '~/stores/i18n.store';
import AppLink from '@/components/Atoms/AppLink';
import Button from '@/components/Atoms/Button';
import Footer from '@/components/Organisms/Footer';
import { useLocale, useTranslations } from 'next-intl';
import Image from 'next/image';
const Home: FC = () => {
const { colorMode } = useColorModeStore();
const { locale } = useI18nStore();
const { t } = useTranslation();
const gradient = colorMode === 'light' ? 'lightGradientBg' : 'darkGradientBg';
export default function Page() {
const t = useTranslations();
const locale = useLocale();
const resumeLink = locale === 'fr' ? '/MarcLi-CV.pdf' : '/MarcLi-Resume.pdf';
return (
<div className="flex h-screen w-full flex-col justify-between overflow-y-hidden">
<div className={`h-full ${gradient}`}>
{/* <!--Content before waves--> */}
<main className="flex h-screen w-full flex-col justify-between overflow-y-hidden">
<div className="h-full bg-[linear-gradient(60deg,var(--tw-gradient-stops))] from-[rgb(84,58,183)] to-[rgb(0,172,193)] dark:from-[rgb(100,10,10)] dark:to-[rgb(0,70,70)]">
<div className="flex h-5/6 flex-col items-center justify-center gap-y-4">
<img
className="h-40 w-40 rounded-full border-2 border-white sm:h-48 sm:w-48"
<Image
className="rounded-full border-2 border-white sm:h-48 sm:w-48"
width="160"
height="160"
src="/avatar.png"
alt="Marc's profile picture"
priority
/>
<h1 className="text-4xl text-white sm:text-5xl">Marc Li</h1>
<AppLink className="mt-2" href={resumeLink}>
<AppLink className="mt-2" href={resumeLink} disableI18N>
<Button>{t('downloadResume')}</Button>
</AppLink>
</div>
</div>
<Footer className="h-1/4 animate-footer" />
</div>
</main>
);
};
export default Home;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment