Merge pull request #33 from pennyteenycat/notification-sound
Notification sound
This commit is contained in:
commit
630ebaaf1b
7
global.d.ts
vendored
Normal file
7
global.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
webkitAudioContext: typeof AudioContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
BIN
public/audio/notification-new-message-1.mp3
Normal file
BIN
public/audio/notification-new-message-1.mp3
Normal file
Binary file not shown.
BIN
public/audio/notification-new-message-1.wav
Normal file
BIN
public/audio/notification-new-message-1.wav
Normal file
Binary file not shown.
@ -4,14 +4,13 @@ import {
|
|||||||
ChatModalsWrapper,
|
ChatModalsWrapper,
|
||||||
MessageInputWrapper,
|
MessageInputWrapper,
|
||||||
} from "@/components/domains/chat";
|
} from "@/components/domains/chat";
|
||||||
import { loadChatsList } from "@/entities/chats/loaders";
|
|
||||||
|
|
||||||
import styles from "./page.module.scss";
|
import styles from "./page.module.scss";
|
||||||
|
|
||||||
export default function Chat() {
|
export default function Chat() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<ChatHeader chatsPromise={loadChatsList()} />
|
<ChatHeader />
|
||||||
<ChatMessagesWrapper />
|
<ChatMessagesWrapper />
|
||||||
<MessageInputWrapper />
|
<MessageInputWrapper />
|
||||||
<ChatModalsWrapper />
|
<ChatModalsWrapper />
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import {
|
|||||||
NewMessagesWrapperSkeleton,
|
NewMessagesWrapperSkeleton,
|
||||||
} from "@/components/domains/chat";
|
} from "@/components/domains/chat";
|
||||||
import { NavigationBar } from "@/components/layout";
|
import { NavigationBar } from "@/components/layout";
|
||||||
import { loadChatsList } from "@/entities/chats/loaders";
|
|
||||||
|
|
||||||
import styles from "./page.module.scss";
|
import styles from "./page.module.scss";
|
||||||
|
|
||||||
@ -19,23 +18,21 @@ export const revalidate = 0;
|
|||||||
export const fetchCache = "force-no-store";
|
export const fetchCache = "force-no-store";
|
||||||
|
|
||||||
export default function Chats() {
|
export default function Chats() {
|
||||||
const chatsPromise = loadChatsList();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<ChatListHeader />
|
<ChatListHeader />
|
||||||
<section className={styles.categories}>
|
<section className={styles.categories}>
|
||||||
<Suspense fallback={<NewMessagesWrapperSkeleton />}>
|
<Suspense fallback={<NewMessagesWrapperSkeleton />}>
|
||||||
<NewMessagesWrapper chatsPromise={chatsPromise} />
|
<NewMessagesWrapper />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Suspense fallback={<CorrespondenceStartedSkeleton />}>
|
<Suspense fallback={<CorrespondenceStartedSkeleton />}>
|
||||||
<CorrespondenceStartedWrapper chatsPromise={chatsPromise} />
|
<CorrespondenceStartedWrapper />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Suspense fallback={<ChatCategoriesSkeleton />}>
|
<Suspense fallback={<ChatCategoriesSkeleton />}>
|
||||||
<ChatCategories chatsPromise={chatsPromise} />
|
<ChatCategories />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</section>
|
</section>
|
||||||
<NavigationBar chatsPromise={chatsPromise} />
|
<NavigationBar />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { DrawerProvider, Header, NavigationBar } from "@/components/layout";
|
import { DrawerProvider, Header, NavigationBar } from "@/components/layout";
|
||||||
import { loadChatsList } from "@/entities/chats/loaders";
|
|
||||||
import { ChatStoreProvider } from "@/providers/chat-store-provider";
|
import { ChatStoreProvider } from "@/providers/chat-store-provider";
|
||||||
|
|
||||||
import styles from "./layout.module.scss";
|
import styles from "./layout.module.scss";
|
||||||
@ -9,13 +8,12 @@ export default function CoreLayout({
|
|||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
const chatsPromise = loadChatsList();
|
|
||||||
return (
|
return (
|
||||||
<DrawerProvider>
|
<DrawerProvider>
|
||||||
<ChatStoreProvider>
|
<ChatStoreProvider>
|
||||||
<Header className={styles.navBar} chatsPromise={chatsPromise} />
|
<Header className={styles.navBar} />
|
||||||
<main className={styles.main}>{children}</main>
|
<main className={styles.main}>{children}</main>
|
||||||
<NavigationBar chatsPromise={chatsPromise} />
|
<NavigationBar />
|
||||||
</ChatStoreProvider>
|
</ChatStoreProvider>
|
||||||
</DrawerProvider>
|
</DrawerProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<section className={styles.page}>
|
<section className={styles.page}>
|
||||||
<Suspense fallback={<NewMessagesSectionSkeleton />}>
|
<Suspense fallback={<NewMessagesSectionSkeleton />}>
|
||||||
<NewMessagesSection chatsPromise={chatsPromise} />
|
<NewMessagesSection />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<Horoscope />
|
<Horoscope />
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { DrawerProvider, Header } from "@/components/layout";
|
import { DrawerProvider, Header } from "@/components/layout";
|
||||||
import { loadChatsList } from "@/entities/chats/loaders";
|
|
||||||
|
|
||||||
import styles from "./layout.module.scss";
|
import styles from "./layout.module.scss";
|
||||||
|
|
||||||
@ -10,7 +9,7 @@ export default function CoreLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<DrawerProvider>
|
<DrawerProvider>
|
||||||
<Header className={styles.navBar} chatsPromise={loadChatsList()} />
|
<Header className={styles.navBar} />
|
||||||
<main className={styles.main}>{children}</main>
|
<main className={styles.main}>{children}</main>
|
||||||
</DrawerProvider>
|
</DrawerProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -10,10 +10,13 @@ import { getMessages } from "next-intl/server";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import YandexMetrika from "@/components/analytics/YandexMetrika";
|
import YandexMetrika from "@/components/analytics/YandexMetrika";
|
||||||
|
import { loadChatsList } from "@/entities/chats/loaders";
|
||||||
import { loadUser, loadUserId } from "@/entities/user/loaders";
|
import { loadUser, loadUserId } from "@/entities/user/loaders";
|
||||||
import { routing } from "@/i18n/routing";
|
import { routing } from "@/i18n/routing";
|
||||||
import { AppUiStoreProvider } from "@/providers/app-ui-store-provider";
|
import { AppUiStoreProvider } from "@/providers/app-ui-store-provider";
|
||||||
|
import { AudioProvider } from "@/providers/audio-provider";
|
||||||
import { ChatsInitializationProvider } from "@/providers/chats-initialization-provider";
|
import { ChatsInitializationProvider } from "@/providers/chats-initialization-provider";
|
||||||
|
import { ChatsProvider } from "@/providers/chats-provider";
|
||||||
import { RetainingStoreProvider } from "@/providers/retaining-store-provider";
|
import { RetainingStoreProvider } from "@/providers/retaining-store-provider";
|
||||||
import SocketProvider from "@/providers/socket-provider";
|
import SocketProvider from "@/providers/socket-provider";
|
||||||
import { ToastProvider } from "@/providers/toast-provider";
|
import { ToastProvider } from "@/providers/toast-provider";
|
||||||
@ -60,6 +63,7 @@ export default async function RootLayout({
|
|||||||
|
|
||||||
const user = await loadUser();
|
const user = await loadUser();
|
||||||
const userId = await loadUserId();
|
const userId = await loadUserId();
|
||||||
|
const chats = await loadChatsList();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale}>
|
<html lang={locale}>
|
||||||
@ -69,11 +73,15 @@ export default async function RootLayout({
|
|||||||
<UserProvider user={user}>
|
<UserProvider user={user}>
|
||||||
<SocketProvider userId={userId}>
|
<SocketProvider userId={userId}>
|
||||||
<RetainingStoreProvider>
|
<RetainingStoreProvider>
|
||||||
<ChatsInitializationProvider>
|
<AudioProvider>
|
||||||
<ToastProvider maxVisible={3}>
|
<ChatsInitializationProvider>
|
||||||
<AppUiStoreProvider>{children}</AppUiStoreProvider>
|
<ChatsProvider initialChats={chats}>
|
||||||
</ToastProvider>
|
<ToastProvider maxVisible={3}>
|
||||||
</ChatsInitializationProvider>
|
<AppUiStoreProvider>{children}</AppUiStoreProvider>
|
||||||
|
</ToastProvider>
|
||||||
|
</ChatsProvider>
|
||||||
|
</ChatsInitializationProvider>
|
||||||
|
</AudioProvider>
|
||||||
</RetainingStoreProvider>
|
</RetainingStoreProvider>
|
||||||
</SocketProvider>
|
</SocketProvider>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { use, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { Skeleton } from "@/components/ui";
|
import { Skeleton } from "@/components/ui";
|
||||||
import { Chips } from "@/components/widgets";
|
import { Chips } from "@/components/widgets";
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
import { useChats } from "@/providers/chats-provider";
|
||||||
import { useChatsSocket } from "@/hooks/chats/useChatsSocket";
|
|
||||||
|
|
||||||
import { CategoryChats, ChatItemsList } from "..";
|
import { CategoryChats, ChatItemsList } from "..";
|
||||||
|
|
||||||
const MAX_HIDE_VISIBLE_COUNT = 3;
|
const MAX_HIDE_VISIBLE_COUNT = 3;
|
||||||
|
|
||||||
interface ChatCategoriesProps {
|
export default function ChatCategories() {
|
||||||
chatsPromise: Promise<IGetChatsListResponse>;
|
const { categorizedChats } = useChats();
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChatCategories({ chatsPromise }: ChatCategoriesProps) {
|
|
||||||
const chats = use(chatsPromise);
|
|
||||||
const { categorizedChats } = useChatsSocket({ initialChats: chats });
|
|
||||||
|
|
||||||
const [activeChip, setActiveChip] = useState<string>("All");
|
const [activeChip, setActiveChip] = useState<string>("All");
|
||||||
const [maxVisibleChats, setMaxVisibleChats] = useState<
|
const [maxVisibleChats, setMaxVisibleChats] = useState<
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { use, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
@ -13,26 +13,20 @@ import {
|
|||||||
UserAvatar,
|
UserAvatar,
|
||||||
} from "@/components/ui";
|
} from "@/components/ui";
|
||||||
import { revalidateChatsPage } from "@/entities/chats/actions";
|
import { revalidateChatsPage } from "@/entities/chats/actions";
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
|
||||||
import { useChatsSocket } from "@/hooks/chats/useChatsSocket";
|
|
||||||
import { useChat } from "@/providers/chat-provider";
|
import { useChat } from "@/providers/chat-provider";
|
||||||
import { useChatStore } from "@/providers/chat-store-provider";
|
import { useChatStore } from "@/providers/chat-store-provider";
|
||||||
|
import { useChats } from "@/providers/chats-provider";
|
||||||
import { formatSecondsToHHMMSS } from "@/shared/utils/date";
|
import { formatSecondsToHHMMSS } from "@/shared/utils/date";
|
||||||
import { delay } from "@/shared/utils/delay";
|
import { delay } from "@/shared/utils/delay";
|
||||||
|
|
||||||
import styles from "./ChatHeader.module.scss";
|
import styles from "./ChatHeader.module.scss";
|
||||||
|
|
||||||
interface ChatHeaderProps {
|
export default function ChatHeader() {
|
||||||
chatsPromise: Promise<IGetChatsListResponse>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChatHeader({ chatsPromise }: ChatHeaderProps) {
|
|
||||||
const t = useTranslations("Chat");
|
const t = useTranslations("Chat");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const currentChat = useChatStore(state => state.currentChat);
|
const currentChat = useChatStore(state => state.currentChat);
|
||||||
const { isLoadingAdvisorMessage, isAvailableChatting } = useChat();
|
const { isLoadingAdvisorMessage, isAvailableChatting } = useChat();
|
||||||
const chats = use(chatsPromise);
|
const { totalUnreadCount } = useChats();
|
||||||
const { totalUnreadCount } = useChatsSocket({ initialChats: chats });
|
|
||||||
const [timer, setTimer] = useState(0);
|
const [timer, setTimer] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,25 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { use } from "react";
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { Skeleton } from "@/components/ui";
|
import { Skeleton } from "@/components/ui";
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
|
||||||
import { useChatsSocket } from "@/hooks/chats/useChatsSocket";
|
|
||||||
import { useAppUiStore } from "@/providers/app-ui-store-provider";
|
import { useAppUiStore } from "@/providers/app-ui-store-provider";
|
||||||
|
import { useChats } from "@/providers/chats-provider";
|
||||||
|
|
||||||
import { ChatItemsList, CorrespondenceStarted } from "..";
|
import { ChatItemsList, CorrespondenceStarted } from "..";
|
||||||
|
|
||||||
interface CorrespondenceStartedWrapperProps {
|
export default function CorrespondenceStartedWrapper() {
|
||||||
chatsPromise: Promise<IGetChatsListResponse>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CorrespondenceStartedWrapper({
|
|
||||||
chatsPromise,
|
|
||||||
}: CorrespondenceStartedWrapperProps) {
|
|
||||||
const t = useTranslations("Chat");
|
const t = useTranslations("Chat");
|
||||||
const chats = use(chatsPromise);
|
const { startedChats } = useChats();
|
||||||
const { startedChats } = useChatsSocket({ initialChats: chats });
|
|
||||||
|
|
||||||
const { isVisibleAll } = useAppUiStore(
|
const { isVisibleAll } = useAppUiStore(
|
||||||
state => state.chats.correspondenceStarted
|
state => state.chats.correspondenceStarted
|
||||||
|
|||||||
@ -1,25 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { use } from "react";
|
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
import { Skeleton } from "@/components/ui";
|
import { Skeleton } from "@/components/ui";
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
|
||||||
import { useChatsSocket } from "@/hooks/chats/useChatsSocket";
|
|
||||||
import { useAppUiStore } from "@/providers/app-ui-store-provider";
|
import { useAppUiStore } from "@/providers/app-ui-store-provider";
|
||||||
|
import { useChats } from "@/providers/chats-provider";
|
||||||
|
|
||||||
import { ChatItemsList, NewMessages } from "..";
|
import { ChatItemsList, NewMessages } from "..";
|
||||||
|
|
||||||
interface NewMessagesWrapperProps {
|
export default function NewMessagesWrapper() {
|
||||||
chatsPromise: Promise<IGetChatsListResponse>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NewMessagesWrapper({
|
|
||||||
chatsPromise,
|
|
||||||
}: NewMessagesWrapperProps) {
|
|
||||||
const t = useTranslations("Chat");
|
const t = useTranslations("Chat");
|
||||||
const chats = use(chatsPromise);
|
const { unreadChats } = useChats();
|
||||||
const { unreadChats } = useChatsSocket({ initialChats: chats });
|
|
||||||
|
|
||||||
const { isVisibleAll } = useAppUiStore(state => state.chats.newMessages);
|
const { isVisibleAll } = useAppUiStore(state => state.chats.newMessages);
|
||||||
const hasHydrated = useAppUiStore(state => state._hasHydrated);
|
const hasHydrated = useAppUiStore(state => state._hasHydrated);
|
||||||
|
|||||||
@ -1,24 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { use } from "react";
|
|
||||||
|
|
||||||
import { NewMessages, ViewAll } from "@/components/domains/chat";
|
import { NewMessages, ViewAll } from "@/components/domains/chat";
|
||||||
import { Skeleton } from "@/components/ui";
|
import { Skeleton } from "@/components/ui";
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
|
||||||
import { useChatsSocket } from "@/hooks/chats/useChatsSocket";
|
|
||||||
import { useAppUiStore } from "@/providers/app-ui-store-provider";
|
import { useAppUiStore } from "@/providers/app-ui-store-provider";
|
||||||
|
import { useChats } from "@/providers/chats-provider";
|
||||||
|
|
||||||
import styles from "./NewMessagesSection.module.scss";
|
import styles from "./NewMessagesSection.module.scss";
|
||||||
|
|
||||||
interface NewMessagesSectionProps {
|
export default function NewMessagesSection() {
|
||||||
chatsPromise: Promise<IGetChatsListResponse>;
|
const { unreadChats } = useChats();
|
||||||
}
|
|
||||||
|
|
||||||
export default function NewMessagesSection({
|
|
||||||
chatsPromise,
|
|
||||||
}: NewMessagesSectionProps) {
|
|
||||||
const chats = use(chatsPromise);
|
|
||||||
const { unreadChats } = useChatsSocket({ initialChats: chats });
|
|
||||||
|
|
||||||
const { isVisibleAll } = useAppUiStore(state => state.home.newMessages);
|
const { isVisibleAll } = useAppUiStore(state => state.home.newMessages);
|
||||||
const hasHydrated = useAppUiStore(state => state._hasHydrated);
|
const hasHydrated = useAppUiStore(state => state._hasHydrated);
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { use } from "react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import { Badge, Button, Icon, IconName, Typography } from "@/components/ui";
|
import { Badge, Button, Icon, IconName, Typography } from "@/components/ui";
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
import { useChats } from "@/providers/chats-provider";
|
||||||
import { useChatsSocket } from "@/hooks/chats/useChatsSocket";
|
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
|
|
||||||
import { useDrawer } from "..";
|
import { useDrawer } from "..";
|
||||||
@ -16,13 +14,11 @@ import styles from "./Header.module.scss";
|
|||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
chatsPromise: Promise<IGetChatsListResponse>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Header({ className, chatsPromise }: HeaderProps) {
|
export default function Header({ className }: HeaderProps) {
|
||||||
const { open } = useDrawer();
|
const { open } = useDrawer();
|
||||||
const chats = use(chatsPromise);
|
const { totalUnreadCount } = useChats();
|
||||||
const { totalUnreadCount } = useChatsSocket({ initialChats: chats });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={clsx(styles.header, className)}>
|
<header className={clsx(styles.header, className)}>
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { use } from "react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import { useLocale } from "next-intl";
|
import { useLocale } from "next-intl";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
import { Badge, Icon, Typography } from "@/components/ui";
|
import { Badge, Icon, Typography } from "@/components/ui";
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
import { useChats } from "@/providers/chats-provider";
|
||||||
import { useChatsSocket } from "@/hooks/chats/useChatsSocket";
|
|
||||||
import { ROUTES } from "@/shared/constants/client-routes";
|
import { ROUTES } from "@/shared/constants/client-routes";
|
||||||
import { NavItem, navItems } from "@/shared/constants/navigation";
|
import { NavItem, navItems } from "@/shared/constants/navigation";
|
||||||
import { stripLocale } from "@/shared/utils/path";
|
import { stripLocale } from "@/shared/utils/path";
|
||||||
@ -20,16 +18,11 @@ const getBadge = (item: NavItem, totalUnreadCount: number) => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface NavigationBarProps {
|
export default function NavigationBar() {
|
||||||
chatsPromise: Promise<IGetChatsListResponse>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NavigationBar({ chatsPromise }: NavigationBarProps) {
|
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const locale = useLocale();
|
const locale = useLocale();
|
||||||
const pathnameWithoutLocale = stripLocale(pathname, locale);
|
const pathnameWithoutLocale = stripLocale(pathname, locale);
|
||||||
const chats = use(chatsPromise);
|
const { totalUnreadCount } = useChats();
|
||||||
const { totalUnreadCount } = useChatsSocket({ initialChats: chats });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={styles.container}>
|
<nav className={styles.container}>
|
||||||
|
|||||||
50
src/hooks/audio/useAudio.ts
Normal file
50
src/hooks/audio/useAudio.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
|
|
||||||
|
export const useAudio = () => {
|
||||||
|
const audio = useMemo(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const audioElement = new Audio("/audio/notification-new-message-1.wav");
|
||||||
|
audioElement.preload = "auto";
|
||||||
|
return audioElement;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const _audioContext = useMemo(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
return new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!audio) return;
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
audio.currentTime = 0;
|
||||||
|
audio.play();
|
||||||
|
setTimeout(() => {
|
||||||
|
audio.pause();
|
||||||
|
}, 10);
|
||||||
|
};
|
||||||
|
document.addEventListener("click", handleClick, { once: true });
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("click", handleClick);
|
||||||
|
};
|
||||||
|
}, [audio]);
|
||||||
|
|
||||||
|
const playNewMessageNotification = useCallback(() => {
|
||||||
|
if (!audio) return;
|
||||||
|
audio.currentTime = 0;
|
||||||
|
audio.play();
|
||||||
|
}, [audio]);
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => ({
|
||||||
|
playNewMessageNotification,
|
||||||
|
}),
|
||||||
|
[playNewMessageNotification]
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -3,11 +3,13 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
import { IGetChatsListResponse } from "@/entities/chats/types";
|
||||||
|
import { useAudioContext } from "@/providers/audio-provider";
|
||||||
|
|
||||||
import { useSocketEvent } from "../socket/useSocketEvent";
|
import { useSocketEvent } from "../socket/useSocketEvent";
|
||||||
|
|
||||||
interface UseChatsSocketOptions {
|
export interface UseChatsSocketOptions {
|
||||||
initialChats?: IGetChatsListResponse;
|
initialChats?: IGetChatsListResponse;
|
||||||
|
enableNotificationSound?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
||||||
@ -18,14 +20,25 @@ export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
|||||||
totalUnreadCount: 0,
|
totalUnreadCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { playNewMessageNotification } = useAudioContext();
|
||||||
|
|
||||||
|
const [isInChat, setIsInChat] = useState(false);
|
||||||
const [chats, setChats] = useState<IGetChatsListResponse>(initialChats);
|
const [chats, setChats] = useState<IGetChatsListResponse>(initialChats);
|
||||||
const [unreadCount, setUnreadCount] = useState<number>(
|
const [unreadCount, setUnreadCount] = useState<number>(
|
||||||
initialChats.totalUnreadCount
|
initialChats.totalUnreadCount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useSocketEvent("chat_joined", () => setIsInChat(true));
|
||||||
|
useSocketEvent("chat_left", () => setIsInChat(false));
|
||||||
|
|
||||||
useSocketEvent("chats_updated", chats => setChats(chats));
|
useSocketEvent("chats_updated", chats => setChats(chats));
|
||||||
useSocketEvent("unread_messages_count", count =>
|
useSocketEvent("unread_messages_count", count =>
|
||||||
setUnreadCount(count.unreadCount)
|
setUnreadCount(prev => {
|
||||||
|
if (!isInChat && prev < count.unreadCount) {
|
||||||
|
playNewMessageNotification();
|
||||||
|
}
|
||||||
|
return count.unreadCount;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
@ -35,7 +48,8 @@ export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
|||||||
startedChats: chats.startedChats,
|
startedChats: chats.startedChats,
|
||||||
categorizedChats: chats.categorizedChats,
|
categorizedChats: chats.categorizedChats,
|
||||||
totalUnreadCount: unreadCount,
|
totalUnreadCount: unreadCount,
|
||||||
|
isInChat,
|
||||||
}),
|
}),
|
||||||
[chats, unreadCount]
|
[chats, unreadCount, isInChat]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
29
src/providers/audio-provider.tsx
Normal file
29
src/providers/audio-provider.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, ReactNode, useContext } from "react";
|
||||||
|
|
||||||
|
import { useAudio } from "@/hooks/audio/useAudio";
|
||||||
|
|
||||||
|
type AudioContextValue = ReturnType<typeof useAudio>;
|
||||||
|
|
||||||
|
const AudioContext = createContext<AudioContextValue | null>(null);
|
||||||
|
|
||||||
|
export function useAudioContext() {
|
||||||
|
const ctx = useContext(AudioContext);
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("useAudio must be used within <AudioProvider>");
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AudioProviderProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AudioProvider({ children }: AudioProviderProps) {
|
||||||
|
const value = useAudio();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AudioContext.Provider value={value}>{children}</AudioContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
34
src/providers/chats-provider.tsx
Normal file
34
src/providers/chats-provider.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { createContext, ReactNode, useContext } from "react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
useChatsSocket,
|
||||||
|
UseChatsSocketOptions,
|
||||||
|
} from "@/hooks/chats/useChatsSocket";
|
||||||
|
|
||||||
|
type ChatsContextValue = ReturnType<typeof useChatsSocket>;
|
||||||
|
|
||||||
|
const ChatsContext = createContext<ChatsContextValue | null>(null);
|
||||||
|
|
||||||
|
export function useChats() {
|
||||||
|
const ctx = useContext(ChatsContext);
|
||||||
|
if (!ctx) {
|
||||||
|
throw new Error("useChats must be used within <ChatsProvider>");
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChatsProviderProps extends UseChatsSocketOptions {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ChatsProvider({ children, initialChats }: ChatsProviderProps) {
|
||||||
|
const value = useChatsSocket({
|
||||||
|
initialChats,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChatsContext.Provider value={value}>{children}</ChatsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user