From 821eb37260cb2668b416bd4cb0d92dbe1cb62b91 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Fri, 18 Oct 2024 22:57:39 -0400 Subject: [PATCH 1/7] implemented gallery creation component --- src/components/dashboard/ProviderGallery.tsx | 88 +++++++++ .../dashboard/ProviderGallerySlide.tsx | 176 ++++++++++++++++++ src/components/dashboard/RowForm.tsx | 51 ++++- 3 files changed, 307 insertions(+), 8 deletions(-) create mode 100644 src/components/dashboard/ProviderGallery.tsx create mode 100644 src/components/dashboard/ProviderGallerySlide.tsx diff --git a/src/components/dashboard/ProviderGallery.tsx b/src/components/dashboard/ProviderGallery.tsx new file mode 100644 index 0000000..9da14a8 --- /dev/null +++ b/src/components/dashboard/ProviderGallery.tsx @@ -0,0 +1,88 @@ +import React, { useState } from "react"; +import ProviderGallerySlide from "./ProviderGallerySlide"; +import { storage } from "../../store"; + +interface GallerySlide { + title: string; + description: string; + imgLink: string; +} + +export default function ProviderGallery({ + slidesArray, +}: { + slidesArray: GallerySlide[]; +}) { + const [slides, setSlides] = useState(slidesArray); + + const handleSlideDataChange = ( + index: number, + field: keyof GallerySlide, + e: React.ChangeEvent + ) => { + let newSlides = [...slides]; + newSlides[index][field] = e.target.value; + setSlides(newSlides); + }; + + const handleDelete = (index: number) => { + if (slides.length != 1) { + // min 1 slide present + let newSlides = [...slides]; + newSlides.splice(index, 1); + setSlides(newSlides); + } + }; + + const handleAdd = (index: number) => { + const defaultSlide = { + title: "", + description: "", + imgLink: "", + }; + let newSlides = [...slides]; + + if (index === newSlides.length - 1) { + newSlides.push(defaultSlide); + } else { + newSlides.splice(index + 1, 0, defaultSlide); + } + setSlides(newSlides); + }; + + const handleUpload = async (file, index) => { + const filename = file.name; + await storage.ref("images").child(filename).put(file); + let newSlides = [...slides]; + await storage + .ref("images") + .child(filename) + .getDownloadURL() + .then((url) => { + newSlides[index].imgLink = url; + setSlides(newSlides); + }); + }; + + return ( +
+
+

Current Data:

+
{JSON.stringify(slides, null, 2)}
+
+ {slides.map((slide, i) => { + return ( + + ); + })} +
+ ); +} diff --git a/src/components/dashboard/ProviderGallerySlide.tsx b/src/components/dashboard/ProviderGallerySlide.tsx new file mode 100644 index 0000000..a79440a --- /dev/null +++ b/src/components/dashboard/ProviderGallerySlide.tsx @@ -0,0 +1,176 @@ +import React, { useState } from "react"; +import Form from "react-bootstrap/Form"; +import { Button } from "react-bootstrap"; +import Dropzone from "react-dropzone"; + +export default function ProviderGallerySlide({ + title, + description, + imgLink, // Opens use of imgLink in slide for editing maybe? + index, + handleSlideDataChange, + handleDelete, + handleAdd, + handleUpload, +}) { + return ( +
+
+ {`Slide ${index + 1}`} + + + Title * + + + handleSlideDataChange(index, "title", e) + } + style={{ borderColor: "#D9D9D9" }} + required + /> + + + Description + + handleSlideDataChange(index, "description", e) + } + style={{ borderColor: "#D9D9D9" }} + /> + + + Upload file + + +
+
+
+ +
+ +
+
+ ); +} + +const ImageModal = ({ handleSuccess, index }) => { + const [uploaded, setUploaded] = useState(false); + const [image, setImage] = useState(null); + + const handleDrop = (img) => { + setImage(img[0]); + setUploaded(true); + }; + + return ( +
+ {!uploaded && ( + + {({ getRootProps, getInputProps }) => ( +
+ +

+ Drop your image here, or{" "} + browse +

+

Supports JPG, JPEG2000, PNG

+
+ )} +
+ )} + {uploaded && ( +
+
+ description +
+ + +
+ )} +
+ ); +}; diff --git a/src/components/dashboard/RowForm.tsx b/src/components/dashboard/RowForm.tsx index c279b47..95ac6ae 100644 --- a/src/components/dashboard/RowForm.tsx +++ b/src/components/dashboard/RowForm.tsx @@ -19,6 +19,39 @@ import Button from "react-bootstrap/Button"; import ActionForm from "./ActionForm"; import ContentForm from "./ContentForm"; +import ProviderGallery from "./ProviderGallery"; +import Collapsible from "components/collapsible"; + +{ + /*TO BE DELETED */ +} +const galleryData = [ + { + title: "testVal1", + description: "testing testing", + imgLink: + "https://www.goodhousekeeping.com/life/pets/g4531/cutest-dog-breeds/", + }, + { + title: "testVal2", + description: "testing testing", + imgLink: + "https://www.goodhousekeeping.com/life/pets/g4531/cutest-dog-breeds/", + }, + { + title: "testVal3", + description: "testing testing", + imgLink: + "https://www.goodhousekeeping.com/life/pets/g4531/cutest-dog-breeds/", + }, + { + title: "testVal4", + description: "testing testing", + imgLink: + "https://www.goodhousekeeping.com/life/pets/g4531/cutest-dog-breeds/", + }, +]; + function validURL(str) { const pattern = new RegExp( "^(https?:\\/\\/)?" + // protocol @@ -27,7 +60,7 @@ function validURL(str) { "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string "(\\#[-a-z\\d_]*)?$", - "i", + "i" ); // fragment locator return !!pattern.test(str); } @@ -248,9 +281,9 @@ const RowForm = (props) => { style={{ color: isValidNumberForRegion( parseIncompletePhoneNumber( - item.phoneNum[0], + item.phoneNum[0] ), - "US", + "US" ) ? "green" : "red", @@ -258,9 +291,9 @@ const RowForm = (props) => { > {isValidNumberForRegion( parseIncompletePhoneNumber( - item.phoneNum[0], + item.phoneNum[0] ), - "US", + "US" ) ? "Valid number" : "Invalid number"} @@ -357,6 +390,8 @@ const RowForm = (props) => { as="textarea" /> + {/*TO BE DELETED */} + ); @@ -387,7 +422,7 @@ const RowForm = (props) => { }} /> - ), + ) )} ); @@ -411,7 +446,7 @@ const RowForm = (props) => { }} /> - ), + ) )} ); @@ -437,7 +472,7 @@ const RowForm = (props) => { }} /> - ), + ) )} ); From 6c7ec99872608db20086df7e9996aeed27b4c660 Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Fri, 18 Oct 2024 23:10:32 -0400 Subject: [PATCH 2/7] fixes to gallery creation component + created carousel file --- src/components/dashboard/ProviderGallery.tsx | 92 ++++++++++--------- .../dashboard/ProviderGalleryCarousel.tsx | 88 ++++++++++++++++++ 2 files changed, 138 insertions(+), 42 deletions(-) create mode 100644 src/components/dashboard/ProviderGalleryCarousel.tsx diff --git a/src/components/dashboard/ProviderGallery.tsx b/src/components/dashboard/ProviderGallery.tsx index 9da14a8..4ad17a3 100644 --- a/src/components/dashboard/ProviderGallery.tsx +++ b/src/components/dashboard/ProviderGallery.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import ProviderGallerySlide from "./ProviderGallerySlide"; import { storage } from "../../store"; @@ -9,59 +9,79 @@ interface GallerySlide { } export default function ProviderGallery({ - slidesArray, + slidesArray = [], }: { - slidesArray: GallerySlide[]; + slidesArray?: GallerySlide[]; }) { - const [slides, setSlides] = useState(slidesArray); + const [slides, setSlides] = useState(slidesArray); + + const defaultSlide: GallerySlide = { + title: "", + description: "", + imgLink: "", + }; + + useEffect(() => { + if (!slides || slides.length === 0) { + setSlides([{ ...defaultSlide }]); + } + }, [slides]); const handleSlideDataChange = ( index: number, field: keyof GallerySlide, e: React.ChangeEvent ) => { - let newSlides = [...slides]; - newSlides[index][field] = e.target.value; + const newSlides = slides.map((slide, i) => + i === index ? { ...slide, [field]: e.target.value } : slide + ); setSlides(newSlides); }; const handleDelete = (index: number) => { - if (slides.length != 1) { - // min 1 slide present - let newSlides = [...slides]; - newSlides.splice(index, 1); + if (slides.length > 1) { + const newSlides = slides.filter((_, i) => i !== index); setSlides(newSlides); } }; const handleAdd = (index: number) => { - const defaultSlide = { - title: "", - description: "", - imgLink: "", - }; - let newSlides = [...slides]; + const newSlides = [...slides]; + const newSlide = { ...defaultSlide }; if (index === newSlides.length - 1) { - newSlides.push(defaultSlide); + newSlides.push(newSlide); } else { - newSlides.splice(index + 1, 0, defaultSlide); + newSlides.splice(index + 1, 0, newSlide); } setSlides(newSlides); }; - const handleUpload = async (file, index) => { + const handleUpload = async (file, index: number) => { const filename = file.name; - await storage.ref("images").child(filename).put(file); - let newSlides = [...slides]; - await storage - .ref("images") - .child(filename) - .getDownloadURL() - .then((url) => { - newSlides[index].imgLink = url; - setSlides(newSlides); - }); + const fileRef = storage.ref("images").child(filename); + + await fileRef.put(file); + const url = await fileRef.getDownloadURL(); + + const newSlides = slides.map((slide, i) => + i === index ? { ...slide, imgLink: url } : slide + ); + setSlides(newSlides); + }; + + const renderSlides = () => { + return slides.map((slide, i) => ( + + )); }; return ( @@ -70,19 +90,7 @@ export default function ProviderGallery({

Current Data:

{JSON.stringify(slides, null, 2)}
- {slides.map((slide, i) => { - return ( - - ); - })} + {renderSlides()} ); } diff --git a/src/components/dashboard/ProviderGalleryCarousel.tsx b/src/components/dashboard/ProviderGalleryCarousel.tsx new file mode 100644 index 0000000..15f2ada --- /dev/null +++ b/src/components/dashboard/ProviderGalleryCarousel.tsx @@ -0,0 +1,88 @@ +import React, { useState } from "react"; +import ProviderGallerySlide from "./ProviderGallerySlide"; +import { storage } from "../../store"; + +interface GallerySlide { + title: string; + description: string; + imgLink: string; +} + +export default function ProviderGalleryCarousel({ + slidesArray, +}: { + slidesArray: GallerySlide[]; +}) { + const [slides, setSlides] = useState(slidesArray); + + const handleSlideDataChange = ( + index: number, + field: keyof GallerySlide, + e: React.ChangeEvent + ) => { + let newSlides = [...slides]; + newSlides[index][field] = e.target.value; + setSlides(newSlides); + }; + + const handleDelete = (index: number) => { + if (slides.length != 1) { + // min 1 slide present + let newSlides = [...slides]; + newSlides.splice(index, 1); + setSlides(newSlides); + } + }; + + const handleAdd = (index: number) => { + const defaultSlide = { + title: "", + description: "", + imgLink: "", + }; + let newSlides = [...slides]; + + if (index === newSlides.length - 1) { + newSlides.push(defaultSlide); + } else { + newSlides.splice(index + 1, 0, defaultSlide); + } + setSlides(newSlides); + }; + + const handleUpload = async (file, index) => { + const filename = file.name; + await storage.ref("images").child(filename).put(file); + let newSlides = [...slides]; + await storage + .ref("images") + .child(filename) + .getDownloadURL() + .then((url) => { + newSlides[index].imgLink = url; + setSlides(newSlides); + }); + }; + + return ( +
+
+

Current Data:

+
{JSON.stringify(slides, null, 2)}
+
+ {slides.map((slide, i) => { + return ( + + ); + })} +
+ ); +} From fc18252a2811f3067987ce18e137aee4af806eaa Mon Sep 17 00:00:00 2001 From: Charlie Lin Date: Sat, 19 Oct 2024 01:25:21 -0400 Subject: [PATCH 3/7] Implemented the gallery carousel --- src/components/dashboard/ProviderGallery.tsx | 2 + .../dashboard/ProviderGalleryCarousel.tsx | 219 +++++++++++++----- src/components/dashboard/RowForm.tsx | 13 +- src/components/dashboard/SingleProvider.tsx | 57 +++++ 4 files changed, 222 insertions(+), 69 deletions(-) diff --git a/src/components/dashboard/ProviderGallery.tsx b/src/components/dashboard/ProviderGallery.tsx index 4ad17a3..b988612 100644 --- a/src/components/dashboard/ProviderGallery.tsx +++ b/src/components/dashboard/ProviderGallery.tsx @@ -86,10 +86,12 @@ export default function ProviderGallery({ return (
+ {/*TO BE DELETED */}

Current Data:

{JSON.stringify(slides, null, 2)}
+ {renderSlides()}
); diff --git a/src/components/dashboard/ProviderGalleryCarousel.tsx b/src/components/dashboard/ProviderGalleryCarousel.tsx index 15f2ada..dd0f1f5 100644 --- a/src/components/dashboard/ProviderGalleryCarousel.tsx +++ b/src/components/dashboard/ProviderGalleryCarousel.tsx @@ -1,6 +1,4 @@ import React, { useState } from "react"; -import ProviderGallerySlide from "./ProviderGallerySlide"; -import { storage } from "../../store"; interface GallerySlide { title: string; @@ -13,76 +11,171 @@ export default function ProviderGalleryCarousel({ }: { slidesArray: GallerySlide[]; }) { - const [slides, setSlides] = useState(slidesArray); + const [currentIndex, setCurrentIndex] = useState(0); - const handleSlideDataChange = ( - index: number, - field: keyof GallerySlide, - e: React.ChangeEvent - ) => { - let newSlides = [...slides]; - newSlides[index][field] = e.target.value; - setSlides(newSlides); + const goToPrevious = () => { + setCurrentIndex((prevIndex) => + prevIndex === 0 ? slidesArray.length - 1 : prevIndex - 1 + ); }; - const handleDelete = (index: number) => { - if (slides.length != 1) { - // min 1 slide present - let newSlides = [...slides]; - newSlides.splice(index, 1); - setSlides(newSlides); - } + const goToNext = () => { + setCurrentIndex((prevIndex) => + prevIndex === slidesArray.length - 1 ? 0 : prevIndex + 1 + ); }; - const handleAdd = (index: number) => { - const defaultSlide = { - title: "", - description: "", - imgLink: "", - }; - let newSlides = [...slides]; + const isActive = (index: number) => currentIndex === index; - if (index === newSlides.length - 1) { - newSlides.push(defaultSlide); - } else { - newSlides.splice(index + 1, 0, defaultSlide); - } - setSlides(newSlides); - }; + return ( +
+
+ - const handleUpload = async (file, index) => { - const filename = file.name; - await storage.ref("images").child(filename).put(file); - let newSlides = [...slides]; - await storage - .ref("images") - .child(filename) - .getDownloadURL() - .then((url) => { - newSlides[index].imgLink = url; - setSlides(newSlides); - }); - }; + {/* Card */} +
+ {/*Left-side */} +
+ {/* Card Title */} +
+

+ {slidesArray[currentIndex].title} +

+
- return ( -
-
-

Current Data:

-
{JSON.stringify(slides, null, 2)}
+ {/* Card Description */} +
+

+ {slidesArray[currentIndex].description} +

+
+
+ {/*Right-side*/} +
+ {slidesArray[currentIndex].title} +
+
+ +
+ + {/* bubbles */} +
+ {slidesArray.map((_, index) => ( +
+ ))}
- {slides.map((slide, i) => { - return ( - - ); - })}
); } diff --git a/src/components/dashboard/RowForm.tsx b/src/components/dashboard/RowForm.tsx index 95ac6ae..d5863ac 100644 --- a/src/components/dashboard/RowForm.tsx +++ b/src/components/dashboard/RowForm.tsx @@ -19,9 +19,10 @@ import Button from "react-bootstrap/Button"; import ActionForm from "./ActionForm"; import ContentForm from "./ContentForm"; +{ + /*TO BE DELETED */ +} import ProviderGallery from "./ProviderGallery"; -import Collapsible from "components/collapsible"; - { /*TO BE DELETED */ } @@ -30,25 +31,25 @@ const galleryData = [ title: "testVal1", description: "testing testing", imgLink: - "https://www.goodhousekeeping.com/life/pets/g4531/cutest-dog-breeds/", + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", }, { title: "testVal2", description: "testing testing", imgLink: - "https://www.goodhousekeeping.com/life/pets/g4531/cutest-dog-breeds/", + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", }, { title: "testVal3", description: "testing testing", imgLink: - "https://www.goodhousekeeping.com/life/pets/g4531/cutest-dog-breeds/", + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", }, { title: "testVal4", description: "testing testing", imgLink: - "https://www.goodhousekeeping.com/life/pets/g4531/cutest-dog-breeds/", + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", }, ]; diff --git a/src/components/dashboard/SingleProvider.tsx b/src/components/dashboard/SingleProvider.tsx index f55c044..24b9634 100644 --- a/src/components/dashboard/SingleProvider.tsx +++ b/src/components/dashboard/SingleProvider.tsx @@ -6,12 +6,67 @@ import ButtonToolbar from "react-bootstrap/ButtonToolbar"; import { Link } from "react-router-dom"; import ProviderInfo from "../subcomponents/ProviderInfo"; import { formRoute } from "../../routes/pathnames"; +import ProviderGalleryCarousel from "./ProviderGalleryCarousel"; // updateFirestore = async () => { // //Change 'ages' to the specific parameter to update // await this.props.firestore.update({collection: 'providers', doc: this.state.itemUpdates['facilityName']}, {'ages': '10'}); // await this.props.firestore.get('providers') // }; +{ + /*TO BE DELETED */ +} +const galleryData = [ + { + title: "Urban Tree Fundraiser", + description: + "Last Friday, we gathered for food, fun, and giving back at Urban Tree cidery. All proceeds from the evening went to our Bereavement fund. Everyone remembered to bring a sweater because the back deck got cold. We enjoyed drinks, games, and more!", + imgLink: + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", + }, + { + title: "testVal2", + description: "testing testing", + imgLink: + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", + }, + { + title: "testVal3", + description: "testing testing", + imgLink: + "https://static.vecteezy.com/system/resources/thumbnails/005/857/332/small_2x/funny-portrait-of-cute-corgi-dog-outdoors-free-photo.jpg", + }, + { + title: "testVal4", + description: "testing testing", + imgLink: + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", + }, + { + title: "testVal1", + description: "testing testing", + imgLink: + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", + }, + { + title: "testVal2", + description: "testing testing", + imgLink: + "https://static.vecteezy.com/system/resources/thumbnails/005/857/332/small_2x/funny-portrait-of-cute-corgi-dog-outdoors-free-photo.jpg", + }, + { + title: "testVal3", + description: "testing testing", + imgLink: + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", + }, + { + title: "testVal4", + description: "testing testing", + imgLink: + "https://images.squarespace-cdn.com/content/v1/54822a56e4b0b30bd821480c/45ed8ecf-0bb2-4e34-8fcf-624db47c43c8/Golden+Retrievers+dans+pet+care.jpeg", + }, +]; const SingleProvider = (props) => (
@@ -56,6 +111,8 @@ const SingleProvider = (props) => ( > + {/*TO BE DELETED */} +
From aa642a7e81270ea952ffc3de37c149898ba0e4cd Mon Sep 17 00:00:00 2001 From: Athanasios Taprantzis Date: Mon, 21 Oct 2024 17:36:55 -0400 Subject: [PATCH 4/7] Admin content add button --- src/components/collapsible/index.tsx | 4 +- src/components/dashboard/AddProvider.tsx | 2 +- src/components/dashboard/ContentForm.tsx | 45 ++++++++++++++++--- .../chartcomponents/ChartComponentForm.tsx | 2 +- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/src/components/collapsible/index.tsx b/src/components/collapsible/index.tsx index 6eea44c..a88866d 100644 --- a/src/components/collapsible/index.tsx +++ b/src/components/collapsible/index.tsx @@ -5,13 +5,13 @@ import {FaAngleDown, FaAngleUp } from "react-icons/fa"; //This is collapsible component, use it as if you are using any pre-designed component //Specify the Style of collapsible component as if you were styling a div using style prompt -const Collapsible = ({style = {}, label, children}) => { +const Collapsible = ({style = {}, titleStyle={}, label, children}) => { const [isOpen, setOpen] = useState(false) const contentRef = useRef(null) const toogle = () => {setOpen(!isOpen)} return (
-
- +
diff --git a/src/components/dashboard/ContentForm.tsx b/src/components/dashboard/ContentForm.tsx index 303c1c8..ef35917 100644 --- a/src/components/dashboard/ContentForm.tsx +++ b/src/components/dashboard/ContentForm.tsx @@ -1,13 +1,15 @@ import "@fontsource/inter"; import React, { useEffect, useRef, useState } from "react"; -import { Button, Col, Container, Form, Row } from "react-bootstrap"; +import { Button, Col, Container, Dropdown, Form, Row } from "react-bootstrap"; // import grabberIcon from "../../assets/svg/grabber.svg"; import { ReactComponent as GrabberIcon } from "../../assets/svg/grabber.svg"; import { ReactComponent as PencilIcon } from "../../assets/svg/pencil.svg"; import { ReactComponent as CheckmarkIcon } from "../../assets/svg/checkmark.svg"; import styles from "./ContentForm.module.css"; +import ChartComponentForm from "components/subcomponents/chartcomponents/ChartComponentForm"; +import Collapsible from "components/collapsible"; const EditableText = ({ text, setText, isEditing, setIsEditing }) => { const inputRef = useRef(null); @@ -97,8 +99,9 @@ const SectionCard = ({ isEditing, setIsEditing, }) => { + const [components, setComponents] = useState([]); return ( - + + {components.map((v, i) => + + {v} + + )} - {index} + + + Add Section + + + + setComponents([...components, + + + + ])}>Chart + + ); @@ -179,9 +211,8 @@ const SectionButton = ({ maxWidth: "12px", borderTopLeftRadius: "8px", borderBottomLeftRadius: "8px", - backgroundColor: `${ - isSelected ? "#226DFF" : "transparent" - }`, + backgroundColor: `${isSelected ? "#226DFF" : "transparent" + }`, }} > { return (
-

Chart

+ {/*

Chart

*/}