From 795239f7b45ae5e89a8df077cd663f7668d4364a Mon Sep 17 00:00:00 2001 From: jxmoose Date: Mon, 9 Dec 2024 03:01:18 -0800 Subject: [PATCH] wip --- app/availability/days/page.tsx | 169 +++++++++++++++++ app/availability/days/styles.ts | 95 ++++++++++ app/availability/details/page.tsx | 92 +++++++++ app/availability/details/styles.ts | 31 +++ app/availability/layout.tsx | 6 + app/availability/page.tsx | 106 ----------- app/availability/review/page.tsx | 134 +++++++++++++ app/availability/review/styles.ts | 48 +++++ app/availability/styles.ts | 178 ++++++++++-------- app/availability/times/page.tsx | 69 +++++++ app/availability/times/styles.ts | 13 ++ app/onboarding/basic-information/page.tsx | 4 +- .../DateTimeSelection/DateTimeSelection.tsx | 91 ++++++--- components/DateTimeSelection/styles.ts | 15 ++ components/TimeSelection/TimeSelection.tsx | 17 +- components/TimeSelection/styles.ts | 8 +- public/images/x.svg | 3 + utils/availabilityContext.tsx | 73 +++++++ 18 files changed, 935 insertions(+), 217 deletions(-) create mode 100644 app/availability/days/page.tsx create mode 100644 app/availability/days/styles.ts create mode 100644 app/availability/details/page.tsx create mode 100644 app/availability/details/styles.ts create mode 100644 app/availability/layout.tsx delete mode 100644 app/availability/page.tsx create mode 100644 app/availability/review/page.tsx create mode 100644 app/availability/review/styles.ts create mode 100644 app/availability/times/page.tsx create mode 100644 app/availability/times/styles.ts create mode 100644 public/images/x.svg create mode 100644 utils/availabilityContext.tsx diff --git a/app/availability/days/page.tsx b/app/availability/days/page.tsx new file mode 100644 index 0000000..ef36cca --- /dev/null +++ b/app/availability/days/page.tsx @@ -0,0 +1,169 @@ +'use client'; + +import React, { useContext, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import dayGridPlugin from '@fullcalendar/daygrid'; +import interactionPlugin from '@fullcalendar/interaction'; +import FullCalendar from '@fullcalendar/react'; +import Back from '@/public/images/back.svg'; +import { + AvailabilityContext, + defaultRange, + TimeRange, +} from '@/utils/availabilityContext'; +import { + BackButton, + Button, + ButtonContainer, + Container, + ContinueText, + Divider, + EventName, + Image, + ProgressBarContainer, + Rectangle, + Title, +} from '../styles'; +import { CalendarContainer } from './styles'; + +type Info = { + start: Date; + end: Date; +}; + +type DateObj = { + date: Date; +}; + +export default function Page() { + const [curMonth, setMonth] = useState(); + const router = useRouter(); + const availabilityContext = useContext(AvailabilityContext); + + if (!availabilityContext) return null; + + const { generalInfo, days, setDays, times, setTimes } = availabilityContext; + + const handleSelect = (info: Info) => { + const { start, end } = info; // Get start and end of the selected range + while (start < end) { + if (!checkValidSelectDate(start)) { + const dateStr: string = start.toISOString(); + if (days.includes(dateStr)) { + setDays(days.filter(days => days !== dateStr)); + } else { + setDays([...days, dateStr]); + } + } + start.setDate(start.getDate() + 1); // Move to the next day + } + }; + + const updateMonth = (info: Info) => { + const middate = new Date((info.start.getTime() + info.end.getTime()) / 2); + setMonth(middate.getMonth()); + }; + + const dayCellClassNames = (date: DateObj) => { + const dateObj = date.date; + const dateStr = dateObj.toISOString(); + const month = dateObj.getMonth(); + // Default classes + const classNames = []; + + // Highlight selected dates + if (days.includes(dateStr)) { + classNames.push('selected-date'); + if (curMonth != month) { + classNames.push('non-cur-selected'); + } else { + classNames.push('cur-selected'); + } + } + + if (checkValidSelectDate(dateObj)) { + // 0 = Sunday, 6 = Saturday + classNames.push('unselectable'); + } + + // Return the dynamic class names + return classNames; + }; + + function checkValidSelectDate(date: Date): boolean { + const day = date.getDay(); + const month = date.getMonth(); + const today = new Date(); + + const sameDay = + date.getDate() == today.getDate() && date.getMonth() == today.getMonth(); + if ( + day === 0 || + day === 6 || + (!sameDay && date < today) || + (curMonth != null && curMonth != month) + ) { + // 0 = Sunday, 6 = Saturday + return true; + } + return false; + } + + const handleSubmit = async () => { + if (!days) { + return; + } + setDays(days.sort((a, b) => a.localeCompare(b))); + console.log(times); + const initTimes: { [date: string]: TimeRange[] } = {}; + for (const day of days) { + if (day in times) { + initTimes[day] = times[day]; + } else { + initTimes[day] = [structuredClone(defaultRange)]; + } + } + setTimes(initTimes); + router.push('/availability/times'); + }; + + const handleBack = () => { + router.push('/availability/details'); + }; + return ( + + + Back icon + + What day's work best? + + + + + + arg.date.getDate()} + selectable={true} + dayCellClassNames={dayCellClassNames} + selectOverlap={false} + select={handleSelect} + datesSet={updateMonth} + /> + + + {generalInfo.eventName} + + + + + ); +} diff --git a/app/availability/days/styles.ts b/app/availability/days/styles.ts new file mode 100644 index 0000000..aee43f7 --- /dev/null +++ b/app/availability/days/styles.ts @@ -0,0 +1,95 @@ +import styled from 'styled-components'; +import COLORS from '../../../styles/colors'; + +export const CalendarContainer = styled.div` + margin-top: 5rem; + margin-left: 2rem; + width: 500px; + height: 500px; + cursor: default; + .fc-prev-button, + .fc-next-button { + background: none; + border: none; + color: black; + box-shadow: none; + } + + .fc-prev-button:hover, + .fc-next-button:hover { + background-color: lightgray; + cursor: pointer; + } + + .fc-highlight { + background-color: transparent; + } + + .selected-date { + position: relative; + } + + .cur-selected::after { + background-color: ${COLORS.bread5} !important; + } + + .non-cur-selected::after { + background-color: ${COLORS.bread3} !important; + } + + .selected-date::after { + content: ''; + position: absolute; + width: 45px; + height: 45px; + bottom: 5.5px; + transform: translateX(11px); + border-radius: 50%; + -moz-border-radius: 50%; + -webkit-border-radius: 50%; + z-index: -1; + } + + .unselectable { + color: ${COLORS.gray8}} !important; + } + + .fc-day-past div { + opacity: 1 !important; + } + + .fc-day-future div { + opacity: 1 !important; + } + + + .fc-day-today { + background-color: transparent !important; + } + + .fc-day-today::after { + content: ''; + position: absolute; + transform: translateX(31px); + bottom: 125px; + width: 4px; /* Diameter of the dot */ + height: 4px; /* Diameter of the dot */ + background-color: ${COLORS.rose11}; /* Color of the dot */ + border-radius: 50%; /* Makes the element a circle */ + } + + .fc-daygrid-day-frame { + display: flex; + justify-content: center; + } + + .fc-daygrid-day-top { + align-items: center; + } + + .fc td, + .fc table, + .fc th { + border-style: none !important; + } +`; diff --git a/app/availability/details/page.tsx b/app/availability/details/page.tsx new file mode 100644 index 0000000..d1f6ac8 --- /dev/null +++ b/app/availability/details/page.tsx @@ -0,0 +1,92 @@ +'use client'; + +import { useContext } from 'react'; +import { useRouter } from 'next/navigation'; +import Back from '@/public/images/back.svg'; +import { AvailabilityContext } from '@/utils/availabilityContext'; +import { + Asterisk, + BackButton, + Button, + ButtonContainer, + Container, + ContinueText, + Divider, + Image, + ProgressBarContainer, + QuestionsContainer, + Rectangle, + Title, +} from '../styles'; +import { + AdditionalInfo, + AdditionalInfoInput, + EventName, + EventNameInput, +} from './styles'; + +export default function Page() { + const router = useRouter(); + const availabilityContext = useContext(AvailabilityContext); + + if (!availabilityContext) return null; + + const { generalInfo, setGeneralInfo } = availabilityContext; + + const handleChange = ( + e: React.ChangeEvent, + ) => { + const { name, value } = e.target; + setGeneralInfo({ + ...generalInfo, + [name]: value, + }); + }; + + const handleSubmit = async () => { + if (!generalInfo.eventName) { + return; + } + router.push('/availability/days'); + }; + + const handleBack = () => { + router.push('/availability'); + }; + + return ( + + + + Back icon + + What's the Occasion? + + + + + + Event Name   * + + + Additional Info + + + + + + + + ); +} diff --git a/app/availability/details/styles.ts b/app/availability/details/styles.ts new file mode 100644 index 0000000..3abc70a --- /dev/null +++ b/app/availability/details/styles.ts @@ -0,0 +1,31 @@ +import styled from 'styled-components'; +import COLORS from '../../../styles/colors'; +import { Sans } from '../../../styles/fonts'; +import { P } from '../../../styles/text'; + +export const EventName = styled.div` + display: flex; + flex-direction: row; + font-weight: 500; +`; + +export const EventNameInput = styled.input` + height: 1.5rem; + text-indent: 0.5rem; + border-radius: 8px; + border: 1px solid ${COLORS.gray6}; + ${Sans.style} +`; + +export const AdditionalInfo = styled(P)` + margin-top: 1.25rem; + font-weight: 500; +`; + +export const AdditionalInfoInput = styled.textarea` + height: 7rem; + border-radius: 8px; + border: 1px solid ${COLORS.gray6}; + padding: 0.5rem; + ${Sans.style} +`; diff --git a/app/availability/layout.tsx b/app/availability/layout.tsx new file mode 100644 index 0000000..a8b26df --- /dev/null +++ b/app/availability/layout.tsx @@ -0,0 +1,6 @@ +import { ReactNode } from 'react'; +import { AvailabilityProvider } from '@/utils/availabilityContext'; + +export default function Layout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/app/availability/page.tsx b/app/availability/page.tsx deleted file mode 100644 index a4594c1..0000000 --- a/app/availability/page.tsx +++ /dev/null @@ -1,106 +0,0 @@ -'use client'; - -import React, { useState } from 'react'; -import dayGridPlugin from '@fullcalendar/daygrid'; -import interactionPlugin from '@fullcalendar/interaction'; -import FullCalendar from '@fullcalendar/react'; -import { Container } from './styles'; - -type Info = { - start: Date; - end: Date; -}; - -type DateObj = { - date: Date; -}; - -export default function Page() { - const [selectedDates, setSelectedDates] = useState([]); - const [curMonth, setMonth] = useState(); - - const handleSelect = (info: Info) => { - const { start, end } = info; // Get start and end of the selected range - while (start < end) { - if (!checkValidSelectDate(start)) { - const dateStr = start.toISOString(); - setSelectedDates(prevDates => - prevDates.includes(dateStr) - ? prevDates.filter(date => date !== dateStr) - : [...prevDates, dateStr], - ); - } - start.setDate(start.getDate() + 1); // Move to the next day - } - }; - - const updateMonth = (info: Info) => { - const middate = new Date((info.start.getTime() + info.end.getTime()) / 2); - setMonth(middate.getMonth()); - }; - - const dayCellClassNames = (date: DateObj) => { - const dateObj = date.date; - const dateStr = dateObj.toISOString(); - const month = dateObj.getMonth(); - // Default classes - const classNames = []; - - // Highlight selected dates - if (selectedDates.includes(dateStr)) { - classNames.push('selected-date'); - if (curMonth != month) { - classNames.push('non-cur-selected'); - } else { - classNames.push('cur-selected'); - } - } - - if (checkValidSelectDate(dateObj)) { - // 0 = Sunday, 6 = Saturday - classNames.push('unselectable'); - } - - // Return the dynamic class names - return classNames; - }; - - function checkValidSelectDate(date: Date): boolean { - const day = date.getDay(); - const month = date.getMonth(); - const today = new Date(); - - const sameDay = - date.getDate() == today.getDate() && date.getMonth() == today.getMonth(); - if ( - day === 0 || - day === 6 || - (!sameDay && date < today) || - (curMonth != null && curMonth != month) - ) { - // 0 = Sunday, 6 = Saturday - return true; - } - return false; - } - - return ( - - arg.date.getDate()} - selectable={true} - dayCellClassNames={dayCellClassNames} - selectOverlap={false} - select={handleSelect} - datesSet={updateMonth} - /> - - ); -} diff --git a/app/availability/review/page.tsx b/app/availability/review/page.tsx new file mode 100644 index 0000000..dc3b685 --- /dev/null +++ b/app/availability/review/page.tsx @@ -0,0 +1,134 @@ +'use client'; + +import { useContext } from 'react'; +import { useRouter } from 'next/navigation'; +import Back from '@/public/images/back.svg'; +import COLORS from '@/styles/colors'; +import { P } from '@/styles/text'; +import { AvailabilityContext } from '@/utils/availabilityContext'; +import { + BackButton, + Button, + ButtonContainer, + Container, + ContinueText, + EventName, + Image, + ProgressBarContainer, + Rectangle, + SplitText, + Title, +} from '../styles'; +import { + BulletedList, + Divider, + EditButton, + EditText, + GradientDivider, + ReviewContainer, + TextContainer, +} from './styles'; + +export default function Page() { + const router = useRouter(); + const availabilityContext = useContext(AvailabilityContext); + + if (!availabilityContext) return null; + + const { days, generalInfo, times, submitAvailabilityData } = + availabilityContext; + + const minutesToFormatted = (minutes: number) => { + const hours = Math.floor(minutes / 60); + const mins = minutes % 60; + const period = hours >= 12 ? 'PM' : 'AM'; + const formattedHours = hours > 12 ? hours - 12 : hours; + return `${formattedHours}:${mins.toString().padStart(2, '0')} ${period}`; + }; + const Availabilities = days.map(key => { + return ( +
+

+ {new Date(key).toLocaleDateString('en-US', { + weekday: 'short', + month: 'long', + day: 'numeric', + year: 'numeric', + })} +

+ + {times[key].map(time => ( +
  • + {minutesToFormatted(time.start)} - {minutesToFormatted(time.end)} +
  • + ))} +
    +
    + ); + }); + + const handleBack = () => { + router.push('/availability/times'); + }; + + const handleDetails = () => { + router.push('/availability/details'); + }; + + const handleDays = () => { + router.push('/availability/days'); + }; + + return ( + + + + Back icon + + Does everything look right? + + + + + About + + edit + + + + +
    +

    Event Name

    +

    + {generalInfo.eventName} +

    +
    +
    +

    Additional Info

    +

    + {generalInfo.additionalInfo || '(blank)'} +

    +
    +
    + + Availabilties + + edit + + + + {Availabilities} + +

    + * Everything can be modified later! +

    + + {generalInfo.eventName} + + +
    +
    + ); +} diff --git a/app/availability/review/styles.ts b/app/availability/review/styles.ts new file mode 100644 index 0000000..22bcfab --- /dev/null +++ b/app/availability/review/styles.ts @@ -0,0 +1,48 @@ +import styled from 'styled-components'; +import COLORS from '@/styles/colors'; +import { P } from '@/styles/text'; + +export const EditButton = styled.button` + cursor: pointer; + all: unset; +`; + +export const TextContainer = styled.div` + display: flex; + flex-direction: column; + gap: 1.5rem; +`; + +export const EditText = styled(P)` + color: ${COLORS.lilac9}; +`; + +export const ReviewContainer = styled.div` + display: flex; + flex-direction: column; + gap: 0.5 rem; +`; + +export const Divider = styled.hr` + height: 0.0625rem; + background-color: ${COLORS.gray7}; + border: none; + margin-bottom: 1rem; +`; + +export const BulletedList = styled.ul` + color: ${COLORS.gray11}; + margin-left: 2rem; +`; +export const GradientDivider = styled.hr` + margin-top: 4rem; + height: 0.0625rem; + border: none; + margin-bottom: 1rem; + background: linear-gradient( + 90deg, + rgba(184, 184, 184, 0) 0%, + #b8b8b8 50%, + rgba(184, 184, 184, 0) 100% + ); +`; diff --git a/app/availability/styles.ts b/app/availability/styles.ts index 4a360c9..c96d7ff 100644 --- a/app/availability/styles.ts +++ b/app/availability/styles.ts @@ -1,93 +1,119 @@ +import NextImage from 'next/image'; import styled from 'styled-components'; import COLORS from '../../styles/colors'; +import { Sans } from '../../styles/fonts'; +import { H4, P } from '../../styles/text'; -export const Container = styled.div` - width: 500px; - margin: 100px; - cursor: default; - .fc-prev-button, - .fc-next-button { - background: none; - border: none; - color: black; - box-shadow: none; - } - - .fc-prev-button:hover, - .fc-next-button:hover { - background-color: lightgray; - cursor: pointer; - } - - .fc-highlight { - background-color: transparent; - } - - .selected-date { - position: relative; - } +export const ProgressBarContainer = styled.div` + width: 100%; +`; - .cur-selected::after { - background-color: ${COLORS.bread5} !important; - } +export const EventName = styled(P)` + margin-left: 10%; + margin-bottom: 0.75rem; +`; - .non-cur-selected::after { - background-color: ${COLORS.bread3} !important; - } +export const Rectangle = styled.div<{ + variant: 'light' | 'dark'; + width: string; +}>` + width: ${({ width }) => width}; + height: 2px; + display: inline-block; + background: ${({ variant }) => + variant === 'light' ? COLORS.gray4 : COLORS.gray12}; +`; - .selected-date::after { - content: ''; - position: absolute; - width: 45px; - height: 45px; - bottom: 5.5px; - transform: translateX(11px); - border-radius: 50%; - -moz-border-radius: 50%; - -webkit-border-radius: 50%; - z-index: -1; - } +export const SplitText = styled.div` + margin-top: 3rem; + display: flex; + flex-direction: row; + justify-content: space-between; +`; +export const BackButton = styled.button` + background: transparent; + border: none; + cursor: pointer; + float: left; + width: 0; +`; - .unselectable { - color: ${COLORS.gray8}} !important; - } +export const Asterisk = styled(P)` + color: ${COLORS.rose11}; +`; - .fc-day-past div { - opacity: 1 !important; - } +export const QuestionsContainer = styled.div` + display: flex; + flex-direction: column; + gap: 0.5rem; +`; - .fc-day-future div { - opacity: 1 !important; - } +export const Title = styled(H4)` + margin-bottom: 0.5rem; +`; +export const Container = styled.div` + display: flex; + flex-direction: column; + min-height: 100vh; + justify-content: space-between; + padding-left: 2rem; + padding-right: 2rem; + padding-top: 5rem; +`; - .fc-day-today { - background-color: transparent !important; - } +export const ButtonContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: end; + width: 100%; +`; - .fc-day-today::after { - content: ''; - position: absolute; - transform: translateX(31px); - bottom: 125px; - width: 4px; /* Diameter of the dot */ - height: 4px; /* Diameter of the dot */ - background-color: ${COLORS.rose11}; /* Color of the dot */ - border-radius: 50%; /* Makes the element a circle */ - } +export const Divider = styled.hr` + height: 0.125rem; + background-color: ${COLORS.gray4}; + border: none; + margin-bottom: 1rem; + width: 100vw; + margin-left: -2rem; +`; - .fc-daygrid-day-frame { - display: flex; - justify-content: center; - } +export const Image = styled(NextImage)` + width: 20px; + height: 20px; + margin-bottom: 16px; +`; - .fc-daygrid-day-top { - align-items: center; +export const Button = styled.button<{ disabled?: boolean }>` + bottom: 70px; + margin-left: 10%; + margin-bottom: 2rem; + width: 80%; + height: 2.75rem; + background-color: ${({ disabled }) => + disabled ? COLORS.pomegranate10 : COLORS.pomegranate}; + border-color: ${({ disabled }) => + disabled ? COLORS.pomegranate10 : COLORS.pomegranate}; + border-style: solid; + border-radius: 8px; + display: inline-flex; + padding: 8px 16px; + justify-content: center; + align-items: center; + cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; + text-decoration: none; + transition: all 0.3s ease; + + @media (max-width: 768px) { + width: 85%; + bottom: 40px; } +`; - .fc td, - .fc table, - .fc th { - border-style: none !important; - } +export const ContinueText = styled.text` + ${Sans.style} + color: white; + font-size: 14px; + padding: 10px; + text-decoration: none; `; diff --git a/app/availability/times/page.tsx b/app/availability/times/page.tsx new file mode 100644 index 0000000..bc5d06f --- /dev/null +++ b/app/availability/times/page.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { useContext } from 'react'; +import { useRouter } from 'next/navigation'; +import DateTimeSelection from '@/components/DateTimeSelection/DateTimeSelection'; +import Back from '@/public/images/back.svg'; +import { AvailabilityContext } from '@/utils/availabilityContext'; +import { + BackButton, + Button, + ButtonContainer, + Container, + ContinueText, + Divider, + EventName, + Image, + ProgressBarContainer, + Rectangle, + SplitText, + Title, +} from '../styles'; +import { DaylightTime, TimeContainer } from './styles'; + +export default function Page() { + const router = useRouter(); + const availabilityContext = useContext(AvailabilityContext); + + if (!availabilityContext) return null; + + const { days, generalInfo } = availabilityContext; + const handleBack = () => { + router.push('/availability/days'); + }; + + const handleSubmit = async () => { + if (!days) { + return; + } + router.push('/availability/review'); + }; + + return ( + + + + Back icon + + What time's work best? + + + + + {days.map((day, index) => ( + + ))} + + + + {generalInfo.eventName} + PDT + + + + + + ); +} diff --git a/app/availability/times/styles.ts b/app/availability/times/styles.ts new file mode 100644 index 0000000..f923c4b --- /dev/null +++ b/app/availability/times/styles.ts @@ -0,0 +1,13 @@ +import styled from 'styled-components'; +import COLORS from '@/styles/colors'; +import { P } from '@/styles/text'; + +export const TimeContainer = styled.div` + display: flex; + flex-direction: column; +`; + +export const DaylightTime = styled(P)` + color: ${COLORS.gray10}; + font-weight: 400; +`; diff --git a/app/onboarding/basic-information/page.tsx b/app/onboarding/basic-information/page.tsx index 0fa5650..2ee0e42 100644 --- a/app/onboarding/basic-information/page.tsx +++ b/app/onboarding/basic-information/page.tsx @@ -33,10 +33,10 @@ export default function Onboarding() { const { generalInfo, setGeneralInfo } = onboardingContext; const handleChange = (e: React.ChangeEvent) => { - const { name, type, checked, value } = e.target; + const { name, value } = e.target; setGeneralInfo({ ...generalInfo, - [name]: type === 'checkbox' ? checked : value, + [name]: value, }); }; diff --git a/components/DateTimeSelection/DateTimeSelection.tsx b/components/DateTimeSelection/DateTimeSelection.tsx index 1571e32..c0525eb 100644 --- a/components/DateTimeSelection/DateTimeSelection.tsx +++ b/components/DateTimeSelection/DateTimeSelection.tsx @@ -1,36 +1,55 @@ -import { useState } from 'react'; +import { useContext } from 'react'; import TimeSelection from '@/components/TimeSelection/TimeSelection'; +import X from '@/public/images/x.svg'; import COLORS from '@/styles/colors'; import { H6, SMALL } from '@/styles/text'; -import { AddTime, Bar, Container, TimeList, Times } from './styles'; +import { AvailabilityContext, defaultRange } from '@/utils/availabilityContext'; +import { + AddTime, + Bar, + Container, + DeleteButton, + Image, + TimeList, + Times, +} from './styles'; -export default function DateTimeSelection({ date }: { date: Date }) { - const [timeList, setTimeList] = useState([ - - {/* initialize at 9 am / 5 pm */} - -
    -
    - -
    , - ]); +export default function DateTimeSelection({ date }: { date: string }) { + const availabilityContext = useContext(AvailabilityContext); - const onAddBtnClick = () => { - setTimeList( - timeList.concat( - - {/* initialize at 9 am / 5 pm */} - -
    -
    - -
    , - ), - ); + if (!availabilityContext) return null; + + const { times, setTimes } = availabilityContext; + console.log(times); + const addTimeRange = () => { + setTimes({ + ...times, + [date]: [...times[date], structuredClone(defaultRange)], + }); + }; + + const updateTimeRange = ( + index: number, + selectType: string, + newVal: number, + ) => { + const newDateTimes = [...times[date]]; + if (selectType == 'start') { + newDateTimes[index].start = newVal; + } else { + newDateTimes[index].end = newVal; + } + setTimes({ ...times, [date]: newDateTimes }); + }; + + const deleteTimeRange = (index: number) => { + setTimes({ ...times, [date]: times[date].filter((_, i) => i !== index) }); }; return (
    - {date.toLocaleDateString('en-US', { + {new Date(date).toLocaleDateString('en-US', { weekday: 'short', month: 'long', day: 'numeric', @@ -38,8 +57,30 @@ export default function DateTimeSelection({ date }: { date: Date }) { })}
    - {timeList} - onAddBtnClick()}> + + {times[date].map((time, index) => ( + + {/* initialize at 9 am / 5 pm */} + +
    + + deleteTimeRange(index)}> + delete + +
    + ))} +
    + addTimeRange()}> add time diff --git a/components/DateTimeSelection/styles.ts b/components/DateTimeSelection/styles.ts index ddd1cd1..fb654e5 100644 --- a/components/DateTimeSelection/styles.ts +++ b/components/DateTimeSelection/styles.ts @@ -1,3 +1,4 @@ +import NextImage from 'next/image'; import styled from 'styled-components'; import COLORS from '@/styles/colors'; @@ -26,6 +27,7 @@ export const AddTime = styled.button` all: unset; padding-top: 1rem; padding-bottom: 1rem; + cursor: pointer; `; export const TimeList = styled.div` @@ -33,3 +35,16 @@ export const TimeList = styled.div` flex-direction: column; gap: 0.5rem; `; + +export const Image = styled(NextImage)` + width: 7px; + height: 7px; +`; + +export const DeleteButton = styled.button` + background: transparent; + border: none; + cursor: pointer; + float: left; + width: 0; +`; diff --git a/components/TimeSelection/TimeSelection.tsx b/components/TimeSelection/TimeSelection.tsx index 8d86a4c..42ae73d 100644 --- a/components/TimeSelection/TimeSelection.tsx +++ b/components/TimeSelection/TimeSelection.tsx @@ -5,7 +5,17 @@ import * as Select from '@radix-ui/react-select'; import classnames from 'classnames'; import { Container, Content } from './styles'; -export default function TimeSelection({ minutes }: { minutes: number }) { +export default function TimeSelection({ + minutes, + updateTimeRange, + selectType, + index, +}: { + minutes: number; + updateTimeRange: (index: number, selectType: string, newVal: number) => void; + selectType: string; + index: number; +}) { const minutesToFormatted = (minutes: number) => { const hours = Math.floor(minutes / 60); const mins = minutes % 60; @@ -30,7 +40,10 @@ export default function TimeSelection({ minutes }: { minutes: number }) { return ( - + updateTimeRange(index, selectType, +val)} + > diff --git a/components/TimeSelection/styles.ts b/components/TimeSelection/styles.ts index 7e928bb..3b5ca93 100644 --- a/components/TimeSelection/styles.ts +++ b/components/TimeSelection/styles.ts @@ -39,8 +39,7 @@ export const Container = styled.div` export const Content = styled.div` .Scroll { - overflow: scroll; - height: 200px; + overflow-y: scroll; background-color: white; border: 1px solid ${COLORS.rose7}; border-radius: 8px; @@ -54,9 +53,6 @@ export const Content = styled.div` height: 100%; } - .SelectContent { - } - .SelectScrollButton { display: flex; align-items: center; @@ -80,7 +76,7 @@ export const Content = styled.div` user-select: none; color = ${COLORS.gray11}; } - + .SelectItem[data-highlighted] { outline: none; background-color: ${COLORS.rose5}; diff --git a/public/images/x.svg b/public/images/x.svg new file mode 100644 index 0000000..11ea7a6 --- /dev/null +++ b/public/images/x.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/utils/availabilityContext.tsx b/utils/availabilityContext.tsx new file mode 100644 index 0000000..93ae021 --- /dev/null +++ b/utils/availabilityContext.tsx @@ -0,0 +1,73 @@ +'use client'; + +import React, { createContext, ReactNode, useState } from 'react'; +import supabase from '@/api/supabase/createClient'; + +export interface GeneralInfo { + eventName: string; + additionalInfo: string; +} + +export interface TimeRange { + start: number; + end: number; +} + +export const defaultRange: TimeRange = { + start: 9 * 60, + end: 17 * 60, +}; + +interface AvailabilityContextType { + generalInfo: GeneralInfo; + setGeneralInfo: (info: GeneralInfo) => void; + days: string[]; + setDays: (days: string[]) => void; + times: { [date: string]: TimeRange[] }; + setTimes: (times: { [date: string]: TimeRange[] }) => void; + submitAvailabilityData: () => Promise; +} + +export const AvailabilityContext = createContext< + AvailabilityContextType | undefined +>(undefined); + +export const AvailabilityProvider = ({ children }: { children: ReactNode }) => { + const [generalInfo, setGeneralInfo] = useState({ + eventName: '', + additionalInfo: '', + }); + const [days, setDays] = useState([]); + const [times, setTimes] = useState<{ [date: string]: TimeRange[] }>({}); + + const submitAvailabilityData = async () => { + // try { + // const { data: volunteerData, error: volunteerError } = await supabase + // .from('availabilities') + // .insert([ + // { + // first_name: generalInfo.firstName, + // last_name: generalInfo.lastName, + // phone_number: generalInfo.phoneNumber, + // notifications_opt_in: generalInfo.notifications, + // }, + // ]); + // if (volunteerError) throw volunteerError; + }; + + return ( + + {children} + + ); +};