Skip to content

Commit

Permalink
Merge pull request #49 from ETLOnline/feature/30-chat-screen-and-real…
Browse files Browse the repository at this point in the history
…time-chat-with-emoji

feat(issue-30): chat screen plus functionality and useful components
  • Loading branch information
usama-tariq1 authored Jan 1, 2025
2 parents 536c5f4 + ac71405 commit a7eed42
Show file tree
Hide file tree
Showing 56 changed files with 2,251 additions and 354 deletions.
7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
},
"dependencies": {
"@clerk/nextjs": "^6.5.1",
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@libsql/client": "^0.14.0",
"@nivo/bar": "^0.88.0",
"@nivo/core": "^0.88.0",
Expand All @@ -20,13 +22,15 @@
"@radix-ui/react-dialog": "^1.1.3",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-radio-group": "^1.2.1",
"@radix-ui/react-scroll-area": "^1.2.1",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.4",
"@radix-ui/react-tooltip": "^1.1.4",
"ably": "^2.6.0",
"class-variance-authority": "^0.7.1",
Expand All @@ -36,10 +40,13 @@
"dotenv": "^16.4.5",
"drizzle-orm": "^0.36.4",
"embla-carousel-react": "^8.5.1",
"emoji-mart": "^5.6.0",
"framer-motion": "^11.15.0",
"jotai": "^2.10.3",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.462.0",
"moment": "^2.30.1",
"moment-timezone": "^0.5.46",
"next": "14.2.16",
"next-themes": "^0.4.4",
"react": "^18",
Expand Down
38 changes: 36 additions & 2 deletions src/app/(dashboard)/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,41 @@
import { ChatScreen } from "@/src/components/dashboard/Chat";
import { SelectChat } from "@/src/db/schema";
import { GetChatBySlugWithMessagesAction, GetUserChatsAction } from "@/src/server-actions/Chat/Chat";

export default function ChatPage() {
return <ChatScreen />
interface ChatPageProps {
searchParams:{
active_chat?: string
}
}

export default async function ChatPage({ searchParams }: ChatPageProps) {
let currentChat : SelectChat | undefined = undefined
let allChats : SelectChat[] = []
let selectedCurrectChatSlug = null

const chatsRes = await GetUserChatsAction()
if(chatsRes?.success && chatsRes?.data){
allChats = chatsRes.data
}

if( allChats.length > 0 ){

if(searchParams.active_chat){
selectedCurrectChatSlug = searchParams.active_chat
}else{
selectedCurrectChatSlug = allChats[0].chat_slug
}

const currentChatRes = await GetChatBySlugWithMessagesAction(selectedCurrectChatSlug)
if(currentChatRes.success){
currentChat = currentChatRes.data
}
}


return (
<ChatScreen allChatsSSR={allChats} currentChatSSR={currentChat} />
)
}


Expand Down
2 changes: 1 addition & 1 deletion src/app/(dashboard)/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AnalyticsDashboard } from '@/src/components/dashboard/AnalyticsDashboard'
import AnalyticsDashboard from '@/src/components/dashboard/AnalyticsDashboard'
import React from 'react'

const page = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function DashboardLayout({ children }: { children: ReactNode }) {
<AppSidebar />
<SidebarInset>
<Header />
<div className="flex flex-1 flex-col gap-4 p-4 pt-0">
<div className="flex flex-1 flex-col gap-4 p-4 pt-0 ">
{children}
</div>
</SidebarInset>
Expand Down
6 changes: 1 addition & 5 deletions src/app/(dashboard)/posts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@

import { useState } from "react"
import PostFeed from "@/src/components/dashboard/post-feed"
import {
Post,
PostFile,
PostPoll
} from "@/src/components/dashboard/Posts/types/posts-types"
import CreatePostForm from "@/src/components/dashboard/create-post-form"
import { Post, PostFile, PostPoll } from "@/src/components/dashboard/posts/types/posts-types"

const samplePosts: (Post | PostFile | PostPoll)[] = [
{
Expand Down
6 changes: 3 additions & 3 deletions src/app/(home)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const PublicLayout = ({ children }: { children: ReactNode }) => {
return (
<>
<Header />
<div className="content">
{children}
</div>
<div className="content">
{children}
</div>
<Footer />

</>
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/webhook/user-created/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Webhook } from 'svix'
import { headers } from 'next/headers'
import { WebhookEvent } from '@clerk/nextjs/server'
import { InsertUser } from '@/src/db/schema'
import { CreateUser, SelectUserByEmail, SelectUserById } from '@/src/db/data-access/user/query'
import { CreateUser, SelectUserByEmail, SelectUserByExternalId } from '@/src/db/data-access/user/query'

export async function POST(req: Request) {

Expand Down Expand Up @@ -60,7 +60,7 @@ export async function POST(req: Request) {
if (evt.type === 'user.created') {
const userObj = evt.data

const userById = await SelectUserById(userObj.id)
const userById = await SelectUserByExternalId(userObj.id)
const userByEmail = await SelectUserByEmail(userObj.email_addresses[0].email_address)

if (userById || userByEmail) {
Expand Down
4 changes: 3 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import "./globals.css";
import {
ClerkProvider
} from '@clerk/nextjs'
import { ThemeProvider } from "../components/ThemeProvider/ThemeProvider";
import ClerkAuthListener from "../services/auth/ClerkAuthListner";
import ThemeProvider from "../components/ThemeProvider/ThemeProvider";
import { Toaster } from "../components/ui/toaster";

const geistSans = localFont({
src: "./fonts/GeistVF.woff",
Expand Down Expand Up @@ -41,6 +42,7 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
<Toaster/>
{children}
</ThemeProvider>
</body>
Expand Down
5 changes: 4 additions & 1 deletion src/components/HomeLayoutComponents/HeaderComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { Suspense } from 'react'
import './header.css'
import { LinkAsButton } from '@/src/components/LinkAsButton/LinkAsButton'
import { SignedIn, SignedOut, UserButton } from '@clerk/nextjs'
Expand All @@ -8,6 +8,8 @@ import ModeToggle from '../ThemeProvider/ThemeToggle'
function Header() {
return (
<div className="header">
<Suspense>

<Navbar />
<div className="button-wrapper">
<SignedIn>
Expand All @@ -19,6 +21,7 @@ function Header() {
</SignedOut>
<ModeToggle />
</div>
</Suspense>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ThemeProvider/ThemeProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"

export function ThemeProvider({
export default function ThemeProvider({
children,
...props
}: React.ComponentProps<typeof NextThemesProvider>) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/AnalyticsDashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const topProjects = [
{ name: "Cybersecurity Toolkit", category: "Cybersecurity", contributors: 7, progress: 30 }
]

export function AnalyticsDashboard() {
export default function AnalyticsDashboard() {
const [timeRange, setTimeRange] = useState("7d")

return (
Expand Down
83 changes: 83 additions & 0 deletions src/components/dashboard/Chat/ChatsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { ScrollArea } from '@radix-ui/react-scroll-area'
import React, { useEffect, useState } from 'react'
import { Avatar, AvatarFallback, AvatarImage } from '../../ui/avatar'
import { Badge } from '../../ui/badge'
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
import { chatStore } from '@/src/store/chat/chatStore'
import { userStore } from '@/src/store/user/userStore'
import { SelectChat, SelectUser } from '@/src/db/schema'
import Loader from '../../common/Loader/Loader'

interface ChatsListProps {
setIsMobileMenuOpen: React.Dispatch<React.SetStateAction<boolean>>
}



const ChatsList = ({ setIsMobileMenuOpen }: ChatsListProps) => {

const [currentChat, setCurrentChat] = useAtom(chatStore.currentChat)
const setSwtichedChat = useSetAtom(chatStore.switchedChat)
const [myChats, setMyChats] = useAtom(chatStore.myChats)
const authUser = useAtomValue(userStore.AuthUser)

const ChatItem = React.useMemo( () => ({chat}: {chat: SelectChat}) => {
const filteredContact = chat?.users?.find((user) => user.user_id !== authUser?.unique_id)
if(!filteredContact) return null
const chatContact = filteredContact?.user || null
if(!chatContact) return null

return (
<div
key={chat.id}
className={`flex items-center space-x-4 p-3 rounded-lg cursor-pointer transition-colors ${
currentChat?.id === chat.id ? "bg-secondary" : "hover:bg-secondary/50"
}`}
onClick={() => {
setSwtichedChat(chat || null)
setIsMobileMenuOpen(false)
}}
>
<Avatar className="h-10 w-10 relative bg-white" >
{
!chat?.is_group && chatContact ? (
<>
{chatContact?.profile_url ? (<AvatarImage src={chatContact.profile_url} />): null }
<AvatarFallback>{chatContact.first_name.charAt(0)}</AvatarFallback>
</>
): null
}
{/* {chat.online && (
<span className="absolute bottom-0 right-0 h-3 w-3 rounded-full bg-green-500 border-2 border-white"></span>
)} */}
</Avatar>
<div className="flex-1">
<p className="font-medium">{chat.is_group ? chat.name :`${chatContact?.first_name} ${chatContact?.last_name}`}</p>
<p className="text-sm text-muted-foreground truncate">{chat?.last_message}</p>
</div>
{(chat?.unread_count || 0) > 0 && (
<Badge variant="secondary" className="rounded-full px-2 py-1">
{chat?.unread_count}
</Badge>
)}
</div>
)
}, [authUser, currentChat, setCurrentChat, setIsMobileMenuOpen])


return (
<ScrollArea className="h-[calc(100vh-20rem)]">
{
authUser ? (
<>
{myChats.map((chat) => (
<ChatItem key={chat.id} chat={chat} />
))}
</>
): <div className='flex items-center justify-center'><Loader /></div>
}
</ScrollArea>
)
}

export default ChatsList
9 changes: 5 additions & 4 deletions src/components/dashboard/Chat/TestChat/TestChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
import React, { useState } from 'react';
import * as Ably from 'ably';
import { AblyProvider, ChannelProvider, useChannel, useConnectionStateListener } from 'ably/react';
import AblyChannelProviderWrapper, { AblyClient } from '@/src/services/realtime/AblyClient';
// import AblyChannelProviderWrapper, { AblyClient } from '@/src/services/realtime/AblyClient';

// Connect to Ably using the AblyProvider component and your API key

export default function TestChat() {
return (
<AblyChannelProviderWrapper channelName="get-started">
<AblyPubSub />
</AblyChannelProviderWrapper>
// <AblyChannelProviderWrapper channelName="get-started">
// <AblyPubSub />
// </AblyChannelProviderWrapper>
<></>
);
}

Expand Down
3 changes: 3 additions & 0 deletions src/components/dashboard/Chat/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const ChatChannelSalt = "Chat-Check";

export const ChatChannelPrefix = "Chat-";
8 changes: 8 additions & 0 deletions src/components/dashboard/Chat/helper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ChatChannelPrefix, ChatChannelSalt } from "../constants"

const ChatChannelHash = (channelId:string)=>{
const channelHash = crypto.subtle.digest('SHA-256', new TextEncoder().encode(ChatChannelSalt + channelId))
return `${ChatChannelPrefix}${channelHash}`
}

export default ChatChannelHash
Loading

0 comments on commit a7eed42

Please sign in to comment.