adding agent pages.

This commit is contained in:
killian-larcher
2024-11-10 23:12:38 +01:00
parent 84030dcba0
commit c76727f0d1
30 changed files with 627 additions and 8469 deletions
@@ -0,0 +1,24 @@
"use client";
import {Card, CardContent, CardHeader} from "@/components/ui/card";
import Link from "next/link";
export type agentCardProps = {
id: string;
name: string;
lastContact: string;
}
export const AgentCard = (props: agentCardProps) => {
const {id, name, lastContact} = props;
return (
<Link href={`/dashboard/agents/${id}`}>
<Card>
<CardHeader>{name}</CardHeader>
<CardContent>Agent's Description</CardContent>
</Card>
</Link>
)
}
@@ -59,6 +59,7 @@ export const LoginForm = (props: loginFormProps) => {
<FormField
control={form.control}
name="email"
defaultValue=""
render={({field}) => (
<FormItem>
<FormLabel>Email</FormLabel>
@@ -73,6 +74,7 @@ export const LoginForm = (props: loginFormProps) => {
<FormField
control={form.control}
name="password"
defaultValue=""
render={({field}) => (
<FormItem>
@@ -63,6 +63,7 @@ export const RegisterForm = (props: registerFormProps) => {
<FormField
control={form.control}
name="name"
defaultValue=""
render={({field}) => (
<FormItem>
<FormLabel>Name</FormLabel>
@@ -77,6 +78,7 @@ export const RegisterForm = (props: registerFormProps) => {
<FormField
control={form.control}
name="email"
defaultValue=""
render={({field}) => (
<FormItem>
<FormLabel>Email</FormLabel>
@@ -91,6 +93,7 @@ export const RegisterForm = (props: registerFormProps) => {
<FormField
control={form.control}
name="password"
defaultValue=""
render={({field}) => (
<FormItem>
<FormLabel className="flex">Password
@@ -115,6 +118,7 @@ export const RegisterForm = (props: registerFormProps) => {
<FormField
control={form.control}
name="confirmPassword"
defaultValue=""
render={({field}) => (
<FormItem>
<FormLabel>Password Confirmation</FormLabel>
@@ -1,4 +1,3 @@
import {Button} from "@/components/ui/button"
import {Avatar, AvatarFallback, AvatarImage} from "@/components/ui/avatar"
import {currentUser} from "@/auth/current-user";
import {LoggedInDropdown} from "@/components/wrappers/Dashboard/LoggedInDropdown/LoggedInDropdown";
@@ -9,23 +9,16 @@ import {
} from "@/components/ui/sidebar"
import {DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger} from "@/components/ui/dropdown-menu";
import {
Calendar,
ChartArea,
ChevronDown,
ChevronUp,
Home,
Inbox,
Search,
Settings,
ShieldHalf,
User2
} from "lucide-react";
import Link from "next/link";
import {signOutAction} from "@/features/auth/auth.action";
import {UserAvatar} from "@/components/wrappers/Dashboard/UserAvatar/UserAvatar";
import {LoggedInDropdown} from "@/components/wrappers/Dashboard/LoggedInDropdown/LoggedInDropdown";
import {LoggedInButton} from "@/components/wrappers/Dashboard/LoggedInButton/LoggedInButton";
import {Button, buttonVariants} from "@/components/ui/button"
import {buttonVariants} from "@/components/ui/button"
import {cn} from "@/lib/utils";
@@ -65,10 +58,10 @@ export function AppSidebar() {
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton>
Select Workspace
<ChevronDown className="ml-auto"/>
</SidebarMenuButton>
<SidebarMenuButton>
Select Workspace
<ChevronDown className="ml-auto"/>
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-[--radix-popper-anchor-width]">
<DropdownMenuItem>
@@ -89,15 +82,18 @@ export function AppSidebar() {
<SidebarMenu>
{items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild >
<SidebarMenuButton asChild>
<Link
className={cn(buttonVariants({size: "lg", variant: "ghost"}), "justify-start p-0")}
className={cn(buttonVariants({
size: "lg",
variant: "ghost"
}), "justify-start p-0")}
href={`${BASE_URL}/${item.url}`}>
<item.icon/>
<span>{item.title}</span>
<item.icon/>
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
<SidebarMenuAction className="peer-data-[active=true]/menu-button:opacity-100" />
<SidebarMenuAction className="peer-data-[active=true]/menu-button:opacity-100"/>
</SidebarMenuItem>
@@ -0,0 +1,150 @@
'use client'
import React, {useState} from 'react'
import {Card} from "@/components/ui/card"
import {cn} from "@/lib/utils";
import {
Pagination, PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext, PaginationPrevious
} from "@/components/ui/pagination";
export type cardsWithPaginationProps = {
data: Array<{}>;
cardItem: React.ComponentType;
cardsPerPage?: number
numberOfColumns?: number
}
export const CardsWithPagination = (props: cardsWithPaginationProps) => {
const {data, cardItem, cardsPerPage = 5, numberOfColumns = 1} = props
const CardItem = cardItem
const [currentPage, setCurrentPage] = useState(1)
const totalPages = Math.ceil(data.length / cardsPerPage)
const indexOfLastCard = currentPage * cardsPerPage
const indexOfFirstCard = indexOfLastCard - cardsPerPage
const currentCards = data.slice(indexOfFirstCard, indexOfLastCard)
const handlePageChange = (pageNumber: number) => {
setCurrentPage(pageNumber)
}
const renderPaginationItems = () => {
const items = []
const maxVisiblePages = 3
if (totalPages <= maxVisiblePages) {
for (let i = 1; i <= totalPages; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink
onClick={() => handlePageChange(i)}
isActive={currentPage === i}
>
{i}
</PaginationLink>
</PaginationItem>
)
}
} else {
if (currentPage <= 2) {
for (let i = 1; i <= maxVisiblePages; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink
onClick={() => handlePageChange(i)}
isActive={currentPage === i}
>
{i}
</PaginationLink>
</PaginationItem>
)
}
items.push(
<PaginationItem key="ellipsis1">
<PaginationEllipsis/>
</PaginationItem>
)
} else if (currentPage >= totalPages - 1) {
items.push(
<PaginationItem key="ellipsis2">
<PaginationEllipsis/>
</PaginationItem>
)
for (let i = totalPages - 2; i <= totalPages; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink
onClick={() => handlePageChange(i)}
isActive={currentPage === i}
>
{i}
</PaginationLink>
</PaginationItem>
)
}
} else {
items.push(
<PaginationItem key="ellipsis3">
<PaginationEllipsis/>
</PaginationItem>
)
for (let i = currentPage - 1; i <= currentPage + 1; i++) {
items.push(
<PaginationItem key={i}>
<PaginationLink
onClick={() => handlePageChange(i)}
isActive={currentPage === i}
>
{i}
</PaginationLink>
</PaginationItem>
)
}
items.push(
<PaginationItem key="ellipsis4">
<PaginationEllipsis/>
</PaginationItem>
)
}
}
return items
}
return (
<div className="">
<div className={cn(`grid auto-rows-min gap-4 md:grid-cols-${numberOfColumns}`)}>
{currentCards.map((card, key) => (
<CardItem key={key} {...card}/>
))}
</div>
<Pagination className="mt-8">
<PaginationContent>
<PaginationItem>
<PaginationPrevious
onClick={() => handlePageChange(Math.max(1, currentPage - 1))}
aria-disabled={currentPage === 1}
tabIndex={currentPage === 1 ? -1 : 0}
/>
</PaginationItem>
{renderPaginationItems()}
<PaginationItem>
<PaginationNext
onClick={() => handlePageChange(Math.min(totalPages, currentPage + 1))}
aria-disabled={currentPage === totalPages}
tabIndex={currentPage === totalPages ? -1 : 0}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</div>
)
}
+2 -1
View File
@@ -1,6 +1,7 @@
"use server"
import {signIn, signOut} from "@/auth/auth";
import {redirect} from "next/navigation";
import {signIn, signOut} from "@/auth/auth";
export const signOutAction = async () => {
await signOut({redirectTo: '/', redirect: true})
+2 -6
View File
@@ -1,10 +1,6 @@
import Image from "next/image"
import {Layout} from "@/components/layout";
import Link from "next/link";
import {Badge} from "@/components/ui/badge";
import {currentUser} from "@/auth/current-user";
import {notFound} from "next/navigation";
import {buttonVariants} from "@/components/ui/button";
import {currentUser} from "@/auth/current-user";
import {LoggedInButton} from "@/components/wrappers/Dashboard/LoggedInButton/LoggedInButton";
import {SidebarTrigger} from "@/components/ui/sidebar";
import {ModeToggle} from "@/features/theme/ModeToggle";
+38
View File
@@ -0,0 +1,38 @@
import {PropsWithChildren} from 'react';
export const Page = ({children}: PropsWithChildren<{}>) => {
return (
<div className="flex flex-1 flex-col gap-4 px-10 py-6">{children}</div>
);
};
export const PageHeader = ({children}: PropsWithChildren<{}>) => {
return (
<div className="flex justify-between">{children}</div>
);
};
export const PageTitle = ({children}: PropsWithChildren<{}>) => {
return (
<h1 className="text-3xl font-bold mb-6">{children}</h1>
);
};
export const PageDescription = ({children}: PropsWithChildren<{}>) => {
return (
<h2 className="text-s mb-6 text-gray-700">{children}</h2>
);
};
export const PageActions = ({children}: PropsWithChildren<{}>) => {
return (
<h1 className="flex gap-4">{children}</h1>
);
};
export const PageContent = ({children}: PropsWithChildren<{}>) => {
return (
<div className="">{children}</div>
);
};
+7 -12
View File
@@ -1,26 +1,21 @@
"use client"
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import {Moon, Sun} from "lucide-react"
import {useTheme} from "next-themes"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {Button} from "@/components/ui/button"
import {DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger} from "@/components/ui/dropdown-menu"
export function ModeToggle() {
const { setTheme } = useTheme()
const {setTheme} = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<Sun className="size-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute size-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<Sun className="size-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0"/>
<Moon className="absolute size-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100"/>
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
+1 -1
View File
@@ -4,6 +4,6 @@ import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
export default function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}
+11 -11
View File
@@ -3,17 +3,17 @@ import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
return !!isMobile
}
+131 -131
View File
@@ -4,191 +4,191 @@
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? {...t, ...action.toast} : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
case "DISMISS_TOAST": {
const {toastId} = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
let memoryState: State = {toasts: []}
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
function toast({...props}: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: {...props, id},
})
const dismiss = () => dispatch({type: "DISMISS_TOAST", toastId: id})
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({type: "DISMISS_TOAST", toastId}),
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }
export {useToast, toast}
+1 -1
View File
@@ -6,5 +6,5 @@ export type LayoutParams<T extends Record<string, string | string[]>> = {
export type PageParams<T extends Record<string, string | string[]>> = {
params: T;
searchParams: {[key: string]: string | string[] | undefined};
searchParams: { [key: string]: string | string[] | undefined };
};