From 2ba969ebab370afbc8d9cdb423afb23cb8745c72 Mon Sep 17 00:00:00 2001 From: rleed <101502594+rleed@users.noreply.github.com> Date: Mon, 11 Sep 2023 17:29:45 -0400 Subject: [PATCH] Add new visitor welcome banner (#418) * add new visitor welcome banner * show dismissible banner on first-time login * add mutation to hide welcome banner * Update components/banners.js Co-authored-by: ekzyis <27162016+ekzyis@users.noreply.github.com> * fix error handling * simplifications and other review suggestions * cleanup * restore selective display logic * remove unnecessary query arguments * cleanup a bit more * don't show welcome banner to existing stackers --------- Co-authored-by: rleed Co-authored-by: ekzyis <27162016+ekzyis@users.noreply.github.com> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: keyan --- api/resolvers/user.js | 8 +++ api/typeDefs/user.js | 2 + components/banners.js | 67 +++++++++++++++++++ components/banners.module.css | 7 ++ fragments/users.js | 8 +++ pages/~/index.js | 2 + .../migration.sql | 2 + prisma/schema.prisma | 1 + 8 files changed, 97 insertions(+) create mode 100644 components/banners.js create mode 100644 components/banners.module.css create mode 100644 prisma/migrations/20230829205147_welcome_banner/migration.sql diff --git a/api/resolvers/user.js b/api/resolvers/user.js index a6742244..112e09cb 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -578,6 +578,14 @@ export default { await models.userSubscription.create({ data }) } return { id } + }, + hideWelcomeBanner: async (parent, data, { me, models }) => { + if (!me) { + throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } }) + } + + await models.user.update({ where: { id: me.id }, data: { hideWelcomeBanner: true } }) + return true } }, diff --git a/api/typeDefs/user.js b/api/typeDefs/user.js index ebb398eb..eaaf47bc 100644 --- a/api/typeDefs/user.js +++ b/api/typeDefs/user.js @@ -31,6 +31,7 @@ export default gql` unlinkAuth(authType: String!): AuthMethods! linkUnverifiedEmail(email: String!): Boolean subscribeUser(id: ID): User + hideWelcomeBanner: Boolean } type AuthMethods { @@ -81,6 +82,7 @@ export default gql` hideFromTopUsers: Boolean! hideCowboyHat: Boolean! hideBookmarks: Boolean! + hideWelcomeBanner: Boolean! clickToLoadImg: Boolean! wildWestMode: Boolean! greeterMode: Boolean! diff --git a/components/banners.js b/components/banners.js new file mode 100644 index 00000000..9efeaed4 --- /dev/null +++ b/components/banners.js @@ -0,0 +1,67 @@ +import Alert from 'react-bootstrap/Alert' +import styles from './banners.module.css' +import { useEffect, useState } from 'react' +import { useMe } from '../components/me' +import { useMutation } from '@apollo/client' +import { WELCOME_BANNER_MUTATION } from '../fragments/users' +import { useToast } from '../components/toast' + +export default function WelcomeBanner () { + const me = useMe() + const toaster = useToast() + const [hidden, setHidden] = useState(true) + const handleClose = async () => { + window.localStorage.setItem('hideWelcomeBanner', true) + setHidden(true) + if (me) { + try { + await hideWelcomeBanner() + } catch (err) { + console.log(err) + toaster.danger('mutation failed') + } + } + } + const [hideWelcomeBanner] = useMutation(WELCOME_BANNER_MUTATION, { + update (cache) { + cache.modify({ + id: `User:${me.id}`, + fields: { + hideWelcomeBanner () { + return true + } + } + }) + } + }) + useEffect(() => { + setHidden(me?.hideWelcomeBanner || (!me && window.localStorage.getItem('hideWelcomeBanner'))) + }, [me?.hideWelcomeBanner]) + + if (hidden) return + + return ( + + + 👋 Welcome to Stacker News! + +

+ To get started, check out our{' '} + FAQs or{' '} + content guidelines, or go ahead and{' '} + { + me + ? ( + make a post + ) + : ( + <> + signup or create an{' '} + anonymous post + + ) + }. +

+
+ ) +} diff --git a/components/banners.module.css b/components/banners.module.css new file mode 100644 index 00000000..e18fdd73 --- /dev/null +++ b/components/banners.module.css @@ -0,0 +1,7 @@ +.banner { + margin-top: 1em; +} + +.banner p:last-of-type { + margin-bottom: 0; +} diff --git a/fragments/users.js b/fragments/users.js index 30709aa5..bbe2fd9c 100644 --- a/fragments/users.js +++ b/fragments/users.js @@ -33,6 +33,7 @@ export const ME = gql` wildWestMode greeterMode lastCheckedJobs + hideWelcomeBanner } }` @@ -108,6 +109,13 @@ gql` } ` +export const WELCOME_BANNER_MUTATION = +gql` + mutation hideWelcomeBanner { + hideWelcomeBanner + } +` + export const USER_SEARCH = gql` query searchUsers($q: String!, $limit: Int, $similarity: Float) { diff --git a/pages/~/index.js b/pages/~/index.js index a3bb8c0d..a52bacf2 100644 --- a/pages/~/index.js +++ b/pages/~/index.js @@ -4,6 +4,7 @@ import Items from '../../components/items' import Layout from '../../components/layout' import { SUB_ITEMS } from '../../fragments/subs' import Snl from '../../components/snl' +import WelcomeBanner from '../../components/banners' export const getServerSideProps = getGetServerSideProps({ query: SUB_ITEMS, @@ -17,6 +18,7 @@ export default function Sub ({ ssrData }) { return ( + ) diff --git a/prisma/migrations/20230829205147_welcome_banner/migration.sql b/prisma/migrations/20230829205147_welcome_banner/migration.sql new file mode 100644 index 00000000..f436ec75 --- /dev/null +++ b/prisma/migrations/20230829205147_welcome_banner/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "users" ADD COLUMN "hideWelcomeBanner" BOOLEAN NOT NULL DEFAULT true, ALTER COLUMN "hideWelcomeBanner" SET DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8a2e2a90..ed216b32 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -87,6 +87,7 @@ model User { hideBookmarks Boolean @default(false) followers UserSubscription[] @relation("follower") followees UserSubscription[] @relation("followee") + hideWelcomeBanner Boolean @default(false) @@index([createdAt], map: "users.created_at_index") @@index([inviteId], map: "users.inviteId_index")