Skip to content

Commit

Permalink
Merge pull request #43 from Bayusetiawan45/feature-alert
Browse files Browse the repository at this point in the history
Feature Notification System
  • Loading branch information
codebayu authored Jan 4, 2024
2 parents 53aafeb + bf1974c commit 567e018
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 9 deletions.
3 changes: 3 additions & 0 deletions common/components/elements/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type RadioInputProps<TFormValue extends FieldValues> = {
rule?: RegisterOptions
isTextArea?: boolean
placeholder?: string
rows?: number
}

export default function InputField<TFormValue extends FieldValues>({
Expand All @@ -16,13 +17,15 @@ export default function InputField<TFormValue extends FieldValues>({
error,
isTextArea = false,
placeholder = '',
rows = 2,
register
}: RadioInputProps<TFormValue>) {
const renderPlaceholder = placeholder || name.charAt(0).toUpperCase() + name.slice(1)
return (
<div className=" w-full space-y-2">
{isTextArea ? (
<textarea
rows={rows}
placeholder={renderPlaceholder}
{...register(name, rule)}
className="bg-neutral-50 dark:bg-neutral-900 dark:outline-neutral-700 w-full rounded-lg p-2 outline outline-neutral-300 focus:outline-neutral-400"
Expand Down
34 changes: 34 additions & 0 deletions common/components/elements/Notif.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use client'

import { AnimatePresence, motion } from 'framer-motion'
import React, { useEffect } from 'react'

import { useNotifStore } from '@/stores/notif'

export default function Notif() {
const { isOpen, text, hideNotif } = useNotifStore()

useEffect(() => {
const timeout = setTimeout(() => {
hideNotif()
}, 3000)

return () => clearTimeout(timeout)
}, [hideNotif, isOpen])

return (
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.3 }}
className="fixed top-6 font-sans right-6 text-sm rounded-md opacity-70 bg-neutral-200 text-neutral-900 py-1 px-2"
>
{text}
</motion.div>
)}
</AnimatePresence>
)
}
4 changes: 4 additions & 0 deletions common/components/layouts/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import dynamic from 'next/dynamic'
import { usePathname, useSearchParams } from 'next/navigation'

import AOS from 'aos'
Expand All @@ -9,6 +10,8 @@ import React, { ReactNode, useEffect } from 'react'
import BottomNavigation from './LeftCollapseNavigation'
import MobileHeader from './MobileHeader'

const Notif = dynamic(() => import('@/common/components/elements/Notif'), { ssr: false })

interface LayoutsProps {
children: ReactNode
}
Expand All @@ -35,6 +38,7 @@ export default function Layouts({ children }: LayoutsProps) {
</main>
</div>
{!hideSidebar && <BottomNavigation />}
<Notif />
</div>
)
}
12 changes: 12 additions & 0 deletions hooks/useNotif.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useNotifStore } from '@/stores/notif'

export function useNotif() {
const { showNotif, setNotifText } = useNotifStore()

function notif(text: string) {
setNotifText(text)
showNotif()
}

return notif
}
11 changes: 9 additions & 2 deletions modules/chat/components/ChatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,19 @@ export default function ChatInput({ reply, sendMessage, cancleReply }: IChatInpu
</button>
</motion.div>
)}
<InputField name="message" placeholder="Type your message..." register={register} error={errors} />
<InputField
isTextArea
rows={1}
name="message"
placeholder="Type your message..."
register={register}
error={errors}
/>
</div>
<button
disabled={disabled}
aria-label="Send message"
className={clsxm('bg-neutral-600 text-white p-3 rounded-lg', disabled && 'cursor-not-allowed opacity-50')}
className={clsxm('bg-neutral-600 mb-1 text-white p-3 rounded-lg', disabled && 'cursor-not-allowed opacity-50')}
>
<SendIcon />
</button>
Expand Down
11 changes: 5 additions & 6 deletions modules/chat/components/ChatItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export default function ChatItem({
id="chat-card"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="flex items-start space-x-3"
className="flex items-start space-x-3 w-full"
>
<Image src={image} alt={name} width={40} height={40} className="rounded-full" />
<div className="flex flex-col space-y-1">
<div className="flex flex-col space-y-1 w-full">
<div className="flex items-center space-x-3">
<div className="flex space-x-1 items-center">
<span className="text-sm">{name}</span>
Expand All @@ -53,17 +53,16 @@ export default function ChatItem({
</div>
<span className="text-neutral-400 text-xs">{time}</span>
</div>

<div
className="flex space-x-2 items-end"
className="flex space-x-2 items-end w-full max-w-[90%]"
onMouseEnter={() => setOnHover(true)}
onMouseLeave={() => setOnHover(false)}
>
<div className="font-sans bg-neutral-100 dark:bg-neutral-800 max-w-[90%] py-2 px-3 rounded-xl rounded-tl-none">
<div className="font-sans bg-neutral-100 dark:bg-neutral-800 py-2 px-3 rounded-xl rounded-tl-none">
<p className="text-neutral-700 dark:text-neutral-200">
{is_reply && (
<>
<span className="text-teal-600">@{reply_to}</span> {message}
<span className="text-teal-600 whitespace-nowrap mr-1">@{reply_to}</span> <span>{message}</span>
</>
)}
{!is_reply && <>{message}</>}
Expand Down
3 changes: 3 additions & 0 deletions modules/chat/components/ChatRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { firebase } from '@/common/libs/firebase'
import { IMessage, IRawMessages } from '@/common/types/messages'

import useHasMounted from '@/hooks/useHasMounted'
import { useNotif } from '@/hooks/useNotif'

import ChatAuth from './ChatAuth'
import ChatItem from './ChatItem'
Expand All @@ -27,6 +28,7 @@ export default function ChatRoom({ user }: ChatRoomProps) {
const [hasScrolledUp, setHasScrolledUp] = useState(false)
const chatListRef = useRef<HTMLDivElement | null>(null)
const mounted = useHasMounted()
const notif = useNotif()

const { runDriver, isProductTour } = createDrivers({ steps: tourChatRoom, product: 'chat-room', timing: 2000 })

Expand Down Expand Up @@ -59,6 +61,7 @@ export default function ChatRoom({ user }: ChatRoomProps) {
}

function clickReply(name: string) {
if (!user) return notif('Please sign in to reply')
setReply({ isReply: true, name })
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "v2.codebayu.com",
"version": "1.2.3",
"version": "1.2.4",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
20 changes: 20 additions & 0 deletions stores/notif.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { create } from 'zustand'

export interface InitialNotifState {
isOpen: boolean
text: string
}

export interface InitialNotifAction {
showNotif(): void
hideNotif(): void
setNotifText(text: string): void
}

export const useNotifStore = create<InitialNotifState & InitialNotifAction>()(set => ({
isOpen: false,
text: '',
showNotif: () => set({ isOpen: true }),
hideNotif: () => set({ isOpen: false }),
setNotifText: (text: string) => set({ text })
}))

0 comments on commit 567e018

Please sign in to comment.