From c0bb76580db097975db228e38a4110ba26bf377c Mon Sep 17 00:00:00 2001 From: sam Date: Mon, 25 Nov 2024 17:35:24 +0100 Subject: [PATCH] even more frontend stuff --- .../Controllers/MembersController.cs | 6 +- .../Controllers/UsersController.cs | 11 +- .../Services/MemberRendererService.cs | 8 +- .../Services/UserRendererService.cs | 3 +- .../src/lib/components/Avatar.svelte | 11 +- .../src/lib/components/Paginator.svelte | 35 ++++++ .../lib/components/editor/AvatarEditor.svelte | 7 +- .../lib/components/editor/BioEditor.svelte | 26 ++++ .../components/editor/NoscriptWarning.svelte | 12 ++ .../editor/ShortNoscriptWarning.svelte | 11 ++ .../lib/components/editor/SidEditor.svelte | 30 +++++ .../components/profile/user/MemberCard.svelte | 8 +- .../src/lib/i18n/locales/en.json | 30 ++++- .../src/routes/@[username]/+page.server.ts | 11 +- .../src/routes/@[username]/+page.svelte | 30 ++--- .../src/routes/@[username]/Paginator.svelte | 31 ----- .../@[username]/[memberName]/+page.server.ts | 15 +++ .../@[username]/[memberName]/+page.svelte | 40 ++++++ .../routes/settings/members/+page.server.ts | 18 +++ .../src/routes/settings/members/+page.svelte | 48 ++++++++ .../settings/members/[id]/+layout.server.ts | 22 ++++ .../settings/members/[id]/+layout@.svelte | 65 ++++++++++ .../settings/members/[id]/+page.server.ts | 82 +++++++++++++ .../routes/settings/members/[id]/+page.svelte | 115 ++++++++++++++++++ .../settings/members/new/+page.server.ts | 34 ++++++ .../routes/settings/members/new/+page.svelte | 22 ++++ .../routes/settings/profile/+layout.server.ts | 7 ++ .../routes/settings/profile/+layout.svelte | 42 ------- .../routes/settings/profile/+layout@.svelte | 65 ++++++++++ .../routes/settings/profile/+page.server.ts | 20 ++- .../src/routes/settings/profile/+page.svelte | 76 ++++++------ .../routes/settings/profile/bio/+page.svelte | 31 +---- package.json | 2 +- 33 files changed, 796 insertions(+), 178 deletions(-) create mode 100644 Foxnouns.Frontend/src/lib/components/Paginator.svelte create mode 100644 Foxnouns.Frontend/src/lib/components/editor/BioEditor.svelte create mode 100644 Foxnouns.Frontend/src/lib/components/editor/NoscriptWarning.svelte create mode 100644 Foxnouns.Frontend/src/lib/components/editor/ShortNoscriptWarning.svelte create mode 100644 Foxnouns.Frontend/src/lib/components/editor/SidEditor.svelte delete mode 100644 Foxnouns.Frontend/src/routes/@[username]/Paginator.svelte create mode 100644 Foxnouns.Frontend/src/routes/@[username]/[memberName]/+page.server.ts create mode 100644 Foxnouns.Frontend/src/routes/@[username]/[memberName]/+page.svelte create mode 100644 Foxnouns.Frontend/src/routes/settings/members/+page.server.ts create mode 100644 Foxnouns.Frontend/src/routes/settings/members/+page.svelte create mode 100644 Foxnouns.Frontend/src/routes/settings/members/[id]/+layout.server.ts create mode 100644 Foxnouns.Frontend/src/routes/settings/members/[id]/+layout@.svelte create mode 100644 Foxnouns.Frontend/src/routes/settings/members/[id]/+page.server.ts create mode 100644 Foxnouns.Frontend/src/routes/settings/members/[id]/+page.svelte create mode 100644 Foxnouns.Frontend/src/routes/settings/members/new/+page.server.ts create mode 100644 Foxnouns.Frontend/src/routes/settings/members/new/+page.svelte create mode 100644 Foxnouns.Frontend/src/routes/settings/profile/+layout.server.ts delete mode 100644 Foxnouns.Frontend/src/routes/settings/profile/+layout.svelte create mode 100644 Foxnouns.Frontend/src/routes/settings/profile/+layout@.svelte diff --git a/Foxnouns.Backend/Controllers/MembersController.cs b/Foxnouns.Backend/Controllers/MembersController.cs index ba9cf28..968b571 100644 --- a/Foxnouns.Backend/Controllers/MembersController.cs +++ b/Foxnouns.Backend/Controllers/MembersController.cs @@ -303,8 +303,8 @@ public class MembersController( .SetProperty(u => u.LastActive, clock.GetCurrentInstant()) ); - // Re-fetch member to fetch the new sid - var updatedMember = await db.ResolveMemberAsync(CurrentUser!.Id, memberRef); - return Ok(memberRenderer.RenderMember(updatedMember, CurrentToken)); + // Fetch the new sid then pass that to RenderMember + var newSid = await db.Members.Where(m => m.Id == member.Id).Select(m => m.Sid).FirstAsync(); + return Ok(memberRenderer.RenderMember(member, CurrentToken, newSid)); } } diff --git a/Foxnouns.Backend/Controllers/UsersController.cs b/Foxnouns.Backend/Controllers/UsersController.cs index 33c38d6..2693bef 100644 --- a/Foxnouns.Backend/Controllers/UsersController.cs +++ b/Foxnouns.Backend/Controllers/UsersController.cs @@ -346,13 +346,20 @@ public class UsersController( .SetProperty(u => u.LastActive, clock.GetCurrentInstant()) ); + // Get the user's new sid + var newSid = await db + .Users.Where(u => u.Id == CurrentUser.Id) + .Select(u => u.Sid) + .FirstAsync(); + var user = await db.ResolveUserAsync(CurrentUser.Id); return Ok( await userRenderer.RenderUserAsync( - user, + CurrentUser, CurrentUser, CurrentToken, - renderMembers: false + renderMembers: false, + overrideSid: newSid ) ); } diff --git a/Foxnouns.Backend/Services/MemberRendererService.cs b/Foxnouns.Backend/Services/MemberRendererService.cs index 7d7cac0..717f06c 100644 --- a/Foxnouns.Backend/Services/MemberRendererService.cs +++ b/Foxnouns.Backend/Services/MemberRendererService.cs @@ -24,13 +24,17 @@ public class MemberRendererService(DatabaseContext db, Config config) return members.Select(m => RenderPartialMember(m, renderUnlisted)); } - public MemberResponse RenderMember(Member member, Token? token = null) + public MemberResponse RenderMember( + Member member, + Token? token = null, + string? overrideSid = null + ) { var renderUnlisted = token?.UserId == member.UserId && token.HasScope("user.read_hidden"); return new MemberResponse( member.Id, - member.Sid, + overrideSid ?? member.Sid, member.Name, member.DisplayName ?? member.Name, member.Bio, diff --git a/Foxnouns.Backend/Services/UserRendererService.cs b/Foxnouns.Backend/Services/UserRendererService.cs index ceeba94..2911dd3 100644 --- a/Foxnouns.Backend/Services/UserRendererService.cs +++ b/Foxnouns.Backend/Services/UserRendererService.cs @@ -20,6 +20,7 @@ public class UserRendererService( Token? token = null, bool renderMembers = true, bool renderAuthMethods = false, + string? overrideSid = null, CancellationToken ct = default ) { @@ -59,7 +60,7 @@ public class UserRendererService( return new UserResponse( user.Id, - user.Sid, + overrideSid ?? user.Sid, user.Username, user.DisplayName, user.Bio, diff --git a/Foxnouns.Frontend/src/lib/components/Avatar.svelte b/Foxnouns.Frontend/src/lib/components/Avatar.svelte index 31f8355..99dd8f3 100644 --- a/Foxnouns.Frontend/src/lib/components/Avatar.svelte +++ b/Foxnouns.Frontend/src/lib/components/Avatar.svelte @@ -1,22 +1,23 @@ diff --git a/Foxnouns.Frontend/src/lib/components/Paginator.svelte b/Foxnouns.Frontend/src/lib/components/Paginator.svelte new file mode 100644 index 0000000..07cbd8d --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/Paginator.svelte @@ -0,0 +1,35 @@ + + +{#if pageCount > 1} +
+ + + + + + + + {#each new Array(pageCount) as _, page} + + {page + 1} + + {/each} + + + + + + + +
+{/if} diff --git a/Foxnouns.Frontend/src/lib/components/editor/AvatarEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/AvatarEditor.svelte index 5998c9d..05a9a62 100644 --- a/Foxnouns.Frontend/src/lib/components/editor/AvatarEditor.svelte +++ b/Foxnouns.Frontend/src/lib/components/editor/AvatarEditor.svelte @@ -4,14 +4,15 @@ import { Icon, InputGroup } from "@sveltestrap/sveltestrap"; import { encode } from "base64-arraybuffer"; import prettyBytes from "pretty-bytes"; + import ShortNoscriptWarning from "./ShortNoscriptWarning.svelte"; type Props = { current: string | null; alt: string; - onclick: (avatar: string) => Promise; + update: (avatar: string) => Promise; updated: boolean; }; - let { current, alt, onclick, updated }: Props = $props(); + let { current, alt, update: onclick, updated }: Props = $props(); const MAX_AVATAR_BYTES = 1_000_000; @@ -59,6 +60,8 @@ + + {#if updated}

diff --git a/Foxnouns.Frontend/src/lib/components/editor/BioEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/BioEditor.svelte new file mode 100644 index 0000000..8c7e744 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/editor/BioEditor.svelte @@ -0,0 +1,26 @@ + + + + + +

+ {$t("edit-profile.bio-length-hint", { + length: value.length, + maxLength, + })} +

+ +{#if value !== ""} +
+
{$t("edit-profile.preview")}
+
{@html renderMarkdown(value)}
+
+{/if} diff --git a/Foxnouns.Frontend/src/lib/components/editor/NoscriptWarning.svelte b/Foxnouns.Frontend/src/lib/components/editor/NoscriptWarning.svelte new file mode 100644 index 0000000..4bddf68 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/editor/NoscriptWarning.svelte @@ -0,0 +1,12 @@ + + + diff --git a/Foxnouns.Frontend/src/lib/components/editor/ShortNoscriptWarning.svelte b/Foxnouns.Frontend/src/lib/components/editor/ShortNoscriptWarning.svelte new file mode 100644 index 0000000..fb474db --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/editor/ShortNoscriptWarning.svelte @@ -0,0 +1,11 @@ + + + diff --git a/Foxnouns.Frontend/src/lib/components/editor/SidEditor.svelte b/Foxnouns.Frontend/src/lib/components/editor/SidEditor.svelte new file mode 100644 index 0000000..1547ff2 --- /dev/null +++ b/Foxnouns.Frontend/src/lib/components/editor/SidEditor.svelte @@ -0,0 +1,30 @@ + + +{$t("edit-profile.sid-current")} {sid} + + + + + +

+ + {$t("edit-profile.sid-hint")} +

diff --git a/Foxnouns.Frontend/src/lib/components/profile/user/MemberCard.svelte b/Foxnouns.Frontend/src/lib/components/profile/user/MemberCard.svelte index 7d1bdc7..d77de02 100644 --- a/Foxnouns.Frontend/src/lib/components/profile/user/MemberCard.svelte +++ b/Foxnouns.Frontend/src/lib/components/profile/user/MemberCard.svelte @@ -35,11 +35,15 @@
- +

- {member.name} + {member.display_name} {#if pronouns}
diff --git a/Foxnouns.Frontend/src/lib/i18n/locales/en.json b/Foxnouns.Frontend/src/lib/i18n/locales/en.json index 5c026f9..7f1e377 100644 --- a/Foxnouns.Frontend/src/lib/i18n/locales/en.json +++ b/Foxnouns.Frontend/src/lib/i18n/locales/en.json @@ -6,13 +6,14 @@ }, "avatar-tooltip": "Avatar for {{name}}", "profile": { - "edit-member-profile-notice": "You are currently viewing the public profile of {memberName}.", + "edit-member-profile-notice": "You are currently viewing the public profile of {{memberName}}.", "edit-user-profile-notice": "You are currently viewing your public profile.", "edit-profile-link": "Edit profile", "names-header": "Names", "pronouns-header": "Pronouns", "default-members-header": "Members", - "create-member-button": "Create member" + "create-member-button": "Create member", + "back-to-user": "Back to {{name}}" }, "title": { "log-in": "Log in", @@ -59,7 +60,10 @@ "validation-disallowed-value-2": "Allowed values are", "validation-reason": "Reason", "validation-generic": "The value you entered is not allowed here. Reason", - "extra-info-header": "Extra error information" + "extra-info-header": "Extra error information", + "noscript-title": "This page requires JavaScript", + "noscript-info": "This page requires JavaScript to function correctly. Some buttons may not work, or the page may not work at all.", + "noscript-short": "Requires JavaScript" }, "settings": { "general-information-tab": "General information", @@ -86,7 +90,9 @@ "log-out-hint": "Use this button to log out on this device only.", "log-out-button": "Log out", "avatar": "Avatar", - "username-update-success": "Successfully changed your username!" + "username-update-success": "Successfully changed your username!", + "create-member-title": "Create a new member", + "create-member-name-label": "Member name" }, "yes": "Yes", "no": "No", @@ -112,7 +118,19 @@ "profile-options-header": "Profile options", "bio-tab": "Bio", "saved-changes": "Successfully saved changes!", - "bio-length-hint": "Using {{length}}/{{maxLength}} characters" + "bio-length-hint": "Using {{length}}/{{maxLength}} characters", + "preview": "Preview", + "fields-tab": "Fields", + "flags-links-tab": "Flags & links", + "back-to-settings-tab": "Back to settings", + "member-header": "Editing member {{name}}", + "username": "Username", + "change-username-info": "As changing your username will also change all of your members' links, you can only change it in your account settings.", + "change-username-link": "Go to settings", + "member-name": "Name", + "change-member-name": "Change name", + "display-name": "Display name" }, - "save-changes": "Save changes" + "save-changes": "Save changes", + "change": "Change" } diff --git a/Foxnouns.Frontend/src/routes/@[username]/+page.server.ts b/Foxnouns.Frontend/src/routes/@[username]/+page.server.ts index 6c582bc..99e7359 100644 --- a/Foxnouns.Frontend/src/routes/@[username]/+page.server.ts +++ b/Foxnouns.Frontend/src/routes/@[username]/+page.server.ts @@ -1,6 +1,8 @@ import { apiRequest } from "$api"; import type { PartialMember, UserWithMembers } from "$api/models"; +const MEMBERS_PER_PAGE = 20; + export const load = async ({ params, fetch, cookies, url }) => { const user = await apiRequest("GET", `/users/${params.username}`, { fetch, @@ -13,10 +15,13 @@ export const load = async ({ params, fetch, cookies, url }) => { let members: PartialMember[] = []; if (user.members) { currentPage = Number(url.searchParams.get("page") || "0"); - pageCount = Math.ceil(user.members.length / 20); - members = user.members.slice(currentPage * 20, (currentPage + 1) * 20); + pageCount = Math.ceil(user.members.length / MEMBERS_PER_PAGE); + members = user.members.slice( + currentPage * MEMBERS_PER_PAGE, + (currentPage + 1) * MEMBERS_PER_PAGE, + ); if (members.length === 0) { - members = user.members.slice(0, 20); + members = user.members.slice(0, MEMBERS_PER_PAGE); currentPage = 0; } } diff --git a/Foxnouns.Frontend/src/routes/@[username]/+page.svelte b/Foxnouns.Frontend/src/routes/@[username]/+page.svelte index 0ed36cc..792df3f 100644 --- a/Foxnouns.Frontend/src/routes/@[username]/+page.svelte +++ b/Foxnouns.Frontend/src/routes/@[username]/+page.svelte @@ -6,7 +6,7 @@ import ProfileFields from "$components/profile/ProfileFields.svelte"; import { t } from "$lib/i18n"; import { Icon } from "@sveltestrap/sveltestrap"; - import Paginator from "./Paginator.svelte"; + import Paginator from "$components/Paginator.svelte"; import MemberCard from "$components/profile/user/MemberCard.svelte"; type Props = { data: PageData }; @@ -25,7 +25,7 @@ {/if} - + {#if data.members.length > 0} @@ -34,27 +34,27 @@ {data.user.member_title || $t("profile.default-members-header")} {#if isMeUser} - + {$t("profile.create-member-button")} {/if} - +

{#each data.members as member (member.id)} {/each}
-
- -
+ {/if}
diff --git a/Foxnouns.Frontend/src/routes/@[username]/Paginator.svelte b/Foxnouns.Frontend/src/routes/@[username]/Paginator.svelte deleted file mode 100644 index cf5aa54..0000000 --- a/Foxnouns.Frontend/src/routes/@[username]/Paginator.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - -{#if pageCount > 1} - - - - - - - - - {currentPage + 1} - - - - - - - - -{/if} diff --git a/Foxnouns.Frontend/src/routes/@[username]/[memberName]/+page.server.ts b/Foxnouns.Frontend/src/routes/@[username]/[memberName]/+page.server.ts new file mode 100644 index 0000000..f3f8400 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/@[username]/[memberName]/+page.server.ts @@ -0,0 +1,15 @@ +import { apiRequest } from "$api"; +import type { Member } from "$api/models/member"; + +export const load = async ({ params, fetch, cookies }) => { + const member = await apiRequest( + "GET", + `/users/${params.username}/members/${params.memberName}`, + { + fetch, + cookies, + }, + ); + + return { member }; +}; diff --git a/Foxnouns.Frontend/src/routes/@[username]/[memberName]/+page.svelte b/Foxnouns.Frontend/src/routes/@[username]/[memberName]/+page.svelte new file mode 100644 index 0000000..a69544a --- /dev/null +++ b/Foxnouns.Frontend/src/routes/@[username]/[memberName]/+page.svelte @@ -0,0 +1,40 @@ + + + + {data.member.display_name} • @{data.member.user.username} • pronouns.cc + + + diff --git a/Foxnouns.Frontend/src/routes/settings/members/+page.server.ts b/Foxnouns.Frontend/src/routes/settings/members/+page.server.ts new file mode 100644 index 0000000..220865b --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/members/+page.server.ts @@ -0,0 +1,18 @@ +const MEMBERS_PER_PAGE = 15; + +export const load = async ({ url, parent }) => { + const { user } = await parent(); + + let currentPage = Number(url.searchParams.get("page") || "0"); + let pageCount = Math.ceil(user.members.length / MEMBERS_PER_PAGE); + let members = user.members.slice( + currentPage * MEMBERS_PER_PAGE, + (currentPage + 1) * MEMBERS_PER_PAGE, + ); + if (members.length === 0) { + members = user.members.slice(0, MEMBERS_PER_PAGE); + currentPage = 0; + } + + return { members, currentPage, pageCount }; +}; diff --git a/Foxnouns.Frontend/src/routes/settings/members/+page.svelte b/Foxnouns.Frontend/src/routes/settings/members/+page.svelte new file mode 100644 index 0000000..563cf90 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/members/+page.svelte @@ -0,0 +1,48 @@ + + +

{$t("settings.members-tab")} ({data.user.members.length})

+ + + + + {#if canCreateMember} + + + {$t("profile.create-member-button")} + + {/if} + {#each data.members as member (member.id)} + + + {member.display_name} + {#if member.display_name !== member.name}({member.name}){/if} + + {/each} + + + diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout.server.ts b/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout.server.ts new file mode 100644 index 0000000..0cdf2e1 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout.server.ts @@ -0,0 +1,22 @@ +import { apiRequest } from "$api"; +import ApiError from "$api/error"; +import type { Member } from "$api/models"; +import log from "$lib/log"; +import { redirect } from "@sveltejs/kit"; + +export const load = async ({ parent, params, fetch, cookies }) => { + const { meUser, token } = await parent(); + if (!meUser) redirect(303, "/"); + + try { + const member = await apiRequest("GET", `/users/@me/members/${params.id}`, { + fetch, + cookies, + }); + return { user: meUser, token: token!, member }; + } catch (e) { + if (e instanceof ApiError) throw e.obj; + log.error("Error trying to fetch member %s:", params.id, e); + throw e; + } +}; diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout@.svelte b/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout@.svelte new file mode 100644 index 0000000..a2539ab --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/+layout@.svelte @@ -0,0 +1,65 @@ + + + + {$t("edit-profile.member-header", { name: data.member.name })} • pronouns.cc + + + diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.server.ts b/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.server.ts new file mode 100644 index 0000000..7665014 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.server.ts @@ -0,0 +1,82 @@ +import { apiRequest, fastRequest } from "$api"; +import ApiError, { ErrorCode, type RawApiError } from "$api/error"; +import type { Member } from "$api/models/member"; +import log from "$lib/log.js"; + +export const load = async ({ params, fetch, cookies }) => { + try { + const member = await apiRequest("GET", `/users/@me/members/${params.id}`, { + fetch, + cookies, + }); + return { member }; + } catch (e) { + if (e instanceof ApiError) throw e.obj; + log.error("Error trying to fetch member %s:", params.id, e); + throw e; + } +}; + +export const actions = { + changeName: async ({ params, request, fetch, cookies }) => { + const body = await request.formData(); + const name = body.get("name") as string | null; + if (!name) + return { + error: { + message: "You must pass a name.", + status: 403, + code: ErrorCode.BadRequest, + } as RawApiError, + ok: false, + }; + + try { + await fastRequest("PATCH", `/users/@me/members/${params.id}`, { + body: { name }, + fetch, + cookies, + }); + return { error: null, ok: true }; + } catch (e) { + if (e instanceof ApiError) return { error: e.obj, ok: false }; + log.error("Error updating name for member %s:", params.id, e); + throw e; + } + }, + changeDisplayName: async ({ params, request, fetch, cookies }) => { + const body = await request.formData(); + let displayName = body.get("display-name") as string | null; + if (!displayName || displayName === "") displayName = null; + + try { + await fastRequest("PATCH", `/users/@me/members/${params.id}`, { + body: { display_name: displayName }, + fetch, + cookies, + }); + return { error: null, ok: true }; + } catch (e) { + if (e instanceof ApiError) return { error: e.obj, ok: false }; + log.error("Error updating name for member %s:", params.id, e); + throw e; + } + }, + bio: async ({ params, request, fetch, cookies }) => { + const body = await request.formData(); + const bio = body.get("bio") as string | null; + + try { + await fastRequest("PATCH", `/users/@me/members/${params.id}`, { + body: { bio }, + fetch, + cookies, + }); + return { error: null, ok: true }; + } catch (e) { + if (e instanceof ApiError) return { error: e.obj, ok: false }; + log.error("Error updating bio for member %s:", params.id, e); + throw e; + } + }, +}; diff --git a/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.svelte b/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.svelte new file mode 100644 index 0000000..174108e --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/members/[id]/+page.svelte @@ -0,0 +1,115 @@ + + +{#if error} + +{/if} + +{#if form} + +{/if} + +
+
+

{$t("settings.avatar")}

+ +
+
+

{$t("edit-profile.member-name")}

+
+ + + + +
+ +

{$t("edit-profile.display-name")}

+
+ + + + +
+ +

{$t("edit-profile.sid")}

+ +
+
+

{$t("edit-profile.bio-tab")}

+
+ + +
+
diff --git a/Foxnouns.Frontend/src/routes/settings/members/new/+page.server.ts b/Foxnouns.Frontend/src/routes/settings/members/new/+page.server.ts new file mode 100644 index 0000000..3fdf2e0 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/members/new/+page.server.ts @@ -0,0 +1,34 @@ +import { apiRequest } from "$api"; +import ApiError, { ErrorCode, type RawApiError } from "$api/error"; +import type { Member } from "$api/models/member"; +import log from "$lib/log.js"; +import { isRedirect, redirect } from "@sveltejs/kit"; + +export const actions = { + default: async ({ request, fetch, cookies }) => { + const body = await request.formData(); + const name = body.get("name") as string | null; + if (!name) + return { + error: { + message: "No name supplied.", + status: 403, + code: ErrorCode.BadRequest, + } as RawApiError, + }; + + try { + const member = await apiRequest("POST", "/users/@me/members", { + body: { name }, + fetch, + cookies, + }); + redirect(303, `/settings/members/${member.id}`); + } catch (e) { + if (isRedirect(e)) throw e; + if (e instanceof ApiError) return { error: e.obj }; + log.error("Could not create member:", e); + throw e; + } + }, +}; diff --git a/Foxnouns.Frontend/src/routes/settings/members/new/+page.svelte b/Foxnouns.Frontend/src/routes/settings/members/new/+page.svelte new file mode 100644 index 0000000..da17621 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/members/new/+page.svelte @@ -0,0 +1,22 @@ + + +

{$t("settings.create-member-title")}

+ +{#if form?.error} + +{/if} + +
+
+ + +
+ +
diff --git a/Foxnouns.Frontend/src/routes/settings/profile/+layout.server.ts b/Foxnouns.Frontend/src/routes/settings/profile/+layout.server.ts new file mode 100644 index 0000000..9d7ef68 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/profile/+layout.server.ts @@ -0,0 +1,7 @@ +import { redirect } from "@sveltejs/kit"; + +export const load = async ({ parent }) => { + const { meUser, token } = await parent(); + if (!meUser) redirect(303, "/"); + return { user: meUser!, token: token! }; +}; diff --git a/Foxnouns.Frontend/src/routes/settings/profile/+layout.svelte b/Foxnouns.Frontend/src/routes/settings/profile/+layout.svelte deleted file mode 100644 index 590d5b5..0000000 --- a/Foxnouns.Frontend/src/routes/settings/profile/+layout.svelte +++ /dev/null @@ -1,42 +0,0 @@ - - -

{$t("edit-profile.user-header")}

- diff --git a/Foxnouns.Frontend/src/routes/settings/profile/+layout@.svelte b/Foxnouns.Frontend/src/routes/settings/profile/+layout@.svelte new file mode 100644 index 0000000..12c16d4 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/settings/profile/+layout@.svelte @@ -0,0 +1,65 @@ + + + + {$t("edit-profile.user-header")} • pronouns.cc + + + diff --git a/Foxnouns.Frontend/src/routes/settings/profile/+page.server.ts b/Foxnouns.Frontend/src/routes/settings/profile/+page.server.ts index 2233626..df356c4 100644 --- a/Foxnouns.Frontend/src/routes/settings/profile/+page.server.ts +++ b/Foxnouns.Frontend/src/routes/settings/profile/+page.server.ts @@ -1,4 +1,4 @@ -import { apiRequest, fastRequest } from "$api"; +import { fastRequest } from "$api"; import ApiError from "$api/error"; import log from "$lib/log.js"; @@ -26,4 +26,22 @@ export const actions = { throw e; } }, + changeDisplayName: async ({ request, fetch, cookies }) => { + const body = await request.formData(); + let displayName = body.get("display-name") as string | null; + if (!displayName || displayName === "") displayName = null; + + try { + await fastRequest("PATCH", "/users/@me", { + body: { display_name: displayName }, + fetch, + cookies, + }); + return { error: null, ok: true }; + } catch (e) { + if (e instanceof ApiError) return { error: e.obj, ok: false }; + log.error("Error patching user:", e); + throw e; + } + }, }; diff --git a/Foxnouns.Frontend/src/routes/settings/profile/+page.svelte b/Foxnouns.Frontend/src/routes/settings/profile/+page.svelte index 5b00f0c..c603ab6 100644 --- a/Foxnouns.Frontend/src/routes/settings/profile/+page.svelte +++ b/Foxnouns.Frontend/src/routes/settings/profile/+page.svelte @@ -1,39 +1,29 @@ -

Bio

- +

{$t("edit-profile.bio-tab")}

-
- - + - -

- {$t("edit-profile.bio-length-hint", { - length: bio.length, - maxLength: data.meta.limits.bio_length, - })} -

- -{#if bio !== ""} -
-
Preview
-
{@html renderMarkdown(bio)}
-
-{/if} diff --git a/package.json b/package.json index db48a60..60f2ca5 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "concurrently": "^9.0.1" }, "scripts": { - "dev": "concurrently -n .net,node,rate -c magenta,yellow,blue -i 'pnpm watch:be' 'cd Foxnouns.Frontend && pnpm dev' 'cd rate && go run -v .'", "watch:be": "dotnet watch --no-hot-reload --project Foxnouns.Backend -- --migrate-and-start", + "dev": "concurrently -n .net,node,rate -c magenta,yellow,blue -i 'pnpm watch:be' 'cd Foxnouns.Frontend && pnpm dev' 'cd rate && go run -v .'", "format": "dotnet csharpier . && cd Foxnouns.Frontend && pnpm format" }, "packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee"