Skip to content
Merged
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
94 changes: 83 additions & 11 deletions src/app/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import Character from "@/src/components/character/character";
import Header from "@/src/components/header/header";
import SkillsTable from "@/src/components/skills-table/skills-table";
import TasksTable from "@/src/components/tasks-table/tasks-table";
import { cn } from "@/src/lib/utils";

type ActivePanel = "skills" | "goals";

export default function HomePage() {
const [skills, setSkills] = useState<Skill[]>([]);
Expand All @@ -18,6 +21,24 @@ export default function HomePage() {

const [loadingGoalIds, setLoadingGoalIds] = useState<Set<string>>(new Set());

// Mobile tabs state
const [activePanel, setActivePanel] = useState<ActivePanel>("skills");

// Handle skill selection with auto-switch to Goals on mobile
const handleSelectSkill = (skillId: string | null) => {
if (skillId === selectedSkillId) {
// Deselecting
setSelectedSkillId(null);
}
else {
setSelectedSkillId(skillId);
// Auto-switch to Goals panel when selecting a skill (for mobile UX)
if (skillId !== null) {
setActivePanel("goals");
}
}
};

// Load skills on mount
useEffect(() => {
async function fetchSkills() {
Expand Down Expand Up @@ -156,15 +177,15 @@ export default function HomePage() {

if (isLoading) {
return (
<div className="flex items-center justify-center h-screen">
<div className="flex items-center justify-center min-h-dvh">
<p>Loading...</p>
</div>
);
}

if (error) {
return (
<div className="flex items-center justify-center h-screen">
<div className="flex items-center justify-center min-h-dvh">
<p className="text-red-500">
Error:
{error}
Expand All @@ -174,19 +195,70 @@ export default function HomePage() {
}

return (
<div className="flex items-center justify-center h-screen p-4">
<div className="w-11/12 max-w-7xl border rounded-lg shadow-lg">
<div className="min-h-dvh p-4 sm:p-6 lg:p-8">
<div className="mx-auto w-full max-w-7xl border rounded-lg shadow-lg">
<Header />
<main className="p-4">
<div className="w-2/3">
<main className="p-4 sm:p-6">
{/* Character section - full width on mobile, 2/3 on larger screens */}
<div className="w-full lg:w-2/3">
<Character />
</div>
<div className="flex gap-4">
<div className="w-2/4 mt-8">
<SkillsTable skills={skills} selectedSkillId={selectedSkillId} setSelectedSkillId={setSelectedSkillId} onAddSkill={addSkill} />

{/* Mobile tabs - only visible on small screens */}
<div className="flex gap-2 mt-6 lg:hidden">
<button
type="button"
onClick={() => setActivePanel("skills")}
className={cn(
"flex-1 py-2 px-4 text-sm font-medium rounded-md transition-colors",
activePanel === "skills"
? "bg-primary text-primary-foreground"
: "bg-muted hover:bg-muted/80",
)}
>
Skills
</button>
<button
type="button"
onClick={() => setActivePanel("goals")}
className={cn(
"flex-1 py-2 px-4 text-sm font-medium rounded-md transition-colors",
activePanel === "goals"
? "bg-primary text-primary-foreground"
: "bg-muted hover:bg-muted/80",
)}
>
Goals
</button>
</div>

{/* Content grid - stacked on mobile (with tabs), side-by-side on lg+ */}
<div className="mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2">
{/* Skills panel */}
<div className={cn(
"min-w-0",
activePanel !== "skills" && "hidden lg:block",
)}
>
<SkillsTable
skills={skills}
selectedSkillId={selectedSkillId}
setSelectedSkillId={handleSelectSkill}
onAddSkill={addSkill}
/>
</div>
<div className="w-2/4 mt-8">
<TasksTable selectedSkill={selectedSkill} onToggleGoal={toggleGoal} loadingGoalIds={loadingGoalIds} />

{/* Goals panel */}
<div className={cn(
"min-w-0",
activePanel !== "goals" && "hidden lg:block",
)}
>
<TasksTable
selectedSkill={selectedSkill}
onToggleGoal={toggleGoal}
loadingGoalIds={loadingGoalIds}
/>
</div>
</div>
</main>
Expand Down
25 changes: 17 additions & 8 deletions src/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,35 @@ function Header() {
};

return (
<header className="flex items-center justify-between p-4 border-b">
<h1 className="text-xl font-semibold">BeetForge</h1>
<div className="flex gap-2 items-center">
<button className="px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-100 transition-colors">
<header className="flex flex-wrap items-center justify-between gap-2 p-3 sm:p-4 border-b">
<h1 className="text-lg sm:text-xl font-semibold">BeetForge</h1>
<div className="flex gap-1 sm:gap-2 items-center">
<button
type="button"
className="px-2 sm:px-4 py-1.5 sm:py-2 text-sm font-medium rounded-md hover:bg-muted transition-colors"
>
Main
</button>
<button className="px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-100 transition-colors">
<button
type="button"
className="hidden sm:block px-4 py-2 text-sm font-medium rounded-md hover:bg-muted transition-colors"
>
Rewards
</button>
<button className="px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-100 transition-colors">
<button
type="button"
className="hidden sm:block px-4 py-2 text-sm font-medium rounded-md hover:bg-muted transition-colors"
>
Help
</button>
<div className="w-px h-6 bg-border mx-2" />
<div className="hidden sm:block w-px h-6 bg-border mx-2" />
<Button
variant="outline"
size="sm"
onClick={handleSignOut}
disabled={isLoading}
>
{isLoading ? "Signing out..." : "Sign Out"}
{isLoading ? "..." : "Sign Out"}
</Button>
</div>
</header>
Expand Down
22 changes: 11 additions & 11 deletions src/components/skills-table/skills-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,20 @@ import AddSkillModal from "../add-skill-modal/add-skill-modal";

function SkillsTable({ skills, selectedSkillId, setSelectedSkillId, onAddSkill }: { skills: Skill[]; selectedSkillId: string | null; setSelectedSkillId: (id: string | null) => void; onAddSkill: (skill: NewSkill) => void }) {
return (
<>
<div className="flex items-center gap-2 border-b border-gray-600 pb-2">
<h2 className="text-lg font-semibold">Skill</h2>
<div className="min-w-0">
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
<h2 className="text-base sm:text-lg font-semibold">Skills</h2>
<AddSkillModal onAddSkill={onAddSkill} />
</div>
<Table>
<TableCaption>A list of your skills.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Rank</TableHead>
<TableHead className="w-[60px] sm:w-[100px]">Rank</TableHead>
<TableHead className="text-center">Title</TableHead>
<TableHead className="text-center">LvL</TableHead>
<TableHead className="text-center">XP</TableHead>
<TableHead className="text-right">Actions</TableHead>
<TableHead className="text-center hidden sm:table-cell">XP</TableHead>
<TableHead className="text-right hidden sm:table-cell">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
Expand All @@ -36,23 +36,23 @@ function SkillsTable({ skills, selectedSkillId, setSelectedSkillId, onAddSkill }
)}
onClick={() => setSelectedSkillId(isSelected ? null : skill.id)}
>
<TableCell className={cn("mt-0.5 font-medium inline-flex items-center justify-center w-8 h-8 text-white rounded", getRankBgColor(skill.rank))}>
<TableCell className={cn("font-medium inline-flex items-center justify-center w-7 h-7 sm:w-8 sm:h-8 text-white text-xs sm:text-sm rounded", getRankBgColor(skill.rank))}>
{skill.rank}
</TableCell>
<TableCell className="text-center">{skill.skillName}</TableCell>
<TableCell className="text-center max-w-[120px] sm:max-w-none truncate">{skill.skillName}</TableCell>
<TableCell className="text-center">{skill.level}</TableCell>
<TableCell className="text-center">
<TableCell className="text-center hidden sm:table-cell">
{skill.currentXp}
/
{skill.nextLevelXp}
</TableCell>
<TableCell className="text-right">actions</TableCell>
<TableCell className="text-right hidden sm:table-cell">actions</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</>
</div>
);
}

Expand Down
87 changes: 48 additions & 39 deletions src/components/tasks-table/tasks-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ import { cn } from "@/src/lib/utils";
function TasksTable({ selectedSkill, onToggleGoal, loadingGoalIds }: { selectedSkill: Skill | null; onToggleGoal: (goalId: string) => void; loadingGoalIds: Set<string> }) {
if (!selectedSkill) {
return (
<>
<div className="flex items-center justify-between border-b border-gray-600 pb-2">
<h2 className="text-lg font-semibold">
Goals for: Select a skill on the left
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2 border-b border-border pb-2">
<h2 className="text-base sm:text-lg font-semibold">
Goals
</h2>
</div>
<div className="p-4 text-center text-muted-foreground">
<div className="p-4 text-center text-muted-foreground text-sm">
Select a skill to view its goals
</div>
</>
</div>
);
}

Expand All @@ -27,56 +27,65 @@ function TasksTable({ selectedSkill, onToggleGoal, loadingGoalIds }: { selectedS
: 0;

return (
<>
<div className="flex items-center justify-between border-b border-gray-600 pb-2">
<h2 className="text-lg font-semibold">
Goals for:
<div className="min-w-0">
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border pb-2">
<h2 className="text-base sm:text-lg font-semibold min-w-0 truncate">
Goals:
{" "}
{selectedSkill.skillName}
<span className="font-normal">{selectedSkill.skillName}</span>
</h2>
<span className={cn("mt-0.5 font-medium inline-flex items-center justify-center w-12 h-6 bg-purple-700/0 text-white rounded-2xl", getRankBgColor(selectedSkill.rank))}>{selectedSkill.rank}</span>
<span className={cn("shrink-0 font-medium inline-flex items-center justify-center w-10 sm:w-12 h-6 text-white text-xs sm:text-sm rounded-2xl", getRankBgColor(selectedSkill.rank))}>{selectedSkill.rank}</span>
</div>
<div className="p-2">
<h2 className="text-sm">
What I want to achieve:
{" "}
<p className="text-xs sm:text-sm text-muted-foreground wrap-break-word">
<span className="font-medium text-foreground">Goal: </span>
{selectedSkill.description ?? "No description"}
</h2>
<div className="flex items-center m-2">
<span className="mr-3">
Lvl:
</p>
<div className="flex items-center mt-3 gap-2 sm:gap-3">
<span className="shrink-0 text-sm font-medium">
Lvl
{" "}
{selectedSkill.level}
</span>
<div className="relative h-6 bg-gray-200 rounded-full overflow-hidden flex-1 min-w-0">
<div className="relative h-5 sm:h-6 bg-muted rounded-full overflow-hidden flex-1 min-w-0">
<div
className="absolute inset-0 bg-yellow-300 rounded-full"
className="absolute inset-0 bg-yellow-400 rounded-full"
style={{ width: `${xpProgress}%` }}
/>
<div className="absolute inset-0 flex items-center justify-center text-sm font-medium text-gray-800">
{`${selectedSkill.currentXp}/${selectedSkill.nextLevelXp} exp.`}
<div className="absolute inset-0 flex items-center justify-center text-xs sm:text-sm font-medium text-foreground">
{`${selectedSkill.currentXp}/${selectedSkill.nextLevelXp} xp`}
</div>
</div>
</div>
</div>
<div className="p-2 border rounded-md border-gray-200 flex flex-col gap-2">
{selectedSkill.goals.map((goal) => {
const isLoading = loadingGoalIds.has(goal.id);
return (
<div key={goal.id} className={cn("flex items-center gap-2 bg-purple-950/30 text-white rounded-md p-2 border-l-4", getRankBorderColor(selectedSkill.rank), getRankTaskBgColor(selectedSkill.rank))}>
{isLoading
? <Loader2 className="w-4 h-4 animate-spin" />
: <Checkbox checked={goal.isCompleted} onCheckedChange={() => onToggleGoal(goal.id)} />}
<span className="text-sm">{goal.goalName}</span>
<div className="ml-auto flex gap-1">
<Pencil className="w-4 h-4" />
<Trash className="w-4 h-4" />
<div className="p-2 border rounded-md border-border flex flex-col gap-2">
{selectedSkill.goals.length === 0
? (
<div className="text-center text-muted-foreground text-sm py-2">
No goals yet
</div>
</div>
);
})}
)
: (
selectedSkill.goals.map((goal) => {
const isLoading = loadingGoalIds.has(goal.id);
return (
<div key={goal.id} className={cn("flex items-start gap-2 text-white rounded-md p-2 border-l-4", getRankBorderColor(selectedSkill.rank), getRankTaskBgColor(selectedSkill.rank))}>
<div className="shrink-0 mt-0.5">
{isLoading
? <Loader2 className="w-4 h-4 animate-spin" />
: <Checkbox checked={goal.isCompleted} onCheckedChange={() => onToggleGoal(goal.id)} />}
</div>
<span className="text-xs sm:text-sm min-w-0 wrap-break-word flex-1">{goal.goalName}</span>
<div className="shrink-0 flex gap-1">
<Pencil className="w-4 h-4" />
<Trash className="w-4 h-4" />
</div>
</div>
);
})
)}
</div>
</>
</div>
);
}

Expand Down
Loading