tercul-frontend/client/src/pages/user/Profile.tsx
google-labs-jules[bot] ea15477b86 feat: Fix TypeScript errors and improve type safety
This commit addresses 275 TypeScript compilation errors and improves type safety, code quality, and maintainability across the frontend codebase.

The following issues have been resolved:
- Standardized `translationId` to `number`
- Fixed missing properties on annotation types
- Resolved `tags` type mismatch
- Corrected `country` type mismatch
- Addressed date vs. string mismatches
- Fixed variable hoisting issues
- Improved server-side type safety
- Added missing null/undefined checks
- Fixed arithmetic operations on non-numbers
- Resolved `RefObject` type issues

Note: I was unable to verify the frontend changes due to local setup issues with the development server. The server would not start, and I was unable to run the Playwright tests.
2025-11-27 17:48:31 +00:00

271 lines
9.1 KiB
TypeScript

import { useQuery } from "@tanstack/react-query";
import { BookOpen, Clock, Heart, PenSquare, Settings } from "lucide-react";
import { useState } from "react";
import { Link } from "wouter";
import { WorkCard } from "@/components/common/WorkCard";
import { PageLayout } from "@/components/layout/PageLayout";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import type { BookmarkWithWork } from "@/lib/types";
// Mock user ID for demo - in a real app, this would come from authentication
const DEMO_USER_ID = 1;
export default function Profile() {
const [activeTab, setActiveTab] = useState("bookmarks");
// Fetch user data
const { data: user, isLoading: userLoading } = useQuery({
queryKey: [`/api/users/${DEMO_USER_ID}`],
});
// Fetch user's bookmarks with work details
const { data: bookmarks, isLoading: bookmarksLoading } = useQuery<
BookmarkWithWork[]
>({
queryKey: [`/api/users/${DEMO_USER_ID}/bookmarks`],
select: (data) =>
data.map((bookmark) => ({
...bookmark,
work: {
...bookmark.work,
tags: bookmark.work.tags?.map((tag) =>
typeof tag === "string" ? { name: tag } : tag,
),
},
})),
});
// Fetch user's contributions (works/translations they've added)
const { data: contributions, isLoading: contributionsLoading } = useQuery({
queryKey: [`/api/users/${DEMO_USER_ID}/contributions`],
});
// Fetch reading progress
const { data: readingProgress, isLoading: progressLoading } = useQuery({
queryKey: [`/api/users/${DEMO_USER_ID}/reading-progress`],
});
return (
<PageLayout>
<div className="max-w-[var(--content-width)] mx-auto px-4 md:px-6 py-8">
{/* Profile header */}
<div className="mb-8">
{userLoading ? (
<div className="flex flex-col md:flex-row gap-6 items-center md:items-start">
<Skeleton className="w-24 h-24 rounded-full" />
<div className="flex-1 text-center md:text-left">
<Skeleton className="h-8 w-48 mb-2 mx-auto md:mx-0" />
<Skeleton className="h-4 w-32 mb-4 mx-auto md:mx-0" />
<Skeleton className="h-16 w-full" />
</div>
</div>
) : user ? (
<div className="flex flex-col md:flex-row gap-6 items-center md:items-start">
<Avatar className="w-24 h-24 border-2 border-sage/20">
<AvatarImage
src={user.avatar}
alt={user.displayName || user.username}
/>
<AvatarFallback className="text-2xl bg-navy/10 dark:bg-navy/20 text-navy dark:text-cream">
{(user.displayName || user.username)
.slice(0, 2)
.toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex-1 text-center md:text-left">
<h1 className="text-2xl md:text-3xl font-bold font-serif text-navy dark:text-cream mb-1">
{user.displayName || user.username}
</h1>
<p className="text-navy/70 dark:text-cream/70 mb-4">
@{user.username}
</p>
<p className="text-navy/80 dark:text-cream/80 max-w-2xl mb-6">
{user.bio ||
"No bio provided. Tell us about yourself and your literary interests."}
</p>
<div className="flex flex-wrap gap-3 justify-center md:justify-start">
<Button variant="outline" size="sm" className="gap-2">
<Settings className="h-4 w-4" />
Edit Profile
</Button>
<Link href="/submit">
<Button
size="sm"
className="gap-2 bg-russet hover:bg-russet/90 text-white"
>
<PenSquare className="h-4 w-4" />
Submit New Translation
</Button>
</Link>
</div>
</div>
</div>
) : (
<div className="text-center py-8">
<h1 className="text-2xl font-bold mb-4">User not found</h1>
<p className="mb-4">Please sign in to view your profile</p>
<Button>Sign In</Button>
</div>
)}
</div>
{/* User content tabs */}
<Tabs
defaultValue="bookmarks"
value={activeTab}
onValueChange={setActiveTab}
>
<TabsList className="mb-6">
<TabsTrigger value="bookmarks" className="gap-2">
<BookOpen className="h-4 w-4" />
<span>Bookmarks</span>
</TabsTrigger>
<TabsTrigger value="contributions" className="gap-2">
<PenSquare className="h-4 w-4" />
<span>Contributions</span>
</TabsTrigger>
<TabsTrigger value="reading" className="gap-2">
<Clock className="h-4 w-4" />
<span>Reading History</span>
</TabsTrigger>
<TabsTrigger value="likes" className="gap-2">
<Heart className="h-4 w-4" />
<span>Likes</span>
</TabsTrigger>
</TabsList>
{/* Bookmarks tab */}
<TabsContent value="bookmarks">
<h2 className="text-xl font-serif font-semibold text-navy dark:text-cream mb-4">
Your Bookmarked Works
</h2>
{bookmarksLoading ? (
<div className="space-y-4">
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className="h-40" />
))}
</div>
) : bookmarks?.length ? (
<div className="space-y-3">
{bookmarks.map((bookmark) => (
<WorkCard key={bookmark.id} work={bookmark.work} />
))}
</div>
) : (
<Card className="bg-cream dark:bg-dark-surface">
<CardContent className="text-center py-12">
<BookOpen className="h-12 w-12 mx-auto mb-4 text-navy/40 dark:text-cream/40" />
<h3 className="text-lg font-medium mb-2">No bookmarks yet</h3>
<p className="text-navy/70 dark:text-cream/70 mb-6">
Save your favorite works for easy access later
</p>
<Link href="/explore">
<Button>Explore Works</Button>
</Link>
</CardContent>
</Card>
)}
</TabsContent>
{/* Contributions tab */}
<TabsContent value="contributions">
<h2 className="text-xl font-serif font-semibold text-navy dark:text-cream mb-4">
Your Contributions
</h2>
{contributionsLoading ? (
<div className="space-y-4">
{Array.from({ length: 2 }).map((_, i) => (
<Skeleton key={i} className="h-40" />
))}
</div>
) : contributions?.length ? (
<div className="space-y-3">
{/* This would display the user's contributions */}
<p>Contributions would appear here</p>
</div>
) : (
<Card className="bg-cream dark:bg-dark-surface">
<CardContent className="text-center py-12">
<PenSquare className="h-12 w-12 mx-auto mb-4 text-navy/40 dark:text-cream/40" />
<h3 className="text-lg font-medium mb-2">
No contributions yet
</h3>
<p className="text-navy/70 dark:text-cream/70 mb-6">
Share your translations with the Tercul community
</p>
<Link href="/submit">
<Button className="bg-russet hover:bg-russet/90 text-white">
Submit Translation
</Button>
</Link>
</CardContent>
</Card>
)}
</TabsContent>
{/* Reading History tab */}
<TabsContent value="reading">
<h2 className="text-xl font-serif font-semibold text-navy dark:text-cream mb-4">
Reading History
</h2>
{progressLoading ? (
<div className="space-y-4">
{Array.from({ length: 3 }).map((_, i) => (
<Skeleton key={i} className="h-20" />
))}
</div>
) : readingProgress?.length ? (
<div className="space-y-3">
{/* This would display the user's reading progress */}
<p>Reading progress would appear here</p>
</div>
) : (
<Card className="bg-cream dark:bg-dark-surface">
<CardContent className="text-center py-12">
<Clock className="h-12 w-12 mx-auto mb-4 text-navy/40 dark:text-cream/40" />
<h3 className="text-lg font-medium mb-2">
No reading history
</h3>
<p className="text-navy/70 dark:text-cream/70 mb-6">
Your reading progress will be saved here
</p>
<Link href="/explore">
<Button>Start Reading</Button>
</Link>
</CardContent>
</Card>
)}
</TabsContent>
{/* Likes tab */}
<TabsContent value="likes">
<h2 className="text-xl font-serif font-semibold text-navy dark:text-cream mb-4">
Your Liked Works
</h2>
<Card className="bg-cream dark:bg-dark-surface">
<CardContent className="text-center py-12">
<Heart className="h-12 w-12 mx-auto mb-4 text-navy/40 dark:text-cream/40" />
<h3 className="text-lg font-medium mb-2">No liked works yet</h3>
<p className="text-navy/70 dark:text-cream/70 mb-6">
Show your appreciation for authors and translators
</p>
<Link href="/explore">
<Button>Explore Works</Button>
</Link>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
</PageLayout>
);
}