From 3b7d52a7a80ed2ec11a5c30223124696f865c969 Mon Sep 17 00:00:00 2001 From: nyne Date: Wed, 14 May 2025 16:48:52 +0800 Subject: [PATCH] user details page --- frontend/package-lock.json | 7 + frontend/src/app.tsx | 3 +- frontend/src/components/navigator.tsx | 6 +- frontend/src/components/popup.tsx | 84 +++++----- frontend/src/network/models.ts | 8 + frontend/src/network/network.ts | 63 +++++++- frontend/src/pages/resource_details_page.tsx | 3 + frontend/src/pages/tagged_resources_page.tsx | 4 +- frontend/src/pages/user_page.tsx | 153 +++++++++++++++++++ go.mod | 2 +- server/api/comment.go | 23 ++- server/api/resource.go | 29 ++++ server/api/user.go | 17 +++ server/dao/comment.go | 21 ++- server/dao/resource.go | 27 +++- server/model/comment.go | 21 ++- server/service/comment.go | 16 +- server/service/image.go | 7 +- server/service/resource.go | 12 ++ server/service/user.go | 8 + 20 files changed, 450 insertions(+), 64 deletions(-) create mode 100644 frontend/src/pages/user_page.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4dfb9bc..e4ae00f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -5891,6 +5891,13 @@ "typescript": ">=4.8.4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/turbo-stream": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 19be6b8..c01f753 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -6,9 +6,9 @@ import HomePage from "./pages/home_page.tsx"; import PublishPage from "./pages/publish_page.tsx"; import SearchPage from "./pages/search_page.tsx"; import ResourcePage from "./pages/resource_details_page.tsx"; -import "./i18n.ts" import ManagePage from "./pages/manage_page.tsx"; import TaggedResourcesPage from "./pages/tagged_resources_page.tsx"; +import UserPage from "./pages/user_page.tsx"; export default function App() { return ( @@ -23,6 +23,7 @@ export default function App() { }/> }/> }/> + }/> diff --git a/frontend/src/components/navigator.tsx b/frontend/src/components/navigator.tsx index c189fa8..d8e9138 100644 --- a/frontend/src/components/navigator.tsx +++ b/frontend/src/components/navigator.tsx @@ -74,7 +74,11 @@ function UserButton() { id={"navi_dropdown_menu"} tabIndex={0} className="menu dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow"> -
  • navigate(`/user/${app.user?.id}`)}>{t("My Profile")}
  • +
  • { + navigate(`/user/${app.user?.username}`); + const menu = document.getElementById("navi_dropdown_menu") as HTMLUListElement; + menu.blur(); + }}>{t("My Profile")}
  • { navigate(`/publish`); const menu = document.getElementById("navi_dropdown_menu") as HTMLUListElement; diff --git a/frontend/src/components/popup.tsx b/frontend/src/components/popup.tsx index 4af50dd..cdd30a9 100644 --- a/frontend/src/components/popup.tsx +++ b/frontend/src/components/popup.tsx @@ -2,60 +2,60 @@ import React from "react"; import { createRoot } from "react-dom/client"; export default function showPopup(content: React.ReactNode, element: HTMLElement) { - const eRect = element.getBoundingClientRect(); + const eRect = element.getBoundingClientRect(); - const div = document.createElement("div"); - div.style.position = "fixed"; - if (eRect.x > window.innerWidth / 2) { - div.style.right = `${window.innerWidth - eRect.x}px`; - } else { - div.style.left = `${eRect.x}px`; - } - if (eRect.y > window.innerHeight / 2) { - div.style.bottom = `${window.innerHeight - eRect.y}px`; - } else { - div.style.top = `${eRect.y}px`; - } + const div = document.createElement("div"); + div.style.position = "fixed"; + if (eRect.x > window.innerWidth / 2) { + div.style.right = `${window.innerWidth - eRect.x}px`; + } else { + div.style.left = `${eRect.x}px`; + } + if (eRect.y > window.innerHeight / 2) { + div.style.bottom = `${window.innerHeight - eRect.y}px`; + } else { + div.style.top = `${eRect.y}px`; + } - div.style.zIndex = "9999"; - div.className = "animate-appearance-in"; + div.style.zIndex = "9999"; + div.className = "animate-appearance-in"; - document.body.appendChild(div); + document.body.appendChild(div); - const mask = document.createElement("div"); + const mask = document.createElement("div"); - const close = () => { - console.log("close popup"); - document.body.removeChild(div); - document.body.removeChild(mask); - }; + const close = () => { + console.log("close popup"); + document.body.removeChild(div); + document.body.removeChild(mask); + }; - mask.style.position = "fixed"; - mask.style.top = "0"; - mask.style.left = "0"; - mask.style.width = "100%"; - mask.style.height = "100%"; - mask.style.zIndex = "9998"; - mask.onclick = close; - document.body.appendChild(mask); + mask.style.position = "fixed"; + mask.style.top = "0"; + mask.style.left = "0"; + mask.style.width = "100%"; + mask.style.height = "100%"; + mask.style.zIndex = "9998"; + mask.onclick = close; + document.body.appendChild(mask); - createRoot(div).render( - {content} - ) + createRoot(div).render( + {content} + ) } -const context = React.createContext<() => void>(() => {}); +const context = React.createContext<() => void>(() => { }); export function useClosePopup() { - return React.useContext(context); + return React.useContext(context); } export function PopupMenuItem({ children, onClick }: { children: React.ReactNode, onClick: () => void }) { - const close = useClosePopup(); - return
  • { - close(); - onClick(); - }}> - {children} -
  • + const close = useClosePopup(); + return
  • { + close(); + onClick(); + }}> + {children} +
  • } \ No newline at end of file diff --git a/frontend/src/network/models.ts b/frontend/src/network/models.ts index 92677a0..40ee67c 100644 --- a/frontend/src/network/models.ts +++ b/frontend/src/network/models.ts @@ -100,3 +100,11 @@ export interface Comment { created_at: string; user: User; } + +export interface CommentWithResource { + id: number; + content: string; + created_at: string; + user: User; + resource: Resource; +} diff --git a/frontend/src/network/network.ts b/frontend/src/network/network.ts index 296ed87..83ba127 100644 --- a/frontend/src/network/network.ts +++ b/frontend/src/network/network.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import {app} from "../app.ts"; +import { app } from "../app.ts"; import { CreateResourceParams, RFile, @@ -12,7 +12,8 @@ import { UploadingFile, User, UserWithToken, - Comment + Comment, + CommentWithResource } from "./models.ts"; class Network { @@ -88,6 +89,23 @@ class Network { } } + async getUserInfo(username: string): Promise> { + try { + const response = await axios.get(`${this.apiBaseUrl}/user/info`, { + params: { + username + } + }) + return response.data + } catch (e: any) { + console.error(e) + return { + success: false, + message: e.toString(), + } + } + } + async changePassword(oldPassword: string, newPassword: string): Promise> { try { const response = await axios.postForm(`${this.apiBaseUrl}/user/password`, { @@ -177,7 +195,7 @@ class Network { async searchUsers(username: string, page: number): Promise> { try { const response = await axios.get(`${this.apiBaseUrl}/user/search`, { - params: { + params: { username, page } @@ -325,6 +343,23 @@ class Network { } } + async getResourcesByUser(username: string, page: number): Promise> { + try { + const response = await axios.get(`${this.apiBaseUrl}/resource/user/${username}`, { + params: { + page + } + }) + return response.data + } catch (e: any) { + console.error(e) + return { + success: false, + message: e.toString(), + } + } + } + async searchResources(keyword: string, page: number): Promise> { try { const response = await axios.get(`${this.apiBaseUrl}/resource/search`, { @@ -357,7 +392,7 @@ class Network { } async createS3Storage(name: string, endPoint: string, accessKeyID: string, - secretAccessKey: string, bucketName: string, maxSizeInMB: number): Promise> { + secretAccessKey: string, bucketName: string, maxSizeInMB: number): Promise> { try { const response = await axios.post(`${this.apiBaseUrl}/storage/s3`, { name, @@ -420,8 +455,8 @@ class Network { } } - async initFileUpload(filename: string, description: string, fileSize: number, - resourceId: number, storageId: number): Promise> { + async initFileUpload(filename: string, description: string, fileSize: number, + resourceId: number, storageId: number): Promise> { try { const response = await axios.post(`${this.apiBaseUrl}/files/upload/init`, { filename, @@ -487,8 +522,8 @@ class Network { } } - async createRedirectFile(filename: string, description: string, - resourceId: number, redirectUrl: string): Promise> { + async createRedirectFile(filename: string, description: string, + resourceId: number, redirectUrl: string): Promise> { try { const response = await axios.post(`${this.apiBaseUrl}/files/redirect`, { filename, @@ -573,6 +608,18 @@ class Network { return { success: false, message: e.toString() }; } } + + async listCommentsByUser(username: string, page: number = 1): Promise> { + try { + const response = await axios.get(`${this.apiBaseUrl}/comments/user/${username}`, { + params: { page } + }); + return response.data; + } catch (e: any) { + console.error(e); + return { success: false, message: e.toString() }; + } + } } export const network = new Network(); diff --git a/frontend/src/pages/resource_details_page.tsx b/frontend/src/pages/resource_details_page.tsx index e41ae27..ad9eeb2 100644 --- a/frontend/src/pages/resource_details_page.tsx +++ b/frontend/src/pages/resource_details_page.tsx @@ -72,6 +72,9 @@ export default function ResourcePage() { }) }