From b738afe9cd2c22cd2a8c1ea32452d48ec76df175 Mon Sep 17 00:00:00 2001 From: Bram Borggreve Date: Sun, 7 Apr 2024 09:39:49 +0100 Subject: [PATCH] feat: add social icons to business visa --- .../src/assets/images/brand/brand-discord.png | Bin 0 -> 6139 bytes .../src/assets/images/brand/brand-github.png | Bin 0 -> 5117 bytes .../src/assets/images/brand/brand-google.png | Bin 0 -> 6303 bytes apps/api/src/assets/images/brand/brand-x.png | Bin 0 -> 2809 bytes .../src/lib/api-metadata-image.service.ts | 28 ++++++-- .../src/lib/api-metadata.service.ts | 47 ++++++++----- .../generate-business-visa-image.tsx | 65 ++++++++++++++++-- .../src/lib/api-metadata.controller.ts | 5 -- .../src/lib/user-asset-detail-feature.tsx | 12 +++- .../community/ui/src/lib/minter-ui-assets.tsx | 8 ++- 10 files changed, 126 insertions(+), 39 deletions(-) create mode 100644 apps/api/src/assets/images/brand/brand-discord.png create mode 100644 apps/api/src/assets/images/brand/brand-github.png create mode 100644 apps/api/src/assets/images/brand/brand-google.png create mode 100644 apps/api/src/assets/images/brand/brand-x.png diff --git a/apps/api/src/assets/images/brand/brand-discord.png b/apps/api/src/assets/images/brand/brand-discord.png new file mode 100644 index 0000000000000000000000000000000000000000..9130ad01d203126c59e5ca28716e187ea316d5ce GIT binary patch literal 6139 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 literal 0 HcmV?d00001 diff --git a/apps/api/src/assets/images/brand/brand-github.png b/apps/api/src/assets/images/brand/brand-github.png new file mode 100644 index 0000000000000000000000000000000000000000..a84152334cb9a868af49cf5fa04a0edcb73033f9 GIT binary patch literal 5117 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 & { profile: { username: string } } type UserWithIdentities = User & { identities: IdentityWithProfile[] } @@ -199,14 +199,23 @@ export class ApiMetadataImageService implements OnModuleInit { const poweredBy = await this.renderPoweredBy() const icon = await this.renderIcon(community) const logo = await this.renderLogo(community) - - if (!icon || !logo) { - throw new Error('Missing icon or logo. Expected in brand folder') + const brands = await this.getBrands() + if (!icon || !logo || !brands) { + throw new Error('Missing brands, icon or logo. Expected in brand folder') } + const discordIdentity = user.identities.find((i) => i.provider === IdentityProvider.Discord) const githubIdentity = user.identities.find((i) => i.provider === IdentityProvider.GitHub) + const googleIdentity = user.identities.find((i) => i.provider === IdentityProvider.Google) const twitterIdentity = user.identities.find((i) => i.provider === IdentityProvider.Twitter) + const socials: BusinessVisaSocials = { + discord: discordIdentity ? discordIdentity?.profile?.username : '', + github: githubIdentity ? githubIdentity?.profile?.username : '', + google: googleIdentity ? googleIdentity?.profile?.username : '', + x: twitterIdentity ? twitterIdentity?.profile?.username : '', + } + const templateData = { left: { username: user.username, @@ -234,10 +243,21 @@ export class ApiMetadataImageService implements OnModuleInit { poweredBy, icon, logo, + brands, + socials, }) const image: Buffer = (await imageBuilder.build({ format: 'png' })) as Buffer return image } + + private async getBrands() { + return { + discord: this.brandMap.get('brand-discord.png') as Buffer, + github: this.brandMap.get('brand-github.png') as Buffer, + google: this.brandMap.get('brand-google.png') as Buffer, + x: this.brandMap.get('brand-x.png') as Buffer, + } + } } diff --git a/libs/api/metadata/data-access/src/lib/api-metadata.service.ts b/libs/api/metadata/data-access/src/lib/api-metadata.service.ts index e8a909f..56d5ea3 100644 --- a/libs/api/metadata/data-access/src/lib/api-metadata.service.ts +++ b/libs/api/metadata/data-access/src/lib/api-metadata.service.ts @@ -35,6 +35,13 @@ export interface LocalMetadata { @Injectable() export class ApiMetadataService { private readonly logger = new Logger(ApiMetadataService.name) + + constructor( + private readonly core: ApiCoreService, + private readonly image: ApiMetadataImageService, + private readonly solana: ApiSolanaService, + ) {} + private readonly externalMetadataCache = new LRUCache({ max: 1000, ttl: TEN_MINUTES, @@ -102,7 +109,7 @@ export class ApiMetadataService { if (metadata) { // The metadata is external, fetch it and return - if (!metadata.state.uri.startsWith(this.core.config.apiUrl)) { + if (!metadata.state.uri.includes('/api/metadata/')) { // We have external metadata const externalMetadata = await this.externalMetadataCache.fetch(metadata.state.uri) @@ -141,29 +148,31 @@ export class ApiMetadataService { }, }) - constructor( - private readonly core: ApiCoreService, - private readonly image: ApiMetadataImageService, - private readonly solana: ApiSolanaService, - ) {} + private readonly imageCache = new LRUCache({ + max: 1000, + ttl: TEN_MINUTES, + fetchMethod: async (account: string) => { + const accountMetadata = await this.accountMetadataCache.fetch(account) + if (!accountMetadata) { + throw new Error(`Failed to fetch metadata for ${account}`) + } - async getImage(account: string): Promise { - this.logger.verbose(`Fetching metadata image for ${account}`) - const accountMetadata = await this.accountMetadataCache.fetch(account) - if (!accountMetadata) { - throw new Error(`Failed to fetch metadata for ${account}`) - } + if (!accountMetadata.state.uri?.includes('/api/metadata/')) { + const externalMetadata = await this.externalMetadataCache.fetch(accountMetadata.state.uri) + if (!externalMetadata) { + throw new Error(`Failed to fetch external metadata for ${accountMetadata.state.uri}`) + } - if (!accountMetadata.state.uri?.startsWith(this.core.config.apiUrl)) { - const externalMetadata = await this.externalMetadataCache.fetch(accountMetadata.state.uri) - if (!externalMetadata) { - throw new Error(`Failed to fetch external metadata for ${accountMetadata.state.uri}`) + return externalMetadata.image } - return externalMetadata.image - } + return this.image.generate(account, accountMetadata) + }, + }) - return this.image.generate(account, accountMetadata) + async getImage(account: string): Promise { + this.logger.verbose(`Fetching metadata image for ${account}`) + return this.imageCache.fetch(account) } async getJson(account: string) { diff --git a/libs/api/metadata/data-access/src/lib/generators/generate-business-visa-image.tsx b/libs/api/metadata/data-access/src/lib/generators/generate-business-visa-image.tsx index d276240..ccef299 100644 --- a/libs/api/metadata/data-access/src/lib/generators/generate-business-visa-image.tsx +++ b/libs/api/metadata/data-access/src/lib/generators/generate-business-visa-image.tsx @@ -3,6 +3,20 @@ import { Builder, JSX } from 'canvacord' import { CSSProperties } from 'react' +export interface BusinessVisaBrands { + discord: Buffer + github: Buffer + google: Buffer + x: Buffer +} + +export interface BusinessVisaSocials { + discord?: string + github?: string + google?: string + x?: string +} + interface Props { status: string earnings: string @@ -13,6 +27,8 @@ interface Props { poweredBy: Buffer icon: Buffer logo: Buffer + brands: BusinessVisaBrands + socials: BusinessVisaSocials } export interface GenerateDynamicImageOptions { @@ -27,6 +43,8 @@ export interface GenerateDynamicImageOptions { poweredBy: Buffer icon: Buffer logo: Buffer + brands: BusinessVisaBrands + socials: BusinessVisaSocials } export class GenerateBusinessVisaImage extends Builder { constructor(props: GenerateDynamicImageOptions) { @@ -44,6 +62,8 @@ export class GenerateBusinessVisaImage extends Builder { poweredBy: props.poweredBy, icon: props.icon, logo: props.logo, + brands: props.brands, + socials: props.socials, }) } @@ -59,12 +79,16 @@ export class GenerateBusinessVisaImage extends Builder { icon={this.options.get('icon')} logo={this.options.get('logo')} avatar={this.options.get('avatar')} + brands={this.options.get('brands')} + socials={this.options.get('socials')} /> ) } } function TemplateRender({ + socials, + brands, icon, logo, poweredBy, @@ -75,6 +99,8 @@ function TemplateRender({ avatar, community, }: { + socials: BusinessVisaSocials + brands: BusinessVisaBrands icon: Buffer logo: Buffer poweredBy: Buffer @@ -89,6 +115,11 @@ function TemplateRender({ const base64PoweredBy = Buffer.from(poweredBy).toString('base64') const base64Icon = Buffer.from(icon).toString('base64') const base64Logo = Buffer.from(logo).toString('base64') + const base64Discord = Buffer.from(brands.discord).toString('base64') + const base64Github = Buffer.from(brands.github).toString('base64') + const base64Google = Buffer.from(brands.google).toString('base64') + const base64X = Buffer.from(brands.x).toString('base64') + const gray = '#383838' const brand = '#8743FF' const brandLight = '#D6C6FF' @@ -108,6 +139,14 @@ function TemplateRender({ width: '401px', left: '541px', } + + const socialMap = Object.keys(socials) + .filter((key) => (socials[key as keyof BusinessVisaSocials] ?? '').length > 0) + .map((key) => ({ + username: socials[key as keyof BusinessVisaSocials], + icon: brands[key as keyof BusinessVisaBrands], + })) + return (
{name}
{community}
-
-
x.com/beeman_nl
-
-
-
github.com/beeman
-
- {/* RIGHT CARD */} + {socialMap + .filter((social) => (social.username ?? '')?.length > 0) + .map((social, index) => { + const top = 640 + index * 40 + return ( +
+ +
+ ) + })}
{status}
@@ -210,3 +252,12 @@ function TemplateRender({
) } + +function SocialIcon({ username, icon }: { username: string; icon: string }) { + return ( +
+ + {username} +
+ ) +} diff --git a/libs/api/metadata/feature/src/lib/api-metadata.controller.ts b/libs/api/metadata/feature/src/lib/api-metadata.controller.ts index 3846d0e..33c3ac9 100644 --- a/libs/api/metadata/feature/src/lib/api-metadata.controller.ts +++ b/libs/api/metadata/feature/src/lib/api-metadata.controller.ts @@ -18,15 +18,10 @@ export class ApiMetadataController { return res.redirect(result) } - // set headers res.setHeader('Content-Type', 'image/png') res.setHeader('Cache-Control', 'no-store no-cache must-revalidate private max-age=0 s-maxage=0 proxy-revalidate') - // send pubkey-profile res.send(result) - // - // res.writeHead(200, { 'Content-Type': 'image/png', 'Content-Length': result.length }) - // res.end(result) } @Get('json/:account') diff --git a/libs/web/asset/feature/src/lib/user-asset-detail-feature.tsx b/libs/web/asset/feature/src/lib/user-asset-detail-feature.tsx index 954b7eb..d0939dc 100644 --- a/libs/web/asset/feature/src/lib/user-asset-detail-feature.tsx +++ b/libs/web/asset/feature/src/lib/user-asset-detail-feature.tsx @@ -1,10 +1,11 @@ -import { Accordion, SimpleGrid, Text } from '@mantine/core' +import { Accordion, Group, SimpleGrid, Text } from '@mantine/core' import { UiCard, UiDebugModal, UiGroup, UiInfo, UiLoader, UiPage, UiWarning } from '@pubkey-ui/core' import { useQuery } from '@tanstack/react-query' import { Asset, AssetActivityType } from '@tokengator/sdk' import { useGetAsset, useGetAssetActivity } from '@tokengator/web-asset-data-access' import { AssetActivityUiEntryList, AssetActivityUiPoints, AssetUiItem } from '@tokengator/web-asset-ui' import { useSdk } from '@tokengator/web-core-data-access' +import { SolanaExplorerIcon } from '@tokengator/web-solana-ui' import { useParams } from 'react-router-dom' export function useMetadataAll(account: string) { @@ -28,7 +29,14 @@ export function UserAssetDetailFeature() { return loading ? ( ) : asset ? ( - }> + + + + + } + > diff --git a/libs/web/community/ui/src/lib/minter-ui-assets.tsx b/libs/web/community/ui/src/lib/minter-ui-assets.tsx index 3e869d2..d7930a2 100644 --- a/libs/web/community/ui/src/lib/minter-ui-assets.tsx +++ b/libs/web/community/ui/src/lib/minter-ui-assets.tsx @@ -1,6 +1,7 @@ -import { AspectRatio, Image, SimpleGrid, useMantineTheme } from '@mantine/core' +import { AspectRatio, Group, Image, SimpleGrid, useMantineTheme } from '@mantine/core' import { UiAnchor, UiCard, UiCardTitle, UiDebugModal, UiGroup } from '@pubkey-ui/core' import { AccountInfo, ParsedAccountData } from '@solana/web3.js' +import { SolanaExplorerIcon } from '@tokengator/web-solana-ui' export function MinterUiAssets({ items }: { items: AccountInfo[] }) { const { colors } = useMantineTheme() @@ -18,7 +19,10 @@ export function MinterUiAssets({ items }: { items: AccountInfo Asset {index} - + + + + } key={index}