Skip to content

Commit

Permalink
add Sidebar components and hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
ngquyduc committed Jul 9, 2023
1 parent 9847a09 commit 28e672e
Show file tree
Hide file tree
Showing 15 changed files with 387 additions and 108 deletions.
105 changes: 58 additions & 47 deletions app/(site)/components/AuthForm.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,98 @@
"use client";
'use client'

import { useCallback, useState } from "react";
import { FieldValues, SubmitHandler, useForm } from "react-hook-form";
import { BsGoogle } from "react-icons/bs";
import { useCallback, useEffect, useState } from 'react'
import { FieldValues, SubmitHandler, useForm } from 'react-hook-form'
import { BsGoogle } from 'react-icons/bs'

import Button from "@/app/components/Buttons";
import Input from "@/app/components/inputs/input";
import axios from "axios";
import { signIn } from "next-auth/react";
import { toast } from "react-hot-toast";
import AuthSocialButton from "./AuthSocialButton";
import Button from '@/app/components/Buttons'
import Input from '@/app/components/inputs/input'
import axios from 'axios'
import { signIn, useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import { toast } from 'react-hot-toast'
import AuthSocialButton from './AuthSocialButton'

type Variant = "LOGIN" | "REGISTER";
type Variant = 'LOGIN' | 'REGISTER'

const AuthForm = () => {
const [variant, setVariant] = useState<Variant>("LOGIN");
const [isLoading, setIsLoading] = useState(false);
const session = useSession()
const router = useRouter()
const [variant, setVariant] = useState<Variant>('LOGIN')
const [isLoading, setIsLoading] = useState(false)

useEffect(() => {
if (session?.status === 'authenticated') {
router.push('/users')
}
}, [session?.status, router])

const toggleVariant = useCallback(() => {
if (variant === "LOGIN") {
setVariant("REGISTER");
if (variant === 'LOGIN') {
setVariant('REGISTER')
} else {
setVariant("LOGIN");
setVariant('LOGIN')
}
}, [variant]);
}, [variant])

const {
register,
handleSubmit,
formState: { errors },
} = useForm<FieldValues>({
defaultValues: {
name: "",
email: "",
password: "",
name: '',
email: '',
password: '',
},
});
})

const onSubmit: SubmitHandler<FieldValues> = (data) => {
setIsLoading(true);
setIsLoading(true)

if (variant === "REGISTER") {
if (variant === 'REGISTER') {
// Axios Register
axios
.post("/api/register", data)
.catch(() => toast.error("Something went wrong!"))
.finally(() => setIsLoading(false));
.post('/api/register', data)
.then(() => signIn('credentials', data))
.catch(() => toast.error('Something went wrong!'))
.finally(() => setIsLoading(false))
}

if (variant == "LOGIN") {
if (variant == 'LOGIN') {
// NextAuth SignIn
signIn("credentials", {
signIn('credentials', {
...data,
redirect: false,
})
.then((callback) => {
if (callback?.error) {
toast.error("Invalid credentials");
toast.error('Invalid credentials')
}

if (callback?.ok && !callback?.error) {
toast.success("Logged in!");
toast.success('Logged in!')
router.push('/users')
}
})
.finally(() => setIsLoading(false));
.finally(() => setIsLoading(false))
}
};
}

const socialAction = (action: string) => {
setIsLoading(true);
setIsLoading(true)

signIn(action, { redirect: false })
.then((callback) => {
if (callback?.error) {
toast.error("Invalid credentials");
toast.error('Invalid credentials')
}

if (callback?.ok && !callback?.error) {
toast.success("Logged in!");
toast.success('Logged in!')
}
})
.finally(() => setIsLoading(false));
};
.finally(() => setIsLoading(false))
}

return (
<div
Expand All @@ -103,7 +114,7 @@ const AuthForm = () => {
"
>
<form className="space-y-6" onSubmit={handleSubmit(onSubmit)}>
{variant === "REGISTER" && (
{variant === 'REGISTER' && (
<Input
id="name"
label="Name"
Expand All @@ -130,7 +141,7 @@ const AuthForm = () => {
/>
<div>
<Button disabled={isLoading} fullWidth type="submit">
{variant === "LOGIN" ? "Sign in" : "Register"}
{variant === 'LOGIN' ? 'Sign in' : 'Register'}
</Button>
</div>
</form>
Expand Down Expand Up @@ -175,7 +186,7 @@ const AuthForm = () => {
<div className="mt-6 flex gap-2">
<AuthSocialButton
icon={BsGoogle}
onClick={() => socialAction("google")}
onClick={() => socialAction('google')}
/>
</div>
</div>
Expand All @@ -192,17 +203,17 @@ const AuthForm = () => {
"
>
<div>
{variant === "LOGIN"
? "New to Messenger?"
: "Already have an account?"}
{variant === 'LOGIN'
? 'New to Messenger?'
: 'Already have an account?'}
</div>
<div onClick={toggleVariant} className="underline cursor-pointer">
{variant === "LOGIN" ? "Create an account" : "Log in"}
{variant === 'LOGIN' ? 'Create an account' : 'Log in'}
</div>
</div>
</div>
</div>
);
};
)
}

export default AuthForm;
export default AuthForm
13 changes: 13 additions & 0 deletions app/components/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const EmptyState = () => {
return (
<div className="px-4 py-10 sm:px-6 lg:px-8 h-full flex justify-center items-center bg-gray-100">
<div className="text-center items-center flex flex-col">
<h3 className="mt-2 text-2xl font-semibold text-gray-900">
Select a chat or start a new conversation
</h3>
</div>
</div>
)
}

export default EmptyState
43 changes: 43 additions & 0 deletions app/components/sidebar/DesktopItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client'

import clsx from 'clsx'
import Link from 'next/link'

interface DesktopItemProps {
label: string
icon: any
href: string
onClick?: () => void
active?: boolean
}

const DesktopItem: React.FC<DesktopItemProps> = ({
label,
icon: Icon,
href,
onClick,
active,
}) => {
const handleClick = () => {
if (onClick) {
return onClick()
}
}

return (
<li onClick={handleClick}>
<Link
href={href}
className={clsx(
`group flex gap-x-3 rounded-md p-3 text-sm leading-6 font-semibold text-gray-500 hover:text-black hover:bg-gray-100`,
active && 'bg-gray-100 text-black'
)}
>
<Icon className="h-6 w-6 shrink-0" />
<span className="sr-only">{label}</span>
</Link>
</li>
)
}

export default DesktopItem
30 changes: 30 additions & 0 deletions app/components/sidebar/DesktopSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import useRoutes from '@/app/hooks/useRoutes'
import { useState } from 'react'
import DesktopItem from './DesktopItem'

const DesktopSidebar = () => {
const routes = useRoutes()
const [isOpen, setIsOpen] = useState(false)
return (
<div className="hidden lg:fixed lg:inset-y-0 lg:left-0 lg:z-40 lg:w-20 xl:px-6 lg:overflow-y-auto lg:bg-white lg:border-r-[1px] lg:pb-4 lg:flex lg:flex-col justify-between">
<nav className="mt-4 flex-4 flex-col justify-between">
<ul role="list" className="flex flex-col items-center space-y-1">
{routes.map((item) => (
<DesktopItem
key={item.label}
href={item.href}
label={item.label}
icon={item.icon}
active={item.active}
onClick={item.onClick}
/>
))}
</ul>
</nav>
</div>
)
}

export default DesktopSidebar
26 changes: 26 additions & 0 deletions app/components/sidebar/MobileFooter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client'

import useConversation from '@/app/hooks/useConversation'
import useRoutes from '@/app/hooks/useRoutes'
import MobileItem from './MobileItem'

const MobileFooter = () => {
const routes = useRoutes()
const { isOpen } = useConversation()
if (isOpen) return null
return (
<div className="fixed justify-between w-full bottom-0 z-40 flex items-center bg-white border-t-[1px] lg:hidden">
{routes.map((route) => (
<MobileItem
key={route.href}
href={route.href}
active={route.active}
icon={route.icon}
onClick={route.onClick}
/>
))}{' '}
</div>
)
}

export default MobileFooter
38 changes: 38 additions & 0 deletions app/components/sidebar/MobileItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client'

import clsx from 'clsx'
import Link from 'next/link'

interface MobileItemProps {
icon: any
href: string
onClick?: () => void
active?: boolean
}

const MobileItem: React.FC<MobileItemProps> = ({
href,
icon: Icon,
active,
onClick,
}) => {
const handleClick = () => {
if (onClick) {
return onClick()
}
}
return (
<Link
href={href}
onClick={onClick}
className={clsx(
`group flex gap-x-3 text-sm leading-6 font-semibold w-full justify-center p-4 text-gray-500 hover:text-black hover:bg-gray-100`,
active && 'bg-gray-100 text-black'
)}
>
<Icon className="h-6 w-6" />
</Link>
)
}

export default MobileItem
14 changes: 14 additions & 0 deletions app/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import DesktopSidebar from './DesktopSidebar'
import MobileFooter from './MobileFooter'

async function Sidebar({ children }: { children: React.ReactNode }) {
return (
<div className="h-full">
<DesktopSidebar />
<MobileFooter />
<main className="lg:pl-20 h-full"> {children}</main>
</div>
)
}

export default Sidebar
11 changes: 11 additions & 0 deletions app/context/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use client'

import { SessionProvider } from 'next-auth/react'

interface AuthContextProps {
children: React.ReactNode
}

export default function AuthContext({ children }: AuthContextProps) {
return <SessionProvider>{children}</SessionProvider>
}
25 changes: 25 additions & 0 deletions app/hooks/useConversation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useParams } from 'next/navigation'
import { useMemo } from 'react'

const useConversation = () => {
const params = useParams()

const conversationId = useMemo(() => {
if (!params?.conversationId) {
return ''
}
return params.conversationId as string
}, [params?.conversationId])

const isOpen = useMemo(() => !!conversationId, [conversationId])

return useMemo(
() => ({
isOpen,
conversationId,
}),
[isOpen, conversationId]
)
}

export default useConversation
Loading

0 comments on commit 28e672e

Please sign in to comment.