diff --git a/.github/workflows/build-and-push.yaml b/.github/workflows/build-and-push.yaml index 363a7017..b3b04126 100644 --- a/.github/workflows/build-and-push.yaml +++ b/.github/workflows/build-and-push.yaml @@ -75,12 +75,20 @@ jobs: name: Get deploy tag id: deploy-tag run: | - # Parse container image tag to deploy - full_tag=$(echo "$DOCKER_METADATA_OUTPUT_JSON" | jq -r '.tags[] | limit(1; select(. | test(":sha-|:v.")))') - echo "Will use tag \"$full_tag\" for deployment." - echo image_tag=$(echo "$full_tag" | cut -f2 -d:) >> "$GITHUB_OUTPUT" - echo image_name=$(echo "$full_tag" | cut -f1 -d:) >> "$GITHUB_OUTPUT" - echo full_tag=$full_tag >> "$GITHUB_OUTPUT" + # Determine environment to deploy to + if ${{ github.ref == 'master' || github.base_ref == 'master'}}; then + environment_name="int" + credentials_json='${{ secrets.SKYVIEWER_INT_SERVICE_ACCOUNT }}' + elif ${{ github.ref_type == 'tag'}}; then + environment_name="prod" + credentials_json='${{ secrets.PIPELINE_EPO_PROD_PROJECT }}' + else + environment_name="dev" + credentials_json='${{ secrets.DEV_SA_KEY }}' + fi + echo environment_name=$environment_name >> "$GITHUB_OUTPUT" + echo credentials_json=$credentials_json >> "$GITHUB_OUTPUT" + - name: Set up QEMU uses: docker/setup-qemu-action@v2 diff --git a/.github/workflows/dev-pull-request.yaml b/.github/workflows/dev-pull-request.yaml index 7388bdd0..ae416f8d 100644 --- a/.github/workflows/dev-pull-request.yaml +++ b/.github/workflows/dev-pull-request.yaml @@ -39,6 +39,7 @@ jobs: --build-arg NEXT_PUBLIC_DEBUG_LOGGING=false \ --build-arg CLOUD_ENV=DEV \ --build-arg NEXT_PUBLIC_GOOGLE_APP_ID=688095955960-t0fpaj4ec3gh5vsr9lhg8govapk2oeo9.apps.googleusercontent.com \ + --build-arg GOOGLE_APP_SECRET=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 \ --build-arg NEXT_PUBLIC_CONTACT_FORM_POST_URL=https://api-dev.rubinobs.com/actions/contact-form/send \ --build-arg NEXT_PUBLIC_PLAUSIBLE_DOMAIN= \ --build-arg NEXT_PREVIEW_SLUG=preview-in-craft-cms \ diff --git a/.github/workflows/dev-push.yaml b/.github/workflows/dev-push.yaml index dca62ef0..d782dc72 100644 --- a/.github/workflows/dev-push.yaml +++ b/.github/workflows/dev-push.yaml @@ -33,6 +33,7 @@ jobs: --build-arg CLOUD_ENV=DEV \ --build-arg NEXT_PUBLIC_BASE_URL=https://dev.rubinobs.com \ --build-arg NEXT_PUBLIC_GOOGLE_APP_ID=688095955960-t0fpaj4ec3gh5vsr9lhg8govapk2oeo9.apps.googleusercontent.com \ + --build-arg GOOGLE_APP_SECRET=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 \ --build-arg NEXT_PUBLIC_CONTACT_FORM_POST_URL=https://api-dev.rubinobs.com/actions/contact-form/send \ --build-arg NEXT_PUBLIC_PLAUSIBLE_DOMAIN= \ --build-arg NEXT_PREVIEW_SLUG=preview-in-craft-cms \ diff --git a/.github/workflows/master-pr.yaml b/.github/workflows/master-pr.yaml index dc3777cb..917994a8 100644 --- a/.github/workflows/master-pr.yaml +++ b/.github/workflows/master-pr.yaml @@ -38,7 +38,8 @@ jobs: --build-arg NEXT_PUBLIC_DEBUG_LOGGING=false \ --build-arg CLOUD_ENV=INT \ --build-arg NEXT_PUBLIC_BASE_URL=https://int.rubinobs.dev \ - --build-arg NEXT_PUBLIC_GOOGLE_APP_ID=688095955960-t0fpaj4ec3gh5vsr9lhg8govapk2oeo9.apps.googleusercontent.com \ + --build-arg NEXT_PUBLIC_GOOGLE_APP_ID=422617727806-q7opitpsisom3fekpplsh8b77s0oe6n2.apps.googleusercontent.com \ + --build-arg GOOGLE_APP_SECRET=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 \ --build-arg NEXT_PUBLIC_CONTACT_FORM_POST_URL=https://api-int.rubinobs.dev/actions/contact-form/send \ --build-arg NEXT_PUBLIC_PLAUSIBLE_DOMAIN= \ --build-arg NEXT_PREVIEW_SLUG=preview-in-craft-cms \ diff --git a/.github/workflows/master-push.yaml b/.github/workflows/master-push.yaml index f9839403..ccec1b63 100644 --- a/.github/workflows/master-push.yaml +++ b/.github/workflows/master-push.yaml @@ -32,7 +32,8 @@ jobs: --build-arg EDC_LOGGER_API_URL=https://us-central1-skyviewer.cloudfunctions.net/edc-logger \ --build-arg NEXT_PUBLIC_DEBUG_LOGGING=false \ --build-arg CLOUD_ENV=INT \ - --build-arg NEXT_PUBLIC_GOOGLE_APP_ID=688095955960-t0fpaj4ec3gh5vsr9lhg8govapk2oeo9.apps.googleusercontent.com \ + --build-arg NEXT_PUBLIC_GOOGLE_APP_ID=422617727806-q7opitpsisom3fekpplsh8b77s0oe6n2.apps.googleusercontent.com \ + --build-arg GOOGLE_APP_SECRET=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 \ --build-arg NEXT_PUBLIC_CONTACT_FORM_POST_URL=https://api-int.rubinobs.dev/actions/contact-form/send \ --build-arg NEXT_PUBLIC_PLAUSIBLE_DOMAIN= \ --build-arg NEXT_PREVIEW_SLUG=preview-in-craft-cms \ diff --git a/.github/workflows/prod-push.yaml b/.github/workflows/prod-push.yaml index f1eaa181..f7209e06 100644 --- a/.github/workflows/prod-push.yaml +++ b/.github/workflows/prod-push.yaml @@ -39,6 +39,7 @@ jobs: --build-arg NEXT_PUBLIC_DEBUG_LOGGING=false \ --build-arg CLOUD_ENV=PROD \ --build-arg NEXT_PUBLIC_GOOGLE_APP_ID=596747551410-vcqlrp7erg5c5gm0dkvc0k21mgi0ilg0.apps.googleusercontent.com \ + --build-arg GOOGLE_APP_SECRET=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 \ --build-arg NEXT_PUBLIC_CONTACT_FORM_POST_URL=https://api.rubinobs.org/actions/contact-form/send \ --build-arg NEXT_PUBLIC_PLAUSIBLE_DOMAIN=rubinobservatory.org,all.epo.sites \ --build-arg NEXT_PREVIEW_SLUG=preview-in-craft-cms \ diff --git a/components/atomic/Button/patterns/GoogleSSOButton.js b/components/atomic/Button/patterns/GoogleSSOButton.js new file mode 100644 index 00000000..2cf2df02 --- /dev/null +++ b/components/atomic/Button/patterns/GoogleSSOButton.js @@ -0,0 +1,52 @@ +import PropTypes from "prop-types"; +import { useGoogleLogin } from "@react-oauth/google"; +import { useRouter } from "next/router"; +import { useAuthenticationContext } from "@/contexts/Authentication"; +import SSOButton from "./SSOButton"; + +export default function GoogleSSOButton({ children, ...buttonProps }) { + const { authenticateWithGoogle } = useAuthenticationContext(); + const { query, push, asPath, pathname } = useRouter(); + const goToGoogleSignIn = useGoogleLogin({ + state: asPath, + onSuccess: (response) => { + push( + { pathname: asPath.split("?")[0], query: { sso: true } }, + undefined, + { + shallow: true, + } + ); + fetch("/api/authGoogle", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ code: response.code }), + }) + .then((res) => res.json()) + .then((data) => { + authenticateWithGoogle(data); + }) + .catch(console.error); + }, + onError: (error) => { + console.error(error); + }, + flow: "auth-code", + }); + + return ( + + {children} + + ); +} + +GoogleSSOButton.propTypes = { + children: PropTypes.node, + service: PropTypes.oneOf(["google", "facebook", "email"]), +}; diff --git a/components/atomic/index.js b/components/atomic/index.js index fc309792..181f6fd7 100644 --- a/components/atomic/index.js +++ b/components/atomic/index.js @@ -1,4 +1,5 @@ export { default as SSOButton } from "./Button/patterns/SSOButton"; +export { default as GoogleSSOButton } from "./Button/patterns/GoogleSSOButton"; export { default as EarlyAccess } from "./Flag/patterns/EarlyAccess"; export { default as Tile } from "./Tile"; export { default as InvestigationTile } from "./Tile/patterns/InvestigationTile"; diff --git a/components/auth/AuthorizePage/index.js b/components/auth/AuthorizePage/index.js index cc0b3358..8c6295ba 100644 --- a/components/auth/AuthorizePage/index.js +++ b/components/auth/AuthorizePage/index.js @@ -15,7 +15,8 @@ const AUTHORIZED_TYPES = { }; function isAuthorized(typeHandle, user, status) { - if (typeHandle === "educatorPages") return user?.group === "educators"; + if (typeHandle === "educatorPages") + return user?.group === "educators" && status === "active"; if (typeHandle === "userProfilePage") return !!user && status === "active"; return false; } diff --git a/components/dynamic/EventList/index.js b/components/dynamic/EventList/index.js index 6230ac49..62176cd7 100644 --- a/components/dynamic/EventList/index.js +++ b/components/dynamic/EventList/index.js @@ -42,6 +42,7 @@ const EventList = ({ endDate, description, id, + hero, image, eventType, registrationCloseDate, @@ -82,7 +83,7 @@ const EventList = ({ } : null } - image={image?.[0]} + image={image?.[0] || hero?.[0]} link={uri} pretitle={ gridType === "events" && eventType?.[0]?.title diff --git a/components/dynamic/JobList/index.js b/components/dynamic/JobList/index.js index 1bda97d7..def7363a 100644 --- a/components/dynamic/JobList/index.js +++ b/components/dynamic/JobList/index.js @@ -59,7 +59,8 @@ const JobList = ({ ); // logic for open/closed - const checkOpen = checkIfBetweenDates(openDate, closeDate) + let checkOpen = "open"; + checkOpen = checkIfBetweenDates(openDate, closeDate) ? "open" : "closed"; const lock = checkOpen === "open" ? "LockOpen" : "LockClosed"; diff --git a/components/dynamic/NewsList/index.js b/components/dynamic/NewsList/index.js index e349968d..3d324c3c 100644 --- a/components/dynamic/NewsList/index.js +++ b/components/dynamic/NewsList/index.js @@ -51,6 +51,7 @@ const NewsList = ({ description, subtitle, id, + hero, image, images: releaseImages, newsAssets, @@ -70,7 +71,11 @@ const NewsList = ({ } : null } - image={image?.[0] || makeReleaseFeature(releaseImages)?.[0]} + image={ + image?.[0] || + makeReleaseFeature(releaseImages)?.[0] || + hero?.[0] + } isFeature={canShowFeatured && page === 1 && i === 0} link={uri} pretitle={ diff --git a/components/dynamic/RelatedList/index.js b/components/dynamic/RelatedList/index.js index 4de8c283..b76f95a3 100644 --- a/components/dynamic/RelatedList/index.js +++ b/components/dynamic/RelatedList/index.js @@ -30,17 +30,19 @@ const RelatedList = ({ <> {entries?.length > 0 && ( - {entries.map(({ id, description, image, title, uri }, i) => ( - - ))} + {entries.map( + ({ id, description, hero, image, title, uri }, i) => ( + + ) + )} )} diff --git a/components/modal/RegisterModal/JoinForm/index.js b/components/modal/RegisterModal/JoinForm/index.js index 18600b1e..2c49a780 100644 --- a/components/modal/RegisterModal/JoinForm/index.js +++ b/components/modal/RegisterModal/JoinForm/index.js @@ -2,7 +2,7 @@ import PropTypes from "prop-types"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { Link as BaseLink } from "@rubin-epo/epo-react-lib"; -import { SSOButton } from "@/components/atomic"; +import { GoogleSSOButton, SSOButton } from "@/components/atomic"; import useAuthModal from "@/hooks/useAuthModal"; import { useAuthenticationContext } from "@/contexts/Authentication"; import AuthModal from "../../AuthModal"; @@ -14,7 +14,7 @@ export default function JoinForm({ onEmailSignup }) { const { pendingGroup, setPendingGroup, - goToGoogleSignIn, + // goToGoogleSignIn, goToFacebookSignIn, } = useAuthenticationContext(); @@ -47,9 +47,9 @@ export default function JoinForm({ onEmailSignup }) { */} - + {t("join.continue_with_google")} - + {/* */} - - {t("sign_in.continue_with_google")} - + + {t("join.continue_with_google")} + {/* {t("sign_in.continue_with_facebook")} */} diff --git a/components/templates/NewsPage/MediaAssets.js b/components/templates/NewsPage/MediaAssets.js index 705bc454..aec1289c 100644 --- a/components/templates/NewsPage/MediaAssets.js +++ b/components/templates/NewsPage/MediaAssets.js @@ -1,7 +1,7 @@ import PropTypes from "prop-types"; import Link from "next/link"; import { useTranslation } from "react-i18next"; -import { ResponsiveImage } from "@rubin-epo/epo-react-lib"; +import { ResponsiveImage, Figure } from "@rubin-epo/epo-react-lib"; import ReleaseAssets from "./ReleaseAssets"; import * as Styled from "./styles"; @@ -15,11 +15,12 @@ export default function MediaAssets({ if (!contentBlockAssets && !releaseImages && !releaseVideos) return null; if ( - contentBlockAssets?.length <= 0 && - releaseImages?.length <= 0 && - releaseVideos?.length <= 0 + contentBlockAssets?.length === 0 && + releaseImages?.length === 0 && + releaseVideos?.length === 0 ) return null; + return (

{t(`media`)}

@@ -27,7 +28,9 @@ export default function MediaAssets({ if (asset.image?.[0].url) { return ( - +
+ +
); } diff --git a/components/templates/NewsPage/NewsAside.js b/components/templates/NewsPage/NewsAside.js index 096e79de..e325c47a 100644 --- a/components/templates/NewsPage/NewsAside.js +++ b/components/templates/NewsPage/NewsAside.js @@ -1,30 +1,32 @@ import PropTypes from "prop-types"; import Link from "next/link"; -import { ResponsiveImage, IconComposer } from "@rubin-epo/epo-react-lib"; +import { + ResponsiveImage, + IconComposer, + Figure, +} from "@rubin-epo/epo-react-lib"; import Tags from "./Tags"; import MediaAssets from "./MediaAssets"; import * as Styled from "./styles"; export default function NewsAside({ - newsAssets, + manualAssets, contentBlockAssets, releaseImages, releaseVideos, tags, rootHomeLink, }) { - // This sets up the automatic media grabber -- if there are no manual media set - let manualMedia = false; // This adds the document icon from the designs, if there is a text-style link near the start. - let manualDoc = newsAssets.some( + let manualDoc = manualAssets.some( (a, i) => i < 4 && (a.textLink?.length > 0 || a.externalLink?.length > 0) ); return ( - {newsAssets?.length > 0 && ( + {manualAssets?.length > 0 && ( - {newsAssets.map((a, i) => { + {manualAssets.map((a, i) => { if (a.assetHeader) { return (

@@ -53,14 +55,14 @@ export default function NewsAside({ ); } else if (a.image?.length > 0) { - manualMedia = true; return ( - - - + +
+ +
+ ); } else if (a.galleryItem?.length > 0) { - manualMedia = true; if (a.galleryItem[0].uri) { return ( block.typeHandle === "image" + const manualAssets = []; + const heroBlock = + hero?.length > 0 + ? { + id: id + "hero", + typeHandle: "image", + image: hero, + caption: heroCaption, + } + : null; + const mediaContentBlocks = [...contentBlocksNews].filter( + (block) => block.typeHandle === "image" || block.typeHandle === "video" ); + newsAssets.forEach((a, i) => { + if (a.image?.length > 0) { + // If there are manually added news assets combine them with the content block media assets + mediaContentBlocks.push({ + id: id + i, + typeHandle: "image", + image: a.image, + caption: a.caption, + }); + } else { + manualAssets.push(a); + } + }); + // If there is a hero then combine it with the content block media assets + if (heroBlock) mediaContentBlocks.unshift(heroBlock); + + // Only show the aside if there are news assets const showAside = - newsAssets?.length > 0 || - imageContentBlocks?.length > 0 || + manualAssets?.length > 0 || + mediaContentBlocks?.length > 0 || releaseImages?.length > 0 || releaseVideos?.length > 0 || postTags?.length > 0; @@ -77,8 +104,8 @@ export default function NewsPage({ data }) { )} {showAside && ( { - const ssoModalUrl = { pathname: "/", query: { sso: true } }; - push(ssoModalUrl, undefined, { - shallow: true, - }); - // eslint-disable-next-line no-console - console.log("onSuccess", response, "onSuccess"); - authenticateWithGoogle({ idToken: response.tokenId }); - }, - onFailure: (error) => { - // eslint-disable-next-line no-console - console.log("onFailure", error, "onFailure"); - console.error(error); - }, - }); - useEffect(() => { // TODO: cancel promise if component unmounts first (async () => await maybeRefreshToken())(); @@ -442,8 +422,8 @@ export default function useAuthentication(data) { forgotPassword, setPassword, activateUser, - goToGoogleSignIn, goToFacebookSignIn, + authenticateWithGoogle, fetchUserData, requestAccountDeletion, }; diff --git a/lib/api/fragments/content-blocks.js b/lib/api/fragments/content-blocks.js index d3fa7216..2fe28d0a 100644 --- a/lib/api/fragments/content-blocks.js +++ b/lib/api/fragments/content-blocks.js @@ -342,6 +342,11 @@ export const relatedContentFragment = ` uri ... on pages_pages_Entry { description + hero { + ...on heroes_Asset { + ${getImageFields("crop", 900, 550)} + } + } image: featuredImage { ...on contentImages_Asset { ${getImageFields("crop", 900, 550)} @@ -350,11 +355,16 @@ export const relatedContentFragment = ` } ...on news_post_Entry { description: teaser - image: hero { + hero { ...on heroes_Asset { ${getImageFields("crop", 900, 550)} } } + image: featuredImage { + ...on contentImages_Asset { + ${getImageFields("crop", 900, 550)} + } + } } ...on investigations_investigation_Entry { uri @@ -711,8 +721,8 @@ export const relatedContentNewsFragment = ` ...on news_post_Entry { date description: teaser - image: hero { - ...on heroes_Asset { + image: featuredImage { + ...on contentImages_Asset { ${getImageFields("crop", 900, 550)} } } diff --git a/lib/api/fragments/event.js b/lib/api/fragments/event.js index 09c383ae..0d67b025 100644 --- a/lib/api/fragments/event.js +++ b/lib/api/fragments/event.js @@ -14,11 +14,16 @@ export const eventFragment = ` startDate endDate: date description - image: hero { + hero { ...on heroes_Asset { ${getImageFields("crop", 400, 400)} } } + image: featuredImage { + ...on contentImages_Asset { + ${getImageFields("crop", 400, 400)} + } + } eventType { id title @@ -48,8 +53,8 @@ export const eventFragmentFull = ` ${getImageFields("crop", 1920, 1067)} } } - featuredImage: hero { - ...on heroes_Asset { + featuredImage { + ...on contentImages_Asset { ${getImageFields("crop", 800, 800)} } } diff --git a/lib/api/fragments/news-post.js b/lib/api/fragments/news-post.js index c8039dc7..c4a75c2f 100644 --- a/lib/api/fragments/news-post.js +++ b/lib/api/fragments/news-post.js @@ -11,11 +11,16 @@ export const newsPostFragment = ` dateCreated description: teaser pressReleaseId - image: hero { + hero { ...on heroes_Asset { ${getImageFields("crop", 900, 550)} } } + image: featuredImage { + ...on contentImages_Asset { + ${getImageFields("crop", 900, 550)} + } + } postType { id title @@ -58,8 +63,8 @@ export const newsPostFragmentFull = ` } } heroCaption: captionRichText - featuredImage: hero { - ...on heroes_Asset { + featuredImage { + ...on contentImages_Asset { ${getImageFields("crop", 800, 800)} } } @@ -83,6 +88,7 @@ export const newsPostFragmentFull = ` ${getImageFields("crop", 900, 550)} } } + caption } ... on newsAssets_galleryItem_BlockType { galleryItem { diff --git a/package.json b/package.json index 9a686b8b..75f7166a 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "@headlessui/react": "^1.6.6", "@influxdata/influxdb-client": "^1.33.2", "@popperjs/core": "^2.11.5", + "@react-oauth/google": "^0.12.1", "@rubin-epo/epo-react-lib": "^1.2.9", "add": "^2.0.6", "classnames": "^2.3.1", @@ -68,6 +69,7 @@ "feed": "^4.2.2", "focus-trap": "^7.0.0", "focus-visible": "^5.1.0", + "google-auth-library": "^9.6.3", "graphql": "^16.5.0", "graphql-request": "^5.0.0", "hoist-non-react-statics": "^3.3.2", @@ -79,7 +81,6 @@ "npm-run-all": "^4.1.5", "react": "18.2.0", "react-dom": "18.2.0", - "react-google-login": "^5.2.2", "react-hook-form": "^7.33.1", "react-i18next": "^12.0.0", "react-is": "^18.2.0", diff --git a/pages/[[...uriSegments]].js b/pages/[[...uriSegments]].js index e49badeb..a23ce0c6 100644 --- a/pages/[[...uriSegments]].js +++ b/pages/[[...uriSegments]].js @@ -1,4 +1,5 @@ import PropTypes from "prop-types"; +import { GoogleOAuthProvider } from "@react-oauth/google"; import { getGlobalData } from "@/api/global"; import { getAllEntries } from "@/api/entries"; import { getEntryDataByUri, getEntrySectionTypeByUri } from "@/api/entry"; @@ -57,6 +58,7 @@ function logNextDir() { }); } +const GOOGLE_APP_ID = process.env.NEXT_PUBLIC_GOOGLE_APP_ID; export default function Page({ section, globalData, ...entryProps }) { globalData.localeInfo.locale === "es" ? updateI18n("es") : updateI18n("en"); @@ -75,9 +77,11 @@ export default function Page({ section, globalData, ...entryProps }) { const Template = sectionMap[section] || PageTemplate; return ( - -