diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 4c008a4..3c38700 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -148,6 +148,7 @@ export const i18nData = { "Create Tag": "Create Tag", "Search Tags": "Search Tags", "Edit Resource": "Edit Resource", + "Change Bio": "Change Bio", } }, "zh-CN": { @@ -299,6 +300,7 @@ export const i18nData = { "Create Tag": "创建标签", "Search Tags": "搜索标签", "Edit Resource": "编辑资源", + "Change Bio": "更改个人简介", } }, "zh-TW": { @@ -450,6 +452,7 @@ export const i18nData = { "Create Tag": "創建標籤", "Search Tags": "搜尋標籤", "Edit Resource": "編輯資源", + "Change Bio": "更改個人簡介", } } } diff --git a/frontend/src/network/models.ts b/frontend/src/network/models.ts index c6a33f1..85b6451 100644 --- a/frontend/src/network/models.ts +++ b/frontend/src/network/models.ts @@ -7,6 +7,7 @@ export interface User { can_upload: boolean; uploads_count: number; comments_count: number; + bio: string; } export interface UserWithToken extends User { diff --git a/frontend/src/network/network.ts b/frontend/src/network/network.ts index 8045a89..2b0d806 100644 --- a/frontend/src/network/network.ts +++ b/frontend/src/network/network.ts @@ -172,6 +172,21 @@ class Network { } } + async changeBio(bio: string): Promise> { + try { + const response = await axios.postForm(`${this.apiBaseUrl}/user/bio`, { + bio + }) + return response.data + } catch (e: any) { + console.error(e) + return { + success: false, + message: e.toString(), + } + } + } + getUserAvatar(user: User): string { return this.baseUrl + user.avatar_path } diff --git a/frontend/src/pages/manage_me_page.tsx b/frontend/src/pages/manage_me_page.tsx index 77db5a2..6f05957 100644 --- a/frontend/src/pages/manage_me_page.tsx +++ b/frontend/src/pages/manage_me_page.tsx @@ -7,6 +7,7 @@ import { MdOutlineAccountCircle, MdLockOutline, MdOutlineEditNote } from "react- import Button from "../components/button"; import showToast from "../components/toast"; import { useNavigator } from "../components/navigator"; +import Input from "../components/input.tsx"; export function ManageMePage() { const { t } = useTranslation(); @@ -19,6 +20,7 @@ export function ManageMePage() { + ; } @@ -92,7 +94,7 @@ function ChangeAvatarDialog() {
- + {"avatar"}
@@ -299,4 +301,69 @@ function ChangePasswordDialog() { ; +} + +function ChangeBioDialog() { + const [bio, setBio] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const { t } = useTranslation(); + + const handleSubmit = async () => { + if (!bio.trim()) { + setError(t("Bio cannot be empty")); + return; + } else if (bio.length > 200) { + setError(t("Bio cannot be longer than 200 characters")); + return; + } + setIsLoading(true); + const res = await network.changeBio(bio); + setIsLoading(false); + if (!res.success) { + setError(res.message); + } else { + app.user = res.data!; + showToast({ + message: t("Bio changed successfully"), + type: "success", + }); + const dialog = document.getElementById("change_bio_dialog") as HTMLDialogElement; + if (dialog) { + dialog.close(); + } + setBio(""); + setError(null); + } + }; + + return <> + } title={t("Change Bio")} onClick={() => { + const dialog = document.getElementById("change_bio_dialog") as HTMLDialogElement; + if (dialog) { + dialog.showModal(); + } + }} /> + +
+

{t("Change Bio")}

+ setBio(e.target.value)} label={"bio"} /> + {error && } +
+
+ +
+ +
+
+
+ ; } \ No newline at end of file diff --git a/frontend/src/pages/user_page.tsx b/frontend/src/pages/user_page.tsx index b524b05..4d01968 100644 --- a/frontend/src/pages/user_page.tsx +++ b/frontend/src/pages/user_page.tsx @@ -63,13 +63,16 @@ function UserCard({ user }: { user: User }) {

{user.username}

-

- {user.uploads_count} - Resources - - {user.comments_count} - Comments -

+ {user.bio.trim() !== "" + ?

{user.bio.trim()}

+ :

+ {user.uploads_count} + Resources + + {user.comments_count} + Comments +

+ }
} diff --git a/server/api/user.go b/server/api/user.go index f86ba5a..a5c2dd0 100644 --- a/server/api/user.go +++ b/server/api/user.go @@ -298,6 +298,26 @@ func handleChangeUsername(c fiber.Ctx) error { }) } +func handleSetUserBio(c fiber.Ctx) error { + uid, ok := c.Locals("uid").(uint) + if !ok { + return model.NewUnAuthorizedError("Unauthorized") + } + bio := c.FormValue("bio") + if bio == "" { + return model.NewRequestError("Bio is required") + } + user, err := service.SetUserBio(uid, bio) + if err != nil { + return err + } + return c.Status(fiber.StatusOK).JSON(model.Response[model.UserView]{ + Success: true, + Data: user, + Message: "Bio updated successfully", + }) +} + func AddUserRoutes(r fiber.Router) { u := r.Group("user") u.Post("/register", handleUserRegister) @@ -312,4 +332,5 @@ func AddUserRoutes(r fiber.Router) { u.Post("/delete", handleDeleteUser) u.Get("/info", handleGetUserInfo) u.Post("/username", handleChangeUsername) + u.Post("/bio", handleSetUserBio) } diff --git a/server/model/user.go b/server/model/user.go index 5c5068c..de80ddf 100644 --- a/server/model/user.go +++ b/server/model/user.go @@ -16,6 +16,7 @@ type User struct { UploadsCount int CommentsCount int Resources []Resource `gorm:"foreignKey:UserID"` + Bio string } type UserView struct { @@ -27,6 +28,7 @@ type UserView struct { CanUpload bool `json:"can_upload"` UploadsCount int `json:"uploads_count"` CommentsCount int `json:"comments_count"` + Bio string `json:"bio"` } type UserViewWithToken struct { @@ -44,6 +46,7 @@ func (u User) ToView() UserView { CanUpload: u.CanUpload || u.IsAdmin, UploadsCount: u.UploadsCount, CommentsCount: u.CommentsCount, + Bio: u.Bio, } } diff --git a/server/service/user.go b/server/service/user.go index c84ab01..a5988f3 100644 --- a/server/service/user.go +++ b/server/service/user.go @@ -292,3 +292,18 @@ func ChangeUsername(uid uint, newUsername string) (model.UserView, error) { } return user.ToView(), nil } + +func SetUserBio(uid uint, bio string) (model.UserView, error) { + if len(bio) > 200 { + return model.UserView{}, model.NewRequestError("Bio must be less than 200 characters") + } + user, err := dao.GetUserByID(uid) + if err != nil { + return model.UserView{}, err + } + user.Bio = bio + if err := dao.UpdateUser(user); err != nil { + return model.UserView{}, err + } + return user.ToView(), nil +}