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
|
<AppHeader
|
||||||
title={channel.name}
|
title={channel.name}
|
||||||
showMembersButton
|
showMembersButton
|
||||||
server={{ id: server.id, name: server.name }}
|
server={{ id: server.id, name: server.name, color: server.color }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChatMessages
|
<ChatMessages
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,7 @@ import { eq } from "drizzle-orm";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
|
||||||
/**
|
/** Handles PATCH requests to update an existing server's name. */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
export async function PATCH(
|
export async function PATCH(
|
||||||
req: Request,
|
req: Request,
|
||||||
{ params }: { params: Promise<{ serverId: string }> },
|
{ params }: { params: Promise<{ serverId: string }> },
|
||||||
|
|
@ -34,6 +25,7 @@ export async function PATCH(
|
||||||
|
|
||||||
const body = await req.json().catch(() => null);
|
const body = await req.json().catch(() => null);
|
||||||
const name = body?.name;
|
const name = body?.name;
|
||||||
|
const color = body?.color;
|
||||||
|
|
||||||
// Ensure name is present, valid string type, and not just whitespace
|
// Ensure name is present, valid string type, and not just whitespace
|
||||||
if (!name || typeof name !== "string" || !name.trim()) {
|
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
|
// Check if the server exists
|
||||||
const [existingServer] = await db
|
const [existingServer] = await db
|
||||||
.select()
|
.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
|
// Update Server
|
||||||
const [updatedServer] = await db
|
const [updatedServer] = await db
|
||||||
.update(servers)
|
.update(servers)
|
||||||
.set({ name: trimmedName })
|
.set(updateData)
|
||||||
.where(eq(servers.id, serverId))
|
.where(eq(servers.id, serverId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|
@ -90,16 +99,7 @@ export async function PATCH(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Handles DELETE requests to remove an existing server. */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
export async function DELETE(
|
export async function DELETE(
|
||||||
req: Request,
|
req: Request,
|
||||||
{ params }: { params: Promise<{ serverId: string }> },
|
{ params }: { params: Promise<{ serverId: string }> },
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ interface AppHeaderProps {
|
||||||
server?: {
|
server?: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
color: string;
|
||||||
};
|
};
|
||||||
showFriendsTabs?: boolean;
|
showFriendsTabs?: boolean;
|
||||||
activeTab?: TabType;
|
activeTab?: TabType;
|
||||||
|
|
@ -140,7 +141,11 @@ export function AppHeader({
|
||||||
{/* Right Action Buttons */}
|
{/* Right Action Buttons */}
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
{server && (
|
{server && (
|
||||||
<ServerSettingsMenu serverId={server.id} serverName={server.name} />
|
<ServerSettingsMenu
|
||||||
|
serverId={server.id}
|
||||||
|
serverName={server.name}
|
||||||
|
serverColor={server.color}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showMembersButton && (
|
{showMembersButton && (
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ import { CreateChannelModal } from "@/components/modals/CreateChannelModal";
|
||||||
interface ServerSettingsMenuProps {
|
interface ServerSettingsMenuProps {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
serverName: string;
|
serverName: string;
|
||||||
|
serverColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Renders a server management dropdown menu with modal triggers for channel, category, and server settings. */
|
/** Renders a server management dropdown menu with modal triggers for channel, category, and server settings. */
|
||||||
export function ServerSettingsMenu({
|
export function ServerSettingsMenu({
|
||||||
serverId,
|
serverId,
|
||||||
serverName,
|
serverName,
|
||||||
|
serverColor,
|
||||||
}: ServerSettingsMenuProps) {
|
}: ServerSettingsMenuProps) {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
const [isEditServerOpen, setIsEditServerOpen] = useState(false);
|
const [isEditServerOpen, setIsEditServerOpen] = useState(false);
|
||||||
|
|
@ -102,6 +104,7 @@ export function ServerSettingsMenu({
|
||||||
isOpen={isEditServerOpen}
|
isOpen={isEditServerOpen}
|
||||||
serverId={serverId}
|
serverId={serverId}
|
||||||
initialName={serverName}
|
initialName={serverName}
|
||||||
|
initialColor={serverColor}
|
||||||
onClose={() => setIsEditServerOpen(false)}
|
onClose={() => setIsEditServerOpen(false)}
|
||||||
/>
|
/>
|
||||||
<CreateCategoryModal
|
<CreateCategoryModal
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
/**
|
/**
|
||||||
* @file components/modals/EditServerModal.tsx
|
* @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";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
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";
|
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 {() => void} onClose - Callback function triggered to close the modal.
|
||||||
* @property {string} serverId - The unique identifier of the server being edited.
|
* @property {string} serverId - The unique identifier of the server being edited.
|
||||||
* @property {string} initialName - The current name of the server.
|
* @property {string} initialName - The current name of the server.
|
||||||
|
* @property {string} [initialColor] - The current accent color of the server.
|
||||||
*/
|
*/
|
||||||
interface EditServerModalProps {
|
interface EditServerModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
initialName: string;
|
initialName: string;
|
||||||
|
initialColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a modal dialog allowing users to modify server properties or delete the server.
|
* 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({
|
export function EditServerModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
serverId,
|
serverId,
|
||||||
initialName,
|
initialName,
|
||||||
|
initialColor,
|
||||||
}: EditServerModalProps) {
|
}: EditServerModalProps) {
|
||||||
const [name, setName] = useState(initialName);
|
const [name, setName] = useState(initialName);
|
||||||
|
const [color, setColor] = useState(initialColor);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
|
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false);
|
||||||
|
|
@ -52,21 +53,17 @@ export function EditServerModal({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setName(initialName);
|
setName(initialName);
|
||||||
}, [initialName]);
|
setColor(initialColor);
|
||||||
|
}, [initialName, initialColor]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
if (!isOpen) return null;
|
||||||
|
|
||||||
const isChanged = name.trim() !== initialName;
|
const isChanged = name.trim() !== initialName || color !== initialColor;
|
||||||
const isValid = name.trim().length > 0;
|
const isValid = name.trim().length > 0;
|
||||||
const canSave = isChanged && isValid && !isLoading && !isDeleting;
|
const canSave = isChanged && isValid && !isLoading && !isDeleting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the asynchronous update of the server name.
|
* Handles the asynchronous update of the server name and accent color.
|
||||||
*
|
|
||||||
* @async
|
|
||||||
* @function handleUpdate
|
|
||||||
* @param {React.FormEvent} e - The form submission event.
|
|
||||||
* @returns {Promise<void>} Resolves when the server update process completes or fails.
|
|
||||||
*/
|
*/
|
||||||
const handleUpdate = async (e: React.FormEvent) => {
|
const handleUpdate = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -79,7 +76,7 @@ export function EditServerModal({
|
||||||
const response = await fetch(`/api/servers/${serverId}`, {
|
const response = await fetch(`/api/servers/${serverId}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ name: name.trim() }),
|
body: JSON.stringify({ name: name.trim(), color }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
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.
|
* Handles the asynchronous deletion of the server.
|
||||||
*
|
|
||||||
* @async
|
|
||||||
* @function handleDelete
|
|
||||||
* @returns {Promise<void>} Resolves when the server deletion process completes or fails.
|
|
||||||
*/
|
*/
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (!isConfirmingDelete) {
|
if (!isConfirmingDelete) {
|
||||||
|
|
@ -154,7 +147,7 @@ export function EditServerModal({
|
||||||
Change server details or delete this server.
|
Change server details or delete this server.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<form onSubmit={handleUpdate} className="space-y-4">
|
<form onSubmit={handleUpdate} className="space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold text-muted uppercase tracking-wider mb-2">
|
<label className="block text-xs font-semibold text-muted uppercase tracking-wider mb-2">
|
||||||
Server Name
|
Server Name
|
||||||
|
|
@ -172,9 +165,41 @@ export function EditServerModal({
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>}
|
{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
|
<ActionButton
|
||||||
type="button"
|
type="button"
|
||||||
variant="danger"
|
variant="danger"
|
||||||
|
|
@ -183,7 +208,7 @@ export function EditServerModal({
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
icon={Trash2}
|
icon={Trash2}
|
||||||
>
|
>
|
||||||
{isConfirmingDelete ? "Sure?" : "Delete Server "}
|
{isConfirmingDelete ? "Sure?" : "Delete Server"}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue