/ src / utils / seo / developer-page.ts
developer-page.ts
  1  import {
  2      type Opt,
  3      unwrapOptional as unwrap,
  4  } from '@jet/environment/types/optional';
  5  import type { Organization, WithContext } from 'schema-dts';
  6  
  7  import type { AppStoreObjectGraph } from '@jet-app/app-store/foundation/runtime/app-store-object-graph';
  8  import {
  9      type Data,
 10      type DataContainer,
 11      dataFromDataContainer,
 12  } from '@jet-app/app-store/foundation/media/data-structure';
 13  import { attributeAsString } from '@jet-app/app-store/foundation/media/attributes';
 14  import { relationshipCollection } from '@jet-app/app-store/foundation/media/relationships';
 15  
 16  import type I18N from '@amp/web-apps-localization';
 17  import type { SeoData } from '@amp/web-app-components/src/components/MetaTags/types';
 18  
 19  import { uniqueById } from '~/utils/array';
 20  import { basicSoftwareApplicationSchema } from '~/utils/seo/product-page';
 21  
 22  /**
 23   * Generate a basic {@linkcode Person} schema for a "developer" page
 24   *
 25   * Note: this is appropriate to be embedded into another schema that
 26   * needs to reference the developer
 27   */
 28  export function basicDeveloperSchema(data: Data) {
 29      return {
 30          '@type': 'Organization',
 31          name: attributeAsString(data, 'name') ?? undefined,
 32          url: attributeAsString(data, 'url') ?? undefined,
 33      } satisfies Organization;
 34  }
 35  
 36  export function buildDeveloperDescription(
 37      props: {
 38          name: string;
 39      },
 40      appData: Data[],
 41      i18n: I18N,
 42  ) {
 43      const { name: developerName } = props;
 44  
 45      switch (appData.length) {
 46          case 0:
 47              return i18n.t(
 48                  'ASE.Web.AppStore.Meta.Developer.Description.ZeroApps',
 49                  {
 50                      developerName,
 51                  },
 52              );
 53          case 1:
 54              return i18n.t(
 55                  'ASE.Web.AppStore.Meta.Developer.Description.OneApp',
 56                  {
 57                      developerName,
 58                      listing1: attributeAsString(appData[0], 'name'),
 59                  },
 60              );
 61          case 2:
 62              return i18n.t(
 63                  'ASE.Web.AppStore.Meta.Developer.Description.TwoApps',
 64                  {
 65                      developerName,
 66                      listing1: attributeAsString(appData[0], 'name'),
 67                      listing2: attributeAsString(appData[1], 'name'),
 68                  },
 69              );
 70          case 3:
 71              return i18n.t(
 72                  'ASE.Web.AppStore.Meta.Developer.Description.ThreeApps',
 73                  {
 74                      developerName,
 75                      listing1: attributeAsString(appData[0], 'name'),
 76                      listing2: attributeAsString(appData[1], 'name'),
 77                      listing3: attributeAsString(appData[2], 'name'),
 78                  },
 79              );
 80          default:
 81              return i18n.t(
 82                  'ASE.Web.AppStore.Meta.Developer.Description.ManyApps',
 83                  {
 84                      developerName,
 85                      listing1: attributeAsString(appData[0], 'name'),
 86                      listing2: attributeAsString(appData[1], 'name'),
 87                      listing3: attributeAsString(appData[2], 'name'),
 88                  },
 89              );
 90      }
 91  }
 92  
 93  /**
 94   * Builds the Schema.org meta-data for a "Developer" page
 95   *
 96   * @param objectGraph The Object Graph
 97   * @param developerPageData The `Data` for the Developer page
 98   * @param appData The `Data` for all apps related to the Developer apge
 99   * @param props Pre-formatted properties also used outside of the Schema
100   * @returns
101   */
102  function developerOrganizationSchemaSeoData(
103      objectGraph: AppStoreObjectGraph,
104      developerPageData: Data,
105      appData: Data[],
106      props: {
107          description: string;
108      },
109  ): Opt<Partial<SeoData>> {
110      const { description } = props;
111  
112      const schemaContent: WithContext<Organization> = {
113          '@context': 'https://schema.org',
114  
115          ...basicDeveloperSchema(developerPageData),
116  
117          description,
118          hasOfferCatalog: {
119              '@type': 'OfferCatalog',
120              itemListElement: appData.map((app) =>
121                  basicSoftwareApplicationSchema(objectGraph, app),
122              ),
123          },
124      };
125  
126      return {
127          schemaName: 'developer',
128          schemaContent,
129      };
130  }
131  
132  /**
133   * Builds the full `SeoData` requirements for a "Developer" page
134   */
135  export function seoDataForDeveloperPage(
136      objectGraph: AppStoreObjectGraph,
137      container: Opt<DataContainer>,
138      i18n: I18N,
139  ): Partial<SeoData> {
140      if (!container) {
141          return {};
142      }
143  
144      const developerPageData = dataFromDataContainer(objectGraph, container);
145      if (!developerPageData) {
146          return {};
147      }
148  
149      const allApps = uniqueById([
150          ...unwrap(relationshipCollection(developerPageData, 'atv-apps')),
151          ...unwrap(relationshipCollection(developerPageData, 'app-bundles')),
152          ...unwrap(relationshipCollection(developerPageData, 'imessage-apps')),
153          ...unwrap(relationshipCollection(developerPageData, 'ios-apps')),
154          ...unwrap(relationshipCollection(developerPageData, 'mac-apps')),
155          ...unwrap(relationshipCollection(developerPageData, 'watch-apps')),
156      ]);
157  
158      const name = unwrap(attributeAsString(developerPageData, 'name'));
159      const description = buildDeveloperDescription({ name }, allApps, i18n);
160  
161      return {
162          description,
163          socialDescription: description,
164          appleDescription: description,
165          ...developerOrganizationSchemaSeoData(
166              objectGraph,
167              developerPageData,
168              allApps,
169              {
170                  description,
171              },
172          ),
173      };
174  }