✨ feat: update index.astro page
swadhinbiswas committed 3 months ago
d60ea80
1 parent 5f99cf3
commit d60ea80
1 file changed
+158 -53
Showing 1 changed file+158-53
▼src/pages/[owner]/index.astro
+158-53
| @@ -3,6 +3,8 @@ import { getDatabase, schema } from "@/db"; | ||
3 | 3 | import BaseLayout from "@/layouts/BaseLayout.astro"; |
4 | 4 | import { and, desc, eq, sql } from "drizzle-orm"; |
5 | 5 | import type { NodePgDatabase } from "drizzle-orm/node-postgres"; |
6 | +import { resolveRepoPath } from "@/lib/git-storage"; | |
7 | +import { getFileContent } from "@/lib/git"; | |
6 | 8 | |
7 | 9 | const { owner: username } = Astro.params; |
8 | 10 | const db = getDatabase() as NodePgDatabase<typeof schema>; |
| @@ -79,6 +81,22 @@ try { | ||
79 | 81 | }); |
80 | 82 | } catch {} |
81 | 83 | |
84 | +// 8. Fetch profile README from {username}/{username} repo | |
85 | +let profileReadme = ""; | |
86 | +try { | |
87 | + const profileRepo = await db.query.repositories.findFirst({ | |
88 | + where: and( | |
89 | + eq(schema.repositories.ownerId, user.id), | |
90 | + eq(schema.repositories.name, username!), | |
91 | + ), | |
92 | + }); | |
93 | + if (profileRepo) { | |
94 | + const localPath = await resolveRepoPath(profileRepo.diskPath); | |
95 | + const fileData = await getFileContent(localPath, "README.md", profileRepo.defaultBranch || "main"); | |
96 | + profileReadme = fileData?.content || ""; | |
97 | + } | |
98 | +} catch {} | |
99 | + | |
82 | 100 | // Language colors (GitHub style) |
83 | 101 | const langColors: Record<string, string> = { |
84 | 102 | JavaScript: "bg-[#f1e05a]", |
| @@ -331,10 +349,17 @@ const weeks = Array.from({ length: 52 }, (_, wi) => | ||
331 | 349 | |
332 | 350 | {tab === "overview" && ( |
333 | 351 | <div class="space-y-6"> |
352 | + <!-- Profile README --> | |
353 | + {profileReadme && ( | |
354 | + <div class="border border-border bg-card rounded-lg p-6 markdown-body"> | |
355 | + <div set:html={profileReadme} /> | |
356 | + </div> | |
357 | + )} | |
358 | + | |
334 | 359 | <!-- Pinned Repositories --> |
335 | 360 | <div> |
336 | 361 | <div class="flex items-center justify-between mb-3"> |
337 | - <span class="text-sm font-semibold text-foreground">Popular repositories</span> | |
362 | + <span class="text-sm font-semibold text-foreground">Pinned</span> | |
338 | 363 | {isOwner && ( |
339 | 364 | <a href={`/${user.username}?tab=repositories`} class="text-xs text-muted-foreground hover:text-blue-500 transition-colors">Customize your pins</a> |
340 | 365 | )} |
| @@ -384,75 +409,155 @@ const weeks = Array.from({ length: 52 }, (_, wi) => | ||
384 | 409 | |
385 | 410 | <!-- Contribution Graph --> |
386 | 411 | <div> |
387 | - <div class="text-sm text-foreground mb-2"> | |
388 | - <span class="font-semibold">{contributionCount}</span> contributions in the last year | |
412 | + <div class="flex items-center justify-between mb-3"> | |
413 | + <div class="text-sm text-foreground"> | |
414 | + <span class="font-semibold">{contributionCount}</span> contributions in the last year | |
415 | + </div> | |
389 | 416 | </div> |
390 | - <div class="border border-border/60 bg-background rounded-md p-4"> | |
417 | + <div class="border border-border bg-card rounded-lg p-4"> | |
391 | 418 | <div class="w-full overflow-x-auto no-scrollbar"> |
392 | - <div class="inline-flex gap-[3px] justify-start min-w-full"> | |
393 | - {weeks.map(week => ( | |
419 | + <div class="inline-flex gap-[3px] justify-start"> | |
420 | + {weeks.map((week, wi) => ( | |
394 | 421 | <div class="flex flex-col gap-[3px]"> |
395 | - {week.map(dayLevel => ( | |
396 | - <div | |
397 | - class:list={[ | |
398 | - "w-[10px] h-[10px] rounded-[2px] transition-colors", | |
399 | - dayLevel === 0 && "bg-[#ebedf0] dark:bg-[#161b22]", | |
400 | - dayLevel === 1 && "bg-[#9be9a8] dark:bg-[#0e4429]", | |
401 | - dayLevel === 2 && "bg-[#40c463] dark:bg-[#006d32]", | |
402 | - dayLevel === 3 && "bg-[#30a14e] dark:bg-[#26a641]", | |
403 | - dayLevel === 4 && "bg-[#216e39] dark:bg-[#39d353]", | |
404 | - ]} | |
405 | - /> | |
406 | - ))} | |
422 | + {week.map((dayLevel, di) => { | |
423 | + const totalDays = wi * 7 + di; | |
424 | + const date = new Date(); | |
425 | + date.setDate(date.getDate() - (364 - totalDays)); | |
426 | + const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); | |
427 | + return ( | |
428 | + <div | |
429 | + class:list={[ | |
430 | + "w-[11px] h-[11px] rounded-[2px] transition-all hover:ring-1 hover:ring-foreground/20", | |
431 | + dayLevel === 0 && "bg-muted", | |
432 | + dayLevel === 1 && "bg-primary/20", | |
433 | + dayLevel === 2 && "bg-primary/40", | |
434 | + dayLevel === 3 && "bg-primary/60", | |
435 | + dayLevel === 4 && "bg-primary", | |
436 | + ]} | |
437 | + title={`${dayLevel} contributions on ${dateStr}`} | |
438 | + /> | |
439 | + ); | |
440 | + })} | |
407 | 441 | </div> |
408 | 442 | ))} |
409 | 443 | </div> |
410 | 444 | </div> |
411 | - <div class="flex items-center justify-between mt-3 text-[11px] text-muted-foreground"> | |
412 | - <span>Learn how we count contributions</span> | |
413 | - <div class="flex items-center gap-[3px]"> | |
445 | + <div class="flex items-center justify-between mt-4 pt-3 border-t border-border text-xs text-muted-foreground"> | |
446 | + <a href="#" class="hover:text-foreground transition-colors underline decoration-dotted underline-offset-2">Learn how we count contributions</a> | |
447 | + <div class="flex items-center gap-1.5"> | |
414 | 448 | <span>Less</span> |
415 | - <div class="w-[10px] h-[10px] rounded-[2px] bg-[#ebedf0] dark:bg-[#161b22]" /> | |
416 | - <div class="w-[10px] h-[10px] rounded-[2px] bg-[#9be9a8] dark:bg-[#0e4429]" /> | |
417 | - <div class="w-[10px] h-[10px] rounded-[2px] bg-[#40c463] dark:bg-[#006d32]" /> | |
418 | - <div class="w-[10px] h-[10px] rounded-[2px] bg-[#30a14e] dark:bg-[#26a641]" /> | |
419 | - <div class="w-[10px] h-[10px] rounded-[2px] bg-[#216e39] dark:bg-[#39d353]" /> | |
449 | + <div class="w-[11px] h-[11px] rounded-[2px] bg-muted" /> | |
450 | + <div class="w-[11px] h-[11px] rounded-[2px] bg-primary/20" /> | |
451 | + <div class="w-[11px] h-[11px] rounded-[2px] bg-primary/40" /> | |
452 | + <div class="w-[11px] h-[11px] rounded-[2px] bg-primary/60" /> | |
453 | + <div class="w-[11px] h-[11px] rounded-[2px] bg-primary" /> | |
420 | 454 | <span>More</span> |
421 | 455 | </div> |
422 | 456 | </div> |
423 | 457 | </div> |
424 | 458 | </div> |
425 | 459 | |
426 | - <!-- Activity --> | |
427 | - {recentActivity.length > 0 && ( | |
428 | - <div> | |
429 | - <div class="text-sm font-semibold text-foreground mb-3">Contribution activity</div> | |
430 | - <div class="relative border-l-2 border-border ml-3 space-y-6 pb-4"> | |
431 | - {recentActivity.map((activity) => ( | |
432 | - <div class="relative"> | |
433 | - <div class="absolute -left-[23px] top-0.5 h-6 w-6 bg-background border border-border rounded-full flex items-center justify-center"> | |
434 | - <div class="h-2 w-2 rounded-full bg-green-500" /> | |
435 | - </div> | |
436 | - <div class="pl-6"> | |
437 | - <p class="text-sm text-foreground"> | |
438 | - {activity.action || "Performed an action"} | |
439 | - {activity.targetType && ` on ${activity.targetType}`} | |
440 | - </p> | |
441 | - <p class="text-xs text-muted-foreground mt-0.5"> | |
442 | - {timeAgo(activity.createdAt)} | |
443 | - </p> | |
460 | + <!-- Contribution Activity --> | |
461 | + <div class="border border-border bg-card rounded-lg p-5"> | |
462 | + <div class="flex items-center gap-2 mb-5"> | |
463 | + <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="text-muted-foreground"> | |
464 | + <path d="M22 12h-4l-3 9L9 3l-3 9H2"/> | |
465 | + </svg> | |
466 | + <h3 class="text-base font-semibold text-foreground">Contribution activity</h3> | |
467 | + </div> | |
468 | + | |
469 | + {recentActivity.length > 0 ? ( | |
470 | + <div class="space-y-0"> | |
471 | + {recentActivity.map((activity) => { | |
472 | + const activityDate = new Date(activity.createdAt); | |
473 | + const dateStr = activityDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); | |
474 | + const timeStr = activityDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); | |
475 | + | |
476 | + // Get icon, color, and action text | |
477 | + const getActivityDisplay = (type: string, action: string) => { | |
478 | + if (type === 'fork' || action === 'forked') return { iconBg: 'bg-blue-500/15', iconColor: 'text-blue-400', actionColor: 'text-blue-400', action: 'Forked', target: 'a repository' }; | |
479 | + if (type === 'star' || action === 'starred') return { iconBg: 'bg-yellow-500/15', iconColor: 'text-yellow-400', actionColor: 'text-yellow-400', action: 'Starred', target: 'a repository' }; | |
480 | + if (type === 'create_repo' || action === 'created') return { iconBg: 'bg-green-500/15', iconColor: 'text-green-400', actionColor: 'text-green-400', action: 'Created', target: 'a repository' }; | |
481 | + if (type === 'open_issue' || (action === 'opened' && type === 'issue')) return { iconBg: 'bg-green-500/15', iconColor: 'text-green-400', actionColor: 'text-green-400', action: 'Opened', target: 'an issue' }; | |
482 | + if (type === 'open_pr' || type === 'pull_request') return { iconBg: 'bg-green-500/15', iconColor: 'text-green-400', actionColor: 'text-green-400', action: 'Opened', target: 'a pull request' }; | |
483 | + if (type === 'close_issue') return { iconBg: 'bg-red-500/15', iconColor: 'text-red-400', actionColor: 'text-red-400', action: 'Closed', target: 'an issue' }; | |
484 | + if (type === 'close_pr') return { iconBg: 'bg-red-500/15', iconColor: 'text-red-400', actionColor: 'text-red-400', action: 'Closed', target: 'a pull request' }; | |
485 | + if (type === 'merge_pr') return { iconBg: 'bg-purple-500/15', iconColor: 'text-purple-400', actionColor: 'text-purple-400', action: 'Merged', target: 'a pull request' }; | |
486 | + if (type === 'push') return { iconBg: 'bg-green-500/15', iconColor: 'text-green-400', actionColor: 'text-green-400', action: 'Pushed', target: 'commits' }; | |
487 | + if (type === 'comment') return { iconBg: 'bg-blue-500/15', iconColor: 'text-blue-400', actionColor: 'text-blue-400', action: 'Commented', target: 'on an issue' }; | |
488 | + if (type === 'follow') return { iconBg: 'bg-purple-500/15', iconColor: 'text-purple-400', actionColor: 'text-purple-400', action: 'Followed', target: 'a user' }; | |
489 | + return { iconBg: 'bg-muted', iconColor: 'text-muted-foreground', actionColor: 'text-foreground', action: action || 'Performed', target: 'an action' }; | |
490 | + }; | |
491 | + | |
492 | + const display = getActivityDisplay(activity.type, activity.action); | |
493 | + | |
494 | + return ( | |
495 | + <div class="flex items-center gap-4 py-3 border-b border-border/50 last:border-0 group"> | |
496 | + {/* Icon Badge */} | |
497 | + <div class:list={[ | |
498 | + "w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0", | |
499 | + display.iconBg | |
500 | + ]}> | |
501 | + {activity.type === 'fork' && ( | |
502 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><circle cx="12" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M12 6v9"/><path d="M24 18c0-3-2.5-6-6-6"/></svg> | |
503 | + )} | |
504 | + {activity.type === 'star' && ( | |
505 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg> | |
506 | + )} | |
507 | + {activity.type === 'create_repo' && ( | |
508 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/><path d="M9 18c-4.51 2-5-2-7-2"/></svg> | |
509 | + )} | |
510 | + {(activity.type === 'open_issue' || activity.type === 'close_issue') && ( | |
511 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg> | |
512 | + )} | |
513 | + {(activity.type === 'open_pr' || activity.type === 'pull_request' || activity.type === 'close_pr' || activity.type === 'merge_pr') && ( | |
514 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/><path d="M11 18H8a2 2 0 0 1-2-2V9"/></svg> | |
515 | + )} | |
516 | + {activity.type === 'push' && ( | |
517 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg> | |
518 | + )} | |
519 | + {activity.type === 'comment' && ( | |
520 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg> | |
521 | + )} | |
522 | + {activity.type === 'follow' && ( | |
523 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg> | |
524 | + )} | |
525 | + {!['fork','star','create_repo','open_issue','close_issue','open_pr','pull_request','close_pr','merge_pr','push','comment','follow'].includes(activity.type) && ( | |
526 | + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class:list={display.iconColor}><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> | |
527 | + )} | |
528 | + </div> | |
529 | + | |
530 | + {/* Activity Text */} | |
531 | + <div class="flex-1 min-w-0"> | |
532 | + <p class="text-sm text-foreground"> | |
533 | + <span class:list={["font-medium", display.actionColor]}>{display.action}</span> | |
534 | + {" "}{display.target} | |
535 | + </p> | |
536 | + </div> | |
537 | + | |
538 | + {/* Date */} | |
539 | + <span class="text-xs text-muted-foreground whitespace-nowrap flex-shrink-0"> | |
540 | + {dateStr} at {timeStr} | |
541 | + </span> | |
444 | 542 | </div> |
445 | - </div> | |
446 | - ))} | |
543 | + ); | |
544 | + })} | |
447 | 545 | </div> |
448 | - </div> | |
449 | - )} | |
546 | + ) : ( | |
547 | + <div class="py-8 text-center"> | |
548 | + <p class="text-sm text-muted-foreground">No recent activity</p> | |
549 | + </div> | |
550 | + )} | |
450 | 551 | |
451 | - {recentActivity.length === 0 && ( | |
452 | - <div class="text-center py-8 text-sm text-muted-foreground"> | |
453 | - No recent activity | |
454 | - </div> | |
455 | - )} | |
552 | + {recentActivity.length > 0 && ( | |
553 | + <div class="mt-4 text-center"> | |
554 | + <a href={`/${user.username}?tab=activity`} class="inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors"> | |
555 | + View all activity | |
556 | + <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 18l6-6-6-6"/></svg> | |
557 | + </a> | |
558 | + </div> | |
559 | + )} | |
560 | + </div> | |
456 | 561 | </div> |
457 | 562 | )} |
458 | 563 | |
459 | 564 | |