From 51ca2f92ef2d2c12d3faf2dbefff43add30eb32f Mon Sep 17 00:00:00 2001 From: James Southern Date: Tue, 26 Mar 2024 17:21:06 +0000 Subject: [PATCH 1/4] Josh's frontpage & layout changes v007-4 (#98) --- app/globals.scss | 111 +++++++--- components/footer/Footer.tsx | 276 ++++++++++++++---------- components/footer/FooterLink.tsx | 4 +- components/sections/MovementSection.tsx | 4 +- 4 files changed, 254 insertions(+), 141 deletions(-) diff --git a/app/globals.scss b/app/globals.scss index 328b9cc..84d86de 100644 --- a/app/globals.scss +++ b/app/globals.scss @@ -95,6 +95,10 @@ $bs-link-hover-color-rgb: 238, 102, 238; --mf-pink-light: #ffccff; --mf-pink-strong: #ff66ff; --mf-pink-dark: #ee66ee; + --mf-pink-rgb: 255, 153, 255; + --mf-pink-light-rgb: 255, 204, 255; + --mf-pink-strong-rgb: 255, 102, 255; + --mf-pink-dark-rgb: 238, 102, 238; --bs-link-color: var(--bs-black); --bs-link-color-rgb: 0, 0, 0; --bs-link-decoration: underline; @@ -268,10 +272,10 @@ main .section-light p a:active { color: var(--bs-black); } -main .section-dark p a:link, -main .section-dark p a:visited, -main .section-dark p a:hover, -main .section-dark p a:active { +main .section-dark a:link, +main .section-dark a:visited, +main .section-dark a:hover, +main .section-dark a:active { color: var(--bs-white); } @@ -314,7 +318,7 @@ footer ul svg:hover { width: 22px; height: 22px; position: relative; - top: -2px; + top: -4px; } .custom-checkbox .form-check-input:checked { @@ -357,7 +361,7 @@ header p { /* SECTIONS */ section { - padding-top: 2rem; + padding-top: 1rem; padding-bottom: 1rem; } @@ -459,6 +463,10 @@ section h2.position-sticky { box-shadow: 0px 0px 24px rgba(0, 0, 0, 0.25); } +.form-search a.btn-light { + color: var(--bs-black) !important; +} + div.alert { margin-left: 12px; margin-right: 12px; @@ -475,17 +483,17 @@ div.alert { .rounded-box h3, .rounded-box h4, .rounded-box p { - color: white; + /*color: white;*/ } -.info-area a:link, -.info-area a:visited, -.info-area a:hover, -.info-area a:active, -.action-area a:link, -.action-area a:visited, -.action-area a:hover, -.action-area a:active { +main .info-area a:link, +main .info-area a:visited, +main .info-area a:hover, +main .info-area a:active, +main .action-area a:link, +main .action-area a:visited, +main .action-area a:hover, +main .action-area a:active { color: var(--bs-black); } @@ -499,6 +507,10 @@ div.alert { color: var(--bs-black); } +.info-area svg { + margin-right: 0.5rem !important; +} + .section-light .action-area { /*background-color: var(--bs-gray-200);*/ background: linear-gradient(var(--bs-gray-100), var(--bs-gray-150)); @@ -535,6 +547,24 @@ div.alert { /*border: solid 1px var(--bs-gray-300);*/ } +section-darker .action-area { + background-color: var(--bs-gray-400); + background: linear-gradient(var(--bs-gray-200), var(--bs-gray-400)); + box-shadow: 0px 5px 10px 0px rgba(var(--bs-black-rgb), 0.5); + /*border: solid 1px var(--bs-gray-300);*/ +} + +.section-darker .info-area { + background-color: var(--bs-gray-400); + background: linear-gradient(var(--bs-gray-200), var(--bs-gray-400)); + background: linear-gradient( + var(--bs-warning-bg-subtle), + var(--bs-warning-border-subtle) + ); + box-shadow: 0px 5px 10px 0px rgba(var(--bs-black-rgb), 0.5); + /*border: solid 1px var(--bs-gray-300);*/ +} + /* FOOTER */ footer { @@ -544,7 +574,7 @@ footer { border-top: 2px solid var(--bs-gray-100); } -@media (min-width: 992px) { +@include media-breakpoint-up(lg) { footer { background: var(--bs-gray-300); padding-top: 6rem; @@ -646,25 +676,54 @@ footer { /* PARTIES */ section h3.party { - font-size: 6vmax; + font-size: 7vmax; font-weight: 800; text-transform: uppercase; + overflow: auto; } -span.party-too-soon, -h3.party-too-soon, -svg.party-too-soon, -i.party-too-soon { - color: var(--bs-gray-600); +@include media-breakpoint-up(lg) { + section h3.party { + font-size: 4vmax; + font-weight: 800; + text-transform: uppercase; + overflow: auto; + } } -span.party-your-heart, -h3.party-your-heart, -svg.party-your-heart, -i.party-your-heart { +a span.party-heart, +h3.party-heart, +svg.party-heart, +i.party-heart { color: var(--mf-pink); } +div.party-heart { + background-color: rgba(var(--mf-pink-rgb), 1); + background: linear-gradient( + rgba(var(--mf-pink-rgb), 1), + rgba(var(--mf-pink-rgb), 0.85) + ); + box-shadow: 0px 5px 10px 0px rgba(var(--bs-black-rgb), 0.075); + /*border: solid 1px var(--bs-gray-300);*/ +} + +a span.party-none h3.party-none, +svg.party-none, +i.party-none { + color: var(--bs-gray-500); +} + +div.party-none { + background-color: rgba(var(--bs-gray-500-rgb), 1); + background: linear-gradient( + rgba(var(--bs-gray-500-rgb), 1), + rgba(var(--bs-gray-500-rgb), 0.85) + ); + box-shadow: 0px 5px 10px 0px rgba(var(--bs-black-rgb), 0.075); + /*border: solid 1px var(--bs-gray-300);*/ +} + span.party-labour, h3.party-labour, svg.party-labour, diff --git a/components/footer/Footer.tsx b/components/footer/Footer.tsx index a37af33..011b637 100644 --- a/components/footer/Footer.tsx +++ b/components/footer/Footer.tsx @@ -22,7 +22,7 @@ import { FaSquareTwitter, FaSquareWhatsapp, } from "react-icons/fa6"; -import { ButtonGroup } from "react-bootstrap"; +import { Button, ButtonGroup } from "react-bootstrap"; import { rubik } from "@/utils/Fonts"; const Footer = ({ blok }: { blok: FooterStoryblok }) => ( @@ -30,45 +30,10 @@ const Footer = ({ blok }: { blok: FooterStoryblok }) => ( @MVTFWD @@ -215,4 +164,109 @@ const Footer = ({ blok }: { blok: FooterStoryblok }) => ( ); +const SmallPrint = () => ( + + + {/* POSTCODE LOOKUP ATTRIBUTION */} +

+ Postcode lookup contains data from  + + + + DemocracyClub + + + + , the  + + + + ONS + + + +  &  + + + + MySociety + + + + . Contains OS data © Crown copyright and database right 2024. + Contains Royal Mail data © Royal Mail copyright and Database right + 2024. Contains GeoPlace data © Local Government Information House + Limited copyright and database right 2024. Source: Office for National + Statistics licensed under the Open Government Licence v.3.0 +

+ + +); + +const CallToAction = () => ( + + + + + + +); export default Footer; diff --git a/components/footer/FooterLink.tsx b/components/footer/FooterLink.tsx index 1c498af..a300a82 100644 --- a/components/footer/FooterLink.tsx +++ b/components/footer/FooterLink.tsx @@ -17,8 +17,8 @@ const link = ( ) => { { const className = `btn ${ - blok.button ? "btn-light" : "btn-link" - } btn-sm fw-bold text-start mx-1`; + blok.button ? "btn-light me-2" : "btn-link" + } btn-sm`; switch (blok.component) { case "footer_internal_link": diff --git a/components/sections/MovementSection.tsx b/components/sections/MovementSection.tsx index cd54e95..49f3ecd 100644 --- a/components/sections/MovementSection.tsx +++ b/components/sections/MovementSection.tsx @@ -35,10 +35,10 @@ const MovementSection = () => { return (
- + {/* PEOPLE */} - +

Join A Movement building voter power, beyond this election.

From 68c946f5036d105e9e290f336f2702db08d2963b Mon Sep 17 00:00:00 2001 From: Automated Version Bump Date: Tue, 26 Mar 2024 17:21:32 +0000 Subject: [PATCH 2/4] ci: version bump to v0.2.55 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d48ff26..46a9bee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "stt-next", - "version": "0.2.54", + "version": "0.2.55", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "stt-next", - "version": "0.2.54", + "version": "0.2.55", "dependencies": { "@octokit/core": "^5.0.1", "@storyblok/react": "^2.4.7", diff --git a/package.json b/package.json index 7f9f6d7..b096cfc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stt-next", - "version": "0.2.54", + "version": "0.2.55", "engines": { "npm": ">=9.0.0 <10.0.0", "node": ">=18.0.0 <19.0.0" From 883ea96c1977c3de525a0f4d73925ad1c9508ac1 Mon Sep 17 00:00:00 2001 From: James Southern Date: Wed, 27 Mar 2024 09:57:51 +0000 Subject: [PATCH 3/4] add signup form on the constituency page (#88) --- app/constituencies/[slug]/SignupShare.tsx | 251 ++++++++++++++++++++++ app/constituencies/[slug]/page.tsx | 37 +--- components/forms/constituencyLookup.tsx | 6 +- 3 files changed, 260 insertions(+), 34 deletions(-) create mode 100644 app/constituencies/[slug]/SignupShare.tsx diff --git a/app/constituencies/[slug]/SignupShare.tsx b/app/constituencies/[slug]/SignupShare.tsx new file mode 100644 index 0000000..8d5dae4 --- /dev/null +++ b/app/constituencies/[slug]/SignupShare.tsx @@ -0,0 +1,251 @@ +"use client"; + +import { + Form, + Button, + ButtonGroup, + Spinner, + InputGroup, +} from "react-bootstrap"; + +import { + FaShare, + FaPuzzlePiece, + FaCopy, + FaHandHoldingHeart, +} from "react-icons/fa6"; +import { submitANForm } from "@/utils/AnApiSubmission"; +import { rubik } from "@/utils/Fonts"; +import { useRef, useState, useEffect } from "react"; +import ConstituencyLookup from "@/components/forms/constituencyLookup"; + +const emailErrorMessage = (code: EmailErrorCode) => { + switch (code) { + case "EMAIL_INVALID": + return "Please add a valid email address."; + case "SERVER_ERROR": + return "Something went wrong signing you up. Please try again?"; + default: + return ""; + } +}; + +type FormData = { + emailOptIn: boolean; + email: string; +}; + +const initialFormState: FormData = { + emailOptIn: false, + email: "", +}; + +// Variables used to track state of the ConstituencyLookup components + +export default function SignupShare({ + constituencyData, +}: { + constituencyData: ConstituencyData; +}) { + const [subscribed, setSubscribed] = useState(false); + const [formState, setFormState] = useState(initialFormState); + const [emailError, setEmailError] = useState(null); + const validPostcode = useRef(""); + const [constituency, setConstituency] = useState(null); + const [constituencyApiLoading, setConstituencyApiLoading] = useState(false); + + // Variables used to track state of the email opt-in components + const formRef = useRef(null); + + // Track whether the user is already subscribed via localStorage + useEffect(() => { + //string = subscription Date.now() + //null = not subscribed on client + //false = on server + setSubscribed(window.localStorage.getItem("fwd-subscribed")); + }, []); + + const submitForm = async () => { + if ( + constituency && + formState.email && + formRef.current && + !formRef.current.email.validity.typeMismatch + ) { + //TODO set source codes from current url params. + const anResponse = await submitANForm( + formState.email, + validPostcode.current, + constituency, + process.env.NEXT_PUBLIC_AN_POSTCODE_FORM || "", + ["stop the tories", "movement forward", "election reminders", "join"], + "", // source codes, + ); + + if (anResponse.ok) { + window.localStorage.setItem("fwd-subscribed", Date.now().toString()); + setSubscribed("Subscribed"); + } else { + setEmailError("SERVER_ERROR"); //AN doesn't give error codes on failure + } + } + + // VALIDATION + // Invalid email + if ( + formState.emailOptIn && + (!formState.email || + (formRef.current && formRef.current.email.validity.typeMismatch)) + ) { + setEmailError("EMAIL_INVALID"); + } + + // // no postcode or invalid postcode or constituency/address not selected + // if ( + // !validPostcode.current || + // !apiResponse || + // !apiResponse.constituencies || + // apiResponse.constituencies.length == 0 + // ) { + // // User hasn't input anything or invalid postcode + // setPostError("POSTCODE_INVALID"); + // return; + // } + + // //not selected constituency or address + // if ( + // apiResponse.constituencies.length > 1 && + // formState.constituencyIndex === false + // ) { + // setPostError("UNCLEAR_CONSTITUENCY"); + // } + }; + + if (!subscribed) { + return ( +
+

Join the movement forward

+
    +
  • + Be counted, I'm voting tactically! +
  • +
  • Get a voting plan
  • +
  • Get reminders and actions
  • +
+ {/* Renders the postcode box, makes API calls, and if necessary shows an address/constituency picker */} + + +
+ <> + + { + setFormState({ ...formState, email: e.target.value }); + if (!e.target.validity.typeMismatch) { + setEmailError(null); + } + }} + className="invalid-text-greyed" + /> + + {emailError ? emailErrorMessage(emailError) : ""} + + +

+ You're opting in to receive emails. We store your email + address, postcode, and constituency, so we can send you exactly + the information you need. +

+ +
+ +
+ + + Privacy Policy + +
+ + ); + } else { + return ( +
+

Grow this movement

+

You're in! Now let's build our numbers

+ + {/* TODO share link */} + + + + + + +
+ ); + } +} diff --git a/app/constituencies/[slug]/page.tsx b/app/constituencies/[slug]/page.tsx index a608f64..0b6ee0f 100644 --- a/app/constituencies/[slug]/page.tsx +++ b/app/constituencies/[slug]/page.tsx @@ -1,7 +1,6 @@ -import { Col, Container, Row, ButtonGroup, Button } from "react-bootstrap"; +import { Col, Container, Row } from "react-bootstrap"; import Link from "next/link"; import Header from "@/components/Header"; -import ActionBox from "@/components/info_box/ActionBox"; import ImpliedChart from "@/components/info_box/ImpliedChart"; import MRPChart from "@/components/info_box/MRPChart"; import PlanToVoteBox from "@/components/info_box/PlanToVoteBox"; @@ -12,13 +11,7 @@ import { getConstituencySlugs, } from "@/utils/constituencyData"; import { notFound } from "next/navigation"; -import { - FaShare, - FaPuzzlePiece, - FaCopy, - FaHandHoldingHeart, -} from "react-icons/fa6"; -import PostcodeLookup from "@/components/constituency_lookup/ConstituencyLookup"; +import SignupShare from "./SignupShare"; export const dynamicParams = false; // Don't allow params not in generateStaticParams @@ -86,6 +79,7 @@ export default async function ConstituencyPage({ } } + // NO ADVICE OVERRIDE (NI & Speaker) if (constituencyData.recommendation.partySlug === "None") { return ( <> @@ -157,30 +151,7 @@ export default async function ConstituencyPage({
-
-

Grow this movement

-

You're in! Now let's build our numbers

- - {/* TODO share link and clipboard copy */} - - - - - - -
+

diff --git a/components/forms/constituencyLookup.tsx b/components/forms/constituencyLookup.tsx index 0cce09d..7dde63d 100644 --- a/components/forms/constituencyLookup.tsx +++ b/components/forms/constituencyLookup.tsx @@ -142,6 +142,9 @@ interface IProps { setConstituency: Dispatch>; loading: boolean; setLoading: Dispatch>; + //Prevent the selected constituency + //displaying on the constituencies own page. + filterConstituencySlug?: string; } const ConstituencyLookup = ({ @@ -150,6 +153,7 @@ const ConstituencyLookup = ({ setConstituency, loading, setLoading, + filterConstituencySlug, }: IProps) => { const [formState, setFormState] = useState(initialFormState); const [apiResponse, setApiResponse] = useState< @@ -275,7 +279,7 @@ const ConstituencyLookup = ({ - {constituency && ( + {constituency && constituency.slug != filterConstituencySlug && ( Date: Wed, 27 Mar 2024 09:58:06 +0000 Subject: [PATCH 4/4] ci: version bump to v0.2.56 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46a9bee..5c66421 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "stt-next", - "version": "0.2.55", + "version": "0.2.56", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "stt-next", - "version": "0.2.55", + "version": "0.2.56", "dependencies": { "@octokit/core": "^5.0.1", "@storyblok/react": "^2.4.7", diff --git a/package.json b/package.json index b096cfc..3609e38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stt-next", - "version": "0.2.55", + "version": "0.2.56", "engines": { "npm": ">=9.0.0 <10.0.0", "node": ">=18.0.0 <19.0.0"