Skip to content

Commit

Permalink
feat: bulletin (#3251)
Browse files Browse the repository at this point in the history
* feat: bulletin added

* chore: deeplink fix

* chore: schema updates

* chore: linking and actions of bulletins

* chore: move bulletins query to home screen for refetches
  • Loading branch information
sandipndev authored May 14, 2024
1 parent bba2970 commit fe8958a
Show file tree
Hide file tree
Showing 9 changed files with 302 additions and 44 deletions.
72 changes: 72 additions & 0 deletions app/components/notifications/bulletins.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from "react"
import { Linking } from "react-native"

import { useNotifications } from "."
import { NotificationCardUI } from "./notification-card-ui"
import {
BulletinsDocument,
BulletinsQuery,
useStatefulNotificationAcknowledgeMutation,
} from "@app/graphql/generated"
import { BLINK_DEEP_LINK_PREFIX } from "@app/config"

type Props = {
loading: boolean
bulletins: BulletinsQuery | undefined
}

export const BulletinsCard: React.FC<Props> = ({ loading, bulletins }) => {
const { cardInfo } = useNotifications()

const [ack, { loading: ackLoading }] = useStatefulNotificationAcknowledgeMutation({
refetchQueries: [BulletinsDocument],
})

if (loading) return null

if (
bulletins &&
bulletins.me?.unacknowledgedStatefulNotificationsWithBulletinEnabled?.edges &&
bulletins.me?.unacknowledgedStatefulNotificationsWithBulletinEnabled?.edges.length > 0
) {
return (
<>
{bulletins.me?.unacknowledgedStatefulNotificationsWithBulletinEnabled?.edges.map(
({ node: bulletin }) => (
<NotificationCardUI
key={bulletin.id}
title={bulletin.title}
text={bulletin.body}
action={async () => {
ack({ variables: { input: { notificationId: bulletin.id } } })
if (bulletin.action?.__typename === "OpenDeepLinkAction")
Linking.openURL(BLINK_DEEP_LINK_PREFIX + bulletin.action.deepLink)
else if (bulletin.action?.__typename === "OpenExternalLinkAction")
Linking.openURL(bulletin.action.url)
}}
dismissAction={() =>
ack({ variables: { input: { notificationId: bulletin.id } } })
}
loading={ackLoading}
/>
),
)}
</>
)
}

if (!cardInfo) {
return null
}

return (
<NotificationCardUI
title={cardInfo.title}
text={cardInfo.text}
icon={cardInfo.icon}
action={cardInfo.action}
loading={cardInfo.loading}
dismissAction={cardInfo.dismissAction}
/>
)
}
20 changes: 0 additions & 20 deletions app/components/notifications/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { Text, makeStyles, useTheme } from "@rneui/themed"

import { GaloyIcon, IconNamesType } from "../atomic/galoy-icon"
import CustomModal from "../custom-modal/custom-modal"
import { NotificationCardUI } from "./notification-card-ui"

type NotifyModalArgs = {
title: string
Expand Down Expand Up @@ -211,23 +210,4 @@ const useStyles = makeStyles(() => ({
},
}))

export const NotificationCard = () => {
const { cardInfo } = useNotifications()

if (!cardInfo) {
return null
}

return (
<NotificationCardUI
title={cardInfo.title}
text={cardInfo.text}
icon={cardInfo.icon}
action={cardInfo.action}
loading={cardInfo.loading}
dismissAction={cardInfo.dismissAction}
/>
)
}

export const useNotifications = () => useContext(NotificationModalContext)
2 changes: 2 additions & 0 deletions app/config/appinfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ export const getInviteLink = (_username: string | null | undefined) => {
const username = _username ? `/${_username}` : ""
return `https://get.blink.sv${username}`
}

export const BLINK_DEEP_LINK_PREFIX = "blink:/"
59 changes: 56 additions & 3 deletions app/graphql/generated.gql
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,49 @@ mutation userUpdateUsername($input: UserUpdateUsernameInput!) {
}
}

query Bulletins($first: Int!, $after: String) {
me {
unacknowledgedStatefulNotificationsWithBulletinEnabled(
first: $first
after: $after
) {
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
__typename
}
edges {
node {
id
title
body
createdAt
acknowledgedAt
bulletinEnabled
action {
... on OpenDeepLinkAction {
deepLink
__typename
}
... on OpenExternalLinkAction {
url
__typename
}
__typename
}
__typename
}
cursor
__typename
}
__typename
}
__typename
}
}

query Circles {
me {
id
Expand Down Expand Up @@ -859,15 +902,25 @@ query SettingsScreen {

query StatefulNotifications($after: String) {
me {
statefulNotifications(first: 10, after: $after) {
statefulNotificationsWithoutBulletinEnabled(first: 10, after: $after) {
nodes {
id
title
body
deepLink
createdAt
acknowledgedAt
bulletinEnabled
action {
... on OpenDeepLinkAction {
deepLink
__typename
}
... on OpenExternalLinkAction {
url
__typename
}
__typename
}
__typename
}
pageInfo {
Expand All @@ -886,7 +939,7 @@ query StatefulNotifications($after: String) {
query UnacknowledgedNotificationCount {
me {
id
unacknowledgedStatefulNotificationsCount
unacknowledgedStatefulNotificationsWithoutBulletinEnabledCount
__typename
}
}
Expand Down
107 changes: 100 additions & 7 deletions app/graphql/generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2173,11 +2173,12 @@ export type User = {
/** Phone number with international calling code. */
readonly phone?: Maybe<Scalars['Phone']['output']>;
readonly statefulNotifications: StatefulNotificationConnection;
readonly statefulNotificationsWithoutBulletinEnabled: StatefulNotificationConnection;
readonly supportChat: ReadonlyArray<SupportMessage>;
/** Whether TOTP is enabled for this user. */
readonly totpEnabled: Scalars['Boolean']['output'];
readonly unacknowledgedStatefulNotificationsCount: Scalars['Int']['output'];
readonly unacknowledgedStatefulNotificationsWithBulletinEnabled: StatefulNotificationConnection;
readonly unacknowledgedStatefulNotificationsWithoutBulletinEnabledCount: Scalars['Int']['output'];
/**
* Optional immutable user friendly identifier.
* @deprecated will be moved to @Handle in Account and Wallet
Expand All @@ -2197,6 +2198,12 @@ export type UserStatefulNotificationsArgs = {
};


export type UserStatefulNotificationsWithoutBulletinEnabledArgs = {
after?: InputMaybe<Scalars['String']['input']>;
first: Scalars['Int']['input'];
};


export type UserUnacknowledgedStatefulNotificationsWithBulletinEnabledArgs = {
after?: InputMaybe<Scalars['String']['input']>;
first: Scalars['Int']['input'];
Expand Down Expand Up @@ -2677,6 +2684,14 @@ export type HomeUnauthedQueryVariables = Exact<{ [key: string]: never; }>;

export type HomeUnauthedQuery = { readonly __typename: 'Query', readonly globals?: { readonly __typename: 'Globals', readonly network: Network } | null, readonly currencyList: ReadonlyArray<{ readonly __typename: 'Currency', readonly id: string, readonly flag: string, readonly name: string, readonly symbol: string, readonly fractionDigits: number }> };

export type BulletinsQueryVariables = Exact<{
first: Scalars['Int']['input'];
after?: InputMaybe<Scalars['String']['input']>;
}>;


export type BulletinsQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly unacknowledgedStatefulNotificationsWithBulletinEnabled: { readonly __typename: 'StatefulNotificationConnection', readonly pageInfo: { readonly __typename: 'PageInfo', readonly endCursor?: string | null, readonly hasNextPage: boolean, readonly hasPreviousPage: boolean, readonly startCursor?: string | null }, readonly edges: ReadonlyArray<{ readonly __typename: 'StatefulNotificationEdge', readonly cursor: string, readonly node: { readonly __typename: 'StatefulNotification', readonly id: string, readonly title: string, readonly body: string, readonly createdAt: number, readonly acknowledgedAt?: number | null, readonly bulletinEnabled: boolean, readonly action?: { readonly __typename: 'OpenDeepLinkAction', readonly deepLink: string } | { readonly __typename: 'OpenExternalLinkAction', readonly url: string } | null } }> } } | null };

export type BusinessMapMarkersQueryVariables = Exact<{ [key: string]: never; }>;


Expand All @@ -2687,7 +2702,7 @@ export type StatefulNotificationsQueryVariables = Exact<{
}>;


export type StatefulNotificationsQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly statefulNotifications: { readonly __typename: 'StatefulNotificationConnection', readonly nodes: ReadonlyArray<{ readonly __typename: 'StatefulNotification', readonly id: string, readonly title: string, readonly body: string, readonly deepLink?: string | null, readonly createdAt: number, readonly acknowledgedAt?: number | null, readonly bulletinEnabled: boolean }>, readonly pageInfo: { readonly __typename: 'PageInfo', readonly endCursor?: string | null, readonly hasNextPage: boolean, readonly hasPreviousPage: boolean, readonly startCursor?: string | null } } } | null };
export type StatefulNotificationsQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly statefulNotificationsWithoutBulletinEnabled: { readonly __typename: 'StatefulNotificationConnection', readonly nodes: ReadonlyArray<{ readonly __typename: 'StatefulNotification', readonly id: string, readonly title: string, readonly body: string, readonly createdAt: number, readonly acknowledgedAt?: number | null, readonly bulletinEnabled: boolean, readonly action?: { readonly __typename: 'OpenDeepLinkAction', readonly deepLink: string } | { readonly __typename: 'OpenExternalLinkAction', readonly url: string } | null }>, readonly pageInfo: { readonly __typename: 'PageInfo', readonly endCursor?: string | null, readonly hasNextPage: boolean, readonly hasPreviousPage: boolean, readonly startCursor?: string | null } } } | null };

export type StatefulNotificationAcknowledgeMutationVariables = Exact<{
input: StatefulNotificationAcknowledgeInput;
Expand Down Expand Up @@ -3062,7 +3077,7 @@ export type AccountDisableNotificationCategoryMutation = { readonly __typename:
export type UnacknowledgedNotificationCountQueryVariables = Exact<{ [key: string]: never; }>;


export type UnacknowledgedNotificationCountQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly unacknowledgedStatefulNotificationsCount: number } | null };
export type UnacknowledgedNotificationCountQuery = { readonly __typename: 'Query', readonly me?: { readonly __typename: 'User', readonly id: string, readonly unacknowledgedStatefulNotificationsWithoutBulletinEnabledCount: number } | null };

export type SettingsScreenQueryVariables = Exact<{ [key: string]: never; }>;

Expand Down Expand Up @@ -4761,6 +4776,76 @@ export type HomeUnauthedQueryHookResult = ReturnType<typeof useHomeUnauthedQuery
export type HomeUnauthedLazyQueryHookResult = ReturnType<typeof useHomeUnauthedLazyQuery>;
export type HomeUnauthedSuspenseQueryHookResult = ReturnType<typeof useHomeUnauthedSuspenseQuery>;
export type HomeUnauthedQueryResult = Apollo.QueryResult<HomeUnauthedQuery, HomeUnauthedQueryVariables>;
export const BulletinsDocument = gql`
query Bulletins($first: Int!, $after: String) {
me {
unacknowledgedStatefulNotificationsWithBulletinEnabled(
first: $first
after: $after
) {
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
edges {
node {
id
title
body
createdAt
acknowledgedAt
bulletinEnabled
action {
... on OpenDeepLinkAction {
deepLink
}
... on OpenExternalLinkAction {
url
}
}
}
cursor
}
}
}
}
`;

/**
* __useBulletinsQuery__
*
* To run a query within a React component, call `useBulletinsQuery` and pass it any options that fit your needs.
* When your component renders, `useBulletinsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useBulletinsQuery({
* variables: {
* first: // value for 'first'
* after: // value for 'after'
* },
* });
*/
export function useBulletinsQuery(baseOptions: Apollo.QueryHookOptions<BulletinsQuery, BulletinsQueryVariables> & ({ variables: BulletinsQueryVariables; skip?: boolean; } | { skip: boolean; }) ) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<BulletinsQuery, BulletinsQueryVariables>(BulletinsDocument, options);
}
export function useBulletinsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<BulletinsQuery, BulletinsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<BulletinsQuery, BulletinsQueryVariables>(BulletinsDocument, options);
}
export function useBulletinsSuspenseQuery(baseOptions?: Apollo.SuspenseQueryHookOptions<BulletinsQuery, BulletinsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<BulletinsQuery, BulletinsQueryVariables>(BulletinsDocument, options);
}
export type BulletinsQueryHookResult = ReturnType<typeof useBulletinsQuery>;
export type BulletinsLazyQueryHookResult = ReturnType<typeof useBulletinsLazyQuery>;
export type BulletinsSuspenseQueryHookResult = ReturnType<typeof useBulletinsSuspenseQuery>;
export type BulletinsQueryResult = Apollo.QueryResult<BulletinsQuery, BulletinsQueryVariables>;
export const BusinessMapMarkersDocument = gql`
query businessMapMarkers {
businessMapMarkers {
Expand Down Expand Up @@ -4810,15 +4895,22 @@ export type BusinessMapMarkersQueryResult = Apollo.QueryResult<BusinessMapMarker
export const StatefulNotificationsDocument = gql`
query StatefulNotifications($after: String) {
me {
statefulNotifications(first: 10, after: $after) {
statefulNotificationsWithoutBulletinEnabled(first: 10, after: $after) {
nodes {
id
title
body
deepLink
createdAt
acknowledgedAt
bulletinEnabled
action {
... on OpenDeepLinkAction {
deepLink
}
... on OpenExternalLinkAction {
url
}
}
}
pageInfo {
endCursor
Expand Down Expand Up @@ -7255,7 +7347,7 @@ export const UnacknowledgedNotificationCountDocument = gql`
query UnacknowledgedNotificationCount {
me {
id
unacknowledgedStatefulNotificationsCount
unacknowledgedStatefulNotificationsWithoutBulletinEnabledCount
}
}
`;
Expand Down Expand Up @@ -9404,10 +9496,11 @@ export type UserResolvers<ContextType = any, ParentType extends ResolversParentT
language?: Resolver<ResolversTypes['Language'], ParentType, ContextType>;
phone?: Resolver<Maybe<ResolversTypes['Phone']>, ParentType, ContextType>;
statefulNotifications?: Resolver<ResolversTypes['StatefulNotificationConnection'], ParentType, ContextType, RequireFields<UserStatefulNotificationsArgs, 'first'>>;
statefulNotificationsWithoutBulletinEnabled?: Resolver<ResolversTypes['StatefulNotificationConnection'], ParentType, ContextType, RequireFields<UserStatefulNotificationsWithoutBulletinEnabledArgs, 'first'>>;
supportChat?: Resolver<ReadonlyArray<ResolversTypes['SupportMessage']>, ParentType, ContextType>;
totpEnabled?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
unacknowledgedStatefulNotificationsCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
unacknowledgedStatefulNotificationsWithBulletinEnabled?: Resolver<ResolversTypes['StatefulNotificationConnection'], ParentType, ContextType, RequireFields<UserUnacknowledgedStatefulNotificationsWithBulletinEnabledArgs, 'first'>>;
unacknowledgedStatefulNotificationsWithoutBulletinEnabledCount?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
username?: Resolver<Maybe<ResolversTypes['Username']>, ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
Expand Down
Loading

0 comments on commit fe8958a

Please sign in to comment.