diff --git a/package.json b/package.json index 9d0d6d3ce2..50646899a1 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,11 @@ "private": true, "dependencies": { "@giscus/react": "^2.0.3", + "@types/lodash": "^4.14.182", "@types/react": "^18.0.6", "@types/react-dom": "^18.0.2", + "date-fns": "^2.28.0", + "lodash": "^4.17.21", "node-sass": "^7.0.1", "plop": "^3.0.5", "react": "^18.0.0", @@ -51,8 +54,8 @@ ] }, "devDependencies": { - "typescript": "^4.6.4", + "puppeteer": "^13.7.0", "react-snap": "^1.23.0", - "puppeteer": "^13.7.0" + "typescript": "^4.6.4" } } diff --git a/src/meta/play-meta.js b/src/meta/play-meta.js index 4f87daadc0..0712a90fda 100644 --- a/src/meta/play-meta.js +++ b/src/meta/play-meta.js @@ -16,6 +16,7 @@ QuoteGenerator, PasswordGenerator, WhyTypescript, NetlifyCardGame, +Calendar, FunQuiz, //import play here } from "plays"; @@ -242,6 +243,19 @@ export const plays = [ language: 'js', featured: true, }, { + id: 'pl-calendar', + name: 'Calendar', + description: 'Simple calendar app to manage events', + component: () => {return }, + path: '/plays/calendar', + level: 'Intermediate', + tags: 'JSX,Hooks,Typescript', + github: 'vincentBCP', + cover: '', + blog: '', + video: '', + language: 'ts' + }, { id: 'pl-fun-quiz', name: 'Fun Quiz', description: 'Its a Fun Quiz app which lets player to choose desirable category to answer 20 unique question with 4 options and pick the correct one.', diff --git a/src/plays/calendar/Calendar.scss b/src/plays/calendar/Calendar.scss new file mode 100644 index 0000000000..f5c574c686 --- /dev/null +++ b/src/plays/calendar/Calendar.scss @@ -0,0 +1,373 @@ +$calendar-play-box-shadow-dark:rgba(0,0,0,0.1); +$calendar-play-box-shadow: 0px 0px 10px 5px $calendar-play-box-shadow-dark; + +$calendar-play-blue-500: #3182CE; +$calendar-play-blue-400: #4299E1; +$calendar-play-blue-300: #63B3ED; + +$calendar-play-red-500: #E53E3E; +$calendar-play-red-200: #FEB2B2; +$calendar-play-red-100: #FED7D7; + +.calendar-play { + background-color: white; + padding: 30px; +} + +.calendar-play-navigation { + display: flex; + align-items: center; + margin-bottom: 30px; + user-select: none; + + button { + background-color: transparent; + border: 1px solid lightgray; + border-radius: 5px; + font-size: 14px; + padding: 10px 15px; + font-weight: 500; + margin-right: 20px; + } + + .calendar-play-navigation-arrow { + width: 35px; + height: 35px; + display: inline-flex; + justify-content: center; + align-items: center; + border-radius: 50%; + font-size: 28px; + cursor: pointer; + padding-top: 3px; + } + + .calendar-play-navigation-current-date { + font-size: 24px; + margin-left: 20px; + } + + button, + .calendar-play-navigation-arrow { + &:hover { + background-color: rgba(0,0,0,0.05); + } + + &:active { + background-color: darkgray; + } + } +} + +.calendar-play-body { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + border-left: 1px solid lightgray; + border-bottom: 1px solid lightgray; +} + +.calendar-play-day-tile { + height: 150px; + border-right: 1px solid lightgray; + border-top: 1px solid lightgray; + display: flex; + flex-direction: column; + align-items: center; + padding-top: 5px; + + .calendar-play-week { + font-size: 12px; + } + + .calendar-play-day { + display: inline-flex; + align-items: center; + justify-content: center; + height: 25px; + width: 100%; + font-size: 12px; + } + + .calendar-play-week { + text-transform: uppercase; + font-weight: 500; + color: gray; + } + + &.today { + .calendar-play-day { + width: 25px; + font-weight: 700; + color: white; + background-color: $calendar-play-blue-500; + border-radius: 50%; + } + } +} + +.calendar-play-modal { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding-top: 120px; + z-index: 999; + background-color: rgba(0,0,0,0.5); + + .calendar-play-modal-content { + width: 500px; + margin: auto; + background-color: white; + padding: 30px 30px 30px 30px; + border-radius: 5px; + box-shadow: $calendar-play-box-shadow; + + & > div:first-of-type { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 20px; + + span:last-of-type { + cursor: pointer; + margin-left: auto; + } + } + + @media (max-width: 768px) { + max-width: 90%; + } + } +} + +.calendar-play-event-form { + input[name='title'] { + width: 100%; + border: none; + border-bottom: 1px solid lightgray; + font-size: 18px; + outline: none; + padding-bottom: 5px; + margin-bottom: 20px; + } + + p { + font-size: 14px; + color: gray; + } + + & > div:first-of-type { + display: flex; + gap: 30px; + + div { + width: 50%; + + label { + display: block; + color: gray; + font-size: 14px; + } + + input { + width: 100%; + outline: none; + border: none; + border-bottom: 1px solid lightgray; + } + } + } + + & > div:last-of-type { + display: flex; + justify-content: flex-end; + gap: 15px; + margin-top: 40px; + + button { + min-width: 80px; + padding: 10px; + border: none; + outline: none; + border-radius: 5px; + font-weight: 500; + transition-duration: 0.3s; + border: 1px solid transparent; + + &.delete { + background-color: transparent; + margin-right: auto; + color: $calendar-play-red-500; + + &:hover { + border: 1px solid $calendar-play-red-500; + } + + &:active { + background-color: $calendar-play-red-100; + } + } + + &.close { + background-color: transparent; + + &:hover { + background-color: rgba(0,0,0,0.05); + } + + &:active { + background-color: rgba(0,0,0,0.2); + } + } + + &.save { + color: white; + background-color: $calendar-play-blue-500; + + &:hover { + background-color: $calendar-play-blue-400; + } + + &:active { + background-color: $calendar-play-blue-300; + } + } + } + } +} + +.calendar-play-event-info { + p:first-of-type { + font-size: 18px; + } + + div { + text-align: right; + + button { + min-width: 80px; + padding: 10px; + border: none; + outline: none; + border-radius: 5px; + font-weight: 500; + transition-duration: 0.3s; + + &:hover { + background-color: rgba(0,0,0,0.1); + } + + &:active { + background-color: rgba(0,0,0,0.2); + } + } + } +} + +.calendar-play-events { + margin-top: 5px; + width: 100%; +} + +.calendar-play-events-more { + padding: 0 10px; + display: block; + cursor: pointer; + position: relative; + + &:hover { + background-color: rgba(0,0,0,0.05); + } + + & > span { + font-weight: 600; + font-size: 12px; + white-space: nowrap; + } + + .calendar-play-events-more-popup { + display: none; + position: absolute; + left: -200px; + top: -200%; + width: 200px; + background-color: white; + padding: 20px 10px; + border-radius: 5px; + box-shadow: $calendar-play-box-shadow; + overflow: auto; + max-height: 200px; + + & > div:first-of-type { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 10px; + + span { + &:first-of-type { + font-size: 14px; + color: gray; + text-transform: uppercase; + } + + &:last-of-type { + font-size: 20px; + } + } + } + } + + &:hover { + .calendar-play-events-more-popup { + display: block; + } + } + + @media (max-width: 425px) { + span { + font-size: 10px; + } + } +} + +.calendar-play-event { + display: flex; + align-items: center; + gap: 5px; + overflow: hidden; + cursor: pointer; + padding: 3px 10px; + transition-duration: 0.3s; + + &:hover { + background-color: rgba(0,0,0,0.05); + } + + div { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: $calendar-play-blue-500; + flex-shrink: 0; + } + + span { + font-size: 12px; + white-space: nowrap; + + &:last-of-type { + font-weight: 500; + flex-grow: 1; + overflow: hidden; + text-overflow: ellipsis; + } + } + + @media (max-width: 425px) { + span { + font-size: 10px; + } + } +} \ No newline at end of file diff --git a/src/plays/calendar/Calendar.tsx b/src/plays/calendar/Calendar.tsx new file mode 100644 index 0000000000..d2e2039467 --- /dev/null +++ b/src/plays/calendar/Calendar.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { getPlayById } from 'meta/play-meta-util'; + +import PlayHeader from 'common/playlists/PlayHeader'; +import CalendarGrid from './CalendarGrid' +import { ContextProvider } from './Context'; + +import './Calendar.scss' +import ModalContainer from './ModalContainer'; + +function Calendar(props:any) { + // Do not remove the below lines. + // The following code is to fetch the current play from the URL + const { id } = props; + const play = getPlayById(id); + + // Your Code Start below. + + return ( + <> +
+ +
+ + + + +
+
+ + ); +} + +export default Calendar; \ No newline at end of file diff --git a/src/plays/calendar/CalendarDayTile.tsx b/src/plays/calendar/CalendarDayTile.tsx new file mode 100644 index 0000000000..d836f86229 --- /dev/null +++ b/src/plays/calendar/CalendarDayTile.tsx @@ -0,0 +1,43 @@ +import React, { useContext } from 'react' +import { format } from 'date-fns' +import CalendarEvents from './CalendarEvents' +import CalendarEventForm from './CalendarEventForm' +import { Context } from './Context' + +interface Props { + date: Date, + showWeek?: boolean, + isToday?: boolean +} + +const CalendarDayTile = ({ date, showWeek, isToday }: Props) => { + const context = useContext(Context) + const { showModal, hideModal } = context + + const handleClick = () => { + showModal( + , + format(date, 'ccc, MMMM dd') + ) + } + + return ( +
+ {showWeek && ( + {format(date, "EEE")} + )} + + {format(date, date.getDate() === 1 && !isToday ? "MMM d" : "d")} + + +
+ ); +} + +export default CalendarDayTile \ No newline at end of file diff --git a/src/plays/calendar/CalendarEvent.tsx b/src/plays/calendar/CalendarEvent.tsx new file mode 100644 index 0000000000..2a5ed36b4f --- /dev/null +++ b/src/plays/calendar/CalendarEvent.tsx @@ -0,0 +1,38 @@ +import React, { useContext } from 'react' +import { format } from 'date-fns' +import CalendarEventForm from './CalendarEventForm' +import { Context } from './Context' +import EventType from './EventType' + +interface Props { + event: EventType +} + +const CalendarEvent = ({ event }: Props) => { + const context = useContext(Context) + const { showModal, hideModal } = context + + const handleClick = () => { + showModal( + , + format(new Date(event.date), 'ccc, MMMM dd') + ) + } + + return ( +
+
+ {format(new Date(`1990-01-01 ${event.startTime}`), 'h:mm aaa')} + {event.title} +
+ ) +} + +export default CalendarEvent \ No newline at end of file diff --git a/src/plays/calendar/CalendarEventForm.tsx b/src/plays/calendar/CalendarEventForm.tsx new file mode 100644 index 0000000000..233ee7fc6e --- /dev/null +++ b/src/plays/calendar/CalendarEventForm.tsx @@ -0,0 +1,154 @@ +import { isBefore } from 'date-fns' +import { format } from 'date-fns/esm' +import { isEqual } from 'lodash' +import React, { useState, useContext, useEffect } from 'react' +import CalendarEventInfo from './CalendarEventInfo' +import { Context } from './Context' +import EventType from './EventType' + +interface Props { + date: Date, + event?: EventType, + onCancel: VoidFunction +} + +const CalendarEventForm = ({ date, event, onCancel }: Props) => { + const [calendarEvent, setCalendarEvent] = useState() + const [data, setData] = useState() + const [editable, setEditable] = useState(false) + const context = useContext(Context) + const { addEvent, updateEvent, deleteEvent } = context + + useEffect(() => { + if (event) { + setData({...event}) + setCalendarEvent({...event}) + setEditable(false) + return + } + + setData({ + date: format(date, 'yyyy-MM-dd'), + title: '', + startTime: '', + endTime: '' + }) + setEditable(true) + }, [date, event]) + + const handleSave = () => { + if (!data.title) { + alert('Please provide title') + return + } + + if (!data.startTime) { + alert('Please provide start time') + return + } + + if (!data.endTime) { + alert('Please provide end time') + return + } + + const start = new Date(`1990-01-01 ${data.startTime}`) + const end = new Date(`1990-01-01 ${data.endTime}`) + if (isEqual(start, end) || isBefore(end, start)) { + alert('Invalid time values') + return + } + + if (event) { + updateEvent(data) + setCalendarEvent({...data}) + setEditable(false) + return + } + + addEvent(data) + onCancel() + } + + const handleDelete = () => { + deleteEvent(event) + onCancel() + } + + const handleCancel = () => { + if (event) { + setEditable(false) + return + } + + onCancel() + } + + const handleEdit = () => { + setData({...calendarEvent}) + setEditable(true) + } + + if (!data) return null + + if (calendarEvent && !editable) { + return ( + + ) + } + + return ( +
ev.stopPropagation()} + > + setData({ ...data, title: ev.target.value })} + /> +
+
+ + + setData({ ...data, startTime: ev.target.value }) + } + /> +
+
+ + setData({ ...data, endTime: ev.target.value })} + /> +
+
+
+ {Boolean(event) && ( + + )} + + +
+
+ ) +} + +export default CalendarEventForm \ No newline at end of file diff --git a/src/plays/calendar/CalendarEventInfo.tsx b/src/plays/calendar/CalendarEventInfo.tsx new file mode 100644 index 0000000000..0301cd6a97 --- /dev/null +++ b/src/plays/calendar/CalendarEventInfo.tsx @@ -0,0 +1,24 @@ +import { format } from 'date-fns' +import React from 'react' +import EventType from './EventType' + +interface Props { + event: EventType, + onEdit: VoidFunction +} + +const CalendarEventInfo = ({ event, onEdit }: Props) => { + return ( +
+

{event.title}

+

{format(new Date(`1990-01-01 ${event.startTime}`), 'h:mm a')} - {format(new Date(`1990-01-01 ${event.endTime}`), 'h:mm a')}

+
+ +
+
+ ) +} + +export default CalendarEventInfo \ No newline at end of file diff --git a/src/plays/calendar/CalendarEvents.tsx b/src/plays/calendar/CalendarEvents.tsx new file mode 100644 index 0000000000..826fa67d1c --- /dev/null +++ b/src/plays/calendar/CalendarEvents.tsx @@ -0,0 +1,41 @@ +import React, { useState, useContext, useEffect } from 'react' +import CalendarEvent from './CalendarEvent' +import CalendarEventsMore from './CalendarEventsMore' +import { Context } from './Context' +import EventType from './EventType' + +interface Props { + date: Date +} + +const MAX_VISIBLE_ITEMS = 3 + +const CalendarEvents = ({ date }: Props) => { + const [events, setEvents] = useState([]) + const context = useContext(Context) + const { getEvents } = context + + useEffect(() => { + setEvents(getEvents(date)) + }, [date, getEvents]) + + return ( +
ev.stopPropagation()}> + {events + .filter((e, index) => index < MAX_VISIBLE_ITEMS) + .map(event => ( + + ))} + {events.length > MAX_VISIBLE_ITEMS && ( + index >= MAX_VISIBLE_ITEMS + )} + /> + )} +
+ ); +} + +export default CalendarEvents \ No newline at end of file diff --git a/src/plays/calendar/CalendarEventsMore.tsx b/src/plays/calendar/CalendarEventsMore.tsx new file mode 100644 index 0000000000..f92dbb78ab --- /dev/null +++ b/src/plays/calendar/CalendarEventsMore.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { format } from 'date-fns' +import CalendarEvent from './CalendarEvent' +import EventType from './EventType' + +interface Props { + date: Date, + events: EventType[] +} + +const CalendarEventsMore = ({ date, events }: Props) => { + return ( +
+ {events.length} more +
+
+ {format(date, "ccc")} + {format(date, "dd")} +
+
+ {events.map(event => ( + + ))} +
+
+
+ ); +} + +export default CalendarEventsMore \ No newline at end of file diff --git a/src/plays/calendar/CalendarGrid.tsx b/src/plays/calendar/CalendarGrid.tsx new file mode 100644 index 0000000000..5acfab9195 --- /dev/null +++ b/src/plays/calendar/CalendarGrid.tsx @@ -0,0 +1,56 @@ +import React, { useState } from 'react' +import CalendarNavigation from './CalendarNavigation' +import CalendarDayTile from './CalendarDayTile' +import { endOfMonth, format, startOfMonth, startOfWeek } from 'date-fns' +import { addDays, endOfWeek } from 'date-fns/esm' + +const WEEK_STARTS_ON = 0 // Sunday + +const CalendarGrid = () => { + const [currentDate, setCurrentDate] = useState(new Date()) + + const generateTiles = () => { + const startDate = startOfWeek(startOfMonth(currentDate), { weekStartsOn: WEEK_STARTS_ON }) + const endDate = endOfWeek(endOfMonth(currentDate), { weekStartsOn: WEEK_STARTS_ON }) + + let curDate = startDate + + const tiles: React.ReactNode[] = [] + + do { + tiles.push( + + ) + + curDate = addDays(curDate, 1) + } while(format(curDate, 'yyyy-MM-dd') !== format(endDate, 'yyyy-MM-dd')) + + tiles.push( + + ) + + return tiles + } + + return ( +
+ setCurrentDate(date)} + /> +
+ { generateTiles() } +
+
+ ) +} + +export default CalendarGrid \ No newline at end of file diff --git a/src/plays/calendar/CalendarNavigation.tsx b/src/plays/calendar/CalendarNavigation.tsx new file mode 100644 index 0000000000..06a60dea44 --- /dev/null +++ b/src/plays/calendar/CalendarNavigation.tsx @@ -0,0 +1,49 @@ +import { addMonths, format } from 'date-fns' +import React from 'react' + +interface Props { + currentDate: Date, + onDateChange: (date: Date) => void +} + +const CalendarNavigation = ({ currentDate, onDateChange }: Props) => { + const navigateTo = (direction: number) => { + /* + -1 = prev + 0 = today + 1 = next + */ + + if (direction === 0) { + onDateChange(new Date()) + return + } + + onDateChange(addMonths(currentDate, direction)) + } + + return ( +
+ + navigateTo(-1)} + > + < + + navigateTo(1)} + > + > + + + {format(currentDate, 'MMMM yyyy')} + +
+ ) +} + +export default CalendarNavigation \ No newline at end of file diff --git a/src/plays/calendar/Context.tsx b/src/plays/calendar/Context.tsx new file mode 100644 index 0000000000..d765771600 --- /dev/null +++ b/src/plays/calendar/Context.tsx @@ -0,0 +1,85 @@ +import { format } from "date-fns"; +import React, { useCallback, useState } from "react"; +import { orderBy } from 'lodash' +import { getDummyEvents } from "./utils"; +import EventType from "./EventType"; + +export const Context = React.createContext({ + modalTitle: '', + modalContent: undefined, + getEvents: (date: Date) => {}, + addEvent: (event: EventType) => {}, + updateEvent: (event: EventType) => {}, + deleteEvent: (event: EventType) => {}, + showModal: (content: React.ReactNode, title?: '') => {}, + hideModal: () => {} +}) + +export const ContextProvider = ({ children }: any) => { + const [events, setEvents] = useState(getDummyEvents()) + const [modalTitle, setModalTitle] = useState('') + const [modalContent, setModalContent] = useState(undefined) + + const getEvents = useCallback((date: Date) => { + return orderBy(events.filter(e => e.date === format(date, 'yyyy-MM-dd')), ['startTime']) + }, [events]) + + const addEvent = (event: EventType) => { + event.id = (new Date()).getTime().toString() + setEvents(oldValue => ([...oldValue, event])) + } + + const updateEvent = (event: EventType) => { + setEvents(oldValue => { + const newEvents = [...oldValue] + const index = newEvents.findIndex(e => e.id === event.id) + + if (index === -1) return newEvents + + newEvents[index] = event + + return newEvents + }) + } + + const deleteEvent = (event: EventType) => { + setEvents(oldValue => { + const newEvents = [...oldValue] + const index = newEvents.findIndex(e => e.id === event.id) + + if (index === -1) return newEvents + + newEvents.splice(index, 1) + + return newEvents + }) + } + + const showModal = (content: React.ReactNode, title?: '') => { + setModalTitle(title || '') + setModalContent(content) + } + + const hideModal = () => { + setModalTitle('') + setModalContent(undefined) + } + + return ( + + {children} + + ) +} + diff --git a/src/plays/calendar/EventType.ts b/src/plays/calendar/EventType.ts new file mode 100644 index 0000000000..9c81d2e4ba --- /dev/null +++ b/src/plays/calendar/EventType.ts @@ -0,0 +1,9 @@ +type EventType = { + id: string, + date: string, + title: string, + startTime: string, + endTime: string +} + +export default EventType \ No newline at end of file diff --git a/src/plays/calendar/ModalContainer.tsx b/src/plays/calendar/ModalContainer.tsx new file mode 100644 index 0000000000..e8e3b033ea --- /dev/null +++ b/src/plays/calendar/ModalContainer.tsx @@ -0,0 +1,28 @@ +import React, { useContext } from 'react' +import { Context } from './Context' + +const ModalContainer = () => { + const context = useContext(Context) + const { modalContent, modalTitle, hideModal } = context + + if (!modalContent) return null + + return ( +
+
ev.stopPropagation()} + > +
+ {Boolean(modalTitle) && ( + {modalTitle} + )} + +
+ {modalContent} +
+
+ ); +} + +export default ModalContainer \ No newline at end of file diff --git a/src/plays/calendar/Readme.md b/src/plays/calendar/Readme.md new file mode 100644 index 0000000000..97561fba67 --- /dev/null +++ b/src/plays/calendar/Readme.md @@ -0,0 +1,12 @@ +# Calendar + +A simple calendar to add, update, and delete events. + +## What will you learn + +- How to use `useState`, and `useContext` to manage component state. +- How to use `useEffect`, and `useCallback` to implement component behaviour. +- How to use `Context` to manage application state. +- How to use `date-fns` to manipulate date using its cool functions. +- How to pass `props` from parent component to child component. +- How to slice your app into smaller components for re-usability and easier management. \ No newline at end of file diff --git a/src/plays/calendar/cover.png b/src/plays/calendar/cover.png new file mode 100644 index 0000000000..ff9f846272 Binary files /dev/null and b/src/plays/calendar/cover.png differ diff --git a/src/plays/calendar/utils.tsx b/src/plays/calendar/utils.tsx new file mode 100644 index 0000000000..26c12f8bae --- /dev/null +++ b/src/plays/calendar/utils.tsx @@ -0,0 +1,85 @@ +import { + addDays, + endOfMonth, + endOfWeek, + format, + getWeekOfMonth, + isWeekend, + startOfMonth, + startOfWeek, +} from "date-fns"; +import EventType from "./EventType"; + +export const getDummyEvents = (): EventType[] => { + // create fake events + + const startDate = startOfWeek(startOfMonth(new Date()), { weekStartsOn: 0 }); + const endDate = endOfWeek(endOfMonth(new Date()), { weekStartsOn: 0 }); + + let curDate = startDate; + + let events: EventType[] = []; + + const addEvent = (date: Date) => { + // add event only if date is Mon - Fri + if (isWeekend(date)) return; + + events.push({ + id: format(date, "yyyy-MM-dd"), + date: format(date, "yyyy-MM-dd"), + title: "Daily stand up", + startTime: "09:30", + endTime: "10:00", + }); + + if (getWeekOfMonth(date) % 2 === 0 && date.getDay() === 2) { + events.push({ + id: `${format(date, "yyyy-MM-dd")}-key`, + date: format(date, "yyyy-MM-dd"), + title: "Sprint Planning", + startTime: "15:00", + endTime: "17:00", + }); + } + }; + + do { + addEvent(curDate); + curDate = addDays(curDate, 1); + } while (format(curDate, "yyyy-MM-dd") !== format(endDate, "yyyy-MM-dd")); + + addEvent(curDate); + + events = events.concat([ + { + id: "1", + date: format(new Date(), "yyyy-MM") + "-13", + title: "Lorem ipsum dolor sit amet", + startTime: "07:30", + endTime: "08:30", + }, + { + id: "2", + date: format(new Date(), "yyyy-MM") + "-13", + title: "Lorem ipsum dolor sit amet", + startTime: "10:00", + endTime: "11:00", + }, + { + id: "3", + date: format(new Date(), "yyyy-MM") + "-13", + title: "Lorem ipsum dolor sit amet", + startTime: "12:30", + endTime: "13:30", + }, + { + id: "4", + date: format(new Date(), "yyyy-MM") + "-13", + title: "Lorem ipsum dolor sit amet", + startTime: "14:00", + endTime: "14:30", + }, + ]); + + return events; +}; diff --git a/src/plays/index.js b/src/plays/index.js index dee1abcc87..89543a1073 100644 --- a/src/plays/index.js +++ b/src/plays/index.js @@ -18,5 +18,6 @@ export { default as AnalogClock } from 'plays/analog-clock/AnalogClock'; export { default as PasswordGenerator } from 'plays/password-generator/PasswordGenerator'; export { default as WhyTypescript } from 'plays/why-typescript/WhyTypescript'; export { default as NetlifyCardGame } from 'plays/memory-game/NetlifyCardGame'; +export { default as Calendar } from 'plays/calendar/Calendar'; export { default as FunQuiz } from 'plays/fun-quiz/FunQuiz'; //add export here