Skip to content

Commit 4064b84

Browse files
committed
Improve code quality and add Telegram integration support
- Add Telegram button component for contact profiles - Enhance ESLint configuration with stricter rules and test file support - Refactor Sidebar component with improved structure and readability - Add resume-related components (WorkExperience, Education, Skills, Awards, Publications, Volunteer, Projects) - Add resume server utilities and type definitions - Improve ProfileHeader with better layout and Telegram button integration - Optimize TechStack component by removing unused code - Enhance GitHub data fetching with better error handling - Update environment variables documentation in .env.example - Add integration tests for TemplateGitHub component - Improve UI components consistency (Button, Toast, LinkedInButton, Dropdown) - Refactor GitHub type definitions for better type safety - Update multiple portfolio components for better accessibility
1 parent cb68fd7 commit 4064b84

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1532
-202
lines changed

.env.example

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ GH_TOKEN=ghp_your_token_here
99
# Example: octocat
1010
GH_USERNAME=your-github-username
1111

12+
# LinkedIn Username (OPTIONAL)
13+
# Your LinkedIn username (not the full URL, just the username part)
14+
# Example: johndoe
15+
# This will be converted to: https://www.linkedin.com/in/johndoe
16+
# LINKEDIN_USERNAME=
17+
18+
# Telegram Username (OPTIONAL)
19+
# Your Telegram username (with or without @ symbol)
20+
# Example: johndoe or @johndoe
21+
# This will be converted to: https://t.me/johndoe
22+
# TELEGRAM_USERNAME=
23+
24+
# JSON Resume URL (OPTIONAL)
25+
# URL to your JSON Resume file (following jsonresume.org schema)
26+
# Example: https://gist.githubusercontent.com/username/gist-id/raw/resume.json
27+
# Example: https://registry.jsonresume.org/username
28+
# JSONRESUME_URL=
29+
1230
# Base Path (OPTIONAL)
1331
# Use this if your site is deployed to a subdirectory (e.g., https://username.github.io/repo-name/)
1432
# Leave empty for root deployment (e.g., https://username.github.io/)
@@ -20,11 +38,3 @@ GH_USERNAME=your-github-username
2038
# This will generate a CNAME file during the build process
2139
# Example: portfolio.example.com
2240
# CUSTOM_DOMAIN=
23-
24-
# LinkedIn URL (OPTIONAL)
25-
# Your LinkedIn profile URL or username
26-
# If you provide just a username, it will be converted to: https://www.linkedin.com/in/username
27-
# If you provide a full URL, it will be used as-is
28-
# Example (username): johndoe
29-
# Example (full URL): https://www.linkedin.com/in/johndoe
30-
# LINKEDIN_URL=

eslint.config.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,23 @@ export default defineConfig(
2323
rules: {
2424
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
2525
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
26-
'no-undef': 'off'
26+
'no-undef': 'off',
27+
// Disable resolve check for external links and goto
28+
'svelte/no-navigation-without-resolve': [
29+
'error',
30+
{
31+
ignoreLinks: true,
32+
ignoreGoto: true
33+
}
34+
],
35+
// Allow unused variables that start with _
36+
'@typescript-eslint/no-unused-vars': [
37+
'error',
38+
{
39+
argsIgnorePattern: '^_',
40+
varsIgnorePattern: '^_'
41+
}
42+
]
2743
}
2844
},
2945
{

src/lib/components/landing/FeaturedProfiles.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
</div>
2323

2424
<div class="grid gap-6 sm:grid-cols-2 md:grid-cols-3">
25-
{#each featuredProfiles as profile}
25+
{#each featuredProfiles as profile (profile.username)}
2626
<button
2727
type="button"
2828
onclick={() => handleProfileClick(profile.username)}

src/lib/components/landing/ScreenshotShowcase.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
// Calculate rotation based on scroll position relative to section
1212
let rotateX = $derived.by(() => {
1313
// Reference scrollY to ensure this re-runs on scroll
14-
const _ = scrollY;
14+
void scrollY;
1515
1616
if (!sectionEl || typeof window === 'undefined') return 20;
1717
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script lang="ts">
2+
import type { Award } from '$lib/types/resume';
3+
import AwardItem from '$lib/components/resume/AwardItem.svelte';
4+
5+
interface Props {
6+
awards: Award[];
7+
class?: string;
8+
}
9+
10+
let { awards, class: className = '' }: Props = $props();
11+
</script>
12+
13+
{#if awards && awards.length > 0}
14+
<section class="space-y-4 {className}">
15+
<div class="mb-4 flex items-center gap-2">
16+
<svg
17+
class="h-5 w-5 text-text-tertiary"
18+
fill="none"
19+
viewBox="0 0 24 24"
20+
stroke="currentColor"
21+
>
22+
<path
23+
stroke-linecap="round"
24+
stroke-linejoin="round"
25+
stroke-width="2"
26+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
27+
/>
28+
</svg>
29+
<h3 class="text-lg font-semibold text-text-primary">Awards & Certificates</h3>
30+
</div>
31+
<div class="grid gap-4 sm:grid-cols-2">
32+
{#each awards as award}
33+
<AwardItem {award} />
34+
{/each}
35+
</div>
36+
</section>
37+
{/if}

src/lib/components/portfolio/ContributionGraph.svelte

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,9 @@
100100

101101
<!-- Months Grid -->
102102
<div class="flex flex-1 justify-between gap-0.5">
103-
{#each weeks as week, i}
103+
{#each weeks as week, i (week.contributionDays[0]?.date ?? i)}
104104
<div class="relative min-w-0 flex-1">
105-
{#each monthLabels as month}
105+
{#each monthLabels as month (month.col)}
106106
{#if month.col === i}
107107
<span class="absolute bottom-0 left-0 truncate text-[10px]">
108108
{month.label}
@@ -118,7 +118,7 @@
118118
<div class="flex w-full gap-2">
119119
<!-- Day labels -->
120120
<div class="flex w-8 shrink-0 flex-col gap-0.5">
121-
{#each dayLabels as label}
121+
{#each dayLabels as label (label)}
122122
<div
123123
class="flex flex-1 items-center justify-end text-[10px] leading-none text-text-tertiary"
124124
>
@@ -129,9 +129,9 @@
129129

130130
<!-- Contribution squares -->
131131
<div class="flex flex-1 justify-between gap-0.5">
132-
{#each weeks as week}
132+
{#each weeks as week (week.contributionDays[0]?.date ?? '')}
133133
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
134-
{#each week.contributionDays as day}
134+
{#each week.contributionDays as day (day.date)}
135135
<div
136136
class="aspect-square w-full rounded-sm transition-opacity hover:opacity-80"
137137
style="background-color: {getContributionColor(
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script lang="ts">
2+
import type { Education } from '$lib/types/resume';
3+
import EducationItem from '$lib/components/resume/EducationItem.svelte';
4+
5+
interface Props {
6+
education: Education[];
7+
class?: string;
8+
}
9+
10+
let { education, class: className = '' }: Props = $props();
11+
</script>
12+
13+
{#if education && education.length > 0}
14+
<section class="space-y-4 {className}">
15+
<div class="mb-4 flex items-center gap-2">
16+
<svg
17+
class="h-5 w-5 text-text-tertiary"
18+
fill="none"
19+
viewBox="0 0 24 24"
20+
stroke="currentColor"
21+
>
22+
<path
23+
stroke-linecap="round"
24+
stroke-linejoin="round"
25+
stroke-width="2"
26+
d="M12 14l9-5-9-5-9 5 9 5z"
27+
/>
28+
<path
29+
stroke-linecap="round"
30+
stroke-linejoin="round"
31+
stroke-width="2"
32+
d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z"
33+
/>
34+
<path
35+
stroke-linecap="round"
36+
stroke-linejoin="round"
37+
stroke-width="2"
38+
d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"
39+
/>
40+
</svg>
41+
<h3 class="text-lg font-semibold text-text-primary">Education</h3>
42+
</div>
43+
<div class="space-y-4">
44+
{#each education as edu, i ((edu.institution || '') + (edu.startDate || '') + i)}
45+
<EducationItem {edu} />
46+
{/each}
47+
</div>
48+
</section>
49+
{/if}

src/lib/components/portfolio/ExternalContributions.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252

5353
<!-- Contributions Grid (2x2 like Notable Projects) -->
5454
<div class="grid gap-4 sm:grid-cols-2">
55-
{#each topContributions as contrib}
55+
{#each topContributions as contrib (contrib.repoName)}
5656
<a
5757
href="https://github.com/{contrib.repoName}"
5858
target="_blank"

src/lib/components/portfolio/LanguageChart.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
{#if topLanguages.length > 0}
4747
<div class="relative shrink-0">
4848
<svg width={size} height={size} class="-rotate-90">
49-
{#each segments as segment}
49+
{#each segments as segment (segment.lang.name)}
5050
<circle
5151
cx={size / 2}
5252
cy={size / 2}
@@ -67,7 +67,7 @@
6767

6868
<!-- Legend -->
6969
<div class="flex-1 space-y-2">
70-
{#each topLanguages as lang}
70+
{#each topLanguages as lang (lang.name)}
7171
<div class="flex items-center justify-between text-sm">
7272
<div class="flex items-center gap-2">
7373
<div class="h-3 w-3 rounded-full" style="background-color: {lang.color}"></div>

src/lib/components/portfolio/ProfileHeader.svelte

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import type { GitHubProfile } from '$lib/types/github';
33
import Badge from '$lib/components/ui/Badge.svelte';
44
import LinkedInButton from '$lib/components/ui/LinkedInButton.svelte';
5+
import TelegramButton from '$lib/components/ui/TelegramButton.svelte';
56
67
interface Props {
78
profile: GitHubProfile;
@@ -73,7 +74,7 @@
7374
</div>
7475

7576
<!-- Social Links -->
76-
{#if profile.user.websiteUrl || profile.user.twitterUsername || profile.user.linkedinUrl}
77+
{#if profile.user.websiteUrl || profile.user.twitterUsername || profile.user.linkedinUsername || profile.user.telegramUsername}
7778
<div class="mt-3 flex flex-wrap gap-2">
7879
{#if profile.user.websiteUrl}
7980
<a
@@ -82,6 +83,7 @@
8283
: `https://${profile.user.websiteUrl}`}
8384
target="_blank"
8485
rel="noopener noreferrer"
86+
data-sveltekit-preload-data="off"
8587
class="border-border-primary inline-flex items-center gap-2 rounded-lg border bg-bg-secondary px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-tertiary"
8688
>
8789
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -100,6 +102,7 @@
100102
href="https://twitter.com/{profile.user.twitterUsername}"
101103
target="_blank"
102104
rel="noopener noreferrer"
105+
data-sveltekit-preload-data="off"
103106
class="border-border-primary inline-flex items-center gap-2 rounded-lg border bg-bg-secondary px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-tertiary"
104107
>
105108
<svg class="h-4 w-4" fill="currentColor" viewBox="0 0 24 24">
@@ -110,8 +113,11 @@
110113
Twitter
111114
</a>
112115
{/if}
113-
{#if profile.user.linkedinUrl}
114-
<LinkedInButton linkedinUrl={profile.user.linkedinUrl} />
116+
{#if profile.user.linkedinUsername}
117+
<LinkedInButton linkedinUsername={profile.user.linkedinUsername} />
118+
{/if}
119+
{#if profile.user.telegramUsername}
120+
<TelegramButton telegramUsername={profile.user.telegramUsername} />
115121
{/if}
116122
</div>
117123
{/if}

0 commit comments

Comments
 (0)