Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/app/[username]/components/BackgroundDecoration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function BackgroundDecoration() {
return (
<div className="absolute inset-0 overflow-hidden pointer-events-none fixed">
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The classes absolute and fixed are mutually exclusive and redundant. Since this is a background decoration, you should choose one based on the desired behavior: fixed to keep it static in the viewport while scrolling, or absolute to stay relative to the page container. Having both is confusing and relies on CSS declaration order (where fixed typically overrides absolute in Tailwind).

Suggested change
<div className="absolute inset-0 overflow-hidden pointer-events-none fixed">
<div className="fixed inset-0 overflow-hidden pointer-events-none">

<div className="absolute -top-[10%] -right-[10%] w-[60%] h-[60%] rounded-full bg-accent opacity-5 blur-[120px] animate-pulse-slow" />
<div
className="absolute top-[40%] -left-[10%] w-[50%] h-[50%] rounded-full bg-success opacity-5 blur-[120px] animate-pulse-slow"
style={{ animationDelay: "2s" }}
/>
</div>
);
}
22 changes: 22 additions & 0 deletions src/app/[username]/components/ErrorMessages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
type Props = {
errors: { section: string; message: string }[];
};

export default function ErrorMessages({ errors }: Props) {
if (!errors || errors.length === 0) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 冗長な null チェック

errors の型は { section: string; message: string }[](非 nullable)と宣言されており、呼び出し元も UserSummary の型通りに配列を渡すため、!errors のチェックは不要です。型情報に合わせてシンプルにできます。

Suggested change
if (!errors || errors.length === 0) {
if (errors.length === 0) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[username]/components/ErrorMessages.tsx
Line: 6

Comment:
**冗長な null チェック**

`errors` の型は `{ section: string; message: string }[]`(非 nullable)と宣言されており、呼び出し元も `UserSummary` の型通りに配列を渡すため、`!errors` のチェックは不要です。型情報に合わせてシンプルにできます。

```suggestion
  if (errors.length === 0) {
```

How can I resolve this? If you propose a fix, please make it concise.

return null;
}

return (
<div className="mb-6 space-y-2 animate-slide-up">
{errors.map((err) => (
<div
key={err.section}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 重複キーによるレンダリング問題の可能性

err.section をキーとして使用しているため、同じセクション名を持つエラーが複数存在する場合に React がキーの重複を警告し、リストの更新が正しく行われないことがあります。元のコードにも同じ問題がありましたが、この抽出を機に配列インデックスなど一意な値をキーとして使うことを検討してください。

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[username]/components/ErrorMessages.tsx
Line: 14

Comment:
**重複キーによるレンダリング問題の可能性**

`err.section` をキーとして使用しているため、同じセクション名を持つエラーが複数存在する場合に React がキーの重複を警告し、リストの更新が正しく行われないことがあります。元のコードにも同じ問題がありましたが、この抽出を機に配列インデックスなど一意な値をキーとして使うことを検討してください。

How can I resolve this? If you propose a fix, please make it concise.

className="rounded-md border border-danger/30 bg-danger/10 px-4 py-3 text-sm text-danger"
>
<strong>{err.section}:</strong> {err.message}
</div>
))}
</div>
);
}
52 changes: 52 additions & 0 deletions src/app/[username]/components/UserSummaryGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import SkillsCard from "@/components/SkillsCard";
import ContributionsCard from "@/components/ContributionsCard";
import ReposCard from "@/components/ReposCard";
import ActivityCard from "@/components/ActivityCard";
import InterestsCard from "@/components/InterestsCard";
import AnimatedWrapper from "@/components/AnimatedWrapper";
import { UserSummary } from "@/lib/types";

type Props = {
summary: UserSummary;
};

export default function UserSummaryGrid({ summary }: Props) {
return (
<div className="mt-8 grid gap-6 lg:grid-cols-2">
{/* Skills */}
{summary.repositories && (
<AnimatedWrapper delay="0.2s">
<SkillsCard repositories={summary.repositories} />
</AnimatedWrapper>
)}

{/* Contributions */}
{summary.contributions && (
<AnimatedWrapper delay="0.3s">
<ContributionsCard contributions={summary.contributions} />
</AnimatedWrapper>
)}

{/* Repos */}
{summary.repositories && (
<AnimatedWrapper delay="0.4s">
<ReposCard repositories={summary.repositories} />
</AnimatedWrapper>
)}

{/* Interests */}
{summary.interests && (
<AnimatedWrapper delay="0.5s">
<InterestsCard interests={summary.interests} />
</AnimatedWrapper>
)}

{/* Activity */}
{summary.activity && (
<AnimatedWrapper delay="0.6s">
<ActivityCard activity={summary.activity} />
</AnimatedWrapper>
)}
</div>
);
}
70 changes: 7 additions & 63 deletions src/app/[username]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import { UserNotFoundError } from "@/lib/types";
import ShareButtons from "@/components/ShareButtons";
import CardGenerator from "@/components/CardGenerator";
import ProfileCard from "@/components/ProfileCard";
import SkillsCard from "@/components/SkillsCard";
import ContributionsCard from "@/components/ContributionsCard";
import ReposCard from "@/components/ReposCard";
import ActivityCard from "@/components/ActivityCard";
import InterestsCard from "@/components/InterestsCard";
import AnimatedWrapper from "@/components/AnimatedWrapper";
import ThemeController from "@/components/ThemeController";
import MyPageBanner from "@/components/MyPageBanner";

import BackgroundDecoration from "./components/BackgroundDecoration";
import ErrorMessages from "./components/ErrorMessages";
import UserSummaryGrid from "./components/UserSummaryGrid";

type Props = {
params: Promise<{ username: string }>;
};
Expand Down Expand Up @@ -62,30 +61,11 @@ export default async function UserPage({ params }: Props) {
topLanguageColor={summary.repositories?.languages[0]?.color}
/>

{/* Background decoration */}
<div className="absolute inset-0 overflow-hidden pointer-events-none fixed">
<div className="absolute -top-[10%] -right-[10%] w-[60%] h-[60%] rounded-full bg-accent opacity-5 blur-[120px] animate-pulse-slow" />
<div
className="absolute top-[40%] -left-[10%] w-[50%] h-[50%] rounded-full bg-success opacity-5 blur-[120px] animate-pulse-slow"
style={{ animationDelay: "2s" }}
/>
</div>
<BackgroundDecoration />

{/* Main */}
<main className="mx-auto w-full max-w-6xl flex-1 px-4 py-8 relative z-10 animate-fade-in">
{/* Errors */}
{summary.errors.length > 0 && (
<div className="mb-6 space-y-2 animate-slide-up">
{summary.errors.map((err) => (
<div
key={err.section}
className="rounded-md border border-danger/30 bg-danger/10 px-4 py-3 text-sm text-danger"
>
<strong>{err.section}:</strong> {err.message}
</div>
))}
</div>
)}
<ErrorMessages errors={summary.errors} />

<MyPageBanner username={username} />

Expand All @@ -105,43 +85,7 @@ export default async function UserPage({ params }: Props) {
</AnimatedWrapper>
)}

{/* Grid */}
<div className="mt-8 grid gap-6 lg:grid-cols-2">
{/* Skills */}
{summary.repositories && (
<AnimatedWrapper delay="0.2s">
<SkillsCard repositories={summary.repositories} />
</AnimatedWrapper>
)}

{/* Contributions */}
{summary.contributions && (
<AnimatedWrapper delay="0.3s">
<ContributionsCard contributions={summary.contributions} />
</AnimatedWrapper>
)}

{/* Repos */}
{summary.repositories && (
<AnimatedWrapper delay="0.4s">
<ReposCard repositories={summary.repositories} />
</AnimatedWrapper>
)}

{/* Interests */}
{summary.interests && (
<AnimatedWrapper delay="0.5s">
<InterestsCard interests={summary.interests} />
</AnimatedWrapper>
)}

{/* Activity */}
{summary.activity && (
<AnimatedWrapper delay="0.6s">
<ActivityCard activity={summary.activity} />
</AnimatedWrapper>
)}
</div>
<UserSummaryGrid summary={summary} />
</main>

{/* Footer */}
Expand Down
Loading