From 1b7e4b94b57c506cc9e582db86b6afe4f08aee69 Mon Sep 17 00:00:00 2001 From: Nico Date: Sat, 14 Feb 2026 00:52:11 -0800 Subject: [PATCH] commit pre-existing group settings content edits --- .../web/components/group-settings-content.tsx | 227 ++++++++---------- 1 file changed, 103 insertions(+), 124 deletions(-) diff --git a/apps/web/components/group-settings-content.tsx b/apps/web/components/group-settings-content.tsx index 71ed615..653d744 100644 --- a/apps/web/components/group-settings-content.tsx +++ b/apps/web/components/group-settings-content.tsx @@ -2,16 +2,18 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useRouter } from "next/navigation"; -import useTags from "@/hooks/use-tags"; -import useGroupSettings from "@/hooks/use-group-settings"; -import useGroupMembers from "@/hooks/use-group-members"; -import useGroupInvites from "@/hooks/use-group-invites"; -import useGroupAudit from "@/hooks/use-group-audit"; +import useTags from "@/features/tags/hooks/use-tags"; +import useGroupSettings from "@/features/groups/hooks/use-group-settings"; +import useGroupMembers from "@/features/groups/hooks/use-group-members"; +import useGroupInvites from "@/features/groups/hooks/use-group-invites"; +import useGroupAudit from "@/features/groups/hooks/use-group-audit"; import { useGroupsContext } from "@/hooks/groups-context"; import TagInput from "@/components/tag-input"; import { useNotificationsContext } from "@/hooks/notifications-context"; import ConfirmSlideModal from "@/components/confirm-slide-modal"; +import ConfirmRetypeModal from "@/components/confirm-retype-modal"; import { groupsDelete, groupsRename } from "@/lib/client/groups"; +import ToggleButtonGroup from "@/components/toggle-button-group"; export default function GroupSettingsContent({ groupId }: { groupId: number }) { const router = useRouter(); @@ -267,6 +269,24 @@ export default function GroupSettingsContent({ groupId }: { groupId: number }) { return { userId: top, count: topCount, name, searchValue }; })(); const mostActiveCount = mostActiveUser?.count ?? 0; + const renameDirty = Boolean(group && renameValue.trim() !== group.name); + const memberCount = members.length; + const showLeaveGroup = role !== "GROUP_OWNER" && !isLastAdmin; + + useEffect(() => { + if (!renameModalOpen && !tagModalOpen) return; + function handleKeyDown(event: KeyboardEvent) { + if (event.key === "Escape") { + if (tagModalOpen) setTagModalOpen(false); + if (renameModalOpen) handleCloseRenameModal(); + } + if (event.key === "Enter" && !event.shiftKey && !event.defaultPrevented) { + if (renameModalOpen && renameDirty && isAdmin && renameValue.trim()) setConfirmRenameOpen(true); + } + } + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [renameModalOpen, tagModalOpen, renameDirty, isAdmin, renameValue, handleCloseRenameModal]); async function handleDeleteGroup() { const result = await groupsDelete(); @@ -288,30 +308,9 @@ export default function GroupSettingsContent({ groupId }: { groupId: number }) { ); } - const renameDirty = renameValue.trim() !== group.name; const visibleTags = showAllTags ? tags : tags.slice(0, 5); const hasMoreTags = tags.length > 5; const tagsScrollable = showAllTags && tags.length > 15; - const memberCount = members.length; - const showLeaveGroup = role !== "GROUP_OWNER" && !isLastAdmin; - - useEffect(() => { - if (!renameModalOpen && !tagModalOpen && !confirmDeleteGroupOpen) return; - function handleKeyDown(event: KeyboardEvent) { - if (event.key === "Escape") { - if (confirmDeleteGroupOpen) setConfirmDeleteGroupOpen(false); - if (tagModalOpen) setTagModalOpen(false); - if (renameModalOpen) handleCloseRenameModal(); - } - if (event.key === "Enter" && !event.shiftKey && !event.defaultPrevented) { - if (renameModalOpen && renameDirty && isAdmin && renameValue.trim()) setConfirmRenameOpen(true); - // if (tagModalOpen && pendingTags.length && canManageTags) handleSaveTags(); - if (confirmDeleteGroupOpen && deleteConfirmText.trim().toUpperCase() === "DELETE") handleDeleteGroup(); - } - } - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); - }, [renameModalOpen, tagModalOpen, confirmDeleteGroupOpen, renameDirty, isAdmin, renameValue, pendingTags.length, canManageTags, deleteConfirmText, handleDeleteGroup, handleSaveTags, handleCloseRenameModal]); return ( <> @@ -425,23 +424,21 @@ export default function GroupSettingsContent({ groupId }: { groupId: number }) {
Join policy
-
- {[ + handleJoinPolicyChange(policy)} + ariaLabel="Join policy" + className="flex flex-wrap gap-2" + buttonBaseClassName="rounded-lg border" + sizeClassName="px-3 py-1.5 text-xs font-semibold transition" + activeClassName="border-accent bg-accent-soft" + inactiveClassName="border-accent-weak bg-panel hover:border-accent" + options={[ { value: "NOT_ACCEPTING", label: "Disabled" }, { value: "AUTO_ACCEPT", label: "Auto" }, { value: "APPROVAL_REQUIRED", label: "Manual" } - ].map(option => ( - - ))} -
+ ]} + />
@@ -518,42 +515,55 @@ export default function GroupSettingsContent({ groupId }: { groupId: number }) { className={`rounded-lg border px-3 py-2 text-sm ${link.revokedAt || isInviteExpired(link.expiresAt) || (link.singleUse && link.usedAt) ? "border-red-400/60 bg-red-500/5" : "border-accent-weak bg-panel"}`} >
-
Token: {link.token.slice(0, 6)}…{link.token.slice(-4)}
-
- - {(link.revokedAt || isInviteExpired(link.expiresAt) || (link.singleUse && link.usedAt)) ? ( - - ) : (!link.revokedAt && !(link.singleUse && link.usedAt) && !isInviteExpired(link.expiresAt)) ? ( - - ) : null} - -
+
Token: {link.token.slice(0, 6)}...{link.token.slice(-4)}
+ {(() => { + const showRevive = link.revokedAt || isInviteExpired(link.expiresAt) || (link.singleUse && link.usedAt); + const showRevoke = !showRevive && !link.revokedAt && !(link.singleUse && link.usedAt) && !isInviteExpired(link.expiresAt); + const options = [ + { + value: "COPY", + label: "Copy link", + className: "btn-outline-accent", + disabled: localJoinPolicy === "NOT_ACCEPTING", + onClick: () => handleCopyInvite(link.token) + }, + ...(showRevive + ? [{ + value: "REVIVE", + label: "Revive", + className: "btn-outline-accent", + disabled: localJoinPolicy === "NOT_ACCEPTING", + onClick: () => reviveInvite(link.id, inviteTtlDays) + }] + : showRevoke + ? [{ + value: "REVOKE", + label: "Revoke", + className: "border border-red-400/60 bg-red-500/10 text-red-200", + onClick: () => revokeInvite(link.id) + }] + : []), + { + value: "DELETE", + label: "Delete", + className: "border border-red-400/60 bg-red-500/10 text-red-200", + onClick: () => setConfirmDeleteInvite({ id: link.id, token: link.token }) + } + ]; + + return ( + + ); + })()}
Expires {formatInviteExpiry(link.expiresAt)} @@ -577,10 +587,10 @@ export default function GroupSettingsContent({ groupId }: { groupId: number }) { {members.map(member => { const isSelf = member.userId === currentUserId; const privilegeLabel = member.role === "GROUP_OWNER" - ? "👑 Owner · Full control" + ? "Owner - Full control" : member.role === "GROUP_ADMIN" - ? "🛡️ Admin · Manage members" - : "👤 Member · Entries only"; + ? "Admin - Manage members" + : "Member - Entries only"; return (
- ✕ + x
Change group name
Edit tags
- +
setConfirmDeleteInvite(null)} onConfirm={async () => { @@ -1000,46 +1008,17 @@ export default function GroupSettingsContent({ groupId }: { groupId: number }) { if (ok) notify({ title: "Ownership transferred", message: target.name }); }} /> - {confirmDeleteGroupOpen ? ( -
setConfirmDeleteGroupOpen(false)}> -
event.stopPropagation()} - onKeyDown={event => { - if (event.key === "Escape") setConfirmDeleteGroupOpen(false); - if (event.key === "Enter" && deleteConfirmText.trim().toUpperCase() === "DELETE") handleDeleteGroup(); - }} - role="dialog" - tabIndex={-1} - > -
Delete group
-

Type DELETE to confirm. This cannot be undone.

- setDeleteConfirmText(e.target.value)} - placeholder="DELETE" - /> -
- - -
-
-
- ) : null} + setConfirmDeleteGroupOpen(false)} + onConfirm={handleDeleteGroup} + />
);