├── .env.example ├── .eslintrc.json ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── app ├── about │ ├── content.mdx │ ├── github-contributions │ │ ├── activity-tooltip.tsx │ │ ├── calendar.tsx │ │ ├── contributions.tsx │ │ ├── days.tsx │ │ ├── github-calendar-skeleton.tsx │ │ ├── github-contributions.tsx │ │ ├── github-stats-skeleton.tsx │ │ ├── github-stats.tsx │ │ ├── github.ts │ │ ├── helpers.ts │ │ └── year-select.tsx │ ├── layout.tsx │ ├── occupation.tsx │ └── page.tsx ├── components │ ├── analytics │ │ ├── analytics.tsx │ │ └── log-rocket.tsx │ ├── animated-number.tsx │ ├── background-gradient-animation.tsx │ ├── command-palette │ │ ├── CommandItem.tsx │ │ ├── CommandPalette.tsx │ │ └── usePaletteOptions.tsx │ ├── contact.tsx │ ├── gooey-background.tsx │ ├── header.tsx │ ├── hero │ │ ├── hero.tsx │ │ └── renderCanvas.js │ ├── intro.tsx │ ├── layouts │ │ ├── back-navigation.tsx │ │ ├── constants.ts │ │ ├── footer.tsx │ │ ├── header.tsx │ │ ├── icons │ │ │ ├── arrow-down-icon.tsx │ │ │ ├── at-sign-icon.tsx │ │ │ ├── github-icon.tsx │ │ │ ├── instagram-icon.tsx │ │ │ ├── linkedin-icon.tsx │ │ │ ├── moon-icon.tsx │ │ │ ├── square-arrow-left.tsx │ │ │ ├── sun-icon.tsx │ │ │ └── x-icon.tsx │ │ ├── main-layout.tsx │ │ ├── mobile-nav.tsx │ │ ├── page-container.tsx │ │ ├── section-container.tsx │ │ └── theme-switch │ │ │ └── theme-switch.tsx │ ├── mask-effect │ │ └── mask-container.tsx │ ├── mdx.tsx │ ├── providers │ │ ├── LenisProvider.tsx │ │ ├── ScrollProvider.tsx │ │ └── ThemeProvider.tsx │ ├── seconds-since-date.tsx │ ├── skeleton.tsx │ ├── spotify │ │ ├── animated-bars.tsx │ │ ├── now-playing.tsx │ │ ├── spotify-skeleton.tsx │ │ ├── spotify.ts │ │ ├── top-tracks.tsx │ │ ├── track.tsx │ │ └── types.ts │ ├── stat-item.tsx │ ├── thoughts.tsx │ ├── tiles │ │ ├── TileContext.tsx │ │ ├── tile-background.tsx │ │ ├── tile-content.tsx │ │ ├── tile-wrapper.tsx │ │ └── tile.tsx │ ├── wakatime │ │ ├── types.ts │ │ ├── utils.ts │ │ ├── wakastats.tsx │ │ ├── wakatime-skeleton.tsx │ │ └── wakatime.ts │ └── work │ │ ├── work-background.tsx │ │ ├── work-container.tsx │ │ ├── work-content.tsx │ │ ├── work-left.tsx │ │ ├── work-right.tsx │ │ ├── workTiles.ts │ │ └── works.tsx ├── fonts.ts ├── layout.tsx ├── not-found.tsx ├── old-home │ └── page.tsx ├── opengraph-image.png ├── page.tsx ├── projects │ ├── constants.ts │ ├── layout.tsx │ ├── page.tsx │ ├── project-item.tsx │ ├── project-preview.tsx │ ├── projects.tsx │ └── types.ts ├── robots.ts ├── sitemap.ts ├── stats │ ├── layout.tsx │ └── page.tsx ├── tailwind.css ├── thoughts │ ├── [slug] │ │ ├── layout.tsx │ │ ├── page-title.tsx │ │ ├── page.tsx │ │ └── scroll-progress-bar.tsx │ ├── page.tsx │ ├── posts │ │ ├── 2025-site-redesign.mdx │ │ ├── create-and-publish-your-own-react-component-library-with-typescript-storybook-and-tailwind.mdx │ │ ├── how-to-merge-a-repository-into-nx.mdx │ │ ├── how-to-share-assets-to-multiple-appslibraries-in-nx.mdx │ │ ├── javascript-promises-async-await.mdx │ │ ├── merge-an-existing-repository-to-turborepo.mdx │ │ ├── redux-toolkit-passing-an-argument-to-selector.mdx │ │ ├── typing-usestate.mdx │ │ └── why-does-react-need-keys.mdx │ └── utils.ts └── uses │ ├── content.mdx │ ├── layout.tsx │ ├── page.tsx │ └── uses-title.tsx ├── bun.lockb ├── eslint.config.mjs ├── next-env.d.ts ├── next.config.ts ├── package.json ├── postcss.config.js ├── public ├── mask.svg └── static │ ├── blog │ ├── create-playground.webp │ ├── nx │ │ └── create-nx-workspace.webp │ ├── promises.webp │ └── tailwind-working.webp │ ├── favicons │ └── favicon.ico │ └── images │ ├── aphex-apps.webp │ ├── avatar.webp │ ├── field-app.webp │ ├── planner-app.webp │ ├── project │ ├── bigdata.webp │ ├── field-app.webp │ ├── mathgame.png │ ├── planner-app.webp │ ├── publication-app.webp │ ├── snakegame.png │ ├── spoken.webp │ └── topo.webp │ └── publication-app.webp ├── renovate.json ├── tailwind.config.js └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | # Spotify 2 | NEXT_PUBLIC_SPOTIFY_CLIENT_ID= 3 | NEXT_PUBLIC_SPOTIFY_CLIENT_SECRET= 4 | NEXT_PUBLIC_SPOTIFY_REFRESH_TOKEN= 5 | 6 | # LogRocket 7 | NEXT_PUBLIC_LOGROCKET_ID= 8 | 9 | # GitHub 10 | NEXT_PUBLIC_GITHUB_TOKEN= 11 | 12 | # WakaTime 13 | WAKATIME_SECRET_KEY= 14 | 15 | # Umami 16 | NEXT_PUBLIC_UMAMI_WEBSITE_ID= 17 | NEXT_PUBLIC_UMAMI_WEBSITE_URL= -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .env 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | public/sitemap.xml 16 | .vercel 17 | 18 | # production 19 | /build 20 | *.xml 21 | # rss feed 22 | /public/feed.xml 23 | 24 | # misc 25 | .DS_Store 26 | 27 | # debug 28 | *.log 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | # local env files 34 | .env.local 35 | .env.development.local 36 | .env.test.local 37 | .env.production.local 38 | 39 | 40 | # Contentlayer 41 | .contentlayer 42 | .env*.local 43 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx --no-install lint-staged 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "trailingComma": "all", 6 | "useTabs": false, 7 | "bracketSpacing": true 8 | } 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dale Larroder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dalelarroder.com ⚡️ 2 | 3 | - **Framework**: [Next.js](https://nextjs.org/) 4 | - **Deployment**: [Vercel](https://vercel.com) 5 | - **Styling**: [Tailwind CSS](https://tailwindcss.com/) 6 | - **Analytics**: [Logrocket](https://logrocket.com/) 7 | - **Content**: [MDX](https://mdxjs.com/) 8 | 9 | ## Running Locally 10 | 11 | ### Installation 12 | 13 | 1. Clone this repo 14 | 15 | ```bash 16 | git clone git@github.com:dlarroder/dalelarroder.git 17 | ``` 18 | 19 | 2. Change directory 20 | 21 | ```sh 22 | cd dalelarroder 23 | ``` 24 | 25 | 3. Install dependencies 26 | 27 | ```bash 28 | bun install 29 | ``` 30 | 31 | 1. Create a `.env.local` file following the `.env.example` 32 | 33 | ```bash 34 | cp .env.example .env.local 35 | ``` 36 | 37 | 5. Add your environment variables to `.env.local` 38 | 39 | ```txt 40 | SPOTIFY_REFRESH_TOKEN= 41 | SPOTIFY_CLIENT_SECRET= 42 | SPOTIFY_CLIENT_ID= 43 | // ... 44 | ``` 45 | 46 | 6. Run the development server 47 | 48 | ```bash 49 | bun run dev 50 | ``` 51 | 52 | ## Previous Version 53 | 54 | This is the second version of my website. 55 | 56 | Prevoius v1 version: 57 | 58 | - https://v1.dalelarroder.com/ 59 | 60 | ## Licence 61 | 62 | [MIT](https://github.com/dlarroder/dalelarroder/blob/master/LICENSE) © [Dale Larroder](https://www.dalelarroder.com) 63 | -------------------------------------------------------------------------------- /app/about/content.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'About Page' 3 | publishedAt: '2024-12-08' 4 | summary: 'About me' 5 | --- 6 | 7 | I'm a software developer with a keen focus on creating beautiful and intuitive user interfaces. 8 | I specialize in React.js, Next.js, TypeScript, and Tailwind to build scalable and efficient web applications. 9 | 10 | I bring extensive experience in web development, having worked with companies like [Code and Theory](https://www.codeandtheory.com/), [Cell 5](https://www.cell5.co.uk/), and [Create](https://www.create.xyz/). 11 | Now I am a Software Developer at [Aphex](https://aphex.co/), where we are building the future of construction software planning. 12 | 13 |
14 | -------------------------------------------------------------------------------- /app/about/github-contributions/activity-tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { Content, Portal, Root, Trigger } from '@radix-ui/react-tooltip'; 2 | import { ReactNode } from 'react'; 3 | 4 | interface Props { 5 | block: ReactNode; 6 | count: number; 7 | date: Date; 8 | } 9 | 10 | export default function ActivityTooltip({ block, date, count }: Props) { 11 | const formattedDate = date.toLocaleDateString('en-US', { 12 | month: 'long', 13 | day: 'numeric', 14 | year: 'numeric', 15 | }); 16 | 17 | return ( 18 | 19 | {block} 20 | 21 | 22 | {`${count} contributions on ${formattedDate}`} 23 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /app/about/github-contributions/calendar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Provider as TooltipProvider } from '@radix-ui/react-tooltip'; 4 | import classNames from 'classnames'; 5 | import { motion } from 'motion/react'; 6 | import ActivityTooltip from './activity-tooltip'; 7 | import { ContributionCalendar } from './github'; 8 | 9 | interface Props { 10 | contributions: ContributionCalendar; 11 | } 12 | 13 | export default function Calendar({ contributions }: Props) { 14 | const { weeks, months, colors } = contributions; 15 | 16 | return ( 17 | 18 |
19 |
    20 | {months.map((month) => ( 21 |
  • 28 | {month.name} 29 |
  • 30 | ))} 31 |
32 | 33 |
34 | {weeks?.map((week) => ( 35 |
36 | {week.contributionDays.map((contribution) => { 37 | const backgroundColor = 38 | contribution.contributionCount > 0 && contribution.color; 39 | 40 | const randomizedDelay = 41 | Math.random() * week.contributionDays.length * 0.2; 42 | 43 | return ( 44 | 63 | } 64 | count={contribution.contributionCount} 65 | date={new Date(contribution.date)} 66 | key={contribution.date} 67 | /> 68 | ); 69 | })} 70 |
71 | ))} 72 |
73 | 74 |
75 |
76 | Less 77 |
    78 | 79 | {colors.map((item, index) => ( 80 | 94 | ))} 95 |
96 | More 97 |
98 |
99 |
100 |
101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /app/about/github-contributions/contributions.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Fragment, useState } from 'react'; 4 | import useSWR from 'swr'; 5 | import Calendar from './calendar'; 6 | import Days from './days'; 7 | import { getContributions, GITHUB_USERNAME } from './github'; 8 | import GithubCalendarSkeleton from './github-calendar-skeleton'; 9 | import GithubStats from './github-stats'; 10 | import GithubStatsSkeleton from './github-stats-skeleton'; 11 | import YearSelect from './year-select'; 12 | 13 | export default function Contributions() { 14 | const [year, setYear] = useState(new Date().getFullYear()); 15 | 16 | const { data: contributions, isLoading } = useSWR( 17 | ['contributions', GITHUB_USERNAME, year], // Cache key 18 | () => getContributions(GITHUB_USERNAME, year), 19 | ); 20 | 21 | if (!contributions || isLoading) { 22 | return ( 23 |
24 |
25 | 26 | 27 | 28 |
29 | 30 |
31 | ); 32 | } 33 | 34 | return ( 35 | 36 |
37 | 38 | 39 | 40 |
41 | 42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /app/about/github-contributions/days.tsx: -------------------------------------------------------------------------------- 1 | export default function Days() { 2 | return ( 3 |
4 |
5 |

Mon

6 |
7 |

Wed

8 |
9 |

Fri

10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /app/about/github-contributions/github-calendar-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import Skeleton from '../../components/skeleton'; 2 | 3 | export default function GithubCalendarSkeleton() { 4 | return ( 5 |
6 | 7 | 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /app/about/github-contributions/github-contributions.tsx: -------------------------------------------------------------------------------- 1 | import Contributions from './contributions'; 2 | 3 | export default function GithubContributions() { 4 | return ( 5 |
6 |
7 |

8 | Github 9 |

10 |

11 | Contributions Stats 12 |

13 |
14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/about/github-contributions/github-stats-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import Skeleton from '../../components/skeleton'; 2 | 3 | export default function GithubStatsSkeleton() { 4 | return ( 5 |
6 | 7 | 8 | 9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /app/about/github-contributions/github-stats.tsx: -------------------------------------------------------------------------------- 1 | import { format } from 'date-fns'; 2 | import AnimatedNumber from '../../components/animated-number'; 3 | import StatItem from '../../components/stat-item'; 4 | import { 5 | ContributionCalendar, 6 | getBestDay, 7 | getContributionStreak, 8 | getDaysFromContribution, 9 | } from './github'; 10 | 11 | interface Props { 12 | contributions: ContributionCalendar; 13 | } 14 | 15 | export default function GithubStats({ contributions }: Props) { 16 | const { weeks, totalContributions } = contributions; 17 | 18 | const bestDay = getBestDay(weeks); 19 | const daysFromContribution = getDaysFromContribution(weeks); 20 | const streak = getContributionStreak( 21 | contributions.weeks.flatMap((week) => week.contributionDays), 22 | ); 23 | const averageContribution = totalContributions / daysFromContribution; 24 | 25 | return ( 26 |
27 | 28 | 29 | contributions 30 | 31 | 32 | 33 | days 34 | 35 | 36 | {format(bestDay.day, 'PP')} — 37 | 38 | contributions 39 | 40 | 41 | 42 | contributions / day 43 | 44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /app/about/github-contributions/github.ts: -------------------------------------------------------------------------------- 1 | import { graphql } from '@octokit/graphql'; 2 | import { cache } from 'react'; 3 | 4 | export const GITHUB_USERNAME = 'dlarroder'; 5 | 6 | const getGraphqlWithAuth = cache(() => { 7 | const github_token = process.env.NEXT_PUBLIC_GITHUB_TOKEN || ''; 8 | 9 | if (!github_token) { 10 | throw new Error('GITHUB_TOKEN is required'); 11 | } 12 | 13 | const graphqlWithAuth = graphql.defaults({ 14 | headers: { authorization: `bearer ${github_token}` }, 15 | }); 16 | 17 | return graphqlWithAuth; 18 | }); 19 | 20 | const query = ` 21 | query ($username: String!, $from: DateTime!, $to: DateTime!) { 22 | user(login: $username) { 23 | contributionsCollection(from: $from, to: $to) { 24 | contributionCalendar { 25 | colors 26 | totalContributions 27 | weeks { 28 | contributionDays { 29 | color 30 | date 31 | contributionCount 32 | } 33 | firstDay 34 | } 35 | months { 36 | firstDay 37 | name 38 | totalWeeks 39 | } 40 | } 41 | } 42 | } 43 | } 44 | `; 45 | 46 | export type GithubResponse = { 47 | user: { 48 | contributionsCollection: { 49 | contributionCalendar: { 50 | colors: string[]; 51 | totalContributions: number; 52 | weeks: { 53 | contributionDays: { 54 | color: string; 55 | date: string; 56 | contributionCount: number; 57 | }[]; 58 | firstDay: string; 59 | }[]; 60 | months: { firstDay: string; name: string; totalWeeks: number }[]; 61 | }; 62 | }; 63 | }; 64 | }; 65 | 66 | export type GithubUser = GithubResponse['user']; 67 | 68 | export type ContributionCalendar = 69 | GithubUser['contributionsCollection']['contributionCalendar']; 70 | 71 | export type ContributionMonths = ContributionCalendar['months']; 72 | 73 | export type ContributionWeeks = ContributionCalendar['weeks']; 74 | 75 | export type ContributionDay = ContributionWeeks[0]['contributionDays'][0]; 76 | 77 | export type ContributionStreak = { 78 | longestStreak: number; 79 | currentStreak: number; 80 | longestStreakStart: string | null; 81 | longestStreakEnd: string | null; 82 | currentStreakStart: string | null; 83 | }; 84 | 85 | export const getContributions = cache( 86 | async (username: string, year: number): Promise => { 87 | const today = new Date(); 88 | const currentYear = today.getFullYear(); 89 | 90 | let from: string; 91 | let to: string = today.toISOString(); // Always set `to` as today 92 | 93 | if (year === currentYear) { 94 | // If the year is the current year, set `from` to today minus 1 year 95 | const pastYear = new Date(); 96 | pastYear.setFullYear(today.getFullYear() - 1); 97 | from = pastYear.toISOString(); 98 | } else { 99 | // Otherwise, use the standard Jan 1 - Dec 31 range 100 | from = new Date(year, 0, 1).toISOString(); 101 | to = new Date(year, 11, 31, 23, 59, 59).toISOString(); 102 | } 103 | 104 | const graphqlWithAuth = getGraphqlWithAuth(); 105 | const response = await graphqlWithAuth({ 106 | query, 107 | username, 108 | from, 109 | to, 110 | }); 111 | 112 | return response.user.contributionsCollection.contributionCalendar; 113 | }, 114 | ); 115 | 116 | export const getBestDay = (weeks: ContributionWeeks) => { 117 | let bestDay: { day: string; count: number } = { day: '', count: 0 }; 118 | 119 | weeks.forEach((week) => { 120 | week.contributionDays.forEach((day) => { 121 | if (day.contributionCount > bestDay.count) { 122 | bestDay = { day: day.date, count: day.contributionCount }; 123 | } 124 | }); 125 | }); 126 | 127 | return bestDay; 128 | }; 129 | 130 | export const getThisWeeksContributions = (weeks: ContributionWeeks) => { 131 | return ( 132 | weeks[weeks.length - 1]?.contributionDays 133 | ?.map((item) => item.contributionCount) 134 | ?.reduce( 135 | (previousValue, currentValue) => previousValue + currentValue, 136 | 0, 137 | ) || 0 138 | ); 139 | }; 140 | 141 | export const getDaysFromContribution = (weeks: ContributionWeeks) => { 142 | return weeks.flatMap((week) => week.contributionDays).length; 143 | }; 144 | 145 | export function getContributionStreak( 146 | contributionDays: ContributionDay[], 147 | ): ContributionStreak { 148 | let longestStreak = 0; 149 | let currentStreak = 0; 150 | let tempStreak = 0; 151 | let longestStreakStart: string | null = null; 152 | let longestStreakEnd: string | null = null; 153 | let currentStreakStart: string | null = null; 154 | 155 | let previousDate: Date | null = null; 156 | 157 | for (const { date, contributionCount } of contributionDays) { 158 | const currentDate = new Date(date); 159 | 160 | if (contributionCount > 0) { 161 | if ( 162 | previousDate && 163 | currentDate.getTime() - previousDate.getTime() === 86400000 164 | ) { 165 | tempStreak++; 166 | } else { 167 | tempStreak = 1; 168 | currentStreakStart = date; 169 | } 170 | 171 | if (tempStreak > longestStreak) { 172 | longestStreak = tempStreak; 173 | longestStreakStart = currentStreakStart; 174 | longestStreakEnd = date; 175 | } 176 | 177 | currentStreak = tempStreak; 178 | } else { 179 | tempStreak = 0; 180 | currentStreak = 0; 181 | currentStreakStart = null; 182 | } 183 | 184 | previousDate = currentDate; 185 | } 186 | 187 | return { 188 | longestStreak, 189 | currentStreak, 190 | longestStreakStart, 191 | longestStreakEnd, 192 | currentStreakStart, 193 | }; 194 | } 195 | -------------------------------------------------------------------------------- /app/about/github-contributions/helpers.ts: -------------------------------------------------------------------------------- 1 | export function subtractOneYear(date: number | string | Date) { 2 | const newDate = new Date(date); // Clone the input date 3 | newDate.setFullYear(newDate.getFullYear() - 1); // Subtract one year 4 | return newDate; 5 | } 6 | 7 | export function formatDateString(date: Date) { 8 | return new Date(date).toISOString().split('T')[0]; 9 | } 10 | -------------------------------------------------------------------------------- /app/about/github-contributions/year-select.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames'; 2 | 3 | interface Props { 4 | selectedYear: number; 5 | onYearChange: (year: number) => void; 6 | } 7 | 8 | export default function YearSelect({ selectedYear, onYearChange }: Props) { 9 | const thisYear = new Date().getFullYear(); 10 | 11 | const yearOptions = Array.from({ length: 5 }, (_, i) => thisYear - i); 12 | 13 | return ( 14 |
15 | {yearOptions.map((year) => ( 16 | 28 | ))} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /app/about/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import SectionContainer from '../components/layouts/section-container'; 3 | 4 | export default function Layout({ children }: { children: ReactNode }) { 5 | return {children}; 6 | } 7 | -------------------------------------------------------------------------------- /app/about/occupation.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | export default function Occupation() { 4 | return ( 5 |
6 |
7 |

8 | Dale Larroder 9 |

10 |

Software Developer

11 |
12 |
13 | Dale Larroder 20 |
21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /app/about/page.tsx: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { Fragment, Suspense } from 'react'; 3 | import Footer from '../components/layouts/footer'; 4 | import Header from '../components/layouts/header'; 5 | import { CustomMDX } from '../components/mdx'; 6 | import SpotifySkeleton from '../components/spotify/spotify-skeleton'; 7 | import TopTracks from '../components/spotify/top-tracks'; 8 | import WakaStats from '../components/wakatime/wakastats'; 9 | import WakatimeSkeleton from '../components/wakatime/wakatime-skeleton'; 10 | import { readMDXFile } from '../thoughts/utils'; 11 | import GithubContributions from './github-contributions/github-contributions'; 12 | import Occupation from './occupation'; 13 | 14 | const contentPath = path.join(process.cwd(), 'app', 'about', 'content.mdx'); 15 | const { content } = readMDXFile(contentPath); 16 | 17 | export const metadata = { 18 | title: 'About', 19 | description: 'About Dale Larroder', 20 | }; 21 | 22 | export default function Page() { 23 | return ( 24 | 25 |
26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | } 36 | > 37 | 38 | 39 | 40 |