refactor(servers): integrate responsive color picker grid into EditServerModal
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 1m20s
All checks were successful
Deploy Waveform to VPS / deploy (push) Successful in 1m20s
This commit is contained in:
parent
dffcfca0ad
commit
e150c819ca
5 changed files with 82 additions and 49 deletions
|
|
@ -48,7 +48,7 @@ export default async function ChannelPage({
|
|||
<AppHeader
|
||||
title={channel.name}
|
||||
showMembersButton
|
||||
server={{ id: server.id, name: server.name }}
|
||||
server={{ id: server.id, name: server.name, color: server.color }}
|
||||
/>
|
||||
|
||||
<ChatMessages
|
||||
|
|
|
|||
|
|
@ -10,16 +10,7 @@ import { eq } from "drizzle-orm";
|
|||
import { revalidatePath } from "next/cache";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
/**
|
||||
* Handles PATCH requests to update an existing server's name.
|
||||
*
|
||||
* @async
|
||||
* @function PATCH
|
||||
* @param {Request} req - The incoming HTTP request object containing the updated server name in the body.
|
||||
* @param {Object} context - The route context parameters.
|
||||
* @param {Promise<{ serverId: string }>} context.params - A promise resolving to the route parameters containing the server ID.
|
||||
* @returns {Promise<NextResponse>} A JSON response containing the updated server object or an error message.
|
||||
*/
|
||||
/** Handles PATCH requests to update an existing server's name. */
|
||||
export async function PATCH(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ serverId: string }> },
|
||||
|
|
@ -34,6 +25,7 @@ export async function PATCH(
|
|||
|
||||
const body = await req.json().catch(() => null);
|
||||
const name = body?.name;
|
||||
const color = body?.color;
|
||||
|
||||
// Ensure name is present, valid string type, and not just whitespace
|
||||
if (!name || typeof name !== "string" || !name.trim()) {
|
||||
|
|
@ -53,6 +45,14 @@ export async function PATCH(
|
|||
);
|
||||
}
|
||||
|
||||
// Validate color if provided
|
||||
if (color !== undefined && (typeof color !== "string" || !color.trim())) {
|
||||
return NextResponse.json(
|
||||
{ error: "Invalid color value provided." },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the server exists
|
||||
const [existingServer] = await db
|
||||
.select()
|
||||
|
|
@ -72,10 +72,19 @@ export async function PATCH(
|
|||
);
|
||||
}
|
||||
|
||||
// Prepare fields to update
|
||||
const updateData: { name: string; color?: string } = {
|
||||
name: trimmedName,
|
||||
};
|
||||
|
||||
if (color && typeof color === "string") {
|
||||
updateData.color = color.trim();
|
||||
}
|
||||
|
||||
// Update Server
|
||||
const [updatedServer] = await db
|
||||
.update(servers)
|
||||
.set({ name: trimmedName })
|
||||
.set(updateData)
|
||||
.where(eq(servers.id, serverId))
|
||||
.returning();
|
||||
|
||||
|
|
@ -90,16 +99,7 @@ export async function PATCH(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles DELETE requests to remove an existing server.
|
||||
*
|
||||
* @async
|
||||
* @function DELETE
|
||||
* @param {Request} req - The incoming HTTP request object.
|
||||
* @param {Object} context - The route context parameters.
|
||||
* @param {Promise<{ serverId: string }>} context.params - A promise resolving to the route parameters containing the server ID.
|
||||
* @returns {Promise<NextResponse>} A JSON response confirming deletion or returning an error message.
|
||||
*/
|
||||
/** Handles DELETE requests to remove an existing server. */
|
||||
export async function DELETE(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ serverId: string }> },
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ interface AppHeaderProps {
|
|||
server?: {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
};
|
||||
showFriendsTabs?: boolean;
|
||||
activeTab?: TabType;
|
||||
|
|
@ -140,7 +141,11 @@ export function AppHeader({
|
|||
{/* Right Action Buttons */}
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
{server && (
|
||||
<ServerSettingsMenu serverId={server.id} serverName={server.name} />
|
||||
<ServerSettingsMenu
|
||||
serverId={server.id}
|
||||
serverName={server.name}
|
||||
serverColor={server.color}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showMembersButton && (
|
||||
|
|
|
|||
|
|
@ -15,12 +15,14 @@ import { CreateChannelModal } from "@/components/modals/CreateChannelModal";
|
|||
interface ServerSettingsMenuProps {
|
||||
serverId: string;
|
||||
serverName: string;
|
||||
serverColor: string;
|
||||
}
|
||||
|
||||
/** Renders a server management dropdown menu with modal triggers for channel, category, and server settings. */
|
||||
export function ServerSettingsMenu({
|
||||
serverId,
|
||||
serverName,
|
||||
serverColor,
|
||||
}: ServerSettingsMenuProps) {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const [isEditServerOpen, setIsEditServerOpen] = useState(false);
|
||||
|
|
@ -102,6 +104,7 @@ export function ServerSettingsMenu({
|
|||
isOpen={isEditServerOpen}
|
||||
serverId={serverId}
|
||||
initialName={serverName}
|
||||
initialColor={serverColor}
|
||||
onClose={() => setIsEditServerOpen(false)}
|
||||
/>
|
||||
<CreateCategoryModal
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
/**
|
||||
* @file components/modals/EditServerModal.tsx
|
||||
* @description Modal dialog to edit server settings or delete the server.
|
||||
* @description Modal dialog to edit server settings (name, color) or delete the server.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { X, Loader2, Trash2 } from "lucide-react";
|
||||
import { X, Trash2, Check } from "lucide-react";
|
||||
import {
|
||||
SERVER_COLOR_CLASSES,
|
||||
SERVER_COLOR_OPTIONS,
|
||||
} from "@/lib/constants/server.styles";
|
||||
import { ActionButton } from "../ui/ActionButton";
|
||||
|
||||
/**
|
||||
|
|
@ -18,31 +22,28 @@ import { ActionButton } from "../ui/ActionButton";
|
|||
* @property {() => void} onClose - Callback function triggered to close the modal.
|
||||
* @property {string} serverId - The unique identifier of the server being edited.
|
||||
* @property {string} initialName - The current name of the server.
|
||||
* @property {string} [initialColor] - The current accent color of the server.
|
||||
*/
|
||||
interface EditServerModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
serverId: string;
|
||||
initialName: string;
|
||||
initialColor: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a modal dialog allowing users to modify server properties or delete the server.
|
||||
*
|
||||
* @param {EditServerModalProps} props - The component props.
|
||||
* @param {boolean} props.isOpen - Determines whether the modal dialog is currently visible.
|
||||
* @param {() => void} props.onClose - Callback function triggered to close the modal.
|
||||
* @param {string} props.serverId - The unique identifier of the server.
|
||||
* @param {string} props.initialName - The current name of the server.
|
||||
* @returns {JSX.Element | null} The rendered edit server modal, or null if closed.
|
||||
*/
|
||||
export function EditServerModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
serverId,
|
||||
initialName,
|
||||
initialColor,
|
||||
}: EditServerModalProps) {
|
||||
const [name, setName] = useState(initialName);
|
||||
const [color, setColor] = useState(initialColor);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
|
||||
|
|
@ -52,21 +53,17 @@ export function EditServerModal({
|
|||
|
||||
useEffect(() => {
|
||||
setName(initialName);
|
||||
}, [initialName]);
|
||||
setColor(initialColor);
|
||||
}, [initialName, initialColor]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const isChanged = name.trim() !== initialName;
|
||||
const isChanged = name.trim() !== initialName || color !== initialColor;
|
||||
const isValid = name.trim().length > 0;
|
||||
const canSave = isChanged && isValid && !isLoading && !isDeleting;
|
||||
|
||||
/**
|
||||
* Handles the asynchronous update of the server name.
|
||||
*
|
||||
* @async
|
||||
* @function handleUpdate
|
||||
* @param {React.FormEvent} e - The form submission event.
|
||||
* @returns {Promise<void>} Resolves when the server update process completes or fails.
|
||||
* Handles the asynchronous update of the server name and accent color.
|
||||
*/
|
||||
const handleUpdate = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -79,7 +76,7 @@ export function EditServerModal({
|
|||
const response = await fetch(`/api/servers/${serverId}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name: name.trim() }),
|
||||
body: JSON.stringify({ name: name.trim(), color }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
|
@ -96,11 +93,7 @@ export function EditServerModal({
|
|||
};
|
||||
|
||||
/**
|
||||
* Handles the asynchronous deletion of the server and redirects the user to the home page.
|
||||
*
|
||||
* @async
|
||||
* @function handleDelete
|
||||
* @returns {Promise<void>} Resolves when the server deletion process completes or fails.
|
||||
* Handles the asynchronous deletion of the server.
|
||||
*/
|
||||
const handleDelete = async () => {
|
||||
if (!isConfirmingDelete) {
|
||||
|
|
@ -154,7 +147,7 @@ export function EditServerModal({
|
|||
Change server details or delete this server.
|
||||
</p>
|
||||
|
||||
<form onSubmit={handleUpdate} className="space-y-4">
|
||||
<form onSubmit={handleUpdate} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-xs font-semibold text-muted uppercase tracking-wider mb-2">
|
||||
Server Name
|
||||
|
|
@ -172,9 +165,41 @@ export function EditServerModal({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<label className="block text-xs font-semibold text-muted uppercase tracking-wider">
|
||||
Accent Color
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-8 gap-3 bg-background/50 border border-surface/80 p-3.5 rounded-2xl max-h-48 overflow-y-auto">
|
||||
{SERVER_COLOR_OPTIONS.map((c) => {
|
||||
const isSelected = color === c;
|
||||
return (
|
||||
<button
|
||||
key={c}
|
||||
type="button"
|
||||
onClick={() => setColor(c)}
|
||||
className={`group relative aspect-square rounded-full ${
|
||||
SERVER_COLOR_CLASSES[c] || c
|
||||
} flex items-center justify-center transition-all duration-150 cursor-pointer ${
|
||||
isSelected
|
||||
? "ring-2 ring-white ring-offset-2 ring-offset-surface scale-110 z-10 shadow-lg"
|
||||
: "opacity-80 hover:opacity-100 hover:scale-105"
|
||||
}`}
|
||||
>
|
||||
{isSelected && (
|
||||
<Check className="w-3.5 h-3.5 text-white drop-shadow-md stroke-3" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-xs text-red-400 mt-2">{error}</p>}
|
||||
|
||||
<div className="flex items-center justify-between pt-4">
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
<ActionButton
|
||||
type="button"
|
||||
variant="danger"
|
||||
|
|
|
|||
Loading…
Reference in a new issue