-
- {NOTIFICATION_TYPE_LABELS[notification.notification_type] || notification.notification_type}
-
- {!notification.is_read && (
-
+
+
+
+ {notification.is_read ? (
+
+ ) : (
+
)}
-
- {new Date(notification.created_at).toLocaleString()}
-
-
- {notification.title}
-
- {notification.content && (
-
- {notification.content}
-
- )}
- {(notification.room || notification.project) && (
-
- {notification.project &&
Project: {notification.project}}
- {notification.room &&
Room: {notification.room}}
+
+
+
+ {NOTIFICATION_TYPE_LABELS[notification.notification_type] ||
+ notification.notification_type}
+
+ {!notification.is_read && (
+
+ )}
+
+ {new Date(notification.created_at).toLocaleString()}
+
- )}
+
+ {notification.title}
+
+ {notification.content && (
+
+ {notification.content}
+
+ )}
+ {(notification.room || notification.project) && (
+
+ {notification.project && (
+ {`Project: ${notification.project}`}
+ )}
+ {notification.room && (
+ {`Room: ${notification.room}`}
+ )}
+
+ )}
+
-
- );
+
+ )
}
export function NotificationList() {
- const queryClient = useQueryClient();
- const [onlyUnread, setOnlyUnread] = useState(false);
+ const queryClient = useQueryClient()
+ const [onlyUnread, setOnlyUnread] = useState(false)
const { data, isLoading, isError } = useQuery({
queryKey: ["notificationList", onlyUnread],
- queryFn: () => notificationList({ only_unread: onlyUnread || undefined, limit: 50 }),
- });
+ queryFn: () =>
+ notificationList({ only_unread: onlyUnread || undefined, limit: 50 }),
+ })
const markAllReadMutation = useMutation({
mutationFn: () => notificationMarkAllRead(),
onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ["notificationList"] });
+ queryClient.invalidateQueries({ queryKey: ["notificationList"] })
},
- });
+ })
if (isLoading) {
return (
-
+
- );
+ )
}
if (isError) {
return (
-
-
Failed to load notifications
-
- );
+
+
+
+
+
+ 通知加载失败
+ 请刷新页面或检查网络连接。
+
+
+ )
}
- const notifications = data?.data?.data?.notifications ?? [];
- const unreadCount = data?.data?.data?.unread_count ?? 0;
+ const notifications = data?.data?.data?.notifications ?? []
+ const unreadCount = data?.data?.data?.unread_count ?? 0
return (
- {/* Toolbar */}
-
-
-
-
- {unreadCount > 0 ? `${unreadCount} unread` : "All read"}
-
-
-
-
- {unreadCount > 0 && (
-
+ {unreadCount > 0 ? `${unreadCount} unread` : "All read"}
+
+
+
+
+ {unreadCount > 0 && (
+
+ )}
+
+
+
{notifications.length === 0 ? (
-
-
-
No notifications
-
- {onlyUnread ? "No unread notifications" : "You're all caught up"}
-
-
+
+
+
+
+
+ 暂无通知
+
+ {onlyUnread ? "没有未读通知" : "你已经查看完了"}
+
+
+
) : (
{notifications.map((n) => (
@@ -168,5 +239,5 @@ export function NotificationList() {
)}
- );
+ )
}
diff --git a/src/app/me/components/ProfileHeader.tsx b/src/app/me/components/ProfileHeader.tsx
index 7d5c9bc..5a4e275 100644
--- a/src/app/me/components/ProfileHeader.tsx
+++ b/src/app/me/components/ProfileHeader.tsx
@@ -1,156 +1,234 @@
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
-import { Button } from "@/components/ui/button";
-import { Link as LinkIcon, Building2, Calendar } from "lucide-react";
-import type { UserInfoExternal } from "@/client/model";
-import { format } from "date-fns";
-import { Skeleton } from "@/components/ui/skeleton";
-import { useUserFollowerCountQuery, useUserStarsQuery, useFollowMutation, useUnfollowMutation } from "@/hooks/useUserQuery";
-import { t } from "@/i18n/T";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
+import { Badge } from "@/components/ui/badge"
+import { Button } from "@/components/ui/button"
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card"
+import { Separator } from "@/components/ui/separator"
+import { Link as LinkIcon, Building2, Calendar } from "lucide-react"
+import type { UserInfoExternal } from "@/client/model"
+import { format } from "date-fns"
+import { Skeleton } from "@/components/ui/skeleton"
+import {
+ useUserFollowerCountQuery,
+ useUserStarsQuery,
+ useFollowMutation,
+ useUnfollowMutation,
+} from "@/hooks/useUserQuery"
+import { t } from "@/i18n/T"
interface ProfileHeaderProps {
- user: UserInfoExternal | null | undefined;
- isMe: boolean;
- isLoading?: boolean;
- starsCount?: number;
- followerCount?: number;
+ user: UserInfoExternal | null | undefined
+ isMe: boolean
+ isLoading?: boolean
+ starsCount?: number
+ followerCount?: number
}
-export function ProfileHeader({ user, isMe, isLoading, starsCount: starsCountProp, followerCount: followerCountProp }: ProfileHeaderProps) {
- const { data: starsData } = useUserStarsQuery(user?.username || "");
- const { data: followerCountApi } = useUserFollowerCountQuery(user?.username || "");
- const followMutation = useFollowMutation();
- const unfollowMutation = useUnfollowMutation();
+function StatBlock({
+ value,
+ label,
+}: {
+ value: string | number
+ label: string
+}) {
+ return (
+
+
+ {value}
+
+
+ {label}
+
+
+ )
+}
- const starsCount = starsCountProp ?? starsData?.total ?? 0;
- const followerCount = followerCountProp ?? followerCountApi ?? 0;
+export function ProfileHeader({
+ user,
+ isMe,
+ isLoading,
+ starsCount: starsCountProp,
+ followerCount: followerCountProp,
+}: ProfileHeaderProps) {
+ const { data: starsData } = useUserStarsQuery(user?.username || "")
+ const { data: followerCountApi } = useUserFollowerCountQuery(
+ user?.username || ""
+ )
+ const followMutation = useFollowMutation()
+ const unfollowMutation = useUnfollowMutation()
+
+ const starsCount = starsCountProp ?? starsData?.total ?? 0
+ const followerCount = followerCountProp ?? followerCountApi ?? 0
const handleFollow = async () => {
- if (!user) return;
+ if (!user) return
try {
- await followMutation.mutateAsync(user.username);
+ await followMutation.mutateAsync(user.username)
} catch (err) {
- console.error("Follow failed:", err);
+ console.error("Follow failed:", err)
}
- };
+ }
const handleUnfollow = async () => {
- if (!user) return;
+ if (!user) return
try {
- await unfollowMutation.mutateAsync(user.username);
+ await unfollowMutation.mutateAsync(user.username)
} catch (err) {
- console.error("Unfollow failed:", err);
+ console.error("Unfollow failed:", err)
}
- };
+ }
+
if (isLoading || !user) {
return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {[...Array(4)].map((_, i) => (
-
-
-
-
- ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {[...Array(4)].map((_, i) => (
+
+ ))}
+
-
-
- );
+
+
+ )
}
return (
-
-
-
-
-
- {user.username[0].toUpperCase()}
-
-
-
-
-
-
-
- {user.display_name || user.username}
-
-
- @{user.username}
-
-
- {!isMe && (
-
-
- );
-}
\ No newline at end of file
+
+
+
+
+
+
+
+
+ (window.location.href = "/me/followers")}
+ className="rounded-xl text-left transition-transform hover:-translate-y-0.5"
+ >
+
+
+
+
+
+ )
+}
diff --git a/src/app/me/components/ProjectList.tsx b/src/app/me/components/ProjectList.tsx
index 3dd28e7..67d60bb 100644
--- a/src/app/me/components/ProjectList.tsx
+++ b/src/app/me/components/ProjectList.tsx
@@ -1,83 +1,129 @@
-import { useNavigate } from "react-router-dom";
-import { Users, Lock } from "lucide-react";
-import type { UserProjectInfo } from "@/client/model";
-import { Skeleton } from "@/components/ui/skeleton";
+import { useNavigate } from "react-router-dom"
+import { Users, Lock } from "lucide-react"
+import type { UserProjectInfo } from "@/client/model"
+import { Card } from "@/components/ui/card"
+import {
+ Empty,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/components/ui/empty"
+import { Skeleton } from "@/components/ui/skeleton"
+import { t } from "@/i18n/T"
interface ProjectListProps {
- projects: UserProjectInfo[];
- isLoading?: boolean;
+ projects: UserProjectInfo[]
+ isLoading?: boolean
}
export function ProjectList({ projects, isLoading }: ProjectListProps) {
- const navigate = useNavigate();
+ const navigate = useNavigate()
if (isLoading) {
return (
-
+
{[...Array(4)].map((_, i) => (
-
-
- );
+ )
}
if (projects.length === 0) {
return (
-
- );
+
+
+
+
+
+ {t("me.top_projects")}
+ 暂无项目
+
+
+ )
}
return (
-
+
{projects.map((project) => (
-
navigate(`/${project.name}`)}
>
-
-
- {project.display_name[0].toUpperCase()}
-
-
+
+
+
+ {project.display_name[0].toUpperCase()}
+
+
-
- {project.display_name}
-
- {!project.is_public && }
+
+ {project.display_name}
+
+ {!project.is_public && (
+
+ )}
-
@{project.name}
+
+ @{project.name}
+
+
+
+
+
+ {project.description || "暂无描述"}
+
+
+
+
+
+ {project.member_count} members
+
+
{new Date(project.created_at).toLocaleDateString()}
-
- {project.description || "No description provided"}
-
-
-
-
- {project.member_count} members
-
-
{new Date(project.created_at).toLocaleDateString()}
-
-
+
))}
- );
+ )
}
diff --git a/src/app/me/components/RepoList.tsx b/src/app/me/components/RepoList.tsx
index 002b92d..ad7daed 100644
--- a/src/app/me/components/RepoList.tsx
+++ b/src/app/me/components/RepoList.tsx
@@ -1,75 +1,121 @@
-import { useNavigate } from "react-router-dom";
-import { GitBranch, Lock } from "lucide-react";
-import type { UserRepoInfo } from "@/client/model";
-import { Skeleton } from "@/components/ui/skeleton";
+import { useNavigate } from "react-router-dom"
+import { GitBranch, Lock } from "lucide-react"
+import type { UserRepoInfo } from "@/client/model"
+import { Card } from "@/components/ui/card"
+import {
+ Empty,
+ EmptyDescription,
+ EmptyHeader,
+ EmptyMedia,
+ EmptyTitle,
+} from "@/components/ui/empty"
+import { Skeleton } from "@/components/ui/skeleton"
+import { t } from "@/i18n/T"
interface RepoListProps {
- repos: UserRepoInfo[];
- isLoading?: boolean;
+ repos: UserRepoInfo[]
+ isLoading?: boolean
}
export function RepoList({ repos, isLoading }: RepoListProps) {
- const navigate = useNavigate();
+ const navigate = useNavigate()
if (isLoading) {
return (
-
+
{[...Array(4)].map((_, i) => (
-
- );
+ )
}
if (repos.length === 0) {
return (
-
-
No repositories found
-
- );
+
+
+
+
+
+ {t("me.recent_repos")}
+ 暂无仓库
+
+
+ )
}
return (
-
+
{repos.map((repo) => (
-
navigate(`/${repo.project_name}/repo/${repo.repo_name}`)}
+ size="sm"
+ className="cursor-pointer transition-all hover:-translate-y-0.5 hover:ring-[var(--accent)]/20"
+ onClick={() =>
+ navigate(`/${repo.project_name}/repo/${repo.repo_name}`)
+ }
>
-
-
-
- {repo.repo_name}
-
- {repo.is_private && (
-
- )}
-
-
- {repo.description || "No description provided"}
-
-
-
-
-
{repo.default_branch}
+
+
+
+
+ {repo.repo_name}
+
+ {repo.is_private && (
+
+ )}
+
+
+ {repo.description || "暂无描述"}
+
+
+
+
+
+ {repo.default_branch}
+
+
+
+ Updated {new Date(repo.updated_at).toLocaleDateString()}
+
-
Updated {new Date(repo.updated_at).toLocaleDateString()}
-
+
))}
- );
+ )
}