diff --git a/.eslintignore b/.eslintignore index 1437c53..7fcb275 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +*.lock # testing /coverage diff --git a/.gitignore b/.gitignore index d0b842f..db238fd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +*.lock # testing /coverage diff --git a/.prettierignore b/.prettierignore index 1437c53..7fcb275 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +*.lock # testing /coverage diff --git a/app/about/head.js b/app/about/head.js index 4aa05f0..655870f 100644 --- a/app/about/head.js +++ b/app/about/head.js @@ -1,16 +1,11 @@ +import DefaultTags from "@app-components/head/default-tags"; + function AboutHead() { return ( <> About Me - Pravasta Caraka - - - - - - - - + ); } diff --git a/app/about/layout.js b/app/about/layout.js new file mode 100644 index 0000000..a4f7dda --- /dev/null +++ b/app/about/layout.js @@ -0,0 +1,11 @@ +import { Stack } from "@app-providers/chakra-ui"; + +function AboutLayout({ children }) { + return ( + + {children} + + ); +} + +export default AboutLayout; diff --git a/app/about/page.js b/app/about/page.js index ad1f396..8dbc3d5 100644 --- a/app/about/page.js +++ b/app/about/page.js @@ -1,93 +1,97 @@ -"use client"; - import { KnowledgeCard } from "@app-components/Card"; -import { BaseMarkdown } from "@app-components/markdown"; -import { AspectRatio, SimpleGrid, Skeleton, SkeletonText, Stack } from "@chakra-ui/react"; -import { useGetAbout, useGetKnowledge } from "helper/api.helper"; +import { CustomReactMarkdown } from "@app-components/markdown"; +import { AspectRatio, SimpleGrid } from "@app-providers/chakra-ui"; import Image from "next/image"; -import ReactMarkdown from "react-markdown"; +import { getPlaiceholder } from "plaiceholder"; + +async function fetchAboutData() { + const res = await fetch(`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/About%20Me`, { + method: "GET", + headers: { + Authorization: `Bearer ${process.env.AIRTABLE_API_KEY}`, + }, + next: { revalidate: 60 }, + }); + + if (!res.ok) { + throw new Error("Failed to fetch data"); + } + + const json = await res.json(); + + const records = await Promise.all( + json.records.map(async (record) => { + const { base64, img } = await getPlaiceholder(record.fields.cover_im[0]?.url); + record.fields.cover_im = { src: img.src, type: img.type, blurDataURL: base64 }; + return { id: record.id, ...record.fields }; + }) + ).then((values) => values); + + return records[0]; +} + +async function fetchKnowledgeData() { + const res = await fetch(`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/Knowledge%20Base`, { + method: "GET", + headers: { + Authorization: `Bearer ${process.env.AIRTABLE_API_KEY}`, + }, + next: { revalidate: 60 }, + }); -function Page() { - const { about, aboutError, aboutLoading } = useGetAbout(); - const { knowledge, knowledgeError, knowledgeLoading } = useGetKnowledge(); + if (!res.ok) { + throw new Error("Failed to fetch data"); + } - const languages = knowledge?.data?.filter((val) => val.fields.type === "language"); - const frameworks = knowledge?.data?.filter((val) => val.fields.type === "framework"); - const databases = knowledge?.data?.filter((val) => val.fields.type === "database"); - const cms = knowledge?.data?.filter((val) => val.fields.type === "cms"); - const tools = knowledge?.data?.filter((val) => val.fields.type === "tool"); - const deployments = knowledge?.data?.filter((val) => val.fields.type === "ci/cd"); + const json = await res.json(); + + const records = json.records.map((record) => { + return { id: record.id, ...record.fields }; + }); + + return records; +} + +async function Page() { + const aboutData = await fetchAboutData(); + const knowledgeData = await fetchKnowledgeData(); + + const languages = knowledgeData.filter((val) => val.type === "language"); + const frameworks = knowledgeData.filter((val) => val.type === "framework"); + const databases = knowledgeData.filter((val) => val.type === "database"); + const cms = knowledgeData.filter((val) => val.type === "cms"); + const tools = knowledgeData.filter((val) => val.type === "tool"); + const deployments = knowledgeData.filter((val) => val.type === "ci/cd"); return ( - + <> - {aboutLoading || aboutError ? ( - - ) : ( - Picture of the Author - )} + Picture of the Author - - {aboutLoading || aboutError ? ( - - ) : ( - {about.data.about.fields.long_desc} - )} - - - {/* - Knowledge Base - */} - - {/* - After a few years of being a computer engineering student and also learning something new on my own, I - have a wide range of skills and knowledge about software development. - */} - - - - - - - - - - - - - You can reach out via email at [hello@pravastacaraka.my.id](mailto:hello@pravastacaraka.my.id), or via - socials below. - - - + {aboutData.long_desc} + + + + + + + + + + + + You can reach out via email at [hello@pravastacaraka.my.id](mailto:hello@pravastacaraka.my.id), or via + socials below. + + ); } diff --git a/app/achievements/Awards.js b/app/achievements/Awards.js index 88fccfc..626cda6 100644 --- a/app/achievements/Awards.js +++ b/app/achievements/Awards.js @@ -2,104 +2,77 @@ import { Button } from "@app-components/Button"; import { NextChakraLink } from "@app-components/NextChakraLink"; -import { useGetAwards } from "@app-helper/api.helper"; import { arrayGroupBy } from "@app-helper/function.helper"; -import { - Box, - Divider, - Flex, - Heading, - Icon, - List, - ListItem, - SkeletonText, - Stack, - Text, -} from "@app-providers/chakra-ui"; +import { Divider, Flex, Heading, Icon, List, ListItem, Stack, Text } from "@app-providers/chakra-ui"; import { useBoolean, useColorModeValue } from "@chakra-ui/react"; import { HiOutlineBadgeCheck, HiOutlineChevronDown, HiOutlineChevronUp, HiOutlineNewspaper } from "react-icons/hi"; -function loadingSkeleton() { - return ( - - - - - - - ); -} - -function Awards() { +function Awards({ data }) { + let countAward = 0; const [showAwards, setShowAwards] = useBoolean(); - const { liColor } = useColorModeValue("gray.600", "gray.400"); - const { iconColor } = useColorModeValue("green.600", "yellow.200"); - const { textColor } = useColorModeValue("green.900", "gray.100"); - - const { awards, awardsError, awardsLoading } = useGetAwards(); + const iconColor = useColorModeValue("blue.600", "yellow.200"); - if (awardsError) return
Oh no! Something went wrong, please try again!
; - if (awardsLoading) return loadingSkeleton(); + if (!data || data?.length < 1) return Don't have any awards and funding.; const awardsGroupByYear = arrayGroupBy("year"); - const awardsGroupedByYear = awardsGroupByYear(awards.data); + const awardsGroupedByYear = awardsGroupByYear(data); + + return ( + + {Object.keys(awardsGroupedByYear) + .reverse() + .map((year, yearIdx) => { + let showDivider = true; + + if (countAward >= 4 && !showAwards) { + return; + } - return awardsGroupedByYear ? ( - <> - - {Object.keys(awardsGroupedByYear) - .reverse() - .map((year, yearIdx) => { - return ( - - + return ( + <> + + {year} - {awardsGroupedByYear[year].map((award) => ( - - - - - {award.title} - - - - {award.desc} - - {award.news_url && ( - - - - {award.news_url} - + {awardsGroupedByYear[year].map((award) => { + if (countAward >= 4 && !showAwards) { + showDivider = false; + return; + } + + countAward++; + + return ( + + + + {award.title} - )} - - ))} + + {award.desc} + + {award.news_url && ( + + + + {award.news_url} + + + )} + + ); + })} - {Object.keys(awardsGroupedByYear).length - 1 !== yearIdx && } - - ); - })} - + + {showDivider && Object.keys(awardsGroupedByYear).length - 1 !== yearIdx && } + + ); + })} {showAwards ? ( )} - - ) : ( - Don't have any awards and funding. + ); } diff --git a/app/achievements/Licenses.js b/app/achievements/Licenses.js index 5613494..d42fa11 100644 --- a/app/achievements/Licenses.js +++ b/app/achievements/Licenses.js @@ -2,99 +2,77 @@ import { Button } from "@app-components/Button"; import { NextChakraLink } from "@app-components/NextChakraLink"; -import { useGetLicenses } from "@app-helper/api.helper"; import { arrayGroupBy } from "@app-helper/function.helper"; -import { - Box, - Divider, - Flex, - Heading, - Icon, - List, - ListItem, - SkeletonText, - Stack, - Text, -} from "@app-providers/chakra-ui"; +import { Divider, Flex, Heading, Icon, List, ListItem, Stack, Text } from "@app-providers/chakra-ui"; import { useBoolean, useColorModeValue } from "@chakra-ui/react"; import { HiOutlineBadgeCheck, HiOutlineChevronDown, HiOutlineChevronUp, HiOutlineNewspaper } from "react-icons/hi"; -function loadingSkeleton() { - return ( - - - - - - - ); -} - -function Licenses() { +function Licenses({ data }) { + let countLicense = 0; const [showLicenses, setShowLicenses] = useBoolean(); - const { liColor } = useColorModeValue("gray.600", "gray.400"); - const { iconColor } = useColorModeValue("green.600", "yellow.200"); - const { textColor } = useColorModeValue("green.900", "gray.100"); - - const { licenses, licensesError, licensesLoading } = useGetLicenses(); + const iconColor = useColorModeValue("blue.600", "yellow.200"); - if (licensesError) return
Oh no! Something went wrong, please try again!
; - if (licensesLoading) return loadingSkeleton(); + if (!data || data?.length < 1) return Don't have any certifications.; const licensesGroupByYear = arrayGroupBy("year"); - const licensesGroupedByYear = licensesGroupByYear(licenses.data); + const licensesGroupedByYear = licensesGroupByYear(data); + + return ( + + {Object.keys(licensesGroupedByYear) + .reverse() + .map((year, yearIdx) => { + let showDivider = true; + + if (countLicense >= 5 && !showLicenses) { + return; + } - return licensesGroupedByYear ? ( - <> - - {Object.keys(licensesGroupedByYear) - .reverse() - .map((year, yearIdx) => { - return ( - - + return ( + <> + + {year} - {licensesGroupedByYear[year].map((license) => ( - - - - - {license.title} - - - - {license.desc} - - {license.news_url && ( - - - - {license.news_url} - + {licensesGroupedByYear[year].map((license) => { + if (countLicense >= 5 && !showLicenses) { + showDivider = false; + return; + } + + countLicense++; + + return ( + + + + {license.title} - )} - - ))} + + {license.desc} + + {license.news_url && ( + + + + {license.news_url} + + + )} + + ); + })} - {Object.keys(licensesGroupedByYear).length - 1 !== yearIdx && } - - ); - })} - + + {showDivider && Object.keys(licensesGroupedByYear).length - 1 !== yearIdx && } + + ); + })} {showLicenses ? ( )} - - ) : ( - Don't have any certifications. + ); } diff --git a/app/achievements/head.js b/app/achievements/head.js index ce51974..e41b244 100644 --- a/app/achievements/head.js +++ b/app/achievements/head.js @@ -1,21 +1,16 @@ -function AboutHead() { +import DefaultTags from "@app-components/head/default-tags"; + +function AchievementHead() { return ( <> Achievements - Pravasta Caraka - - - - - - - - + ); } -export default AboutHead; +export default AchievementHead; diff --git a/app/achievements/layout.js b/app/achievements/layout.js index d7fea24..44d8776 100644 --- a/app/achievements/layout.js +++ b/app/achievements/layout.js @@ -3,13 +3,10 @@ import { Heading, Stack, Text } from "@app-providers/chakra-ui"; function AchievementsLayout({ children }) { return ( - + Achievements - - Here are some of the accomplishment on my journey to becoming a developer. - + Here are some of the accomplishment on my journey to becoming a developer. - {children} ); diff --git a/app/achievements/page.js b/app/achievements/page.js index 23212ad..ae541e5 100644 --- a/app/achievements/page.js +++ b/app/achievements/page.js @@ -1,26 +1,68 @@ -import { Flex, Heading, Stack } from "@app-providers/chakra-ui"; +import { Heading, Stack } from "@app-providers/chakra-ui"; import Awards from "./Awards"; import Licenses from "./Licenses"; -function Page() { +async function fetchAwardsData() { + const res = await fetch(`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/Awards`, { + method: "GET", + headers: { + Authorization: `Bearer ${process.env.AIRTABLE_API_KEY}`, + }, + next: { revalidate: 60 }, + }); + + if (!res.ok) { + throw new Error("Failed to fetch data"); + } + + const json = await res.json(); + + const records = json.records.map((record) => { + return { id: record.id, ...record.fields }; + }); + + return records; +} + +async function fetchLicensesData() { + const res = await fetch(`https://api.airtable.com/v0/${process.env.AIRTABLE_BASE_ID}/Licenses`, { + method: "GET", + headers: { + Authorization: `Bearer ${process.env.AIRTABLE_API_KEY}`, + }, + next: { revalidate: 60 }, + }); + + if (!res.ok) { + throw new Error("Failed to fetch data"); + } + + const json = await res.json(); + + const records = json.records.map((record) => { + return { id: record.id, ...record.fields }; + }); + + return records; +} + +async function Page() { + const awardsData = await fetchAwardsData(); + const licensesData = await fetchLicensesData(); return ( <> - + Awards and Funding - - - + - + Licenses and Certifications - - - + ); diff --git a/app/blog/head.js b/app/blog/head.js index cc61a0d..40be783 100644 --- a/app/blog/head.js +++ b/app/blog/head.js @@ -1,19 +1,14 @@ +import DefaultTags from "@app-components/head/default-tags"; + function BlogHead() { return ( <> Blog - Pravasta Caraka - - - - - - - - + ); } diff --git a/app/blog/page.js b/app/blog/page.js index be75a99..24aa72b 100644 --- a/app/blog/page.js +++ b/app/blog/page.js @@ -9,8 +9,8 @@ function Page() {
diff --git a/app/contact/head.js b/app/contact/head.js index b1a7684..ae6cc8d 100644 --- a/app/contact/head.js +++ b/app/contact/head.js @@ -1,19 +1,14 @@ +import DefaultTags from "@app-components/head/default-tags"; + function ContactHead() { return ( <> Get in Touch - Pravasta Caraka - - - - - - - - + ); } diff --git a/app/head.js b/app/head.js index c1f59e8..a3a3054 100644 --- a/app/head.js +++ b/app/head.js @@ -1,16 +1,11 @@ +import DefaultTags from "@app-components/head/default-tags"; + function RootHead() { return ( <> Pravasta Caraka - - - - - - - - + ); } diff --git a/app/layout.js b/app/layout.js index dcbfccf..aeec4b0 100644 --- a/app/layout.js +++ b/app/layout.js @@ -1,18 +1,14 @@ -"use client"; - import Footer from "@app-components/Footer"; import Header from "@app-components/Header"; import MobileNavigation from "@app-components/MobileNavigation"; -import { ChakraProvider, ColorModeScript, Container } from "@app-providers/chakra-ui"; -import customTheme from "@app-styles/theme"; -import { Analytics } from "@vercel/analytics/react"; +import { ChakraWrapper, Container } from "@app-providers/chakra-ui"; +import { Analytics } from "@app-providers/vercel"; function RootLayout({ children }) { return ( - - +
{children} @@ -20,7 +16,7 @@ function RootLayout({ children }) {