create schedule meeting component, modifided homepage and so on
This commit is contained in:
parent
15f7330617
commit
367a6f5ae3
14 changed files with 4554 additions and 31 deletions
|
@ -10,12 +10,17 @@ import UserFormInfo from "./UserFormInfo";
|
|||
import { collection, doc, getDocs, getFirestore, query, setDoc, where } from "firebase/firestore";
|
||||
import { app } from "@/config/FirebaseConfig";
|
||||
import { toast } from "sonner";
|
||||
import Plunk from "@plunk/node";
|
||||
import { render } from '@react-email/render';
|
||||
import Email from "@/emails";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const MeetingEventSelection = ({meetingEventInfo, businessInfo}) => {
|
||||
const [date, setDate] = useState(new Date());
|
||||
const [timeSlots, setTimeSlots] = useState();
|
||||
const [unavialableTime, setUnavialableTime] = useState(false);
|
||||
const [selectedTime, setSelectedTime] = useState();
|
||||
const router = useRouter();
|
||||
|
||||
//set user schedule data
|
||||
const [userName, setUserName] = useState();
|
||||
|
@ -30,6 +35,9 @@ const MeetingEventSelection = ({meetingEventInfo, businessInfo}) => {
|
|||
|
||||
const db = getFirestore(app);
|
||||
|
||||
//send an email using React Email and the Plunk Node.js SDK
|
||||
const plunk = new Plunk(process.env.NEXT_PUBLIC_PLUNK_API_KEY);
|
||||
|
||||
//create time slot
|
||||
const createTimeSlot = (interval) => {
|
||||
const startTime = 8 * 60;
|
||||
|
@ -77,13 +85,16 @@ const MeetingEventSelection = ({meetingEventInfo, businessInfo}) => {
|
|||
userEmail: userEmail,
|
||||
selectedTime: selectedTime,
|
||||
selectedDate: date,
|
||||
formatedDate:format(date, 'PPP'),
|
||||
formatedTimeStamp:format(date, 't'),
|
||||
userMessage: userMessage
|
||||
}).then(res => {
|
||||
toast('Your schedule is created successfully');
|
||||
sendEmail(userName)
|
||||
})
|
||||
}
|
||||
|
||||
//fetch data to block out selection days and time
|
||||
//fetch data to blockout selection days and time
|
||||
const eventBooking = async (datebooking) => {
|
||||
const q = query(collection(db, 'ScheduleMeeting'), where('selectedDate', '==', datebooking), where('eventId', '==', meetingEventInfo.id));
|
||||
const querySnapshot = await getDocs(q);
|
||||
|
@ -92,6 +103,26 @@ const MeetingEventSelection = ({meetingEventInfo, businessInfo}) => {
|
|||
setBooking(prev => [...prev, doc.data()])
|
||||
})
|
||||
}
|
||||
|
||||
//create an email using Plunk
|
||||
const sendEmail = (user) => {
|
||||
const emailHtml = render(<Email
|
||||
userFirstName={user}
|
||||
businessName={businessInfo?.businessName}
|
||||
meetingDate={format(date, 'PPP').toString()}
|
||||
meetingTime={selectedTime}
|
||||
duration={meetingEventInfo?.timeDuration}
|
||||
meetingUrl={meetingEventInfo?.urlMeeting}/>);
|
||||
|
||||
plunk.emails.send({
|
||||
to: userEmail,
|
||||
subject: "Meeting Schedule Detail",
|
||||
body: emailHtml,
|
||||
}).then(res => {
|
||||
console.log(res)
|
||||
router.replace('/confirmation')
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
meetingEventInfo?.timeDuration && createTimeSlot(meetingEventInfo?.timeDuration)
|
||||
|
|
14
scheduler_app/app/(routes)/confirmation/page.js
Normal file
14
scheduler_app/app/(routes)/confirmation/page.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import Image from 'next/image'
|
||||
import React from 'react'
|
||||
|
||||
const Confirmation = () => {
|
||||
return (
|
||||
<div className='max-w-sm mx-auto h-screen flex flex-col items-center px-5 text-center'>
|
||||
<Image src='/check.png' width={80} height={80} alt='check' className='mt-40 mb-5'/>
|
||||
<p className='font-bold text-2xl text-[#1F1717] mb-2'>This meeting is scheduled</p>
|
||||
<p className='text-sm text-gray-500'>We emailed you a confirmation and meeting details to you on your email.</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Confirmation
|
|
@ -21,8 +21,6 @@ const DashboardHeader = () => {
|
|||
<DropdownMenuContent className='border border-gray-200 mt-1 bg-white'>
|
||||
<DropdownMenuLabel>{user.given_name}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>Profile</DropdownMenuItem>
|
||||
<DropdownMenuItem>Setting</DropdownMenuItem>
|
||||
<DropdownMenuItem><LogoutLink>Logout</LogoutLink></DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
|
@ -4,7 +4,8 @@ import { app } from '@/config/FirebaseConfig'
|
|||
import { useKindeBrowserClient } from '@kinde-oss/kinde-auth-nextjs'
|
||||
import { collection, deleteDoc, doc, getDoc, getDocs, getFirestore, orderBy, query, where } from 'firebase/firestore'
|
||||
import { GoClock } from "react-icons/go";
|
||||
import { PiMapPinLight, PiCopyLight, PiShareFatLight } from 'react-icons/pi'
|
||||
import { PiMapPinLight, PiCopyLight } from 'react-icons/pi'
|
||||
import { IoTrashOutline } from "react-icons/io5";
|
||||
import { IoSettingsOutline } from "react-icons/io5";
|
||||
import { toast } from 'sonner'
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"
|
||||
|
@ -65,21 +66,8 @@ const MeetingEventList = () => {
|
|||
</div>
|
||||
<hr/>
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex gap-2'>
|
||||
<button onClick={() => {onCopyHandle(event)}} className='text-lg'><PiCopyLight/></button>
|
||||
<button className="text-lg"><PiShareFatLight/></button>
|
||||
</div>
|
||||
<div className='flex'>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<IoSettingsOutline className='cursor-pointer'/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => onDeleteEvent(event)}>Delete</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
<button onClick={() => {onCopyHandle(event)}} className='text-lg'><PiCopyLight/></button>
|
||||
<button onClick={() => onDeleteEvent(event)}><IoTrashOutline/></button>
|
||||
</div>
|
||||
</div>
|
||||
)): <p className='font-semibold text-green-800'>Loading...</p>
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import React from 'react'
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"
|
||||
import { PiCalendarCheck } from 'react-icons/pi'
|
||||
import { GoClock } from 'react-icons/go'
|
||||
import { AiOutlineLink } from "react-icons/ai";
|
||||
import Link from 'next/link'
|
||||
|
||||
|
||||
const ScheduleList = ({meetingList}) => {
|
||||
return (
|
||||
<div>
|
||||
{meetingList && meetingList.map((meeting, index) => (
|
||||
<Accordion key={index} type="single" collapsible>
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>{meeting?.formatedDate}</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<div>
|
||||
<div className='mt-2 flex flex-col gap-2'>
|
||||
<p className='flex gap-1 items-center'><PiCalendarCheck/>{meeting.formatedDate}</p>
|
||||
<p className='flex gap-1 items-center'><GoClock/>{meeting?.timeDuration} minutes
|
||||
<span className="text-sm text-gray-500">({meeting.selectedTime})</span>
|
||||
</p>
|
||||
<div className='flex justify-between items-center'>
|
||||
<p className='flex gap-1 items-center'>
|
||||
<AiOutlineLink/>
|
||||
<Link href={meeting?.urlMeeting?meeting?.urlMeeting:'#'} className="text-gray-500 underline text-sm">{meeting?.urlMeeting}</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScheduleList
|
|
@ -0,0 +1,61 @@
|
|||
"use client"
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import ScheduleList from './_components/ScheduleList'
|
||||
import { collection, getDocs, getFirestore, query, where } from 'firebase/firestore'
|
||||
import { app } from '@/config/FirebaseConfig'
|
||||
import { useKindeBrowserClient } from '@kinde-oss/kinde-auth-nextjs'
|
||||
import { format } from 'date-fns'
|
||||
|
||||
|
||||
const ScheduleMeeting = () => {
|
||||
const [meetingList, setMeetingList] = useState([]);
|
||||
|
||||
const {user} = useKindeBrowserClient();
|
||||
const db = getFirestore(app);
|
||||
|
||||
const getSchedule = async () => {
|
||||
setMeetingList([])
|
||||
const q = query(collection(db, 'ScheduleMeeting'), where('businessEmail', '==', user.email))
|
||||
const querySnapshot = await getDocs(q);
|
||||
|
||||
querySnapshot.forEach(doc => {
|
||||
console.log(doc.data());
|
||||
setMeetingList(prev => [...prev, doc.data()])
|
||||
})
|
||||
}
|
||||
|
||||
//filter meeting
|
||||
const filterMeetingList = (type) => {
|
||||
if(type == 'upcoming'){
|
||||
return meetingList.filter(item => item.formatedTimeStamp >= format(new Date(), 't'))
|
||||
} else{
|
||||
return meetingList.filter(item => item.formatedTimeStamp < format(new Date(), 't'))
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
user && getSchedule()
|
||||
}, [user])
|
||||
|
||||
|
||||
return (
|
||||
<div className='px-5'>
|
||||
<p className='font-semibold text-xl mb-5'>Schedule Meeting</p><hr/>
|
||||
<Tabs defaultValue="upcoming" className="w-[400px] mt-5">
|
||||
<TabsList>
|
||||
<TabsTrigger value="upcoming">Upcoming</TabsTrigger>
|
||||
<TabsTrigger value="history">Meeting History</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="upcoming">
|
||||
<ScheduleList meetingList={filterMeetingList('upcoming')}/>
|
||||
</TabsContent>
|
||||
<TabsContent value="history">
|
||||
<ScheduleList meetingList={filterMeetingList('history')}/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ScheduleMeeting
|
|
@ -1,26 +1,36 @@
|
|||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { GoClock } from 'react-icons/go'
|
||||
import { PiCalendarCheck, PiInfo, PiMapPinLight } from 'react-icons/pi'
|
||||
|
||||
const Hero = () => {
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col text-center mt-20 md:mt-40 overflow-hidden'>
|
||||
<div>
|
||||
<p className='uppercase text-5xl md:text-6xl font-semibold drop-shadow-xl font-outline-2 text-white mb-5'>Schedule meetings</p>
|
||||
<p className='uppercase text-4xl md:text-5xl font-semibold drop-shadow-xl mb-5'>for <span>everyone</span></p>
|
||||
<p className='text-lg sm:text-xl font-semibold'>Set your availability and share your link.</p>
|
||||
<p className='text-lg sm:text-xl font-semibold'>Schedule.Me ensures you never get double booked!</p>
|
||||
<p className='text-base sm:text-xl font-semibold min-[400px]:text-lg'>Set your availability and share your link.</p>
|
||||
<p className='text-base sm:text-xl font-semibold min-[400px]:text-lg'>Schedule.Me ensures you never get double booked!</p>
|
||||
</div>
|
||||
{/*<div className='mt-10 sm:mt-20'>
|
||||
<p className='mb-10 text-sm'>Sign up with <span className='font-semibold'>Google</span> and <span className='font-semibold'>Facebook</span></p>
|
||||
<div className='flex flex-col md:flex md:flex-row gap-5 justify-center items-center text-center'>
|
||||
<button className='flex items-center gap-1 mb-0 md:mb-5 px-4 py-2 border bg-[#101010] text-white hover:opacity-90'><Image src='/google.png' width={20} height={20} alt='google' className='w-[15px] h-[15px]'/>Sign up with Google</button>
|
||||
<button className='flex items-center gap-1 mb-5 px-4 py-2 border border-[#101010] hover:bg-[gray] hover:border-[gray] hover:text-white'><Image src='/facebook.png' alt='facebook' width={20} height={20} className='w-[15px] h-[15px]'/>Sign up with Facebook</button>
|
||||
</div>
|
||||
<hr className='mb-5 mt-5 w-[350px] mx-auto'/>
|
||||
<Link href="" className='text-sm text-[gray] hover:underline transition-all duration-500'>Sign up with your email</Link>
|
||||
</div>*/}
|
||||
</div>
|
||||
<div className='max-w-xl bg-white grid grid-cols-2 mx-auto mt-2 border border-black border-t-8 rounded-md shadow-md min-[400px]:mt-5'>
|
||||
<div className='flex flex-col gap-2 p-5 border-r'>
|
||||
<p className='text-md uppercase font-semibold tracking-wide mb-2'>Schedule.Me</p>
|
||||
<p className='flex w-5 h-5 rounded-full bg-gray-500 text-white justify-center items-center mb-2'>S</p>
|
||||
<p className='flex gap-1 items-center text-xs'><PiInfo className='text-base'/>Stand-up meeting</p>
|
||||
<p className='flex gap-1 items-center text-xs'><PiCalendarCheck className='text-base'/>April 30th, 2024</p>
|
||||
<p className='flex gap-1 items-center text-xs'><GoClock className='text-base'/>30 minutes </p>
|
||||
<p className='flex gap-1 items-center text-xs'><PiMapPinLight className='text-base'/>Zoom</p>
|
||||
<Link href="https://zoom.us/" className="text-gray-500 underline text-xs">https://zoom.us</Link>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2 px-5 mt-14 mb-5'>
|
||||
<p className='text-xs font-semibold'>Select date & time</p>
|
||||
<Image src='/calendar.png' width={200} height={200} alt='calendar'/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
43
scheduler_app/components/ui/accordion.jsx
Normal file
43
scheduler_app/components/ui/accordion.jsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Accordion = AccordionPrimitive.Root
|
||||
|
||||
const AccordionItem = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item ref={ref} className={cn("border-b", className)} {...props} />
|
||||
))
|
||||
AccordionItem.displayName = "AccordionItem"
|
||||
|
||||
const AccordionTrigger = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
)}
|
||||
{...props}>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
|
||||
const AccordionContent = React.forwardRef(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
))
|
||||
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
43
scheduler_app/components/ui/tabs.jsx
Normal file
43
scheduler_app/components/ui/tabs.jsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props} />
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
54
scheduler_app/emails/index.jsx
Normal file
54
scheduler_app/emails/index.jsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { Body, Button, Container, Column, Head, Heading, Hr, Html, Img, Link, Row, Section, Text, Tailwind, } from "@react-email/components";
|
||||
import * as React from "react";
|
||||
|
||||
|
||||
const baseUrl = "";
|
||||
|
||||
export const Email = ({
|
||||
userFirstName,
|
||||
businessName,
|
||||
meetingUrl,
|
||||
meetingDate,
|
||||
meetingTime,
|
||||
duration
|
||||
}) => {
|
||||
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Tailwind>
|
||||
<Body className="bg-white my-auto mx-auto font-sans px-2">
|
||||
<Container className="border border-solid border-[#eaeaea] rounded my-[40px] mx-auto p-[20px] max-w-[465px]">
|
||||
<Section className="mt-[32px]">
|
||||
<p className="flex justify-center text-xl uppercase font-semibold tracking-wide">Schedule.Me</p>
|
||||
</Section>
|
||||
<Text className="text-black text-[14px] leading-[24px]">Hello <span className="capitalize">{userFirstName}</span>,</Text>
|
||||
<Text className="text-black text-[14px] leading-[24px]">
|
||||
You have scheduled meeting with {businessName}. Please find your meeting details below this email. Thank you for scheduling meeting with us.
|
||||
</Text>
|
||||
<Text>
|
||||
<p className="text-[14px]"><b>Date:</b> {meetingDate}</p>
|
||||
<p className="text-[14px]"><b>Time:</b> {meetingTime}</p>
|
||||
<p className="text-[14px]"><b>Time duration:</b> {duration}</p>
|
||||
</Text>
|
||||
|
||||
<Section className="text-center mt-[32px] mb-[32px]">
|
||||
<Button className="bg-[#000000] rounded text-white text-[12px] font-semibold no-underline text-center px-5 py-3" href={meetingUrl}> Join Now </Button>
|
||||
</Section>
|
||||
<Text>
|
||||
or copy and paste this URL into your browser:{meetingUrl}
|
||||
</Text>
|
||||
<Hr className="border border-solid border-[#eaeaea] my-[26px] mx-0 w-full" />
|
||||
<Text className="text-[#666666] text-[12px] leading-[24px]">
|
||||
This notification was intended for <span className="text-black capitalize">{userFirstName}</span>. If you were not scheduling this meeting, you can ignore this email.
|
||||
</Text>
|
||||
</Container>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
export default Email;
|
||||
|
4236
scheduler_app/package-lock.json
generated
4236
scheduler_app/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -6,13 +6,18 @@
|
|||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"email": "email dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kinde-oss/kinde-auth-nextjs": "^2.2.4",
|
||||
"@plunk/node": "^3.0.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@react-email/components": "0.0.17",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^3.6.0",
|
||||
|
@ -23,6 +28,7 @@
|
|||
"react": "^18",
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-dom": "^18",
|
||||
"react-email": "2.1.2",
|
||||
"react-icons": "^5.1.0",
|
||||
"sonner": "^1.4.41",
|
||||
"tailwind-merge": "^2.3.0",
|
||||
|
|
BIN
scheduler_app/public/calendar.png
Normal file
BIN
scheduler_app/public/calendar.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
scheduler_app/public/check.png
Normal file
BIN
scheduler_app/public/check.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Loading…
Add table
Reference in a new issue