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

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 +640 -35
  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 [
@@ -1959,14 +1994,18 @@ export default defineConfig(
1959
1994
  }
1960
1995
  function createAppRuntimeConfig(app) {
1961
1996
  const namespace = appI18nNamespace(app);
1997
+ const localeMessages = (language)=>{
1998
+ if ('shell' !== app.kind) return createAppLocaleMessages(app, language);
1999
+ return Object.assign({}, createAppLocaleMessages(app, language), ...remoteApps.map((remote)=>createAppLocaleMessages(remote, language)));
2000
+ };
1962
2001
  const resources = {
1963
2002
  cs: {
1964
- [namespace]: createAppLocaleMessages(app, 'cs'),
1965
- translation: createAppLocaleMessages(app, 'cs')
2003
+ [namespace]: localeMessages('cs'),
2004
+ translation: localeMessages('cs')
1966
2005
  },
1967
2006
  en: {
1968
- [namespace]: createAppLocaleMessages(app, 'en'),
1969
- translation: createAppLocaleMessages(app, 'en')
2007
+ [namespace]: localeMessages('en'),
2008
+ translation: localeMessages('en')
1970
2009
  }
1971
2010
  };
1972
2011
  return `import { defineRuntimeConfig } from '@modern-js/runtime';
@@ -2034,6 +2073,103 @@ nav {
2034
2073
  a {
2035
2074
  color: var(--um-color-link);
2036
2075
  }
2076
+
2077
+ .commerce-shell {
2078
+ background: #f1eadc;
2079
+ color: #0b0a08;
2080
+ min-height: 100vh;
2081
+ padding: 1.5rem clamp(1rem, 4vw, 3rem) 4rem;
2082
+ }
2083
+
2084
+ .commerce-shell-actions {
2085
+ align-items: center;
2086
+ display: flex;
2087
+ flex-wrap: wrap;
2088
+ gap: 0.75rem;
2089
+ justify-content: flex-end;
2090
+ margin: -4.25rem auto 3rem;
2091
+ max-width: 88rem;
2092
+ }
2093
+
2094
+ .commerce-language {
2095
+ margin: 0;
2096
+ }
2097
+
2098
+ .commerce-page {
2099
+ margin: 3rem auto 0;
2100
+ max-width: 88rem;
2101
+ }
2102
+
2103
+ .commerce-hero {
2104
+ padding: 4rem 0 2rem;
2105
+ }
2106
+
2107
+ .commerce-eyebrow {
2108
+ color: #00624b;
2109
+ font-size: 0.85rem;
2110
+ font-weight: 850;
2111
+ letter-spacing: 0.16rem;
2112
+ text-transform: uppercase;
2113
+ }
2114
+
2115
+ .commerce-title {
2116
+ font-size: clamp(2.5rem, 6vw, 4.8rem);
2117
+ line-height: 0.95;
2118
+ margin: 0.65rem 0 1.4rem;
2119
+ max-width: 58rem;
2120
+ }
2121
+
2122
+ .commerce-lede {
2123
+ color: #555149;
2124
+ font-size: 1.2rem;
2125
+ line-height: 1.65;
2126
+ max-width: 42rem;
2127
+ }
2128
+
2129
+ .commerce-checkout {
2130
+ align-items: center;
2131
+ display: flex;
2132
+ flex-wrap: wrap;
2133
+ gap: 0.75rem;
2134
+ margin-top: 1.5rem;
2135
+ }
2136
+
2137
+ .commerce-pill,
2138
+ .commerce-button,
2139
+ .commerce-link-button,
2140
+ .commerce-cart-button {
2141
+ align-items: center;
2142
+ border-radius: 999px;
2143
+ border: 0.0625rem solid rgba(23, 23, 23, 0.14);
2144
+ box-shadow: 0 0.25rem 0.75rem rgba(20, 17, 10, 0.08);
2145
+ color: #14120d;
2146
+ display: inline-flex;
2147
+ font: inherit;
2148
+ font-weight: 750;
2149
+ justify-content: center;
2150
+ min-height: 2.5rem;
2151
+ padding: 0.65rem 1.05rem;
2152
+ text-decoration: none;
2153
+ }
2154
+
2155
+ .commerce-button {
2156
+ background: #00624b;
2157
+ border-color: #00624b;
2158
+ color: #ffffff;
2159
+ }
2160
+
2161
+ .commerce-link-button,
2162
+ .commerce-pill,
2163
+ .commerce-cart-button {
2164
+ background: rgba(255, 255, 255, 0.92);
2165
+ }
2166
+
2167
+ @media (max-width: 860px) {
2168
+ .commerce-shell-actions {
2169
+ justify-content: flex-start;
2170
+ margin-top: 1rem;
2171
+ }
2172
+ }
2037
2173
  }
2038
2174
 
2039
2175
  @layer ultramodern-shell-overlay {
@@ -2075,7 +2211,11 @@ a {
2075
2211
  `;
2076
2212
  }
2077
2213
  function createRemoteStyles(enableTailwind, scope, app) {
2078
- if ('commerce' === app.domain) return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}
2214
+ if ([
2215
+ 'explore',
2216
+ 'decide',
2217
+ 'checkout'
2218
+ ].includes(app.domain ?? '')) return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}
2079
2219
 
2080
2220
  @layer ultramodern-remote-${app.domain} {
2081
2221
  .commerce-shell {
@@ -2566,13 +2706,14 @@ function createShellPage() {
2566
2706
  return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2567
2707
  import { Helmet } from '@modern-js/runtime/head';
2568
2708
  import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2709
+ import Header from 'explore/Header';
2710
+ import StorePicker from 'explore/StorePicker';
2711
+ import MiniCart from 'checkout/MiniCart';
2569
2712
  import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
2570
2713
  import { ultramodernUiMarker } from '../../ultramodern-build';
2571
2714
 
2572
2715
  const languageCodes = ['en', 'cs'] as const;
2573
2716
 
2574
- const remoteKeys = ['explore', 'decide', 'checkout'] as const;
2575
-
2576
2717
  ${createLocalizedHeadComponent()}
2577
2718
  export default function ShellHome() {
2578
2719
  const { i18nInstance, language } = useModernI18n();
@@ -2581,29 +2722,169 @@ export default function ShellHome() {
2581
2722
  const suffix = locationSuffix(location);
2582
2723
 
2583
2724
  return (
2584
- <main>
2725
+ <main className="commerce-shell">
2585
2726
  <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}\`)}
2727
+ <Header />
2728
+ <div className="commerce-shell-actions">
2729
+ <nav aria-label={t('shell.language.switcher')} className="commerce-language">
2730
+ {languageCodes.map(code => (
2731
+ <a
2732
+ aria-current={language === code ? 'page' : undefined}
2733
+ className="commerce-pill"
2734
+ href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2735
+ key={code}
2736
+ >
2737
+ {t(\`shell.language.\${code}\`)}
2738
+ </a>
2739
+ ))}
2740
+ </nav>
2741
+ <MiniCart />
2742
+ </div>
2743
+ <section className="commerce-page commerce-hero">
2744
+ <p className="commerce-eyebrow">{t('shell.hero.eyebrow')}</p>
2745
+ <h1 className="commerce-title">{t('shell.title')}</h1>
2746
+ <p className="commerce-lede">{t('shell.hero.lede')}</p>
2747
+ <div className="commerce-checkout">
2748
+ <a className="commerce-button" href={\`/\${language}/tractors/field-loader-112\`}>
2749
+ {t('shell.hero.primary')}
2594
2750
  </a>
2595
- ))}
2596
- </nav>
2597
- <h1>{t('shell.title')}</h1>
2751
+ <a className="commerce-link-button" href={\`/\${language}/tractors\`}>
2752
+ {t('shell.hero.secondary')}
2753
+ </a>
2754
+ </div>
2755
+ </section>
2756
+ <StorePicker />
2598
2757
  <p data-testid="ultramodern-preset">presetUltramodern workspace</p>
2599
2758
  <p data-build-marker={ultramodernUiMarker.build} data-testid="ultramodern-ui-marker">
2600
2759
  {ultramodernUiMarker.appId}:{ultramodernUiMarker.version}
2601
2760
  </p>
2602
- <ul>
2603
- {remoteKeys.map(remote => (
2604
- <li key={remote}>{t(\`shell.remotes.\${remote}\`)}</li>
2605
- ))}
2606
- </ul>
2761
+ </main>
2762
+ );
2763
+ }
2764
+ `;
2765
+ }
2766
+ function createShellTractorsPage() {
2767
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2768
+ import { Helmet } from '@modern-js/runtime/head';
2769
+ import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2770
+ import Header from 'explore/Header';
2771
+ import Recommendations from 'explore/Recommendations';
2772
+ import MiniCart from 'checkout/MiniCart';
2773
+ import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2774
+
2775
+ const languageCodes = ['en', 'cs'] as const;
2776
+
2777
+ ${createLocalizedHeadComponent()}
2778
+ export default function ShellTractorsPage() {
2779
+ const { i18nInstance, language } = useModernI18n();
2780
+ const t = i18nInstance.t.bind(i18nInstance);
2781
+ const location = useLocation();
2782
+ const suffix = locationSuffix(location);
2783
+
2784
+ return (
2785
+ <main className="commerce-shell">
2786
+ <LocalizedHead />
2787
+ <Header />
2788
+ <div className="commerce-shell-actions">
2789
+ <nav aria-label={t('shell.language.switcher')} className="commerce-language">
2790
+ {languageCodes.map(code => (
2791
+ <a
2792
+ aria-current={language === code ? 'page' : undefined}
2793
+ className="commerce-pill"
2794
+ href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2795
+ key={code}
2796
+ >
2797
+ {t(\`shell.language.\${code}\`)}
2798
+ </a>
2799
+ ))}
2800
+ </nav>
2801
+ <MiniCart />
2802
+ </div>
2803
+ <Recommendations />
2804
+ </main>
2805
+ );
2806
+ }
2807
+ `;
2808
+ }
2809
+ function createShellProductPage() {
2810
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2811
+ import { Helmet } from '@modern-js/runtime/head';
2812
+ import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2813
+ import Header from 'explore/Header';
2814
+ import ProductPage from 'decide/ProductPage';
2815
+ import MiniCart from 'checkout/MiniCart';
2816
+ import { ultramodernLocalisedUrls } from '../../../ultramodern-route-metadata';
2817
+
2818
+ const languageCodes = ['en', 'cs'] as const;
2819
+
2820
+ ${createLocalizedHeadComponent()}
2821
+ export default function ShellProductPage() {
2822
+ const { i18nInstance, language } = useModernI18n();
2823
+ const t = i18nInstance.t.bind(i18nInstance);
2824
+ const location = useLocation();
2825
+ const suffix = locationSuffix(location);
2826
+
2827
+ return (
2828
+ <main className="commerce-shell">
2829
+ <LocalizedHead />
2830
+ <Header />
2831
+ <div className="commerce-shell-actions">
2832
+ <nav aria-label={t('shell.language.switcher')} className="commerce-language">
2833
+ {languageCodes.map(code => (
2834
+ <a
2835
+ aria-current={language === code ? 'page' : undefined}
2836
+ className="commerce-pill"
2837
+ href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2838
+ key={code}
2839
+ >
2840
+ {t(\`shell.language.\${code}\`)}
2841
+ </a>
2842
+ ))}
2843
+ </nav>
2844
+ <MiniCart />
2845
+ </div>
2846
+ <ProductPage />
2847
+ </main>
2848
+ );
2849
+ }
2850
+ `;
2851
+ }
2852
+ function createShellCartPage() {
2853
+ return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2854
+ import { Helmet } from '@modern-js/runtime/head';
2855
+ import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2856
+ import Header from 'explore/Header';
2857
+ import CartPage from 'checkout/CartPage';
2858
+ import { ultramodernLocalisedUrls } from '../../ultramodern-route-metadata';
2859
+
2860
+ const languageCodes = ['en', 'cs'] as const;
2861
+
2862
+ ${createLocalizedHeadComponent()}
2863
+ export default function ShellCartPage() {
2864
+ const { i18nInstance, language } = useModernI18n();
2865
+ const t = i18nInstance.t.bind(i18nInstance);
2866
+ const location = useLocation();
2867
+ const suffix = locationSuffix(location);
2868
+
2869
+ return (
2870
+ <main className="commerce-shell">
2871
+ <LocalizedHead />
2872
+ <Header />
2873
+ <div className="commerce-shell-actions">
2874
+ <nav aria-label={t('shell.language.switcher')} className="commerce-language">
2875
+ {languageCodes.map(code => (
2876
+ <a
2877
+ aria-current={language === code ? 'page' : undefined}
2878
+ className="commerce-pill"
2879
+ href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2880
+ key={code}
2881
+ >
2882
+ {t(\`shell.language.\${code}\`)}
2883
+ </a>
2884
+ ))}
2885
+ </nav>
2886
+ </div>
2887
+ <CartPage />
2607
2888
  </main>
2608
2889
  );
2609
2890
  }
@@ -2716,19 +2997,181 @@ function createRemoteWidget(app) {
2716
2997
  `;
2717
2998
  }
2718
2999
  function createRemoteExposeComponent(app, expose) {
3000
+ if ('remote-explore' === app.id && './Header' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3001
+
3002
+ export default function Header() {
3003
+ const { i18nInstance, language } = useModernI18n();
3004
+ const t = i18nInstance.t.bind(i18nInstance);
3005
+
3006
+ return (
3007
+ <header className="commerce-header" data-mf-boundary="explore">
3008
+ <a className="commerce-logo" href={\`/\${language}\`}>Acre & Iron</a>
3009
+ <nav aria-label={t('explore.header.navigation')} className="commerce-nav">
3010
+ <a href={\`/\${language}/tractors\`}>{t('explore.header.machines')}</a>
3011
+ <a href={\`/\${language}/stores\`}>{t('explore.header.stores')}</a>
3012
+ </nav>
3013
+ </header>
3014
+ );
3015
+ }
3016
+ `;
3017
+ if ('remote-explore' === app.id && './Recommendations' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3018
+
3019
+ const tractors = [
3020
+ { badge: 'explore.recommendations.bestRows', name: 'Orchard Tractor', slug: 'orchard-tractor' },
3021
+ { badge: 'explore.recommendations.aiFirst', name: 'Autonomy Retrofit Kit', slug: 'autonomy-retrofit-kit' },
3022
+ { badge: 'explore.recommendations.loaderReady', name: 'Field Loader 112', slug: 'field-loader-112' },
3023
+ { badge: 'explore.recommendations.vineyard', name: 'Vineyard Narrow 80', slug: 'vineyard-narrow-80' },
3024
+ ] as const;
3025
+
3026
+ export default function Recommendations() {
3027
+ const { i18nInstance, language } = useModernI18n();
3028
+ const t = i18nInstance.t.bind(i18nInstance);
3029
+
3030
+ return (
3031
+ <section className="commerce-page" data-mf-boundary="explore">
3032
+ <h2 className="commerce-section-title">{t('explore.recommendations.title')}</h2>
3033
+ <div className="commerce-grid">
3034
+ {tractors.map(tractor => (
3035
+ <a className="commerce-card" href={\`/\${language}/tractors/\${tractor.slug}\`} key={tractor.slug}>
3036
+ <span>{t(tractor.badge)}</span>
3037
+ <strong>{tractor.name}</strong>
3038
+ </a>
3039
+ ))}
3040
+ </div>
3041
+ </section>
3042
+ );
3043
+ }
3044
+ `;
3045
+ if ('remote-explore' === app.id && './StorePicker' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3046
+
3047
+ export default function StorePicker() {
3048
+ const { i18nInstance } = useModernI18n();
3049
+ const t = i18nInstance.t.bind(i18nInstance);
3050
+
3051
+ return (
3052
+ <section className="commerce-page" data-mf-boundary="explore">
3053
+ <h2 className="commerce-section-title">{t('explore.stores.title')}</h2>
3054
+ <div className="commerce-grid">
3055
+ <article className="commerce-card">
3056
+ <span>{t('explore.stores.northRegion')}</span>
3057
+ <strong>Bohemia Field Supply</strong>
3058
+ </article>
3059
+ <article className="commerce-card">
3060
+ <span>{t('explore.stores.southRegion')}</span>
3061
+ <strong>Moravia Iron Works</strong>
3062
+ </article>
3063
+ </div>
3064
+ </section>
3065
+ );
3066
+ }
3067
+ `;
3068
+ if ('remote-explore' === app.id && './Footer' === expose) return `export default function Footer() {
3069
+ return <footer className="commerce-footer" data-mf-boundary="explore">Acre & Iron</footer>;
3070
+ }
3071
+ `;
2719
3072
  if ('./Widget' === expose) return createRemoteWidget(app);
2720
3073
  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';
3074
+ if ('remote-decide' === app.id && './ProductPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3075
+ import AddToCart from 'checkout/AddToCart';
2722
3076
  import Recommendations from 'explore/Recommendations';
2723
3077
 
2724
3078
  export default function ${componentName}() {
3079
+ const { i18nInstance } = useModernI18n();
3080
+ const t = i18nInstance.t.bind(i18nInstance);
3081
+
2725
3082
  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 />
3083
+ <>
3084
+ <section className="commerce-page commerce-product" data-mf-boundary="decide" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3085
+ <div className="commerce-product-media" aria-hidden="true" />
3086
+ <div>
3087
+ <p className="commerce-eyebrow">{t('decide.product.eyebrow')}</p>
3088
+ <h1 className="commerce-title">Field Loader 112</h1>
3089
+ <p className="commerce-lede">{t('decide.product.lede')}</p>
3090
+ <div className="commerce-facts">
3091
+ <article className="commerce-fact"><span>{t('decide.product.price')}</span><strong>EUR 42,500</strong></article>
3092
+ <article className="commerce-fact"><span>{t('decide.product.power')}</span><strong>112 hp</strong></article>
3093
+ <article className="commerce-fact"><span>{t('decide.product.availability')}</span><strong>{t('decide.product.inStock')}</strong></article>
3094
+ </div>
3095
+ <AddToCart />
3096
+ </div>
3097
+ </section>
2731
3098
  <Recommendations />
3099
+ </>
3100
+ );
3101
+ }
3102
+ `;
3103
+ if ('remote-checkout' === app.id && './AddToCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3104
+ import { useCartLines } from '../cart-store';
3105
+
3106
+ export default function ${componentName}() {
3107
+ const { i18nInstance, language } = useModernI18n();
3108
+ const t = i18nInstance.t.bind(i18nInstance);
3109
+ const cart = useCartLines();
3110
+
3111
+ return (
3112
+ <div className="commerce-checkout" data-mf-boundary="checkout">
3113
+ <button className="commerce-button" onClick={cart.addFieldLoader} type="button">
3114
+ {t('checkout.actions.addToCart')}
3115
+ </button>
3116
+ <a className="commerce-link-button" href={\`/\${language}/cart\`}>
3117
+ {t('checkout.actions.viewCart')}
3118
+ </a>
3119
+ </div>
3120
+ );
3121
+ }
3122
+ `;
3123
+ if ('remote-checkout' === app.id && './MiniCart' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3124
+ import { useCartLines } from '../cart-store';
3125
+
3126
+ export default function ${componentName}() {
3127
+ const { i18nInstance, language } = useModernI18n();
3128
+ const t = i18nInstance.t.bind(i18nInstance);
3129
+ const cart = useCartLines();
3130
+ const count = cart.lines.reduce((sum, line) => sum + line.quantity, 0);
3131
+
3132
+ return (
3133
+ <a className="commerce-cart-button" data-mf-boundary="checkout" href={\`/\${language}/cart\`}>
3134
+ {t('checkout.cart.title')} ({count})
3135
+ </a>
3136
+ );
3137
+ }
3138
+ `;
3139
+ if ('remote-checkout' === app.id && './CartPage' === expose) return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3140
+ import { useCartLines } from '../cart-store';
3141
+
3142
+ export default function ${componentName}() {
3143
+ const { i18nInstance } = useModernI18n();
3144
+ const t = i18nInstance.t.bind(i18nInstance);
3145
+ const cart = useCartLines();
3146
+
3147
+ return (
3148
+ <section className="commerce-page" data-mf-boundary="checkout" data-mf-remote="${app.id}" data-mf-expose="${expose}">
3149
+ <h1 className="commerce-title">{t('checkout.cart.title')}</h1>
3150
+ <div className="commerce-cart-panel">
3151
+ {cart.lines.length === 0 ? (
3152
+ <p>{t('checkout.cart.empty')}</p>
3153
+ ) : (
3154
+ <>
3155
+ {cart.lines.map(line => (
3156
+ <article className="commerce-cart-line" key={line.id}>
3157
+ <div>
3158
+ <strong>{line.name}</strong>
3159
+ <p>EUR {line.price.toLocaleString('en-US')}</p>
3160
+ </div>
3161
+ <div className="commerce-quantity">
3162
+ <button className="commerce-quantity-button" onClick={() => cart.decrement(line.id)} type="button">-</button>
3163
+ <span>{line.quantity}</span>
3164
+ <button className="commerce-quantity-button" onClick={() => cart.increment(line.id)} type="button">+</button>
3165
+ <button className="commerce-link-button" onClick={() => cart.remove(line.id)} type="button">
3166
+ {t('checkout.actions.remove')}
3167
+ </button>
3168
+ </div>
3169
+ </article>
3170
+ ))}
3171
+ <p><strong>{t('checkout.cart.total')}: EUR {cart.total.toLocaleString('en-US')}</strong></p>
3172
+ </>
3173
+ )}
3174
+ </div>
2732
3175
  </section>
2733
3176
  );
2734
3177
  }
@@ -2773,6 +3216,12 @@ function createAppLocaleMessages(app, language) {
2773
3216
  };
2774
3217
  if ('shell' === app.kind) return {
2775
3218
  shell: {
3219
+ hero: {
3220
+ eyebrow: 'en' === language ? 'Federated tractor commerce' : 'Federovaný obchod s traktory',
3221
+ 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.',
3222
+ primary: 'en' === language ? 'View Field Loader' : 'Zobrazit Field Loader',
3223
+ secondary: 'en' === language ? 'Compare machines' : 'Porovnat stroje'
3224
+ },
2776
3225
  language: {
2777
3226
  cs: 'en' === language ? 'Czech' : 'Čeština',
2778
3227
  en: 'en' === language ? 'English' : 'Angličtina',
@@ -2784,9 +3233,12 @@ function createAppLocaleMessages(app, language) {
2784
3233
  explore: 'en' === language ? 'Explore Remote' : 'Explore remote'
2785
3234
  },
2786
3235
  routes: {
2787
- home: 'en' === language ? 'Home' : 'Domů'
3236
+ cart: 'en' === language ? 'Cart' : 'Košík',
3237
+ home: 'en' === language ? 'Home' : 'Domů',
3238
+ listing: 'en' === language ? 'Tractors' : 'Traktory',
3239
+ productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru'
2788
3240
  },
2789
- title: 'en' === language ? 'UltraModern SuperApp Shell' : 'UltraModern SuperApp shell'
3241
+ title: 'en' === language ? 'Acre & Iron' : 'Acre & Iron'
2790
3242
  }
2791
3243
  };
2792
3244
  const domain = app.domain ?? app.id;
@@ -2812,7 +3264,48 @@ function createAppLocaleMessages(app, language) {
2812
3264
  thankYou: 'en' === language ? 'Order confirmation' : 'Potvrzení objednávky',
2813
3265
  unavailable: 'en' === language ? 'Unavailable' : 'Nedostupné'
2814
3266
  },
2815
- title: 'en' === language ? app.displayName : czechLabel.title
3267
+ title: 'en' === language ? app.displayName : czechLabel.title,
3268
+ ...'explore' === domain ? {
3269
+ header: {
3270
+ machines: 'en' === language ? 'Machines' : 'Stroje',
3271
+ navigation: 'en' === language ? 'Main navigation' : 'Hlavní navigace',
3272
+ stores: 'en' === language ? 'Stores' : 'Prodejci'
3273
+ },
3274
+ recommendations: {
3275
+ aiFirst: 'en' === language ? 'AI-first option' : 'AI varianta',
3276
+ bestRows: 'en' === language ? 'Best for tight rows' : 'Nejlepší do úzkých řádků',
3277
+ loaderReady: 'en' === language ? 'Loader-ready' : 'Připraveno pro nakladač',
3278
+ title: 'en' === language ? 'Compare alternatives' : 'Porovnat alternativy',
3279
+ vineyard: 'en' === language ? 'Vineyard profile' : 'Profil pro vinice'
3280
+ },
3281
+ stores: {
3282
+ northRegion: 'en' === language ? 'North region' : 'Severní region',
3283
+ southRegion: 'en' === language ? 'South region' : 'Jižní region',
3284
+ title: 'en' === language ? 'Stores' : 'Prodejci'
3285
+ }
3286
+ } : {},
3287
+ ...'decide' === domain ? {
3288
+ product: {
3289
+ availability: 'en' === language ? 'Availability' : 'Dostupnost',
3290
+ eyebrow: 'en' === language ? 'Machine detail' : 'Detail stroje',
3291
+ inStock: 'en' === language ? 'In stock' : 'Skladem',
3292
+ 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.',
3293
+ power: 'en' === language ? 'Power' : 'Výkon',
3294
+ price: 'en' === language ? 'Price' : 'Cena'
3295
+ }
3296
+ } : {},
3297
+ ...'checkout' === domain ? {
3298
+ actions: {
3299
+ addToCart: 'en' === language ? 'Add to cart' : 'Přidat do košíku',
3300
+ remove: 'en' === language ? 'Remove' : 'Odebrat',
3301
+ viewCart: 'en' === language ? 'View cart' : 'Zobrazit košík'
3302
+ },
3303
+ cart: {
3304
+ empty: 'en' === language ? 'Your cart is empty.' : 'Košík je prázdný.',
3305
+ title: 'en' === language ? 'Your cart' : 'Váš košík',
3306
+ total: 'en' === language ? 'Total' : 'Celkem'
3307
+ }
3308
+ } : {}
2816
3309
  }
2817
3310
  };
2818
3311
  }
@@ -2846,6 +3339,102 @@ function createDesignTokens() {
2846
3339
  } as const;
2847
3340
  `;
2848
3341
  }
3342
+ function createCheckoutCartStore() {
3343
+ return `import { useEffect, useMemo, useState } from 'react';
3344
+
3345
+ export type CartLine = {
3346
+ id: string;
3347
+ name: string;
3348
+ price: number;
3349
+ quantity: number;
3350
+ };
3351
+
3352
+ const storageKey = 'ultramodern-tractor-cart';
3353
+ const cartEvent = 'ultramodern-cart-change';
3354
+ const fieldLoader: CartLine = {
3355
+ id: 'field-loader-112',
3356
+ name: 'Field Loader 112',
3357
+ price: 42500,
3358
+ quantity: 1,
3359
+ };
3360
+
3361
+ const readCart = (): CartLine[] => {
3362
+ if (typeof window === 'undefined') {
3363
+ return [];
3364
+ }
3365
+
3366
+ try {
3367
+ const value = window.localStorage.getItem(storageKey);
3368
+ return value ? (JSON.parse(value) as CartLine[]) : [];
3369
+ } catch {
3370
+ return [];
3371
+ }
3372
+ };
3373
+
3374
+ const writeCart = (lines: CartLine[]) => {
3375
+ if (typeof window === 'undefined') {
3376
+ return;
3377
+ }
3378
+
3379
+ window.localStorage.setItem(storageKey, JSON.stringify(lines));
3380
+ window.dispatchEvent(new CustomEvent(cartEvent));
3381
+ };
3382
+
3383
+ const updateLine = (
3384
+ id: string,
3385
+ updater: (line: CartLine) => CartLine | undefined,
3386
+ ) => {
3387
+ const next = readCart()
3388
+ .map(line => (line.id === id ? updater(line) : line))
3389
+ .filter((line): line is CartLine => Boolean(line));
3390
+ writeCart(next);
3391
+ };
3392
+
3393
+ export function useCartLines() {
3394
+ const [lines, setLines] = useState<CartLine[]>(() => readCart());
3395
+
3396
+ useEffect(() => {
3397
+ const refresh = () => setLines(readCart());
3398
+ window.addEventListener(cartEvent, refresh);
3399
+ window.addEventListener('storage', refresh);
3400
+ refresh();
3401
+
3402
+ return () => {
3403
+ window.removeEventListener(cartEvent, refresh);
3404
+ window.removeEventListener('storage', refresh);
3405
+ };
3406
+ }, []);
3407
+
3408
+ return useMemo(
3409
+ () => ({
3410
+ lines,
3411
+ total: lines.reduce((sum, line) => sum + line.price * line.quantity, 0),
3412
+ addFieldLoader: () => {
3413
+ const existing = readCart();
3414
+ const match = existing.find(line => line.id === fieldLoader.id);
3415
+ writeCart(
3416
+ match
3417
+ ? existing.map(line =>
3418
+ line.id === fieldLoader.id
3419
+ ? { ...line, quantity: line.quantity + 1 }
3420
+ : line,
3421
+ )
3422
+ : [...existing, fieldLoader],
3423
+ );
3424
+ },
3425
+ increment: (id: string) =>
3426
+ updateLine(id, line => ({ ...line, quantity: line.quantity + 1 })),
3427
+ decrement: (id: string) =>
3428
+ updateLine(id, line =>
3429
+ line.quantity > 1 ? { ...line, quantity: line.quantity - 1 } : undefined,
3430
+ ),
3431
+ remove: (id: string) => writeCart(readCart().filter(line => line.id !== id)),
3432
+ }),
3433
+ [lines],
3434
+ );
3435
+ }
3436
+ `;
3437
+ }
2849
3438
  function createSharedDesignTokensCss() {
2850
3439
  return `@layer ultramodern-shared-tokens {
2851
3440
  :root {
@@ -4715,8 +5304,23 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
4715
5304
  writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig() : createRemoteModuleFederationConfig(app));
4716
5305
  writeFile(targetDir, `${app.directory}/src/routes/layout.tsx`, createLayout(app.id));
4717
5306
  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));
5307
+ for (const route of createRouteOwnedI18nPaths(app)){
5308
+ if ('/' === route.canonicalPath || 'shell' === app.kind) continue;
5309
+ const routePaths = new Set([
5310
+ route.canonicalPath,
5311
+ ...Object.values(route.localisedPaths)
5312
+ ]);
5313
+ for (const routePath of routePaths)if ('/' !== routePath) writeFile(targetDir, createRoutePageFilePath(app, routePath), createRouteAliasPage(routePath));
5314
+ }
5315
+ if ('shell' === app.kind) {
5316
+ writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
5317
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/tractors/page.tsx`, createShellTractorsPage());
5318
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/traktory/page.tsx`, createShellTractorsPage());
5319
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/tractors/[slug]/page.tsx`, createShellProductPage());
5320
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/traktory/[slug]/page.tsx`, createShellProductPage());
5321
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/cart/page.tsx`, createShellCartPage());
5322
+ writeFile(targetDir, `${app.directory}/src/routes/[lang]/kosik/page.tsx`, createShellCartPage());
5323
+ }
4720
5324
  if (appHasEffectApi(app)) {
4721
5325
  writeFile(targetDir, `${app.directory}/shared/effect/api.ts`, createEffectSharedApi(app));
4722
5326
  writeFile(targetDir, `${app.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, app, '../../shared/effect/api'));
@@ -4724,6 +5328,7 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
4724
5328
  }
4725
5329
  if ('vertical' === app.kind || 'horizontal-remote' === app.kind) {
4726
5330
  writeFile(targetDir, `${app.directory}/src/remote-entry.tsx`, createRemoteEntry(app));
5331
+ if ('remote-checkout' === app.id) writeFile(targetDir, `${app.directory}/src/cart-store.ts`, createCheckoutCartStore());
4727
5332
  for (const expose of Object.keys(app.exposes ?? {})){
4728
5333
  const outputPath = remoteComponentOutputPath(app, expose);
4729
5334
  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.36",
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.36"
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.36"
58
58
  }
59
59
  }