diff --git a/bun.lockb b/bun.lockb index dd470a8..bc04427 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/public/bluesky-logo.svg b/public/bluesky-logo.svg index c9ecb15..48ed575 100644 --- a/public/bluesky-logo.svg +++ b/public/bluesky-logo.svg @@ -1,4 +1,4 @@ - - + + diff --git a/public/linkedin-logo.png b/public/linkedin-logo.png index a9fe49b..0ce2ac3 100644 Binary files a/public/linkedin-logo.png and b/public/linkedin-logo.png differ diff --git a/public/portrait-one.jpg b/public/portrait-one.jpg new file mode 100644 index 0000000..5f65e57 Binary files /dev/null and b/public/portrait-one.jpg differ diff --git a/public/portrait-three.png b/public/portrait-three.png new file mode 100644 index 0000000..b1f454a Binary files /dev/null and b/public/portrait-three.png differ diff --git a/public/portrait.png b/public/portrait-two.png similarity index 100% rename from public/portrait.png rename to public/portrait-two.png diff --git a/src/app/globals.css b/src/app/globals.css index 97ee168..c20c24b 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -24,7 +24,7 @@ body { --accent-foreground: 60 9.1% 97.8%; --destructive: 0 72.2% 50.6%; --destructive-foreground: 60 9.1% 97.8%; - --border: 12 6.5% 15.1%; + --border: 180 0% 20%; --input: 12 6.5% 15.1%; --ring: 20.5 90.2% 48.2%; --chart-1: 220 70% 50%; diff --git a/src/app/page.tsx b/src/app/page.tsx index 770cdd9..0a20673 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,10 +2,11 @@ import React from 'react'; import Image from 'next/image'; import { Input } from '@/components/ui/input'; import { Separator } from '@/components/ui/separator'; +import { Skeleton } from '@/components/ui/skeleton'; import { Card, CardContent, CardDescription, CardHeader } from '@/components/ui/card'; import Link from 'next/link'; import { SearchResult } from '@/components/search-result'; -import { PublicUser } from '@/lib/github-user'; +import { PublicUser, Repository } from '@/lib/github'; const calculateAge = (birthdate: Date): number => { const today = new Date(); @@ -21,7 +22,7 @@ const calculateAge = (birthdate: Date): number => { }; const getRepos = async (username: string) => { - let repos: any[] = []; + let repos: Repository[] = []; let page = 1; let hasNextPage = true; @@ -45,7 +46,7 @@ const getRepos = async (username: string) => { return repos; }; -const getLanguages = async (repo: any) => { +const getLanguages = async (repo: Repository) => { const response = await fetch(repo.languages_url, { headers: { Accept: 'application/vnd.github+json', @@ -54,7 +55,7 @@ const getLanguages = async (repo: any) => { return await response.json(); }; -const accumulateLanguages = async (repos: any[]) => { +const accumulateLanguages = async (repos: Repository[]) => { const languages: { [key: string]: number } = {}; for (const repo of repos) { @@ -67,6 +68,16 @@ const accumulateLanguages = async (repos: any[]) => { return languages; }; +const accumulateStars = (repos: Repository[]) => { + + let stars = 0; + for (const repo of repos) { + stars = stars + repo.stargazers_count; + } + + return stars; +}; + export default async function Home() { const profileResponse = await fetch('https://api.github.com/users/markusthielker'); @@ -75,15 +86,16 @@ export default async function Home() { const username = 'markusthielker'; const repos = await getRepos(username); const languages = await accumulateLanguages(repos); + const stars = accumulateStars(repos); const totalBytes = Object.values(languages).reduce((sum, bytes) => sum + bytes, 0); const languagePercentages = Object.entries(languages) .map(([lang, bytes]) => ({ - lang, + name: lang, percent: (bytes / totalBytes) * 100, })) .sort((a, b) => b.percent - a.percent) - .slice(0, 5); + .slice(0, 3); const age = calculateAge(new Date('2001-03-04')); @@ -93,12 +105,12 @@ export default async function Home() { { /* search header */}
- +
+ T + L + K + R +
{ /* header content (centered) */}
@@ -106,12 +118,12 @@ export default async function Home() { { /* input */} { /* tab navigation */} -
+
All News Images @@ -144,28 +156,31 @@ export default async function Home() { className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 grid-rows-3 lg:grid-rows-2 gap-4 max-h-92 lg:max-h-72"> { /* images */} - -
+ +
Portrait of Markus Thielker
Portrait of Markus Thielker
-
+
Portrait of Markus Thielker
@@ -187,10 +202,21 @@ export default async function Home() {
- {profile.name} - {profile.followers} Followers + MarkusThielker + { + stars > 0 ? + {stars} total stars + : + + } + { + profile.followers ? + {profile.followers} followers + : + + }
- {profile.bio} + Full stack developer of Kotlin and Java backends with experience in Android, React and Angular frontend development.
@@ -204,8 +230,8 @@ export default async function Home() { - 23 Jahre - 04. März 2001 + {age} years + March 2001 @@ -226,19 +252,23 @@ export default async function Home() { - Employer + Languages { - languagePercentages && -
    - {Object.entries(languagePercentages).map(([lang, percent]) => ( -
  • - {lang}: {percent as unknown as string} % -
  • - ))} -
+ languagePercentages ? +
    + {Object.entries(languagePercentages).map(([index, language]) => ( +
  • + {language.name}: {Math.round(language.percent)} % +
  • + ))} +
+ : +
+ +
}
@@ -256,7 +286,7 @@ export default async function Home() { website_icon_uri="/linkedin-logo.png" href="https://linkedin.com/in/markusthielker" title="LinkedIn" - description="Als erfahrener Softwareentwickler bin ich stolz darauf, innovative Software Lösungen zu schaffen. Neben meiner beruflichen Tätigkeit engagiere ich mich auch in persönlichen Projekten, insbesondere in den Bereichen Web- und Android Full Stack Entwicklung."/> + description="As an experienced software developer, I take pride in crafting innovative software solutions. Beyond my professional role, I'm actively involved in personal projects, especially in the areas of web and Android full stack development."/> { /* person info column */} -
- Info - - Markus Thielker ist ein {age} Jahre alter Software Engineer aus Neu-Ulm, Deutschland. - Er entwickelt, nach einiger Jahre Android App Entwicklung, webbasierte Sofwtare. Dabei - verwendet er vorranig Next.js und Angular. +
+ + + +
+ Info + + Markus Thielker is a {age} year old software engineer from Neu-Ulm, Germany. + After several years of Android app development, he now develops web-based software. + In doing so he primarily uses Next.js and Angular. -
- Degree: None - Experience: 2.5 years - Pets: One dog - Favorite Tech: Next.js & Kotlin +
+ Degree: None + Work Experience: 2.5 years + Pets: The cutest dog in the world + Favorite Tech: Next.js & Kotlin +
+
+ Markus Thielker © 2024 +
); } diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..01b8b6d --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/src/lib/github-user.ts b/src/lib/github-user.ts deleted file mode 100644 index 304a849..0000000 --- a/src/lib/github-user.ts +++ /dev/null @@ -1,108 +0,0 @@ -export type GithubUser = PrivateUser | PublicUser - -/** - * Private User - */ -export interface PrivateUser { - login: string - id: number - user_view_type?: string - node_id: string - avatar_url: string - gravatar_id: string | null - url: string - html_url: string - followers_url: string - following_url: string - gists_url: string - starred_url: string - subscriptions_url: string - organizations_url: string - repos_url: string - events_url: string - received_events_url: string - type: string - site_admin: boolean - name: string | null - company: string | null - blog: string | null - location: string | null - email: string | null - notification_email?: string | null - hireable: boolean | null - bio: string | null - twitter_username?: string | null - public_repos: number - public_gists: number - followers: number - following: number - created_at: string - updated_at: string - private_gists: number - total_private_repos: number - owned_private_repos: number - disk_usage: number - collaborators: number - two_factor_authentication: boolean - plan?: { - collaborators: number - name: string - space: number - private_repos: number - [k: string]: unknown - } - business_plus?: boolean - ldap_dn?: string - [k: string]: unknown -} -/** - * Public User - */ -export interface PublicUser { - login: string - id: number - user_view_type?: string - node_id: string - avatar_url: string - gravatar_id: string | null - url: string - html_url: string - followers_url: string - following_url: string - gists_url: string - starred_url: string - subscriptions_url: string - organizations_url: string - repos_url: string - events_url: string - received_events_url: string - type: string - site_admin: boolean - name: string | null - company: string | null - blog: string | null - location: string | null - email: string | null - notification_email?: string | null - hireable: boolean | null - bio: string | null - twitter_username?: string | null - public_repos: number - public_gists: number - followers: number - following: number - created_at: string - updated_at: string - plan?: { - collaborators: number - name: string - space: number - private_repos: number - [k: string]: unknown - } - private_gists?: number - total_private_repos?: number - owned_private_repos?: number - disk_usage?: number - collaborators?: number -} diff --git a/src/lib/github.ts b/src/lib/github.ts new file mode 100644 index 0000000..2fce850 --- /dev/null +++ b/src/lib/github.ts @@ -0,0 +1,561 @@ +export type Github = PrivateUser | PublicUser + +/** + * Private User + */ +export interface PrivateUser { + login: string + id: number + user_view_type?: string + node_id: string + avatar_url: string + gravatar_id: string | null + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean + name: string | null + company: string | null + blog: string | null + location: string | null + email: string | null + notification_email?: string | null + hireable: boolean | null + bio: string | null + twitter_username?: string | null + public_repos: number + public_gists: number + followers: number + following: number + created_at: string + updated_at: string + private_gists: number + total_private_repos: number + owned_private_repos: number + disk_usage: number + collaborators: number + two_factor_authentication: boolean + plan?: { + collaborators: number + name: string + space: number + private_repos: number + [k: string]: unknown + } + business_plus?: boolean + ldap_dn?: string + [k: string]: unknown +} + +/** + * Public User + */ +export interface PublicUser { + login: string + id: number + user_view_type?: string + node_id: string + avatar_url: string + gravatar_id: string | null + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean + name: string | null + company: string | null + blog: string | null + location: string | null + email: string | null + notification_email?: string | null + hireable: boolean | null + bio: string | null + twitter_username?: string | null + public_repos: number + public_gists: number + followers: number + following: number + created_at: string + updated_at: string + plan?: { + collaborators: number + name: string + space: number + private_repos: number + [k: string]: unknown + } + private_gists?: number + total_private_repos?: number + owned_private_repos?: number + disk_usage?: number + collaborators?: number +} + +/** + * Full Repository + */ +export interface FullRepository { + id: number + node_id: string + name: string + full_name: string + owner: SimpleUser + private: boolean + html_url: string + description: string | null + fork: boolean + url: string + archive_url: string + assignees_url: string + blobs_url: string + branches_url: string + collaborators_url: string + comments_url: string + commits_url: string + compare_url: string + contents_url: string + contributors_url: string + deployments_url: string + downloads_url: string + events_url: string + forks_url: string + git_commits_url: string + git_refs_url: string + git_tags_url: string + git_url: string + issue_comment_url: string + issue_events_url: string + issues_url: string + keys_url: string + labels_url: string + languages_url: string + merges_url: string + milestones_url: string + notifications_url: string + pulls_url: string + releases_url: string + ssh_url: string + stargazers_url: string + statuses_url: string + subscribers_url: string + subscription_url: string + tags_url: string + teams_url: string + trees_url: string + clone_url: string + mirror_url: string | null + hooks_url: string + svn_url: string + homepage: string | null + language: string | null + forks_count: number + stargazers_count: number + watchers_count: number + /** + * The size of the repository, in kilobytes. Size is calculated hourly. When a repository is initially created, the size is 0. + */ + size: number + default_branch: string + open_issues_count: number + is_template?: boolean + topics?: string[] + has_issues: boolean + has_projects: boolean + has_wiki: boolean + has_pages: boolean + has_downloads?: boolean + has_discussions: boolean + archived: boolean + /** + * Returns whether or not this repository disabled. + */ + disabled: boolean + /** + * The repository visibility: public, private, or internal. + */ + visibility?: string + pushed_at: string + created_at: string + updated_at: string + permissions?: { + admin: boolean + maintain?: boolean + push: boolean + triage?: boolean + pull: boolean + [k: string]: unknown + } + allow_rebase_merge?: boolean + template_repository?: null | Repository + temp_clone_token?: string | null + allow_squash_merge?: boolean + allow_auto_merge?: boolean + delete_branch_on_merge?: boolean + allow_merge_commit?: boolean + allow_update_branch?: boolean + use_squash_pr_title_as_default?: boolean + /** + * The default value for a squash merge commit title: + * + * - `PR_TITLE` - default to the pull request's title. + * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull request's title (when more than one commit). + */ + squash_merge_commit_title?: "PR_TITLE" | "COMMIT_OR_PR_TITLE" + /** + * The default value for a squash merge commit message: + * + * - `PR_BODY` - default to the pull request's body. + * - `COMMIT_MESSAGES` - default to the branch's commit messages. + * - `BLANK` - default to a blank commit message. + */ + squash_merge_commit_message?: "PR_BODY" | "COMMIT_MESSAGES" | "BLANK" + /** + * The default value for a merge commit title. + * + * - `PR_TITLE` - default to the pull request's title. + * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull request #123 from branch-name). + */ + merge_commit_title?: "PR_TITLE" | "MERGE_MESSAGE" + /** + * The default value for a merge commit message. + * + * - `PR_TITLE` - default to the pull request's title. + * - `PR_BODY` - default to the pull request's body. + * - `BLANK` - default to a blank commit message. + */ + merge_commit_message?: "PR_BODY" | "PR_TITLE" | "BLANK" + allow_forking?: boolean + web_commit_signoff_required?: boolean + subscribers_count: number + network_count: number + license: null | LicenseSimple + organization?: null | SimpleUser + parent?: Repository + source?: Repository + forks: number + master_branch?: string + open_issues: number + watchers: number + /** + * Whether anonymous git access is allowed. + */ + anonymous_access_enabled?: boolean + code_of_conduct?: CodeOfConductSimple + security_and_analysis?: { + advanced_security?: { + status?: "enabled" | "disabled" + [k: string]: unknown + } + /** + * Enable or disable Dependabot security updates for the repository. + */ + dependabot_security_updates?: { + /** + * The enablement status of Dependabot security updates for the repository. + */ + status?: "enabled" | "disabled" + [k: string]: unknown + } + secret_scanning?: { + status?: "enabled" | "disabled" + [k: string]: unknown + } + secret_scanning_push_protection?: { + status?: "enabled" | "disabled" + [k: string]: unknown + } + secret_scanning_non_provider_patterns?: { + status?: "enabled" | "disabled" + [k: string]: unknown + } + secret_scanning_ai_detection?: { + status?: "enabled" | "disabled" + [k: string]: unknown + } + [k: string]: unknown + } | null + /** + * The custom properties that were defined for the repository. The keys are the custom property names, and the values are the corresponding custom property values. + */ + custom_properties?: { + [k: string]: unknown + } + [k: string]: unknown +} + +/** + * A GitHub user. + */ +export interface SimpleUser { + name?: string | null + email?: string | null + login: string + id: number + node_id: string + avatar_url: string + gravatar_id: string | null + url: string + html_url: string + followers_url: string + following_url: string + gists_url: string + starred_url: string + subscriptions_url: string + organizations_url: string + repos_url: string + events_url: string + received_events_url: string + type: string + site_admin: boolean + starred_at?: string + user_view_type?: string + [k: string]: unknown +} + +/** + * A repository on GitHub. + */ +export interface Repository { + /** + * Unique identifier of the repository + */ + id: number + node_id: string + /** + * The name of the repository. + */ + name: string + full_name: string + license: null | LicenseSimple + forks: number + permissions?: { + admin: boolean + pull: boolean + triage?: boolean + push: boolean + maintain?: boolean + [k: string]: unknown + } + owner: SimpleUser + /** + * Whether the repository is private or public. + */ + private: boolean + html_url: string + description: string | null + fork: boolean + url: string + archive_url: string + assignees_url: string + blobs_url: string + branches_url: string + collaborators_url: string + comments_url: string + commits_url: string + compare_url: string + contents_url: string + contributors_url: string + deployments_url: string + downloads_url: string + events_url: string + forks_url: string + git_commits_url: string + git_refs_url: string + git_tags_url: string + git_url: string + issue_comment_url: string + issue_events_url: string + issues_url: string + keys_url: string + labels_url: string + languages_url: string + merges_url: string + milestones_url: string + notifications_url: string + pulls_url: string + releases_url: string + ssh_url: string + stargazers_url: string + statuses_url: string + subscribers_url: string + subscription_url: string + tags_url: string + teams_url: string + trees_url: string + clone_url: string + mirror_url: string | null + hooks_url: string + svn_url: string + homepage: string | null + language: string | null + forks_count: number + stargazers_count: number + watchers_count: number + /** + * The size of the repository, in kilobytes. Size is calculated hourly. When a repository is initially created, the size is 0. + */ + size: number + /** + * The default branch of the repository. + */ + default_branch: string + open_issues_count: number + /** + * Whether this repository acts as a template that can be used to generate new repositories. + */ + is_template?: boolean + topics?: string[] + /** + * Whether issues are enabled. + */ + has_issues: boolean + /** + * Whether projects are enabled. + */ + has_projects: boolean + /** + * Whether the wiki is enabled. + */ + has_wiki: boolean + has_pages: boolean + /** + * Whether downloads are enabled. + */ + has_downloads: boolean + /** + * Whether discussions are enabled. + */ + has_discussions?: boolean + /** + * Whether the repository is archived. + */ + archived: boolean + /** + * Returns whether or not this repository disabled. + */ + disabled: boolean + /** + * The repository visibility: public, private, or internal. + */ + visibility?: string + pushed_at: string | null + created_at: string | null + updated_at: string | null + /** + * Whether to allow rebase merges for pull requests. + */ + allow_rebase_merge?: boolean + temp_clone_token?: string + /** + * Whether to allow squash merges for pull requests. + */ + allow_squash_merge?: boolean + /** + * Whether to allow Auto-merge to be used on pull requests. + */ + allow_auto_merge?: boolean + /** + * Whether to delete head branches when pull requests are merged + */ + delete_branch_on_merge?: boolean + /** + * Whether or not a pull request head branch that is behind its base branch can always be updated even if it is not required to be up to date before merging. + */ + allow_update_branch?: boolean + /** + * Whether a squash merge commit can use the pull request title as default. **This property is closing down. Please use `squash_merge_commit_title` instead. + */ + use_squash_pr_title_as_default?: boolean + /** + * The default value for a squash merge commit title: + * + * - `PR_TITLE` - default to the pull request's title. + * - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) or the pull request's title (when more than one commit). + */ + squash_merge_commit_title?: "PR_TITLE" | "COMMIT_OR_PR_TITLE" + /** + * The default value for a squash merge commit message: + * + * - `PR_BODY` - default to the pull request's body. + * - `COMMIT_MESSAGES` - default to the branch's commit messages. + * - `BLANK` - default to a blank commit message. + */ + squash_merge_commit_message?: "PR_BODY" | "COMMIT_MESSAGES" | "BLANK" + /** + * The default value for a merge commit title. + * + * - `PR_TITLE` - default to the pull request's title. + * - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., Merge pull request #123 from branch-name). + */ + merge_commit_title?: "PR_TITLE" | "MERGE_MESSAGE" + /** + * The default value for a merge commit message. + * + * - `PR_TITLE` - default to the pull request's title. + * - `PR_BODY` - default to the pull request's body. + * - `BLANK` - default to a blank commit message. + */ + merge_commit_message?: "PR_BODY" | "PR_TITLE" | "BLANK" + /** + * Whether to allow merge commits for pull requests. + */ + allow_merge_commit?: boolean + /** + * Whether to allow forking this repo + */ + allow_forking?: boolean + /** + * Whether to require contributors to sign off on web-based commits + */ + web_commit_signoff_required?: boolean + open_issues: number + watchers: number + master_branch?: string + starred_at?: string + /** + * Whether anonymous git access is enabled for this repository + */ + anonymous_access_enabled?: boolean + [k: string]: unknown +} + +/** + * License Simple + */ +export interface LicenseSimple { + key: string + name: string + url: string | null + spdx_id: string | null + node_id: string + html_url?: string + [k: string]: unknown +} + +/** + * Code of Conduct Simple + */ +export interface CodeOfConductSimple { + url: string + key: string + name: string + html_url: string | null + [k: string]: unknown +}