Skip to content

Commit

Permalink
use get channel hook, update channel name endpoint, update channel na…
Browse files Browse the repository at this point in the history
…me hook
  • Loading branch information
Diivvuu committed Oct 12, 2024
1 parent ceb0b45 commit 3a54d5a
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 5 deletions.
35 changes: 35 additions & 0 deletions convex/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@ import { getAuthUserId } from "@convex-dev/auth/server";
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";

export const update = mutation({
args: {
id: v.id("channels"),
name: v.string(),
},
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) {
throw new Error("Unauthorized");
}

const channel = await ctx.db.get(args.id);

if (!channel) {
throw new Error("Channel not found");
}

const member = await ctx.db
.query("members")
.withIndex("by_workspace_id_user_id", (q) =>
q.eq("workspaceId", channel.workspaceId).eq("userId", userId)
)
.unique();

if (!member || member.role !== "admin") {
throw new Error("Unauthorized");
}

await ctx.db.patch(args.id, {
name: args.name,
});
return args.id;
},
});

export const create = mutation({
args: {
name: v.string(),
Expand Down
94 changes: 94 additions & 0 deletions src/app/workspace/[workspaceId]/channel/[channelId]/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"use client";
import { useState } from "react";

import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { TrashIcon } from "lucide-react";
import { FaChevronDown } from "react-icons/fa";
import { Input } from "@/components/ui/input";

interface HeaderProps {
title: string;
}
export const Header = ({ title }: HeaderProps) => {
const [editOpen, setEditOpen] = useState(false);
const [value, setValue] = useState(title);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/\s+/g, "-").toLowerCase();
setValue(value);
};

return (
<div className="bg-white border-b h-[49px] flex items-center px-4 overflow-hidden">
<Dialog>
<DialogTrigger asChild>
<Button
className="text-lg font-semibold px-2 overflow-hidden w-auto"
variant="ghost"
>
<span className="truncate"># {title}</span>
<FaChevronDown className="size-2.5 ml-2" />
</Button>
</DialogTrigger>
<DialogContent className="p-0 bg-gray-50 overflow-hidden">
<DialogHeader className="p-4 border-b bg-white">
<DialogTitle>#{title}</DialogTitle>
</DialogHeader>
<div className="px-4 pb-4 flex flex-col gap-y-2">
<Dialog open={editOpen} onOpenChange={setEditOpen}>
<DialogTrigger asChild>
<div className="px-5 py-4 bg-white rounded-lg border cursor-pointer hover:bg-gray-50">
<div className="flex items-center justify-between">
<p className="text-sm font-semibold">Channel name</p>
<p className="text-sm text-[#1264a3] font-semibold hover:underline">
Edit
</p>
</div>
<p className=""># {title}</p>
</div>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename this channel</DialogTitle>
</DialogHeader>
<form className="space-y-4">
<Input
value={value}
disabled={false}
onChange={handleChange}
required
autoFocus
minLength={3}
maxLength={80}
placeholder="e.g. plan-budget"
/>
<DialogFooter>
<DialogClose asChild>
<Button variant="outline" disabled={false}>
Cancel
</Button>
</DialogClose>
<Button disabled={false}>Save</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<button className="flex items-center gap-x-2 px-5 py-4 bg-white rounded-lg cursor-pointer border hover:bg-gray-50 text-rose-600">
<TrashIcon className="size-4" />
<p className="text-sm font-semibold">Delete channel</p>
</button>
</div>
</DialogContent>
</Dialog>
</div>
);
};
40 changes: 36 additions & 4 deletions src/app/workspace/[workspaceId]/channel/[channelId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
"use client";
import { useGetChannel } from "@/features/channels/api/use-get-channel";
import { useChannelId } from "@/hooks/use-channel-id";

import { Loader, TriangleAlert } from "lucide-react";
import { Header } from "./Header";

const ChannelIdPage = () => {
const channelId = useChannelId();

const { data: channel, isLoading: channelLoading } = useGetChannel({
id: channelId,
});

if (channelLoading) {
return (
<div className="h-full flex-1 flex items-center justify-center">
<Loader className="animate-spin size-5 text-muted-foreground" />
</div>
);
}

if (!channel) {
return (
<div className="h-full flex-1 flex flex-col gap-y-2 items-center justify-center">
<TriangleAlert className="size-5 text-muted-foreground" />
<span>Channel not found</span>
</div>
);
}

return (
<div>ChannelIdPage</div>
)
}
<div className="flex flex-col h-full">
<Header title={channel.name} />
</div>
);
};

export default ChannelIdPage
export default ChannelIdPage;
2 changes: 1 addition & 1 deletion src/app/workspace/[workspaceId]/workspace-sidebar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const WorkspaceSidebar = () => {
<SidebarItem label="Drafts & Sent" icon={SendHorizonal} id="drafts" />
</div>
<WorkspaceSection
label="Channels name"
label="Channels"
hint="New Channel"
onNew={() => {
member.role === "admin" ? setOpen(true) : undefined;
Expand Down
14 changes: 14 additions & 0 deletions src/features/channels/api/use-get-channel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useQuery } from "convex/react";
import { Id } from "../../../../convex/_generated/dataModel";
import { api } from "../../../../convex/_generated/api";

interface UseGetChannelProps {
id: Id<"channels">;
}

export const useGetChannel = ({ id }: UseGetChannelProps) => {
const data = useQuery(api.channels.getById, { id });
const isLoading = data === undefined;

return { data, isLoading };
};
63 changes: 63 additions & 0 deletions src/features/channels/api/use-update-channel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { useCallback, useMemo, useState } from "react";
import { Id } from "../../../../convex/_generated/dataModel";
import { useMutation } from "convex/react";
import { api } from "../../../../convex/_generated/api";

type RequestType = { name: string; id: Id<"channels"> };
type ResponseType = Id<"channels"> | null;

type Options = {
onSuccess?: (data: ResponseType) => void;
onError?: (error: Error) => void;
onSettled?: () => void;
throwError?: boolean;
};

export const useUpdateChannel = () => {
const [data, setData] = useState<ResponseType>(null);
const [error, setError] = useState<Error | null>(null);
const [status, setStatus] = useState<
"success" | "pending" | "error" | "settled" | null
>(null);

const isPending = useMemo(() => status === "pending", [status]);
const isSuccess = useMemo(() => status === "success", [status]);
const isError = useMemo(() => status === "error", [status]);
const isSettled = useMemo(() => status === "settled", [status]);

const mutation = useMutation(api.channels.update);

const mutate = useCallback(
async (values: RequestType, options?: Options) => {
try {
setData(null);
setError(null);
setStatus("pending");

const response = await mutation(values);
options?.onSuccess?.(response);
setStatus("success");
return response;
} catch (error) {
setStatus("error");
options?.onError?.(error as Error);
if (options?.throwError) {
throw error;
}
} finally {
setStatus("settled");
options?.onSettled?.();
}
},
[mutation]
);
return {
mutate,
data,
error,
isPending,
isSuccess,
isError,
isSettled,
};
};

0 comments on commit 3a54d5a

Please sign in to comment.