Add bio field to user (#3346)

* add bio to users table

* lint

* add bio field to edit user admin page

* fix bio saving on new user

* simplify updating localstorage user

* linting

---------

Co-authored-by: timothycarambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield
2025-02-27 07:23:24 +08:00
committed by GitHub
parent 1aa2eed6c7
commit 6dd1fdc546
7 changed files with 74 additions and 8 deletions

View File

@@ -50,9 +50,9 @@ export default function AccountModal({ user, hideModal }) {
const { success, error } = await System.updateUser(data);
if (success) {
let storedUser = JSON.parse(localStorage.getItem(AUTH_USER));
if (storedUser) {
storedUser.username = data.username;
storedUser.bio = data.bio;
localStorage.setItem(AUTH_USER, JSON.stringify(storedUser));
}
showToast("Profile updated.", "success", { clear: true });
@@ -164,6 +164,20 @@ export default function AccountModal({ user, hideModal }) {
Password must be at least 8 characters long
</p>
</div>
<div>
<label
htmlFor="bio"
className="block mb-2 text-sm font-medium text-white"
>
Bio
</label>
<textarea
name="bio"
className="border-none bg-theme-settings-input-bg placeholder:text-theme-settings-input-placeholder border-gray-500 text-white text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5 min-h-[100px] resize-y"
placeholder="Tell us about yourself..."
defaultValue={user.bio}
/>
</div>
<div className="flex flex-row gap-x-8">
<ThemePreference />
<LanguagePreference />

View File

@@ -95,6 +95,21 @@ export default function NewUserModal({ closeModal }) {
Password must be at least 8 characters long
</p>
</div>
<div>
<label
htmlFor="bio"
className="block mb-2 text-sm font-medium text-white"
>
Bio
</label>
<textarea
name="bio"
className="border-none bg-theme-settings-input-bg w-full text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="User's bio"
autoComplete="off"
rows={3}
/>
</div>
<div>
<label
htmlFor="role"

View File

@@ -2,6 +2,7 @@ import React, { useState } from "react";
import { X } from "@phosphor-icons/react";
import Admin from "@/models/admin";
import { MessageLimitInput, RoleHintDisplay } from "../..";
import { AUTH_USER } from "@/utils/constants";
export default function EditUserModal({ currentUser, user, closeModal }) {
const [role, setRole] = useState(user.role);
@@ -27,7 +28,17 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
}
const { success, error } = await Admin.updateUser(user.id, data);
if (success) window.location.reload();
if (success) {
// Update local storage if we're editing our own user
if (currentUser && currentUser.id === user.id) {
currentUser.username = data.username;
currentUser.bio = data.bio;
currentUser.role = data.role;
localStorage.setItem(AUTH_USER, JSON.stringify(currentUser));
}
window.location.reload();
}
setError(error);
};
@@ -92,6 +103,22 @@ export default function EditUserModal({ currentUser, user, closeModal }) {
Password must be at least 8 characters long
</p>
</div>
<div>
<label
htmlFor="bio"
className="block mb-2 text-sm font-medium text-white"
>
Bio
</label>
<textarea
name="bio"
className="border-none bg-theme-settings-input-bg w-full text-white placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
placeholder="User's bio"
defaultValue={user.bio}
autoComplete="off"
rows={3}
/>
</div>
<div>
<label
htmlFor="role"

View File

@@ -1089,7 +1089,7 @@ function systemEndpoints(app) {
app.post("/system/user", [validatedRequest], async (request, response) => {
try {
const sessionUser = await userFromSession(request, response);
const { username, password } = reqBody(request);
const { username, password, bio } = reqBody(request);
const id = Number(sessionUser.id);
if (!id) {
@@ -1098,12 +1098,10 @@ function systemEndpoints(app) {
}
const updates = {};
if (username) {
if (username)
updates.username = User.validations.username(String(username));
}
if (password) {
updates.password = String(password);
}
if (password) updates.password = String(password);
if (bio) updates.bio = String(bio);
if (Object.keys(updates).length === 0) {
response

View File

@@ -22,6 +22,7 @@ const User = {
"role",
"suspended",
"dailyMessageLimit",
"bio",
],
validations: {
username: (newValue = "") => {
@@ -54,6 +55,12 @@ const User = {
}
return limit;
},
bio: (bio = "") => {
if (!bio || typeof bio !== "string") return "";
if (bio.length > 1000)
throw new Error("Bio cannot be longer than 1,000 characters");
return String(bio);
},
},
// validations for the above writable fields.
castColumnValue: function (key, value) {
@@ -77,6 +84,7 @@ const User = {
password,
role = "default",
dailyMessageLimit = null,
bio = "",
}) {
const passwordCheck = this.checkPasswordComplexity(password);
if (!passwordCheck.checkedOK) {
@@ -97,6 +105,7 @@ const User = {
username: this.validations.username(username),
password: hashedPassword,
role: this.validations.role(role),
bio: this.validations.bio(bio),
dailyMessageLimit:
this.validations.dailyMessageLimit(dailyMessageLimit),
},

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "bio" TEXT DEFAULT '';

View File

@@ -68,6 +68,7 @@ model users {
createdAt DateTime @default(now())
lastUpdatedAt DateTime @default(now())
dailyMessageLimit Int?
bio String? @default("")
workspace_chats workspace_chats[]
workspace_users workspace_users[]
embed_configs embed_configs[]