summaryrefslogtreecommitdiff
path: root/src/components/structure
diff options
context:
space:
mode:
authorrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
committerrxliuli <rxliuli@gmail.com>2025-11-04 05:03:50 +0800
commitbce557cc2dc767628bed6aac87301a1be7c5431b (patch)
treeb51a051228d01fe3306cd7626d4a96768aadb944 /src/components/structure
init commit
Diffstat (limited to 'src/components/structure')
-rw-r--r--src/components/structure/Fonts.svelte19
-rw-r--r--src/components/structure/Footer.svelte47
-rw-r--r--src/components/structure/MetaTags.svelte68
-rw-r--r--src/components/structure/VisionProFooter.svelte142
4 files changed, 276 insertions, 0 deletions
diff --git a/src/components/structure/Fonts.svelte b/src/components/structure/Fonts.svelte
new file mode 100644
index 0000000..63af7b6
--- /dev/null
+++ b/src/components/structure/Fonts.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+ import { BASE, getFontURL } from '@amp/web-apps-fonts';
+
+ export let language: string;
+
+ $: fontURL = getFontURL(language);
+</script>
+
+<svelte:head>
+ <link rel="preconnect" href={BASE} crossorigin="anonymous" />
+
+ <link
+ rel="stylesheet"
+ as="style"
+ href={fontURL}
+ type="text/css"
+ referrerpolicy="strict-origin-when-cross-origin"
+ />
+</svelte:head>
diff --git a/src/components/structure/Footer.svelte b/src/components/structure/Footer.svelte
new file mode 100644
index 0000000..ceabfec
--- /dev/null
+++ b/src/components/structure/Footer.svelte
@@ -0,0 +1,47 @@
+<script lang="ts">
+ import { getI18n } from '~/stores/i18n';
+ import Footer, {
+ type Translate,
+ } from '@amp/web-app-components/src/components/Footer/Footer.svelte';
+ import LocaleSwitcherButton from '@amp/web-app-components/src/components/buttons/LocaleSwitcherButton/LocaleSwitcherButton.svelte';
+ import { items } from '~/constants/footer-items';
+ import { getLocale } from '~/utils/locale';
+ import {
+ regions,
+ languages,
+ storefrontNameTranslations,
+ } from '~/utils/storefront-data';
+
+ const i18n = getI18n();
+ const locale = getLocale();
+
+ const translate: Translate = (key, options) => $i18n.t(key, options);
+</script>
+
+<section class="footer-container">
+ <Footer footerItems={items} translateFn={translate}>
+ <LocaleSwitcherButton
+ slot="secondary-content"
+ translateFn={translate}
+ {regions}
+ {languages}
+ {locale}
+ {storefrontNameTranslations}
+ defaultRoute="iphone/today"
+ />
+ </Footer>
+</section>
+
+<style lang="scss">
+ @use 'ac-sasskit/modules/viewportcontent/core' as *;
+ @use 'amp/stylekit/core/viewports' as *;
+
+ .footer-container {
+ background-color: var(--footerBg);
+ }
+
+ .footer-container :global(footer) {
+ max-width: calc(viewport-content-for(xlarge));
+ margin: 0 auto;
+ }
+</style>
diff --git a/src/components/structure/MetaTags.svelte b/src/components/structure/MetaTags.svelte
new file mode 100644
index 0000000..11b9477
--- /dev/null
+++ b/src/components/structure/MetaTags.svelte
@@ -0,0 +1,68 @@
+<script lang="ts">
+ import type { Opt } from '@jet/environment/types/optional';
+ import type { Organization, WithContext } from 'schema-dts';
+ import type { WebRenderablePage } from '@jet-app/app-store/api/models/web-renderable-page';
+
+ import MetaTags from '@amp/web-app-components/src/components/MetaTags/MetaTags.svelte';
+ import type { SeoData } from '@amp/web-app-components/src/components/MetaTags/types';
+ import { getLocale } from '@amp/web-app-components/src/utils/internal/locale';
+ import { getPageDir } from '@amp/web-apps-localization/src';
+
+ import { getI18n } from '~/stores/i18n';
+
+ export let page: WebRenderablePage;
+
+ const i18n = getI18n();
+ const locale = getLocale();
+
+ const organizationSchema: WithContext<Organization> = {
+ '@context': 'https://schema.org',
+ '@id': 'https://apps.apple.com/#organization',
+ '@type': 'Organization',
+ name: 'App Store',
+ url: 'https://apps.apple.com',
+ logo: 'https://apps.apple.com/assets/app-store.png',
+ sameAs: [
+ 'https://www.wikidata.org/wiki/Q368215',
+ 'https://twitter.com/AppStore',
+ 'https://www.instagram.com/appstore/',
+ 'https://www.facebook.com/appstore/',
+ ],
+ parentOrganization: {
+ '@type': 'Organization',
+ name: 'Apple',
+ '@id': 'https://www.apple.com/#organization',
+ url: 'https://www.apple.com/',
+ },
+ };
+
+ // This cast of `.seoData` is technically a little risky, but our app fully
+ // defines this property, which should make it fairly safe. Whatever is returned
+ // for the page from the `SEO` dependency on the Object Graph will be the value
+ // reflected here.
+ $: seoData = (page.seoData as Opt<SeoData>) ?? undefined;
+
+ // Provide default title for pages not yet set up with SEO data
+ $: defaultTitle = $i18n.t('ASE.Web.AppStore.Meta.SiteName');
+ $: pageDir = getPageDir(locale.language) ?? 'ltr';
+</script>
+
+<MetaTags
+ {defaultTitle}
+ {locale}
+ {pageDir}
+ {seoData}
+ origin={'https://apps.apple.com/'}
+>
+ <svelte:fragment slot="schemaOrganizationData">
+ {#if import.meta.env.SSR}
+ <svelte:element
+ this="script"
+ id="organization"
+ type="application/ld+json"
+ >
+ {JSON.stringify(organizationSchema)}
+ </svelte:element>
+ {/if}
+ </svelte:fragment>
+</MetaTags>
diff --git a/src/components/structure/VisionProFooter.svelte b/src/components/structure/VisionProFooter.svelte
new file mode 100644
index 0000000..59dcd5b
--- /dev/null
+++ b/src/components/structure/VisionProFooter.svelte
@@ -0,0 +1,142 @@
+<script lang="ts">
+ import ShelfTitle from '~/components/Shelf/Title.svelte';
+ import ShelfWrapper from '~/components/Shelf/Wrapper.svelte';
+ import Grid from '~/components/Grid.svelte';
+ import { getI18n } from '~/stores/i18n';
+ import { getLocale } from '~/utils/locale';
+
+ const locale = getLocale();
+ const i18n = getI18n();
+
+ let links: Record<string, string>;
+
+ function getAboutAppStoreUrl(storefront: string, language: string) {
+ let storefrontSlug = `${storefront}/`;
+
+ if (storefront === 'us') {
+ storefrontSlug = '';
+ } else if (storefront === 'gb') {
+ // The UK "About App Store" link is https://www.apple.com/uk/app-store/, not https://www.apple.com/gb/app-store/.
+ storefrontSlug = 'uk/';
+ } else if (storefront === 'ae' && language === 'ar') {
+ storefrontSlug = 'ae-ar/';
+ }
+
+ return `https://www.apple.com/${storefrontSlug}app-store/`;
+ }
+
+ $: storefront = locale.storefront;
+ $: links = {
+ 'ASE.Web.AppStore.VisionPro.Footer.Links.AboutAppStore':
+ getAboutAppStoreUrl(storefront, locale.language),
+ 'ASE.Web.AppStore.VisionPro.Footer.Links.AboutPurchases': `https://apps.apple.com/${storefront}/story/id1436214772`,
+ 'ASE.Web.AppStore.VisionPro.Footer.Links.RequestRefund': `https://www.apple.com/${storefront}/shop/goto/help/sales_refunds`,
+ 'ASE.Web.AppStore.VisionPro.Footer.Links.PaymentMethods': `https://support.apple.com/118429`,
+ };
+
+ $: if (storefront === 'fr') {
+ links[
+ 'AppStore.QuickLinks.AboutFrenchAppStore.Title'
+ ] = `https://apps.apple.com/${storefront}/story/1700848501`;
+ }
+</script>
+
+<ShelfWrapper centered={false} withBottomPadding={false}>
+ <section data-test-id="vision-footer">
+ <p class="blurb">
+ {$i18n.t('ASE.Web.AppStore.VisionPro.Footer.Blurb')}
+ </p>
+
+ <article class="quick-links-container">
+ <ShelfTitle
+ title={$i18n.t('ASE.Web.AppStore.VisionPro.Footer.LinksTitle')}
+ />
+
+ <navigation>
+ <Grid
+ items={Object.entries(links)}
+ gridType="FooterLink"
+ let:item
+ >
+ {@const [title, href] = item}
+ <a {href}>{$i18n.t(title)}</a>
+ </Grid>
+ </navigation>
+ </article>
+
+ <article class="disclaimer-container">
+ <p>
+ {$i18n.t('ASE.Web.AppStore.VisionPro.Footer.Disclaimer')}
+ </p>
+ </article>
+ </section>
+</ShelfWrapper>
+
+<style lang="scss">
+ @use 'ac-sasskit/modules/viewportcontent/core' as *;
+ @use 'amp/stylekit/core/viewports' as *;
+
+ section {
+ font: var(--body-tall);
+ }
+
+ .blurb {
+ flex-grow: 1;
+ width: 100%;
+ max-width: calc(viewport-content-for(xlarge) * 0.66);
+ margin: 40px auto 50px;
+ padding: 0 var(--shelfGridPaddingInline, 40px);
+ text-align: center;
+ }
+
+ .quick-links-container {
+ max-width: viewport-content-for(xlarge);
+ margin: 50px auto;
+ padding: 0 var(--bodyGutter);
+ }
+
+ a {
+ display: block;
+ padding: var(--grid-column-gap-medium) 0 var(--grid-column-gap-medium);
+ word-break: break-all;
+ font: var(--title-2);
+ color: var(--keyColor);
+ border-bottom: 1px solid var(--systemQuinary);
+
+ @media (--range-xsmall-down) {
+ padding: var(--grid-column-gap-xsmall) 0
+ var(--grid-column-gap-xsmall);
+ }
+ }
+
+ @media (--range-medium-up) {
+ .quick-links-container li:nth-child(n + 4) a {
+ border-bottom: none;
+ }
+ }
+
+ @media (--small) {
+ .quick-links-container li:nth-child(n + 5) a {
+ border-bottom: none;
+ }
+ }
+
+ @media (--range-xsmall-down) {
+ .quick-links-container li:last-child a {
+ border-bottom: none;
+ }
+ }
+
+ .disclaimer-container {
+ flex-grow: 1;
+ width: 100%;
+ color: var(--systemTertiary);
+ background-color: var(--footerBg);
+ }
+
+ .disclaimer-container p {
+ max-width: viewport-content-for(xlarge);
+ margin: 0 auto;
+ padding: 32px var(--bodyGutter, 40px);
+ }
+</style>