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
68 changes: 68 additions & 0 deletions src/@types/api.ts
Original file line number Diff line number Diff line change
@@ -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<T = unknown> = {
success: boolean;
data?: T;
message?: string;
};
104 changes: 104 additions & 0 deletions src/@types/database.ts
Original file line number Diff line number Diff line change
@@ -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 }>;
};
17 changes: 5 additions & 12 deletions src/@types/message.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -85,19 +83,14 @@ 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
export interface SupabasePresenceState {
presence_ref: string;
[key: string]: unknown;
}

// Re-export database types for convenience
export type { ChatParticipantWithChat, ChatParticipantWithUser } from "./database";
70 changes: 26 additions & 44 deletions src/app/admin/posts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<Post[]>([]);
const [posts, setPosts] = useState<PostWithUser[]>([]);
const [loading, setLoading] = useState(true);
const [selectedPost, setSelectedPost] = useState<Post | null>(null);
const [selectedPost, setSelectedPost] = useState<PostWithUser | null>(null);
const [deleteModalOpened, setDeleteModalOpened] = useState(false);
const [deleting, setDeleting] = useState(false);

Expand Down Expand Up @@ -190,28 +170,30 @@ export default function AdminPostsPage() {
<Stack gap="md">
{/* Author Info */}
<Group justify="space-between">
<Group gap="xs">
<Avatar
src={getProfileImageUrl(post.User.profileImageKey)}
size="sm"
radius="xl"
/>
<Stack gap={0}>
<Group gap="xs">
<Text size="sm" fw={600} c="white">
{post.User.fullName}
{post.User && (
<Group gap="xs">
<Avatar
src={getProfileImageUrl(post.User.profileImageKey)}
size="sm"
radius="xl"
/>
<Stack gap={0}>
<Group gap="xs">
<Text size="sm" fw={600} c="white">
{post.User.fullName}
</Text>
{post.User.isVerified && (
<Badge size="xs" color="blue">
ยืนยันแล้ว
</Badge>
)}
</Group>
<Text size="xs" c="dimmed">
@{post.User.username}
</Text>
{post.User.isVerified && (
<Badge size="xs" color="blue">
ยืนยันแล้ว
</Badge>
)}
</Group>
<Text size="xs" c="dimmed">
@{post.User.username}
</Text>
</Stack>
</Group>
</Stack>
</Group>
)}
<Button
size="xs"
color="red"
Expand Down
25 changes: 4 additions & 21 deletions src/app/admin/users/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client";

import type { DatabaseUser } from "@/@types/database";
import { BOTTOM_NAVBAR_HEIGHT_PX } from "@/components/element/BottomNavbar";
import { SearchInput } from "@/components/element/SearchInput";
import {
Expand All @@ -25,24 +26,6 @@ import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";

type User = {
id: string;
username: string;
name: string | null;
lastname: string | null;
fullName: string;
phone: string | null;
email: string | null;
status: "ACTIVE" | "INACTIVE" | "SUSPENDED";
statusUpdatedAt: string | null;
role: "USER" | "ADMIN";
isVerified: boolean;
verifiedAt: string | null;
verifiedBy: string | null;
createdAt: string;
updatedAt: string;
};

type StatusType = "verification" | "usage" | "account";

export default function AdminUsersPage() {
Expand All @@ -66,7 +49,7 @@ export default function AdminUsersPage() {
refetch: fetchUsers,
updateUserStatus,
} = useAdminUsers(searchQuery);
const users = (usersData as unknown as User[]) || [];
const users = (usersData as DatabaseUser[]) || [];
const loading = usersLoading || initialLoading;

// Clean up initial loading
Expand Down Expand Up @@ -144,7 +127,7 @@ export default function AdminUsersPage() {
return "";
};

const getCurrentStatusValue = (user: User) => {
const getCurrentStatusValue = (user: DatabaseUser) => {
if (statusType === "verification") {
return user.isVerified ? "verified" : "pending";
} else if (statusType === "usage") {
Expand All @@ -165,7 +148,7 @@ export default function AdminUsersPage() {
});
};

const getLastUpdatedDate = (user: User) => {
const getLastUpdatedDate = (user: DatabaseUser) => {
if (statusType === "verification") {
return formatDate(user.verifiedAt);
} else if (statusType === "usage") {
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/admin/chats/[chatId]/messages/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { NextRequest, NextResponse } from "next/server";
* Get all messages in any chat (admin can see all chat messages)
*/
export async function GET(
req: NextRequest,
_req: NextRequest,
{ params }: { params: Promise<{ chatId: string }> }
) {
try {
Expand Down
2 changes: 1 addition & 1 deletion src/app/api/admin/posts/[postId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { NextRequest, NextResponse } from "next/server";
* Admin can delete posts
*/
export async function DELETE(
req: NextRequest,
_req: NextRequest,
{ params }: { params: Promise<{ postId: string }> }
) {
try {
Expand Down
Loading