top of page

Source Code

CalendarApp.jsx

​

import {useState} from 'react'

 

const CalendarApp = () => {

    const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']

    const monthsOfYear = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]

   

    const currentDate = new Date()

   

    const [currentMonth, setCurrentMonth] = useState(currentDate.getMonth())

    const [currentYear, setCurrentYear] = useState(currentDate.getFullYear())

    const [selectedDate, setSelectedDate] = useState(currentDate)

    const [showEventPopup, setShowEventPopup] = useState(false)

    const [events, setEvents] = useState([])

    const [eventTime, setEventTime] = useState({hours: '00', minutes: "00"})

    const [eventText, setEventText] = useState('')

    const [editingEvent, setEditingEvent] = useState(null)

 

    const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate()

    const firstDayOfMonth = new Date(currentYear, currentMonth, 1).getDay()

 

    const prevMonth = () => {

        setCurrentMonth((prevMonth) => (prevMonth === 0 ? 11 : prevMonth - 1))

        setCurrentYear((prevYear) => (currentMonth === 0 ? prevYear - 1 : prevYear))

    }

 

    const nextMonth = () => {

        setCurrentMonth((prevMonth) => (prevMonth === 11 ? 0 : prevMonth + 1))

        setCurrentYear((prevYear) => (currentMonth === 11 ? prevYear + 1 : prevYear))

    }

 

    const handleDayClick = (day) => {

        const clickedDate = new Date(currentYear, currentMonth, day)

        const today = new Date()

 

        if (clickedDate >= today || isSameDay(clickedDate, today)) {

            setSelectedDate(clickedDate)

            setShowEventPopup(true)

 

            setEventTime({ hours: '00', minutes: '00'})

 

            setEventText("")

            setEditingEvent(null)

        }

    }

 

    const isSameDay = (date1, date2) => {

        return(

            date1.getFullYear() === date2.getFullYear() &&

            date1.getMonth() === date2.getMonth() &&

            date1.getDate() === date2.getDate()

        )

    }

 

    const handleEventSubmit = () => {

        const newEvent = {

            id: editingEvent ? editingEvent.id : Date.now(),

            date: selectedDate,

 

            time: `${eventTime.hours.padStart(2, '0')}:${eventTime.minutes.padStart(2, '0')}`,

 

            text: eventText,

        }

 

        let updatedEvents = [...events]

 

        if(editingEvent) {

            updatedEvents = updatedEvents.map((event) =>

                event.id === editingEvent.id ? newEvent : event,

            )

        }

        else {

            updatedEvents.push(newEvent)

        }

       

        updatedEvents.sort((a, b) => new Date(a.date) - new Date(b.date))

 

        setEvents(updatedEvents)

        setEventTime({hours: '00', minutes: '00'})

        setEventText("")

        setShowEventPopup(false)

        setEditingEvent(null)

    }

 

    const handleEditEvent = (event) => {

        setSelectedDate(new Date(event.date))

        setEventTime({

            hours: event.time.split(":")[0],

            minutes: event.time.split(":")[1],

        })

        setEventText(event.text)

        setEditingEvent(event)

        setShowEventPopup(true)

    }

 

    const handleDeleteEvent = (eventId) => {

        const updatedEvents = events.filter((event) => event.id !== eventId)

        setEvents(updatedEvents)

    }

 

    const handleTimeChange = (e) => {

        const {name, value} = e.target

        setEventTime((prevTime) => ({...prevTime, [name]: value.padStart(2, '0')}))

    }

 

    return (

        <div className="calendar-app">

            <div className="calendar">

                <h1 className="heading">Calendar</h1>

                <div className="navigate-date">

                    <h2 className="month">{monthsOfYear[currentMonth]},</h2>

                    <h2 className="year">{currentYear}</h2>

                    <div className="buttons">

                        <i className="bx bx-chevron-left" onClick={prevMonth}></i>

                        <i className="bx bx-chevron-right" onClick={nextMonth}></i>

                    </div>

                </div>

                <div className="weekdays">

                    {daysOfWeek.map((day) => <span key={day}>{day}</span>)}

                </div>

                <div className="days">

                    {[...Array(firstDayOfMonth).keys()].map((_, index) => (

                        <span key={`empty-${index}`} />

                    ))}

                    {[...Array(daysInMonth).keys()].map((day) => {

                        const isCurrentDay =

                            day + 1 === currentDate.getDate() &&

                            currentMonth === currentDate.getMonth() &&

                            currentYear === currentDate.getFullYear();

 

                        const hasEvent = events.some(event =>

                            isSameDay(new Date(event.date), new Date(currentYear, currentMonth, day + 1))

                        );

                        return (

                            <span

                                key={day + 1}

                                className={`${isCurrentDay ? 'current-day' : ''} ${hasEvent ? 'event-day' : ''}`}

                                onClick={() => handleDayClick(day + 1)}

                            >

                                {day + 1}

                            </span>

                        );

                    })}

                </div>

            </div>

            <div className="events">

                {showEventPopup && (

                    <div className="event-popup">

                        <div className="time-input">

                            <div className="event-popup-time">Time</div>

                            <input

                                type="number"

                                name="hours"

                                min={0}

                                max={24}

                                className="hours"

                                value={eventTime.hours}

                                onChange={handleTimeChange}

                            />

                            <input type="number" name="minutes" min={0} max={59} className="minutes" value={eventTime.minutes}

                                onChange={(e) => setEventTime({...eventTime, minutes: e.target.value})}/>

                        </div>

                    <textarea

                        placeholder="Enter Event Text (Maximum 60 Characters)"

                        value={eventText} onChange={(e) => {

                            if(e.target.value.length <= 60) {

                                setEventText(e.target.value)

                            }

                        }}

                    ></textarea>

                    <button className="event-popup-btn" onClick={handleEventSubmit}>

                        {editingEvent ? "Update Event" : "Add Event"}

                    </button>

                    <button className="close-event-popup" onClick={() => setShowEventPopup(false)}>

                        <i className="bx bx-x"></i>

                    </button>

                </div>

                )}

                {events.map((event, index) => (

                    <div className="event"key={index}>

                    <div className="event-date-wrapper">

                        <div className="event-date">{`${

                            monthsOfYear[event.date.getMonth()]} ${event.date.getDate()} ${event.date.getFullYear()}`}

                        </div>

                        <div className="event-time">{event.time}</div>

                    </div>

                    <div className="event-text">{event.text}</div>

                    <div className="event-button">

                        <i className="bx bxs-edit-alt" onClick={() => handleEditEvent(event)}></i>

                        <i className="bx bxs-message-alt-x" onClick={() => handleDeleteEvent(event.id)}></i>

                    </div>

                </div>

                ))}

               

            </div>

        </div>

    )

​}

 

export default CalendarApp

 

CalendarApp.css​

​

.calendar-app {

    width: 60%;

    min-width: 90vmin;

    aspect-ratio: 3 / 2;

    background-color: #1e242d;

    padding: 3rem;

    border-radius: 3rem;

    border: 1rem solid #0f1319;

    display: flex;

    column-gap: 5rem;

    position: relative;

    transform-style: preserve-3d;

​}

 

.calendar-app::after {

    content: "";

    position: absolute;

    bottom: -12rem;

    left: 50%;

    transform: translate(-50%) rotateX(50deg);

    width: 90%;

    height: 16rem;

    background-color: rgba(0, 0, 0, 0.5);

    border-radius: 20rem;

    filter: blur(4rem);

​}

 

.calendar {

    width: 40%;

​}

 

.heading {

    font-family: "Bebas Neue", sans-serif;

    font-size: clamp(4rem, 3.8cqi, 7rem);

    color: #fff;

    letter-spacing: 0.3rem;

    margin-left: 15px;

​}

 

.navigate-date {

    display: flex;

    align-items: center;

    margin: 3.5rem 0;

​}

 

.navigate-date h2 {

    font-size: clamp(1.5rem, 1.5cqi, 2.5rem);

    color: #bbb;

    padding-left: 1.3rem;

​}

 

.buttons {

    display: flex;

    column-gap: 1rem;

    margin-left: auto;

​}

 

.buttons i {

    width: 3.5rem;

    height: 3.5rem;

    background-color: #2c3542;

    border-radius: 50%;

    display: flex;

    justify-content: center;

    align-items: center;

    font-size: 2rem;

    color: #c97f1a;

    cursor: pointer;

​}

 

.weekdays {

    width: 100%;

    display: flex;

    margin: 3rem 0;

​}

 

.weekdays span {

    width: calc(100% / 7);

    font-size: clamp(1rem, 0.8cqi, 1.3rem);

    font-weight: bold;

    text-transform: uppercase;

    color: #78879e;

    letter-spacing: 0.1rem;

    display: flex;

    justify-content: center;

​}

 

.days {

    display: flex;

    flex-wrap: wrap;

​}

 

.days span {

    font-size: clamp(1.2rem, 1cqi, 1.6rem);

    width: calc(100% / 7);

    aspect-ratio: 1;

    display: flex;

    justify-content: center;

    align-items: center;

    color: #ddd;

    cursor: pointer;

    text-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.2);

​}

 

.current-day {

    background-color: #ef9011;

    border-radius: 50%;

    box-shadow: 0 0 1.5rem 1rem rgba(239, 144, 17, 0.3);

​}

 

.events {

    width: 60%;

    height: 100%;

    padding: 3rem 0;

    overflow-y: auto;

​}

 

.events::-webkit-scrollbar {

    display: none;

​}

 

.event-popup {

    position: absolute;

    top: 38%;

    left: 3rem;

    background-color: #161b22;

    width: clamp(25rem, 21cqi, 40rem);

    aspect-ratio: 10 / 9;

    border-radius: 1rem;

    box-shadow: 0 1rem 3rem rbga(0, 0, 0, 0.3);

    display: flex;

    flex-direction: column;

    justify-content: center;

    align-items: center;

    row-gap: 2rem;

​}

 

.time-input {

    display: flex;

    column-gap: 1rem;

​}

 

.event-popup-time {

    width: clamp(4rem, 4cqi, 7rem);

    background-color: #00a3ff;

    color: #fff;

    font-family: "Bebas Neue", sans-serif;

    font-size: clamp(1.5rem, 1.5cqi, 2.2rem);

    display: flex;

    justify-content: center;

    align-items: center;

    box-shadow: 0 0 1.5rem 1rem rgba(0, 163, 255, 0.2);

    letter-spacing: 0.1rem;

​}

 

.time-input input {

    background-color: transparent;

    border: none;

    border-top: 0.2rem solid #00a3ff;

    border-bottom: 0.2rem solid #00a3ff;

    color: #fff;

    width: clamp(4rem, 4cqi, 7rem);

    height: 4rem;

    text-align: center;

    font-size: clamp(1.2rem, 1.2cqi, 1.6rem);

​}

 

.time-time input [type="number"] ::-webkit-outer-spin-button,

.time-time input [type="number"] ::-webkit-inner-spin-button{

    appearance: none;

​}

 

.event-popup textarea {

    width: clamp(15rem, 15cqi, 25rem);

    aspect-ratio: 5 / 2;

    resize: none;

    background-color: #0f1319;

    border: none;

    padding: 1rem;

    border-radius: 0.5rem;

    color: #78879e;

    transition: border 0.5s;

​}

 

.event-popup textarea:focus{

    border: 0.1rem solid #00a3ff;

​}

 

.event-popup textarea::placeholder {

    font-size: clamp(1rem, 0.8cqi, 1.2rem);

    color: #78879e;

​}

 

.event-popup textarea:focus::placeholder{

    color: transparent;

​}

 

.event-popup-btn {

    width: clamp(15rem, 15cqi, 25rem);

    height: 4rem;

    background-color: #ef9011;

    color: #fff;

    font-family: "Bebas Neue", sans-serif;

    font-size: clamp(1.5rem, 1.5cqi, 2.2rem);

    letter-spacing: 0.1rem;

    border: none;

    box-shadow: 0 0 1.5rem 1rem rgba(239, 144, 17, 0.2);

    cursor: pointer;

​}

 

​.event-popup-btn:active {

    transform: translateY(0.1rem);

​}

 

.close-event-popup {

    position: absolute;

    top: 1rem;

    right: 1rem;

    background-color: transparent;

    border: none;

    cursor: pointer;

​}

 

.close-event-popup i {

    font-size: 2.5rem;

    color: #fff;

​}

 

.event {

    width: 100%;

    height: 9rem;

    background-color:  #00a3ff;

    padding: 1.5rem 0;

    border-radius: 1rem;

    display: flex;

    align-items: center;

    margin-bottom: 2rem;

    position: relative;

​}

 

.event-day {

    background-color: #00a3ff;

​}

 

.event-date-wrapper {

    display: flex;

    flex-direction: column;

    align-items: center;

    width: 30%;

    border-right: 0.1rem solid rgba(255, 255, 255, 0.5);

​}

 

.event-date {

    font-size: clamp(1rem, 1cqi, 1.2rem);

    color: #fff;

    margin-top:  10px;

    margin-left: 10px;

    margin-right: 10px;

    text-align: center;

​}

 

.event-time {

    font-size: clamp(1.3rem, 1cqi, 1.6rem);

    line-height: 4rem;

    font-weight: bold;

    color: #fff;

​}

 

.event-text {

    font-size: clamp(1.2rem, 1cqi, 1.4rem);

    line-height: 2rem;

    color: #fff;

    width: 75%;

    padding: 0 3rem 0 1rem;

    margin-left: 5px;

    overflow-wrap: break-word;

​}

 

.event-button {

    position: absolute;

    top: 50%;

    transform: translateY(-50%);

    right: 1rem;

    display: flex;

    flex-direction: column;

    row-gap: 1rem;

​}

 

.event-button i {

    font-size: 1.6rem;

    color: #fff;

    cursor: pointer;

​}

 

@media (max-width: 850px) {

    .calendar-app{

        flex-direction: column;

        row-gap: 2rem;

        aspect-ratio: 3 / 2;

    }

 

    .calendar {

        width: 70%;

        margin: auto;

    }

 

    .navigate-date {

        margin: 1rem 0;

    }

 

    .weekdays {

        margin: 1rem 0;

    }

 

    .events {

        width: 80%;

        margin: auto;

    }

 

    .event-popup {

        top: 18%;

        left: 50%;

        transform: translateX(-50%);

        width: 60%;

        aspect-ratio: 4 / 3;

        row-gap: 1rem;

    }

 

    .event-popup-time {

        width: clamp(5rem, 10cqi, 8rem);

        font-size: clamp(1.5rem, 3cqi, 2.2rem);

​    }

 

    .time-input input {

        width: clamp(5rem, 10cqi, 8rem);

        font-size: clamp(1.2rem, 2cqi, 1rem);

​    }

 

    .event-popup textarea {

        width: clamp(18rem, 35cqi, 28rem);

    }

 

    .event-popup textarea::placeholder {

        font-size: clamp(1rem, 1.5cqi, 1.2rem);

    }

 

    .event-popup-btn {

        width: clamp(18rem, 35cqi, 28rem);

        font-size: clamp(1.5rem, 3cqi, 2.2rem);

    }

 

    .event-date {

        font-size: clamp(1rem, 1cqi, 1rem);

​    }

 

    .event-time {

        font-size: clamp(1rem, 2cqi, 1.6rem);

    }

 

    .event-text {

        font-size: clamp(1rem, 2.5cqi, 1.4rem);

​   }

}

 

@media (max-width: 500px) {

    .calendar-app {

        aspect-ratio: 10 / 9;

    }

 

    .calendar {

        width: 100%;

    }

 

    .event-popup {

        top: 23%;

        width: 80%;

    }

 

    .events {

        width: 100%;

    }

​}

 

@media (max-width: 375px) {

    .calendar-app {

        aspect-ratio: 3 / 2;

    }

​}

 

​App.jsx

​

import CalendarApp from "./Components/CalendarApp"

import './Components/CalendarApp.css'

 

const App = () => {

  return (

    <div className='container'>

      <CalendarApp />

    </div>

  )

​}

 

export default App

 

​index.css

​

@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Comfortaa:wght@300..700&display=swap');

 

​*{

  margin: 0;

  padding: 0;

  box-sizing: border-box;

  outline: none;

  font-family: "Comfortaa", sans-serif;

​}

 

html {

  font-size: 62.5%;

​}

 

.container {

  width: 100%;

  height: 100vh;

  background-color: #2c3542;

  display: grid;

  place-items: center;

  overflow: hidden;

  perspective: 100rem;

​}

 

@media (max-width: 500px) {

  html {

    font-size: 55%;

  }

}

content_warning_un_nuevo_jugador_by_deser7cat_dh61w3x-414w-2x.jpg
- Chang Hui Ming
bottom of page