Merge pull request #765 from arifszn/v4

Release v4
This commit is contained in:
Ariful Alam 2025-07-06 18:22:09 +06:00 committed by GitHub
commit 8f413562e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 1041 additions and 1425 deletions

View File

@ -43,7 +43,7 @@
**Features:** **Features:**
✓ [Easy to Setup](#-installation--setup) ✓ [Easy to Setup](#-installation--setup)
✓ [33 Themes](#themes) ✓ [37 Themes](#themes)
✓ [Google Analytics](#google-analytics) ✓ [Google Analytics](#google-analytics)
✓ [Hotjar](#hotjar) ✓ [Hotjar](#hotjar)
✓ [SEO](#seo) ✓ [SEO](#seo)
@ -202,11 +202,7 @@ const CONFIG = {
], ],
}, },
}, },
seo: { seo: { title: 'Portfolio of Ariful Alam', description: '', imageURL: '' },
title: 'Portfolio of Ariful Alam',
description: '',
imageURL: '',
},
social: { social: {
linkedin: 'ariful-alam', linkedin: 'ariful-alam',
x: 'arif_szn', x: 'arif_szn',
@ -317,10 +313,7 @@ const CONFIG = {
id: '', // GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX id: '', // GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX
}, },
// Track visitor interaction and behavior. https://www.hotjar.com // Track visitor interaction and behavior. https://www.hotjar.com
hotjar: { hotjar: { id: '', snippetVersion: 6 },
id: '',
snippetVersion: 6,
},
themeConfig: { themeConfig: {
defaultTheme: 'nord', defaultTheme: 'nord',
@ -369,19 +362,11 @@ const CONFIG = {
'dim', 'dim',
'nord', 'nord',
'sunset', 'sunset',
'caramellatte',
'abyss',
'silk',
'procyon', 'procyon',
], ],
// Custom theme, applied to `procyon` theme
customTheme: {
primary: '#fc055b',
secondary: '#219aaf',
accent: '#e8d03a',
neutral: '#2A2730',
'base-100': '#E3E3ED',
'--rounded-box': '3rem',
'--rounded-btn': '3rem',
},
}, },
// Optional Footer. Supports plain text or HTML. // Optional Footer. Supports plain text or HTML.
@ -399,7 +384,7 @@ export default CONFIG;
### Themes ### Themes
There are 33 themes available that can be selected from the dropdown. There are 37 themes available that can be selected from the dropdown.
The default theme can be specified. The default theme can be specified.
@ -418,34 +403,23 @@ const CONFIG = {
<img src="https://github.com/arifszn/gitprofile/assets/45073703/91a2d9e6-67e5-47b4-9752-1881ac0f907f" alt="Theme Dropdown" width="50%"> <img src="https://github.com/arifszn/gitprofile/assets/45073703/91a2d9e6-67e5-47b4-9752-1881ac0f907f" alt="Theme Dropdown" width="50%">
</p> </p>
You can create your own custom theme by modifying these values. Theme `procyon` will have the custom styles. You can create your own custom theme by modifying the CSS variables in `src/assets/index.css`. Theme `procyon` is defined as a custom theme.
```ts ```css
// gitprofile.config.ts /* src/assets/index.css */
const CONFIG = { @plugin "daisyui/theme" {
/** name: 'procyon';
* Defines the custom theme colors and styles for the application. color-scheme: light;
* The theme includes the following properties:
* - `primary`: The primary color used throughout the application. --color-base-100: #e3e3ed;
* - `secondary`: The secondary color used for accents and highlights. --color-base-200: #d1d1db;
* - `accent`: The accent color used for special elements. --color-base-300: #bfbfc9;
* - `neutral`: The neutral color used for backgrounds and text. --color-base-content: #2a2730;
* - `base-100`: The base background color. --color-primary: #fc055b;
* - `--rounded-box`: The border radius for boxes and containers. --color-primary-content: #ffffff;
* - `--rounded-btn`: The border radius for buttons. --color-secondary: #219aaf;
*/ --color-secondary-content: #ffffff;
themeConfig: { }
customTheme: {
primary: '#fc055b',
secondary: '#219aaf',
accent: '#e8d03a',
neutral: '#2A2730',
'base-100': '#E3E3ED',
'--rounded-box': '3rem',
'--rounded-btn': '3rem',
},
},
};
``` ```
### Google Analytics ### Google Analytics
@ -456,9 +430,7 @@ const CONFIG = {
// gitprofile.config.ts // gitprofile.config.ts
const CONFIG = { const CONFIG = {
// ... // ...
googleAnalytics: { googleAnalytics: { id: 'G-XXXXXXXXX' },
id: 'G-XXXXXXXXX',
},
}; };
``` ```
@ -472,10 +444,7 @@ Besides tracking visitors, it will track `click events` on projects and blog pos
// gitprofile.config.ts // gitprofile.config.ts
const CONFIG = { const CONFIG = {
// ... // ...
hotjar: { hotjar: { id: '', snippetVersion: 6 },
id: '',
snippetVersion: 6,
},
}; };
``` ```
@ -487,11 +456,7 @@ You can customize the meta tags for SEO in `seo`.
// gitprofile.config.ts // gitprofile.config.ts
const CONFIG = { const CONFIG = {
// ... // ...
seo: { seo: { title: 'Portfolio of Ariful Alam', description: '', imageURL: '' },
title: 'Portfolio of Ariful Alam',
description: '',
imageURL: '',
},
}; };
``` ```
@ -730,11 +695,7 @@ If you have [medium](https://medium.com) or [dev](https://dev.to) account, you c
// gitprofile.config.ts // gitprofile.config.ts
const CONFIG = { const CONFIG = {
// ... // ...
blog: { blog: { source: 'dev', username: 'arifszn', limit: 5 },
source: 'dev',
username: 'arifszn',
limit: 5,
},
}; };
``` ```

View File

@ -51,11 +51,7 @@ const CONFIG = {
], ],
}, },
}, },
seo: { seo: { title: 'Portfolio of Ariful Alam', description: '', imageURL: '' },
title: 'Portfolio of Ariful Alam',
description: '',
imageURL: '',
},
social: { social: {
linkedin: 'ariful-alam', linkedin: 'ariful-alam',
x: 'arif_szn', x: 'arif_szn',
@ -166,10 +162,7 @@ const CONFIG = {
id: '', // GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX id: '', // GA3 tracking id/GA4 tag id UA-XXXXXXXXX-X | G-XXXXXXXXXX
}, },
// Track visitor interaction and behavior. https://www.hotjar.com // Track visitor interaction and behavior. https://www.hotjar.com
hotjar: { hotjar: { id: '', snippetVersion: 6 },
id: '',
snippetVersion: 6,
},
themeConfig: { themeConfig: {
defaultTheme: 'lofi', defaultTheme: 'lofi',
@ -218,19 +211,11 @@ const CONFIG = {
'dim', 'dim',
'nord', 'nord',
'sunset', 'sunset',
'caramellatte',
'abyss',
'silk',
'procyon', 'procyon',
], ],
// Custom theme, applied to `procyon` theme
customTheme: {
primary: '#fc055b',
secondary: '#219aaf',
accent: '#e8d03a',
neutral: '#2A2730',
'base-100': '#E3E3ED',
'--rounded-box': '3rem',
'--rounded-btn': '3rem',
},
}, },
// Optional Footer. Supports plain text or HTML. // Optional Footer. Supports plain text or HTML.

42
global.d.ts vendored
View File

@ -284,43 +284,6 @@ interface Blog {
limit?: number; limit?: number;
} }
interface CustomTheme {
/**
* Primary color
*/
primary?: string;
/**
* Secondary color
*/
secondary?: string;
/**
* Accent color
*/
accent?: string;
/**
* Neutral color
*/
neutral?: string;
/**
* Base color of page
*/
'base-100'?: string;
/**
* Border radius of rounded-box
*/
'--rounded-box'?: string;
/**
* Border radius of rounded-btn
*/
'--rounded-btn'?: string;
}
interface ThemeConfig { interface ThemeConfig {
/** /**
* Default theme * Default theme
@ -346,11 +309,6 @@ interface ThemeConfig {
* Available themes * Available themes
*/ */
themes?: Array<string>; themes?: Array<string>;
/**
* Custom theme
*/
customTheme?: CustomTheme;
} }
interface Config { interface Config {

View File

@ -23,6 +23,7 @@
<meta name="twitter:title" content="<%- metaTitle %>" /> <meta name="twitter:title" content="<%- metaTitle %>" />
<meta name="twitter:description" content="<%- metaDescription %>" /> <meta name="twitter:description" content="<%- metaDescription %>" />
<meta name="twitter:image" content="<%- metaImageURL %>" /> <meta name="twitter:image" content="<%- metaImageURL %>" />
<%- googleAnalyticsScript %>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

1567
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{ {
"name": "@arifszn/gitprofile", "name": "@arifszn/gitprofile",
"description": "Create an automatic portfolio based on GitHub profile", "description": "Create an automatic portfolio based on GitHub profile",
"version": "3.1.0", "version": "4.0.0",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"author": "arifszn", "author": "arifszn",
@ -22,31 +22,30 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"react": "^18.3.1", "react": "^19.1.0",
"react-dom": "^18.3.1" "react-dom": "^19.1.0"
}, },
"devDependencies": { "devDependencies": {
"@arifszn/blog-js": "^2.0.6", "@arifszn/blog-js": "^2.0.6",
"@types/react": "^18.3.3", "@tailwindcss/postcss": "^4.1.11",
"@types/react-dom": "^18.3.0", "@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.3.1", "@typescript-eslint/parser": "^7.3.1",
"@vitejs/plugin-react": "^4.4.1", "@vitejs/plugin-react": "^4.4.1",
"autoprefixer": "^10.4.20",
"axios": "^1.10.0", "axios": "^1.10.0",
"daisyui": "^4.11.1", "daisyui": "^5.0.43",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.1.3", "eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react-hooks": "^5.1.0", "eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.20", "eslint-plugin-react-refresh": "^0.4.20",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.3.2", "prettier": "^3.6.2",
"react-helmet-async": "^2.0.5",
"react-hotjar": "^6.3.1", "react-hotjar": "^6.3.1",
"react-icons": "^5.4.0", "react-icons": "^5.4.0",
"tailwindcss": "^3.4.3", "tailwindcss": "^4.1.11",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vail": "^1.0.3", "vail": "^1.0.3",
"vite": "^7.0.2", "vite": "^7.0.2",

View File

@ -1,6 +1,5 @@
export default { export default {
plugins: { plugins: {
tailwindcss: {}, '@tailwindcss/postcss': {},
autoprefixer: {},
}, },
}; };

View File

@ -1,6 +1,55 @@
@tailwind base; @import 'tailwindcss';
@tailwind components; @plugin "daisyui" {
@tailwind utilities; themes:
'light', 'dark', 'cupcake', 'bumblebee', 'emerald', 'corporate',
'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween', 'garden',
'forest', 'aqua', 'lofi', 'pastel', 'fantasy', 'wireframe', 'black',
'luxury', 'dracula', 'cmyk', 'autumn', 'business', 'acid', 'lemonade',
'night', 'coffee', 'winter', 'dim', 'nord', 'sunset', 'caramellatte',
'abyss', 'silk', 'procyon';
}
@plugin "daisyui/theme" {
name: 'procyon';
color-scheme: light;
--color-base-100: #e3e3ed;
--color-base-200: #d1d1db;
--color-base-300: #bfbfc9;
--color-base-content: #2a2730;
--color-primary: #fc055b;
--color-primary-content: #ffffff;
--color-secondary: #219aaf;
--color-secondary-content: #ffffff;
--color-accent: #e8d03a;
--color-accent-content: #2a2730;
--color-neutral: #2a2730;
--color-neutral-content: #ffffff;
--color-info: #219aaf;
--color-info-content: #ffffff;
--color-success: #16a34a;
--color-success-content: #ffffff;
--color-warning: #e8d03a;
--color-warning-content: #2a2730;
--color-error: #dc2626;
--color-error-content: #ffffff;
/* border radius */
--radius-selector: 3rem;
--radius-field: 3rem;
--radius-box: 3rem;
/* base sizes */
--size-selector: 0.25rem;
--size-field: 0.25rem;
/* border size */
--border: 1px;
/* effects */
--depth: 1;
--noise: 0;
}
* { * {
scrollbar-width: thin; scrollbar-width: thin;
@ -40,10 +89,6 @@ code {
source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
} }
.text-base-content-important {
color: hsla(var(--bc) / var(--tw-text-opacity)) !important;
}
svg { svg {
vertical-align: unset; vertical-align: unset;
} }

View File

@ -23,9 +23,9 @@ const AvatarCard: React.FC<AvatarCardProps> = ({
loading, loading,
avatarRing, avatarRing,
resumeFileUrl, resumeFileUrl,
}): JSX.Element => { }): React.JSX.Element => {
return ( return (
<div className="card shadow-lg compact bg-base-100"> <div className="card shadow-lg card-sm bg-base-100">
<div className="grid place-items-center py-8"> <div className="grid place-items-center py-8">
{loading || !profile ? ( {loading || !profile ? (
<div className="avatar opacity-90"> <div className="avatar opacity-90">
@ -42,7 +42,7 @@ const AvatarCard: React.FC<AvatarCardProps> = ({
<div <div
className={`mb-8 rounded-full w-32 h-32 ${ className={`mb-8 rounded-full w-32 h-32 ${
avatarRing avatarRing
? 'ring ring-primary ring-offset-base-100 ring-offset-2' ? 'ring-3 ring-primary ring-offset-base-100 ring-offset-2'
: '' : ''
}`} }`}
> >
@ -70,7 +70,7 @@ const AvatarCard: React.FC<AvatarCardProps> = ({
</span> </span>
)} )}
</h5> </h5>
<div className="mt-3 text-base-content text-opacity-60 font-mono"> <div className="mt-3 text-base-content font-mono">
{loading || !profile {loading || !profile
? skeleton({ widthCls: 'w-48', heightCls: 'h-5' }) ? skeleton({ widthCls: 'w-48', heightCls: 'h-5' })
: profile.bio} : profile.bio}

View File

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import LazyImage from '../lazy-image'; import LazyImage from '../lazy-image';
import { AiOutlineContainer } from 'react-icons/ai'; import { PiNewspaper } from 'react-icons/pi';
import { getDevPost, getMediumPost } from '@arifszn/blog-js'; import { getDevPost, getMediumPost } from '@arifszn/blog-js';
import { formatDistance } from 'date-fns'; import { formatDistance } from 'date-fns';
import { SanitizedBlog } from '../../interfaces/sanitized-config'; import { SanitizedBlog } from '../../interfaces/sanitized-config';
@ -38,7 +38,7 @@ const BlogCard = ({
const array = []; const array = [];
for (let index = 0; index < blog.limit; index++) { for (let index = 0; index < blog.limit; index++) {
array.push( array.push(
<div className="card shadow-lg compact bg-base-100" key={index}> <div className="card shadow-md card-sm bg-base-100" key={index}>
<div className="p-8 h-full w-full"> <div className="p-8 h-full w-full">
<div className="flex items-center flex-col md:flex-row"> <div className="flex items-center flex-col md:flex-row">
<div className="avatar mb-5 md:mb-0"> <div className="avatar mb-5 md:mb-0">
@ -95,7 +95,7 @@ const BlogCard = ({
return articles && articles.length ? ( return articles && articles.length ? (
articles.slice(0, blog.limit).map((article, index) => ( articles.slice(0, blog.limit).map((article, index) => (
<a <a
className="card shadow-lg compact bg-base-100 cursor-pointer" className="card shadow-md card-sm bg-base-100 cursor-pointer"
key={index} key={index}
href={article.link} href={article.link}
onClick={(e) => { onClick={(e) => {
@ -140,7 +140,7 @@ const BlogCard = ({
addSuffix: true, addSuffix: true,
})} })}
</p> </p>
<p className="mt-3 text-base-content text-opacity-60 text-sm"> <p className="mt-3 text-base-content text-sm">
{article.description} {article.description}
</p> </p>
<div className="mt-4 flex items-center flex-wrap justify-center md:justify-start"> <div className="mt-4 flex items-center flex-wrap justify-center md:justify-start">
@ -162,7 +162,7 @@ const BlogCard = ({
)) ))
) : ( ) : (
<div className="text-center mb-6"> <div className="text-center mb-6">
<AiOutlineContainer className="mx-auto h-12 w-12 opacity-30" /> <PiNewspaper className="mx-auto h-12 w-12 opacity-30" />
<p className="mt-1 text-sm opacity-50 text-base-content"> <p className="mt-1 text-sm opacity-50 text-base-content">
No recent post No recent post
</p> </p>
@ -172,34 +172,38 @@ const BlogCard = ({
return ( return (
<div className="col-span-1 lg:col-span-2"> <div className="col-span-1 lg:col-span-2">
<div className="grid grid-cols-2 gap-6"> <div className="card bg-base-200 shadow-xl border border-base-300">
<div className="col-span-2"> <div className="card-body p-8">
<div <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
className={`card compact bg-base-100 ${ <div className="flex items-center space-x-3">
loading || (articles && articles.length) {loading ? (
? 'shadow bg-opacity-40' skeleton({
: 'shadow-lg' widthCls: 'w-12',
}`} heightCls: 'h-12',
> className: 'rounded-xl',
<div className="card-body"> })
<div className="mx-3 mb-2"> ) : (
<h5 className="card-title"> <div className="flex items-center justify-center w-12 h-12 bg-primary/10 rounded-xl">
{loading ? ( <PiNewspaper className="text-2xl" />
skeleton({ widthCls: 'w-28', heightCls: 'h-8' }) </div>
) : ( )}
<span className="text-base-content opacity-70"> <div className="min-w-0 flex-1">
My Articles <h3 className="text-base sm:text-lg font-bold text-base-content truncate">
</span> {loading
)} ? skeleton({ widthCls: 'w-28', heightCls: 'h-8' })
</h5> : 'My Articles'}
</div> </h3>
<div className="col-span-2"> <div className="text-base-content/60 text-xs sm:text-sm mt-1 truncate">
<div className="grid grid-cols-1 gap-6"> {loading
{loading || !articles ? renderSkeleton() : renderArticles()} ? skeleton({ widthCls: 'w-32', heightCls: 'h-4' })
: 'Recent posts'}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 gap-6">
{loading || !articles ? renderSkeleton() : renderArticles()}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -59,7 +59,7 @@ const CertificationCard = ({
}; };
return ( return (
<div className="card shadow-lg compact bg-base-100"> <div className="card shadow-lg card-sm bg-base-100">
<div className="card-body"> <div className="card-body">
<div className="mx-3"> <div className="mx-3">
<h5 className="card-title"> <h5 className="card-title">
@ -72,7 +72,7 @@ const CertificationCard = ({
)} )}
</h5> </h5>
</div> </div>
<div className="text-base-content text-opacity-60"> <div className="text-base-content">
<ol className="relative border-l border-base-300 border-opacity-30 my-2 mx-4"> <ol className="relative border-l border-base-300 border-opacity-30 my-2 mx-4">
{loading ? ( {loading ? (
renderSkeleton() renderSkeleton()

View File

@ -66,12 +66,12 @@ const ListItem: React.FC<{
}> = ({ icon, title, value, link, skeleton = false }) => { }> = ({ icon, title, value, link, skeleton = false }) => {
return ( return (
<div className="flex justify-start py-2 px-1 items-center"> <div className="flex justify-start py-2 px-1 items-center">
<div className="flex-grow font-medium gap-2 flex items-center my-1"> <div className="grow font-medium gap-2 flex items-center my-1">
{icon} {title} {icon} {title}
</div> </div>
<div <div
className={`${ className={`${
skeleton ? 'flex-grow' : '' skeleton ? 'grow' : ''
} text-sm font-normal text-right mr-2 ml-3 ${link ? 'truncate' : ''}`} } text-sm font-normal text-right mr-2 ml-3 ${link ? 'truncate' : ''}`}
style={{ style={{
wordBreak: 'break-word', wordBreak: 'break-word',
@ -124,12 +124,12 @@ const OrganizationItem: React.FC<{
return ( return (
<div className="flex justify-start py-2 px-1 items-center"> <div className="flex justify-start py-2 px-1 items-center">
<div className="flex-grow font-medium gap-2 flex items-center my-1"> <div className="grow font-medium gap-2 flex items-center my-1">
{icon} {title} {icon} {title}
</div> </div>
<div <div
className={`${ className={`${
skeleton ? 'flex-grow' : '' skeleton ? 'grow' : ''
} text-sm font-normal text-right mr-2 ml-3 space-x-2 ${link ? 'truncate' : ''}`} } text-sm font-normal text-right mr-2 ml-3 space-x-2 ${link ? 'truncate' : ''}`}
style={{ style={{
wordBreak: 'break-word', wordBreak: 'break-word',
@ -169,9 +169,9 @@ const DetailsCard = ({ profile, loading, social, github }: Props) => {
}; };
return ( return (
<div className="card shadow-lg compact bg-base-100"> <div className="card shadow-lg card-sm bg-base-100">
<div className="card-body"> <div className="card-body">
<div className="text-base-content text-opacity-60"> <div className="text-base-content">
{loading || !profile ? ( {loading || !profile ? (
renderSkeleton() renderSkeleton()
) : ( ) : (

View File

@ -53,7 +53,7 @@ const EducationCard = ({
}; };
return ( return (
<div className="card shadow-lg compact bg-base-100"> <div className="card shadow-lg card-sm bg-base-100">
<div className="card-body"> <div className="card-body">
<div className="mx-3"> <div className="mx-3">
<h5 className="card-title"> <h5 className="card-title">
@ -64,7 +64,7 @@ const EducationCard = ({
)} )}
</h5> </h5>
</div> </div>
<div className="text-base-content text-opacity-60"> <div className="text-base-content">
<ol className="relative border-l border-base-300 border-opacity-30 my-2 mx-4"> <ol className="relative border-l border-base-300 border-opacity-30 my-2 mx-4">
{loading ? ( {loading ? (
renderSkeleton() renderSkeleton()

View File

@ -16,9 +16,7 @@ const ErrorPage: React.FC<CustomError> = (props) => {
{`${props.status}`} {`${props.status}`}
</h1> </h1>
<p className="text-lg pb-2 text-base-content">{props.title}</p> <p className="text-lg pb-2 text-base-content">{props.title}</p>
<div className="text-base-content text-opacity-60"> <div className="text-base-content">{props.subTitle}</div>
{props.subTitle}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -58,7 +58,7 @@ const ExperienceCard = ({
return array; return array;
}; };
return ( return (
<div className="card shadow-lg compact bg-base-100"> <div className="card shadow-lg card-sm bg-base-100">
<div className="card-body"> <div className="card-body">
<div className="mx-3"> <div className="mx-3">
<h5 className="card-title"> <h5 className="card-title">
@ -69,7 +69,7 @@ const ExperienceCard = ({
)} )}
</h5> </h5>
</div> </div>
<div className="text-base-content text-opacity-60"> <div className="text-base-content">
<ol className="relative border-l border-base-300 border-opacity-30 my-2 mx-4"> <ol className="relative border-l border-base-300 border-opacity-30 my-2 mx-4">
{loading ? ( {loading ? (
renderSkeleton() renderSkeleton()

View File

@ -1,5 +1,6 @@
import { Fragment } from 'react'; import { Fragment } from 'react';
import LazyImage from '../lazy-image'; import LazyImage from '../lazy-image';
import { MdOpenInNew } from 'react-icons/md';
import { ga, skeleton } from '../../utils'; import { ga, skeleton } from '../../utils';
import { SanitizedExternalProject } from '../../interfaces/sanitized-config'; import { SanitizedExternalProject } from '../../interfaces/sanitized-config';
@ -18,7 +19,7 @@ const ExternalProjectCard = ({
const array = []; const array = [];
for (let index = 0; index < externalProjects.length; index++) { for (let index = 0; index < externalProjects.length; index++) {
array.push( array.push(
<div className="card shadow-lg compact bg-base-100" key={index}> <div className="card shadow-md card-sm bg-base-100" key={index}>
<div className="p-8 h-full w-full"> <div className="p-8 h-full w-full">
<div className="flex items-center flex-col"> <div className="flex items-center flex-col">
<div className="w-full"> <div className="w-full">
@ -69,7 +70,7 @@ const ExternalProjectCard = ({
const renderExternalProjects = () => { const renderExternalProjects = () => {
return externalProjects.map((item, index) => ( return externalProjects.map((item, index) => (
<a <a
className="card shadow-lg compact bg-base-100 cursor-pointer" className="card shadow-md card-sm bg-base-100 cursor-pointer"
key={index} key={index}
href={item.link} href={item.link}
onClick={(e) => { onClick={(e) => {
@ -111,7 +112,7 @@ const ExternalProjectCard = ({
</div> </div>
</div> </div>
)} )}
<p className="mt-2 text-base-content text-opacity-60 text-sm text-justify"> <p className="mt-2 text-base-content text-sm text-justify">
{item.description} {item.description}
</p> </p>
</div> </div>
@ -126,28 +127,38 @@ const ExternalProjectCard = ({
return ( return (
<Fragment> <Fragment>
<div className="col-span-1 lg:col-span-2"> <div className="col-span-1 lg:col-span-2">
<div className="grid grid-cols-2 gap-6"> <div className="card bg-base-200 shadow-xl border border-base-300">
<div className="col-span-2"> <div className="card-body p-8">
<div className="card compact bg-base-100 shadow bg-opacity-40"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
<div className="card-body"> <div className="flex items-center space-x-3">
<div className="mx-3 flex items-center justify-between mb-2"> {loading ? (
<h5 className="card-title"> skeleton({
{loading ? ( widthCls: 'w-12',
skeleton({ widthCls: 'w-40', heightCls: 'h-8' }) heightCls: 'h-12',
) : ( className: 'rounded-xl',
<span className="text-base-content opacity-70"> })
{header} ) : (
</span> <div className="flex items-center justify-center w-12 h-12 bg-primary/10 rounded-xl">
)} <MdOpenInNew className="text-2xl" />
</h5> </div>
</div> )}
<div className="col-span-2"> <div className="min-w-0 flex-1">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <h3 className="text-base sm:text-lg font-bold text-base-content truncate">
{loading ? renderSkeleton() : renderExternalProjects()} {loading
? skeleton({ widthCls: 'w-40', heightCls: 'h-8' })
: header}
</h3>
<div className="text-base-content/60 text-xs sm:text-sm mt-1 truncate">
{loading
? skeleton({ widthCls: 'w-32', heightCls: 'h-4' })
: `Showcasing ${externalProjects.length} external projects`}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{loading ? renderSkeleton() : renderExternalProjects()}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import { Fragment } from 'react'; import { Fragment } from 'react';
import { AiOutlineFork, AiOutlineStar } from 'react-icons/ai'; import { AiOutlineFork, AiOutlineStar, AiOutlineGithub } from 'react-icons/ai';
import { MdInsertLink } from 'react-icons/md'; import { MdInsertLink } from 'react-icons/md';
import { ga, getLanguageColor, skeleton } from '../../utils'; import { ga, getLanguageColor, skeleton } from '../../utils';
import { GithubProject } from '../../interfaces/github-project'; import { GithubProject } from '../../interfaces/github-project';
@ -9,14 +9,12 @@ const GithubProjectCard = ({
githubProjects, githubProjects,
loading, loading,
limit, limit,
username,
googleAnalyticsId, googleAnalyticsId,
}: { }: {
header: string; header: string;
githubProjects: GithubProject[]; githubProjects: GithubProject[];
loading: boolean; loading: boolean;
limit: number; limit: number;
username: string;
googleAnalyticsId?: string; googleAnalyticsId?: string;
}) => { }) => {
if (!loading && githubProjects.length === 0) { if (!loading && githubProjects.length === 0) {
@ -27,7 +25,7 @@ const GithubProjectCard = ({
const array = []; const array = [];
for (let index = 0; index < limit; index++) { for (let index = 0; index < limit; index++) {
array.push( array.push(
<div className="card shadow-lg compact bg-base-100" key={index}> <div className="card shadow-md card-sm bg-base-100" key={index}>
<div className="flex justify-between flex-col p-8 h-full w-full"> <div className="flex justify-between flex-col p-8 h-full w-full">
<div> <div>
<div className="flex items-center"> <div className="flex items-center">
@ -51,7 +49,7 @@ const GithubProjectCard = ({
</div> </div>
</div> </div>
<div className="flex justify-between"> <div className="flex justify-between">
<div className="flex flex-grow"> <div className="flex grow">
<span className="mr-3 flex items-center"> <span className="mr-3 flex items-center">
{skeleton({ widthCls: 'w-12', heightCls: 'h-4' })} {skeleton({ widthCls: 'w-12', heightCls: 'h-4' })}
</span> </span>
@ -76,7 +74,7 @@ const GithubProjectCard = ({
const renderProjects = () => { const renderProjects = () => {
return githubProjects.map((item, index) => ( return githubProjects.map((item, index) => (
<a <a
className="card shadow-lg compact bg-base-100 cursor-pointer" className="card shadow-md card-sm bg-base-100 cursor-pointer"
href={item.html_url} href={item.html_url}
key={index} key={index}
onClick={(e) => { onClick={(e) => {
@ -84,9 +82,7 @@ const GithubProjectCard = ({
try { try {
if (googleAnalyticsId) { if (googleAnalyticsId) {
ga.event('Click project', { ga.event('Click project', { project: item.name });
project: item.name,
});
} }
} catch (error) { } catch (error) {
console.error(error); console.error(error);
@ -103,12 +99,12 @@ const GithubProjectCard = ({
<span>{item.name}</span> <span>{item.name}</span>
</div> </div>
</div> </div>
<p className="mb-5 mt-1 text-base-content text-opacity-60 text-sm"> <p className="mb-5 mt-1 text-base-content text-sm">
{item.description} {item.description}
</p> </p>
</div> </div>
<div className="flex justify-between text-sm text-base-content text-opacity-60 truncate"> <div className="flex justify-between text-sm text-base-content truncate">
<div className="flex flex-grow"> <div className="flex grow">
<span className="mr-3 flex items-center"> <span className="mr-3 flex items-center">
<AiOutlineStar className="mr-0.5" /> <AiOutlineStar className="mr-0.5" />
<span>{item.stargazers_count}</span> <span>{item.stargazers_count}</span>
@ -136,40 +132,41 @@ const GithubProjectCard = ({
return ( return (
<Fragment> <Fragment>
<div className="col-span-1 lg:col-span-2"> <div className="col-span-1 lg:col-span-2">
<div className="grid grid-cols-2 gap-6"> <div className="card bg-base-200 shadow-xl border border-base-300">
<div className="col-span-2"> <div className="card-body p-8">
<div className="card compact bg-base-100 shadow bg-opacity-40"> {/* Enhanced Header Section */}
<div className="card-body"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
<div className="mx-3 flex items-center justify-between mb-2"> <div className="flex items-center space-x-3">
<h5 className="card-title"> {loading ? (
{loading ? ( skeleton({
skeleton({ widthCls: 'w-40', heightCls: 'h-8' }) widthCls: 'w-12',
) : ( heightCls: 'h-12',
<span className="text-base-content opacity-70"> className: 'rounded-xl',
{header} })
</span> ) : (
)} <div className="flex items-center justify-center w-12 h-12 bg-primary/10 rounded-xl">
</h5> <AiOutlineGithub className="text-2xl" />
{loading ? ( </div>
skeleton({ widthCls: 'w-10', heightCls: 'h-5' }) )}
) : ( <div className="min-w-0 flex-1">
<a <h3 className="text-base sm:text-lg font-bold text-base-content truncate">
href={`https://github.com/${username}?tab=repositories`} {loading
target="_blank" ? skeleton({ widthCls: 'w-48', heightCls: 'h-8' })
rel="noreferrer" : header}
className="text-base-content opacity-50 hover:underline" </h3>
> <div className="text-base-content/60 text-xs sm:text-sm mt-1 truncate">
See All {loading
</a> ? skeleton({ widthCls: 'w-32', heightCls: 'h-4' })
)} : `Showcasing ${githubProjects.length} featured repositories`}
</div>
<div className="col-span-2">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{loading ? renderSkeleton() : renderProjects()}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* Projects Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{loading ? renderSkeleton() : renderProjects()}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -8,12 +8,10 @@ import {
INVALID_GITHUB_USERNAME_ERROR, INVALID_GITHUB_USERNAME_ERROR,
setTooManyRequestError, setTooManyRequestError,
} from '../constants/errors'; } from '../constants/errors';
import { HelmetProvider } from 'react-helmet-async';
import '../assets/index.css'; import '../assets/index.css';
import { getInitialTheme, getSanitizedConfig, setupHotjar } from '../utils'; import { getInitialTheme, getSanitizedConfig, setupHotjar } from '../utils';
import { SanitizedConfig } from '../interfaces/sanitized-config'; import { SanitizedConfig } from '../interfaces/sanitized-config';
import ErrorPage from './error-page'; import ErrorPage from './error-page';
import HeadTagEditor from './head-tag-editor';
import { DEFAULT_THEMES } from '../constants/default-themes'; import { DEFAULT_THEMES } from '../constants/default-themes';
import ThemeChanger from './theme-changer'; import ThemeChanger from './theme-changer';
import { BG_COLOR } from '../constants'; import { BG_COLOR } from '../constants';
@ -180,123 +178,115 @@ const GitProfile = ({ config }: { config: Config }) => {
}; };
return ( return (
<HelmetProvider> <div className="fade-in h-screen">
<div className="fade-in h-screen"> {error ? (
{error ? ( <ErrorPage
<ErrorPage status={error.status}
status={error.status} title={error.title}
title={error.title} subTitle={error.subTitle}
subTitle={error.subTitle} />
/> ) : (
) : ( <>
<> <div className={`p-4 lg:p-10 min-h-full ${BG_COLOR}`}>
<HeadTagEditor <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 rounded-box">
googleAnalyticsId={sanitizedConfig.googleAnalytics.id} <div className="col-span-1">
appliedTheme={theme} <div className="grid grid-cols-1 gap-6">
/> {!sanitizedConfig.themeConfig.disableSwitch && (
<div className={`p-4 lg:p-10 min-h-full ${BG_COLOR}`}> <ThemeChanger
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 rounded-box"> theme={theme}
<div className="col-span-1"> setTheme={setTheme}
<div className="grid grid-cols-1 gap-6">
{!sanitizedConfig.themeConfig.disableSwitch && (
<ThemeChanger
theme={theme}
setTheme={setTheme}
loading={loading}
themeConfig={sanitizedConfig.themeConfig}
/>
)}
<AvatarCard
profile={profile}
loading={loading} loading={loading}
avatarRing={sanitizedConfig.themeConfig.displayAvatarRing} themeConfig={sanitizedConfig.themeConfig}
resumeFileUrl={sanitizedConfig.resume.fileUrl}
/> />
<DetailsCard )}
profile={profile} <AvatarCard
profile={profile}
loading={loading}
avatarRing={sanitizedConfig.themeConfig.displayAvatarRing}
resumeFileUrl={sanitizedConfig.resume.fileUrl}
/>
<DetailsCard
profile={profile}
loading={loading}
github={sanitizedConfig.github}
social={sanitizedConfig.social}
/>
{sanitizedConfig.skills.length !== 0 && (
<SkillCard
loading={loading} loading={loading}
github={sanitizedConfig.github} skills={sanitizedConfig.skills}
social={sanitizedConfig.social}
/> />
{sanitizedConfig.skills.length !== 0 && ( )}
<SkillCard {sanitizedConfig.experiences.length !== 0 && (
loading={loading} <ExperienceCard
skills={sanitizedConfig.skills} loading={loading}
/> experiences={sanitizedConfig.experiences}
)} />
{sanitizedConfig.experiences.length !== 0 && ( )}
<ExperienceCard {sanitizedConfig.certifications.length !== 0 && (
loading={loading} <CertificationCard
experiences={sanitizedConfig.experiences} loading={loading}
/> certifications={sanitizedConfig.certifications}
)} />
{sanitizedConfig.certifications.length !== 0 && ( )}
<CertificationCard {sanitizedConfig.educations.length !== 0 && (
loading={loading} <EducationCard
certifications={sanitizedConfig.certifications} loading={loading}
/> educations={sanitizedConfig.educations}
)} />
{sanitizedConfig.educations.length !== 0 && ( )}
<EducationCard
loading={loading}
educations={sanitizedConfig.educations}
/>
)}
</div>
</div> </div>
<div className="lg:col-span-2 col-span-1"> </div>
<div className="grid grid-cols-1 gap-6"> <div className="lg:col-span-2 col-span-1">
{sanitizedConfig.projects.github.display && ( <div className="grid grid-cols-1 gap-6">
<GithubProjectCard {sanitizedConfig.projects.github.display && (
header={sanitizedConfig.projects.github.header} <GithubProjectCard
limit={sanitizedConfig.projects.github.automatic.limit} header={sanitizedConfig.projects.github.header}
githubProjects={githubProjects} limit={sanitizedConfig.projects.github.automatic.limit}
loading={loading} githubProjects={githubProjects}
username={sanitizedConfig.github.username} loading={loading}
googleAnalyticsId={sanitizedConfig.googleAnalytics.id} googleAnalyticsId={sanitizedConfig.googleAnalytics.id}
/> />
)} )}
{sanitizedConfig.publications.length !== 0 && ( {sanitizedConfig.publications.length !== 0 && (
<PublicationCard <PublicationCard
loading={loading} loading={loading}
publications={sanitizedConfig.publications} publications={sanitizedConfig.publications}
/> />
)} )}
{sanitizedConfig.projects.external.projects.length !== {sanitizedConfig.projects.external.projects.length !== 0 && (
0 && ( <ExternalProjectCard
<ExternalProjectCard loading={loading}
loading={loading} header={sanitizedConfig.projects.external.header}
header={sanitizedConfig.projects.external.header} externalProjects={
externalProjects={ sanitizedConfig.projects.external.projects
sanitizedConfig.projects.external.projects }
} googleAnalyticId={sanitizedConfig.googleAnalytics.id}
googleAnalyticId={sanitizedConfig.googleAnalytics.id} />
/> )}
)} {sanitizedConfig.blog.display && (
{sanitizedConfig.blog.display && ( <BlogCard
<BlogCard loading={loading}
loading={loading} googleAnalyticsId={sanitizedConfig.googleAnalytics.id}
googleAnalyticsId={sanitizedConfig.googleAnalytics.id} blog={sanitizedConfig.blog}
blog={sanitizedConfig.blog} />
/> )}
)}
</div>
</div> </div>
</div> </div>
</div> </div>
{sanitizedConfig.footer && ( </div>
<footer {sanitizedConfig.footer && (
className={`p-4 footer ${BG_COLOR} text-base-content footer-center`} <footer
> className={`p-4 footer ${BG_COLOR} text-base-content footer-center`}
<div className="card compact bg-base-100 shadow"> >
<Footer content={sanitizedConfig.footer} loading={loading} /> <div className="card card-sm bg-base-100 shadow-sm">
</div> <Footer content={sanitizedConfig.footer} loading={loading} />
</footer> </div>
)} </footer>
</> )}
)} </>
</div> )}
</HelmetProvider> </div>
); );
}; };

View File

@ -1,47 +0,0 @@
import { Helmet } from 'react-helmet-async';
import { isDarkishTheme } from '../../utils';
type HeadTagEditorProps = {
googleAnalyticsId?: string;
appliedTheme: string;
};
/**
* Renders the head tag editor component.
*
* @param {HeadTagEditorProps} googleAnalyticsId - The Google Analytics ID.
* @param {HeadTagEditorProps} appliedTheme - The applied theme.
* @return {React.ReactElement} The head tag editor component.
*/
const HeadTagEditor: React.FC<HeadTagEditorProps> = ({
googleAnalyticsId,
appliedTheme,
}) => {
return (
<Helmet>
<meta
name="theme-color"
content={isDarkishTheme(appliedTheme) ? '#000000' : '#ffffff'}
/>
{googleAnalyticsId && (
<>
<script
async
src={`https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsId}`}
></script>
<script>
{`window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', '${googleAnalyticsId}');
`}
</script>
</>
)}
</Helmet>
);
};
export default HeadTagEditor;

View File

@ -1,4 +1,5 @@
import { Fragment } from 'react'; import { Fragment } from 'react';
import { AiOutlineBook } from 'react-icons/ai';
import { SanitizedPublication } from '../../interfaces/sanitized-config'; import { SanitizedPublication } from '../../interfaces/sanitized-config';
import { skeleton } from '../../utils'; import { skeleton } from '../../utils';
@ -13,7 +14,7 @@ const PublicationCard = ({
const array = []; const array = [];
for (let index = 0; index < publications.length; index++) { for (let index = 0; index < publications.length; index++) {
array.push( array.push(
<div className="card shadow-lg compact bg-base-100" key={index}> <div className="card shadow-md card-sm bg-base-100" key={index}>
<div className="p-8 h-full w-full"> <div className="p-8 h-full w-full">
<div className="flex items-center flex-col"> <div className="flex items-center flex-col">
<div className="w-full"> <div className="w-full">
@ -76,7 +77,7 @@ const PublicationCard = ({
const renderPublications = () => { const renderPublications = () => {
return publications.map((item, index) => ( return publications.map((item, index) => (
<a <a
className="card shadow-lg compact bg-base-100 cursor-pointer" className="card shadow-md card-sm bg-base-100 cursor-pointer"
key={index} key={index}
href={item.link} href={item.link}
target="_blank" target="_blank"
@ -104,7 +105,7 @@ const PublicationCard = ({
</p> </p>
)} )}
{item.description && ( {item.description && (
<p className="mt-2 text-base-content text-opacity-60 text-sm text-justify"> <p className="mt-2 text-base-content text-sm text-justify">
{item.description} {item.description}
</p> </p>
)} )}
@ -120,28 +121,38 @@ const PublicationCard = ({
return ( return (
<Fragment> <Fragment>
<div className="col-span-1 lg:col-span-2"> <div className="col-span-1 lg:col-span-2">
<div className="grid grid-cols-2 gap-6"> <div className="card bg-base-200 shadow-xl border border-base-300">
<div className="col-span-2"> <div className="card-body p-8">
<div className="card compact bg-base-100 shadow bg-opacity-40"> <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-8">
<div className="card-body"> <div className="flex items-center space-x-3">
<div className="mx-3 flex items-center justify-between mb-2"> {loading ? (
<h5 className="card-title"> skeleton({
{loading ? ( widthCls: 'w-12',
skeleton({ widthCls: 'w-40', heightCls: 'h-8' }) heightCls: 'h-12',
) : ( className: 'rounded-xl',
<span className="text-base-content opacity-70"> })
Publications ) : (
</span> <div className="flex items-center justify-center w-12 h-12 bg-primary/10 rounded-xl">
)} <AiOutlineBook className="text-2xl" />
</h5> </div>
</div> )}
<div className="col-span-2"> <div className="min-w-0 flex-1">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <h3 className="text-base sm:text-lg font-bold text-base-content truncate">
{loading ? renderSkeleton() : renderPublications()} {loading
? skeleton({ widthCls: 'w-40', heightCls: 'h-8' })
: 'Publications'}
</h3>
<div className="text-base-content/60 text-xs sm:text-sm mt-1 truncate">
{loading
? skeleton({ widthCls: 'w-32', heightCls: 'h-4' })
: `Showcasing ${publications.length} publications`}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{loading ? renderSkeleton() : renderPublications()}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -21,7 +21,7 @@ const SkillCard = ({
}; };
return ( return (
<div className="card shadow-lg compact bg-base-100"> <div className="card shadow-lg card-sm bg-base-100">
<div className="card-body"> <div className="card-body">
<div className="mx-3"> <div className="mx-3">
<h5 className="card-title"> <h5 className="card-title">
@ -33,14 +33,11 @@ const SkillCard = ({
</h5> </h5>
</div> </div>
<div className="p-3 flow-root"> <div className="p-3 flow-root">
<div className="-m-1 flex flex-wrap justify-center"> <div className="-m-1 flex flex-wrap justify-center gap-2">
{loading {loading
? renderSkeleton() ? renderSkeleton()
: skills.map((skill, index) => ( : skills.map((skill, index) => (
<div <div key={index} className="badge badge-primary badge-sm">
key={index}
className="m-1 text-xs inline-flex items-center font-bold leading-sm px-3 py-1 badge-primary bg-opacity-90 rounded-full"
>
{skill} {skill}
</div> </div>
))} ))}

View File

@ -1,4 +1,4 @@
import { AiOutlineControl } from 'react-icons/ai'; import { RiDice4Line } from 'react-icons/ri';
import { SanitizedThemeConfig } from '../../interfaces/sanitized-config'; import { SanitizedThemeConfig } from '../../interfaces/sanitized-config';
import { LOCAL_STORAGE_KEY_NAME } from '../../constants'; import { LOCAL_STORAGE_KEY_NAME } from '../../constants';
import { skeleton } from '../../utils'; import { skeleton } from '../../utils';
@ -40,7 +40,7 @@ const ThemeChanger = ({
}; };
return ( return (
<div className="card overflow-visible shadow-lg compact bg-base-100"> <div className="card overflow-visible shadow-lg card-sm bg-base-100">
<div className="flex-row items-center space-x-4 flex pl-6 pr-2 py-4"> <div className="flex-row items-center space-x-4 flex pl-6 pr-2 py-4">
<div className="flex-1"> <div className="flex-1">
<h5 className="card-title"> <h5 className="card-title">
@ -54,7 +54,7 @@ const ThemeChanger = ({
<span className="text-base-content opacity-70">Theme</span> <span className="text-base-content opacity-70">Theme</span>
)} )}
</h5> </h5>
<span className="text-base-content text-opacity-40 capitalize text-sm"> <span className="text-base-content/50 capitalize text-sm">
{loading {loading
? skeleton({ widthCls: 'w-16', heightCls: 'h-5' }) ? skeleton({ widthCls: 'w-16', heightCls: 'h-5' })
: theme === themeConfig.defaultTheme : theme === themeConfig.defaultTheme
@ -65,7 +65,7 @@ const ThemeChanger = ({
<div className="flex-0"> <div className="flex-0">
{loading ? ( {loading ? (
skeleton({ skeleton({
widthCls: 'w-14 md:w-28', widthCls: 'w-12',
heightCls: 'h-10', heightCls: 'h-10',
className: 'mr-6', className: 'mr-6',
}) })
@ -73,23 +73,15 @@ const ThemeChanger = ({
<div title="Change Theme" className="dropdown dropdown-end"> <div title="Change Theme" className="dropdown dropdown-end">
<div <div
tabIndex={0} tabIndex={0}
className="btn btn-ghost m-1 normal-case opacity-50 text-base-content" className="btn btn-ghost m-1 normal-case opacity-50 text-base-content flex items-center whitespace-nowrap"
> >
<AiOutlineControl className="inline-block w-5 h-5 stroke-current md:mr-2" /> <RiDice4Line className="inline-block w-5 h-5 stroke-current" />
<span className="hidden md:inline">Change Theme</span>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1792 1792"
className="inline-block w-4 h-4 ml-1 fill-current"
>
<path d="M1395 736q0 13-10 23l-466 466q-10 10-23 10t-23-10l-466-466q-10-10-10-23t10-23l50-50q10-10 23-10t23 10l393 393 393-393q10-10 23-10t23 10l50 50q10 10 10 23z" />
</svg>
</div> </div>
<div <div
tabIndex={0} tabIndex={0}
className="mt-16 overflow-y-auto shadow-2xl top-px dropdown-content max-h-96 w-52 rounded-lg bg-base-200 text-base-content z-10" className="mt-16 overflow-y-auto shadow-2xl top-px dropdown-content max-h-96 min-w-max rounded-lg bg-base-200 text-base-content z-10"
> >
<ul className="p-4 menu compact"> <ul className="p-4 menu menu-sm">
{[ {[
themeConfig.defaultTheme, themeConfig.defaultTheme,
...themeConfig.themes.filter( ...themeConfig.themes.filter(

View File

@ -1,9 +0,0 @@
export const DEFAULT_CUSTOM_THEME = {
primary: '#fc055b',
secondary: '#219aaf',
accent: '#e8d03a',
neutral: '#2A2730',
'base-100': '#E3E3ED',
'--rounded-box': '3rem',
'--rounded-btn': '3rem',
};

View File

@ -31,5 +31,8 @@ export const DEFAULT_THEMES = [
'dim', 'dim',
'nord', 'nord',
'sunset', 'sunset',
'caramellatte',
'abyss',
'silk',
'procyon', 'procyon',
]; ];

View File

@ -116,23 +116,12 @@ export interface SanitizedBlog {
limit: number; limit: number;
} }
export interface SanitizedCustomTheme {
primary: string;
secondary: string;
accent: string;
neutral: string;
'base-100': string;
'--rounded-box': string;
'--rounded-btn': string;
}
export interface SanitizedThemeConfig { export interface SanitizedThemeConfig {
defaultTheme: string; defaultTheme: string;
disableSwitch: boolean; disableSwitch: boolean;
respectPrefersColorScheme: boolean; respectPrefersColorScheme: boolean;
displayAvatarRing: boolean; displayAvatarRing: boolean;
themes: Array<string>; themes: Array<string>;
customTheme: SanitizedCustomTheme;
} }
export interface SanitizedConfig { export interface SanitizedConfig {

View File

@ -1,6 +1,7 @@
import React from 'react';
import { hotjar } from 'react-hotjar'; import { hotjar } from 'react-hotjar';
import { LOCAL_STORAGE_KEY_NAME } from '../constants'; import { LOCAL_STORAGE_KEY_NAME } from '../constants';
import { DEFAULT_CUSTOM_THEME } from '../constants/default-custom-theme';
import { DEFAULT_THEMES } from '../constants/default-themes'; import { DEFAULT_THEMES } from '../constants/default-themes';
import colors from '../data/colors.json'; import colors from '../data/colors.json';
import { import {
@ -124,29 +125,6 @@ export const getSanitizedConfig = (
config?.themeConfig?.respectPrefersColorScheme || false, config?.themeConfig?.respectPrefersColorScheme || false,
displayAvatarRing: config?.themeConfig?.displayAvatarRing ?? true, displayAvatarRing: config?.themeConfig?.displayAvatarRing ?? true,
themes: config?.themeConfig?.themes || DEFAULT_THEMES, themes: config?.themeConfig?.themes || DEFAULT_THEMES,
customTheme: {
primary:
config?.themeConfig?.customTheme?.primary ||
DEFAULT_CUSTOM_THEME.primary,
secondary:
config?.themeConfig?.customTheme?.secondary ||
DEFAULT_CUSTOM_THEME.secondary,
accent:
config?.themeConfig?.customTheme?.accent ||
DEFAULT_CUSTOM_THEME.accent,
neutral:
config?.themeConfig?.customTheme?.neutral ||
DEFAULT_CUSTOM_THEME.neutral,
'base-100':
config?.themeConfig?.customTheme?.['base-100'] ||
DEFAULT_CUSTOM_THEME['base-100'],
'--rounded-box':
config?.themeConfig?.customTheme?.['--rounded-box'] ||
DEFAULT_CUSTOM_THEME['--rounded-box'],
'--rounded-btn':
config?.themeConfig?.customTheme?.['--rounded-btn'] ||
DEFAULT_CUSTOM_THEME['--rounded-btn'],
},
}, },
footer: config?.footer, footer: config?.footer,
enablePWA: config?.enablePWA ?? true, enablePWA: config?.enablePWA ?? true,
@ -194,7 +172,7 @@ export const skeleton = ({
style?: React.CSSProperties; style?: React.CSSProperties;
shape?: string; shape?: string;
className?: string | null; className?: string | null;
}): JSX.Element => { }): React.JSX.Element => {
const classNames = ['bg-base-300', 'animate-pulse', shape]; const classNames = ['bg-base-300', 'animate-pulse', shape];
if (className) { if (className) {
classNames.push(className); classNames.push(className);

View File

@ -1,17 +0,0 @@
import CONFIG from './gitprofile.config';
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [require('daisyui')],
daisyui: {
logs: false,
themes: [
...CONFIG.themeConfig.themes,
{ procyon: CONFIG.themeConfig.customTheme },
],
},
};

View File

@ -15,6 +15,16 @@ export default defineConfig({
metaTitle: CONFIG.seo.title, metaTitle: CONFIG.seo.title,
metaDescription: CONFIG.seo.description, metaDescription: CONFIG.seo.description,
metaImageURL: CONFIG.seo.imageURL, metaImageURL: CONFIG.seo.imageURL,
googleAnalyticsScript: CONFIG.googleAnalytics.id
? `<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=${CONFIG.googleAnalytics.id}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${CONFIG.googleAnalytics.id}');
</script>`
: '',
}, },
}, },
}), }),