Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions app/(app)/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import type { InvitationsPage, User } from "@absmach/magistrala-sdk";
import ChatPage from "@/components/chat/chat-page";
import { FetchData } from "@/lib/actions";
import { ListChannels } from "@/lib/channels";
import { GetWorkspaceInvitations } from "@/lib/invitations";
import { RequestOptions } from "@/lib/magistrala";
import { getServerSession } from "@/lib/nextauth";
import { UserProfile } from "@/lib/users";
import { ListWorkspaces, ListWorkspaceUsers } from "@/lib/workspace";
import type { Member } from "@/types/entities";
import { EntityType, type Member } from "@/types/entities";

export type Props = {
searchParams?: Promise<{
Expand All @@ -29,24 +31,44 @@ export default async function Page({ searchParams }: Props) {
});
const searchParamsValue = await searchParams;
const status = searchParamsValue?.status || "pending";
const inviResponse = await GetWorkspaceInvitations({
offset: 0,
limit: 100,
state: status,
});
const roles = session?.workspace?.roles || [];
let inviResponse;
if (roles.some((role) => role.role_name === "admin")) {
inviResponse = await GetWorkspaceInvitations({
offset: 0,
limit: 100,
state: status,
});
}
const dmChannelResponse = await ListChannels({
queryParams: { offset: 0, limit: 1, tag: "dm" },
});
const dmChannelId = dmChannelResponse?.data?.channels?.[0]?.id;
const user = await UserProfile(session.accessToken);

const getWorkspaceUsers = ({ id, queryParams }: RequestOptions) => {
return ListWorkspaceUsers(id!, queryParams);
};

const initMembers = await FetchData(
EntityType.Member,
{
offset: 0,
limit: 20,
status: "enabled",
},
getWorkspaceUsers,
workspaceId,
);

return (
<ChatPage
session={session}
members={memResponse.data?.members as Member[]}
invitationsPage={inviResponse?.data as InvitationsPage}
dmChannelId={dmChannelId as string}
user={user.data as User}
initMembers={initMembers}
/>
);
}
4 changes: 1 addition & 3 deletions app/auth/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ export default function Page() {
<div className="min-h-screen bg-gradient-to-br from-purple-600 to-blue-600 flex items-center justify-center p-4">
<div className="bg-white rounded-lg shadow-xl p-8 w-full max-w-md">
<div className="text-center mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">
MG Chat
</h1>
<h1 className="text-3xl font-bold text-gray-900 mb-2">MG Chat</h1>
<p className="text-gray-600">Connect with your team</p>
</div>
<AuthForm />
Expand Down
145 changes: 145 additions & 0 deletions components/chat/add-members-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { useState } from "react";
import { useForm } from "react-hook-form";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { Form, FormField, FormItem, FormLabel } from "../ui/form";
import { Button } from "../ui/button";
import { toast } from "sonner";
import z from "zod";
import { AddChannelRoleMembers } from "@/lib/roles";
import { zodResolver } from "@hookform/resolvers/zod";
import { InfiniteSelect } from "../custom/infinite-select";
import { EntityType } from "@/types/entities";
import { ListWorkspaceUsers } from "@/lib/workspace";
import { EntityFetchData } from "@/lib/actions";
import { RequestOptions } from "@/lib/magistrala";
import { ListChannelRoles } from "@/lib/roles";

const addRoleMembersFormSchema = () =>
z.object({
member: z.string().min(1, { error: "Please select a member" }),
});

export const AddRoleMembersDialog = ({
open,
setOpen,
channelId,
chatName,
initMembers,
workspaceId,
}: {
open: boolean;
setOpen: (open: boolean) => void;
channelId: string;
chatName: string;
initMembers: EntityFetchData;
workspaceId: string;
}) => {
const [processing, setProcessing] = useState(false);
const form = useForm<z.infer<ReturnType<typeof addRoleMembersFormSchema>>>({
resolver: zodResolver(addRoleMembersFormSchema()),
defaultValues: {
member: "",
},
});
async function onSubmit(
values: z.infer<ReturnType<typeof addRoleMembersFormSchema>>
) {
setProcessing(true);
const toastId = toast("Sonner");

toast.loading("Adding chat member...", {
id: toastId,
});
const roleResponse = await ListChannelRoles({
id: channelId,
queryParams: { offset: 0, limit: 10 },
});

const memberRole = roleResponse?.data?.roles?.find(
(role) => role.name === "chat-member"
);

const roleId = memberRole?.id as string;
const result = await AddChannelRoleMembers(channelId, roleId, [
values.member,
]);
setProcessing(false);
if (result.error === null) {
toast.success(
`Member "${values.member}" added successfully to role ${chatName}.`,
{
id: toastId,
}
);
form.reset();
setOpen(false);
} else {
toast.error(
`Failed to add member to role "${chatName}" with error "${result.error}"`,
{
id: toastId,
}
);
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="w-[90%] max-w-md max-h-[95%] overflow-auto">
<DialogHeader>
<DialogTitle>{"Add chat member"}</DialogTitle>
<DialogDescription />
</DialogHeader>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4 md:space-y-8"
>
<FormField
control={form.control}
name="member"
render={({ field }) => (
<FormItem>
<FormLabel>
Member
</FormLabel>
<InfiniteSelect
field={field}
initData={initMembers}
entityType={EntityType.Member}
getData={(options: RequestOptions) => ListWorkspaceUsers(workspaceId, options)}
disableSearch={true}
/>
</FormItem>
)}
/>
<DialogFooter className="flex flex-row justify-end gap-2">
<DialogClose asChild={true}>
<Button
type="button"
variant="secondary"
disabled={processing}
onClick={() => {
form.reset();
}}
>
Close
</Button>
</DialogClose>
<Button disabled={processing} type="submit">
Add
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
</Dialog>
);
};
59 changes: 59 additions & 0 deletions components/chat/chat-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";

import { EllipsisVertical } from "lucide-react";
import { Button } from "../ui/button";
import {
DropdownMenu,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuContent,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { useState } from "react";
import { AddRoleMembersDialog } from "./add-members-dialog";
import { EntityFetchData } from "@/lib/actions";

export function ChatMenu({
channelId,
chatName,
initMembers,
workspaceId,
}: {
channelId: string;
chatName: string;
initMembers: EntityFetchData;
workspaceId: string;
}) {
const [showMembersDialog, setShowMembersDialog] = useState(false);
return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild={true}>
<Button variant="ghost">
<EllipsisVertical className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-32 rounded-lg ml-2"
side="bottom"
align="end"
sideOffset={4}
>
<DropdownMenuGroup>
<DropdownMenuItem asChild={true} onSelect={() => setShowMembersDialog(true)}>
<span>Add members</span>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<AddRoleMembersDialog
open={showMembersDialog}
setOpen={setShowMembersDialog}
channelId={channelId}
chatName={chatName}
initMembers={initMembers}
workspaceId={workspaceId}
/>
</>
);
}
11 changes: 7 additions & 4 deletions components/chat/chat-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ import { Session } from "@/types/auth";
import { useState } from "react";
import { Member } from "@/types/entities";
import { InvitationsPage, User } from "@absmach/magistrala-sdk";
import { EntityFetchData } from "@/lib/actions";

interface Props {
session: Session;
members: Member[];
invitationsPage: InvitationsPage;
invitationsPage?: InvitationsPage;
dmChannelId: string;
user: User
user: User;
initMembers: EntityFetchData;
}
export default function ChatPage({ session, members, invitationsPage, dmChannelId, user }: Props) {
export default function ChatPage({ session, members, invitationsPage, dmChannelId, user, initMembers }: Props) {
const [selectedChannel, setSelectedChannel] = useState<string | null>(null);
const [selectedDM, setSelectedDM] = useState<string | null>(session?.user?.id as string);
const workspaceId = session.workspace
Expand All @@ -33,7 +35,7 @@ export default function ChatPage({ session, members, invitationsPage, dmChannelI
setSelectedChannel={setSelectedChannel}
setSelectedDM={setSelectedDM}
members={members}
invitationsPage={invitationsPage}
invitationsPage={invitationsPage as InvitationsPage}
dmChannelId={dmChannelId as string}
user={user}
/>
Expand All @@ -47,6 +49,7 @@ export default function ChatPage({ session, members, invitationsPage, dmChannelI
session={session}
workspaceId={workspaceId as string}
dmChannelId={dmChannelId as string}
initMembers={initMembers}
/>
</div>
</>
Expand Down
Loading