diff --git a/assets/css/_trip-plan-results.scss b/assets/css/_trip-plan-results.scss index 9075b24534..bfa87176f9 100644 --- a/assets/css/_trip-plan-results.scss +++ b/assets/css/_trip-plan-results.scss @@ -30,6 +30,20 @@ } } + &-tag { + background-color: $brand-primary-darkest; + border-radius: 0 0 8px; + color: $white; + font-weight: bold; + grid-column-end: 3; + grid-column-start: 1; + margin-bottom: 1rem; + margin-left: -1rem; + margin-top: -1rem; + padding: .5rem 1rem; + width: fit-content; + } + /* stylelint-disable property-no-vendor-prefix */ &-header { background-color: $brand-primary-lightest-contrast; @@ -37,7 +51,7 @@ border-bottom: 0; display: -ms-grid; display: grid; - -ms-grid-columns: 1fr 1fr; + -ms-grid-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr; line-height: 1.5; margin-top: 0; diff --git a/assets/css/_utilities.scss b/assets/css/_utilities.scss index 65a423992e..601a3d3ba4 100644 --- a/assets/css/_utilities.scss +++ b/assets/css/_utilities.scss @@ -267,6 +267,10 @@ margin-top: 0 !important; } +.mt-05 { + margin-top: calc(#{$base-spacing} / 2) !important; +} + .mt-1 { margin-top: $base-spacing !important; } diff --git a/assets/css/_variables.scss b/assets/css/_variables.scss index c7ccdbb245..9af231f723 100644 --- a/assets/css/_variables.scss +++ b/assets/css/_variables.scss @@ -151,6 +151,8 @@ $gray-stripe-pattern: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAA $form-check-margin-bottom: $base-spacing-sm; $form-check-input-gutter: $base-spacing; $form-check-input-margin-y: 0; +$input-border-color: $brand-primary; +$input-btn-border-width: 2px; // Font-awesome settings $fa-font-path: '/fonts'; diff --git a/assets/react_app.js b/assets/react_app.js index 5d347eb6d6..8c638ea428 100644 --- a/assets/react_app.js +++ b/assets/react_app.js @@ -5,7 +5,6 @@ import readline from "readline"; import TransitNearMe from "../assets/ts/tnm/components/TransitNearMe"; import AdditionalLineInfo from "../assets/ts/schedule/components/AdditionalLineInfo"; import ScheduleFinder from "../assets/ts/schedule/components/ScheduleFinder"; -import TripPlannerResults from "../assets/ts/trip-plan-results/components/TripPlannerResults"; import ProjectsPage from "../assets/ts/projects/components/ProjectsPage"; import LiveCrowdingIcon from "./ts/schedule/components/line-diagram/LiveCrowdingIcon"; @@ -56,7 +55,6 @@ const Components = { ScheduleFinder, AdditionalLineInfo, TransitNearMe, - TripPlannerResults, ProjectsPage, LiveCrowdingIcon }; diff --git a/assets/ts/components/Feedback.tsx b/assets/ts/components/Feedback.tsx deleted file mode 100644 index 6a06b48dcc..0000000000 --- a/assets/ts/components/Feedback.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { ReactElement } from "react"; - -const Feedback = (): ReactElement => { - const url = - /* istanbul ignore next */ - typeof document !== "undefined" - ? `/customer-support?comments=${document.title}` - : `/customer-support`; - return ( -
-

Feedback

- Please report issues with cleanliness, signage, and any other concerns you - may have. We will do our best to address the issues as soon as possible. - - Contact us - -
- ); -}; - -export default Feedback; diff --git a/assets/ts/components/FeedbackForm.tsx b/assets/ts/components/FeedbackForm.tsx new file mode 100644 index 0000000000..c990326490 --- /dev/null +++ b/assets/ts/components/FeedbackForm.tsx @@ -0,0 +1,190 @@ +import { uniqueId } from "lodash"; +import React, { + ChangeEvent, + ReactElement, + useEffect, + useRef, + useState +} from "react"; +import renderFa from "../helpers/render-fa"; + +interface VoteButtonProps { + upOrDown: "up" | "down"; + ariaLabel: string; + onPress: Function; + isActive?: boolean; +} +const VoteButton = ({ + upOrDown, + ariaLabel, + onPress, + isActive = false +}: VoteButtonProps): ReactElement => { + return ( + + ); +}; + +const sendFormData = ( + formEl: HTMLFormElement | null, + callback: Function +): void => { + if (!formEl) return; + const formData = new FormData(formEl); + const formJson = Object.fromEntries(formData.entries()); + callback(formJson); +}; + +interface FeedbackFormProps { + promptText: string; + upLabel: string; + downLabel: string; + commentPromptText: string; + commentLabel: string; + commentPlaceholder: string; + formDataCallback: (formData: Record) => void; +} +/** + * A compact form featuring upvote/downvote button, optional comment submission, + * and invocation of a callback function on submit + */ +const FeedbackForm = ({ + promptText, + upLabel, + downLabel, + commentPromptText, + commentLabel, + commentPlaceholder, + formDataCallback +}: FeedbackFormProps): ReactElement => { + const [showComment, setShowComment] = useState(false); + const [showConfirmation, setShowConfirmation] = useState(false); + const [vote, setVote] = useState<"up" | "down" | null>(null); + const [hasComment, setHasComment] = useState(false); + + const upVote = (): void => { + setVote(v => (v === "up" ? null : "up")); + setShowComment(false); + }; + const downVote = (): void => setVote(v => (v === "down" ? null : "down")); + const showCommentPrompt = vote === "down"; + const toggleComment = (): void => { + setShowComment(show => !show); + }; + const handleTextAreaChange = ( + event: ChangeEvent + ): void => { + const { + target: { value } + } = event; + setHasComment(!!value && value !== ""); + }; + + const formRef = useRef(null); + + useEffect(() => { + sendFormData(formRef.current, formDataCallback); + }, [vote, formDataCallback]); + + if (showConfirmation) + return ( +

+ Your feedback has been submitted. Thanks for helping us improve the + website! +

+ ); + + const commentId = uniqueId(); + return ( +
) => { + event.preventDefault(); + sendFormData(event.currentTarget, formDataCallback); + setShowComment(false); + setShowConfirmation(true); + }} + > +
+ {vote === null ? promptText : "Thanks for your response!"} +
+
+ + + +
+ + {showCommentPrompt && ( + + )} + + {showComment && ( +
+
+