3.0.0 - Dependency updates, improved typesafe config, improve typing
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m44s
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m44s
This commit is contained in:
12
flake.nix
12
flake.nix
@@ -3,8 +3,8 @@
|
|||||||
description = "A portable development environment for atridotdad with Nix Flakes";
|
description = "A portable development environment for atridotdad with Nix Flakes";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; # Pin to a specific branch or commit for stability
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
flake-utils.url = "github:numtide/flake-utils"; # Helps with system boilerplate
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
curl
|
curl
|
||||||
];
|
];
|
||||||
|
|
||||||
# Common libraries needed for Playwright across platforms (e.g., for WebKit/Firefox)
|
# Common libraries needed for Playwright
|
||||||
playwrightCommonLibs = with pkgs; [
|
playwrightCommonLibs = with pkgs; [
|
||||||
glib
|
glib
|
||||||
nss
|
nss
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
gnutls
|
gnutls
|
||||||
];
|
];
|
||||||
|
|
||||||
# Linux-specific libraries for Playwright (mostly Chromium dependencies)
|
# Linux-specific libraries for Playwright
|
||||||
playwrightLinuxSpecificLibs = with pkgs; [
|
playwrightLinuxSpecificLibs = with pkgs; [
|
||||||
glibc
|
glibc
|
||||||
libgcc
|
libgcc
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
packages = commonDevTools ++ (
|
packages = commonDevTools ++ (
|
||||||
if isDarwin
|
if isDarwin
|
||||||
then playwrightCommonLibs # For macOS, Playwright will download Chromium. Provide base libs.
|
then playwrightCommonLibs # For macOS, Playwright will download Chromium.
|
||||||
else [ pkgs.chromium ] ++ playwrightSelfDownloadLibs # For Linux, provide Chromium and its dependencies
|
else [ pkgs.chromium ] ++ playwrightSelfDownloadLibs # For Linux, provide Chromium and its dependencies
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
|
|
||||||
if [ ! -d "node_modules" ]; then
|
if [ ! -d "node_modules" ]; then
|
||||||
echo "📦 Installing pnpm dependencies..."
|
echo "📦 Installing pnpm dependencies..."
|
||||||
pnpm install --frozen-lockfile # Use --frozen-lockfile for more consistent builds
|
pnpm install --frozen-lockfile
|
||||||
fi
|
fi
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|||||||
26
package.json
26
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "atridotdad",
|
"name": "atridotdad",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.1.0",
|
"version": "3.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
@@ -10,26 +10,26 @@
|
|||||||
"nix": "nix develop"
|
"nix": "nix develop"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/mdx": "^4.3.4",
|
"@astrojs/mdx": "^4.3.6",
|
||||||
"@astrojs/node": "^9.4.3",
|
"@astrojs/node": "^9.4.4",
|
||||||
"@astrojs/preact": "^4.1.0",
|
"@astrojs/preact": "^4.1.1",
|
||||||
"@astrojs/rss": "^4.0.12",
|
"@astrojs/rss": "^4.0.12",
|
||||||
"@iarna/toml": "^2.2.5",
|
"@iarna/toml": "^2.2.5",
|
||||||
"@preact/signals": "^2.3.1",
|
"@preact/signals": "^2.3.1",
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.18",
|
||||||
"@tailwindcss/vite": "^4.1.12",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
"astro": "^5.13.5",
|
"astro": "^5.13.10",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"lucide-preact": "^0.542.0",
|
"lucide-preact": "^0.544.0",
|
||||||
"playwright": "^1.55.0",
|
"playwright": "^1.55.0",
|
||||||
"preact": "^10.27.1",
|
"preact": "^10.27.2",
|
||||||
"sharp": "^0.34.3",
|
"sharp": "^0.34.4",
|
||||||
"tailwindcss": "^4.1.12"
|
"tailwindcss": "^4.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify-json/mdi": "^1.2.3",
|
"@iconify-json/mdi": "^1.2.3",
|
||||||
"@iconify-json/simple-icons": "^1.2.50",
|
"@iconify-json/simple-icons": "^1.2.52",
|
||||||
"daisyui": "^5.1.6"
|
"daisyui": "^5.1.14"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
|
|||||||
1712
pnpm-lock.yaml
generated
1712
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,10 @@
|
|||||||
import { Icon } from 'astro-icon/components';
|
import { Icon } from "astro-icon/components";
|
||||||
import type { IconType, LucideIcon, AstroIconName, CustomIconComponent } from '../types';
|
import type {
|
||||||
|
IconType,
|
||||||
|
LucideIcon,
|
||||||
|
AstroIconName,
|
||||||
|
CustomIconComponent,
|
||||||
|
} from "../types";
|
||||||
|
|
||||||
interface IconRendererProps {
|
interface IconRendererProps {
|
||||||
icon: IconType;
|
icon: IconType;
|
||||||
@@ -10,18 +15,23 @@ interface IconRendererProps {
|
|||||||
|
|
||||||
// Type guard functions
|
// Type guard functions
|
||||||
function isLucideIcon(icon: IconType): icon is LucideIcon {
|
function isLucideIcon(icon: IconType): icon is LucideIcon {
|
||||||
return typeof icon === 'function' && icon.length <= 1; // Lucide icons are function components
|
return typeof icon === "function" && icon.length <= 1; // Lucide icons are function components
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAstroIconName(icon: IconType): icon is AstroIconName {
|
function isAstroIconName(icon: IconType): icon is AstroIconName {
|
||||||
return typeof icon === 'string';
|
return typeof icon === "string";
|
||||||
}
|
}
|
||||||
|
|
||||||
function isCustomComponent(icon: IconType): icon is CustomIconComponent {
|
function isCustomComponent(icon: IconType): icon is CustomIconComponent {
|
||||||
return typeof icon === 'function' && !isLucideIcon(icon);
|
return typeof icon === "function" && !isLucideIcon(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function IconRenderer({ icon, size, class: className, ...props }: IconRendererProps) {
|
export default function IconRenderer({
|
||||||
|
icon,
|
||||||
|
size,
|
||||||
|
class: className,
|
||||||
|
...props
|
||||||
|
}: IconRendererProps) {
|
||||||
if (isLucideIcon(icon)) {
|
if (isLucideIcon(icon)) {
|
||||||
const LucideComponent = icon;
|
const LucideComponent = icon;
|
||||||
return <LucideComponent size={size} class={className} {...props} />;
|
return <LucideComponent size={size} class={className} {...props} />;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useComputed, useSignal } from "@preact/signals";
|
import { useComputed, useSignal } from "@preact/signals";
|
||||||
import { useEffect } from "preact/hooks";
|
import { useEffect } from "preact/hooks";
|
||||||
import { navigationItems } from "../config/data";
|
import { config } from "../config";
|
||||||
import type { LucideIcon } from "../types";
|
import type { LucideIcon } from "../types";
|
||||||
|
|
||||||
interface NavigationBarProps {
|
interface NavigationBarProps {
|
||||||
@@ -20,7 +20,7 @@ export default function NavigationBar({ currentPath }: NavigationBarProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Filter out disabled navigation items
|
// Filter out disabled navigation items
|
||||||
const enabledNavigationItems = navigationItems.filter(
|
const enabledNavigationItems = config.navigationItems.filter(
|
||||||
(item) => item.enabled !== false,
|
(item) => item.enabled !== false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ interface Props {
|
|||||||
const { project } = Astro.props;
|
const { project } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="card bg-accent text-accent-content w-full max-w-sm shrink shadow-md">
|
<div
|
||||||
|
class="card bg-accent text-accent-content w-full max-w-sm shrink shadow-md"
|
||||||
|
>
|
||||||
<div class="card-body break-words">
|
<div class="card-body break-words">
|
||||||
<h2
|
<h2
|
||||||
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words"
|
class="card-title text-xl md:text-2xl font-bold justify-center text-center break-words"
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import { socialLinks } from "../config/data";
|
import { config } from "../config";
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="flex flex-row gap-3 text-3xl flex-wrap justify-center">
|
<div class="flex flex-row gap-3 text-3xl flex-wrap justify-center">
|
||||||
{
|
{
|
||||||
socialLinks.map((link) => {
|
config.socialLinks.map((link) => (
|
||||||
return (
|
|
||||||
<a
|
<a
|
||||||
href={link.url}
|
href={link.url}
|
||||||
target={link.url.startsWith("http") ? "_blank" : undefined}
|
target={link.url.startsWith("http") ? "_blank" : undefined}
|
||||||
@@ -20,7 +19,6 @@ import { socialLinks } from "../config/data";
|
|||||||
>
|
>
|
||||||
<Icon name={link.icon} />
|
<Icon name={link.icon} />
|
||||||
</a>
|
</a>
|
||||||
);
|
))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import { techLinks } from "../config/data";
|
import { config } from "../config";
|
||||||
|
|
||||||
// Helper function to check if icon is a string (Astro icon)
|
// Helper function to check if icon is a string (Astro icon)
|
||||||
function isAstroIcon(icon: any): icon is string {
|
function isAstroIcon(icon: any): icon is string {
|
||||||
@@ -8,9 +8,9 @@ function isAstroIcon(icon: any): icon is string {
|
|||||||
}
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="flex flex-row gap-4 text-3xl flex-wrap justify-center">
|
<div class="flex flex-row gap-3 text-3xl flex-wrap justify-center">
|
||||||
{
|
{
|
||||||
techLinks.map((link) => {
|
config.techLinks.map((link) => {
|
||||||
if (isAstroIcon(link.icon)) {
|
if (isAstroIcon(link.icon)) {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
|
|||||||
392
src/config.ts
Normal file
392
src/config.ts
Normal file
@@ -0,0 +1,392 @@
|
|||||||
|
import type { Config } from "./types";
|
||||||
|
|
||||||
|
// Import Lucide Icons
|
||||||
|
import { Home, Newspaper, FileUser, CodeXml, Megaphone } from "lucide-preact";
|
||||||
|
|
||||||
|
import logo from "./assets/logo_real.webp";
|
||||||
|
import resumeToml from "./assets/resume.toml?raw";
|
||||||
|
|
||||||
|
// Astro Icon references
|
||||||
|
const EMAIL_ICON = "mdi:email" as const;
|
||||||
|
const RSS_ICON = "mdi:rss" as const;
|
||||||
|
const GITEA_ICON = "simple-icons:gitea" as const;
|
||||||
|
const BLUESKY_ICON = "simple-icons:bluesky" as const;
|
||||||
|
const REACT_ICON = "simple-icons:react" as const;
|
||||||
|
const TYPESCRIPT_ICON = "simple-icons:typescript" as const;
|
||||||
|
const ASTRO_ICON = "simple-icons:astro" as const;
|
||||||
|
const GO_ICON = "simple-icons:go" as const;
|
||||||
|
const POSTGRESQL_ICON = "simple-icons:postgresql" as const;
|
||||||
|
const REDIS_ICON = "simple-icons:redis" as const;
|
||||||
|
const DOCKER_ICON = "simple-icons:docker" as const;
|
||||||
|
const KOTLIN_ICON = "simple-icons:kotlin" as const;
|
||||||
|
const SWIFT_ICON = "simple-icons:swift" as const;
|
||||||
|
const FLUTTER_ICON = "simple-icons:flutter" as const;
|
||||||
|
|
||||||
|
export const config: Config = {
|
||||||
|
personalInfo: {
|
||||||
|
name: "Atridad Lahiji",
|
||||||
|
profileImage: {
|
||||||
|
src: logo,
|
||||||
|
alt: "A drawing of Atridad Lahiji by Shelze!",
|
||||||
|
},
|
||||||
|
tagline: "Researcher, Full-Stack Developer, and IT Professional",
|
||||||
|
description: "Researcher, Full-Stack Developer, and IT Professional",
|
||||||
|
},
|
||||||
|
|
||||||
|
homepageSections: {
|
||||||
|
socialLinks: {
|
||||||
|
title: "Places I Exist:",
|
||||||
|
description: "Find me across the web",
|
||||||
|
},
|
||||||
|
techStack: {
|
||||||
|
title: "Technologies I Use:",
|
||||||
|
description: "Technologies and tools I work with",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
resumeConfig: {
|
||||||
|
tomlFile: resumeToml,
|
||||||
|
layout: {
|
||||||
|
leftColumn: ["experience", "volunteer"],
|
||||||
|
rightColumn: ["skills", "education", "awards"],
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
enabled: [
|
||||||
|
"summary",
|
||||||
|
"experience",
|
||||||
|
"education",
|
||||||
|
"skills",
|
||||||
|
"volunteer",
|
||||||
|
"awards",
|
||||||
|
],
|
||||||
|
summary: {
|
||||||
|
title: "Summary",
|
||||||
|
},
|
||||||
|
experience: {
|
||||||
|
title: "Professional Experience",
|
||||||
|
},
|
||||||
|
education: {
|
||||||
|
title: "Education",
|
||||||
|
},
|
||||||
|
skills: {
|
||||||
|
title: "Skills",
|
||||||
|
},
|
||||||
|
volunteer: {
|
||||||
|
title: "Volunteer Work",
|
||||||
|
},
|
||||||
|
awards: {
|
||||||
|
title: "Awards",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
siteConfig: {
|
||||||
|
personal: {
|
||||||
|
name: "Atridad Lahiji",
|
||||||
|
profileImage: {
|
||||||
|
src: logo,
|
||||||
|
alt: "A drawing of Atridad Lahiji by Shelze!",
|
||||||
|
},
|
||||||
|
tagline: "Researcher, Full-Stack Developer, and IT Professional",
|
||||||
|
description: "Researcher, Full-Stack Developer, and IT Professional",
|
||||||
|
},
|
||||||
|
homepage: {
|
||||||
|
socialLinks: {
|
||||||
|
title: "Places I Exist:",
|
||||||
|
description: "Find me across the web",
|
||||||
|
},
|
||||||
|
techStack: {
|
||||||
|
title: "Technologies I Use:",
|
||||||
|
description: "Technologies and tools I work with",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resume: {
|
||||||
|
tomlFile: resumeToml,
|
||||||
|
layout: {
|
||||||
|
leftColumn: ["experience", "volunteer"],
|
||||||
|
rightColumn: ["skills", "education", "awards"],
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
enabled: [
|
||||||
|
"summary",
|
||||||
|
"experience",
|
||||||
|
"education",
|
||||||
|
"skills",
|
||||||
|
"volunteer",
|
||||||
|
"awards",
|
||||||
|
],
|
||||||
|
summary: {
|
||||||
|
title: "Summary",
|
||||||
|
},
|
||||||
|
experience: {
|
||||||
|
title: "Professional Experience",
|
||||||
|
},
|
||||||
|
education: {
|
||||||
|
title: "Education",
|
||||||
|
},
|
||||||
|
skills: {
|
||||||
|
title: "Skills",
|
||||||
|
},
|
||||||
|
volunteer: {
|
||||||
|
title: "Volunteer Work",
|
||||||
|
},
|
||||||
|
awards: {
|
||||||
|
title: "Awards",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
title: "Atridad Lahiji",
|
||||||
|
description:
|
||||||
|
"Personal website of Atridad Lahiji - Researcher, Full-Stack Developer, and IT Professional",
|
||||||
|
url: "https://atri.dad",
|
||||||
|
author: "Atridad Lahiji",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
talks: [
|
||||||
|
{
|
||||||
|
id: "devedmonton-hateoas",
|
||||||
|
name: "Hypermedia as the engine of application state - An Introduction",
|
||||||
|
description:
|
||||||
|
"A basic introduction to the concepts behind HATEOAS or Hypermedia as the engine of application state.",
|
||||||
|
link: "/files/DevEdmonton_Talk_HATEOAS.pdf",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
id: "openclimb",
|
||||||
|
name: "OpenClimb",
|
||||||
|
description: "FOSS Rock Climbing Tracker for iOS and Android",
|
||||||
|
link: "https://git.atri.dad/atridad/OpenClimb",
|
||||||
|
tags: [
|
||||||
|
"kotlin",
|
||||||
|
"jetpack compose",
|
||||||
|
"swift",
|
||||||
|
"swiftui",
|
||||||
|
"mobile",
|
||||||
|
"monorepo",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "muse",
|
||||||
|
name: "muse",
|
||||||
|
description: "Go-based music generation using TOML song definitions",
|
||||||
|
link: "https://git.atri.dad/atridad/muse",
|
||||||
|
tags: ["golang", "cli"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "magiccounter",
|
||||||
|
name: "MagicCounter",
|
||||||
|
description: "Jeckpack Compose based Magic the Gathering Health Tracker",
|
||||||
|
link: "https://git.atri.dad/atridad/MagicCounter",
|
||||||
|
tags: ["kotlin", "mobile"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "mealient",
|
||||||
|
name: "Mealient (Fork of project by Kirill Kamakin)",
|
||||||
|
description: "An Android client for a self-hosted recipe manager Mealie.",
|
||||||
|
link: "https://git.atri.dad/atridad/Mealient",
|
||||||
|
tags: ["kotlin", "mobile", "fork"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "goth-stack",
|
||||||
|
name: "GOTH Stack",
|
||||||
|
description:
|
||||||
|
"🚀 A Web Application Template Powered by HTMX + Go + Tailwind 🚀",
|
||||||
|
link: "https://git.atri.dad/atridad/goth.stack",
|
||||||
|
tags: ["golang", "web"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "himbot",
|
||||||
|
name: "Himbot",
|
||||||
|
description:
|
||||||
|
"A discord bot written in Go. Loosly named after my username online (HimbothySwaggins).",
|
||||||
|
link: "https://git.atri.dad/atridad/himbot",
|
||||||
|
tags: ["golang", "webserver"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "loadr",
|
||||||
|
name: "loadr",
|
||||||
|
description:
|
||||||
|
"A lightweight REST load testing tool with robust support for different verbs, token auth, and performance reports.",
|
||||||
|
link: "https://git.atri.dad/atridad/loadr",
|
||||||
|
tags: ["golang", "cli"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
sections: {
|
||||||
|
resume: {
|
||||||
|
name: "Resume",
|
||||||
|
path: "/resume",
|
||||||
|
description: "Professional experience, skills, and background",
|
||||||
|
},
|
||||||
|
posts: {
|
||||||
|
name: "Blog Posts",
|
||||||
|
path: "/posts",
|
||||||
|
description: "Technical articles and thoughts",
|
||||||
|
},
|
||||||
|
talks: {
|
||||||
|
name: "Talks",
|
||||||
|
path: "/talks",
|
||||||
|
description: "Conference talks and presentations",
|
||||||
|
},
|
||||||
|
projects: {
|
||||||
|
name: "Projects",
|
||||||
|
path: "/projects",
|
||||||
|
description: "Personal and professional projects",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
socialLinks: [
|
||||||
|
{
|
||||||
|
id: "email",
|
||||||
|
name: "Email",
|
||||||
|
url: "mailto:me@atri.dad",
|
||||||
|
icon: EMAIL_ICON,
|
||||||
|
ariaLabel: "Email me",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "rss",
|
||||||
|
name: "RSS Feed",
|
||||||
|
url: "/feed",
|
||||||
|
icon: RSS_ICON,
|
||||||
|
ariaLabel: "RSS Feed",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "gitea",
|
||||||
|
name: "Forgejo (Git)",
|
||||||
|
url: "https://git.atri.dad/atridad",
|
||||||
|
icon: GITEA_ICON,
|
||||||
|
ariaLabel: "Forgejo (Git)",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "bluesky",
|
||||||
|
name: "Bluesky",
|
||||||
|
url: "https://bsky.app/profile/atri.dad",
|
||||||
|
icon: BLUESKY_ICON,
|
||||||
|
ariaLabel: "Bluesky Profile",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
techLinks: [
|
||||||
|
{
|
||||||
|
id: "react",
|
||||||
|
name: "React",
|
||||||
|
url: "https://react.dev/",
|
||||||
|
icon: REACT_ICON,
|
||||||
|
ariaLabel: "React",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "typescript",
|
||||||
|
name: "TypeScript",
|
||||||
|
url: "https://www.typescriptlang.org/",
|
||||||
|
icon: TYPESCRIPT_ICON,
|
||||||
|
ariaLabel: "TypeScript",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "astro",
|
||||||
|
name: "Astro",
|
||||||
|
url: "https://astro.build/",
|
||||||
|
icon: ASTRO_ICON,
|
||||||
|
ariaLabel: "Astro",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "go",
|
||||||
|
name: "Go",
|
||||||
|
url: "https://go.dev/",
|
||||||
|
icon: GO_ICON,
|
||||||
|
ariaLabel: "Go",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "postgresql",
|
||||||
|
name: "PostgreSQL",
|
||||||
|
url: "https://www.postgresql.org/",
|
||||||
|
icon: POSTGRESQL_ICON,
|
||||||
|
ariaLabel: "PostgreSQL",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "redis",
|
||||||
|
name: "Redis",
|
||||||
|
url: "https://redis.io/",
|
||||||
|
icon: REDIS_ICON,
|
||||||
|
ariaLabel: "Redis",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "docker",
|
||||||
|
name: "Docker",
|
||||||
|
url: "https://www.docker.com/",
|
||||||
|
icon: DOCKER_ICON,
|
||||||
|
ariaLabel: "Docker",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "kotlin",
|
||||||
|
name: "Kotlin",
|
||||||
|
url: "https://kotlinlang.org/",
|
||||||
|
icon: KOTLIN_ICON,
|
||||||
|
ariaLabel: "Kotlin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "swift",
|
||||||
|
name: "Swift",
|
||||||
|
url: "https://www.swift.org/",
|
||||||
|
icon: SWIFT_ICON,
|
||||||
|
ariaLabel: "Swift",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "flutter",
|
||||||
|
name: "Flutter",
|
||||||
|
url: "https://flutter.dev",
|
||||||
|
icon: FLUTTER_ICON,
|
||||||
|
ariaLabel: "Flutter",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
navigationItems: [
|
||||||
|
{
|
||||||
|
id: "home",
|
||||||
|
name: "Home",
|
||||||
|
path: "/",
|
||||||
|
tooltip: "Home",
|
||||||
|
icon: Home,
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "posts",
|
||||||
|
name: "Posts",
|
||||||
|
path: "/posts",
|
||||||
|
tooltip: "Posts",
|
||||||
|
icon: Newspaper,
|
||||||
|
enabled: true,
|
||||||
|
isActive: (path: string) =>
|
||||||
|
path.startsWith("/posts") || path.startsWith("/post/"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "resume",
|
||||||
|
name: "Resume",
|
||||||
|
path: "/resume",
|
||||||
|
tooltip: "Resume",
|
||||||
|
icon: FileUser,
|
||||||
|
enabled: !!(resumeToml && resumeToml.trim()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "projects",
|
||||||
|
name: "Projects",
|
||||||
|
path: "/projects",
|
||||||
|
tooltip: "Projects",
|
||||||
|
icon: CodeXml,
|
||||||
|
enabled: true,
|
||||||
|
isActive: (path: string) => path.startsWith("/projects"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "talks",
|
||||||
|
name: "Talks",
|
||||||
|
path: "/talks",
|
||||||
|
tooltip: "Talks",
|
||||||
|
icon: Megaphone,
|
||||||
|
enabled: true,
|
||||||
|
isActive: (path: string) => path.startsWith("/talks"),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as const;
|
||||||
@@ -1,361 +0,0 @@
|
|||||||
import type {
|
|
||||||
Talk,
|
|
||||||
Project,
|
|
||||||
SocialLink,
|
|
||||||
TechLink,
|
|
||||||
NavigationItem,
|
|
||||||
PersonalInfo,
|
|
||||||
HomepageSections,
|
|
||||||
SiteConfig,
|
|
||||||
ResumeConfig,
|
|
||||||
} from "../types";
|
|
||||||
|
|
||||||
// Import Lucide Icons
|
|
||||||
import {
|
|
||||||
Home,
|
|
||||||
Newspaper,
|
|
||||||
FileUser,
|
|
||||||
CodeXml,
|
|
||||||
Megaphone,
|
|
||||||
} from "lucide-preact";
|
|
||||||
|
|
||||||
import logo from "../assets/logo_real.webp";
|
|
||||||
import resumeToml from "../assets/resume.toml?raw";
|
|
||||||
|
|
||||||
// Astro Icon references
|
|
||||||
const EMAIL_ICON = "mdi:email";
|
|
||||||
const RSS_ICON = "mdi:rss";
|
|
||||||
const GITEA_ICON = "simple-icons:gitea";
|
|
||||||
const BLUESKY_ICON = "simple-icons:bluesky";
|
|
||||||
const REACT_ICON = "simple-icons:react";
|
|
||||||
const TYPESCRIPT_ICON = "simple-icons:typescript";
|
|
||||||
const ASTRO_ICON = "simple-icons:astro";
|
|
||||||
const GO_ICON = "simple-icons:go";
|
|
||||||
const POSTGRESQL_ICON = "simple-icons:postgresql";
|
|
||||||
const REDIS_ICON = "simple-icons:redis";
|
|
||||||
const DOCKER_ICON = "simple-icons:docker";
|
|
||||||
const KOTLIN_ICON = "simple-icons:kotlin";
|
|
||||||
const SWIFT_ICON = "simple-icons:swift";
|
|
||||||
const FLUTTER_ICON = "simple-icons:flutter";
|
|
||||||
|
|
||||||
// Personal Information Configuration
|
|
||||||
export const personalInfo: PersonalInfo = {
|
|
||||||
name: "Atridad Lahiji",
|
|
||||||
profileImage: {
|
|
||||||
src: logo,
|
|
||||||
alt: "A drawing of Atridad Lahiji by Shelze!",
|
|
||||||
},
|
|
||||||
tagline: "Researcher, Full-Stack Developer, and IT Professional",
|
|
||||||
description: "Researcher, Full-Stack Developer, and IT Professional",
|
|
||||||
};
|
|
||||||
|
|
||||||
// Homepage Section Configuration
|
|
||||||
export const homepageSections: HomepageSections = {
|
|
||||||
socialLinks: {
|
|
||||||
title: "Places I Exist:",
|
|
||||||
description: "Find me across the web",
|
|
||||||
},
|
|
||||||
techStack: {
|
|
||||||
title: "Technologies I Use:",
|
|
||||||
description: "Technologies and tools I work with",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resume Configuration
|
|
||||||
export const resumeConfig: ResumeConfig = {
|
|
||||||
tomlFile: resumeToml,
|
|
||||||
layout: {
|
|
||||||
leftColumn: ["experience", "volunteer"],
|
|
||||||
rightColumn: ["skills", "education", "awards"],
|
|
||||||
},
|
|
||||||
sections: {
|
|
||||||
enabled: [
|
|
||||||
"summary",
|
|
||||||
"experience",
|
|
||||||
"education",
|
|
||||||
"skills",
|
|
||||||
"volunteer",
|
|
||||||
"awards",
|
|
||||||
],
|
|
||||||
summary: {
|
|
||||||
title: "Summary",
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
experience: {
|
|
||||||
title: "Professional Experience",
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
education: {
|
|
||||||
title: "Education",
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
skills: {
|
|
||||||
title: "Skills",
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
volunteer: {
|
|
||||||
title: "Volunteer Work",
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
awards: {
|
|
||||||
title: "Awards",
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Site Metadata Configuration
|
|
||||||
export const siteConfig: SiteConfig = {
|
|
||||||
personal: personalInfo,
|
|
||||||
homepage: homepageSections,
|
|
||||||
resume: resumeConfig,
|
|
||||||
meta: {
|
|
||||||
title: "Atridad Lahiji",
|
|
||||||
description:
|
|
||||||
"Personal website of Atridad Lahiji - Researcher, Full-Stack Developer, and IT Professional",
|
|
||||||
url: "https://atri.dad",
|
|
||||||
author: "Atridad Lahiji",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const talks: Talk[] = [
|
|
||||||
{
|
|
||||||
id: "devedmonton-hateoas",
|
|
||||||
name: "Hypermedia as the engine of application state - An Introduction",
|
|
||||||
description:
|
|
||||||
"A basic introduction to the concepts behind HATEOAS or Hypermedia as the engine of application state.",
|
|
||||||
link: "/files/DevEdmonton_Talk_HATEOAS.pdf",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const projects: Project[] = [
|
|
||||||
{
|
|
||||||
id: "muse",
|
|
||||||
name: "muse",
|
|
||||||
description: "Go-based music generation using TOML song definitions",
|
|
||||||
link: "https://git.atri.dad/atridad/muse",
|
|
||||||
tags: ["golang","cli"]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "openclimb",
|
|
||||||
name: "OpenClimb",
|
|
||||||
description: "Jeckpack Compose based Rock Climbing Tracker",
|
|
||||||
link: "https://git.atri.dad/atridad/OpenClimb",
|
|
||||||
tags: ["kotlin","mobile"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "mealient",
|
|
||||||
name: "Mealient (Fork of project by Kirill Kamakin)",
|
|
||||||
description:
|
|
||||||
"An Android client for a self-hosted recipe manager Mealie.",
|
|
||||||
link: "https://git.atri.dad/atridad/Mealient",
|
|
||||||
tags: ["kotlin","mobile"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "magiccounter",
|
|
||||||
name: "MagicCounter",
|
|
||||||
description:
|
|
||||||
"Jeckpack Compose based Magic the Gathering Health Tracker",
|
|
||||||
link: "https://git.atri.dad/atridad/MagicCounter",
|
|
||||||
tags: ["kotlin","mobile"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "goth-stack",
|
|
||||||
name: "GOTH Stack",
|
|
||||||
description:
|
|
||||||
"🚀 A Web Application Template Powered by HTMX + Go + Tailwind 🚀",
|
|
||||||
link: "https://git.atri.dad/atridad/goth.stack",
|
|
||||||
tags: ["golang","web"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "himbot",
|
|
||||||
name: "Himbot",
|
|
||||||
description:
|
|
||||||
"A discord bot written in Go. Loosly named after my username online (HimbothySwaggins).",
|
|
||||||
link: "https://git.atri.dad/atridad/himbot",
|
|
||||||
tags: ["golang","webserver"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "loadr",
|
|
||||||
name: "loadr",
|
|
||||||
description:
|
|
||||||
"A lightweight REST load testing tool with robust support for different verbs, token auth, and performance reports.",
|
|
||||||
link: "https://git.atri.dad/atridad/loadr",
|
|
||||||
tags: ["golang","cli"],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const sections = {
|
|
||||||
resume: {
|
|
||||||
name: "Resume",
|
|
||||||
path: "/resume",
|
|
||||||
description: "Professional experience, skills, and background",
|
|
||||||
},
|
|
||||||
posts: {
|
|
||||||
name: "Blog Posts",
|
|
||||||
path: "/posts",
|
|
||||||
description: "Technical articles and thoughts",
|
|
||||||
},
|
|
||||||
talks: {
|
|
||||||
name: "Talks",
|
|
||||||
path: "/talks",
|
|
||||||
description: "Conference talks and presentations",
|
|
||||||
},
|
|
||||||
projects: {
|
|
||||||
name: "Projects",
|
|
||||||
path: "/projects",
|
|
||||||
description: "Personal and professional projects",
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const socialLinks: SocialLink[] = [
|
|
||||||
{
|
|
||||||
id: "email",
|
|
||||||
name: "Email",
|
|
||||||
url: "mailto:me@atri.dad",
|
|
||||||
icon: EMAIL_ICON,
|
|
||||||
ariaLabel: "Email me",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "rss",
|
|
||||||
name: "RSS Feed",
|
|
||||||
url: "/feed",
|
|
||||||
icon: RSS_ICON,
|
|
||||||
ariaLabel: "RSS Feed",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "gitea",
|
|
||||||
name: "Forgejo (Git)",
|
|
||||||
url: "https://git.atri.dad/atridad",
|
|
||||||
icon: GITEA_ICON,
|
|
||||||
ariaLabel: "Forgejo (Git)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "bluesky",
|
|
||||||
name: "Bluesky",
|
|
||||||
url: "https://bsky.app/profile/atri.dad",
|
|
||||||
icon: BLUESKY_ICON,
|
|
||||||
ariaLabel: "Bluesky Profile",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const techLinks: TechLink[] = [
|
|
||||||
{
|
|
||||||
id: "react",
|
|
||||||
name: "React",
|
|
||||||
url: "https://react.dev/",
|
|
||||||
icon: REACT_ICON,
|
|
||||||
ariaLabel: "React",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "typescript",
|
|
||||||
name: "TypeScript",
|
|
||||||
url: "https://www.typescriptlang.org/",
|
|
||||||
icon: TYPESCRIPT_ICON,
|
|
||||||
ariaLabel: "TypeScript",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "astro",
|
|
||||||
name: "Astro",
|
|
||||||
url: "https://astro.build/",
|
|
||||||
icon: ASTRO_ICON,
|
|
||||||
ariaLabel: "Astro",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "go",
|
|
||||||
name: "Go",
|
|
||||||
url: "https://go.dev/",
|
|
||||||
icon: GO_ICON,
|
|
||||||
ariaLabel: "Go",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "postgresql",
|
|
||||||
name: "PostgreSQL",
|
|
||||||
url: "https://www.postgresql.org/",
|
|
||||||
icon: POSTGRESQL_ICON,
|
|
||||||
ariaLabel: "PostgreSQL",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "redis",
|
|
||||||
name: "Redis",
|
|
||||||
url: "https://redis.io/",
|
|
||||||
icon: REDIS_ICON,
|
|
||||||
ariaLabel: "Redis",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "docker",
|
|
||||||
name: "Docker",
|
|
||||||
url: "https://www.docker.com/",
|
|
||||||
icon: DOCKER_ICON,
|
|
||||||
ariaLabel: "Docker",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "kotlin",
|
|
||||||
name: "Kotlin",
|
|
||||||
url: "https://kotlinlang.org/",
|
|
||||||
icon: KOTLIN_ICON,
|
|
||||||
ariaLabel: "Kotlin",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "swift",
|
|
||||||
name: "Swift",
|
|
||||||
url: "https://www.swift.org/",
|
|
||||||
icon: SWIFT_ICON,
|
|
||||||
ariaLabel: "Swift",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "flutter",
|
|
||||||
name: "Flutter",
|
|
||||||
url: "https://flutter.dev",
|
|
||||||
icon: FLUTTER_ICON,
|
|
||||||
ariaLabel: "Flutter",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const navigationItems: NavigationItem[] = [
|
|
||||||
{
|
|
||||||
id: "home",
|
|
||||||
name: "Home",
|
|
||||||
path: "/",
|
|
||||||
tooltip: "Home",
|
|
||||||
icon: Home,
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "posts",
|
|
||||||
name: "Posts",
|
|
||||||
path: "/posts",
|
|
||||||
tooltip: "Posts",
|
|
||||||
icon: Newspaper,
|
|
||||||
enabled: true,
|
|
||||||
isActive: (path: string) =>
|
|
||||||
path.startsWith("/posts") || path.startsWith("/post/"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "resume",
|
|
||||||
name: "Resume",
|
|
||||||
path: "/resume",
|
|
||||||
tooltip: "Resume",
|
|
||||||
icon: FileUser,
|
|
||||||
enabled: !!(resumeConfig.tomlFile && resumeConfig.tomlFile.trim()),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "projects",
|
|
||||||
name: "Projects",
|
|
||||||
path: "/projects",
|
|
||||||
tooltip: "Projects",
|
|
||||||
icon: CodeXml,
|
|
||||||
enabled: true,
|
|
||||||
isActive: (path: string) => path.startsWith("/projects"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "talks",
|
|
||||||
name: "Talks",
|
|
||||||
path: "/talks",
|
|
||||||
tooltip: "Talks",
|
|
||||||
icon: Megaphone,
|
|
||||||
enabled: true,
|
|
||||||
isActive: (path: string) => path.startsWith("/talks"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
import { ClientRouter } from "astro:transitions";
|
import { ClientRouter } from "astro:transitions";
|
||||||
import NavigationBar from "../components/NavigationBar";
|
import NavigationBar from "../components/NavigationBar";
|
||||||
import ScrollUpButton from "../components/ScrollUpButton";
|
import ScrollUpButton from "../components/ScrollUpButton";
|
||||||
import { siteConfig } from "../config/data";
|
import { config } from "../config";
|
||||||
const currentPath = Astro.url.pathname;
|
const currentPath = Astro.url.pathname;
|
||||||
import "../styles/global.css";
|
import "../styles/global.css";
|
||||||
|
|
||||||
@@ -14,9 +14,9 @@ export interface Props {
|
|||||||
const { title, description } = Astro.props;
|
const { title, description } = Astro.props;
|
||||||
|
|
||||||
const pageTitle = title
|
const pageTitle = title
|
||||||
? `${title} | ${siteConfig.meta.title}`
|
? `${title} | ${config.siteConfig.meta.title}`
|
||||||
: siteConfig.meta.title;
|
: config.siteConfig.meta.title;
|
||||||
const pageDescription = description || siteConfig.meta.description;
|
const pageDescription = description || config.siteConfig.meta.description;
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
@@ -27,7 +27,7 @@ const pageDescription = description || siteConfig.meta.description;
|
|||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
<meta name="description" content={pageDescription} />
|
<meta name="description" content={pageDescription} />
|
||||||
<meta name="author" content={siteConfig.meta.author} />
|
<meta name="author" content={config.siteConfig.meta.author} />
|
||||||
<title>{pageTitle}</title>
|
<title>{pageTitle}</title>
|
||||||
<ClientRouter />
|
<ClientRouter />
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import * as TOML from "@iarna/toml";
|
import * as TOML from "@iarna/toml";
|
||||||
import { siteConfig } from "../../config/data";
|
import { config } from "../../config";
|
||||||
|
|
||||||
export const GET: APIRoute = async ({ request }) => {
|
export const GET: APIRoute = async ({ request }) => {
|
||||||
try {
|
try {
|
||||||
// Check if resume TOML content is configured
|
// Check if resume TOML content is configured
|
||||||
if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) {
|
if (!config.resumeConfig.tomlFile || !config.resumeConfig.tomlFile.trim()) {
|
||||||
return new Response("Resume not configured", { status: 404 });
|
return new Response("Resume not configured", { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
let tomlContent: string;
|
let tomlContent: string;
|
||||||
|
|
||||||
// Check if tomlFile is a path (starts with /) or raw content
|
// Check if tomlFile is a path (starts with /) or raw content
|
||||||
if (siteConfig.resume.tomlFile.startsWith("/")) {
|
if (config.resumeConfig.tomlFile.startsWith("/")) {
|
||||||
// It's a file path - fetch it
|
// It's a file path - fetch it
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const baseUrl = `${url.protocol}//${url.host}`;
|
const baseUrl = `${url.protocol}//${url.host}`;
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`);
|
const response = await fetch(`${baseUrl}${config.resumeConfig.tomlFile}`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -28,7 +28,7 @@ export const GET: APIRoute = async ({ request }) => {
|
|||||||
tomlContent = await response.text();
|
tomlContent = await response.text();
|
||||||
} else {
|
} else {
|
||||||
// It's raw TOML content
|
// It's raw TOML content
|
||||||
tomlContent = siteConfig.resume.tomlFile;
|
tomlContent = config.resumeConfig.tomlFile;
|
||||||
}
|
}
|
||||||
const resumeData = TOML.parse(tomlContent);
|
const resumeData = TOML.parse(tomlContent);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import { chromium } from "playwright";
|
import { chromium } from "playwright";
|
||||||
import { siteConfig } from "../../../config/data";
|
import { config } from "../../../config";
|
||||||
import * as TOML from "@iarna/toml";
|
import * as TOML from "@iarna/toml";
|
||||||
|
|
||||||
// Helper function to fetch and return SVG icon from Simple Icons CDN
|
// Helper function to fetch and return SVG icon from Simple Icons CDN
|
||||||
@@ -300,7 +300,7 @@ const fetchProfileIcons = async (profiles: any[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
||||||
const resumeConfig = siteConfig.resume;
|
const resumeConfig = config.resumeConfig;
|
||||||
// Use layout from TOML data, fallback to site config, then to default
|
// Use layout from TOML data, fallback to site config, then to default
|
||||||
const layout = data.layout
|
const layout = data.layout
|
||||||
? {
|
? {
|
||||||
@@ -405,19 +405,19 @@ async function generatePDFFromToml(tomlContent: string): Promise<Uint8Array> {
|
|||||||
|
|
||||||
export const GET: APIRoute = async ({ request }) => {
|
export const GET: APIRoute = async ({ request }) => {
|
||||||
try {
|
try {
|
||||||
if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) {
|
if (!config.resumeConfig.tomlFile || !config.resumeConfig.tomlFile.trim()) {
|
||||||
return new Response("Resume not configured", { status: 404 });
|
return new Response("Resume not configured", { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
let tomlContent: string;
|
let tomlContent: string;
|
||||||
|
|
||||||
// Check if tomlFile is a path (starts with /) or raw content
|
// Check if tomlFile is a path (starts with /) or raw content
|
||||||
if (siteConfig.resume.tomlFile.startsWith("/")) {
|
if (config.resumeConfig.tomlFile.startsWith("/")) {
|
||||||
// It's a file path - fetch it
|
// It's a file path - fetch it
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
const baseUrl = `${url.protocol}//${url.host}`;
|
const baseUrl = `${url.protocol}//${url.host}`;
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`);
|
const response = await fetch(`${baseUrl}${config.resumeConfig.tomlFile}`);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -428,7 +428,7 @@ export const GET: APIRoute = async ({ request }) => {
|
|||||||
tomlContent = await response.text();
|
tomlContent = await response.text();
|
||||||
} else {
|
} else {
|
||||||
// It's raw TOML content
|
// It's raw TOML content
|
||||||
tomlContent = siteConfig.resume.tomlFile;
|
tomlContent = config.resumeConfig.tomlFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pdfBuffer = await generatePDFFromToml(tomlContent);
|
const pdfBuffer = await generatePDFFromToml(tomlContent);
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import { Image } from "astro:assets";
|
|||||||
import SocialLinks from "../components/SocialLinks.astro";
|
import SocialLinks from "../components/SocialLinks.astro";
|
||||||
import TechLinks from "../components/TechLinks.astro";
|
import TechLinks from "../components/TechLinks.astro";
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import { personalInfo, homepageSections } from "../config/data";
|
import { config } from "../config";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<Image
|
<Image
|
||||||
src={personalInfo.profileImage.src}
|
src={config.personalInfo.profileImage.src}
|
||||||
alt={personalInfo.profileImage.alt}
|
alt={config.personalInfo.profileImage.alt}
|
||||||
width={300}
|
width={300}
|
||||||
height={300}
|
height={300}
|
||||||
layout="constrained"
|
layout="constrained"
|
||||||
@@ -18,24 +18,22 @@ import { personalInfo, homepageSections } from "../config/data";
|
|||||||
style="max-width: 12rem; width: 100%;"
|
style="max-width: 12rem; width: 100%;"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<h1
|
<h1 class="text-primary text-4xl sm:text-6xl font-bold text-center">
|
||||||
class="text-primary text-4xl sm:text-6xl font-bold text-center"
|
{config.personalInfo.name}
|
||||||
>
|
|
||||||
{personalInfo.name}
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<h2 class="text-xl sm:text-3xl font-bold text-center mx-6">
|
<h2 class="text-xl sm:text-3xl font-bold text-center mx-6">
|
||||||
{personalInfo.tagline}
|
{config.personalInfo.tagline}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<h3 class="text-lg sm:text-2xl font-bold">
|
<h3 class="text-lg sm:text-2xl font-bold">
|
||||||
{homepageSections.socialLinks.title}
|
{config.homepageSections.socialLinks.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<SocialLinks />
|
<SocialLinks />
|
||||||
|
|
||||||
<h3 class="text-lg sm:text-2xl font-bold">
|
<h3 class="text-lg sm:text-2xl font-bold">
|
||||||
{homepageSections.techStack.title}
|
{config.homepageSections.techStack.title}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<TechLinks />
|
<TechLinks />
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const { Content } = await post.render();
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<div class="min-h-screen p-4 md:p-8">
|
<div class="w-full p-4 md:p-8">
|
||||||
<div class="max-w-3xl mx-auto">
|
<div class="max-w-3xl mx-auto">
|
||||||
<div class="p-4 md:p-8">
|
<div class="p-4 md:p-8">
|
||||||
<h1 class="text-4xl md:text-5xl font-bold text-primary mb-6">
|
<h1 class="text-4xl md:text-5xl font-bold text-primary mb-6">
|
||||||
@@ -45,7 +45,10 @@ const { Content } = await post.render();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Back button */}
|
{/* Back button */}
|
||||||
<a href="/posts" class="btn btn-outline btn-primary btn-sm font-bold">
|
<a
|
||||||
|
href="/posts"
|
||||||
|
class="btn btn-outline btn-primary btn-sm font-bold"
|
||||||
|
>
|
||||||
<Icon name="mdi:arrow-left" class="text-lg" />
|
<Icon name="mdi:arrow-left" class="text-lg" />
|
||||||
Back
|
Back
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const sortedPosts = posts.sort(
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<div class="min-h-screen min-w-screen p-4 sm:p-8">
|
<div class="w-full p-4 sm:p-8">
|
||||||
<h1
|
<h1
|
||||||
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import ProjectCard from "../components/ProjectCard.astro";
|
import ProjectCard from "../components/ProjectCard.astro";
|
||||||
import { projects } from "../config/data";
|
import { config } from "../config";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<div class="min-h-screen min-w-screen p-4 sm:p-8">
|
<div class="w-full p-4 sm:p-8">
|
||||||
<h1
|
<h1
|
||||||
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
||||||
>
|
>
|
||||||
@@ -14,11 +14,15 @@ import { projects } from "../config/data";
|
|||||||
<div
|
<div
|
||||||
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
|
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
|
||||||
>
|
>
|
||||||
{projects.map((project) => <ProjectCard project={project} />)}
|
{
|
||||||
|
config.projects.map((project) => (
|
||||||
|
<ProjectCard project={project} />
|
||||||
|
))
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
projects.length === 0 && (
|
config.projects.length === 0 && (
|
||||||
<p class="text-center text-gray-500 mt-12">
|
<p class="text-center text-gray-500 mt-12">
|
||||||
No projects available yet. Check back soon!
|
No projects available yet. Check back soon!
|
||||||
</p>
|
</p>
|
||||||
@@ -26,4 +30,3 @@ import { projects } from "../config/data";
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Layout from "../layouts/Layout.astro";
|
|||||||
import ResumeSkills from "../components/ResumeSkills";
|
import ResumeSkills from "../components/ResumeSkills";
|
||||||
import ResumeDownloadButton from "../components/ResumeDownloadButton";
|
import ResumeDownloadButton from "../components/ResumeDownloadButton";
|
||||||
import ResumeSettingsModal from "../components/ResumeSettingsModal";
|
import ResumeSettingsModal from "../components/ResumeSettingsModal";
|
||||||
import { siteConfig } from "../config/data";
|
import { config } from "../config";
|
||||||
import "../styles/global.css";
|
import "../styles/global.css";
|
||||||
import * as TOML from "@iarna/toml";
|
import * as TOML from "@iarna/toml";
|
||||||
|
|
||||||
@@ -57,16 +57,18 @@ interface ResumeData {
|
|||||||
let resumeData: ResumeData | undefined = undefined;
|
let resumeData: ResumeData | undefined = undefined;
|
||||||
let fetchError: string | null = null;
|
let fetchError: string | null = null;
|
||||||
|
|
||||||
if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) {
|
if (!config.resumeConfig.tomlFile || !config.resumeConfig.tomlFile.trim()) {
|
||||||
return Astro.redirect("/");
|
return Astro.redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let tomlContent: string;
|
let tomlContent: string;
|
||||||
|
|
||||||
if (siteConfig.resume.tomlFile.startsWith("/")) {
|
if (config.resumeConfig.tomlFile.startsWith("/")) {
|
||||||
const baseUrl = Astro.url.origin;
|
const baseUrl = Astro.url.origin;
|
||||||
const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`);
|
const response = await fetch(
|
||||||
|
`${baseUrl}${config.resumeConfig.tomlFile}`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -76,7 +78,7 @@ try {
|
|||||||
|
|
||||||
tomlContent = await response.text();
|
tomlContent = await response.text();
|
||||||
} else {
|
} else {
|
||||||
tomlContent = siteConfig.resume.tomlFile;
|
tomlContent = config.resumeConfig.tomlFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
resumeData = TOML.parse(tomlContent) as unknown as ResumeData;
|
resumeData = TOML.parse(tomlContent) as unknown as ResumeData;
|
||||||
@@ -86,7 +88,7 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = resumeData;
|
const data = resumeData;
|
||||||
const resumeConfig = siteConfig.resume;
|
const resumeConfig = config.resumeConfig;
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return Astro.redirect("/");
|
return Astro.redirect("/");
|
||||||
@@ -136,11 +138,13 @@ if (!data) {
|
|||||||
<ResumeDownloadButton client:load />
|
<ResumeDownloadButton client:load />
|
||||||
|
|
||||||
{
|
{
|
||||||
data.summary && resumeConfig.sections.summary?.enabled && (
|
data.summary &&
|
||||||
|
resumeConfig.sections.enabled.includes("summary") && (
|
||||||
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6 break-words">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.summary.title || "Summary"}
|
{resumeConfig.sections.summary?.title ||
|
||||||
|
"Summary"}
|
||||||
</h2>
|
</h2>
|
||||||
<div>{data.summary.content}</div>
|
<div>{data.summary.content}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -151,11 +155,12 @@ if (!data) {
|
|||||||
{
|
{
|
||||||
data.skills &&
|
data.skills &&
|
||||||
data.skills.length > 0 &&
|
data.skills.length > 0 &&
|
||||||
resumeConfig.sections.skills?.enabled && (
|
resumeConfig.sections.enabled.includes("skills") && (
|
||||||
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6 break-words">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.skills.title || "Skills"}
|
{resumeConfig.sections.skills?.title ||
|
||||||
|
"Skills"}
|
||||||
</h2>
|
</h2>
|
||||||
<ResumeSkills
|
<ResumeSkills
|
||||||
skills={data.skills.map((skill, index) => ({
|
skills={data.skills.map((skill, index) => ({
|
||||||
@@ -173,11 +178,11 @@ if (!data) {
|
|||||||
{
|
{
|
||||||
data.experience &&
|
data.experience &&
|
||||||
data.experience.length > 0 &&
|
data.experience.length > 0 &&
|
||||||
resumeConfig.sections.experience?.enabled && (
|
resumeConfig.sections.enabled.includes("experience") && (
|
||||||
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6 break-words">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.experience.title ||
|
{resumeConfig.sections.experience?.title ||
|
||||||
"Experience"}
|
"Experience"}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4 sm:space-y-6">
|
<div class="space-y-4 sm:space-y-6">
|
||||||
@@ -222,11 +227,11 @@ if (!data) {
|
|||||||
{
|
{
|
||||||
data.education &&
|
data.education &&
|
||||||
data.education.length > 0 &&
|
data.education.length > 0 &&
|
||||||
resumeConfig.sections.education?.enabled && (
|
resumeConfig.sections.enabled.includes("education") && (
|
||||||
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6 break-words">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.education.title ||
|
{resumeConfig.sections.education?.title ||
|
||||||
"Education"}
|
"Education"}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -264,11 +269,11 @@ if (!data) {
|
|||||||
{
|
{
|
||||||
data.volunteer &&
|
data.volunteer &&
|
||||||
data.volunteer.length > 0 &&
|
data.volunteer.length > 0 &&
|
||||||
resumeConfig.sections.volunteer?.enabled && (
|
resumeConfig.sections.enabled.includes("volunteer") && (
|
||||||
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6 break-words">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.volunteer.title ||
|
{resumeConfig.sections.volunteer?.title ||
|
||||||
"Volunteer Work"}
|
"Volunteer Work"}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -296,11 +301,11 @@ if (!data) {
|
|||||||
{
|
{
|
||||||
data.awards &&
|
data.awards &&
|
||||||
data.awards.length > 0 &&
|
data.awards.length > 0 &&
|
||||||
resumeConfig.sections.awards?.enabled && (
|
resumeConfig.sections.enabled.includes("awards") && (
|
||||||
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
|
||||||
<div class="card-body p-4 sm:p-6 break-words">
|
<div class="card-body p-4 sm:p-6 break-words">
|
||||||
<h2 class="card-title text-xl sm:text-2xl">
|
<h2 class="card-title text-xl sm:text-2xl">
|
||||||
{resumeConfig.sections.awards.title ||
|
{resumeConfig.sections.awards?.title ||
|
||||||
"Awards & Recognition"}
|
"Awards & Recognition"}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "../layouts/Layout.astro";
|
||||||
import TalkCard from "../components/TalkCard.astro";
|
import TalkCard from "../components/TalkCard.astro";
|
||||||
import { talks } from "../config/data";
|
import { config } from "../config";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<div class="min-h-screen min-w-screen p-4 sm:p-8">
|
<div class="w-full p-4 sm:p-8">
|
||||||
<h1
|
<h1
|
||||||
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
|
||||||
>
|
>
|
||||||
@@ -14,11 +14,11 @@ import { talks } from "../config/data";
|
|||||||
<div
|
<div
|
||||||
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
|
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
|
||||||
>
|
>
|
||||||
{talks.map((talk) => <TalkCard talk={talk} />)}
|
{config.talks.map((talk) => <TalkCard talk={talk} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
talks.length === 0 && (
|
config.talks.length === 0 && (
|
||||||
<p class="text-center text-gray-500 mt-12">
|
<p class="text-center text-gray-500 mt-12">
|
||||||
No talks available yet. Check back soon!
|
No talks available yet. Check back soon!
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -50,41 +50,43 @@ export interface NavigationItem {
|
|||||||
isActive?: (path: string) => boolean;
|
isActive?: (path: string) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ResumeSectionKey =
|
||||||
|
| "summary"
|
||||||
|
| "experience"
|
||||||
|
| "education"
|
||||||
|
| "skills"
|
||||||
|
| "volunteer"
|
||||||
|
| "profiles"
|
||||||
|
| "awards";
|
||||||
|
|
||||||
export interface ResumeConfig {
|
export interface ResumeConfig {
|
||||||
tomlFile: string; // Can be a file path or raw TOML content
|
tomlFile: string; // Can be a file path or raw TOML content
|
||||||
layout?: {
|
layout?: {
|
||||||
leftColumn?: string[];
|
leftColumn?: ResumeSectionKey[];
|
||||||
rightColumn?: string[];
|
rightColumn?: ResumeSectionKey[];
|
||||||
};
|
};
|
||||||
sections: {
|
sections: {
|
||||||
enabled: string[];
|
enabled: ResumeSectionKey[];
|
||||||
summary?: {
|
summary?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
enabled?: boolean;
|
|
||||||
};
|
};
|
||||||
experience?: {
|
experience?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
enabled?: boolean;
|
|
||||||
};
|
};
|
||||||
education?: {
|
education?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
enabled?: boolean;
|
|
||||||
};
|
};
|
||||||
skills?: {
|
skills?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
enabled?: boolean;
|
|
||||||
};
|
};
|
||||||
volunteer?: {
|
volunteer?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
enabled?: boolean;
|
|
||||||
};
|
};
|
||||||
profiles?: {
|
profiles?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
enabled?: boolean;
|
|
||||||
};
|
};
|
||||||
awards?: {
|
awards?: {
|
||||||
title?: string;
|
title?: string;
|
||||||
enabled?: boolean;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -123,3 +125,37 @@ export interface SiteConfig {
|
|||||||
author: string;
|
author: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
personalInfo: PersonalInfo;
|
||||||
|
homepageSections: HomepageSections;
|
||||||
|
resumeConfig: ResumeConfig;
|
||||||
|
siteConfig: SiteConfig;
|
||||||
|
talks: readonly Talk[];
|
||||||
|
projects: readonly Project[];
|
||||||
|
sections: {
|
||||||
|
readonly resume: {
|
||||||
|
readonly name: string;
|
||||||
|
readonly path: string;
|
||||||
|
readonly description: string;
|
||||||
|
};
|
||||||
|
readonly posts: {
|
||||||
|
readonly name: string;
|
||||||
|
readonly path: string;
|
||||||
|
readonly description: string;
|
||||||
|
};
|
||||||
|
readonly talks: {
|
||||||
|
readonly name: string;
|
||||||
|
readonly path: string;
|
||||||
|
readonly description: string;
|
||||||
|
};
|
||||||
|
readonly projects: {
|
||||||
|
readonly name: string;
|
||||||
|
readonly path: string;
|
||||||
|
readonly description: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
socialLinks: readonly SocialLink[];
|
||||||
|
techLinks: readonly TechLink[];
|
||||||
|
navigationItems: readonly NavigationItem[];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user