diff --git a/app/blog/[slug]/not-found.tsx b/app/blog/[slug]/not-found.tsx index ae1991c..dfdcf37 100644 --- a/app/blog/[slug]/not-found.tsx +++ b/app/blog/[slug]/not-found.tsx @@ -1,13 +1,15 @@ -import Link from "next/link"; +import Link from 'next/link'; -export default function NotFound() { +const NotFound = () => { return (

Post Not Found

- + Go to list page
); -} +}; + +export default NotFound; diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx index 089761d..268b739 100644 --- a/app/blog/[slug]/page.tsx +++ b/app/blog/[slug]/page.tsx @@ -1,20 +1,16 @@ -import { Metadata } from "next"; -import Link from "next/link"; -import { notFound } from "next/navigation"; +import { Metadata } from 'next'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; -import { getRecordMap } from "../../../lib/notion"; -import { getAllPostsFromNotion } from "../../../lib/posts"; -import { Post } from "../../../types/post"; -import { PostPageContainer } from "../../../components/posts/custom-container"; +import { getRecordMap } from '../../../lib/notion'; +import { getPostsFromNotion } from '../../../lib/posts'; +import PostView from '../../../components/posts/post-view'; +import type { Post } from '../../../types/post'; -export default async function PostPage({ - params: { slug }, -}: { - params: { slug: string }; -}) { - const allPosts = await getAllPostsFromNotion(); +const PostPage = async ({ params: { slug } }: { params: { slug: string } }) => { + const posts = await getPostsFromNotion(); - const post = allPosts.find((p) => p.slug === slug); + const post = posts.find((p) => p.slug === slug); if (!post) { return notFound(); } @@ -23,7 +19,7 @@ export default async function PostPage({ return (

Post Not Found

- + Go to list page @@ -31,7 +27,7 @@ export default async function PostPage({ ); } - const relatedPosts: Post[] = allPosts.filter( + const relatedPosts: Post[] = posts.filter( (p) => p.slug !== slug && p.published && @@ -41,33 +37,31 @@ export default async function PostPage({ const recordMap = await getRecordMap(post.id); return ( - + ); -} +}; -export async function generateStaticParams() { - const allPosts = await getAllPostsFromNotion(); +export const generateStaticParams = async () => { + const posts = await getPostsFromNotion(); - return allPosts.map((post) => ({ + return posts.map((post) => ({ slug: post.slug, })); -} +}; -export async function generateMetadata({ +export const generateMetadata = async ({ params: { slug }, }: { params: { slug: string }; -}): Promise { - const allPosts = await getAllPostsFromNotion(); - const post = allPosts.find((p) => p.slug === slug); +}): Promise => { + const posts = await getPostsFromNotion(); + const post = posts.find((p) => p.slug === slug); return post ? { title: post.title, } : {}; -} +}; + +export default PostPage; diff --git a/app/blog/page.tsx b/app/blog/page.tsx index 977a98f..9c2c9bd 100644 --- a/app/blog/page.tsx +++ b/app/blog/page.tsx @@ -1,10 +1,10 @@ -import { PostListContainer } from '../../components/posts/custom-container'; -import { getAllPostsFromNotion } from '../../lib/posts'; +import PostList from '../../components/posts/post-list'; +import { getPostsFromNotion } from '../../lib/posts'; const Blog = async () => { - const posts = await getAllPostsFromNotion(); + const posts = await getPostsFromNotion(); - return ; + return ; }; export default Blog; diff --git a/app/layout.tsx b/app/layout.tsx index 7ff5a8b..425653c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,25 +1,21 @@ -import { Analytics } from "@vercel/analytics/react"; -import { SpeedInsights } from "@vercel/speed-insights/next"; -import CustomProviders from "../components/custom-providers"; -import "./globals.css"; +import { Analytics } from '@vercel/analytics/react'; +import { SpeedInsights } from '@vercel/speed-insights/next'; +import CustomProvider from '../components/custom-provider'; +import './globals.css'; -const theme = "dark"; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - // NOTE: suppressHydrationWarning is a hack to avoid warnings - // see: https://legacy.reactjs.org/docs/dom-elements.html#suppresshydrationwarning:~:text=It%20only%20works%20one%20level%20deep +// NOTE: suppressHydrationWarning is a hack to avoid warnings +// see: https://legacy.reactjs.org/docs/dom-elements.html#suppresshydrationwarning:~:text=It%20only%20works%20one%20level%20deep +const RootLayout = ({ children }: { children: React.ReactNode }) => { return ( - + - {children} + {children} ); -} +}; + +export default RootLayout; diff --git a/app/not-found.tsx b/app/not-found.tsx index 89c8683..9272e20 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -1,4 +1,4 @@ -import Link from "next/link"; +import Link from 'next/link'; const NotFound = () => { return ( @@ -6,7 +6,7 @@ const NotFound = () => {

Not Found

Could not find requested resource

- Go back to Home + Go back to Home
); diff --git a/app/page.tsx b/app/page.tsx index 20c2846..e19994c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,23 +1,23 @@ -import type { NextPage, Metadata } from "next"; +import type { NextPage, Metadata } from 'next'; -import About from "../components/about"; -import Experience from "../components/experience"; -import Projects from "../components/projects"; +import About from '../components/about'; +import Experience from '../components/experience'; +import Projects from '../components/projects'; export const metadata: Metadata = { - title: "Yuta Kusuno Portfolio", + title: 'Yuta Kusuno', }; const Home: NextPage = () => { return ( <> -
+
-
+
-
+
diff --git a/components/about.tsx b/components/about.tsx index 1903d56..101d689 100644 --- a/components/about.tsx +++ b/components/about.tsx @@ -1,8 +1,8 @@ -import { Container } from "@chakra-ui/react"; +import { Container } from '@chakra-ui/react'; -import { NotionProfilePage } from "./notion-profile-page"; -import { notion } from "../lib/notion"; -import { notionAboutPageId } from "../lib/config"; +import { NotionProfilePage } from './notion/notion-profile-page'; +import { notion } from '../lib/notion'; +import { notionAboutPageId } from '../lib/config'; const About = async () => { if (!notionAboutPageId) return null; @@ -12,10 +12,10 @@ const About = async () => { return ( diff --git a/components/custom-providers.tsx b/components/custom-provider.tsx similarity index 90% rename from components/custom-providers.tsx rename to components/custom-provider.tsx index accb08b..7d57c04 100644 --- a/components/custom-providers.tsx +++ b/components/custom-provider.tsx @@ -7,7 +7,7 @@ import { ThemeProvider as NextThemeProvider } from 'next-themes'; import Navbar from './navbar'; import Footer from './footer'; -const CustomProviders = (props: { +const CustomProvider = (props: { theme: string; children: React.ReactNode; }) => { @@ -26,4 +26,4 @@ const CustomProviders = (props: { ); }; -export default CustomProviders; +export default CustomProvider; diff --git a/components/experience.tsx b/components/experience.tsx index d44dcc7..b79e126 100644 --- a/components/experience.tsx +++ b/components/experience.tsx @@ -1,8 +1,8 @@ -import { Container } from "@chakra-ui/react"; +import { Container } from '@chakra-ui/react'; -import { NotionProfilePage } from "./notion-profile-page"; -import { notion } from "../lib/notion"; -import { notionExperiencePageId } from "../lib/config"; +import { NotionProfilePage } from './notion/notion-profile-page'; +import { notion } from '../lib/notion'; +import { notionExperiencePageId } from '../lib/config'; const Experience = async () => { if (!notionExperiencePageId) return null; @@ -12,10 +12,10 @@ const Experience = async () => { return ( diff --git a/components/utils/category-filter.tsx b/components/filters/category-filter.tsx similarity index 100% rename from components/utils/category-filter.tsx rename to components/filters/category-filter.tsx diff --git a/components/category-list.tsx b/components/filters/category-list.tsx similarity index 100% rename from components/category-list.tsx rename to components/filters/category-list.tsx diff --git a/components/utils/category.tsx b/components/filters/category.tsx similarity index 100% rename from components/utils/category.tsx rename to components/filters/category.tsx diff --git a/components/utils/search-bar.tsx b/components/filters/search-bar.tsx similarity index 100% rename from components/utils/search-bar.tsx rename to components/filters/search-bar.tsx diff --git a/components/navbar.tsx b/components/navbar.tsx index 0b8d86d..06b2752 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -1,7 +1,7 @@ -import { useEffect } from "react"; -import { Flex, Button, useColorMode, ButtonGroup } from "@chakra-ui/react"; -import { MoonIcon, SunIcon } from "@chakra-ui/icons"; -import NextLink from "next/link"; +import { useEffect } from 'react'; +import { Flex, Button, useColorMode, ButtonGroup } from '@chakra-ui/react'; +import { MoonIcon, SunIcon } from '@chakra-ui/icons'; +import NextLink from 'next/link'; type PageList = { name: string; @@ -9,10 +9,10 @@ type PageList = { }; const pageList: Array = [ - { name: "About", path: "/#about" }, - { name: "Experience", path: "/#experience" }, - { name: "Projects", path: "/#projects" }, - { name: "Blog", path: "/blog" }, + { name: 'About', path: '/#about' }, + { name: 'Experience', path: '/#experience' }, + { name: 'Projects', path: '/#projects' }, + { name: 'Blog', path: '/blog' }, ]; const Navbar = (props: { theme: string }) => { @@ -28,24 +28,24 @@ const Navbar = (props: { theme: string }) => { return ( - + {pageList.map((item, idx) => ( ))} diff --git a/components/notion-blog-page.tsx b/components/notion/notion-blog-page.tsx similarity index 52% rename from components/notion-blog-page.tsx rename to components/notion/notion-blog-page.tsx index aea31b0..dc22643 100644 --- a/components/notion-blog-page.tsx +++ b/components/notion/notion-blog-page.tsx @@ -1,13 +1,13 @@ -"use client"; +'use client'; -import dynamic from "next/dynamic"; -import { NotionRenderer } from "react-notion-x"; -import { ExtendedRecordMap } from "notion-types"; -import { Flex, Text, useColorMode } from "@chakra-ui/react"; -import "react-notion-x/src/styles.css"; +import dynamic from 'next/dynamic'; +import { NotionRenderer } from 'react-notion-x'; +import { ExtendedRecordMap } from 'notion-types'; +import { Flex, Text, useColorMode } from '@chakra-ui/react'; +import 'react-notion-x/src/styles.css'; -import CategoryList from "./category-list"; -import { Post } from "../types/post"; +import CategoryList from '../filters/category-list'; +import { Post } from '../../types/post'; export const NotionBlogPage = ({ post, @@ -21,12 +21,12 @@ export const NotionBlogPage = ({ return ( - + Posted on {post.date} @@ -44,28 +44,28 @@ export const NotionBlogPage = ({ }; const Code = dynamic(() => - import("react-notion-x/build/third-party/code").then((m) => m.Code) + import('react-notion-x/build/third-party/code').then((m) => m.Code) ); const Collection = dynamic(() => - import("react-notion-x/build/third-party/collection").then( + import('react-notion-x/build/third-party/collection').then( (m) => m.Collection ) ); const Equation = dynamic(() => - import("react-notion-x/build/third-party/equation").then((m) => m.Equation) + import('react-notion-x/build/third-party/equation').then((m) => m.Equation) ); const Pdf = dynamic( - () => import("react-notion-x/build/third-party/pdf").then((m) => m.Pdf), + () => import('react-notion-x/build/third-party/pdf').then((m) => m.Pdf), { ssr: false, } ); const Modal = dynamic( - () => import("react-notion-x/build/third-party/modal").then((m) => m.Modal), + () => import('react-notion-x/build/third-party/modal').then((m) => m.Modal), { ssr: false, } diff --git a/components/notion-profile-page.tsx b/components/notion/notion-profile-page.tsx similarity index 100% rename from components/notion-profile-page.tsx rename to components/notion/notion-profile-page.tsx diff --git a/components/posts/custom-container.tsx b/components/posts/custom-container.tsx deleted file mode 100644 index b8236d7..0000000 --- a/components/posts/custom-container.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { ExtendedRecordMap } from 'notion-types'; -import { Container, Grid, GridItem } from '@chakra-ui/react'; - -import CategoryFilter from '../utils/category-filter'; -import SearchBar from '../utils/search-bar'; -import PostsGrid from './posts-grid'; -import { NotionBlogPage } from '../notion-blog-page'; -import RelatedPosts from './related-posts'; -import { CategoryProvider } from '../contexts/category-context'; -import { toUniqueArray } from '../../lib/to-unique-array'; -import type { Post } from '../../types/post'; - -export const PostListContainer = ({ posts }: { posts: Post[] }) => { - const [query, setQuery] = useState(''); - const categories: string[] = toUniqueArray( - posts - .filter((post) => post.published) - .map((post) => post.categories) - .flat(1) - ).sort(); - - return ( - - - - - - - - - - - - ); -}; - -export const PostPageContainer = (props: { - post: Post; - recordMap: ExtendedRecordMap; - relatedPosts: Post[]; -}) => { - const { post, recordMap, relatedPosts } = props; - - return ( - - - - - ); -}; diff --git a/components/posts/post-card.tsx b/components/posts/post-card.tsx index 344f18a..85a31b4 100644 --- a/components/posts/post-card.tsx +++ b/components/posts/post-card.tsx @@ -3,8 +3,8 @@ import NextLink from 'next/link'; import { Text, Heading, Card, CardBody, Link, Flex } from '@chakra-ui/react'; -import CategoryList from '../category-list'; -import { Post } from '../../types/post'; +import CategoryList from '../filters/category-list'; +import type { Post } from '../../types/post'; const PostCard = ({ post: { slug, title, date, categories, outerLink }, diff --git a/components/posts/post-list.tsx b/components/posts/post-list.tsx new file mode 100644 index 0000000..26ed3a1 --- /dev/null +++ b/components/posts/post-list.tsx @@ -0,0 +1,39 @@ +'use client'; + +import { useState } from 'react'; +import { Container, Grid, GridItem } from '@chakra-ui/react'; + +import CategoryFilter from '../filters/category-filter'; +import SearchBar from '../filters/search-bar'; +import PostsGrid from './posts-grid'; +import { CategoryProvider } from '../contexts/category-context'; +import type { Post } from '../../types/post'; + +const PostList = ({ posts }: { posts: Post[] }) => { + const [query, setQuery] = useState(''); + + const categories: string[] = [ + ...new Set( + posts + .filter((post) => post.published) + .map((post) => post.categories) + .flat(1) + ), + ].sort(); + + return ( + + + + + + + + + + + + ); +}; + +export default PostList; diff --git a/components/posts/post-view.tsx b/components/posts/post-view.tsx new file mode 100644 index 0000000..31efd68 --- /dev/null +++ b/components/posts/post-view.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { ExtendedRecordMap } from 'notion-types'; +import { Container } from '@chakra-ui/react'; + +import { NotionBlogPage } from '../notion/notion-blog-page'; +import RelatedPosts from './related-posts'; +import type { Post } from '../../types/post'; + +const PostView = (props: { + post: Post; + recordMap: ExtendedRecordMap; + relatedPosts: Post[]; +}) => { + const { post, recordMap, relatedPosts } = props; + + return ( + + + + + ); +}; + +export default PostView; diff --git a/components/posts/posts-grid.tsx b/components/posts/posts-grid.tsx index 08ff0c2..1efe6e9 100644 --- a/components/posts/posts-grid.tsx +++ b/components/posts/posts-grid.tsx @@ -5,7 +5,6 @@ import { Box, VStack } from '@chakra-ui/react'; import PostCard from './post-card'; import { CategoryContext } from '../contexts/category-context'; -import { toUniqueArray } from '../../lib/to-unique-array'; import type { Post } from '../../types/post'; const PostsGrid = ({ posts, query }: { posts: Post[]; query: string }) => { @@ -13,7 +12,7 @@ const PostsGrid = ({ posts, query }: { posts: Post[]; query: string }) => { const filteredPosts = useMemo( () => - posts.filter((post) => { + posts.filter((post: Post) => { if (!post.published) return false; if ( @@ -24,7 +23,7 @@ const PostsGrid = ({ posts, query }: { posts: Post[]; query: string }) => { } if (selectedCategories.length > 0) { - const isCategoryMatch = selectedCategories.every((category) => + const isCategoryMatch = selectedCategories.every((category: string) => post.categories.includes(category) ); if (!isCategoryMatch) return false; @@ -36,19 +35,21 @@ const PostsGrid = ({ posts, query }: { posts: Post[]; query: string }) => { [posts, selectedCategories, query] ); - filteredPosts.sort((postA, postB) => (postA.date > postB.date ? -1 : 1)); + filteredPosts.sort((postA: Post, postB: Post) => + postA.date > postB.date ? -1 : 1 + ); // Update categories in context useEffect(() => { - setCategories( - toUniqueArray(filteredPosts.map((post) => post.categories).flat()) - ); + setCategories([ + ...new Set(filteredPosts.map((post: Post) => post.categories).flat(1)), + ]); }, [filteredPosts, setCategories]); return ( {filteredPosts.length ? ( - filteredPosts.map((post) => ( + filteredPosts.map((post: Post) => ( diff --git a/components/posts/related-posts.tsx b/components/posts/related-posts.tsx index 8fb5a3f..ba3238a 100644 --- a/components/posts/related-posts.tsx +++ b/components/posts/related-posts.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import { Box, Button, Center, Heading, SimpleGrid } from '@chakra-ui/react'; import PostCard from './post-card'; -import { Post } from '../../types/post'; +import type { Post } from '../../types/post'; const INITIAL_NUM_POSTS = 3; const ADDITIONAL_NUM_POSTS = 3; @@ -21,7 +21,7 @@ const RelatedPosts = ({ posts }: { posts: Post[] }) => { return ( Related Posts - {posts.slice(0, numPosts).map((post) => ( + {posts.slice(0, numPosts).map((post: Post) => ( diff --git a/components/projects.tsx b/components/projects.tsx index e4a9a5d..47367b2 100644 --- a/components/projects.tsx +++ b/components/projects.tsx @@ -1,7 +1,7 @@ -"use client"; +'use client'; -import { useState } from "react"; -import type { NextPage } from "next"; +import { useState } from 'react'; +import type { NextPage } from 'next'; import { Button, ButtonGroup, @@ -18,16 +18,16 @@ import { Text, Tabs, SimpleGrid, -} from "@chakra-ui/react"; +} from '@chakra-ui/react'; -import projectsData from "../public/data/projects.json"; -import CategoryList from "./category-list"; +import projectsData from '../public/data/projects.json'; +import CategoryList from './filters/category-list'; enum TabMenu { - All = "All", - Web = "Web", - Mobile = "Mobile", - OSS = "OSS", + All = 'All', + Web = 'Web', + Mobile = 'Mobile', + OSS = 'OSS', } const Projects: NextPage = () => { @@ -50,16 +50,16 @@ const Projects: NextPage = () => { }; return ( - - + + Projects - + {Object.keys(TabMenu).map((menu, idx) => { return ( - filterProjects(menu)}> + filterProjects(menu)}> {menu} ); @@ -71,16 +71,16 @@ const Projects: NextPage = () => { {projectsList.map((project, idx) => { return ( - + Green double couch with wooden legs - - {project.title} - {project.description} + + {project.title} + {project.description} @@ -88,14 +88,14 @@ const Projects: NextPage = () => { - + {project.link.github && ( - )} {project.link.live && ( - )} diff --git a/components/date.tsx b/components/utils/date.tsx similarity index 100% rename from components/date.tsx rename to components/utils/date.tsx diff --git a/lib/posts.ts b/lib/posts.ts index 7cd967a..6133e16 100644 --- a/lib/posts.ts +++ b/lib/posts.ts @@ -1,8 +1,8 @@ import { getRecordMap } from './notion'; -import { Post } from '../types/post'; import { notionBlogDatabaseId } from './config'; +import type { Post } from '../types/post'; -export async function getAllPostsFromNotion() { +const getPostsFromNotion = async () => { const posts: Post[] = []; const recordMap = await getRecordMap(notionBlogDatabaseId!); const { block, collection } = recordMap; @@ -50,4 +50,6 @@ export async function getAllPostsFromNotion() { }); return posts; -} +}; + +export { getPostsFromNotion }; diff --git a/lib/to-unique-array.ts b/lib/to-unique-array.ts deleted file mode 100644 index 3f76df4..0000000 --- a/lib/to-unique-array.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function toUniqueArray(array: U[]) { - return [...new Set(array)]; -}