From 1613fc9e8789251e29809acdbd6375fa487da54a Mon Sep 17 00:00:00 2001 From: SecurityQQ Date: Thu, 6 Jun 2024 18:43:53 +0400 Subject: [PATCH] fix typing bugs --- src/assets/recommendations.json | 32 +++ src/components/Calendar.tsx | 190 +++++++++++++----- src/components/Loader.tsx | 18 ++ src/components/MiniCalendar.tsx | 90 +++++++++ src/components/PeriodTracking.tsx | 67 ++++-- src/contexts/UserContext.tsx | 54 +++-- .../api/{invitations => invitation}/index.ts | 0 .../{menstruations => menstruation}/index.ts | 27 ++- src/pages/api/{users => user}/[id]/balance.ts | 0 src/pages/api/{users => user}/[id]/index.ts | 0 src/pages/api/{users => user}/index.ts | 0 src/pages/calendar.tsx | 32 ++- src/pages/period-tracker.tsx | 18 +- 13 files changed, 419 insertions(+), 109 deletions(-) create mode 100644 src/assets/recommendations.json create mode 100644 src/components/Loader.tsx create mode 100644 src/components/MiniCalendar.tsx rename src/pages/api/{invitations => invitation}/index.ts (100%) rename src/pages/api/{menstruations => menstruation}/index.ts (51%) rename src/pages/api/{users => user}/[id]/balance.ts (100%) rename src/pages/api/{users => user}/[id]/index.ts (100%) rename src/pages/api/{users => user}/index.ts (100%) diff --git a/src/assets/recommendations.json b/src/assets/recommendations.json new file mode 100644 index 0000000..c5b31f0 --- /dev/null +++ b/src/assets/recommendations.json @@ -0,0 +1,32 @@ +{ + "recommendations": [ + "Сегодня первый день твоих месячных, дорогая. Побалуй себя горячей ванной и шоколадом! 🍫", + "Сегодня комфортный день: надень уютную одежду, смотри сериалы и расслабься", + "Сегодня день йоги и смузи! Легкая растяжка и вкусный напиток со шпинатом 🧘‍♀️", + "Сегодня день фильма! Укутайся в одеяло, смотри кино и ешь попкорн 🍿", + "Последний день! Сделай маникюр, маску для лица и порадуй себя уходом 💅", + "Сегодня активный день! Прогулка в парке или танцевальный класс для энергии 🌳", + "Сегодня начни хобби или проект, ешь овощи и белок для энергии", + "Сегодня побалуй себя десертом и любимым занятием, расслабься и наслаждайся 🍰", + "Сегодня спа-день! Увлажняющая маска и сияющая кожа, наслаждайся ✨", + "Сегодня день красоты! Нарядись, даже если для себя, планируй свидание 💃", + "Сегодня тренировка! Чувствуй себя сильной и ешь антиоксиданты 🏋️‍♀️", + "Сегодня творчество! Пиши, рисуй, занимайся чем-то вдохновляющим 🎨", + "Сегодня расслабься! Массаж или ванна со свечами перед овуляцией 🛀", + "Сегодня овуляция! Наслаждайся романтикой или сольным удовольствием ❤️", + "Сегодня йога! Сохрани спокойствие и балансируй свои эмоции 🧘‍♀️", + "Сегодня чай и клетчатка! Пей травяной чай и ешь пищу для снятия вздутия ☕", + "Сегодня уход за кожей! Увлажняющая маска и много воды для сияния 💧", + "Сегодня медитация! Практикуй медитацию или глубокое дыхание для спокойствия 🧘", + "Сегодня ужин! Встречайся с друзьями или семьей для поднятия настроения 🍽️", + "Сегодня творчество! Займись рукоделием или декором для креативности ✂️", + "Сегодня отдых! Слушай тело, отдохни с хорошей книгой или фильмом 📚", + "Сегодня суп! Ешь сытный суп или рагу для утешения и питательной ценности 🍲", + "Сегодня ванна! Расслабляющая ванна с лавандой для спокойствия 🛀", + "Сегодня дневник! Ведение дневника или прогулка на природе для гармонии 🌿", + "Сегодня подготовка! Запасись любимыми менструальными продуктами", + "Сегодня легкая йога! Упражнения и гидратация для снятия предменструальных болей 🧘‍♀️", + "Сегодня уют! Расслабься дома с теплым напитком и любимым шоу ☕", + "Сегодня уход за собой! Размышляй и ставь цели на следующий месяц 🌟" + ] +} diff --git a/src/components/Calendar.tsx b/src/components/Calendar.tsx index 878e592..75dc7e7 100644 --- a/src/components/Calendar.tsx +++ b/src/components/Calendar.tsx @@ -1,73 +1,153 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; + +import MiniCalendar from './MiniCalendar'; type CalendarProps = { lastMenstruationDate: Date; cycleLength: number; mode: 'mini' | 'full'; + isEditing: boolean; + onEdit: () => void; + onSave: (date: Date) => Promise; }; -const Calendar: React.FC = ({ lastMenstruationDate, cycleLength, mode }) => { +const Calendar: React.FC = ({ + lastMenstruationDate, + cycleLength, + mode, + isEditing, + onEdit, + onSave, +}) => { + const [months, setMonths] = useState([]); + const [currentPeriodDate, setCurrentPeriodDate] = useState(lastMenstruationDate); + + useEffect(() => { + const initialMonths = []; + for (let i = -1; i <= 1; i++) { + initialMonths.push(new Date(new Date().getFullYear(), new Date().getMonth() + i, 1)); + } + setMonths(initialMonths); + }, []); + + const loadPreviousMonth = () => { + setMonths((prev) => [new Date(prev[0].getFullYear(), prev[0].getMonth() - 1, 1), ...prev]); + }; + + const loadNextMonth = () => { + setMonths((prev) => [ + ...prev, + new Date(prev[prev.length - 1].getFullYear(), prev[prev.length - 1].getMonth() + 1, 1), + ]); + }; + + const daysInMonth = (date: Date) => + new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); + + const generateDates = (month: Date) => { + const startOfMonth = new Date(month.getFullYear(), month.getMonth(), 1); + const endOfMonth = new Date(month.getFullYear(), month.getMonth() + 1, 0); + const daysInPrevMonth = daysInMonth(new Date(month.getFullYear(), month.getMonth() - 1)); + const startDay = startOfMonth.getDay(); + const daysInCurrentMonth = daysInMonth(month); + + const dates = []; + + // Fill dates from previous month + for (let i = startDay - 1; i >= 0; i--) { + dates.push(new Date(month.getFullYear(), month.getMonth() - 1, daysInPrevMonth - i)); + } + + // Fill dates from current month + for (let i = 1; i <= daysInCurrentMonth; i++) { + dates.push(new Date(month.getFullYear(), month.getMonth(), i)); + } + + // Fill dates from next month + const nextMonthDays = 42 - dates.length; // 42 to fill the calendar grid + for (let i = 1; i <= nextMonthDays; i++) { + dates.push(new Date(month.getFullYear(), month.getMonth() + 1, i)); + } + + return dates; + }; + + const today = new Date(); const daysSinceLastPeriod = Math.floor( - (new Date().getTime() - new Date(lastMenstruationDate).getTime()) / (1000 * 60 * 60 * 24) + (today.getTime() - new Date(currentPeriodDate).getTime()) / (1000 * 60 * 60 * 24) ); - const today = new Date(); - let dates = []; + const isFutureDate = (date: Date) => date > today; + + const handleDateClick = async (date: Date) => { + if (isEditing) { + setCurrentPeriodDate(date); + await onSave(date); + } + }; if (mode === 'mini') { - dates = Array.from({ length: 7 }, (_, index) => { - const date = new Date(today); - date.setDate(today.getDate() + index); - return date; - }); - } else { - const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1); - const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0); - const daysInMonth = endOfMonth.getDate(); - - dates = Array.from({ length: daysInMonth }, (_, index) => { - const date = new Date(startOfMonth); - date.setDate(startOfMonth.getDate() + index); - return date; - }); + return ; } - const dayNames = ['S', 'M', 'T', 'W', 'T', 'F', 'S']; - return ( -
-
- {dayNames.map((day, index) => ( - - {day} - - ))} -
-
- {dates.map((date, index) => { - const cycleDay = - (daysSinceLastPeriod + (mode === 'mini' ? index : date.getDate() - 1)) % cycleLength; - const isOvulation = cycleDay >= 12 && cycleDay <= 17; - const isPeriod = cycleDay >= 1 && cycleDay <= 5; - const isToday = date.toDateString() === today.toDateString(); - - return ( -
-
- {date.getDate()} -
-
- ); - })} +
+ {months.map((month, index) => ( +
+
+

+ {month.toLocaleString('default', { month: 'long', year: 'numeric' })} +

+
+ +
+ {generateDates(month).map((date, index) => { + const isCurrentMonth = date.getMonth() === month.getMonth(); + const daysFromStartOfCycle = + Math.floor( + (date.getTime() - new Date(currentPeriodDate).getTime()) / (1000 * 60 * 60 * 24) + ) % cycleLength; + const isPeriod = daysFromStartOfCycle >= 0 && daysFromStartOfCycle < 5; + const isOvulation = daysFromStartOfCycle >= 12 && daysFromStartOfCycle < 17; + const isToday = date.toDateString() === today.toDateString(); + const futurePeriod = isPeriod && isFutureDate(date); + const futureOvulation = isOvulation && isFutureDate(date); + const showPeriod = isPeriod && date >= currentPeriodDate; + const showOvulation = isOvulation && date >= currentPeriodDate; + + return ( +
+
handleDateClick(date)}> + {date.getDate()} +
+
+ ); + })} +
+
+ ))} +
+
); diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx new file mode 100644 index 0000000..668cf4a --- /dev/null +++ b/src/components/Loader.tsx @@ -0,0 +1,18 @@ +import React from 'react'; + +interface LoaderProps { + text?: string; +} + +const Loader: React.FC = ({ text = 'Loading...' }) => { + return ( +
+
+ Loading +

{text}

+
+
+ ); +}; + +export default Loader; diff --git a/src/components/MiniCalendar.tsx b/src/components/MiniCalendar.tsx new file mode 100644 index 0000000..21f394b --- /dev/null +++ b/src/components/MiniCalendar.tsx @@ -0,0 +1,90 @@ +import React from 'react'; + +type MiniCalendarProps = { + lastMenstruationDate: Date; + cycleLength: number; +}; + +const MiniCalendar: React.FC = ({ lastMenstruationDate, cycleLength }) => { + const today = new Date(); + const daysSinceLastPeriod = Math.floor( + (today.getTime() - new Date(lastMenstruationDate).getTime()) / (1000 * 60 * 60 * 24) + ); + + const generateNext7Days = () => { + const dates = []; + for (let i = 0; i < 7; i++) { + const date = new Date(today); + date.setDate(today.getDate() + i); + dates.push(date); + } + return dates; + }; + + const dates = generateNext7Days(); + + const dayNames = ['П', 'Вт', 'Ср', 'Че', 'П', 'Сб', 'Во']; + + const getOrderedDayNames = () => { + const startIndex = (today.getDay() + 6) % 7; // getDay() returns 0 for Sunday, so adjust + const orderedDays = [...dayNames.slice(startIndex), ...dayNames.slice(0, startIndex)]; + return orderedDays; + }; + + const orderedDayNames = getOrderedDayNames(); + + return ( +
+
+ {orderedDayNames.map((day, index) => { + const daysFromStartOfCycle = (daysSinceLastPeriod + index) % cycleLength; + const isPeriod = daysFromStartOfCycle >= 0 && daysFromStartOfCycle < 5; + const isToday = index === 0; // First element corresponds to today + + return ( + + {isToday ? 'СЕГОДНЯ' : day} + + ); + })} +
+
+ {dates.map((date, index) => { + const daysFromStartOfCycle = (daysSinceLastPeriod + index) % cycleLength; + const isPeriod = daysFromStartOfCycle >= 0 && daysFromStartOfCycle < 5; + const isOvulation = daysFromStartOfCycle >= 12 && daysFromStartOfCycle < 17; + const isToday = date.toDateString() === today.toDateString(); + + return ( +
+
+ {date.getDate()} +
+
+ ); + })} +
+
+ ); +}; + +export default MiniCalendar; diff --git a/src/components/PeriodTracking.tsx b/src/components/PeriodTracking.tsx index f89209b..e8a3946 100644 --- a/src/components/PeriodTracking.tsx +++ b/src/components/PeriodTracking.tsx @@ -1,6 +1,7 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; -import Calendar from './Calendar'; +import MiniCalendar from './MiniCalendar'; +import recommendationsData from '../assets/recommendations.json'; import { useTonConnect } from '../hooks/useTonConnect'; type PeriodTrackingProps = { @@ -15,6 +16,12 @@ type PeriodTrackingProps = { onSubscribeChannels: () => void; }; +const getDayLabel = (days: number) => { + if (days === 1) return 'день'; + if (days >= 2 && days <= 4) return 'дня'; + return 'дней'; +}; + const PeriodTracking: React.FC = ({ userName, lastMenstruationDate, @@ -27,37 +34,67 @@ const PeriodTracking: React.FC = ({ onSubscribeChannels, }) => { const { network, wallet, address } = useTonConnect(); + const [isEditing, setIsEditing] = useState(false); + const [recommendation, setRecommendation] = useState(''); + const [daysUntilNextEvent, setDaysUntilNextEvent] = useState(0); + const [eventMessage, setEventMessage] = useState(''); + const [bgColor, setBgColor] = useState('bg-blue-100'); + const [textColor, setTextColor] = useState('text-blue-500'); - // Calculate days until next period assuming a 28-day cycle const cycleLength = 28; const daysSinceLastPeriod = Math.floor( (new Date().getTime() - new Date(lastMenstruationDate).getTime()) / (1000 * 60 * 60 * 24) ); const daysUntilNextPeriod = cycleLength - (daysSinceLastPeriod % cycleLength); + useEffect(() => { + const phaseIndex = daysSinceLastPeriod % recommendationsData.recommendations.length; + setRecommendation(recommendationsData.recommendations[phaseIndex]); + + if (daysSinceLastPeriod < 5) { + setDaysUntilNextEvent(5 - daysSinceLastPeriod); + setEventMessage('Месячные будут'); + setBgColor('bg-ffd3c6'); + setTextColor('text-f77047'); + } else if (daysSinceLastPeriod >= 5 && daysSinceLastPeriod < 12) { + setDaysUntilNextEvent(12 - daysSinceLastPeriod); + setEventMessage('Овуляция через'); + setBgColor('bg-blue-100'); + setTextColor('text-blue-500'); + } else if (daysSinceLastPeriod >= 12 && daysSinceLastPeriod < 17) { + setDaysUntilNextEvent(17 - daysSinceLastPeriod); + setEventMessage('Овуляция будет'); + setBgColor('bg-blue-100'); + setTextColor('text-blue-500'); + } else { + setDaysUntilNextEvent(daysUntilNextPeriod); + setEventMessage('Месячные через'); + setBgColor('bg-blue-100'); + setTextColor('text-blue-500'); + } + }, [daysSinceLastPeriod]); + return (
-

Fem Health

+

Female TON

- - {/* You can change mode to "full" to display the full calendar */} - {/* */} + -
-

Месячные через

-

{daysUntilNextPeriod} дней

-

Низкая вероятность забеременеть

+
+

{eventMessage}

+

+ {daysUntilNextEvent} {getDayLabel(daysUntilNextEvent)} +

+

{recommendation}