fix: make dashboard UI consistent and compact on mobile

This commit is contained in:
Thomas Durieux committed 2026-10-07 14:54:49 +00:00
1 parent adc9dbd439
commit 282efdc16e
52 files changed
+2177 -712

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
import { createHash } from "crypto";
import AnonymousError from "../../core/AnonymousError";
import User from "../../core/User";
import UserModel from "../../core/model/users/users.model";
export function projectNameKey(type: string, id: string): string {
return createHash("sha256").update(`${type}:${id}`).digest("hex");
}
// Names belong to the user's dashboard, rather than the shared artifact.
export async function saveProjectName(user: User, input: unknown): Promise<string> {
const body = input as { type?: unknown; id?: unknown; name?: unknown } | null;
if (!body || typeof body.type !== "string" || !["repo", "pr", "gist"].includes(body.type)
|| typeof body.id !== "string" || !body.id || body.id.length > 250
|| typeof body.name !== "string" || body.name.trim().length > 100
|| Array.from(body.name).some(character => {
const code = character.charCodeAt(0);
return code < 32 || code === 127;
})) {
throw new AnonymousError("invalid_project_name", { httpStatus: 400 });
}
const type = String(body.type);
const records = type === "repo" ? await user.getRepositories()
: type === "pr" ? await user.getPullRequests() : await user.getGists();
const field = type === "repo" ? "repoId" : type === "pr" ? "pullRequestId" : "gistId";
if (!records.some(record => (record as unknown as Record<string, unknown>)[field] === body.id)) {
throw new AnonymousError("not_authorized", { httpStatus: 403 });
}
const key = projectNameKey(type, body.id);
const name = body.name.trim();
const path = `projectNames.${key}`;
await UserModel.updateOne({ _id: user.model._id }, name
? { $set: { [path]: name } } : { $unset: { [path]: "" } });
if (name) user.model.set(path, name);
else user.model.projectNames?.delete(key);
return name;
}
+14 -2
View File
@@ -13,6 +13,7 @@ import FileModel from "../../core/model/files/files.model";
import { isConnected } from "../database";
import { octokit } from "../../core/GitHubUtils";
import { createLogger, serializeError } from "../../core/logger";
import { projectNameKey, saveProjectName } from "./project-names";
const logger = createLogger("user");
@@ -124,6 +125,15 @@ router.get("/quota", async (req, res) => {
}
});
router.post("/project-name", async (req, res) => {
try {
const user = await getUser(req);
res.json({ name: await saveProjectName(user, req.body) });
} catch (error) {
handleError(error, res, req);
}
});
router.get("/default", async (req, res) => {
try {
const user = await getUser(req);
@@ -214,6 +224,7 @@ router.delete("/", async (req, res) => {
externalIDs: "",
photo: "",
default: "",
projectNames: "",
},
}
).exec();
@@ -239,6 +250,7 @@ router.get(
(await user.getRepositories()).map((x) => {
const json = x.toJSON() as Record<string, unknown>;
json.role = x.owner.id === user.model.id ? "owner" : "coauthor";
json.projectName = user.model.projectNames?.get(projectNameKey("repo", x.repoId));
return json;
})
);
@@ -254,7 +266,7 @@ router.get(
const user = await getUser(req);
res.json(
(await user.getGists()).map((x) => {
return x.toJSON();
return { ...x.toJSON(), projectName: user.model.projectNames?.get(projectNameKey("gist", x.gistId)) };
})
);
} catch (error) {
@@ -269,7 +281,7 @@ router.get(
const user = await getUser(req);
res.json(
(await user.getPullRequests()).map((x) => {
return x.toJSON();
return { ...x.toJSON(), projectName: user.model.projectNames?.get(projectNameKey("pr", x.pullRequestId)) };
})
);
} catch (error) {