Skip to content

Commit

Permalink
End of part 29.4
Browse files Browse the repository at this point in the history
  • Loading branch information
codinginflow committed Jul 13, 2024
1 parent 55ce978 commit 2f1e8f6
Show file tree
Hide file tree
Showing 13 changed files with 587 additions and 16 deletions.
20 changes: 14 additions & 6 deletions src/app/(auth)/signup/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { lucia } from "@/auth";
import prisma from "@/lib/prisma";
import streamServerClient from "@/lib/stream";
import { signUpSchema, SignUpValues } from "@/lib/validation";
import { hash } from "@node-rs/argon2";
import { generateIdFromEntropySize } from "lucia";
Expand Down Expand Up @@ -54,14 +55,21 @@ export async function signUp(
};
}

await prisma.user.create({
data: {
await prisma.$transaction(async (tx) => {
await tx.user.create({
data: {
id: userId,
username,
displayName: username,
email,
passwordHash,
},
});
await streamServerClient.upsertUser({
id: userId,
username,
displayName: username,
email,
passwordHash,
},
name: username,
});
});

const session = await lucia.createSession(userId, {});
Expand Down
45 changes: 45 additions & 0 deletions src/app/(main)/messages/Chat.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use client";

import { Loader2 } from "lucide-react";
import { useTheme } from "next-themes";
import { useState } from "react";
import { Chat as StreamChat } from "stream-chat-react";
import ChatChannel from "./ChatChannel";
import ChatSidebar from "./ChatSidebar";
import useInitializeChatClient from "./useInitializeChatClient";

export default function Chat() {
const chatClient = useInitializeChatClient();

const { resolvedTheme } = useTheme();

const [sidebarOpen, setSidebarOpen] = useState(false);

if (!chatClient) {
return <Loader2 className="mx-auto my-3 animate-spin" />;
}

return (
<main className="relative w-full overflow-hidden rounded-2xl bg-card shadow-sm">
<div className="absolute bottom-0 top-0 flex w-full">
<StreamChat
client={chatClient}
theme={
resolvedTheme === "dark"
? "str-chat__theme-dark"
: "str-chat__theme-light"
}
>
<ChatSidebar
open={sidebarOpen}
onClose={() => setSidebarOpen(false)}
/>
<ChatChannel
open={!sidebarOpen}
openSidebar={() => setSidebarOpen(true)}
/>
</StreamChat>
</div>
</main>
);
}
50 changes: 50 additions & 0 deletions src/app/(main)/messages/ChatChannel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { Menu } from "lucide-react";
import {
Channel,
ChannelHeader,
ChannelHeaderProps,
MessageInput,
MessageList,
Window,
} from "stream-chat-react";

interface ChatChannelProps {
open: boolean;
openSidebar: () => void;
}

export default function ChatChannel({ open, openSidebar }: ChatChannelProps) {
return (
<div className={cn("w-full md:block", !open && "hidden")}>
<Channel>
<Window>
<CustomChannelHeader openSidebar={openSidebar} />
<MessageList />
<MessageInput />
</Window>
</Channel>
</div>
);
}

interface CustomChannelHeaderProps extends ChannelHeaderProps {
openSidebar: () => void;
}

function CustomChannelHeader({
openSidebar,
...props
}: CustomChannelHeaderProps) {
return (
<div className="flex items-center gap-3">
<div className="h-full p-2 md:hidden">
<Button size="icon" variant="ghost" onClick={openSidebar}>
<Menu className="size-5" />
</Button>
</div>
<ChannelHeader {...props} />
</div>
);
}
100 changes: 100 additions & 0 deletions src/app/(main)/messages/ChatSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { MailPlus, X } from "lucide-react";
import { useCallback, useState } from "react";
import {
ChannelList,
ChannelPreviewMessenger,
ChannelPreviewUIComponentProps,
} from "stream-chat-react";
import { useSession } from "../SessionProvider";
import NewChatDialog from "./NewChatDialog";

interface ChatSidebarProps {
open: boolean;
onClose: () => void;
}

export default function ChatSidebar({ open, onClose }: ChatSidebarProps) {
const { user } = useSession();

const ChannelPreviewCustom = useCallback(
(props: ChannelPreviewUIComponentProps) => (
<ChannelPreviewMessenger
{...props}
onSelect={() => {
props.setActiveChannel?.(props.channel, props.watchers);
onClose();
}}
/>
),
[onClose],
);

return (
<div
className={cn(
"size-full flex-col border-e md:flex md:w-72",
open ? "flex" : "hidden",
)}
>
<MenuHeader onClose={onClose} />
<ChannelList
filters={{
type: "messaging",
members: { $in: [user.id] },
}}
showChannelSearch
options={{ state: true, presence: true, limit: 8 }}
sort={{ last_message_at: -1 }}
additionalChannelSearchProps={{
searchForChannels: true,
searchQueryParams: {
channelFilters: {
filters: { members: { $in: [user.id] } },
},
},
}}
Preview={ChannelPreviewCustom}
/>
</div>
);
}

interface MenuHeaderProps {
onClose: () => void;
}

function MenuHeader({ onClose }: MenuHeaderProps) {
const [showNewChatDialog, setShowNewChatDialog] = useState(false);

return (
<>
<div className="flex items-center gap-3 p-2">
<div className="h-full md:hidden">
<Button size="icon" variant="ghost" onClick={onClose}>
<X className="size-5" />
</Button>
</div>
<h1 className="me-auto text-xl font-bold md:ms-2">Messages</h1>
<Button
size="icon"
variant="ghost"
title="Start new chat"
onClick={() => setShowNewChatDialog(true)}
>
<MailPlus className="size-5" />
</Button>
</div>
{showNewChatDialog && (
<NewChatDialog
onOpenChange={setShowNewChatDialog}
onChatCreated={() => {
setShowNewChatDialog(false);
onClose();
}}
/>
)}
</>
);
}
Loading

0 comments on commit 2f1e8f6

Please sign in to comment.