From db263c92a6fd838303f510da3080ddecfa14b81e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CChinapanda=E2=80=9D?= Date: Sun, 11 Jan 2026 21:10:47 +0700 Subject: [PATCH 1/2] feat: Add TypeScript types for API requests and responses; enhance tsconfig with stricter checks --- src/@types/api.ts | 68 ++++++ src/@types/database.ts | 104 +++++++++ src/@types/message.ts | 17 +- src/app/admin/posts/page.tsx | 70 +++--- src/app/admin/users/page.tsx | 25 +-- .../admin/chats/[chatId]/messages/route.ts | 2 +- src/app/api/admin/posts/[postId]/route.ts | 2 +- src/app/api/admin/posts/route.ts | 23 +- .../api/chat/[chatId]/participants/route.ts | 2 +- src/app/api/users/[userId]/unverify/route.ts | 2 +- src/app/api/users/[userId]/verify/route.ts | 2 +- src/app/api/users/check-username/route.ts | 21 +- src/app/feed/page.tsx | 58 ++--- src/app/inbox/page.tsx | 204 +++++++++--------- src/components/icons/GoogleIcon.tsx | 1 - src/components/icons/LineIcon.tsx | 1 - src/hooks/useApiMutation.tsx | 2 +- src/hooks/useChatMessages.tsx | 1 + src/services/profile/update.ts | 32 ++- src/services/supabase/posts.ts | 47 ++-- src/services/supabase/users.ts | 10 +- tsconfig.json | 5 + 22 files changed, 414 insertions(+), 285 deletions(-) create mode 100644 src/@types/api.ts create mode 100644 src/@types/database.ts diff --git a/src/@types/api.ts b/src/@types/api.ts new file mode 100644 index 0000000..dd1182a --- /dev/null +++ b/src/@types/api.ts @@ -0,0 +1,68 @@ +// API request/response types + +// Username check API +export type UsernameCheckRequest = { + username: string; + excludeUserId?: string; +}; + +export type UsernameCheckResponse = { + available: boolean; + message: string; +}; + +// User verification API +export type UserVerificationResponse = { + success: boolean; + message?: string; + error?: string; +}; + +// User status update API +export type UserStatusUpdateRequest = { + status: "ACTIVE" | "INACTIVE" | "SUSPENDED"; + reason?: string; +}; + +export type UserStatusUpdateResponse = { + success: boolean; + message?: string; + error?: string; +}; + +// Chat invite API +export type ChatInviteRequest = { + userIds: string[]; + chatName?: string; +}; + +export type ChatInviteResponse = { + success: boolean; + chatId?: string; + message?: string; + error?: string; +}; + +// Chat update name API +export type ChatUpdateNameRequest = { + name: string; +}; + +export type ChatUpdateNameResponse = { + success: boolean; + message?: string; + error?: string; +}; + +// Generic API error response +export type ApiErrorResponse = { + error: string; + message?: string; +}; + +// Generic API success response +export type ApiSuccessResponse = { + success: boolean; + data?: T; + message?: string; +}; diff --git a/src/@types/database.ts b/src/@types/database.ts new file mode 100644 index 0000000..25f05ec --- /dev/null +++ b/src/@types/database.ts @@ -0,0 +1,104 @@ +// Shared database types based on Prisma schema +// These types represent the structure returned from Supabase queries + +export type UserStatus = "ACTIVE" | "INACTIVE" | "SUSPENDED"; +export type UserRole = "USER" | "ADMIN"; +export type PostVisibility = "PUBLIC" | "MEMBERS_ONLY"; + +// Base database entity types +export type DatabaseUser = { + id: string; + profileImageKey: string | null; + email: string | null; + username: string; + name: string | null; + lastname: string | null; + fullName: string; + bio: string | null; + birthday: string | null; + relationShipStatus: string | null; + age: number | null; + gender: string | null; + height: number | null; + weight: number | null; + phone: string | null; + lineId: string | null; + status: UserStatus; + statusUpdatedAt: string | null; + role: UserRole; + isVerified: boolean; + verifiedAt: string | null; + verifiedBy: string | null; + createdAt: string; + updatedAt: string; +}; + +// User with minimal fields for display +export type UserDisplay = { + id: string; + fullName: string; + username: string; + profileImageKey: string | null; + isVerified?: boolean; + verifiedBy?: string | null; + verifiedByUsername?: string | null; + role?: UserRole; + status?: UserStatus; +}; + +// ChatParticipant with nested User +export type ChatParticipantWithUser = { + id: string; + chatId: string; + userId: string; + isAdmin: boolean; + lastReadAt: string | null; + User: UserDisplay | null; +}; + +// Message with User (imported from message types) +import type { MessageWithUser } from "./message"; + +// Chat with nested relations +export type ChatWithRelations = { + id: string; + isGroup: boolean; + name: string | null; + createdById: string; + isAdminVisible: boolean; + createdAt: string; + User?: UserDisplay | null; + ChatParticipant?: ChatParticipantWithUser[]; + latestMessage?: MessageWithUser; + hasUnread?: boolean; +}; + +// ChatParticipant with nested Chat +export type ChatParticipantWithChat = { + id: string; + chatId: string; + userId: string; + isAdmin: boolean; + lastReadAt: string | null; + Chat: ChatWithRelations; +}; + +// Post content structure +export type PostContent = { + text?: string; + [key: string]: unknown; +}; + +// Post with User +export type PostWithUser = { + id: string; + authorId: string; + content: PostContent | null; + imageUrl: string[] | null; + visibility: PostVisibility; + createdAt: string; + updatedAt: string; + User: UserDisplay | null; + PostLike?: Array<{ count?: number }>; + PostSave?: Array<{ count?: number }>; +}; diff --git a/src/@types/message.ts b/src/@types/message.ts index fc34b84..b1f704e 100644 --- a/src/@types/message.ts +++ b/src/@types/message.ts @@ -1,6 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-nocheck -import { Chat } from "../../generated/prisma"; +import type { ChatWithRelations } from "./database"; // Base types from database export type MessageInsert = { @@ -85,15 +83,7 @@ export interface ChatWithLatestMessage { chatId: string; userId: string; isAdmin: boolean; - Chat: Chat & { - User?: { - id: string; - fullName: string; - profileImageKey: string | null; - }; - latestMessage?: MessageWithUser; - hasUnread?: boolean; - }; + Chat: ChatWithRelations; } // Supabase presence state type @@ -101,3 +91,6 @@ export interface SupabasePresenceState { presence_ref: string; [key: string]: unknown; } + +// Re-export database types for convenience +export type { ChatParticipantWithChat, ChatParticipantWithUser } from "./database"; diff --git a/src/app/admin/posts/page.tsx b/src/app/admin/posts/page.tsx index 92ef91c..267ed33 100644 --- a/src/app/admin/posts/page.tsx +++ b/src/app/admin/posts/page.tsx @@ -2,6 +2,7 @@ import { TopNavbar, TOP_NAVBAR_HEIGHT_PX } from "@/components/element/TopNavbar"; import { BUCKET_NAME, supabase } from "@/client/supabase"; +import type { PostWithUser } from "@/@types/database"; import { Avatar, Badge, @@ -23,33 +24,12 @@ import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { notifications } from "@mantine/notifications"; -type Post = { - id: string; - content: { - text?: string; - [key: string]: unknown; - } | null; - imageUrl: string[] | null; - visibility: string; - createdAt: string; - User: { - id: string; - fullName: string; - username: string; - profileImageKey: string | null; - isVerified: boolean; - status: string; - }; - PostLike?: Array<{ count?: number }>; - PostSave?: Array<{ count?: number }>; -}; - export default function AdminPostsPage() { const router = useRouter(); const { status } = useSession(); - const [posts, setPosts] = useState([]); + const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); - const [selectedPost, setSelectedPost] = useState(null); + const [selectedPost, setSelectedPost] = useState(null); const [deleteModalOpened, setDeleteModalOpened] = useState(false); const [deleting, setDeleting] = useState(false); @@ -190,28 +170,30 @@ export default function AdminPostsPage() { {/* Author Info */} - - - - - - {post.User.fullName} + {post.User && ( + + + + + + {post.User.fullName} + + {post.User.isVerified && ( + + ยืนยันแล้ว + + )} + + + @{post.User.username} - {post.User.isVerified && ( - - ยืนยันแล้ว - - )} - - - @{post.User.username} - - - + + + )}