diff --git a/.gitignore b/.gitignore index 594931c..7e8b523 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ yarn-error.log* next-env.d.ts .idea +.turbo /private /.react-email diff --git a/package-lock.json b/package-lock.json index 8b00f6f..d310c9e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "aws-sdk": "^2.1483.0", "axios": "^1.5.1", "bcrypt": "^5.1.1", + "dayjs": "^1.11.10", "framer-motion": "^10.16.4", "jsonwebtoken": "^9.0.2", "next": "^13.5.6", @@ -32,6 +33,7 @@ "next-nprogress-bar": "^2.1.2", "next-themes": "^0.2.1", "nodemailer": "^6.9.7", + "ramda": "^0.29.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-email": "1.9.5", @@ -49,6 +51,7 @@ "@types/multer": "^1.4.9", "@types/node": "^20", "@types/nodemailer": "^6.4.13", + "@types/ramda": "^0.29.7", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10", @@ -5482,6 +5485,15 @@ "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", "dev": true }, + "node_modules/@types/ramda": { + "version": "0.29.7", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.29.7.tgz", + "integrity": "sha512-IUl6U95qwlQtVvZkSX4ODj08oJVtPyWMFRtPVNqhxc2rt+Bh7lCzTrGMYMZ7dmRKcAjtot3xrPnYGwsjdt8gzQ==", + "dev": true, + "dependencies": { + "types-ramda": "^0.29.5" + } + }, "node_modules/@types/range-parser": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", @@ -6566,6 +6578,11 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -10252,6 +10269,15 @@ } ] }, + "node_modules/ramda": { + "version": "0.29.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz", + "integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -11398,6 +11424,12 @@ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "dev": true + }, "node_modules/tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -11554,6 +11586,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/types-ramda": { + "version": "0.29.5", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.29.5.tgz", + "integrity": "sha512-u+bAYXHDPJR+amB0qMrMU/NXRB2PG8QqpO2v6j7yK/0mPZhlaaZj++ynYjnVpkPEpCkZEGxNpWY3X7qyLCGE3w==", + "dev": true, + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", diff --git a/package.json b/package.json index 606fc0d..721b8fe 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "aws-sdk": "^2.1483.0", "axios": "^1.5.1", "bcrypt": "^5.1.1", + "dayjs": "^1.11.10", "framer-motion": "^10.16.4", "jsonwebtoken": "^9.0.2", "next": "^13.5.6", @@ -35,6 +36,7 @@ "next-nprogress-bar": "^2.1.2", "next-themes": "^0.2.1", "nodemailer": "^6.9.7", + "ramda": "^0.29.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-email": "1.9.5", @@ -52,6 +54,7 @@ "@types/multer": "^1.4.9", "@types/node": "^20", "@types/nodemailer": "^6.4.13", + "@types/ramda": "^0.29.7", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10", diff --git a/src/app/(site)/(internal)/dashboard/calendar/components/Calendar.tsx b/src/app/(site)/(internal)/dashboard/calendar/components/Calendar.tsx new file mode 100644 index 0000000..c448d80 --- /dev/null +++ b/src/app/(site)/(internal)/dashboard/calendar/components/Calendar.tsx @@ -0,0 +1,93 @@ +"use client" + +import {FC, useState} from "react"; +import Select from "@/app/(site)/components/inputs/Select"; +import { + createDaysForCurrentMonth, + createDaysForNextMonth, createDaysForPreviousMonth, + daysOfWeek +} from "@/app/(site)/(internal)/dashboard/calendar/utils/calendar-utils"; + +const Calendar: FC = () => { + const [[year, month], setCurrentYearAndMonth] = useState<[number, number]>([new Date().getFullYear(), new Date().getMonth() + 1]); + + let currentMonthDays = createDaysForCurrentMonth(year, month); + let previousMonthDays = createDaysForPreviousMonth( + year, + month, + currentMonthDays + ); + let nextMonthDays = createDaysForNextMonth(year, month, currentMonthDays); + let calendarGridDayObjects = [ + ...previousMonthDays, + ...currentMonthDays, + ...nextMonthDays + ]; + + return ( +
+
+
+ + +
+ +
+
+ {daysOfWeek.map((day, i) => ( +

+ {day} +

+ + ))} +
+
+ {daysOfWeek.map((day, i) => ( +

+ {day.charAt(0)} +

+ + ))} +
+
+ { + calendarGridDayObjects.map(day => ( +
+
+ {day.dayOfMonth} +
+
+ hi +
+
+ )) + } +
+
+ ) +} + +export default Calendar \ No newline at end of file diff --git a/src/app/(site)/(internal)/dashboard/calendar/page.tsx b/src/app/(site)/(internal)/dashboard/calendar/page.tsx new file mode 100644 index 0000000..fad7eb8 --- /dev/null +++ b/src/app/(site)/(internal)/dashboard/calendar/page.tsx @@ -0,0 +1,13 @@ +import {FC, Fragment} from "react"; +import Calendar from "@/app/(site)/(internal)/dashboard/calendar/components/Calendar"; + +const CalendarPage: FC = () => { + return ( + +

Dream Calendar

+ +
+ ) +} + +export default CalendarPage \ No newline at end of file diff --git a/src/app/(site)/(internal)/dashboard/calendar/utils/calendar-utils.ts b/src/app/(site)/(internal)/dashboard/calendar/utils/calendar-utils.ts new file mode 100644 index 0000000..6bb5826 --- /dev/null +++ b/src/app/(site)/(internal)/dashboard/calendar/utils/calendar-utils.ts @@ -0,0 +1,100 @@ +import { range } from "ramda"; +import dayjs from "dayjs"; +import weekday from "dayjs/plugin/weekday"; +import weekOfYear from "dayjs/plugin/weekOfYear"; + +dayjs.extend(weekday); +dayjs.extend(weekOfYear); + +export const daysOfWeek = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" +]; + +export function getYearDropdownOptions(currentYear: number) { + let minYear = currentYear - 4; + let maxYear = currentYear + 5; + return range(minYear, maxYear + 1).map((y) => ({ label: `${y}`, value: y })); +} + +export function getMonthDropdownOptions() { + return range(1, 13).map((m) => ({ + value: m, + label: dayjs() + .month(m - 1) + .format("MMMM") + })); +} + +export function getNumberOfDaysInMonth(year: number, month: number) { + return dayjs(`${year}-${month}-01`).daysInMonth(); +} + +export function createDaysForCurrentMonth(year: number, month: number) { + return [...Array(getNumberOfDaysInMonth(year, month))].map((_, index) => { + return { + dateString: dayjs(`${year}-${month}-${index + 1}`).format("YYYY-MM-DD"), + dayOfMonth: index + 1, + isCurrentMonth: true + }; + }); +} + +export function createDaysForPreviousMonth(year: number, month: number, currentMonthDays: any[]) { + const firstDayOfTheMonthWeekday = getWeekday(currentMonthDays[0].dateString); + const previousMonth = dayjs(`${year}-${month}-01`).subtract(1, "month"); + + const visibleNumberOfDaysFromPreviousMonth = firstDayOfTheMonthWeekday; + + const previousMonthLastMondayDayOfMonth = dayjs( + currentMonthDays[0].dateString + ) + .subtract(visibleNumberOfDaysFromPreviousMonth, "day") + .date(); + + return [...Array(visibleNumberOfDaysFromPreviousMonth)].map((_, index) => { + return { + dateString: dayjs( + `${previousMonth.year()}-${previousMonth.month() + 1}-${ + previousMonthLastMondayDayOfMonth + index + }` + ).format("YYYY-MM-DD"), + dayOfMonth: previousMonthLastMondayDayOfMonth + index, + isCurrentMonth: false, + isPreviousMonth: true + }; + }); +} + +export function createDaysForNextMonth(year: number, month: number, currentMonthDays: any) { + const lastDayOfTheMonthWeekday = getWeekday( + `${year}-${month}-${currentMonthDays.length}` + ); + const nextMonth = dayjs(`${year}-${month}-01`).add(1, "month"); + const visibleNumberOfDaysFromNextMonth = 6 - lastDayOfTheMonthWeekday; + + return [...Array(visibleNumberOfDaysFromNextMonth)].map((day, index) => { + return { + dateString: dayjs( + `${nextMonth.year()}-${nextMonth.month() + 1}-${index + 1}` + ).format("YYYY-MM-DD"), + dayOfMonth: index + 1, + isCurrentMonth: false, + isNextMonth: true + }; + }); +} + +// sunday === 0, saturday === 6 +export function getWeekday(dateString: string) { + return dayjs(dateString).weekday(); +} + +export function isWeekendDay(dateString: string) { + return [6, 0].includes(getWeekday(dateString)); +} diff --git a/src/app/(site)/(internal)/dashboard/components/DashboardSidebar.tsx b/src/app/(site)/(internal)/dashboard/components/DashboardSidebar.tsx index 63871f7..b11a83c 100644 --- a/src/app/(site)/(internal)/dashboard/components/DashboardSidebar.tsx +++ b/src/app/(site)/(internal)/dashboard/components/DashboardSidebar.tsx @@ -21,7 +21,7 @@ const DashboardSidebar: FC = () => { } title="Dream Calendar" - href="/dashboard" + href="/dashboard/calendar" /> } diff --git a/src/app/(site)/(internal)/settings/account/components/DeleteAccountButton.tsx b/src/app/(site)/(internal)/settings/account/components/DeleteAccountButton.tsx index d5170e8..67e33dd 100644 --- a/src/app/(site)/(internal)/settings/account/components/DeleteAccountButton.tsx +++ b/src/app/(site)/(internal)/settings/account/components/DeleteAccountButton.tsx @@ -6,7 +6,7 @@ import ConfirmationModal from "@/app/(site)/components/ConfirmationModal"; import {AnimatePresence, motion} from "framer-motion"; import Input from "@/app/(site)/components/inputs/Input"; import {signOut, useSession} from "next-auth/react"; -import {deleteMutator} from "@/utils/client/client-utils"; +import {deleteMutatorWithArgs} from "@/utils/client/client-utils"; import {DeleteSelfDto} from "@/app/api/me/self-user.dto"; import useSWRMutation from "swr/mutation"; import {Member} from "@prisma/client"; @@ -14,7 +14,7 @@ import toast from "react-hot-toast"; import {AxiosError} from "axios"; const DeleteAccount = () => ( - useSWRMutation('/api/me', deleteMutator()) + useSWRMutation('/api/me', deleteMutatorWithArgs()) ) const DeleteAccountButton: FC = () => { diff --git a/src/utils/client/client-utils.tsx b/src/utils/client/client-utils.tsx index cf23fb8..2bbc4ea 100644 --- a/src/utils/client/client-utils.tsx +++ b/src/utils/client/client-utils.tsx @@ -21,7 +21,8 @@ export type MutatorArgs = { export const postMutator = () => (url: string, {arg}: MutatorArgs) => axios.post(url, arg.body) export const patchMutator = () => (url: string, {arg}: MutatorArgs) => axios.patch(url, arg.body) -export const deleteMutator = , R>() => (url: string, args?: MutatorArgs) => axios.delete(`${url}${args ? "?" + new URLSearchParams(args.arg.body).toString() : ""}`) +export const deleteMutatorWithArgs = | undefined, R>() => (url: string, args?: MutatorArgs) => axios.delete(`${url}${args ? "?" + new URLSearchParams(args.arg.body).toString() : ""}`) +export const deleteMutator = () => (url: string) => axios.delete(url) export function handleAxiosError(error: any): undefined { diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..75e9460 --- /dev/null +++ b/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build": { + "outputs": [".next/**", "!.next/cache/**"] + }, + "lint": {} + } +} \ No newline at end of file