@bleedingdev/modern-js-create 3.2.0-ultramodern.35 → 3.2.0-ultramodern.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +649 -38
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1209,6 +1209,11 @@ function createCloudflarePublicUrlEnv(app) {
1209
1209
  return `ULTRAMODERN_PUBLIC_URL_${toEnvSegment(app.id)}`;
1210
1210
  }
1211
1211
  function createCloudflareProofRoute(app) {
1212
+ if ('shell' === app.kind) return {
1213
+ ssr: '/en',
1214
+ mfManifest: '/mf-manifest.json',
1215
+ locale: `/locales/en/${appI18nNamespace(app)}.json`
1216
+ };
1212
1217
  const languageRoutes = createLocalisedUrlsMap(app);
1213
1218
  const firstCanonicalPath = Object.keys(languageRoutes)[0];
1214
1219
  const localizedPath = firstCanonicalPath && isRecord(languageRoutes[firstCanonicalPath]) ? languageRoutes[firstCanonicalPath].en : void 0;
@@ -1703,6 +1708,36 @@ function createRouteOwnedI18nPaths(app) {
1703
1708
  en: '/'
1704
1709
  },
1705
1710
  titleKey: 'shell.title'
1711
+ },
1712
+ {
1713
+ ...base,
1714
+ canonicalPath: '/tractors',
1715
+ id: 'shell-tractors',
1716
+ localisedPaths: {
1717
+ cs: '/traktory',
1718
+ en: '/tractors'
1719
+ },
1720
+ titleKey: 'shell.routes.listing'
1721
+ },
1722
+ {
1723
+ ...base,
1724
+ canonicalPath: '/tractors/:slug',
1725
+ id: 'shell-product-detail',
1726
+ localisedPaths: {
1727
+ cs: '/traktory/:slug',
1728
+ en: '/tractors/:slug'
1729
+ },
1730
+ titleKey: 'shell.routes.productDetail'
1731
+ },
1732
+ {
1733
+ ...base,
1734
+ canonicalPath: '/cart',
1735
+ id: 'shell-cart',
1736
+ localisedPaths: {
1737
+ cs: '/kosik',
1738
+ en: '/cart'
1739
+ },
1740
+ titleKey: 'shell.routes.cart'
1706
1741
  }
1707
1742
  ];
1708
1743
  if ('explore' === app.domain) return [
@@ -1865,10 +1900,16 @@ function createRouteOwnedI18nPaths(app) {
1865
1900
  ];
1866
1901
  }
1867
1902
  function createLocalisedUrlsMap(app) {
1868
- return Object.fromEntries(createRouteOwnedI18nPaths(app).filter((route)=>'/' !== route.canonicalPath).map((route)=>[
1903
+ return Object.fromEntries(createRouteOwnedI18nPaths(app).flatMap((route)=>{
1904
+ if ('/' === route.canonicalPath) return [];
1905
+ return Array.from(new Set([
1869
1906
  route.canonicalPath,
1870
- route.localisedPaths
1871
- ]));
1907
+ ...Object.values(route.localisedPaths)
1908
+ ])).map((pathname)=>[
1909
+ pathname,
1910
+ route.localisedPaths
1911
+ ]);
1912
+ }));
1872
1913
  }
1873
1914
  function createRouteMetadataModule(app) {
1874
1915
  const routes = createRouteOwnedI18nPaths(app);
@@ -1959,14 +2000,18 @@ export default defineConfig(
1959
2000
  }
1960
2001
  function createAppRuntimeConfig(app) {
1961
2002
  const namespace = appI18nNamespace(app);
2003
+ const localeMessages = (language)=>{
2004
+ if ('shell' !== app.kind) return createAppLocaleMessages(app, language);
2005
+ return Object.assign({}, createAppLocaleMessages(app, language), ...remoteApps.map((remote)=>createAppLocaleMessages(remote, language)));
2006
+ };
1962
2007
  const resources = {
1963
2008
  cs: {
1964
- [namespace]: createAppLocaleMessages(app, 'cs'),
1965
- translation: createAppLocaleMessages(app, 'cs')
2009
+ [namespace]: localeMessages('cs'),
2010
+ translation: localeMessages('cs')
1966
2011
  },
1967
2012
  en: {
1968
- [namespace]: createAppLocaleMessages(app, 'en'),
1969
- translation: createAppLocaleMessages(app, 'en')
2013
+ [namespace]: localeMessages('en'),
2014
+ translation: localeMessages('en')
1970
2015
  }
1971
2016
  };
1972
2017
  return `import { defineRuntimeConfig } from '@modern-js/runtime';
@@ -2034,6 +2079,103 @@ nav {
2034
2079
  a {
2035
2080
  color: var(--um-color-link);
2036
2081
  }
2082
+
2083
+ .commerce-shell {
2084
+ background: #f1eadc;
2085
+ color: #0b0a08;
2086
+ min-height: 100vh;
2087
+ padding: 1.5rem clamp(1rem, 4vw, 3rem) 4rem;
2088
+ }
2089
+
2090
+ .commerce-shell-actions {
2091
+ align-items: center;
2092
+ display: flex;
2093
+ flex-wrap: wrap;
2094
+ gap: 0.75rem;
2095
+ justify-content: flex-end;
2096
+ margin: -4.25rem auto 3rem;
2097
+ max-width: 88rem;
2098
+ }
2099
+
2100
+ .commerce-language {
2101
+ margin: 0;
2102
+ }
2103
+
2104
+ .commerce-page {
2105
+ margin: 3rem auto 0;
2106
+ max-width: 88rem;
2107
+ }
2108
+
2109
+ .commerce-hero {
2110
+ padding: 4rem 0 2rem;
2111
+ }
2112
+
2113
+ .commerce-eyebrow {
2114
+ color: #00624b;
2115
+ font-size: 0.85rem;
2116
+ font-weight: 850;
2117
+ letter-spacing: 0.16rem;
2118
+ text-transform: uppercase;
2119
+ }
2120
+
2121
+ .commerce-title {
2122
+ font-size: clamp(2.5rem, 6vw, 4.8rem);
2123
+ line-height: 0.95;
2124
+ margin: 0.65rem 0 1.4rem;
2125
+ max-width: 58rem;
2126
+ }
2127
+
2128
+ .commerce-lede {
2129
+ color: #555149;
2130
+ font-size: 1.2rem;
2131
+ line-height: 1.65;
2132
+ max-width: 42rem;
2133
+ }
2134
+
2135
+ .commerce-checkout {
2136
+ align-items: center;
2137
+ display: flex;
2138
+ flex-wrap: wrap;
2139
+ gap: 0.75rem;
2140
+ margin-top: 1.5rem;
2141
+ }
2142
+
2143
+ .commerce-pill,
2144
+ .commerce-button,
2145
+ .commerce-link-button,
2146
+ .commerce-cart-button {
2147
+ align-items: center;
2148
+ border-radius: 999px;
2149
+ border: 0.0625rem solid rgba(23, 23, 23, 0.14);
2150
+ box-shadow: 0 0.25rem 0.75rem rgba(20, 17, 10, 0.08);
2151
+ color: #14120d;
2152
+ display: inline-flex;
2153
+ font: inherit;
2154
+ font-weight: 750;
2155
+ justify-content: center;
2156
+ min-height: 2.5rem;
2157
+ padding: 0.65rem 1.05rem;
2158
+ text-decoration: none;
2159
+ }
2160
+
2161
+ .commerce-button {
2162
+ background: #00624b;
2163
+ border-color: #00624b;
2164
+ color: #ffffff;
2165
+ }
2166
+
2167
+ .commerce-link-button,
2168
+ .commerce-pill,
2169
+ .commerce-cart-button {
2170
+ background: rgba(255, 255, 255, 0.92);
2171
+ }
2172
+
2173
+ @media (max-width: 860px) {
2174
+ .commerce-shell-actions {
2175
+ justify-content: flex-start;
2176
+ margin-top: 1rem;
2177
+ }
2178
+ }
2037
2179
  }
2038
2180
 
2039
2181
  @layer ultramodern-shell-overlay {
@@ -2075,7 +2217,11 @@ a {
2075
2217
  `;
2076
2218
  }
2077
2219
  function createRemoteStyles(enableTailwind, scope, app) {
2078
- if ('commerce' === app.domain) return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}
2220
+ if ([
2221
+ 'explore',
2222
+ 'decide',
2223
+ 'checkout'
2224
+ ].includes(app.domain ?? '')) return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}
2079
2225
 
2080
2226
  @layer ultramodern-remote-${app.domain} {
2081
2227
  .commerce-shell {
@@ -2566,13 +2712,14 @@ function createShellPage() {
2566
2712
  return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2567
2713
  import { Helmet } from '@modern-js/runtime/head';
2568
2714
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2715
+ import Header from 'explore/Header';
2716
+ import StorePicker from 'explore/StorePicker';
2717
+ import MiniCart from 'checkout/MiniCart';
2569
2718
  import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
2570
2719
  import { ultramodernUiMarker } from '../../ultramodern-build';
2571
2720
 
2572
2721
  const languageCodes = ['en', 'cs'] as const;
2573
2722
 
2574
- const remoteKeys = ['explore', 'decide', 'checkout'] as const;
2575
-
2576
2723
  ${createLocalizedHeadComponent()}
2577
2724
  export default function ShellHome() {
2578
2725
  const { i18nInstance, language } = useModernI18n();
@@ -2581,29 +2728,169 @@ export default function ShellHome() {
2581
2728
  const suffix = locationSuffix(location);
2582
2729
 
2583
2730
  return (
2584
- <main>
2731
+ <main className="commerce-shell">
2585
2732
  <LocalizedHead />
2586
- <nav aria-label={t('shell.language.switcher')}>
2587
- {languageCodes.map(code => (
2588
- <a
2589
- aria-current={language === code ? 'page' : undefined}
2590
- href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2591
- key={code}
2592
- >
2593
- {t(\`shell.language.\${code}\`)}
2733
+ <Header />
2734
+ <div className="commerce-shell-actions">
2735
+ <nav aria-label={t('shell.language.switcher')} className="commerce-language">
2736
+ {languageCodes.map(code => (
2737
+ <a
2738
+ aria-current={language === code ? 'page' : undefined}
2739
+ className="commerce-pill"
2740
+ href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2741
+ key={code}
2742
+ >
2743
+ {t(\`shell.language.\${code}\`)}
2744
+ </a>
2745
+ ))}
2746
+ </nav>
2747
+ <MiniCart />
2748
+ </div>
2749
+ <section className="commerce-page commerce-hero">
2750
+ <p className="commerce-eyebrow">{t('shell.hero.eyebrow')}</p>
2751
+ <h1 className="commerce-title">{t('shell.title')}</h1>
2752
+ <p className="commerce-lede">{t('shell.hero.lede')}</p>
2753
+ <div className="commerce-checkout">
2754
+ <a className="commerce-button" href={\`/\${language}/tractors/field-loader-112\`}>
2755
+ {t('shell.hero.primary')}
2594
2756
  </a>
2595
- ))}
2596
- </nav>
2597
- <h1>{t('shell.title')}</h1>
2757
+ <a className="commerce-link-button" href={\`/\${language}/tractors\`}>
2758
+ {t('shell.hero.secondary')}
2759
+ </a>
2760
+ </div>
2761
+ </section>
2762
+ <StorePicker />
2598
2763
  <p data-testid="ultramodern-preset">presetUltramodern workspace</p>
2599
2764
  <p data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2600
2765
  {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
2601
2766
  </p>
2602
- <ul>
2603
- {remoteKeys.map(remote => (
2604
- <li key={remote}>{t(\`shell.remotes.\${remote}\`)}</li>
2605
- ))}
2606
- </ul>
2767
+ </main>
2768
+ );
2769
+ }
2770
+ `;
2771
+ }
2772
+ function createShellTractorsPage() {
2773
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2774
+ import { Helmet } from '@modern-js/runtime/head';
2775
+ import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2776
+ import Header from 'explore/Header';
2777
+ import Recommendations from 'explore/Recommendations';
2778
+ import MiniCart from 'checkout/MiniCart';
2779
+ import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2780
+
2781
+ const languageCodes = ['en', 'cs'] as const;
2782
+
2783
+ ${createLocalizedHeadComponent()}
2784
+ export default function ShellTractorsPage() {
2785
+ const { i18nInstance, language } = useModernI18n();
2786
+ const t = i18nInstance.t.bind(i18nInstance);
2787
+ const location = useLocation();
2788
+ const suffix = locationSuffix(location);
2789
+
2790
+ return (
2791
+ <main className="commerce-shell">
2792
+ <LocalizedHead />
2793
+ <Header />
2794
+ <div className="commerce-shell-actions">
2795
+ <nav aria-label={t('shell.language.switcher')} className="commerce-language">
2796
+ {languageCodes.map(code => (
2797
+ <a
2798
+ aria-current={language === code ? 'page' : undefined}
2799
+ className="commerce-pill"
2800
+ href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2801
+ key={code}
2802
+ >
2803
+ {t(\`shell.language.\${code}\`)}
2804
+ </a>
2805
+ ))}
2806
+ </nav>
2807
+ <MiniCart />
2808
+ </div>
2809
+ <Recommendations />
2810
+ </main>
2811
+ );
2812
+ }
2813
+ `;
2814
+ }
2815
+ function createShellProductPage() {
2816
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2817
+ import { Helmet } from '@modern-js/runtime/head';
2818
+ import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2819
+ import Header from 'explore/Header';
2820
+ import ProductPage from 'decide/ProductPage';
2821
+ import MiniCart from 'checkout/MiniCart';
2822
+ import { ultramodernLocalisedUrls } from '../../../ultramodern-route-metadata';
2823
+
2824
+ const languageCodes = ['en', 'cs'] as const;
2825
+
2826
+ ${createLocalizedHeadComponent()}
2827
+ export default function ShellProductPage() {
2828
+ const { i18nInstance, language } = useModernI18n();
2829
+ const t = i18nInstance.t.bind(i18nInstance);
2830
+ const location = useLocation();
2831
+ const suffix = locationSuffix(location);
2832
+
2833
+ return (
2834
+ <main className="commerce-shell">
2835
+ <LocalizedHead />
2836
+ <Header />
2837
+ <div className="commerce-shell-actions">
2838
+ <nav aria-label={t('shell.language.switcher')} className="commerce-language">
2839
+ {languageCodes.map(code => (
2840
+ <a
2841
+ aria-current={language === code ? 'page' : undefined}
2842
+ className="commerce-pill"
2843
+ href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2844
+ key={code}
2845
+ >
2846
+ {t(\`shell.language.\${code}\`)}
2847
+ </a>
2848
+ ))}
2849
+ </nav>
2850
+ <MiniCart />
2851
+ </div>
2852
+ <ProductPage />
2853
+ </main>
2854
+ );
2855
+ }
2856
+ `;
2857
+ }
2858
+ function createShellCartPage() {
2859
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2860
+ import { Helmet } from '@modern-js/runtime/head';
2861
+ import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2862
+ import Header from 'explore/Header';
2863
+ import CartPage from 'checkout/CartPage';
2864
+ import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2865
+
2866
+ const languageCodes = ['en', 'cs'] as const;
2867
+
2868
+ ${createLocalizedHeadComponent()}
2869
+ export default function ShellCartPage() {
2870
+ const { i18nInstance, language } = useModernI18n();
2871
+ const t = i18nInstance.t.bind(i18nInstance);
2872
+ const location = useLocation();
2873
+ const suffix = locationSuffix(location);
2874
+
2875
+ return (
2876
+ <main className="commerce-shell">
2877
+ <LocalizedHead />
2878
+ <Header />
2879
+ <div className="commerce-shell-actions">
2880
+ <nav aria-label={t('shell.language.switcher')} className="commerce-language">
2881
+ {languageCodes.map(code => (
2882
+ <a
2883
+ aria-current={language === code ? 'page' : undefined}
2884
+ className="commerce-pill"
2885
+ href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2886
+ key={code}
2887
+ >
2888
+ {t(\`shell.language.\${code}\`)}
2889
+ </a>
2890
+ ))}
2891
+ </nav>
2892
+ </div>
2893
+ <CartPage />
2607
2894
  </main>
2608
2895
  );
2609
2896
  }
@@ -2716,19 +3003,181 @@ function createRemoteWidget(app) {
2716
3003
  `;
2717
3004
  }
2718
3005
  function createRemoteExposeComponent(app, expose) {
3006
+ if ('remote-explore' === app.id && './Header' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3007
+
3008
+ export default function Header() {
3009
+ const { i18nInstance, language } = useModernI18n();
3010
+ const t = i18nInstance.t.bind(i18nInstance);
3011
+
3012
+ return (
3013
+ <header className="commerce-header" data-mf-boundary="explore">
3014
+ <a className="commerce-logo" href={\`/\${language}\`}>Acre & Iron</a>
3015
+ <nav aria-label={t('explore.header.navigation')} className="commerce-nav">
3016
+ <a href={\`/\${language}/tractors\`}>{t('explore.header.machines')}</a>
3017
+ <a href={\`/\${language}/stores\`}>{t('explore.header.stores')}</a>
3018
+ </nav>
3019
+ </header>
3020
+ );
3021
+ }
3022
+ `;
3023
+ if ('remote-explore' === app.id && './Recommendations' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3024
+
3025
+ const tractors = [
3026
+ { badge: 'explore.recommendations.bestRows', name: 'Orchard Tractor', slug: 'orchard-tractor' },
3027
+ { badge: 'explore.recommendations.aiFirst', name: 'Autonomy Retrofit Kit', slug: 'autonomy-retrofit-kit' },
3028
+ { badge: 'explore.recommendations.loaderReady', name: 'Field Loader 112', slug: 'field-loader-112' },
3029
+ { badge: 'explore.recommendations.vineyard', name: 'Vineyard Narrow 80', slug: 'vineyard-narrow-80' },
3030
+ ] as const;
3031
+
3032
+ export default function Recommendations() {
3033
+ const { i18nInstance, language } = useModernI18n();
3034
+ const t = i18nInstance.t.bind(i18nInstance);
3035
+
3036
+ return (
3037
+ <section className="commerce-page" data-mf-boundary="explore">
3038
+ <h2 className="commerce-section-title">{t('explore.recommendations.title')}</h2>
3039
+ <div className="commerce-grid">
3040
+ {tractors.map(tractor => (
3041
+ <a className="commerce-card" href={\`/\${language}/tractors/\${tractor.slug}\`} key={tractor.slug}>
3042
+ <span>{t(tractor.badge)}</span>
3043
+ <strong>{tractor.name}</strong>
3044
+ </a>
3045
+ ))}
3046
+ </div>
3047
+ </section>
3048
+ );
3049
+ }
3050
+ `;
3051
+ if ('remote-explore' === app.id && './StorePicker' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3052
+
3053
+ export default function StorePicker() {
3054
+ const { i18nInstance } = useModernI18n();
3055
+ const t = i18nInstance.t.bind(i18nInstance);
3056
+
3057
+ return (
3058
+ <section className="commerce-page" data-mf-boundary="explore">
3059
+ <h2 className="commerce-section-title">{t('explore.stores.title')}</h2>
3060
+ <div className="commerce-grid">
3061
+ <article className="commerce-card">
3062
+ <span>{t('explore.stores.northRegion')}</span>
3063
+ <strong>Bohemia Field Supply</strong>
3064
+ </article>
3065
+ <article className="commerce-card">
3066
+ <span>{t('explore.stores.southRegion')}</span>
3067
+ <strong>Moravia Iron Works</strong>
3068
+ </article>
3069
+ </div>
3070
+ </section>
3071
+ );
3072
+ }
3073
+ `;
3074
+ if ('remote-explore' === app.id && './Footer' === expose) return `export default function Footer() {
3075
+ return <footer className="commerce-footer" data-mf-boundary="explore">Acre & Iron</footer>;
3076
+ }
3077
+ `;
2719
3078
  if ('./Widget' === expose) return createRemoteWidget(app);
2720
3079
  const componentName = `${toPascalCase(app.domain ?? app.id)}${toPascalCase(expose.replace(/^\.\//u, ''))}`;
2721
- if ('remote-decide' === app.id && './ProductPage' === expose) return `import AddToCart from 'checkout/AddToCart';
3080
+ if ('remote-decide' === app.id && './ProductPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3081
+ import AddToCart from 'checkout/AddToCart';
2722
3082
  import Recommendations from 'explore/Recommendations';
2723
3083
 
2724
3084
  export default function ${componentName}() {
3085
+ const { i18nInstance } = useModernI18n();
3086
+ const t = i18nInstance.t.bind(i18nInstance);
3087
+
2725
3088
  return (
2726
- <section data-mf-remote="${app.id}" data-mf-expose="${expose}">
2727
- <p>Decide owns tractor product selection.</p>
2728
- <h2>Field Loader 112</h2>
2729
- <p>Hydraulic-ready compact tractor with guided implement matching.</p>
2730
- <AddToCart />
3089
+ <>
3090
+ <section className="commerce-page commerce-product" data-mf-boundary="decide" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3091
+ <div className="commerce-product-media" aria-hidden="true" />
3092
+ <div>
3093
+ <p className="commerce-eyebrow">{t('decide.product.eyebrow')}</p>
3094
+ <h1 className="commerce-title">Field Loader 112</h1>
3095
+ <p className="commerce-lede">{t('decide.product.lede')}</p>
3096
+ <div className="commerce-facts">
3097
+ <article className="commerce-fact"><span>{t('decide.product.price')}</span><strong>EUR 42,500</strong></article>
3098
+ <article className="commerce-fact"><span>{t('decide.product.power')}</span><strong>112 hp</strong></article>
3099
+ <article className="commerce-fact"><span>{t('decide.product.availability')}</span><strong>{t('decide.product.inStock')}</strong></article>
3100
+ </div>
3101
+ <AddToCart />
3102
+ </div>
3103
+ </section>
2731
3104
  <Recommendations />
3105
+ </>
3106
+ );
3107
+ }
3108
+ `;
3109
+ if ('remote-checkout' === app.id && './AddToCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3110
+ import { useCartLines } from '../cart-store';
3111
+
3112
+ export default function ${componentName}() {
3113
+ const { i18nInstance, language } = useModernI18n();
3114
+ const t = i18nInstance.t.bind(i18nInstance);
3115
+ const cart = useCartLines();
3116
+
3117
+ return (
3118
+ <div className="commerce-checkout" data-mf-boundary="checkout">
3119
+ <button className="commerce-button" onClick={cart.addFieldLoader} type="button">
3120
+ {t('checkout.actions.addToCart')}
3121
+ </button>
3122
+ <a className="commerce-link-button" href={\`/\${language}/cart\`}>
3123
+ {t('checkout.actions.viewCart')}
3124
+ </a>
3125
+ </div>
3126
+ );
3127
+ }
3128
+ `;
3129
+ if ('remote-checkout' === app.id && './MiniCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3130
+ import { useCartLines } from '../cart-store';
3131
+
3132
+ export default function ${componentName}() {
3133
+ const { i18nInstance, language } = useModernI18n();
3134
+ const t = i18nInstance.t.bind(i18nInstance);
3135
+ const cart = useCartLines();
3136
+ const count = cart.lines.reduce((sum, line) => sum + line.quantity, 0);
3137
+
3138
+ return (
3139
+ <a className="commerce-cart-button" data-mf-boundary="checkout" href={\`/\${language}/cart\`}>
3140
+ {t('checkout.cart.title')} ({count})
3141
+ </a>
3142
+ );
3143
+ }
3144
+ `;
3145
+ if ('remote-checkout' === app.id && './CartPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3146
+ import { useCartLines } from '../cart-store';
3147
+
3148
+ export default function ${componentName}() {
3149
+ const { i18nInstance } = useModernI18n();
3150
+ const t = i18nInstance.t.bind(i18nInstance);
3151
+ const cart = useCartLines();
3152
+
3153
+ return (
3154
+ <section className="commerce-page" data-mf-boundary="checkout" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3155
+ <h1 className="commerce-title">{t('checkout.cart.title')}</h1>
3156
+ <div className="commerce-cart-panel">
3157
+ {cart.lines.length === 0 ? (
3158
+ <p>{t('checkout.cart.empty')}</p>
3159
+ ) : (
3160
+ <>
3161
+ {cart.lines.map(line => (
3162
+ <article className="commerce-cart-line" key={line.id}>
3163
+ <div>
3164
+ <strong>{line.name}</strong>
3165
+ <p>EUR {line.price.toLocaleString('en-US')}</p>
3166
+ </div>
3167
+ <div className="commerce-quantity">
3168
+ <button className="commerce-quantity-button" onClick={() => cart.decrement(line.id)} type="button">-</button>
3169
+ <span>{line.quantity}</span>
3170
+ <button className="commerce-quantity-button" onClick={() => cart.increment(line.id)} type="button">+</button>
3171
+ <button className="commerce-link-button" onClick={() => cart.remove(line.id)} type="button">
3172
+ {t('checkout.actions.remove')}
3173
+ </button>
3174
+ </div>
3175
+ </article>
3176
+ ))}
3177
+ <p><strong>{t('checkout.cart.total')}: EUR {cart.total.toLocaleString('en-US')}</strong></p>
3178
+ </>
3179
+ )}
3180
+ </div>
2732
3181
  </section>
2733
3182
  );
2734
3183
  }
@@ -2773,6 +3222,12 @@ function createAppLocaleMessages(app, language) {
2773
3222
  };
2774
3223
  if ('shell' === app.kind) return {
2775
3224
  shell: {
3225
+ hero: {
3226
+ eyebrow: 'en' === language ? 'Federated tractor commerce' : 'Federovaný obchod s traktory',
3227
+ lede: 'en' === language ? 'A full-stack Micro Vertical reference where Explore, Decide, and Checkout ship independently but compose into one storefront.' : 'Full-stack Micro Vertical ukázka, kde Procházení, Rozhodování a Pokladna vycházejí samostatně, ale skládají jeden obchod.',
3228
+ primary: 'en' === language ? 'View Field Loader' : 'Zobrazit Field Loader',
3229
+ secondary: 'en' === language ? 'Compare machines' : 'Porovnat stroje'
3230
+ },
2776
3231
  language: {
2777
3232
  cs: 'en' === language ? 'Czech' : 'Čeština',
2778
3233
  en: 'en' === language ? 'English' : 'Angličtina',
@@ -2784,9 +3239,12 @@ function createAppLocaleMessages(app, language) {
2784
3239
  explore: 'en' === language ? 'Explore Remote' : 'Explore remote'
2785
3240
  },
2786
3241
  routes: {
2787
- home: 'en' === language ? 'Home' : 'Domů'
3242
+ cart: 'en' === language ? 'Cart' : 'Košík',
3243
+ home: 'en' === language ? 'Home' : 'Domů',
3244
+ listing: 'en' === language ? 'Tractors' : 'Traktory',
3245
+ productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru'
2788
3246
  },
2789
- title: 'en' === language ? 'UltraModern SuperApp Shell' : 'UltraModern SuperApp shell'
3247
+ title: 'en' === language ? 'Acre & Iron' : 'Acre & Iron'
2790
3248
  }
2791
3249
  };
2792
3250
  const domain = app.domain ?? app.id;
@@ -2812,7 +3270,48 @@ function createAppLocaleMessages(app, language) {
2812
3270
  thankYou: 'en' === language ? 'Order confirmation' : 'Potvrzení objednávky',
2813
3271
  unavailable: 'en' === language ? 'Unavailable' : 'Nedostupné'
2814
3272
  },
2815
- title: 'en' === language ? app.displayName : czechLabel.title
3273
+ title: 'en' === language ? app.displayName : czechLabel.title,
3274
+ ...'explore' === domain ? {
3275
+ header: {
3276
+ machines: 'en' === language ? 'Machines' : 'Stroje',
3277
+ navigation: 'en' === language ? 'Main navigation' : 'Hlavní navigace',
3278
+ stores: 'en' === language ? 'Stores' : 'Prodejci'
3279
+ },
3280
+ recommendations: {
3281
+ aiFirst: 'en' === language ? 'AI-first option' : 'AI varianta',
3282
+ bestRows: 'en' === language ? 'Best for tight rows' : 'Nejlepší do úzkých řádků',
3283
+ loaderReady: 'en' === language ? 'Loader-ready' : 'Připraveno pro nakladač',
3284
+ title: 'en' === language ? 'Compare alternatives' : 'Porovnat alternativy',
3285
+ vineyard: 'en' === language ? 'Vineyard profile' : 'Profil pro vinice'
3286
+ },
3287
+ stores: {
3288
+ northRegion: 'en' === language ? 'North region' : 'Severní region',
3289
+ southRegion: 'en' === language ? 'South region' : 'Jižní region',
3290
+ title: 'en' === language ? 'Stores' : 'Prodejci'
3291
+ }
3292
+ } : {},
3293
+ ...'decide' === domain ? {
3294
+ product: {
3295
+ availability: 'en' === language ? 'Availability' : 'Dostupnost',
3296
+ eyebrow: 'en' === language ? 'Machine detail' : 'Detail stroje',
3297
+ inStock: 'en' === language ? 'In stock' : 'Skladem',
3298
+ lede: 'en' === language ? 'A loader-ready tractor for feed, hay, gravel, and winter road work.' : 'Traktor připravený pro nakladač na krmivo, seno, štěrk a zimní údržbu cest.',
3299
+ power: 'en' === language ? 'Power' : 'Výkon',
3300
+ price: 'en' === language ? 'Price' : 'Cena'
3301
+ }
3302
+ } : {},
3303
+ ...'checkout' === domain ? {
3304
+ actions: {
3305
+ addToCart: 'en' === language ? 'Add to cart' : 'Přidat do košíku',
3306
+ remove: 'en' === language ? 'Remove' : 'Odebrat',
3307
+ viewCart: 'en' === language ? 'View cart' : 'Zobrazit košík'
3308
+ },
3309
+ cart: {
3310
+ empty: 'en' === language ? 'Your cart is empty.' : 'Košík je prázdný.',
3311
+ title: 'en' === language ? 'Your cart' : 'Váš košík',
3312
+ total: 'en' === language ? 'Total' : 'Celkem'
3313
+ }
3314
+ } : {}
2816
3315
  }
2817
3316
  };
2818
3317
  }
@@ -2846,6 +3345,102 @@ function createDesignTokens() {
2846
3345
  } as const;
2847
3346
  `;
2848
3347
  }
3348
+ function createCheckoutCartStore() {
3349
+ return `import { useEffect, useMemo, useState } from 'react';
3350
+
3351
+ export type CartLine = {
3352
+ id: string;
3353
+ name: string;
3354
+ price: number;
3355
+ quantity: number;
3356
+ };
3357
+
3358
+ const storageKey = 'ultramodern-tractor-cart';
3359
+ const cartEvent = 'ultramodern-cart-change';
3360
+ const fieldLoader: CartLine = {
3361
+ id: 'field-loader-112',
3362
+ name: 'Field Loader 112',
3363
+ price: 42500,
3364
+ quantity: 1,
3365
+ };
3366
+
3367
+ const readCart = (): CartLine[] => {
3368
+ if (typeof window === 'undefined') {
3369
+ return [];
3370
+ }
3371
+
3372
+ try {
3373
+ const value = window.localStorage.getItem(storageKey);
3374
+ return value ? (JSON.parse(value) as CartLine[]) : [];
3375
+ } catch {
3376
+ return [];
3377
+ }
3378
+ };
3379
+
3380
+ const writeCart = (lines: CartLine[]) => {
3381
+ if (typeof window === 'undefined') {
3382
+ return;
3383
+ }
3384
+
3385
+ window.localStorage.setItem(storageKey, JSON.stringify(lines));
3386
+ window.dispatchEvent(new CustomEvent(cartEvent));
3387
+ };
3388
+
3389
+ const updateLine = (
3390
+ id: string,
3391
+ updater: (line: CartLine) => CartLine | undefined,
3392
+ ) => {
3393
+ const next = readCart()
3394
+ .map(line => (line.id === id ? updater(line) : line))
3395
+ .filter((line): line is CartLine => Boolean(line));
3396
+ writeCart(next);
3397
+ };
3398
+
3399
+ export function useCartLines() {
3400
+ const [lines, setLines] = useState<CartLine[]>(() => readCart());
3401
+
3402
+ useEffect(() => {
3403
+ const refresh = () => setLines(readCart());
3404
+ window.addEventListener(cartEvent, refresh);
3405
+ window.addEventListener('storage', refresh);
3406
+ refresh();
3407
+
3408
+ return () => {
3409
+ window.removeEventListener(cartEvent, refresh);
3410
+ window.removeEventListener('storage', refresh);
3411
+ };
3412
+ }, []);
3413
+
3414
+ return useMemo(
3415
+ () => ({
3416
+ lines,
3417
+ total: lines.reduce((sum, line) => sum + line.price * line.quantity, 0),
3418
+ addFieldLoader: () => {
3419
+ const existing = readCart();
3420
+ const match = existing.find(line => line.id === fieldLoader.id);
3421
+ writeCart(
3422
+ match
3423
+ ? existing.map(line =>
3424
+ line.id === fieldLoader.id
3425
+ ? { ...line, quantity: line.quantity + 1 }
3426
+ : line,
3427
+ )
3428
+ : [...existing, fieldLoader],
3429
+ );
3430
+ },
3431
+ increment: (id: string) =>
3432
+ updateLine(id, line => ({ ...line, quantity: line.quantity + 1 })),
3433
+ decrement: (id: string) =>
3434
+ updateLine(id, line =>
3435
+ line.quantity > 1 ? { ...line, quantity: line.quantity - 1 } : undefined,
3436
+ ),
3437
+ remove: (id: string) => writeCart(readCart().filter(line => line.id !== id)),
3438
+ }),
3439
+ [lines],
3440
+ );
3441
+ }
3442
+ `;
3443
+ }
2849
3444
  function createSharedDesignTokensCss() {
2850
3445
  return `@layer ultramodern-shared-tokens {
2851
3446
  :root {
@@ -4715,8 +5310,23 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
4715
5310
  writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig() : createRemoteModuleFederationConfig(app));
4716
5311
  writeFile(targetDir, `${app.directory}/src/routes/layout.tsx`, createLayout(app.id));
4717
5312
  writeFile(targetDir, `${app.directory}/src/routes/[lang]/page.tsx`, 'shell' === app.kind ? createShellPage() : createRemotePage(app));
4718
- for (const route of createRouteOwnedI18nPaths(app))if ('/' !== route.canonicalPath) writeFile(targetDir, createRoutePageFilePath(app, route.canonicalPath), createRouteAliasPage(route.canonicalPath));
4719
- if ('shell' === app.kind) writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
5313
+ for (const route of createRouteOwnedI18nPaths(app)){
5314
+ if ('/' === route.canonicalPath || 'shell' === app.kind) continue;
5315
+ const routePaths = new Set([
5316
+ route.canonicalPath,
5317
+ ...Object.values(route.localisedPaths)
5318
+ ]);
5319
+ for (const routePath of routePaths)if ('/' !== routePath) writeFile(targetDir, createRoutePageFilePath(app, routePath), createRouteAliasPage(routePath));
5320
+ }
5321
+ if ('shell' === app.kind) {
5322
+ writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
5323
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/tractors/page.tsx`, createShellTractorsPage());
5324
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/traktory/page.tsx`, createShellTractorsPage());
5325
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/tractors/[slug]/page.tsx`, createShellProductPage());
5326
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/traktory/[slug]/page.tsx`, createShellProductPage());
5327
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/cart/page.tsx`, createShellCartPage());
5328
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/kosik/page.tsx`, createShellCartPage());
5329
+ }
4720
5330
  if (appHasEffectApi(app)) {
4721
5331
  writeFile(targetDir, `${app.directory}/shared/effect/api.ts`, createEffectSharedApi(app));
4722
5332
  writeFile(targetDir, `${app.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, app, '../../shared/effect/api'));
@@ -4724,6 +5334,7 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
4724
5334
  }
4725
5335
  if ('vertical' === app.kind || 'horizontal-remote' === app.kind) {
4726
5336
  writeFile(targetDir, `${app.directory}/src/remote-entry.tsx`, createRemoteEntry(app));
5337
+ if ('remote-checkout' === app.id) writeFile(targetDir, `${app.directory}/src/cart-store.ts`, createCheckoutCartStore());
4727
5338
  for (const expose of Object.keys(app.exposes ?? {})){
4728
5339
  const outputPath = remoteComponentOutputPath(app, expose);
4729
5340
  if (outputPath) writeFile(targetDir, outputPath, createRemoteExposeComponent(app, expose));
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.2.0-ultramodern.35",
24
+ "version": "3.2.0-ultramodern.37",
25
25
  "types": "./dist/types/index.d.ts",
26
26
  "main": "./dist/index.js",
27
27
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "@types/node": "^25.9.1",
42
42
  "@typescript/native-preview": "7.0.0-dev.20260527.2",
43
43
  "tsx": "^4.22.3",
44
- "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.35"
44
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.37"
45
45
  },
46
46
  "publishConfig": {
47
47
  "registry": "https://registry.npmjs.org/",
@@ -54,6 +54,6 @@
54
54
  "start": "node ./dist/index.js"
55
55
  },
56
56
  "ultramodern": {
57
- "frameworkVersion": "3.2.0-ultramodern.35"
57
+ "frameworkVersion": "3.2.0-ultramodern.37"
58
58
  }
59
59
  }