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";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; # Pin to a specific branch or commit for stability
|
||||
flake-utils.url = "github:numtide/flake-utils"; # Helps with system boilerplate
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
@@ -22,7 +22,7 @@
|
||||
curl
|
||||
];
|
||||
|
||||
# Common libraries needed for Playwright across platforms (e.g., for WebKit/Firefox)
|
||||
# Common libraries needed for Playwright
|
||||
playwrightCommonLibs = with pkgs; [
|
||||
glib
|
||||
nss
|
||||
@@ -44,7 +44,7 @@
|
||||
gnutls
|
||||
];
|
||||
|
||||
# Linux-specific libraries for Playwright (mostly Chromium dependencies)
|
||||
# Linux-specific libraries for Playwright
|
||||
playwrightLinuxSpecificLibs = with pkgs; [
|
||||
glibc
|
||||
libgcc
|
||||
@@ -75,7 +75,7 @@
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = commonDevTools ++ (
|
||||
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
|
||||
);
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
if [ ! -d "node_modules" ]; then
|
||||
echo "📦 Installing pnpm dependencies..."
|
||||
pnpm install --frozen-lockfile # Use --frozen-lockfile for more consistent builds
|
||||
pnpm install --frozen-lockfile
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
26
package.json
26
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "atridotdad",
|
||||
"type": "module",
|
||||
"version": "2.1.0",
|
||||
"version": "3.0.0",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
@@ -10,26 +10,26 @@
|
||||
"nix": "nix develop"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/mdx": "^4.3.4",
|
||||
"@astrojs/node": "^9.4.3",
|
||||
"@astrojs/preact": "^4.1.0",
|
||||
"@astrojs/mdx": "^4.3.6",
|
||||
"@astrojs/node": "^9.4.4",
|
||||
"@astrojs/preact": "^4.1.1",
|
||||
"@astrojs/rss": "^4.0.12",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@preact/signals": "^2.3.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tailwindcss/vite": "^4.1.12",
|
||||
"astro": "^5.13.5",
|
||||
"@tailwindcss/typography": "^0.5.18",
|
||||
"@tailwindcss/vite": "^4.1.13",
|
||||
"astro": "^5.13.10",
|
||||
"astro-icon": "^1.1.5",
|
||||
"lucide-preact": "^0.542.0",
|
||||
"lucide-preact": "^0.544.0",
|
||||
"playwright": "^1.55.0",
|
||||
"preact": "^10.27.1",
|
||||
"sharp": "^0.34.3",
|
||||
"tailwindcss": "^4.1.12"
|
||||
"preact": "^10.27.2",
|
||||
"sharp": "^0.34.4",
|
||||
"tailwindcss": "^4.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/mdi": "^1.2.3",
|
||||
"@iconify-json/simple-icons": "^1.2.50",
|
||||
"daisyui": "^5.1.6"
|
||||
"@iconify-json/simple-icons": "^1.2.52",
|
||||
"daisyui": "^5.1.14"
|
||||
},
|
||||
"pnpm": {
|
||||
"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 type { IconType, LucideIcon, AstroIconName, CustomIconComponent } from '../types';
|
||||
import { Icon } from "astro-icon/components";
|
||||
import type {
|
||||
IconType,
|
||||
LucideIcon,
|
||||
AstroIconName,
|
||||
CustomIconComponent,
|
||||
} from "../types";
|
||||
|
||||
interface IconRendererProps {
|
||||
icon: IconType;
|
||||
@@ -10,18 +15,23 @@ interface IconRendererProps {
|
||||
|
||||
// Type guard functions
|
||||
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 {
|
||||
return typeof icon === 'string';
|
||||
return typeof icon === "string";
|
||||
}
|
||||
|
||||
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)) {
|
||||
const LucideComponent = icon;
|
||||
return <LucideComponent size={size} class={className} {...props} />;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useComputed, useSignal } from "@preact/signals";
|
||||
import { useEffect } from "preact/hooks";
|
||||
import { navigationItems } from "../config/data";
|
||||
import { config } from "../config";
|
||||
import type { LucideIcon } from "../types";
|
||||
|
||||
interface NavigationBarProps {
|
||||
@@ -20,7 +20,7 @@ export default function NavigationBar({ currentPath }: NavigationBarProps) {
|
||||
});
|
||||
|
||||
// Filter out disabled navigation items
|
||||
const enabledNavigationItems = navigationItems.filter(
|
||||
const enabledNavigationItems = config.navigationItems.filter(
|
||||
(item) => item.enabled !== false,
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ interface 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">
|
||||
<h2
|
||||
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 { socialLinks } from "../config/data";
|
||||
import { config } from "../config";
|
||||
---
|
||||
|
||||
<div class="flex flex-row gap-3 text-3xl flex-wrap justify-center">
|
||||
{
|
||||
socialLinks.map((link) => {
|
||||
return (
|
||||
config.socialLinks.map((link) => (
|
||||
<a
|
||||
href={link.url}
|
||||
target={link.url.startsWith("http") ? "_blank" : undefined}
|
||||
@@ -20,7 +19,6 @@ import { socialLinks } from "../config/data";
|
||||
>
|
||||
<Icon name={link.icon} />
|
||||
</a>
|
||||
);
|
||||
})
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
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)
|
||||
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)) {
|
||||
return (
|
||||
<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 NavigationBar from "../components/NavigationBar";
|
||||
import ScrollUpButton from "../components/ScrollUpButton";
|
||||
import { siteConfig } from "../config/data";
|
||||
import { config } from "../config";
|
||||
const currentPath = Astro.url.pathname;
|
||||
import "../styles/global.css";
|
||||
|
||||
@@ -14,9 +14,9 @@ export interface Props {
|
||||
const { title, description } = Astro.props;
|
||||
|
||||
const pageTitle = title
|
||||
? `${title} | ${siteConfig.meta.title}`
|
||||
: siteConfig.meta.title;
|
||||
const pageDescription = description || siteConfig.meta.description;
|
||||
? `${title} | ${config.siteConfig.meta.title}`
|
||||
: config.siteConfig.meta.title;
|
||||
const pageDescription = description || config.siteConfig.meta.description;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
@@ -27,7 +27,7 @@ const pageDescription = description || siteConfig.meta.description;
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<meta name="description" content={pageDescription} />
|
||||
<meta name="author" content={siteConfig.meta.author} />
|
||||
<meta name="author" content={config.siteConfig.meta.author} />
|
||||
<title>{pageTitle}</title>
|
||||
<ClientRouter />
|
||||
</head>
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import * as TOML from "@iarna/toml";
|
||||
import { siteConfig } from "../../config/data";
|
||||
import { config } from "../../config";
|
||||
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// 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 });
|
||||
}
|
||||
|
||||
let tomlContent: string;
|
||||
|
||||
// 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
|
||||
const url = new URL(request.url);
|
||||
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) {
|
||||
throw new Error(
|
||||
@@ -28,7 +28,7 @@ export const GET: APIRoute = async ({ request }) => {
|
||||
tomlContent = await response.text();
|
||||
} else {
|
||||
// It's raw TOML content
|
||||
tomlContent = siteConfig.resume.tomlFile;
|
||||
tomlContent = config.resumeConfig.tomlFile;
|
||||
}
|
||||
const resumeData = TOML.parse(tomlContent);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { chromium } from "playwright";
|
||||
import { siteConfig } from "../../../config/data";
|
||||
import { config } from "../../../config";
|
||||
import * as TOML from "@iarna/toml";
|
||||
|
||||
// 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 resumeConfig = siteConfig.resume;
|
||||
const resumeConfig = config.resumeConfig;
|
||||
// Use layout from TOML data, fallback to site config, then to default
|
||||
const layout = data.layout
|
||||
? {
|
||||
@@ -405,19 +405,19 @@ async function generatePDFFromToml(tomlContent: string): Promise<Uint8Array> {
|
||||
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
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 });
|
||||
}
|
||||
|
||||
let tomlContent: string;
|
||||
|
||||
// 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
|
||||
const url = new URL(request.url);
|
||||
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) {
|
||||
throw new Error(
|
||||
@@ -428,7 +428,7 @@ export const GET: APIRoute = async ({ request }) => {
|
||||
tomlContent = await response.text();
|
||||
} else {
|
||||
// It's raw TOML content
|
||||
tomlContent = siteConfig.resume.tomlFile;
|
||||
tomlContent = config.resumeConfig.tomlFile;
|
||||
}
|
||||
|
||||
const pdfBuffer = await generatePDFFromToml(tomlContent);
|
||||
|
||||
@@ -3,13 +3,13 @@ import { Image } from "astro:assets";
|
||||
import SocialLinks from "../components/SocialLinks.astro";
|
||||
import TechLinks from "../components/TechLinks.astro";
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import { personalInfo, homepageSections } from "../config/data";
|
||||
import { config } from "../config";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<Image
|
||||
src={personalInfo.profileImage.src}
|
||||
alt={personalInfo.profileImage.alt}
|
||||
src={config.personalInfo.profileImage.src}
|
||||
alt={config.personalInfo.profileImage.alt}
|
||||
width={300}
|
||||
height={300}
|
||||
layout="constrained"
|
||||
@@ -18,24 +18,22 @@ import { personalInfo, homepageSections } from "../config/data";
|
||||
style="max-width: 12rem; width: 100%;"
|
||||
/>
|
||||
|
||||
<h1
|
||||
class="text-primary text-4xl sm:text-6xl font-bold text-center"
|
||||
>
|
||||
{personalInfo.name}
|
||||
<h1 class="text-primary text-4xl sm:text-6xl font-bold text-center">
|
||||
{config.personalInfo.name}
|
||||
</h1>
|
||||
|
||||
<h2 class="text-xl sm:text-3xl font-bold text-center mx-6">
|
||||
{personalInfo.tagline}
|
||||
{config.personalInfo.tagline}
|
||||
</h2>
|
||||
|
||||
<h3 class="text-lg sm:text-2xl font-bold">
|
||||
{homepageSections.socialLinks.title}
|
||||
{config.homepageSections.socialLinks.title}
|
||||
</h3>
|
||||
|
||||
<SocialLinks />
|
||||
|
||||
<h3 class="text-lg sm:text-2xl font-bold">
|
||||
{homepageSections.techStack.title}
|
||||
{config.homepageSections.techStack.title}
|
||||
</h3>
|
||||
|
||||
<TechLinks />
|
||||
|
||||
@@ -18,7 +18,7 @@ const { Content } = await post.render();
|
||||
---
|
||||
|
||||
<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="p-4 md:p-8">
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-primary mb-6">
|
||||
@@ -45,7 +45,10 @@ const { Content } = await post.render();
|
||||
</div>
|
||||
|
||||
{/* 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" />
|
||||
Back
|
||||
</a>
|
||||
|
||||
@@ -14,7 +14,7 @@ const sortedPosts = posts.sort(
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div class="min-h-screen min-w-screen p-4 sm:p-8">
|
||||
<div class="w-full p-4 sm:p-8">
|
||||
<h1
|
||||
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 ProjectCard from "../components/ProjectCard.astro";
|
||||
import { projects } from "../config/data";
|
||||
import { config } from "../config";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div class="min-h-screen min-w-screen p-4 sm:p-8">
|
||||
<div class="w-full p-4 sm:p-8">
|
||||
<h1
|
||||
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
|
||||
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>
|
||||
|
||||
{
|
||||
projects.length === 0 && (
|
||||
config.projects.length === 0 && (
|
||||
<p class="text-center text-gray-500 mt-12">
|
||||
No projects available yet. Check back soon!
|
||||
</p>
|
||||
@@ -26,4 +30,3 @@ import { projects } from "../config/data";
|
||||
}
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
import ResumeSkills from "../components/ResumeSkills";
|
||||
import ResumeDownloadButton from "../components/ResumeDownloadButton";
|
||||
import ResumeSettingsModal from "../components/ResumeSettingsModal";
|
||||
import { siteConfig } from "../config/data";
|
||||
import { config } from "../config";
|
||||
import "../styles/global.css";
|
||||
import * as TOML from "@iarna/toml";
|
||||
|
||||
@@ -57,16 +57,18 @@ interface ResumeData {
|
||||
let resumeData: ResumeData | undefined = undefined;
|
||||
let fetchError: string | null = null;
|
||||
|
||||
if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) {
|
||||
if (!config.resumeConfig.tomlFile || !config.resumeConfig.tomlFile.trim()) {
|
||||
return Astro.redirect("/");
|
||||
}
|
||||
|
||||
try {
|
||||
let tomlContent: string;
|
||||
|
||||
if (siteConfig.resume.tomlFile.startsWith("/")) {
|
||||
if (config.resumeConfig.tomlFile.startsWith("/")) {
|
||||
const baseUrl = Astro.url.origin;
|
||||
const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`);
|
||||
const response = await fetch(
|
||||
`${baseUrl}${config.resumeConfig.tomlFile}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
@@ -76,7 +78,7 @@ try {
|
||||
|
||||
tomlContent = await response.text();
|
||||
} else {
|
||||
tomlContent = siteConfig.resume.tomlFile;
|
||||
tomlContent = config.resumeConfig.tomlFile;
|
||||
}
|
||||
|
||||
resumeData = TOML.parse(tomlContent) as unknown as ResumeData;
|
||||
@@ -86,7 +88,7 @@ try {
|
||||
}
|
||||
|
||||
const data = resumeData;
|
||||
const resumeConfig = siteConfig.resume;
|
||||
const resumeConfig = config.resumeConfig;
|
||||
|
||||
if (!data) {
|
||||
return Astro.redirect("/");
|
||||
@@ -136,11 +138,13 @@ if (!data) {
|
||||
<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-body p-4 sm:p-6 break-words">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{resumeConfig.sections.summary.title || "Summary"}
|
||||
{resumeConfig.sections.summary?.title ||
|
||||
"Summary"}
|
||||
</h2>
|
||||
<div>{data.summary.content}</div>
|
||||
</div>
|
||||
@@ -151,11 +155,12 @@ if (!data) {
|
||||
{
|
||||
data.skills &&
|
||||
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-body p-4 sm:p-6 break-words">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{resumeConfig.sections.skills.title || "Skills"}
|
||||
{resumeConfig.sections.skills?.title ||
|
||||
"Skills"}
|
||||
</h2>
|
||||
<ResumeSkills
|
||||
skills={data.skills.map((skill, index) => ({
|
||||
@@ -173,11 +178,11 @@ if (!data) {
|
||||
{
|
||||
data.experience &&
|
||||
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-body p-4 sm:p-6 break-words">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{resumeConfig.sections.experience.title ||
|
||||
{resumeConfig.sections.experience?.title ||
|
||||
"Experience"}
|
||||
</h2>
|
||||
<div class="space-y-4 sm:space-y-6">
|
||||
@@ -222,11 +227,11 @@ if (!data) {
|
||||
{
|
||||
data.education &&
|
||||
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-body p-4 sm:p-6 break-words">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{resumeConfig.sections.education.title ||
|
||||
{resumeConfig.sections.education?.title ||
|
||||
"Education"}
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
@@ -264,11 +269,11 @@ if (!data) {
|
||||
{
|
||||
data.volunteer &&
|
||||
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-body p-4 sm:p-6 break-words">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{resumeConfig.sections.volunteer.title ||
|
||||
{resumeConfig.sections.volunteer?.title ||
|
||||
"Volunteer Work"}
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
@@ -296,11 +301,11 @@ if (!data) {
|
||||
{
|
||||
data.awards &&
|
||||
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-body p-4 sm:p-6 break-words">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{resumeConfig.sections.awards.title ||
|
||||
{resumeConfig.sections.awards?.title ||
|
||||
"Awards & Recognition"}
|
||||
</h2>
|
||||
<div class="space-y-4">
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import TalkCard from "../components/TalkCard.astro";
|
||||
import { talks } from "../config/data";
|
||||
import { config } from "../config";
|
||||
---
|
||||
|
||||
<Layout>
|
||||
<div class="min-h-screen min-w-screen p-4 sm:p-8">
|
||||
<div class="w-full p-4 sm:p-8">
|
||||
<h1
|
||||
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
|
||||
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>
|
||||
|
||||
{
|
||||
talks.length === 0 && (
|
||||
config.talks.length === 0 && (
|
||||
<p class="text-center text-gray-500 mt-12">
|
||||
No talks available yet. Check back soon!
|
||||
</p>
|
||||
|
||||
@@ -50,41 +50,43 @@ export interface NavigationItem {
|
||||
isActive?: (path: string) => boolean;
|
||||
}
|
||||
|
||||
export type ResumeSectionKey =
|
||||
| "summary"
|
||||
| "experience"
|
||||
| "education"
|
||||
| "skills"
|
||||
| "volunteer"
|
||||
| "profiles"
|
||||
| "awards";
|
||||
|
||||
export interface ResumeConfig {
|
||||
tomlFile: string; // Can be a file path or raw TOML content
|
||||
layout?: {
|
||||
leftColumn?: string[];
|
||||
rightColumn?: string[];
|
||||
leftColumn?: ResumeSectionKey[];
|
||||
rightColumn?: ResumeSectionKey[];
|
||||
};
|
||||
sections: {
|
||||
enabled: string[];
|
||||
enabled: ResumeSectionKey[];
|
||||
summary?: {
|
||||
title?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
experience?: {
|
||||
title?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
education?: {
|
||||
title?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
skills?: {
|
||||
title?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
volunteer?: {
|
||||
title?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
profiles?: {
|
||||
title?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
awards?: {
|
||||
title?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -123,3 +125,37 @@ export interface SiteConfig {
|
||||
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