first commit

This commit is contained in:
2025-05-13 12:12:47 +02:00
commit c37cf55f67
75 changed files with 13258 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
import Image from 'next/image';
import { VscEye, VscHeart, VscComment } from 'react-icons/vsc';
import { Article } from '@/types';
import styles from '@/styles/ArticleCard.module.css';
interface ArticleCardProps {
article: Article;
}
const ArticleCard = ({ article }: ArticleCardProps) => {
return (
<a
href={article.url}
target="_blank"
rel="noopener noreferrer"
className={styles.container}
>
<div className={styles.imageWrapper}>
<Image
src={article.cover_image}
alt={article.title}
fill
sizes="(max-width: 768px) 100vw, 300px"
className={styles.image}
/>
<div className={styles.viewsBadge}>
<VscEye /> {article.page_views_count}
</div>
</div>
<div className={styles.content}>
<h3 className={styles.title}>{article.title}</h3>
<p className={styles.description}>{article.description}</p>
<div className={styles.footer}>
<div className={styles.stats}>
<div className={styles.stat}>
<VscHeart className={styles.icon} />{' '}
{article.public_reactions_count}
</div>
<div className={styles.stat}>
<VscComment className={styles.icon} /> {article.comments_count}
</div>
</div>
</div>
</div>
</a>
);
};
export default ArticleCard;

49
components/Bottombar.tsx Normal file
View File

@@ -0,0 +1,49 @@
import {
VscBell,
VscCheck,
VscError,
VscWarning,
VscSourceControl,
} from 'react-icons/vsc';
import { SiNextdotjs } from 'react-icons/si';
import styles from '@/styles/Bottombar.module.css';
const Bottombar = () => {
return (
<footer className={styles.bottomBar}>
<div className={styles.container}>
<a
href="https://github.com/itsnitinr/vscode-portfolio"
target="_blank"
rel="noreferrer noopener"
className={styles.section}
>
<VscSourceControl className={styles.icon} />
<p>main</p>
</a>
<div className={styles.section}>
<VscError className={styles.icon} />
<p className={styles.errorText}>0</p>&nbsp;&nbsp;
<VscWarning className={styles.icon} />
<p>0</p>
</div>
</div>
<div className={styles.container}>
<div className={styles.section}>
<SiNextdotjs className={styles.icon} />
<p>Powered by Next.js</p>
</div>
<div className={styles.section}>
<VscCheck className={styles.icon} />
<p>Prettier</p>
</div>
<div className={styles.section}>
<VscBell />
</div>
</div>
</footer>
);
};
export default Bottombar;

View File

@@ -0,0 +1,51 @@
import styles from '@/styles/ContactCode.module.css';
const contactItems = [
{
social: 'website',
link: 'Portfolio',
href: 'https://ahmed.galadima.talenttic.com',
},
{
social: 'email',
link: 'ahmed.galadima@hotmail.com',
href: 'mailto:ahmed.galadima@hotmail.com',
},
{
social: 'github',
link: 'galads',
href: 'https://github.com/glimz',
},
{
social: 'linkedin',
link: 'Linkedin URL',
href: 'https://www.linkedin.com/in/ahmed-galadima',
},
{
social: 'twitter',
link: 'galads',
href: 'https://x.com/Mr_galads',
},
];
const ContactCode = () => {
return (
<div className={styles.code}>
<p className={styles.line}>
<span className={styles.className}>.socials</span> &#123;
</p>
{contactItems.map((item, index) => (
<p className={styles.line} key={index}>
&nbsp;&nbsp;&nbsp;{item.social}:{' '}
<a href={item.href} target="_blank" rel="noopener">
{item.link}
</a>
;
</p>
))}
<p className={styles.line}>&#125;</p>
</div>
);
};
export default ContactCode;

80
components/Explorer.tsx Normal file
View File

@@ -0,0 +1,80 @@
import Link from 'next/link';
import Image from 'next/image';
import { useState } from 'react';
import { VscChevronRight } from 'react-icons/vsc';
import styles from '@/styles/Explorer.module.css';
const explorerItems = [
{
name: 'home.tsx',
path: '/',
icon: '/logos/react_icon.svg',
},
{
name: 'about.html',
path: '/about',
icon: '/logos/html_icon.svg',
},
{
name: 'contact.css',
path: '/contact',
icon: '/logos/css_icon.svg',
},
{
name: 'projects.js',
path: '/projects',
icon: '/logos/js_icon.svg',
},
/* {
name: 'articles.json',
path: '/articles',
icon: '/logos/json_icon.svg',
}, */
{
name: 'github.md',
path: '/github',
icon: '/logos/markdown_icon.svg',
},
];
const Explorer = () => {
const [portfolioOpen, setPortfolioOpen] = useState(true);
return (
<div className={styles.explorer}>
<p className={styles.title}>Explorer</p>
<div>
<input
type="checkbox"
className={styles.checkbox}
id="portfolio-checkbox"
checked={portfolioOpen}
onChange={() => setPortfolioOpen(!portfolioOpen)}
/>
<label htmlFor="portfolio-checkbox" className={styles.heading}>
<VscChevronRight
className={styles.chevron}
style={portfolioOpen ? { transform: 'rotate(90deg)' } : {}}
/>
Portfolio
</label>
<div
className={styles.files}
style={portfolioOpen ? { display: 'block' } : { display: 'none' }}
>
{explorerItems.map((item) => (
<Link href={item.path} key={item.name}>
<div className={styles.file}>
<Image src={item.icon} alt={item.name} height={18} width={18} />{' '}
<p>{item.name}</p>
</div>
</Link>
))}
</div>
</div>
</div>
);
};
export default Explorer;

35
components/Head.tsx Normal file
View File

@@ -0,0 +1,35 @@
import Head from 'next/head';
interface CustomHeadProps {
title: string;
}
const CustomHead = ({ title }: CustomHeadProps) => {
return (
<Head>
<title>{title}</title>
<meta
name="description"
content="Ahmed Galadima, Senior QA Engineer, Automation Consultant, Software Development, Test Automation, CI/CD, DevOps"
/>
<meta
name="keywords"
content="Ahmed Galadima, Senior QA Engineer, Automation Consultant, Software Development, Test Automation, CI/CD, DevOps"
/>
<meta property="og:title" content="Ahmed Galadima's Portfolio" />
<meta
property="og:description"
content="A Senior QA Engineer, Automation Consultant. Software Development, Test Automation, CI/CD, DevOps"
/>
<meta property="og:image" content="https://imgur.com/4zi5KkQ.png" />
<meta property="og:url" content="https://vscode-portfolio.vercel.app" />
<meta name="twitter:card" content="summary_large_image" />
</Head>
);
};
export default CustomHead;
CustomHead.defaultProps = {
title: 'Ahmed Galadima | Portfolio',
};

View File

@@ -0,0 +1,25 @@
import { SVGProps } from 'react';
const Illustration = (props: SVGProps<SVGSVGElement>) => {
return (
<svg
width={486}
height={534}
viewBox="0 0 486 534"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<circle cx={167} cy={60} r={60} fill="#D7F484" />
<circle cx={37.5} cy={215.5} r={37.5} fill="currentColor" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M486 144.469c-38.145-31.86-87.255-51.033-140.842-51.033-121.415 0-219.842 98.427-219.842 219.842 0 14.167 1.34 28.02 3.9 41.441 47.414-86.154 91.678-142.17 146.717-170.767 56.069-29.132 121.816-29.08 210.067-6.68v-32.803zm0 48.288v289.33c-38.145 31.86-87.255 51.033-140.842 51.033-100.321 0-184.947-67.197-211.325-159.037l1.502.805c49.937-93.22 94.046-149.844 147.514-177.625 52.014-27.025 114.411-27.498 203.151-4.506z"
fill="currentColor"
/>
</svg>
);
};
export default Illustration;

44
components/Layout.tsx Normal file
View File

@@ -0,0 +1,44 @@
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import Titlebar from '@/components/Titlebar';
import Sidebar from '@/components/Sidebar';
import Explorer from '@/components/Explorer';
import Bottombar from '@/components/Bottombar';
import Tabsbar from '@/components/Tabsbar';
import styles from '@/styles/Layout.module.css';
interface LayoutProps {
children: React.ReactNode;
}
const Layout = ({ children }: LayoutProps) => {
// set scroll to top of main content on url pathname change
const router = useRouter();
useEffect(() => {
const main = document.getElementById('main-editor');
if (main) {
main.scrollTop = 0;
}
}, [router.pathname]);
return (
<>
<Titlebar />
<div className={styles.main}>
<Sidebar />
<Explorer />
<div style={{ width: '100%' }}>
<Tabsbar />
<main id="main-editor" className={styles.content}>
{children}
</main>
</div>
</div>
<Bottombar />
</>
);
};
export default Layout;

View File

@@ -0,0 +1,51 @@
import Image from 'next/image';
import { Project } from '@/types';
import styles from '@/styles/ProjectCard.module.css';
interface ProjectCardProps {
project: Project;
}
const ProjectCard = ({ project }: ProjectCardProps) => {
return (
<a
href={project.link}
target="_blank"
rel="noopener noreferrer"
className={styles.card}
>
<div className={styles.content}>
<div className={styles.logoWrapper}>
<Image
src={project.logo}
alt={`${project.title} logo`}
width={24}
height={24}
className={styles.logo}
/>
</div>
<p className={styles.type}>{project.type}</p>
<h3 className={styles.title}>{project.title}</h3>
<p className={styles.description}>{project.description}</p>
</div>
<div className={styles.footer}>
<p className={styles.date}>{project.date}</p>
<p className={styles.status} data-status={project.status.toLowerCase()}>
{project.status}
</p>
{/* <p className={styles.skills}>{project.skills}</p> */}
</div>
</a>
);
};
// Add hover effect to the card
// Add a gradient overlay on hover
export default ProjectCard;

71
components/RepoCard.tsx Normal file
View File

@@ -0,0 +1,71 @@
import {
VscEye,
VscRepoForked,
VscStarEmpty,
VscGithubAlt,
VscLinkExternal,
VscTypeHierarchy,
} from 'react-icons/vsc';
import { Repo } from '@/types';
import styles from '@/styles/RepoCard.module.css';
interface RepoCardProps {
repo: Repo;
}
const RepoCard = ({ repo }: RepoCardProps) => {
return (
<div className={styles.card}>
<div className={styles.cardHeader}>
<h3 className={styles.title}>{repo.name}</h3>
{repo.language && (
<div className={styles.language}>
<VscTypeHierarchy className={styles.languageIcon} />
<span>{repo.language}</span>
</div>
)}
</div>
<p>{repo.description || 'No description provided'}</p>
<div className={styles.stats}>
<div>
<div>
<VscStarEmpty className={styles.icon} />
{repo.stargazers_count}
</div>
<div>
<VscRepoForked className={styles.icon} />
{repo.forks}
</div>
<div>
<VscEye className={styles.icon} />
{repo.watchers}
</div>
</div>
<div>
<a
href={repo.html_url}
target="_blank"
rel="noopener noreferrer"
title="View Repository"
>
<VscGithubAlt className={styles.icon} />
</a>
{repo.homepage && (
<a
href={repo.homepage}
target="_blank"
rel="noopener noreferrer"
title="Visit Live Site"
>
<VscLinkExternal className={styles.icon} />
</a>
)}
</div>
</div>
</div>
);
};
export default RepoCard;

74
components/Sidebar.tsx Normal file
View File

@@ -0,0 +1,74 @@
import Link from 'next/link';
import { useRouter } from 'next/router';
import {
VscAccount,
VscSettings,
VscMail,
VscGithubAlt,
VscCode,
VscFiles,
VscEdit,
} from 'react-icons/vsc';
import styles from '@/styles/Sidebar.module.css';
const sidebarTopItems = [
{ Icon: VscFiles, path: '/' },
{ Icon: VscGithubAlt, path: '/github' },
{ Icon: VscCode, path: '/projects' },
{ Icon: VscEdit, path: '/articles' },
{ Icon: VscMail, path: '/contact' },
];
const sidebarBottomItems = [
{ Icon: VscAccount, path: '/about' },
{ Icon: VscSettings, path: '/settings' },
];
const Sidebar = () => {
const router = useRouter();
return (
<aside className={styles.sidebar}>
<div className={styles.sidebarTop}>
{sidebarTopItems.map(({ Icon, path }) => (
<Link href={path} key={path}>
<div
className={`${styles.iconContainer} ${
router.pathname === path && styles.active
}`}
>
<Icon
size={16}
fill={
router.pathname === path
? 'rgb(225, 228, 232)'
: 'rgb(106, 115, 125)'
}
className={styles.icon}
/>
</div>
</Link>
))}
</div>
<div className={styles.sidebarBottom}>
{sidebarBottomItems.map(({ Icon, path }) => (
<div className={styles.iconContainer} key={path}>
<Link href={path}>
<Icon
fill={
router.pathname === path
? 'rgb(225, 228, 232)'
: 'rgb(106, 115, 125)'
}
className={styles.icon}
/>
</Link>
</div>
))}
</div>
</aside>
);
};
export default Sidebar;

28
components/Tab.tsx Normal file
View File

@@ -0,0 +1,28 @@
import Link from 'next/link';
import Image from 'next/image';
import { useRouter } from 'next/router';
import styles from '@/styles/Tab.module.css';
interface TabProps {
icon: string;
filename: string;
path: string;
}
const Tab = ({ icon, filename, path }: TabProps) => {
const router = useRouter();
return (
<Link href={path}>
<div
className={`${styles.tab} ${router.pathname === path && styles.active}`}
>
<Image src={icon} alt={filename} height={18} width={18} />
<p>{filename}</p>
</div>
</Link>
);
};
export default Tab;

26
components/Tabsbar.tsx Normal file
View File

@@ -0,0 +1,26 @@
import Tab from '@/components/Tab';
import styles from '@/styles/Tabsbar.module.css';
const Tabsbar = () => {
return (
<div className={styles.tabs}>
<Tab icon="/logos/react_icon.svg" filename="home.tsx" path="/" />
<Tab icon="/logos/html_icon.svg" filename="about.html" path="/about" />
<Tab icon="/logos/css_icon.svg" filename="contact.css" path="/contact" />
<Tab icon="/logos/js_icon.svg" filename="projects.js" path="/projects" />
{/* <Tab
icon="/logos/json_icon.svg"
filename="articles.json"
path="/articles"
/> */}
<Tab
icon="/logos/markdown_icon.svg"
filename="github.md"
path="/github"
/>
</div>
);
};
export default Tabsbar;

40
components/ThemeInfo.tsx Normal file
View File

@@ -0,0 +1,40 @@
import Image from 'next/image';
import styles from '@/styles/ThemeInfo.module.css';
interface ThemeInfoProps {
icon: string;
name: string;
publisher: string;
theme: string;
}
const ThemeInfo = ({ icon, name, publisher, theme }: ThemeInfoProps) => {
const setTheme = (theme: string) => {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('theme', theme);
};
return (
<div className={styles.container}>
<div className={styles.imageWrapper}>
<Image
src={icon}
alt={name}
height={80}
width={80}
className={styles.themeImage}
/>
</div>
<div className={styles.info}>
<div>
<h3>{name}</h3>
<h5>{publisher}</h5>
</div>
<button onClick={() => setTheme(theme)}>Set Color Theme</button>
</div>
</div>
);
};
export default ThemeInfo;

34
components/Titlebar.tsx Normal file
View File

@@ -0,0 +1,34 @@
import Image from 'next/image';
import styles from '@/styles/Titlebar.module.css';
const Titlebar = () => {
return (
<section className={styles.titlebar}>
<Image
src="/logos/vscode_icon.svg"
alt="VSCode Icon"
height={15}
width={15}
className={styles.icon}
/>
<div className={styles.items}>
<p>File</p>
<p>Edit</p>
<p>View</p>
<p>Go</p>
<p>Run</p>
<p>Terminal</p>
<p>Help</p>
</div>
<p className={styles.title}>Ahmed Galadima - Visual Studio Code</p>
<div className={styles.windowButtons}>
<span className={styles.minimize}></span>
<span className={styles.maximize}></span>
<span className={styles.close}></span>
</div>
</section>
);
};
export default Titlebar;