214 lines
11 KiB
TypeScript
214 lines
11 KiB
TypeScript
"use client";
|
|
|
|
import type { GroupSettingsViewModelState } from "@/features/groups/components/use-group-settings-view-model";
|
|
import TagInput from "@/shared/components/forms/tag-input";
|
|
import ConfirmRetypeModal from "@/shared/components/modals/confirm-retype-modal";
|
|
import ConfirmSlideModal from "@/shared/components/modals/confirm-slide-modal";
|
|
|
|
type GroupSettingsModalsProps = {
|
|
vm: GroupSettingsViewModelState;
|
|
};
|
|
|
|
export default function GroupSettingsModals({ vm }: GroupSettingsModalsProps) {
|
|
return (
|
|
<>
|
|
{vm.renameModalOpen ? (
|
|
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/60 p-4 !mt-0" onClick={vm.handleCloseRenameModal}>
|
|
<div
|
|
className="relative w-full max-w-sm rounded-2xl border border-accent-weak bg-panel p-5"
|
|
onClick={event => event.stopPropagation()}
|
|
onKeyDown={event => {
|
|
if (event.key === "Escape") vm.handleCloseRenameModal();
|
|
if (event.key === "Enter" && vm.renameDirty && vm.isAdmin && vm.renameValue.trim()) vm.setConfirmRenameOpen(true);
|
|
}}
|
|
role="dialog"
|
|
tabIndex={-1}
|
|
>
|
|
<button
|
|
type="button"
|
|
className="absolute right-3 top-3 rounded-lg btn-outline-accent px-2 py-1 text-sm"
|
|
onClick={vm.handleCloseRenameModal}
|
|
aria-label="Close"
|
|
>
|
|
x
|
|
</button>
|
|
<div className="text-lg font-semibold">Change group name</div>
|
|
<input
|
|
className={`mt-4 w-full input-base px-3 py-2 text-sm ${vm.renameValue.trim() ? "" : "border-red-400/70"}`}
|
|
value={vm.renameValue}
|
|
onChange={event => vm.setRenameValue(event.target.value)}
|
|
placeholder="Group name"
|
|
/>
|
|
{vm.renameDirty ? (
|
|
<div className="mt-3 rounded-lg border border-yellow-400/60 bg-yellow-500/10 px-3 py-2 text-xs text-yellow-200">
|
|
You have unsaved changes.
|
|
</div>
|
|
) : null}
|
|
<div className="mt-4 flex items-center gap-2">
|
|
{vm.renameDirty ? (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded-lg btn-outline-accent px-3 py-2 text-sm font-semibold"
|
|
onClick={vm.handleCloseRenameModal}
|
|
>
|
|
Discard changes
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded-lg btn-accent px-3 py-2 text-sm font-semibold disabled:opacity-60"
|
|
onClick={() => vm.setConfirmRenameOpen(true)}
|
|
disabled={!vm.isAdmin || !vm.renameValue.trim()}
|
|
>
|
|
Rename
|
|
</button>
|
|
</>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
className="flex-1 rounded-lg btn-outline-accent px-3 py-2 text-sm font-semibold"
|
|
onClick={vm.handleCloseRenameModal}
|
|
>
|
|
Dismiss
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
|
|
{vm.tagModalOpen ? (
|
|
<div className="fixed inset-0 z-[70] flex items-center justify-center bg-black/60 p-4 !mt-0" onClick={() => vm.setTagModalOpen(false)}>
|
|
<div
|
|
className="w-full max-w-xl rounded-2xl border border-accent-weak bg-panel p-5"
|
|
onClick={event => event.stopPropagation()}
|
|
onKeyDown={event => {
|
|
if (event.key === "Escape") vm.setTagModalOpen(false);
|
|
}}
|
|
role="dialog"
|
|
tabIndex={-1}
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<div className="text-lg font-semibold">Edit tags</div>
|
|
<button type="button" className="rounded-lg btn-outline-accent px-2 py-1 text-sm" onClick={() => vm.setTagModalOpen(false)} aria-label="Close">x</button>
|
|
</div>
|
|
<div className="mt-4 space-y-3">
|
|
<TagInput
|
|
label="Add tags"
|
|
tags={vm.pendingTags}
|
|
suggestions={vm.tags}
|
|
enableBackspaceRemove
|
|
onToggleTag={tag => vm.setPendingTags(prev => prev.filter(item => item !== tag))}
|
|
onAddTag={tag => vm.setPendingTags(prev => (prev.includes(tag) ? prev : [...prev, tag]))}
|
|
/>
|
|
<button
|
|
type="button"
|
|
className="rounded-lg btn-accent px-3 py-2 text-sm font-semibold disabled:opacity-60"
|
|
onClick={vm.handleSaveTags}
|
|
disabled={!vm.pendingTags.length || !vm.canManageTags}
|
|
>
|
|
Save tags
|
|
</button>
|
|
{!vm.canManageTags ? (
|
|
<div className="text-xs text-soft">Only admins can add new tags.</div>
|
|
) : null}
|
|
<div className="divider" />
|
|
<div className="text-sm font-semibold">Existing tags</div>
|
|
<div className="max-h-60 overflow-auto resize-y pr-1">
|
|
<div className="flex flex-wrap gap-2">
|
|
{vm.tags.map(tag => (
|
|
<button
|
|
key={tag}
|
|
type="button"
|
|
className={`rounded-full border px-2 py-0.5 text-xs text-[color:var(--color-text)] ${vm.toggleRemoveTags.includes(tag) ? "border-red-400/60 text-red-200 bg-red-500/10" : "border-accent-weak bg-accent-soft hover:border-accent"}`}
|
|
onClick={() => {
|
|
if (!vm.canManageTags) return;
|
|
vm.setToggleRemoveTags(prev => (prev.includes(tag) ? prev.filter(item => item !== tag) : [...prev, tag]));
|
|
}}
|
|
>
|
|
#{tag}
|
|
</button>
|
|
))}
|
|
{!vm.tags.length ? <div className="text-xs text-soft">No tags yet.</div> : null}
|
|
</div>
|
|
</div>
|
|
{vm.toggleRemoveTags.length ? (
|
|
<button
|
|
type="button"
|
|
className="rounded-lg border border-red-400/60 bg-red-500/10 px-3 py-2 text-sm font-semibold text-red-200"
|
|
onClick={() => vm.setConfirmDeleteOpen(true)}
|
|
>
|
|
Delete selected ({vm.toggleRemoveTags.length})
|
|
</button>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
|
|
<ConfirmSlideModal
|
|
isOpen={vm.confirmDeleteOpen}
|
|
title="Delete selected tags"
|
|
description="Tags will be removed from this group and any entries using them."
|
|
confirmLabel="Delete tags"
|
|
onClose={() => vm.setConfirmDeleteOpen(false)}
|
|
onConfirm={() => {
|
|
vm.setConfirmDeleteOpen(false);
|
|
vm.handleConfirmDelete();
|
|
}}
|
|
/>
|
|
<ConfirmSlideModal
|
|
isOpen={Boolean(vm.confirmDeleteInvite)}
|
|
title="Delete invite link"
|
|
description={vm.confirmDeleteInvite ? `Delete invite ${vm.confirmDeleteInvite.token.slice(0, 6)}...${vm.confirmDeleteInvite.token.slice(-4)}?` : ""}
|
|
confirmLabel="Delete link"
|
|
onClose={() => vm.setConfirmDeleteInvite(null)}
|
|
onConfirm={vm.handleConfirmDeleteInvite}
|
|
/>
|
|
<ConfirmSlideModal
|
|
isOpen={vm.confirmRenameOpen}
|
|
title="Rename group"
|
|
description={`Change group name to \"${vm.renameValue.trim()}\"?`}
|
|
confirmLabel="Rename"
|
|
onClose={() => vm.setConfirmRenameOpen(false)}
|
|
onConfirm={vm.handleRenameGroup}
|
|
/>
|
|
<ConfirmSlideModal
|
|
isOpen={vm.confirmLeaveOpen}
|
|
title="Leave group"
|
|
description="You will lose access to this group."
|
|
confirmLabel="Leave"
|
|
onClose={() => vm.setConfirmLeaveOpen(false)}
|
|
onConfirm={vm.handleConfirmLeaveGroup}
|
|
/>
|
|
<ConfirmSlideModal
|
|
isOpen={Boolean(vm.confirmKick)}
|
|
title="Kick member"
|
|
description={vm.confirmKick ? `Remove ${vm.confirmKick.name} from this group?` : ""}
|
|
confirmLabel="Kick"
|
|
onClose={() => vm.setConfirmKick(null)}
|
|
onConfirm={vm.handleConfirmKickMember}
|
|
/>
|
|
<ConfirmSlideModal
|
|
isOpen={Boolean(vm.confirmTransfer)}
|
|
title="Transfer ownership"
|
|
description={vm.confirmTransfer ? `Make ${vm.confirmTransfer.name} the new owner?` : ""}
|
|
confirmLabel="Transfer"
|
|
onClose={() => vm.setConfirmTransfer(null)}
|
|
onConfirm={vm.handleConfirmTransferOwnership}
|
|
/>
|
|
<ConfirmRetypeModal
|
|
isOpen={vm.confirmDeleteGroupOpen}
|
|
title="Delete group"
|
|
description="Type DELETE to confirm. This cannot be undone."
|
|
expectedText="DELETE"
|
|
value={vm.deleteConfirmText}
|
|
onChange={vm.setDeleteConfirmText}
|
|
confirmLabel="Delete"
|
|
onClose={() => vm.setConfirmDeleteGroupOpen(false)}
|
|
onConfirm={vm.handleDeleteGroup}
|
|
/>
|
|
</>
|
|
);
|
|
}
|