diff --git a/package-lock.json b/package-lock.json index abb3f03..d1d7d49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", @@ -2117,6 +2118,419 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.1.tgz", + "integrity": "sha512-3y1A3isulwnWhvTTwmIreiB8CF4L+qRjZnK1wYLO7pplddzXKby/GnZ2M7OZY3qgnl6p9AodUIHRYGXNah8Y7g==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", + "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", + "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-portal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", + "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", + "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, + "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", diff --git a/package.json b/package.json index edde4c3..7d935ac 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.1.1", "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", diff --git a/src/app/company/setup/page.tsx b/src/app/company/setup/page.tsx index 826f688..f1cbdfe 100644 --- a/src/app/company/setup/page.tsx +++ b/src/app/company/setup/page.tsx @@ -1,24 +1,33 @@ "use client"; -import React, { useRef, useEffect } from "react"; +import React, { useRef, useEffect, useState } from "react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Progress } from "@/components/ui/progress"; import Image from "next/image"; -// import { gql, useMutation } from "@apollo/client"; -// import { CREATE_COMPANY} from "@/lib/graphql/mutations"; +import { type FetchResult, useMutation } from "@apollo/client"; +import { CREATE_COMPANY } from "@/lib/graphql/mutations"; import Loader from "@/components/layout/Loader"; +import { useRouter } from "next/navigation"; +import { toast } from "@/components/ui/use-toast"; -type Inputs = { +type DisplayInputs = { "Full Company Name": string; "CEO Email": string; "CEO Name": string; "CEO Password": string; }; -function Setup() { - const [step, setStep] = React.useState(0); +type MutationInputs = { + companyName: string; + ownerEmail: string; + ownerName: string; + ownerPassword: string; +}; - const [inputs, setInputs] = React.useState({ +function Setup() { + const [step, setStep] = useState(0); + const router = useRouter(); + const [inputs, setInputs] = useState({ "Full Company Name": "", "CEO Email": "", "CEO Name": "", @@ -26,106 +35,134 @@ function Setup() { }); const placeholders = ["Full Company Name", "CEO Email", "CEO Name", "CEO Password"]; - const input: React.MutableRefObject = useRef(null); - const [error, setError] = React.useState(""); - // const [createCompany, { data, loading, error: mutationError }] = useMutation(CREATE_COMPANY); + const input = useRef(null); + const [error, setError] = useState(""); + const [createCompany, { loading, error: mutationError }] = useMutation(CREATE_COMPANY); + const nextStep = () => { - if (input.current?.value === "") { + if (input.current?.value === null) { setError("Please fill in the input field"); return; - } else if (input.current?.validity.valid === false) { + } + if (step === 1 && input.current?.validity.valid === false) { setError("Please enter a valid email"); return; - } else { - setError(""); - setStep(step + 1); } + setError(""); + setStep((prev) => prev + 1); }; + const submit = async () => { - if (input.current?.value === "") { + if (input.current?.value === null) { setError("Please fill in the input field"); return; - } else setError(""); + } if (input.current?.validity.valid === false) { setError("Please enter a valid password"); return; - } else { - setError(""); - console.info("submitting", inputs); - - setStep(4); - // await createCompany({ variables: inputs }).then((res) => { - // console.log("done", res); - // }); + } + + const allInputsFilled = Object.values(inputs).every((value) => value.trim() !== ""); + if (!allInputsFilled) { + setError("Please fill in all fields"); + return; + } + + setError(""); + console.info("submitting", inputs); + + const mutationInputs: MutationInputs = { + companyName: inputs["Full Company Name"], + ownerEmail: inputs["CEO Email"], + ownerName: inputs["CEO Name"], + ownerPassword: inputs["CEO Password"] + }; + + try { + const res: FetchResult<{ createCompany: string }> = await createCompany({ variables: mutationInputs }); + if (res.data?.createCompany.includes("200") === true) { + toast({ + title: "Company created!", + description: "You can now login to your account", + variant: "default", + className: "border-emerald-300" + }); + setTimeout(() => { + router.push("/login"); + }, 1000); + } else { + setError("An error occurred while creating the company: " + res.data?.createCompany); + } + } catch (err) { + console.error("Mutation error:", err); + setError("An error occurred while creating the company"); } }; + useEffect(() => { - const onKeyDown = (e: KeyboardEvent) => { + const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Enter") { - if (step === 3) - submit().catch((err) => { - console.error(err); - }); - else nextStep(); + if (step === 3) { + void submit(); + } else { + nextStep(); + } } }; - window.addEventListener("keydown", onKeyDown); + window.addEventListener("keydown", handleKeyDown); return () => { - window.removeEventListener("keydown", onKeyDown); + window.removeEventListener("keydown", handleKeyDown); }; - }, [step]); + }, [step, inputs]); + + if (loading || step === 4) return ; + return ( -
-
- {""} -

MeetMate

+
+
+ +

MeetMate

-
- {step === 4 ? ( - - ) : ( - - { - setInputs({ ...inputs, [placeholders[step]]: e.target.value }); - }} - placeholder={placeholders[step]} - className={"border-0 text-lg font-medium text-foreground focus-visible:ring-0"} - /> - - -
-

{error}

- {step > 0 && ( - - )} - {step < 3 && ( - - )} - {step === 3 && ( - - )} -
-
- )} +
+ { + setInputs((prev) => ({ ...prev, [placeholders[step]]: e.target.value })); + }} + placeholder={placeholders[step]} + className="border-0 text-lg font-medium text-foreground focus-visible:ring-0" + /> + + +
+

{error !== "" || (mutationError !== null && mutationError?.message)}

+ {step > 0 && ( + + )} + {step < 3 && ( + + )} + {step === 3 && ( + + )} +
); diff --git a/src/app/dashboard/bookings/page.tsx b/src/app/dashboard/bookings/page.tsx index 67c5bac..bca9d4c 100644 --- a/src/app/dashboard/bookings/page.tsx +++ b/src/app/dashboard/bookings/page.tsx @@ -15,7 +15,7 @@ import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { extractNameInitials } from "@/lib/utils"; import { useRouter } from "next/navigation"; import Scheduler from "@/components/dashboard/scheduler/Scheduler"; -import { FaPlus, FaRegCircleQuestion } from "react-icons/fa6"; +import { FaPlus, FaRegCircleQuestion, FaArrowLeft } from "react-icons/fa6"; import { AlertDialog, AlertDialogCancel, @@ -40,130 +40,103 @@ import { Progress } from "@/components/ui/progress"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Calendar } from "@/components/ui/calendar"; import { useDashboardData } from "@/components/dashboard/DashboardContext"; +import { type Appointment, type Company } from "@/types"; function Bookings() { - const user = useDashboardData().user; + const { user } = useDashboardData(); const [searchQuery, setSearchQuery] = useState(""); - const [events, setEvents] = useState< - Array<{ Id: number; Subject: string; Location: string; StartTime: Date; EndTime: Date; RecurrenceRule: string }> - >([ + const [appointments, setAppointments] = useState([ { - Id: 1, - Subject: "Scrum Meeting", - Location: "Office", - StartTime: new Date(2024, 4, 6, 14, 30), - EndTime: new Date(2024, 4, 6, 15, 30), - RecurrenceRule: "" + id: 1, + from: new Date(2024, 8, 3, 18, 30), + to: new Date(2024, 8, 3, 19, 30), + title: "Scrum Meeting", + description: "Weekly team sync", + companyId: "1", + location: "Office", + client: null, + status: "PENDING" }, { - Id: 2, - Subject: "Scrum Meeting", - Location: "Office", - StartTime: new Date(2024, 4, 8, 17, 30), - EndTime: new Date(2024, 4, 8, 18, 30), - RecurrenceRule: "" + id: 2, + from: new Date(2024, 8, 5, 17, 30), + to: new Date(2024, 8, 5, 18, 30), + title: "Client Presentation", + description: "Presenting project progress", + companyId: "2", + location: "Conference Room", + client: null, + status: "BOOKED" + }, + { + id: 3, + from: new Date(2024, 8, 6, 21, 30), + to: new Date(2024, 8, 6, 22, 30), + title: "Client Presentation", + description: "Presenting project progress", + companyId: "2", + location: "Conference Room", + client: null, + status: "BOOKED" } ]); - const [filteredEvents, setFilteredEvents] = useState< - Array<{ Id: number; Subject: string; Location: string; StartTime: Date; EndTime: Date; RecurrenceRule: string }> - >([]); - - const router = useRouter(); - - const [select, setSelect] = useState(""); - const [date, setDate] = useState(new Date()); - - const [bookingMode, setBookingMode] = useState(0); - const [bookingContent, setBookingContent] = useState({ - title: "Select your company", - description: "With which company do you want to book this appointment?", - select: { - active: true, - content: ["Github", "Böckmann GmbH", "MeetMate"] - }, - calendar: { - active: false - }, - progress: 33 - }); + const [companies, setCompanies] = useState([ + { + id: "1", + name: "Github", + createdAt: "2024-01-01", + description: "Version control platform", + owner: user!, + members: [], + settings: { + appointmentDuration: 60, + appointmentBuffer: 15, + appointmentTypes: ["Code Review", "Project Planning"], + appointmentLocations: ["Online", "Office"], + openingHours: { + from: "09:00", + to: "17:00" + } + } + } + // Add more companies as needed + ]); useEffect(() => { - if (false) setEvents([]); // This is a dummy condition to avoid unused variable warning (events) + if (false) setAppointments([]); + if (false) setCompanies([]); }, []); - const [bookingResult, setBookingResult] = useState<{ company: string; date: Date | undefined; time: string }>({ - company: "", - date: new Date(), - time: "" + const [filteredAppointments, setFilteredAppointments] = useState(appointments); + const router = useRouter(); + + const [bookingState, setBookingState] = useState({ + step: 0, + selectedCompany: "", + selectedDate: new Date(), + selectedTime: "", + isLoading: false, + error: null as string | null }); useEffect(() => { - setFilteredEvents(events.filter((event) => event.Subject.toLowerCase().includes(searchQuery.toLowerCase()))); - }, [searchQuery, events]); + const filtered = + searchQuery === "" + ? appointments + : appointments.filter((appointment) => appointment.title.toLowerCase().includes(searchQuery.toLowerCase())); + setFilteredAppointments(filtered); + }, [searchQuery, appointments]); - function updateBookingProgress() { - if (bookingMode === 1 && select !== "") { - setBookingResult({ ...bookingResult, company: select }); - setSelect(""); - setBookingContent({ - title: "Select your Date", - description: "When would you like to book this appointment?", - select: { - active: false, - content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"] - }, - calendar: { - active: true - }, - progress: 66 - }); - setBookingMode(bookingMode + 1); - } else if (bookingMode === 2) { - setBookingResult({ ...bookingResult, date }); - setBookingContent({ - title: "Select your Slot", - description: "At what time would you like to book this appointment?", - select: { - active: true, - content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"] - }, - calendar: { - active: false - }, - progress: 80 - }); - setBookingMode(bookingMode + 1); - } else if (bookingMode === 3 && select !== "") { - setBookingResult({ ...bookingResult, time: select }); - setBookingContent({ - title: "Success", - description: "Your appointment was successfully booked", - select: { - active: false, - content: ["13:00 - 14:00", "15:00 - 16:00", "19:00 - 20:30"] - }, - calendar: { - active: false - }, - progress: 100 - }); - console.log(bookingResult); - } else if (bookingMode === 0) { - setBookingContent({ - title: "Select your company", - description: "With which company do you want to book this appointment?", - select: { - active: true, - content: ["Github", "Böckmann GmbH", "MeetMate"] - }, - calendar: { - active: false - }, - progress: 33 - }); - setBookingMode(1); - } - } + const handleAppointmentCancel = (appointment: Appointment): "success" | "error" => { + console.log("Appointment cancelled", appointment); + // Implement cancellation logic here + return "success"; + }; + + const handleAppointmentChange = (appointment: Appointment) => { + console.log("Appointment change requested", appointment); + // Implement change logic here + }; const logout = async () => { try { @@ -175,6 +148,200 @@ function Bookings() { } }; + const updateBookingStep = (step: number) => { + setBookingState((prev) => ({ + ...prev, + step, + error: null // Clear any previous errors when moving to a new step + })); + }; + + const handleSelectChange = (value: string, field: "selectedCompany" | "selectedTime") => { + setBookingState((prev) => ({ + ...prev, + [field]: value + })); + }; + + const handleDateChange = (date: Date | undefined) => { + // eslint-disable-next-line eqeqeq + if (date != null) { + setBookingState((prev) => ({ + ...prev, + selectedDate: date + })); + } + }; + + const calculateSchedulerHours = (calcAppointments: Appointment[]): { open: string; close: string } => { + const DEFAULT_INTERVAL = 6; // 7 hours + const DEFAULT_START = 9; // Default start at 9 AM + const DEFAULT_END = 15; // Default end at 3 PM (6 hours later) + + if (calcAppointments.length === 0) { + return { + open: `${DEFAULT_START.toString().padStart(2, "0")}:00`, + close: `${DEFAULT_END.toString().padStart(2, "0")}:00` + }; + } + + // Find the earliest start time and latest end time + const earliestTime = Math.min(...calcAppointments.map((a) => a.from.getHours())); + const latestTime = Math.max(...calcAppointments.map((a) => a.to.getHours())); + + let startHour: number; + let endHour: number; + + if (latestTime - earliestTime >= DEFAULT_INTERVAL) { + // Rare case: appointments span more than 6 hours + startHour = earliestTime; + endHour = latestTime + 1; // Add 1 hour for padding + } else { + // Normal case: shift the 6-hour window to include all appointments + endHour = Math.min(24, Math.max(latestTime + 1, earliestTime + DEFAULT_INTERVAL)); + startHour = Math.max(0, endHour - DEFAULT_INTERVAL); + } + + // Ensure we always have a 6-hour window minimum + if (endHour - startHour < DEFAULT_INTERVAL) { + endHour = Math.min(24, startHour + DEFAULT_INTERVAL); + } + + return { + open: `${startHour.toString().padStart(2, "0")}:00`, + close: `${endHour.toString().padStart(2, "0")}:00` + }; + }; + + // Calculate scheduler hours whenever appointments change + const schedulerHours = calculateSchedulerHours(appointments); + + const handleBookAppointment = async () => { + setBookingState((prev) => ({ ...prev, isLoading: true, error: null })); + + // Log the appointment data + console.log("Booking appointment with the following details:", { + company: bookingState.selectedCompany, + date: bookingState.selectedDate, + time: bookingState.selectedTime + }); + + try { + // Simulated API call + // const response = await fetch('/api/book-appointment', { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // }, + // body: JSON.stringify({ + // companyId: bookingState.selectedCompany, + // date: bookingState.selectedDate, + // time: bookingState.selectedTime, + // }), + // }); + + // if (!response.ok) { + // throw new Error('Failed to book appointment'); + // } + + // Simulate API delay + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // Simulate success or failure randomly + if (Math.random() > 0.2) { + // 80% success rate + updateBookingStep(4); // Success step + } else { + throw new Error("Failed to book appointment"); + } + } catch (error) { + setBookingState((prev) => ({ + ...prev, + error: error instanceof Error ? error.message : "An unknown error occurred" + })); + } finally { + setBookingState((prev) => ({ ...prev, isLoading: false })); + } + }; + + const renderBookingStep = () => { + switch (bookingState.step) { + case 0: + return ( + + Select your company + With which company do you want to book this appointment? + + + ); + case 1: + return ( + + Select your Date + When would you like to book this appointment? + + + ); + case 2: + return ( + + Select your Slot + At what time would you like to book this appointment? + + + ); + case 3: + return ( + + Confirm Booking + Please confirm your appointment details: +
+

Company: {companies.find((c) => c.id === bookingState.selectedCompany)?.name}

+

Date: {bookingState.selectedDate.toDateString()}

+

Time: {bookingState.selectedTime}

+
+
+ ); + case 4: + return ( + + Success + Your appointment was successfully booked + + ); + default: + return null; + } + }; + return (
@@ -186,7 +353,8 @@ function Bookings() { value={searchQuery} onChange={(e) => { setSearchQuery(e.target.value); - }}> + }} + /> - - ) : ( - +
+ {bookingState.step > 0 && bookingState.step < 4 && ( + + )} +
+ {bookingState.step === 4 ? ( - - - )} + )} +
- +
@@ -306,7 +475,12 @@ function Bookings() {
- +
); diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 464856c..9cd5514 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -12,11 +12,13 @@ import { DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; -import { type User } from "@/types"; +import { type User, type Appointment } from "@/types"; import { extractNameInitials } from "@/lib/utils"; import Image from "next/image"; import { useRouter } from "next/navigation"; import Link from "next/link"; +import OverviewScheduler from "@/components/dashboard/scheduler/OverviewScheduler"; +import AppointmentDisplay from "@/components/dashboard/AppointmentDisplay"; function Dashboard() { const [user, setUser] = useState(); @@ -24,6 +26,46 @@ function Dashboard() { const router = useRouter(); + const [appointments, setAppointments] = useState([ + { + id: 1, + from: new Date(2024, 8, 23, 18, 30), + to: new Date(2024, 8, 23, 19, 30), + title: "Scrum Meeting", + description: "Weekly team sync", + companyId: "1", + location: "Office", + client: null, + status: "PENDING" + }, + { + id: 2, + from: new Date(2024, 8, 25, 17, 30), + to: new Date(2024, 8, 25, 18, 30), + title: "Client Presentation", + description: "Presenting project progress", + companyId: "2", + location: "Conference Room", + client: null, + status: "BOOKED" + }, + { + id: 3, + from: new Date(2024, 8, 25, 21, 30), + to: new Date(2024, 8, 25, 22, 30), + title: "Client Presentation", + description: "Presenting project progress", + companyId: "2", + location: "Conference Room", + client: null, + status: "BOOKED" + } + ]); + + useEffect(() => { + if (false) setAppointments([]); + }, []); + useEffect(() => { const fetchUser = async () => { setLoading(true); @@ -39,6 +81,15 @@ function Dashboard() { fetchUser().catch(console.error); }, []); + const [selectedAppointmentId, setSelectedAppointmentId] = useState(null); + + const handleAppointmentClick = (appointment: Appointment) => { + console.log("Appointment clicked", appointment); + // highlight selected appointment + setSelectedAppointmentId(appointment.id === selectedAppointmentId ? null : appointment.id); + console.log(selectedAppointmentId); + }; + const logout = async () => { try { await deleteToken(); @@ -103,9 +154,25 @@ function Dashboard() { -
- -
+
+
+

Upcoming Appointments

+
+ {appointments.map((appointment) => ( + + ))} +
+
+
+

Timeline

+ +
+
); } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 9a3dfeb..7ca25bd 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -153,7 +153,7 @@ const LoginForm = () => { - + Forgot password? diff --git a/src/app/signup/page.tsx b/src/app/signup/page.tsx index 601c4a4..f83cce1 100644 --- a/src/app/signup/page.tsx +++ b/src/app/signup/page.tsx @@ -48,6 +48,9 @@ const SignupForm = () => { variant: "default", className: "border-emerald-300" }); + setTimeout(() => { + router.push("/login"); + }, 2500); } }, [formState, toast]); diff --git a/src/assets/globals.scss b/src/assets/globals.scss index fc010fc..4742169 100644 --- a/src/assets/globals.scss +++ b/src/assets/globals.scss @@ -523,4 +523,3 @@ canvas { .z-50.overflow-hidden.border.bg-background.px-3.py-1\.5.text-sm.text-foreground.shadow-md.animate-in.fade-in-0.zoom-in-95.data-\[state\=closed\]\:animate-out.data-\[state\=closed\]\:fade-out-0.data-\[state\=closed\]\:zoom-out-95.data-\[side\=bottom\]\:slide-in-from-top-2.data-\[side\=left\]\:slide-in-from-right-2.data-\[side\=right\]\:slide-in-from-left-2.data-\[side\=top\]\:slide-in-from-bottom-2.rounded-full.border-border { background: $background !important; } - diff --git a/src/components/dashboard/AppointmentDisplay.tsx b/src/components/dashboard/AppointmentDisplay.tsx new file mode 100644 index 0000000..8777f62 --- /dev/null +++ b/src/components/dashboard/AppointmentDisplay.tsx @@ -0,0 +1,31 @@ +import { type Appointment } from "@/types"; + +export default function AppointmentDisplay(props: { + data: Appointment; + selected: boolean; + onClick: (appointment: Appointment) => void; +}) { + return ( +
{ + props.onClick(props.data); + }} + className={`flex h-[100px] w-[350px] cursor-pointer flex-row items-center justify-start gap-x-4 rounded-2xl text-white ${props.selected && "bg-primary"}`}> +
+

+ {props.data.from.toLocaleString("en-US", { weekday: "short" })} +

+

{props.data.from.getDate()}

+
+
+

{props.data.title}

+

{"GitHub Company"}

{/* Get Company by ID here */} +
+ {props.data.from.getHours()}:{props.data.from.getMinutes()} +
+
+
+ ); +} diff --git a/src/components/dashboard/scheduler/OverviewScheduler.tsx b/src/components/dashboard/scheduler/OverviewScheduler.tsx new file mode 100644 index 0000000..9c3814a --- /dev/null +++ b/src/components/dashboard/scheduler/OverviewScheduler.tsx @@ -0,0 +1,128 @@ +import React, { useMemo } from "react"; +import { ScheduleComponent, ViewsDirective, ViewDirective, Inject, WorkWeek } from "@syncfusion/ej2-react-schedule"; +import "./scheduler.scss"; +import "./overviewscheduler.scss"; +import { registerLicense } from "@syncfusion/ej2-base"; +import { type Appointment } from "@/types"; + +type SchedulerProps = { + data: Appointment[]; + selectedAppointmentId: number | null; +}; + +function OverviewScheduler(props: SchedulerProps) { + const fieldsData = { + id: "id", + subject: { name: "title" }, + startTime: { name: "from" }, + endTime: { name: "to" }, + location: { name: "location" }, + description: { name: "description" }, + status: { name: "status" } + }; + const eventSettings = { dataSource: props.data, fields: fieldsData }; + + const predefinedColors = ["#6EE7B7", "#F87171", "#FACC15"]; + + registerLicense(process.env.NEXT_PUBLIC_SYNCFUSION_LICENSE!); + + const onEventClick = (args: { cancel: boolean }) => { + console.log("Event clicked", args); + args.cancel = true; + }; + + const calculateSchedulerHours = (calcAppointments: Appointment[]): { open: string; close: string } => { + const DEFAULT_INTERVAL = 6; // 7 hours + const DEFAULT_START = 9; // Default start at 9 AM + const DEFAULT_END = 15; // Default end at 3 PM (6 hours later) + + if (calcAppointments.length === 0) { + return { + open: `${DEFAULT_START.toString().padStart(2, "0")}:00`, + close: `${DEFAULT_END.toString().padStart(2, "0")}:00` + }; + } + + // Find the earliest start time and latest end time + const earliestTime = Math.min(...calcAppointments.map((a) => a.from.getHours())); + const latestTime = Math.max(...calcAppointments.map((a) => a.to.getHours())); + + let startHour: number; + let endHour: number; + + if (latestTime - earliestTime >= DEFAULT_INTERVAL) { + // Rare case: appointments span more than 6 hours + startHour = earliestTime; + endHour = latestTime + 1; // Add 1 hour for padding + } else { + // Normal case: shift the 6-hour window to include all appointments + endHour = Math.min(24, Math.max(latestTime + 1, earliestTime + DEFAULT_INTERVAL)); + startHour = Math.max(0, endHour - DEFAULT_INTERVAL); + } + + // Ensure we always have a 6-hour window minimum + if (endHour - startHour < DEFAULT_INTERVAL) { + endHour = Math.min(24, startHour + DEFAULT_INTERVAL); + } + + return { + open: `${startHour.toString().padStart(2, "0")}:00`, + close: `${endHour.toString().padStart(2, "0")}:00` + }; + }; + + const appointmentColors = useMemo(() => { + return props.data.reduce( + (acc, appointment) => { + acc[appointment.id] = predefinedColors[appointment.id % predefinedColors.length]; + return acc; + }, + {} as Record + ); + }, [props.data]); + + // Calculate scheduler hours whenever appointments change + const schedulerHours = calculateSchedulerHours(props.data); + + const onEventRendered = (args: { data: Record; element: HTMLDivElement }) => { + const appointmentData = args.data as Appointment; + const color = appointmentColors[appointmentData.id]; + args.element.style.background = color; + args.element.style.left = "50%"; + args.element.style.transform = "translateX(-50%)"; + }; + + return ( +
+ + + ) => { + return ( +
+ ); + }} + /> +
+ +
+
+ ); +} + +export default OverviewScheduler; diff --git a/src/components/dashboard/scheduler/Scheduler.tsx b/src/components/dashboard/scheduler/Scheduler.tsx index 50ee2f4..fe050fb 100644 --- a/src/components/dashboard/scheduler/Scheduler.tsx +++ b/src/components/dashboard/scheduler/Scheduler.tsx @@ -1,22 +1,36 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { ScheduleComponent, ViewsDirective, ViewDirective, Inject, WorkWeek } from "@syncfusion/ej2-react-schedule"; import "./scheduler.scss"; import { registerLicense } from "@syncfusion/ej2-base"; import { Button } from "@/components/ui/button"; import { FaArrowRight, FaArrowLeft } from "react-icons/fa6"; +import { Dialog, DialogContent, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; +import { type Appointment } from "@/types"; +import { useToast } from "@/components/ui/use-toast"; -function Scheduler(props: { +type SchedulerProps = { openingHours: { open: string; close: string }; - data: Array<{ - Id: number; - Subject: string; - Location: string; - StartTime: Date; - EndTime: Date; - RecurrenceRule: string; - }>; -}) { - const eventSettings = { dataSource: props.data }; + data: Appointment[]; + handleAppointmentChange: (appointment: Appointment) => void; + handleAppointmentCancel: (appointment: Appointment) => "success" | "error"; +}; + +function Scheduler(props: SchedulerProps) { + const { toast } = useToast(); + const fieldsData = { + id: "id", + subject: { name: "title" }, + startTime: { name: "from" }, + endTime: { name: "to" }, + location: { name: "location" }, + description: { name: "description" }, + status: { name: "status" } + }; + const eventSettings = { dataSource: props.data, fields: fieldsData }; + useEffect(() => { + // eslint-disable-next-line no-console + console.log("Scheduler props", props); + }, []); const [currentDate, setCurrentDate] = useState(new Date()); @@ -37,17 +51,44 @@ function Scheduler(props: { registerLicense(process.env.NEXT_PUBLIC_SYNCFUSION_LICENSE!); const onEventClick = (args: { cancel: boolean }) => { + console.log("Event clicked", args); args.cancel = true; }; - const getRandomColor = () => { - const randomIndex = Math.floor(Math.random() * predefinedColors.length); - return predefinedColors[randomIndex]; + const onEventRendered = (args: { element: HTMLDivElement }) => { + args.element.style.borderColor = "transparent"; + args.element.style.width = "100%"; + }; + + const handleAppointmentCancel = (event: Appointment): void => { + const now = new Date(); + const timeDifference = (event.from.getTime() - now.getTime()) / (1000 * 60 * 60); // Difference in hours + + if (timeDifference < 24) { + toast({ + title: "Cancellation Failed", + description: "You cannot cancel an appointment less than 24 hours before the appointment time.", + variant: "default", + className: "border-red-700" + }); + } else { + const result = props.handleAppointmentCancel(event); + if (result === "success") { + toast({ + title: "Appointment Cancelled", + description: "The appointment has been successfully cancelled.", + variant: "default", + className: "border-emerald-300" + }); + } else { + alert("Failed to cancel the appointment. Please try again."); + } + } }; return ( -
+
@@ -64,6 +105,7 @@ function Scheduler(props: { height={"500px"} eventSettings={eventSettings} showHeaderBar={false} + eventRendered={onEventRendered} readonly={true} eventClick={onEventClick}> @@ -72,25 +114,84 @@ function Scheduler(props: { startHour={props.openingHours.open} endHour={props.openingHours.close} timeScale={{ interval: 60, slotCount: 2 }} - eventTemplate={(eventProps: { Subject: string; StartTime: Date; EndTime: Date }) => ( -
-

{eventProps.Subject}

-
- {new Intl.DateTimeFormat("en-US", { - hour: "numeric", - minute: "numeric" - }).format(eventProps.StartTime)}{" "} - {" "} - {new Intl.DateTimeFormat("en-US", { - hour: "numeric", - minute: "numeric" - }).format(eventProps.EndTime)} -
-
+ eventTemplate={(eventProps: Appointment) => ( + + +
+

{eventProps.title}

+
+ {new Intl.DateTimeFormat("en-US", { + hour: "numeric", + minute: "numeric" + }).format(eventProps.from)}{" "} + {" "} + {new Intl.DateTimeFormat("en-US", { + hour: "numeric", + minute: "numeric" + }).format(eventProps.to)} +
+
+
+ +
+
+ Appointment Information +

+ Do you need to request a change to this appointment or cancel it? +

+
+
+

{eventProps.title}

+

+ Date:{" "} + {new Intl.DateTimeFormat("en-US", { + year: "numeric", + month: "long", + day: "numeric" + }).format(eventProps.from)} +

+

+ From:{" "} + {new Intl.DateTimeFormat("en-US", { + hour: "numeric", + minute: "numeric" + }).format(eventProps.from)} +

+

+ To:{" "} + {new Intl.DateTimeFormat("en-US", { + hour: "numeric", + minute: "numeric" + }).format(eventProps.to)} +

+

Description: {eventProps.description}

+

Location: {eventProps.location}

+

Status: {eventProps.status}

+

Company ID: {eventProps.companyId}

+ {eventProps.client !== null &&

Client: {eventProps.client.name}

} +
+ + +
+
+
)} />
diff --git a/src/components/dashboard/scheduler/overviewscheduler.scss b/src/components/dashboard/scheduler/overviewscheduler.scss new file mode 100644 index 0000000..dbb7c3c --- /dev/null +++ b/src/components/dashboard/scheduler/overviewscheduler.scss @@ -0,0 +1,24 @@ +.overviewScheduler .e-schedule .e-vertical-view .e-day-wrapper .e-appointment { + width: 55% !important; + border: none !important; +} +.overviewScheduler .e-left-indent { + display: none !important; +} + +.overviewScheduler .e-table-container > div > table > tbody > tr:nth-child(2) > td:nth-child(1) { + display: none !important; +} + +.overviewScheduler .e-appointment-details, +.overviewScheduler .e-appointment-details div { + width: 100% !important; + height: 100% !important; + padding: 0 !important; +} + +.overviewScheduler .e-schedule .e-vertical-view .e-day-wrapper .e-appointment .e-appointment-details { + width: 100% !important; + height: 100% !important; + padding: 0 !important; +} diff --git a/src/components/dashboard/scheduler/scheduler.scss b/src/components/dashboard/scheduler/scheduler.scss index 7e7278c..e6bdc21 100644 --- a/src/components/dashboard/scheduler/scheduler.scss +++ b/src/components/dashboard/scheduler/scheduler.scss @@ -40,7 +40,8 @@ text-indent: 16px; } -.e-bigger .e-popup.e-ddl .e-list-group-item, .e-bigger .e-popup.e-ddl .e-fixed-head { +.e-bigger .e-popup.e-ddl .e-list-group-item, +.e-bigger .e-popup.e-ddl .e-fixed-head { font-size: 14px; line-height: 32px; padding-left: 0; @@ -51,7 +52,8 @@ padding-left: 0; } -.e-bigger .e-popup.e-ddl .e-input-group input, .e-bigger .e-popup.e-ddl .e-input-group input.e-input { +.e-bigger .e-popup.e-ddl .e-input-group input, +.e-bigger .e-popup.e-ddl .e-input-group input.e-input { font-size: 16px; height: 36px; } @@ -93,7 +95,9 @@ .e-input-group.e-ddl.e-readonly .e-input[readonly] ~ span.e-input-group-icon.e-ddl-icon.e-search-icon, .e-input-group.e-control-wrapper.e-readonly .e-input[readonly] ~ span.e-input-group-icon.e-ddl-icon.e-search-icon, .e-float-input.e-ddl.e-readonly .e-input[readonly] ~ span.e-input-group-icon.e-ddl-icon.e-search-icon, -.e-float-input.e-control-wrapper.e-ddl.e-readonly .e-input[readonly] ~ span.e-input-group-icon.e-ddl-icon.e-search-icon { +.e-float-input.e-control-wrapper.e-ddl.e-readonly + .e-input[readonly] + ~ span.e-input-group-icon.e-ddl-icon.e-search-icon { background: #343a40; } @@ -131,7 +135,9 @@ font-size: 16px; } -.e-bigger.e-small .e-ddl.e-popup .e-list-item, .e-bigger.e-small .e-ddl.e-popup .e-list-group-item, .e-bigger.e-small .e-ddl.e-popup .e-fixed-head { +.e-bigger.e-small .e-ddl.e-popup .e-list-item, +.e-bigger.e-small .e-ddl.e-popup .e-list-group-item, +.e-bigger.e-small .e-ddl.e-popup .e-fixed-head { font-size: 14px; line-height: 34px; padding-left: 0; @@ -142,7 +148,8 @@ padding-left: 4px; } -.e-bigger.e-small .e-ddl.e-popup .e-input-group input, .e-bigger.e-small .e-ddl.e-popup .e-input-group input.e-input { +.e-bigger.e-small .e-ddl.e-popup .e-input-group input, +.e-bigger.e-small .e-ddl.e-popup .e-input-group input.e-input { height: 30px; } @@ -176,8 +183,8 @@ width: 24px; } -.e-multi-select-wrapper .e-searcher input[type=text], -.e-multi-select-wrapper .e-multi-searcher input[type=text] { +.e-multi-select-wrapper .e-searcher input[type="text"], +.e-multi-select-wrapper .e-multi-searcher input[type="text"] { color: #fff; height: 100%; } @@ -389,20 +396,41 @@ .e-schedule .e-schedule-toolbar .e-toolbar-items.e-tbar-pos > div { height: inherit; } -.e-schedule .e-schedule-toolbar .e-toolbar-items .e-tbar-btn.e-btn.e-tbtn-txt .e-icons.e-icon-right.e-btn-icon.e-icon-down-arrow { +.e-schedule + .e-schedule-toolbar + .e-toolbar-items + .e-tbar-btn.e-btn.e-tbtn-txt + .e-icons.e-icon-right.e-btn-icon.e-icon-down-arrow { font-size: 18px; margin-top: 2px; } -.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-left .e-tbar-btn.e-icon-btn:focus, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-left .e-tbar-btn.e-icon-btn:hover { +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-left .e-tbar-btn.e-icon-btn:focus, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-left .e-tbar-btn.e-icon-btn:hover { border-radius: 4px; } -.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-day .e-icon-day, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-week .e-icon-week, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-work-week .e-icon-workweek, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-month .e-icon-month, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-year .e-icon-year, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-agenda .e-icon-agenda, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-month-agenda .e-icon-month-agenda, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-day .e-icon-timeline-day, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-week .e-icon-timeline-week, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-work-week .e-icon-timeline-workweek, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-month .e-icon-timeline-month, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-year .e-icon-timeline-year-vertical, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-year .e-icon-timeline-year-horizontal, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-add .e-tbar-btn-text, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-today .e-icon-day, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-today .e-icon-today { +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-day .e-icon-day, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-week .e-icon-week, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-work-week .e-icon-workweek, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-month .e-icon-month, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-year .e-icon-year, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-agenda .e-icon-agenda, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-month-agenda .e-icon-month-agenda, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-day .e-icon-timeline-day, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-week .e-icon-timeline-week, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-work-week .e-icon-timeline-workweek, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-month .e-icon-timeline-month, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-year .e-icon-timeline-year-vertical, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-timeline-year .e-icon-timeline-year-horizontal, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-add .e-tbar-btn-text, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-today .e-icon-day, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-today .e-icon-today { display: none; } .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-hidden { display: none; } -.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-prev .e-icon-prev, .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-next .e-icon-next { +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-prev .e-icon-prev, +.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-next .e-icon-next { font-size: 18px; } .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item.e-separator { @@ -450,7 +478,8 @@ height: 54px; min-height: 54px; } -.e-schedule.e-device .e-schedule-toolbar .e-toolbar-items .e-toolbar-left .e-tbar-btn.e-icon-btn:hover, .e-schedule.e-device .e-schedule-toolbar .e-toolbar-items .e-toolbar-left .e-tbar-btn.e-icon-btn:focus { +.e-schedule.e-device .e-schedule-toolbar .e-toolbar-items .e-toolbar-left .e-tbar-btn.e-icon-btn:hover, +.e-schedule.e-device .e-schedule-toolbar .e-toolbar-items .e-toolbar-left .e-tbar-btn.e-icon-btn:focus { height: calc(100% - 20px); } .e-schedule.e-device .e-schedule-toolbar .e-toolbar-items { @@ -467,7 +496,12 @@ .e-schedule.e-device .e-schedule-toolbar .e-toolbar-items.e-tbar-pos > div { height: inherit; } -.e-schedule.e-device .e-schedule-toolbar .e-toolbar-items .e-toolbar-item .e-tbar-btn.e-btn.e-tbtn-txt .e-icons.e-btn-icon { +.e-schedule.e-device + .e-schedule-toolbar + .e-toolbar-items + .e-toolbar-item + .e-tbar-btn.e-btn.e-tbtn-txt + .e-icons.e-btn-icon { padding: 4px 6px; } .e-schedule.e-device .e-schedule-toolbar .e-tbar-btn .e-tbar-btn-text { @@ -720,21 +754,26 @@ .e-schedule .e-read-only { opacity: 0.8; } -.e-schedule.e-event-action .e-appointment:not(.e-schedule-event-clone), .e-schedule.e-event-action .e-block-appointment { +.e-schedule.e-event-action .e-appointment:not(.e-schedule-event-clone), +.e-schedule.e-event-action .e-block-appointment { pointer-events: none; } -.e-schedule.e-event-action .e-drag-clone, .e-schedule.e-event-action .e-timeline-view .e-drag-clone { +.e-schedule.e-event-action .e-drag-clone, +.e-schedule.e-event-action .e-timeline-view .e-drag-clone { cursor: move; } .e-schedule.e-event-action .e-drag-clone .e-top-handler, -.e-schedule.e-event-action .e-drag-clone .e-bottom-handler, .e-schedule.e-event-action .e-timeline-view .e-drag-clone .e-top-handler, +.e-schedule.e-event-action .e-drag-clone .e-bottom-handler, +.e-schedule.e-event-action .e-timeline-view .e-drag-clone .e-top-handler, .e-schedule.e-event-action .e-timeline-view .e-drag-clone .e-bottom-handler { pointer-events: none; } .e-schedule.e-event-action .e-vertical-view .e-appointment-wrapper .e-resize-clone { cursor: ns-resize; } -.e-schedule.e-event-action .e-timeline-view .e-resize-clone, .e-schedule.e-event-action .e-timeline-month-view .e-resize-clone, .e-schedule.e-event-action .e-all-day-appointment-wrapper .e-resize-clone { +.e-schedule.e-event-action .e-timeline-view .e-resize-clone, +.e-schedule.e-event-action .e-timeline-month-view .e-resize-clone, +.e-schedule.e-event-action .e-all-day-appointment-wrapper .e-resize-clone { cursor: ew-resize; } .e-schedule.e-device .e-appointment { @@ -767,18 +806,21 @@ .e-schedule.e-device .e-appointment.e-appointment-border .e-right-handler .e-left-right-resize { margin-left: 8px; } -.e-schedule .e-timeline-year-view .e-event-resize.e-left-handler, .e-schedule .e-timeline-year-view .e-event-resize.e-right-handler { +.e-schedule .e-timeline-year-view .e-event-resize.e-left-handler, +.e-schedule .e-timeline-year-view .e-event-resize.e-right-handler { height: 100%; width: 5px; } -.e-schedule .e-timeline-year-view .e-event-resize.e-top-handler, .e-schedule .e-timeline-year-view .e-event-resize.e-bottom-handler { +.e-schedule .e-timeline-year-view .e-event-resize.e-top-handler, +.e-schedule .e-timeline-year-view .e-event-resize.e-bottom-handler { height: 5px; width: 100%; } .e-schedule .e-event-resize { position: absolute; } -.e-schedule .e-event-resize.e-left-handler, .e-schedule .e-event-resize.e-right-handler { +.e-schedule .e-event-resize.e-left-handler, +.e-schedule .e-event-resize.e-right-handler { height: 100%; min-width: 1px; max-width: 10px; @@ -794,7 +836,8 @@ float: right; right: 0; } -.e-schedule .e-event-resize.e-top-handler, .e-schedule .e-event-resize.e-bottom-handler { +.e-schedule .e-event-resize.e-top-handler, +.e-schedule .e-event-resize.e-bottom-handler { min-height: 1px; max-height: 10px; height: 20%; @@ -869,9 +912,13 @@ .e-schedule .e-vertical-view.e-timescale-disable .e-appointment .e-right-icon { line-height: 54px; } -.e-schedule .e-vertical-view.e-timescale-disable .e-appointment.e-appointment-border, .e-schedule .e-vertical-view.e-timescale-disable .e-appointment:focus { +.e-schedule .e-vertical-view.e-timescale-disable .e-appointment.e-appointment-border, +.e-schedule .e-vertical-view.e-timescale-disable .e-appointment:focus { border: 0; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); } .e-schedule .e-vertical-view.e-timescale-disable .e-appointment.e-allow-select { pointer-events: none; @@ -995,7 +1042,8 @@ .e-schedule .e-vertical-view .e-all-day-appointment-section.e-appointment-collapse { transform: rotate(180deg); } -.e-schedule .e-vertical-view .e-all-day-appointment-section:hover, .e-schedule .e-vertical-view .e-all-day-appointment-section:focus { +.e-schedule .e-vertical-view .e-all-day-appointment-section:hover, +.e-schedule .e-vertical-view .e-all-day-appointment-section:focus { background: #495057; border-radius: 100%; color: #adb5bd; @@ -1092,7 +1140,6 @@ cursor: default; } .e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment { - background: #0d6efd; border: 1px solid #444c54; border-radius: 2px; color: #fff; @@ -1125,7 +1172,12 @@ padding: 1px 4px 2px 0; } .e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment .e-appointment-details .e-recurrence-icon, -.e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment .e-appointment-details .e-recurrence-edit-icon, +.e-schedule + .e-vertical-view + .e-all-day-appointment-wrapper + .e-appointment + .e-appointment-details + .e-recurrence-edit-icon, .e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment .e-appointment-details .e-left-icon, .e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment .e-appointment-details .e-right-icon { line-height: 18px; @@ -1134,15 +1186,18 @@ .e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment .e-disable { display: none; } -.e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment.e-appointment-border, .e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment:focus { +.e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment.e-appointment-border, +.e-schedule .e-vertical-view .e-all-day-appointment-wrapper .e-appointment:focus { border: 0; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); } .e-schedule .e-vertical-view .e-day-wrapper .e-appointment:not(.e-schedule-event-clone) { cursor: default; } .e-schedule .e-vertical-view .e-day-wrapper .e-appointment { - background: #0d6efd; border: 1px solid #444c54; border-radius: 2px; color: #fff; @@ -1208,9 +1263,13 @@ bottom: 6px; position: absolute; } -.e-schedule .e-vertical-view .e-day-wrapper .e-appointment.e-appointment-border, .e-schedule .e-vertical-view .e-day-wrapper .e-appointment:focus { +.e-schedule .e-vertical-view .e-day-wrapper .e-appointment.e-appointment-border, +.e-schedule .e-vertical-view .e-day-wrapper .e-appointment:focus { border: 0; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); } .e-schedule .e-vertical-view.e-day-view .e-header-date { cursor: default; @@ -1431,9 +1490,13 @@ line-height: 26px; padding: 0 2px; } -.e-schedule .e-month-view .e-appointment.e-appointment-border, .e-schedule .e-month-view .e-appointment:focus { +.e-schedule .e-month-view .e-appointment.e-appointment-border, +.e-schedule .e-month-view .e-appointment:focus { border: 0; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); } .e-schedule .e-month-view .e-appointment.e-allow-select { pointer-events: none; @@ -1649,7 +1712,8 @@ padding: 0; width: 100px; } -.e-schedule .e-timeline-year-view.e-vertical .e-left-indent, .e-schedule .e-timeline-year-view.e-vertical .e-left-indent .e-header-cells { +.e-schedule .e-timeline-year-view.e-vertical .e-left-indent, +.e-schedule .e-timeline-year-view.e-vertical .e-left-indent .e-header-cells { width: 100px; } .e-schedule .e-timeline-year-view .e-month-header { @@ -1758,9 +1822,13 @@ line-height: 26px; padding: 0 2px; } -.e-schedule .e-timeline-year-view .e-event-table .e-appointment.e-appointment-border, .e-schedule .e-timeline-year-view .e-event-table .e-appointment:focus { +.e-schedule .e-timeline-year-view .e-event-table .e-appointment.e-appointment-border, +.e-schedule .e-timeline-year-view .e-event-table .e-appointment:focus { border: 0; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); } .e-schedule .e-timeline-year-view .e-event-table .e-appointment.e-allow-select { pointer-events: none; @@ -1898,7 +1966,9 @@ .e-schedule .e-timeline-month-view .e-auto-height { height: auto; } -.e-schedule .e-timeline-view.e-ignore-whitespace .e-work-cells, .e-schedule .e-timeline-view.e-ignore-whitespace .e-resource-cells, .e-schedule .e-timeline-view.e-ignore-whitespace .e-event-container, +.e-schedule .e-timeline-view.e-ignore-whitespace .e-work-cells, +.e-schedule .e-timeline-view.e-ignore-whitespace .e-resource-cells, +.e-schedule .e-timeline-view.e-ignore-whitespace .e-event-container, .e-schedule .e-timeline-month-view.e-ignore-whitespace .e-work-cells, .e-schedule .e-timeline-month-view.e-ignore-whitespace .e-resource-cells, .e-schedule .e-timeline-month-view.e-ignore-whitespace .e-event-container { @@ -2014,11 +2084,15 @@ .e-schedule .e-timeline-month-view .e-appointment .e-right-icon { padding-right: 5px; } -.e-schedule .e-timeline-view .e-appointment.e-appointment-border, .e-schedule .e-timeline-view .e-appointment:focus, +.e-schedule .e-timeline-view .e-appointment.e-appointment-border, +.e-schedule .e-timeline-view .e-appointment:focus, .e-schedule .e-timeline-month-view .e-appointment.e-appointment-border, .e-schedule .e-timeline-month-view .e-appointment:focus { border: 0; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); } .e-schedule .e-timeline-view .e-appointment.e-allow-select, .e-schedule .e-timeline-month-view .e-appointment.e-allow-select { @@ -2055,10 +2129,13 @@ .e-schedule .e-timeline-month-view .e-content-wrap table col { width: 70px; } -.e-schedule .e-virtual-scroll.e-vertical-view .e-content-table, .e-schedule .e-virtual-scroll.e-month-view .e-content-table { +.e-schedule .e-virtual-scroll.e-vertical-view .e-content-table, +.e-schedule .e-virtual-scroll.e-month-view .e-content-table { transform: translateX(0); } -.e-schedule .e-virtual-scroll.e-timeline-view .e-content-table, .e-schedule .e-virtual-scroll.e-timeline-month-view .e-content-table, .e-schedule .e-virtual-scroll.e-timeline-year-view.e-vertical .e-content-table { +.e-schedule .e-virtual-scroll.e-timeline-view .e-content-table, +.e-schedule .e-virtual-scroll.e-timeline-month-view .e-content-table, +.e-schedule .e-virtual-scroll.e-timeline-year-view.e-vertical .e-content-table { transform: translateY(0); } .e-schedule .e-virtual-scroll .e-content-table { @@ -2221,7 +2298,8 @@ .e-schedule .e-month-agenda-view .e-appointment:hover { background: #23282c; } -.e-schedule .e-month-agenda-view .e-appointment.e-appointment-border, .e-schedule .e-month-agenda-view .e-appointment:focus { +.e-schedule .e-month-agenda-view .e-appointment.e-appointment-border, +.e-schedule .e-month-agenda-view .e-appointment:focus { background: #343a40; } .e-schedule .e-month-agenda-view .e-appointment.e-template { @@ -2443,7 +2521,8 @@ .e-schedule .e-agenda-view .e-appointment:hover { background: #23282c; } -.e-schedule .e-agenda-view .e-appointment.e-appointment-border, .e-schedule .e-agenda-view .e-appointment:focus { +.e-schedule .e-agenda-view .e-appointment.e-appointment-border, +.e-schedule .e-agenda-view .e-appointment:focus { background: #343a40; } .e-schedule .e-agenda-view .e-appointment.e-template { @@ -2567,10 +2646,27 @@ min-width: 24px; padding: 0 1.5px; } -.e-bigger .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item button.e-btn.e-tbtn-txt .e-icons.e-icon-right, -.e-bigger .e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item .e-tbar-btn.e-btn.e-control.e-tbtn-txt .e-icons.e-icon-right, +.e-bigger + .e-schedule + .e-schedule-toolbar + .e-toolbar-items + .e-toolbar-item + button.e-btn.e-tbtn-txt + .e-icons.e-icon-right, +.e-bigger + .e-schedule + .e-schedule-toolbar + .e-toolbar-items + .e-toolbar-item + .e-tbar-btn.e-btn.e-control.e-tbtn-txt + .e-icons.e-icon-right, .e-bigger.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item button.e-btn.e-tbtn-txt .e-icons.e-icon-right, -.e-bigger.e-schedule .e-schedule-toolbar .e-toolbar-items .e-toolbar-item .e-tbar-btn.e-btn.e-control.e-tbtn-txt .e-icons.e-icon-right { +.e-bigger.e-schedule + .e-schedule-toolbar + .e-toolbar-items + .e-toolbar-item + .e-tbar-btn.e-btn.e-control.e-tbtn-txt + .e-icons.e-icon-right { padding: 2px 0 0 12px; font-size: 24px; } @@ -2649,28 +2745,35 @@ min-width: 306px; } -.e-schedule.e-multi-drag .e-vertical-view .e-all-day-appointment-wrapper .e-appointment.e-appointment-border, .e-schedule.e-multi-drag .e-vertical-view .e-all-day-appointment-wrapper .e-appointment:focus { +.e-schedule.e-multi-drag .e-vertical-view .e-all-day-appointment-wrapper .e-appointment.e-appointment-border, +.e-schedule.e-multi-drag .e-vertical-view .e-all-day-appointment-wrapper .e-appointment:focus { border: 1px solid rgba(68, 76, 84, 0.5); } -.e-schedule.e-multi-drag .e-vertical-view.e-timescale-disable .e-appointment.e-appointment-border, .e-schedule.e-multi-drag .e-vertical-view.e-timescale-disable .e-appointment:focus { +.e-schedule.e-multi-drag .e-vertical-view.e-timescale-disable .e-appointment.e-appointment-border, +.e-schedule.e-multi-drag .e-vertical-view.e-timescale-disable .e-appointment:focus { border: 1px solid rgba(68, 76, 84, 0.5); } -.e-schedule.e-multi-drag .e-vertical-view .e-day-wrapper .e-appointment.e-appointment-border, .e-schedule.e-multi-drag .e-vertical-view .e-day-wrapper .e-appointment:focus { +.e-schedule.e-multi-drag .e-vertical-view .e-day-wrapper .e-appointment.e-appointment-border, +.e-schedule.e-multi-drag .e-vertical-view .e-day-wrapper .e-appointment:focus { border: 1px solid rgba(68, 76, 84, 0.5); } -.e-schedule.e-multi-drag .e-month-view .e-appointment.e-appointment-border, .e-schedule.e-multi-drag .e-month-view .e-appointment:focus { +.e-schedule.e-multi-drag .e-month-view .e-appointment.e-appointment-border, +.e-schedule.e-multi-drag .e-month-view .e-appointment:focus { border: 1px solid rgba(68, 76, 84, 0.5); } -.e-schedule.e-multi-drag .e-timeline-year-view .e-event-table .e-appointment.e-appointment-border, .e-schedule.e-multi-drag .e-timeline-year-view .e-event-table .e-appointment:focus { +.e-schedule.e-multi-drag .e-timeline-year-view .e-event-table .e-appointment.e-appointment-border, +.e-schedule.e-multi-drag .e-timeline-year-view .e-event-table .e-appointment:focus { border: 1px solid rgba(68, 76, 84, 0.5); } -.e-schedule.e-multi-drag .e-timeline-view .e-appointment.e-appointment-border, .e-schedule.e-multi-drag .e-timeline-view .e-appointment:focus, +.e-schedule.e-multi-drag .e-timeline-view .e-appointment.e-appointment-border, +.e-schedule.e-multi-drag .e-timeline-view .e-appointment:focus, .e-schedule.e-multi-drag .e-timeline-month-view .e-appointment.e-appointment-border, .e-schedule.e-multi-drag .e-timeline-month-view .e-appointment:focus { border: 1px solid rgba(68, 76, 84, 0.5); } -.e-more-popup-wrapper.e-multi-drag .e-appointment.e-appointment-border, .e-more-popup-wrapper.e-multi-drag .e-appointment:focus { +.e-more-popup-wrapper.e-multi-drag .e-appointment.e-appointment-border, +.e-more-popup-wrapper.e-multi-drag .e-appointment:focus { border: 1px solid rgba(68, 76, 84, 0.5); } @@ -3158,7 +3261,10 @@ .e-quick-popup-wrapper { background: #212529; border-radius: 6px; - box-shadow: 0 16px 48px rgba(0, 0, 0, 0.175), 0 16px 48px rgba(0, 0, 0, 0.175), 0 16px 48px rgba(0, 0, 0, 0.175); + box-shadow: + 0 16px 48px rgba(0, 0, 0, 0.175), + 0 16px 48px rgba(0, 0, 0, 0.175), + 0 16px 48px rgba(0, 0, 0, 0.175); color: #fff; max-width: 365px; min-width: 320px; @@ -3183,7 +3289,8 @@ .e-quick-popup-wrapper .e-cell-popup .e-popup-header .e-header-icon-wrapper .e-close { color: #adb5bd; } -.e-quick-popup-wrapper .e-cell-popup .e-popup-header .e-header-icon-wrapper .e-edit:focus, .e-quick-popup-wrapper .e-cell-popup .e-popup-header .e-header-icon-wrapper .e-edit:hover, +.e-quick-popup-wrapper .e-cell-popup .e-popup-header .e-header-icon-wrapper .e-edit:focus, +.e-quick-popup-wrapper .e-cell-popup .e-popup-header .e-header-icon-wrapper .e-edit:hover, .e-quick-popup-wrapper .e-cell-popup .e-popup-header .e-header-icon-wrapper .e-delete:focus, .e-quick-popup-wrapper .e-cell-popup .e-popup-header .e-header-icon-wrapper .e-delete:hover, .e-quick-popup-wrapper .e-cell-popup .e-popup-header .e-header-icon-wrapper .e-close:focus, @@ -3228,7 +3335,8 @@ .e-quick-popup-wrapper .e-event-popup .e-popup-header .e-header-icon-wrapper .e-close { color: #adb5bd; } -.e-quick-popup-wrapper .e-event-popup .e-popup-header .e-header-icon-wrapper .e-edit:focus, .e-quick-popup-wrapper .e-event-popup .e-popup-header .e-header-icon-wrapper .e-edit:hover, +.e-quick-popup-wrapper .e-event-popup .e-popup-header .e-header-icon-wrapper .e-edit:focus, +.e-quick-popup-wrapper .e-event-popup .e-popup-header .e-header-icon-wrapper .e-edit:hover, .e-quick-popup-wrapper .e-event-popup .e-popup-header .e-header-icon-wrapper .e-delete:focus, .e-quick-popup-wrapper .e-event-popup .e-popup-header .e-header-icon-wrapper .e-delete:hover, .e-quick-popup-wrapper .e-event-popup .e-popup-header .e-header-icon-wrapper .e-close:focus, @@ -3398,7 +3506,8 @@ .e-quick-popup-wrapper.e-device .e-event-popup .e-popup-header .e-header-icon-wrapper { background: #212529; } -.e-quick-popup-wrapper.e-device .e-event-popup .e-popup-header .e-header-icon-wrapper .e-edit:focus, .e-quick-popup-wrapper.e-device .e-event-popup .e-popup-header .e-header-icon-wrapper .e-edit:hover, +.e-quick-popup-wrapper.e-device .e-event-popup .e-popup-header .e-header-icon-wrapper .e-edit:focus, +.e-quick-popup-wrapper.e-device .e-event-popup .e-popup-header .e-header-icon-wrapper .e-edit:hover, .e-quick-popup-wrapper.e-device .e-event-popup .e-popup-header .e-header-icon-wrapper .e-delete:focus, .e-quick-popup-wrapper.e-device .e-event-popup .e-popup-header .e-header-icon-wrapper .e-delete:hover, .e-quick-popup-wrapper.e-device .e-event-popup .e-popup-header .e-header-icon-wrapper .e-close:focus, @@ -3441,7 +3550,8 @@ height: 35px; width: 35px; } -.e-quick-popup-wrapper.e-device .e-multiple-event-popup .e-popup-header .e-close:focus, .e-quick-popup-wrapper.e-device .e-multiple-event-popup .e-popup-header .e-close:hover, +.e-quick-popup-wrapper.e-device .e-multiple-event-popup .e-popup-header .e-close:focus, +.e-quick-popup-wrapper.e-device .e-multiple-event-popup .e-popup-header .e-close:hover, .e-quick-popup-wrapper.e-device .e-multiple-event-popup .e-popup-header .e-edit:focus, .e-quick-popup-wrapper.e-device .e-multiple-event-popup .e-popup-header .e-edit:hover, .e-quick-popup-wrapper.e-device .e-multiple-event-popup .e-popup-header .e-delete:focus, @@ -3561,7 +3671,10 @@ .e-appointment.e-schedule-event-clone { background: #0d6efd; border-radius: 2px; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); color: #fff; display: -ms-flexbox; display: flex; @@ -3618,7 +3731,11 @@ display: none; } -.e-vertical-view .e-all-day-appointment-wrapper .e-appointment.e-schedule-event-clone .e-appointment-details .e-subject { +.e-vertical-view + .e-all-day-appointment-wrapper + .e-appointment.e-schedule-event-clone + .e-appointment-details + .e-subject { padding: 3px 0 1px 4px; } .e-vertical-view .e-all-day-appointment-wrapper .e-appointment.e-schedule-event-clone .e-appointment-details .e-time { @@ -3665,7 +3782,10 @@ .e-bigger .e-more-popup-wrapper .e-more-event-content .e-appointment-border, .e-more-popup-wrapper .e-more-event-content .e-appointment-border { border: 0; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); } .e-bigger .e-more-popup-wrapper .e-more-event-date-header, .e-more-popup-wrapper .e-more-event-date-header { @@ -3720,7 +3840,8 @@ .e-more-popup-wrapper .e-more-event-close .e-btn-icon { margin-top: 3px; } -.e-bigger .e-more-popup-wrapper .e-more-event-close:focus, .e-bigger .e-more-popup-wrapper .e-more-event-close:hover, +.e-bigger .e-more-popup-wrapper .e-more-event-close:focus, +.e-bigger .e-more-popup-wrapper .e-more-event-close:hover, .e-more-popup-wrapper .e-more-event-close:focus, .e-more-popup-wrapper .e-more-event-close:hover { background: #212529; @@ -3774,11 +3895,15 @@ line-height: 26px; padding: 0 2px; } -.e-bigger .e-more-popup-wrapper .e-appointment.e-appointment-border, .e-bigger .e-more-popup-wrapper .e-appointment:focus, +.e-bigger .e-more-popup-wrapper .e-appointment.e-appointment-border, +.e-bigger .e-more-popup-wrapper .e-appointment:focus, .e-more-popup-wrapper .e-appointment.e-appointment-border, .e-more-popup-wrapper .e-appointment:focus { border: 0; - box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15), 0 8px 12px rgba(0, 0, 0, 0.15); + box-shadow: + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15), + 0 8px 12px rgba(0, 0, 0, 0.15); } .e-bigger .e-more-popup-wrapper.e-device, .e-more-popup-wrapper.e-device { @@ -4653,7 +4778,6 @@ } .e-appointment { - background: hsl(240 6% 10%) !important; border-radius: 12px !important; //border: 1px solid #6EE7B7 !important; } @@ -4667,32 +4791,38 @@ } .e-schedule .e-content-wrap { - background-color: #18181B !important; + background-color: #18181b !important; } .e-schedule .e-schedule-toolbar { - background-color: #18181B !important; + background-color: #18181b !important; } .e-schedule .e-work-hours { - background-color: #18181B !important; + background-color: #18181b !important; } .e-schedule .e-work-cells { - background-color: #18181B !important; - border-color: #27272A !important; + background-color: #18181b !important; + border-color: #27272a !important; } .e-schedule .e-time-cells, .e-schedule .e-time-slots, .e-schedule .e-header-cells { - background-color: #18181B !important; + background-color: #18181b !important; } .e-schedule .e-current-day { color: hsl(217.2 91.2% 59.8%) !important; } -.e-schedule .e-vertical-view .e-day-wrapper .e-appointment { + +.overviewScheduler .e-schedule .e-vertical-view .e-day-wrapper .e-appointment { + width: 55% !important; border: none !important; +} + +.scheduler .e-appointment { width: 100% !important; -} \ No newline at end of file + border: none !important; +} diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx new file mode 100644 index 0000000..f14fc6b --- /dev/null +++ b/src/components/ui/popover.tsx @@ -0,0 +1,31 @@ +"use client"; + +import * as React from "react"; +import * as PopoverPrimitive from "@radix-ui/react-popover"; + +import { cn } from "@/lib/utils"; + +const Popover = PopoverPrimitive.Root; + +const PopoverTrigger = PopoverPrimitive.Trigger; + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)); +PopoverContent.displayName = PopoverPrimitive.Content.displayName; + +export { Popover, PopoverTrigger, PopoverContent }; diff --git a/src/lib/authActions.ts b/src/lib/authActions.ts index b5650cf..f460808 100644 --- a/src/lib/authActions.ts +++ b/src/lib/authActions.ts @@ -67,7 +67,7 @@ export async function refreshAccessToken(refreshToken?: string) { }); return res.access_Token; } - } else if (response.status === 401 || response.status === 403) { + } else if (response.status === 401 || response.status === 403 || response.status === 500) { return undefined; } else { throw new Error("There was a problem refreshing the access token: " + response.statusText); @@ -86,10 +86,10 @@ export async function getUser(accessToken?: string): Promise { if (response.ok) { return (await response.json()) as User; - } else if (response.status === 403) { + } else if (response.status === 403 || response.status === 500) { return null; } else { - throw new Error("There was a problem fetching the user: " + response.statusText); + throw new Error("There was a problem fetching the user: " + response.statusText + " " + response.status); } } diff --git a/src/middleware.ts b/src/middleware.ts index 8ed807c..5bb620a 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -32,7 +32,7 @@ export async function middleware(req: NextRequest) { response = NextResponse.redirect(new URL("/company/dashboard", req.url)); } else if ( req.nextUrl.pathname.startsWith("/company/dashboard") && - !["COMPANY_MEMBER", "COMPANY_ADMIN"].includes(user.role) + !["COMPANY_MEMBER", "COMPANY_OWNER"].includes(user.role) ) { response = NextResponse.redirect(new URL("/dashboard", req.url)); } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index e651eda..5f95e8d 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -59,3 +59,34 @@ export type SignupFormState = { confirmPassword: string; }; }; + +export type Appointment = { + id: number; + from: Date; + to: Date; + title: string; + description: string; + companyId: string; + location: string; + client: User | null; // null if not booked + status: "PENDING" | "BOOKED" | "CANCELLED" | "COMPLETED"; +}; + +export type Company = { + id: string; + name: string; + createdAt: string; + description: string; + owner: User; + members: User[]; + settings: { + appointmentDuration: number; + appointmentBuffer: number; + appointmentTypes: string[]; + appointmentLocations: string[]; + openingHours: { + from: string; + to: string; + }; + }; +};