get feature list on web

This commit is contained in:
Hongbo Wu
2024-03-28 15:27:37 +08:00
parent cc2fe04568
commit f677f1167f
10 changed files with 62 additions and 34 deletions

View File

@ -3696,7 +3696,7 @@ export enum UploadImportFileType {
export type User = { export type User = {
__typename?: 'User'; __typename?: 'User';
email?: Maybe<Scalars['String']>; email?: Maybe<Scalars['String']>;
featureList?: Maybe<Array<Maybe<Feature>>>; featureList?: Maybe<Array<Feature>>;
features?: Maybe<Array<Maybe<Scalars['String']>>>; features?: Maybe<Array<Maybe<Scalars['String']>>>;
followersCount?: Maybe<Scalars['Int']>; followersCount?: Maybe<Scalars['Int']>;
friendsCount?: Maybe<Scalars['Int']>; friendsCount?: Maybe<Scalars['Int']>;
@ -7140,7 +7140,7 @@ export type UploadImportFileSuccessResolvers<ContextType = ResolverContext, Pare
export type UserResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User']> = { export type UserResolvers<ContextType = ResolverContext, ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User']> = {
email?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>; email?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
featureList?: Resolver<Maybe<Array<Maybe<ResolversTypes['Feature']>>>, ParentType, ContextType>; featureList?: Resolver<Maybe<Array<ResolversTypes['Feature']>>, ParentType, ContextType>;
features?: Resolver<Maybe<Array<Maybe<ResolversTypes['String']>>>, ParentType, ContextType>; features?: Resolver<Maybe<Array<Maybe<ResolversTypes['String']>>>, ParentType, ContextType>;
followersCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>; followersCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
friendsCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>; friendsCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;

View File

@ -2984,7 +2984,7 @@ enum UploadImportFileType {
type User { type User {
email: String email: String
featureList: [Feature] featureList: [Feature!]
features: [String] features: [String]
followersCount: Int followersCount: Int
friendsCount: Int friendsCount: Int

View File

@ -368,6 +368,17 @@ export const functionResolvers = {
} }
return undefined return undefined
}, },
async featureList(
_: User,
__: Record<string, unknown>,
ctx: WithDataSourcesContext
) {
if (!ctx.claims?.uid) {
return undefined
}
return findUserFeatures(ctx.claims.uid)
},
async features( async features(
user: User, user: User,
__: Record<string, unknown>, __: Record<string, unknown>,
@ -376,18 +387,8 @@ export const functionResolvers = {
if (!ctx.claims?.uid) { if (!ctx.claims?.uid) {
return undefined return undefined
} }
const userFeatures = await findUserFeatures(ctx.claims.uid)
return userFeatures.map((feature) => feature.name) return (await findUserFeatures(ctx.claims.uid)).map((f) => f.name)
},
async featureList(
_: User,
__: Record<string, unknown>,
ctx: WithDataSourcesContext
) {
if (!ctx.uid) {
return undefined
}
return findUserFeatures(ctx.uid)
}, },
}, },
Article: { Article: {

View File

@ -89,7 +89,7 @@ const schema = gql`
source: String source: String
intercomHash: String intercomHash: String
features: [String] features: [String]
featureList: [Feature] featureList: [Feature!]
} }
type Profile { type Profile {

View File

@ -123,10 +123,8 @@ export const signFeatureToken = (
} }
export const findUserFeatures = async (userId: string) => { export const findUserFeatures = async (userId: string) => {
return getRepository(Feature).find({ return getRepository(Feature).findBy({
where: { user: { id: userId },
user: { id: userId },
},
}) })
} }

View File

@ -7,5 +7,10 @@ export const userHasFeature = (
if (!user) { if (!user) {
return false return false
} }
return user.features.includes(feature) return user.featureList.some(
(f) =>
f.name === feature &&
f.grantedAt &&
(!f.expiresAt || new Date(f.expiresAt) > new Date())
)
} }

View File

@ -0,0 +1,21 @@
import { gql } from 'graphql-request'
export const featureFragment = gql`
fragment FeatureFields on Feature {
id
name
createdAt
updatedAt
grantedAt
expiresAt
}
`
export interface Feature {
id: string
name: string
createdAt: Date
updatedAt?: Date
grantedAt?: Date
expiresAt?: Date
}

View File

@ -1,16 +1,17 @@
import { gql } from 'graphql-request' import { gql } from 'graphql-request'
import { Feature, featureFragment } from '../fragments/featureFragment'
import { gqlFetcher } from '../networkHelpers' import { gqlFetcher } from '../networkHelpers'
export interface OptInFeatureInput { export interface OptInFeatureInput {
name: string name: string
} }
export interface OptInFeatureSuccess { export interface OptInFeatureResponse {
feature: { id: string } feature?: Feature
} }
interface Response { interface Response {
optInFeature: OptInFeatureSuccess optInFeature: OptInFeatureResponse
} }
export async function optInFeature( export async function optInFeature(
@ -21,7 +22,7 @@ export async function optInFeature(
optInFeature(input: $input) { optInFeature(input: $input) {
... on OptInFeatureSuccess { ... on OptInFeatureSuccess {
feature { feature {
id ...FeatureFields
} }
} }
... on OptInFeatureError { ... on OptInFeatureError {
@ -29,17 +30,14 @@ export async function optInFeature(
} }
} }
} }
${featureFragment}
` `
try { try {
const data = await gqlFetcher(mutation, { const data = await gqlFetcher(mutation, {
input, input,
}) })
const output = data as Response | undefined const output = data as Response | undefined
if ( if (!output || !output.optInFeature.feature) {
!output ||
!output.optInFeature ||
'errorCodes' in output?.optInFeature
) {
return false return false
} }
return true return true

View File

@ -1,5 +1,6 @@
import { gql } from 'graphql-request' import { gql } from 'graphql-request'
import useSWR from 'swr' import useSWR from 'swr'
import { Feature, featureFragment } from '../fragments/featureFragment'
import { publicGqlFetcher } from '../networkHelpers' import { publicGqlFetcher } from '../networkHelpers'
type ViewerQueryResponse = { type ViewerQueryResponse = {
@ -22,6 +23,7 @@ export type UserBasicData = {
source: string source: string
intercomHash: string intercomHash: string
features: string[] features: string[]
featureList: Feature[]
} }
export type UserProfile = { export type UserProfile = {
@ -48,8 +50,12 @@ export function useGetViewerQuery(): ViewerQueryResponse {
source source
intercomHash intercomHash
features features
featureList {
...FeatureFields
}
} }
} }
${featureFragment}
` `
const { data, error, mutate } = useSWR(query, publicGqlFetcher) const { data, error, mutate } = useSWR(query, publicGqlFetcher)

View File

@ -6,6 +6,7 @@ import { HStack, VStack } from '../../../components/elements/LayoutPrimitives'
import { StyledText } from '../../../components/elements/StyledText' import { StyledText } from '../../../components/elements/StyledText'
import { SettingsLayout } from '../../../components/templates/SettingsLayout' import { SettingsLayout } from '../../../components/templates/SettingsLayout'
import { styled } from '../../../components/tokens/stitches.config' import { styled } from '../../../components/tokens/stitches.config'
import { userHasFeature } from '../../../lib/featureFlag'
import { optInFeature } from '../../../lib/networking/mutations/optIntoFeatureMutation' import { optInFeature } from '../../../lib/networking/mutations/optIntoFeatureMutation'
import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery' import { useGetViewerQuery } from '../../../lib/networking/queries/useGetViewerQuery'
import { applyStoredTheme } from '../../../lib/themeUpdater' import { applyStoredTheme } from '../../../lib/themeUpdater'
@ -43,13 +44,11 @@ export default function Account(): JSX.Element {
) )
const hasYouTube = useMemo(() => { const hasYouTube = useMemo(() => {
return ( return userHasFeature(viewerData?.me, 'youtube-transcripts')
(viewerData?.me?.features.indexOf('youtube-transcripts') ?? -1) !== -1
)
}, [viewerData]) }, [viewerData])
const hasNotion = useMemo(() => { const hasNotion = useMemo(() => {
return (viewerData?.me?.features.indexOf('notion') ?? -1) !== -1 return userHasFeature(viewerData?.me, 'notion')
}, [viewerData]) }, [viewerData])
applyStoredTheme() applyStoredTheme()