rothzerg 0.1.0

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 (341) hide show
  1. package/dist/cli.js +949 -0
  2. package/package.json +27 -0
  3. package/readme.md +0 -0
  4. package/templates/_base-website/App.tsx +59 -0
  5. package/templates/_base-website/InitialDataContext.tsx +21 -0
  6. package/templates/_base-website/_gitignore +5 -0
  7. package/templates/_base-website/client.tsx +26 -0
  8. package/templates/_base-website/components/DeepLinkLayout.css +41 -0
  9. package/templates/_base-website/components/DeepLinkLayout.tsx +39 -0
  10. package/templates/_base-website/components/DownloadSection.css +60 -0
  11. package/templates/_base-website/components/DownloadSection.tsx +61 -0
  12. package/templates/_base-website/components/SiteFeaturesSection.tsx +68 -0
  13. package/templates/_base-website/components/SiteFooter.tsx +29 -0
  14. package/templates/_base-website/components/SiteHeroSection.tsx +30 -0
  15. package/templates/_base-website/components/SiteHighlightsSection.tsx +38 -0
  16. package/templates/_base-website/components/SiteLanguageSwitcher.tsx +9 -0
  17. package/templates/_base-website/components/SiteNavigation.tsx +24 -0
  18. package/templates/_base-website/config.ts +78 -0
  19. package/templates/_base-website/data/blog/authors.json +18 -0
  20. package/templates/_base-website/data/blog/categories.json +53 -0
  21. package/templates/_base-website/data/blog/posts.json +29 -0
  22. package/templates/_base-website/data/blog/tags.json +86 -0
  23. package/templates/_base-website/i18n/config.ts +60 -0
  24. package/templates/_base-website/i18n/sections/blog/_index.ts +41 -0
  25. package/templates/_base-website/i18n/sections/blog/ar.ts +24 -0
  26. package/templates/_base-website/i18n/sections/blog/de.ts +24 -0
  27. package/templates/_base-website/i18n/sections/blog/en.ts +24 -0
  28. package/templates/_base-website/i18n/sections/blog/es.ts +24 -0
  29. package/templates/_base-website/i18n/sections/blog/fr.ts +24 -0
  30. package/templates/_base-website/i18n/sections/blog/hi.ts +24 -0
  31. package/templates/_base-website/i18n/sections/blog/id.ts +24 -0
  32. package/templates/_base-website/i18n/sections/blog/it.ts +24 -0
  33. package/templates/_base-website/i18n/sections/blog/ja.ts +24 -0
  34. package/templates/_base-website/i18n/sections/blog/ko.ts +24 -0
  35. package/templates/_base-website/i18n/sections/blog/nl.ts +24 -0
  36. package/templates/_base-website/i18n/sections/blog/pl.ts +24 -0
  37. package/templates/_base-website/i18n/sections/blog/pt.ts +24 -0
  38. package/templates/_base-website/i18n/sections/blog/ru.ts +24 -0
  39. package/templates/_base-website/i18n/sections/blog/sv.ts +24 -0
  40. package/templates/_base-website/i18n/sections/blog/th.ts +24 -0
  41. package/templates/_base-website/i18n/sections/blog/tr.ts +24 -0
  42. package/templates/_base-website/i18n/sections/blog/vi.ts +24 -0
  43. package/templates/_base-website/i18n/sections/blog/zh.ts +24 -0
  44. package/templates/_base-website/i18n/sections/common/_index.ts +41 -0
  45. package/templates/_base-website/i18n/sections/common/ar.ts +29 -0
  46. package/templates/_base-website/i18n/sections/common/de.ts +29 -0
  47. package/templates/_base-website/i18n/sections/common/en.ts +29 -0
  48. package/templates/_base-website/i18n/sections/common/es.ts +29 -0
  49. package/templates/_base-website/i18n/sections/common/fr.ts +29 -0
  50. package/templates/_base-website/i18n/sections/common/hi.ts +29 -0
  51. package/templates/_base-website/i18n/sections/common/id.ts +29 -0
  52. package/templates/_base-website/i18n/sections/common/it.ts +29 -0
  53. package/templates/_base-website/i18n/sections/common/ja.ts +29 -0
  54. package/templates/_base-website/i18n/sections/common/ko.ts +29 -0
  55. package/templates/_base-website/i18n/sections/common/nl.ts +29 -0
  56. package/templates/_base-website/i18n/sections/common/pl.ts +29 -0
  57. package/templates/_base-website/i18n/sections/common/pt.ts +29 -0
  58. package/templates/_base-website/i18n/sections/common/ru.ts +29 -0
  59. package/templates/_base-website/i18n/sections/common/sv.ts +29 -0
  60. package/templates/_base-website/i18n/sections/common/th.ts +29 -0
  61. package/templates/_base-website/i18n/sections/common/tr.ts +29 -0
  62. package/templates/_base-website/i18n/sections/common/vi.ts +29 -0
  63. package/templates/_base-website/i18n/sections/common/zh.ts +29 -0
  64. package/templates/_base-website/i18n/sections/deepLink/_index.ts +41 -0
  65. package/templates/_base-website/i18n/sections/deepLink/ar.ts +14 -0
  66. package/templates/_base-website/i18n/sections/deepLink/de.ts +14 -0
  67. package/templates/_base-website/i18n/sections/deepLink/en.ts +14 -0
  68. package/templates/_base-website/i18n/sections/deepLink/es.ts +14 -0
  69. package/templates/_base-website/i18n/sections/deepLink/fr.ts +14 -0
  70. package/templates/_base-website/i18n/sections/deepLink/hi.ts +14 -0
  71. package/templates/_base-website/i18n/sections/deepLink/id.ts +14 -0
  72. package/templates/_base-website/i18n/sections/deepLink/it.ts +14 -0
  73. package/templates/_base-website/i18n/sections/deepLink/ja.ts +14 -0
  74. package/templates/_base-website/i18n/sections/deepLink/ko.ts +14 -0
  75. package/templates/_base-website/i18n/sections/deepLink/nl.ts +14 -0
  76. package/templates/_base-website/i18n/sections/deepLink/pl.ts +14 -0
  77. package/templates/_base-website/i18n/sections/deepLink/pt.ts +14 -0
  78. package/templates/_base-website/i18n/sections/deepLink/ru.ts +14 -0
  79. package/templates/_base-website/i18n/sections/deepLink/sv.ts +14 -0
  80. package/templates/_base-website/i18n/sections/deepLink/th.ts +14 -0
  81. package/templates/_base-website/i18n/sections/deepLink/tr.ts +14 -0
  82. package/templates/_base-website/i18n/sections/deepLink/vi.ts +14 -0
  83. package/templates/_base-website/i18n/sections/deepLink/zh.ts +14 -0
  84. package/templates/_base-website/i18n/sections/home/_index.ts +41 -0
  85. package/templates/_base-website/i18n/sections/home/ar.ts +85 -0
  86. package/templates/_base-website/i18n/sections/home/de.ts +85 -0
  87. package/templates/_base-website/i18n/sections/home/en.ts +85 -0
  88. package/templates/_base-website/i18n/sections/home/es.ts +85 -0
  89. package/templates/_base-website/i18n/sections/home/fr.ts +85 -0
  90. package/templates/_base-website/i18n/sections/home/hi.ts +85 -0
  91. package/templates/_base-website/i18n/sections/home/id.ts +85 -0
  92. package/templates/_base-website/i18n/sections/home/it.ts +85 -0
  93. package/templates/_base-website/i18n/sections/home/ja.ts +85 -0
  94. package/templates/_base-website/i18n/sections/home/ko.ts +85 -0
  95. package/templates/_base-website/i18n/sections/home/nl.ts +85 -0
  96. package/templates/_base-website/i18n/sections/home/pl.ts +85 -0
  97. package/templates/_base-website/i18n/sections/home/pt.ts +85 -0
  98. package/templates/_base-website/i18n/sections/home/ru.ts +85 -0
  99. package/templates/_base-website/i18n/sections/home/sv.ts +85 -0
  100. package/templates/_base-website/i18n/sections/home/th.ts +85 -0
  101. package/templates/_base-website/i18n/sections/home/tr.ts +85 -0
  102. package/templates/_base-website/i18n/sections/home/vi.ts +85 -0
  103. package/templates/_base-website/i18n/sections/home/zh.ts +85 -0
  104. package/templates/_base-website/i18n/sections/support/_index.ts +41 -0
  105. package/templates/_base-website/i18n/sections/support/ar.ts +37 -0
  106. package/templates/_base-website/i18n/sections/support/de.ts +37 -0
  107. package/templates/_base-website/i18n/sections/support/en.ts +37 -0
  108. package/templates/_base-website/i18n/sections/support/es.ts +37 -0
  109. package/templates/_base-website/i18n/sections/support/fr.ts +37 -0
  110. package/templates/_base-website/i18n/sections/support/hi.ts +37 -0
  111. package/templates/_base-website/i18n/sections/support/id.ts +37 -0
  112. package/templates/_base-website/i18n/sections/support/it.ts +37 -0
  113. package/templates/_base-website/i18n/sections/support/ja.ts +37 -0
  114. package/templates/_base-website/i18n/sections/support/ko.ts +37 -0
  115. package/templates/_base-website/i18n/sections/support/nl.ts +37 -0
  116. package/templates/_base-website/i18n/sections/support/pl.ts +37 -0
  117. package/templates/_base-website/i18n/sections/support/pt.ts +37 -0
  118. package/templates/_base-website/i18n/sections/support/ru.ts +37 -0
  119. package/templates/_base-website/i18n/sections/support/sv.ts +37 -0
  120. package/templates/_base-website/i18n/sections/support/th.ts +37 -0
  121. package/templates/_base-website/i18n/sections/support/tr.ts +37 -0
  122. package/templates/_base-website/i18n/sections/support/vi.ts +37 -0
  123. package/templates/_base-website/i18n/sections/support/zh.ts +37 -0
  124. package/templates/_base-website/i18n/translations.ts +25 -0
  125. package/templates/_base-website/index.ts +460 -0
  126. package/templates/_base-website/pages/404.tsx +35 -0
  127. package/templates/_base-website/pages/blog/author.tsx +97 -0
  128. package/templates/_base-website/pages/blog/category.tsx +89 -0
  129. package/templates/_base-website/pages/blog/index.tsx +81 -0
  130. package/templates/_base-website/pages/blog/post.tsx +110 -0
  131. package/templates/_base-website/pages/blog/tag.tsx +86 -0
  132. package/templates/_base-website/pages/custom-pages/example.tsx +54 -0
  133. package/templates/_base-website/pages/index.tsx +29 -0
  134. package/templates/_base-website/pages/privacy.tsx +45 -0
  135. package/templates/_base-website/pages/support.tsx +154 -0
  136. package/templates/_base-website/pages/terms.tsx +68 -0
  137. package/templates/_base-website/public/images/16.png +0 -0
  138. package/templates/_base-website/public/images/apple-touch-icon.png +0 -0
  139. package/templates/_base-website/public/images/favicon-32x32.png +0 -0
  140. package/templates/_base-website/public/images/favicon.png +0 -0
  141. package/templates/_base-website/public/images/logo_dark.svg +6 -0
  142. package/templates/_base-website/public/images/logo_light.svg +6 -0
  143. package/templates/_base-website/public/images/og-image.png +0 -0
  144. package/templates/_base-website/public/images/screenshots/ar_dashboard.jpg +0 -0
  145. package/templates/_base-website/public/images/screenshots/de_dashboard.jpg +0 -0
  146. package/templates/_base-website/public/images/screenshots/en_dashboard.jpg +0 -0
  147. package/templates/_base-website/public/images/screenshots/es_dashboard.jpg +0 -0
  148. package/templates/_base-website/public/images/screenshots/fr_dashboard.jpg +0 -0
  149. package/templates/_base-website/public/images/screenshots/hi_dashboard.jpg +0 -0
  150. package/templates/_base-website/public/images/screenshots/id_dashboard.jpg +0 -0
  151. package/templates/_base-website/public/images/screenshots/it_dashboard.jpg +0 -0
  152. package/templates/_base-website/public/images/screenshots/ja_dashboard.jpg +0 -0
  153. package/templates/_base-website/public/images/screenshots/ko_dashboard.jpg +0 -0
  154. package/templates/_base-website/public/images/screenshots/nl_dashboard.jpg +0 -0
  155. package/templates/_base-website/public/images/screenshots/pl_dashboard.jpg +0 -0
  156. package/templates/_base-website/public/images/screenshots/pt_dashboard.jpg +0 -0
  157. package/templates/_base-website/public/images/screenshots/ru_dashboard.jpg +0 -0
  158. package/templates/_base-website/public/images/screenshots/sv_dashboard.jpg +0 -0
  159. package/templates/_base-website/public/images/screenshots/th_dashboard.jpg +0 -0
  160. package/templates/_base-website/public/images/screenshots/tr_dashboard.jpg +0 -0
  161. package/templates/_base-website/public/images/screenshots/vi_dashboard.jpg +0 -0
  162. package/templates/_base-website/public/images/screenshots/zh_dashboard.jpg +0 -0
  163. package/templates/_base-website/rothzerg.template.json +63 -0
  164. package/templates/_base-website/styles/404.css +32 -0
  165. package/templates/_base-website/styles/_app.css +131 -0
  166. package/templates/_base-website/styles/_shared.css +194 -0
  167. package/templates/_base-website/styles/index.css +1 -0
  168. package/templates/_base-website/styles/privacy.css +1 -0
  169. package/templates/_base-website/styles/support.css +148 -0
  170. package/templates/_base-website/styles/terms.css +22 -0
  171. package/templates/mobile-app/_gitignore +46 -0
  172. package/templates/mobile-app/analysis_options.yaml +28 -0
  173. package/templates/mobile-app/android/app/build.gradle.kts +56 -0
  174. package/templates/mobile-app/android/app/google-services.json +29 -0
  175. package/templates/mobile-app/android/app/src/debug/AndroidManifest.xml +7 -0
  176. package/templates/mobile-app/android/app/src/main/AndroidManifest.xml +54 -0
  177. package/templates/mobile-app/android/app/src/main/kotlin/com/example/{{projectNameSnake}}/MainActivity.kt +5 -0
  178. package/templates/mobile-app/android/app/src/main/res/drawable/launch_background.xml +12 -0
  179. package/templates/mobile-app/android/app/src/main/res/drawable-v21/launch_background.xml +12 -0
  180. package/templates/mobile-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  181. package/templates/mobile-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  182. package/templates/mobile-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  183. package/templates/mobile-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  184. package/templates/mobile-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  185. package/templates/mobile-app/android/app/src/main/res/values/styles.xml +18 -0
  186. package/templates/mobile-app/android/app/src/main/res/values-night/styles.xml +18 -0
  187. package/templates/mobile-app/android/app/src/profile/AndroidManifest.xml +7 -0
  188. package/templates/mobile-app/android/build.gradle.kts +24 -0
  189. package/templates/mobile-app/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  190. package/templates/mobile-app/android/gradle.properties +2 -0
  191. package/templates/mobile-app/android/settings.gradle.kts +31 -0
  192. package/templates/mobile-app/assets/icons/logo.png +0 -0
  193. package/templates/mobile-app/assets/icons/logo_dark.svg +5 -0
  194. package/templates/mobile-app/assets/icons/logo_light.svg +5 -0
  195. package/templates/mobile-app/assets/lottie/tick.json +1 -0
  196. package/templates/mobile-app/devtools_options.yaml +3 -0
  197. package/templates/mobile-app/ios/Flutter/AppFrameworkInfo.plist +26 -0
  198. package/templates/mobile-app/ios/Flutter/Debug.xcconfig +2 -0
  199. package/templates/mobile-app/ios/Flutter/Release.xcconfig +2 -0
  200. package/templates/mobile-app/ios/Podfile +46 -0
  201. package/templates/mobile-app/ios/Podfile.lock +1807 -0
  202. package/templates/mobile-app/ios/Runner/AppDelegate.swift +16 -0
  203. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +122 -0
  204. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png +0 -0
  205. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png +0 -0
  206. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png +0 -0
  207. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png +0 -0
  208. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png +0 -0
  209. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png +0 -0
  210. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png +0 -0
  211. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png +0 -0
  212. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png +0 -0
  213. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png +0 -0
  214. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png +0 -0
  215. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png +0 -0
  216. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png +0 -0
  217. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png +0 -0
  218. package/templates/mobile-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png +0 -0
  219. package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json +23 -0
  220. package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png +0 -0
  221. package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png +0 -0
  222. package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png +0 -0
  223. package/templates/mobile-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md +5 -0
  224. package/templates/mobile-app/ios/Runner/Base.lproj/LaunchScreen.storyboard +37 -0
  225. package/templates/mobile-app/ios/Runner/Base.lproj/Main.storyboard +26 -0
  226. package/templates/mobile-app/ios/Runner/GoogleService-Info.plist +30 -0
  227. package/templates/mobile-app/ios/Runner/Info.plist +70 -0
  228. package/templates/mobile-app/ios/Runner/Runner-Bridging-Header.h +1 -0
  229. package/templates/mobile-app/ios/Runner.xcodeproj/project.pbxproj +772 -0
  230. package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  231. package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  232. package/templates/mobile-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  233. package/templates/mobile-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +101 -0
  234. package/templates/mobile-app/ios/Runner.xcworkspace/contents.xcworkspacedata +10 -0
  235. package/templates/mobile-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  236. package/templates/mobile-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +8 -0
  237. package/templates/mobile-app/ios/RunnerTests/RunnerTests.swift +12 -0
  238. package/templates/mobile-app/l10n.yaml +5 -0
  239. package/templates/mobile-app/lib/core/config/app_config.dart +76 -0
  240. package/templates/mobile-app/lib/core/constants/app_colors.dart +13 -0
  241. package/templates/mobile-app/lib/core/constants/app_constants.dart +123 -0
  242. package/templates/mobile-app/lib/core/constants/countries.dart +199 -0
  243. package/templates/mobile-app/lib/core/constants/keys.dart +46 -0
  244. package/templates/mobile-app/lib/core/constants/languages.dart +51 -0
  245. package/templates/mobile-app/lib/core/services/admin_service.dart +271 -0
  246. package/templates/mobile-app/lib/core/services/analytics_service.dart +296 -0
  247. package/templates/mobile-app/lib/core/services/auth_service.dart +185 -0
  248. package/templates/mobile-app/lib/core/services/device_service.dart +228 -0
  249. package/templates/mobile-app/lib/core/services/firebase_service.dart +490 -0
  250. package/templates/mobile-app/lib/core/services/in_app_messaging_service.dart +166 -0
  251. package/templates/mobile-app/lib/core/services/logging_service.dart +451 -0
  252. package/templates/mobile-app/lib/core/services/messaging_service.dart +460 -0
  253. package/templates/mobile-app/lib/core/services/remote_config_service.dart +167 -0
  254. package/templates/mobile-app/lib/core/services/revenue_cat_service.dart +364 -0
  255. package/templates/mobile-app/lib/core/services/storage_service.dart +313 -0
  256. package/templates/mobile-app/lib/core/theme/dark_theme.dart +295 -0
  257. package/templates/mobile-app/lib/core/theme/light_theme.dart +296 -0
  258. package/templates/mobile-app/lib/core/utils/formatters.dart +236 -0
  259. package/templates/mobile-app/lib/core/utils/image_picker_helper.dart +78 -0
  260. package/templates/mobile-app/lib/core/utils/validators.dart +292 -0
  261. package/templates/mobile-app/lib/firebase_options.dart +19 -0
  262. package/templates/mobile-app/lib/l10n/app_en.arb +1371 -0
  263. package/templates/mobile-app/lib/l10n/app_localizations.dart +2042 -0
  264. package/templates/mobile-app/lib/l10n/app_localizations_en.dart +1033 -0
  265. package/templates/mobile-app/lib/main.dart +152 -0
  266. package/templates/mobile-app/lib/models/device_model.dart +90 -0
  267. package/templates/mobile-app/lib/models/news_banner.dart +53 -0
  268. package/templates/mobile-app/lib/models/user_model.dart +120 -0
  269. package/templates/mobile-app/lib/providers/admin_provider.dart +67 -0
  270. package/templates/mobile-app/lib/providers/auth_provider.dart +328 -0
  271. package/templates/mobile-app/lib/providers/remote_config_provider.dart +81 -0
  272. package/templates/mobile-app/lib/providers/subscription_provider.dart +67 -0
  273. package/templates/mobile-app/lib/providers/theme_provider.dart +51 -0
  274. package/templates/mobile-app/lib/screens/admin/admin_panel_screen.dart +436 -0
  275. package/templates/mobile-app/lib/screens/admin/admin_panel_user_details_screen.dart +942 -0
  276. package/templates/mobile-app/lib/screens/admin/admin_panel_users_screen.dart +344 -0
  277. package/templates/mobile-app/lib/screens/auth/auth_home_screen.dart +266 -0
  278. package/templates/mobile-app/lib/screens/auth/forgot_password_screen.dart +214 -0
  279. package/templates/mobile-app/lib/screens/auth/login_screen.dart +312 -0
  280. package/templates/mobile-app/lib/screens/auth/profile_setup_screen.dart +157 -0
  281. package/templates/mobile-app/lib/screens/auth/register_screen.dart +288 -0
  282. package/templates/mobile-app/lib/screens/developer/developer_panel_screen.dart +436 -0
  283. package/templates/mobile-app/lib/screens/developer/remote_config_screen.dart +315 -0
  284. package/templates/mobile-app/lib/screens/developer/remote_config_test_screen.dart +461 -0
  285. package/templates/mobile-app/lib/screens/home/home_screen.dart +270 -0
  286. package/templates/mobile-app/lib/screens/legal/privacy_policy_screen.dart +63 -0
  287. package/templates/mobile-app/lib/screens/legal/terms_screen.dart +82 -0
  288. package/templates/mobile-app/lib/screens/main_navigation_screen.dart +85 -0
  289. package/templates/mobile-app/lib/screens/premium/subscription_screen.dart +516 -0
  290. package/templates/mobile-app/lib/screens/profile/edit_profile_screen.dart +246 -0
  291. package/templates/mobile-app/lib/screens/settings/about_app_screen.dart +200 -0
  292. package/templates/mobile-app/lib/screens/settings/cache_manager_screen.dart +400 -0
  293. package/templates/mobile-app/lib/screens/settings/change_password_screen.dart +269 -0
  294. package/templates/mobile-app/lib/screens/settings/delete_account_screen.dart +261 -0
  295. package/templates/mobile-app/lib/screens/settings/settings_screen.dart +400 -0
  296. package/templates/mobile-app/lib/screens/splash/splash_screen.dart +125 -0
  297. package/templates/mobile-app/lib/widgets/app_button.dart +255 -0
  298. package/templates/mobile-app/lib/widgets/app_drawer.dart +207 -0
  299. package/templates/mobile-app/lib/widgets/bottom_sheet_selector.dart +79 -0
  300. package/templates/mobile-app/lib/widgets/button_banner.dart +177 -0
  301. package/templates/mobile-app/lib/widgets/country_picker.dart +169 -0
  302. package/templates/mobile-app/lib/widgets/custom_app_bar.dart +30 -0
  303. package/templates/mobile-app/lib/widgets/email_verification_banner.dart +163 -0
  304. package/templates/mobile-app/lib/widgets/empty_screen.dart +42 -0
  305. package/templates/mobile-app/lib/widgets/language_picker.dart +145 -0
  306. package/templates/mobile-app/lib/widgets/news_banner_widget.dart +264 -0
  307. package/templates/mobile-app/lib/widgets/remote_config/remote_config_widgets.dart +294 -0
  308. package/templates/mobile-app/lib/widgets/section_header.dart +23 -0
  309. package/templates/mobile-app/lib/widgets/success_overlay.dart +117 -0
  310. package/templates/mobile-app/lib/widgets/top_notification.dart +263 -0
  311. package/templates/mobile-app/lib/widgets/user_avatar_picker.dart +58 -0
  312. package/templates/mobile-app/pubspec.yaml +97 -0
  313. package/templates/mobile-app/rothzerg.template.json +43 -0
  314. package/templates/web-app/_gitignore +4 -0
  315. package/templates/web-app/index.html +12 -0
  316. package/templates/web-app/package.json +23 -0
  317. package/templates/web-app/rothzerg.template.json +41 -0
  318. package/templates/web-app/src/App.css +6 -0
  319. package/templates/web-app/src/App.tsx +12 -0
  320. package/templates/web-app/src/index.css +14 -0
  321. package/templates/web-app/src/main.tsx +10 -0
  322. package/templates/web-app/tsconfig.app.json +20 -0
  323. package/templates/web-app/tsconfig.json +7 -0
  324. package/templates/web-app/tsconfig.node.json +18 -0
  325. package/templates/web-app/vite.config.ts +6 -0
  326. package/templates/website-blog/overrides/config.ts +76 -0
  327. package/templates/website-blog/overrides/pages/index.tsx +63 -0
  328. package/templates/website-blog/rothzerg.template.json +29 -0
  329. package/templates/website-business/overrides/App.tsx +49 -0
  330. package/templates/website-business/overrides/components/SiteHeroSection.tsx +33 -0
  331. package/templates/website-business/overrides/config.ts +76 -0
  332. package/templates/website-business/overrides/pages/about.tsx +50 -0
  333. package/templates/website-business/overrides/pages/contact.tsx +61 -0
  334. package/templates/website-business/overrides/pages/index.tsx +37 -0
  335. package/templates/website-business/overrides/pages/services.tsx +70 -0
  336. package/templates/website-business/rothzerg.template.json +18 -0
  337. package/templates/website-mobile/overrides/App.tsx +62 -0
  338. package/templates/website-mobile/overrides/components/SiteHeroSection.tsx +18 -0
  339. package/templates/website-mobile/overrides/config.ts +76 -0
  340. package/templates/website-mobile/overrides/pages/index.tsx +34 -0
  341. package/templates/website-mobile/rothzerg.template.json +64 -0
@@ -0,0 +1,37 @@
1
+ export const supportSV: Record<string, any> = {
2
+ "supportPage": {
3
+ "title": "Support & Hjälpcenter",
4
+ "subtitle": "Vi är här för att hjälpa",
5
+ "faqTitle": "Vanliga Frågor",
6
+ "faq": [
7
+ {
8
+ "question": "Hur kommer jag igång?",
9
+ "answer": "Ladda ner och skapa konto."
10
+ },
11
+ {
12
+ "question": "Är Emrius gratis?",
13
+ "answer": "Det finns en gratis plan."
14
+ },
15
+ {
16
+ "question": "Hur avbryter jag?",
17
+ "answer": "I enhetens inställningar."
18
+ },
19
+ {
20
+ "question": "Kan jag använda offline?",
21
+ "answer": "Ja, efter nedladdning."
22
+ },
23
+ {
24
+ "question": "Hur spårar jag framsteg?",
25
+ "answer": "I instrumentpanelen."
26
+ }
27
+ ],
28
+ "contactTitle": "Kontakta Oss",
29
+ "contactSubtitle": "Kontakta supportteamet.",
30
+ "generalSupport": "Allmän Support",
31
+ "generalSupportDesc": "Appfrågor",
32
+ "legalInquiries": "Juridiska Frågor",
33
+ "legalInquiriesDesc": "Juridiska frågor",
34
+ "privacyConcerns": "Integritet",
35
+ "privacyConcernsDesc": "Datafrågor"
36
+ }
37
+ };
@@ -0,0 +1,37 @@
1
+ export const supportTH: Record<string, any> = {
2
+ "supportPage": {
3
+ "title": "การสนับสนุน",
4
+ "subtitle": "เราอยู่ที่นี่เพื่อช่วย",
5
+ "faqTitle": "คำถามที่พบบ่อย",
6
+ "faq": [
7
+ {
8
+ "question": "เริ่มต้นอย่างไร?",
9
+ "answer": "ดาวน์โหลดและสร้างบัญชี"
10
+ },
11
+ {
12
+ "question": "Emrius ฟรีหรือไม่?",
13
+ "answer": "มีแผนฟรี"
14
+ },
15
+ {
16
+ "question": "ยกเลิกอย่างไร?",
17
+ "answer": "ในการตั้งค่าอุปกรณ์"
18
+ },
19
+ {
20
+ "question": "ใช้ออฟไลน์ได้ไหม?",
21
+ "answer": "ได้หลังดาวน์โหลด"
22
+ },
23
+ {
24
+ "question": "ติดตามความก้าวหน้าอย่างไร?",
25
+ "answer": "ในแดชบอร์ด"
26
+ }
27
+ ],
28
+ "contactTitle": "ติดต่อเรา",
29
+ "contactSubtitle": "ติดต่อทีมสนับสนุน",
30
+ "generalSupport": "การสนับสนุนทั่วไป",
31
+ "generalSupportDesc": "คำถามเกี่ยวกับแอป",
32
+ "legalInquiries": "คำถามทางกฎหมาย",
33
+ "legalInquiriesDesc": "เรื่องทางกฎหมาย",
34
+ "privacyConcerns": "ความเป็นส่วนตัว",
35
+ "privacyConcernsDesc": "คำถามเกี่ยวกับข้อมูล"
36
+ }
37
+ };
@@ -0,0 +1,37 @@
1
+ export const supportTR: Record<string, any> = {
2
+ "supportPage": {
3
+ "title": "Destek ve Yardım Merkezi",
4
+ "subtitle": "Emrius'tan en iyi şekilde yararlanmanıza yardımcı oluruz",
5
+ "faqTitle": "Sık Sorulan Sorular",
6
+ "faq": [
7
+ {
8
+ "question": "Emrius'a nasıl başlarım?",
9
+ "answer": "İndirin ve hesap oluşturun."
10
+ },
11
+ {
12
+ "question": "Emrius ücretsiz mi?",
13
+ "answer": "Ücretsiz plan mevcuttur."
14
+ },
15
+ {
16
+ "question": "Aboneliğimi nasıl iptal ederim?",
17
+ "answer": "Cihaz ayarlarından."
18
+ },
19
+ {
20
+ "question": "Çevrimdışı kullanabilir miyim?",
21
+ "answer": "Evet, indirme sonrası."
22
+ },
23
+ {
24
+ "question": "İlerlemi nasıl takip ederim?",
25
+ "answer": "Pano ekranında."
26
+ }
27
+ ],
28
+ "contactTitle": "Bize Ulaşın",
29
+ "contactSubtitle": "Destek ekibimize ulaşın.",
30
+ "generalSupport": "Genel Destek",
31
+ "generalSupportDesc": "Uygulama soruları için",
32
+ "legalInquiries": "Yasal Sorular",
33
+ "legalInquiriesDesc": "Yasal konular için",
34
+ "privacyConcerns": "Gizlilik Soruları",
35
+ "privacyConcernsDesc": "Veri soruları için"
36
+ }
37
+ };
@@ -0,0 +1,37 @@
1
+ export const supportVI: Record<string, any> = {
2
+ "supportPage": {
3
+ "title": "Hỗ Trợ & Trung Tâm Trợ Giúp",
4
+ "subtitle": "Chúng tôi ở đây để giúp",
5
+ "faqTitle": "Câu Hỏi Thường Gặp",
6
+ "faq": [
7
+ {
8
+ "question": "Bắt đầu như thế nào?",
9
+ "answer": "Tải về và tạo tài khoản."
10
+ },
11
+ {
12
+ "question": "Emrius có miễn phí không?",
13
+ "answer": "Có gói miễn phí."
14
+ },
15
+ {
16
+ "question": "Hủy đăng ký như thế nào?",
17
+ "answer": "Trong cài đặt thiết bị."
18
+ },
19
+ {
20
+ "question": "Có thể dùng offline không?",
21
+ "answer": "Có sau khi tải."
22
+ },
23
+ {
24
+ "question": "Theo dõi tiến độ như thế nào?",
25
+ "answer": "Trong bảng điều khiển."
26
+ }
27
+ ],
28
+ "contactTitle": "Liên Hệ",
29
+ "contactSubtitle": "Liên hệ đội hỗ trợ.",
30
+ "generalSupport": "Hỗ Trợ Chung",
31
+ "generalSupportDesc": "Câu hỏi về ứng dụng",
32
+ "legalInquiries": "Câu Hỏi Pháp Lý",
33
+ "legalInquiriesDesc": "Vấn đề pháp lý",
34
+ "privacyConcerns": "Bảo Mật",
35
+ "privacyConcernsDesc": "Câu hỏi về dữ liệu"
36
+ }
37
+ };
@@ -0,0 +1,37 @@
1
+ export const supportZH: Record<string, any> = {
2
+ "supportPage": {
3
+ "title": "支持与帮助中心",
4
+ "subtitle": "帮助您充分利用Emrius",
5
+ "faqTitle": "常见问题",
6
+ "faq": [
7
+ {
8
+ "question": "如何开始使用Emrius?",
9
+ "answer": "下载并创建账户。"
10
+ },
11
+ {
12
+ "question": "Emrius是免费的吗?",
13
+ "answer": "有免费版本。"
14
+ },
15
+ {
16
+ "question": "如何取消订阅?",
17
+ "answer": "通过设备设置取消。"
18
+ },
19
+ {
20
+ "question": "可以离线使用吗?",
21
+ "answer": "下载后可以。"
22
+ },
23
+ {
24
+ "question": "如何追踪进度?",
25
+ "answer": "在仪表板中查看。"
26
+ }
27
+ ],
28
+ "contactTitle": "联系我们",
29
+ "contactSubtitle": "联系我们的支持团队。",
30
+ "generalSupport": "一般支持",
31
+ "generalSupportDesc": "应用相关问题",
32
+ "legalInquiries": "法律咨询",
33
+ "legalInquiriesDesc": "法律事务",
34
+ "privacyConcerns": "隐私咨询",
35
+ "privacyConcernsDesc": "数据和隐私问题"
36
+ }
37
+ };
@@ -0,0 +1,25 @@
1
+ // {{siteName}} website translations for all 19 supported languages
2
+ import { commonTranslations } from "./sections/common/_index.ts";
3
+ import { homeTranslations } from "./sections/home/_index.ts";
4
+ import { supportTranslations } from "./sections/support/_index.ts";
5
+ import { deepLinkTranslations } from "./sections/deepLink/_index.ts";
6
+ import { blogTranslations } from "./sections/blog/_index.ts";
7
+
8
+ export const translationData: Record<string, any> = {};
9
+
10
+ const languages = Object.keys(commonTranslations);
11
+
12
+ for (const lang of languages) {
13
+ translationData[lang] = {
14
+ ...commonTranslations[lang],
15
+ ...homeTranslations[lang],
16
+ ...supportTranslations[lang],
17
+ ...deepLinkTranslations[lang],
18
+ ...blogTranslations[lang],
19
+ };
20
+ }
21
+
22
+ // Format for i18next
23
+ export const translations: Record<string, { translation: any }> = Object.fromEntries(
24
+ Object.entries(translationData).map(([lang, data]) => [lang, { translation: data }])
25
+ );
@@ -0,0 +1,460 @@
1
+ import { renderToString } from "react-dom/server";
2
+ import { App } from "./App.tsx";
3
+ import React from "react";
4
+ import { StaticRouter } from "react-router";
5
+ import { generateTailwindCSS } from "../../utils/tailwind-generator.ts";
6
+ import { buildClientBundle } from "../../utils/buildClientBundle.ts";
7
+ import { SUPPORTED_LANGUAGES, LANGUAGE_LOCALES, SITE_CONFIG, DEFAULT_LANGUAGE, isValidLanguage, fetchFromAPI, type Language, APP_ADS_TXT } from "./config.ts";
8
+ import { translationData } from "./i18n/translations.ts";
9
+ import { InitialDataProvider } from "./InitialDataContext.tsx";
10
+ import { getRobotsTxt } from "../../utils/get-robots.ts";
11
+ import { escapeHtml } from "../../utils/html-utils.ts";
12
+ import { generateHreflangTags, generateSitemap } from "../../utils/seo-utils.ts";
13
+ import { matchDeepLinkSegment } from "../../utils/routing-utils.ts";
14
+ import { renderDeepLinkPage, renderNotFound } from "../../utils/render-utils.ts";
15
+ import {
16
+ getBlogPostsWithDetails,
17
+ getBlogPostWithDetails,
18
+ getBlogCategories,
19
+ getBlogTags,
20
+ getBlogAuthor,
21
+ getBlogAuthors,
22
+ getBlogPostsByTag,
23
+ getBlogPostsByCategory,
24
+ getBlogPostsByAuthor,
25
+ } from "../../utils/blog-utils.ts";
26
+
27
+ async function getBlogSidebar(lang: string) {
28
+ const [allPosts, categories, tags] = await Promise.all([
29
+ getBlogPostsWithDetails(SITE_DIR, lang),
30
+ getBlogCategories(SITE_DIR, lang),
31
+ getBlogTags(SITE_DIR, lang),
32
+ ]);
33
+ const recentPosts = allPosts.slice(0, 5).map((p) => ({
34
+ slug: p.slug,
35
+ title: p.title,
36
+ publishedAt: p.publishedAt,
37
+ coverImage: p.coverImage || null,
38
+ }));
39
+ const categoriesWithCount = categories.map((cat) => ({
40
+ ...cat,
41
+ postCount: allPosts.filter((p) => p.categorySlug === cat.slug).length,
42
+ }));
43
+ return { recentPosts, categories: categoriesWithCount, tags };
44
+ }
45
+
46
+ const SITE_DIR = "{{domain}}";
47
+
48
+ // Generate CSS and JS bundles at startup
49
+ const cssInputPath = new URL("./styles/_app.css", import.meta.url).pathname;
50
+ const outputDir = new URL("./public", import.meta.url).pathname;
51
+
52
+ const cssContent = await generateTailwindCSS({ inputPath: cssInputPath, outputDir, returnCssOnly: true });
53
+ const clientName = await buildClientBundle(SITE_CONFIG.domain);
54
+
55
+ function getPageMeta(pathname: string): {
56
+ lang: Language;
57
+ title: string;
58
+ description: string;
59
+ ogType: string;
60
+ } {
61
+ const parts = pathname.split("/").filter(Boolean);
62
+ const langFromPath = parts[0];
63
+ const lang: Language = isValidLanguage(langFromPath) ? langFromPath : "en";
64
+ const page = parts[1] || "home";
65
+
66
+ const t = (translationData[lang] || translationData["en"]) as any;
67
+
68
+ let title = t.meta.homeTitle;
69
+ let description = t.meta.homeDescription;
70
+ let ogType = "website";
71
+
72
+ if (page === "terms") {
73
+ title = t.meta.termsTitle;
74
+ description = t.meta.termsDescription;
75
+ ogType = "article";
76
+ } else if (page === "privacy") {
77
+ title = t.meta.privacyTitle;
78
+ description = t.meta.privacyDescription;
79
+ ogType = "article";
80
+ } else if (page === "support") {
81
+ title = t.meta.supportTitle;
82
+ description = t.meta.supportDescription;
83
+ } else if (page === "blog") {
84
+ title = "Blog";
85
+ description = `Articles, tutorials, and insights on building modern web applications from ${SITE_CONFIG.name}.`;
86
+ }
87
+
88
+ return {
89
+ lang,
90
+ title: `${title} | ${SITE_CONFIG.name}`,
91
+ description,
92
+ ogType,
93
+ };
94
+ }
95
+
96
+ function buildBaseHtml(
97
+ pathname: string,
98
+ lang: Language,
99
+ title: string,
100
+ description: string,
101
+ ogType: string,
102
+ bodyHtml: string,
103
+ extraHead: string = "",
104
+ ogImage?: string,
105
+ initialData?: unknown
106
+ ): string {
107
+ const locale = LANGUAGE_LOCALES[lang] || "en_US";
108
+ const canonicalUrl = `${SITE_CONFIG.url}${pathname}`;
109
+ const isRTL = lang === "ar";
110
+ const safeTitle = escapeHtml(title);
111
+ const safeDesc = escapeHtml(description);
112
+ const imageUrl = ogImage || `${SITE_CONFIG.url}/static/images/og-image.png`;
113
+
114
+ return `<!DOCTYPE html>
115
+ <html lang="${lang}"${isRTL ? ' dir="rtl"' : ""}>
116
+ <head>
117
+ <meta charset="UTF-8">
118
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
119
+ <link rel="icon" type="image/x-icon" href="/static/images/favicon.png">
120
+ <title>${safeTitle}</title>
121
+ <meta name="description" content="${safeDesc}">
122
+ <meta name="keywords" content="${escapeHtml(SITE_CONFIG.keywords)}">
123
+ <meta name="author" content="${SITE_CONFIG.author}">
124
+ <meta name="theme-color" content="${SITE_CONFIG.themeColor}">
125
+ <link rel="canonical" href="${canonicalUrl}">
126
+ ${generateHreflangTags(pathname, SUPPORTED_LANGUAGES, SITE_CONFIG.url, DEFAULT_LANGUAGE)}
127
+ <!-- Open Graph -->
128
+ <meta property="og:type" content="${ogType}">
129
+ <meta property="og:title" content="${safeTitle}">
130
+ <meta property="og:description" content="${safeDesc}">
131
+ <meta property="og:url" content="${canonicalUrl}">
132
+ <meta property="og:site_name" content="${SITE_CONFIG.name}">
133
+ <meta property="og:locale" content="${locale}">
134
+ <meta property="og:image" content="${imageUrl}">
135
+ <!-- Twitter Card -->
136
+ <meta name="twitter:card" content="summary_large_image">
137
+ <meta name="twitter:title" content="${safeTitle}">
138
+ <meta name="twitter:description" content="${safeDesc}">
139
+ <meta name="twitter:site" content="${SITE_CONFIG.twitterHandle}">
140
+ <meta name="twitter:image" content="${imageUrl}">
141
+ <!-- App Links -->
142
+ <meta name="apple-itunes-app" content="app-id=${SITE_CONFIG.iosAppId}">
143
+ ${extraHead}
144
+ ${SITE_CONFIG.googleAnalyticsId ? ` <!-- Google Analytics -->
145
+ <script async src="https://www.googletagmanager.com/gtag/js?id=${SITE_CONFIG.googleAnalyticsId}"></script>
146
+ <script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','${SITE_CONFIG.googleAnalyticsId}');</script>` : ""}
147
+ <script>(function(){var t=localStorage.getItem('theme')||(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');document.documentElement.setAttribute('data-theme',t);})();</script>
148
+ <style>
149
+ :root {
150
+ --color-primary: ${SITE_CONFIG.theme.primary};
151
+ --color-secondary: ${SITE_CONFIG.theme.secondary};
152
+ }
153
+ ${cssContent}
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <div id="root">${bodyHtml}</div>
158
+ ${initialData ? `<script>window.__INITIAL_DATA__ = ${JSON.stringify(initialData).replace(/<\//g, "<\\/")};</script>` : ""}
159
+ <script type="module" src="/static/${clientName}"></script>
160
+ </body>
161
+ </html>`;
162
+ }
163
+
164
+
165
+ export async function handleRequest(hostname: string, pathname: string = "/"): Promise<Response> {
166
+ if (pathname === "/") {
167
+ return new Response(null, {
168
+ status: 302,
169
+ headers: { Location: "/en/" },
170
+ });
171
+ }
172
+
173
+ // ── Blog API routes (/api/blog/:lang/...) ────────────────────────────────
174
+ if (pathname.startsWith("/api/blog/")) {
175
+ const parts = pathname.split("/").filter(Boolean); // ["api","blog",lang,resource?,id?]
176
+ const blogLang = parts[2];
177
+ const resource = parts[3];
178
+ const id = parts[4];
179
+
180
+ const jsonHeaders = { "Content-Type": "application/json; charset=utf-8" };
181
+ const notFoundJson = new Response(JSON.stringify({ error: "Not found" }), { status: 404, headers: jsonHeaders });
182
+
183
+ if (!isValidLanguage(blogLang)) {
184
+ return new Response(JSON.stringify({ error: "Invalid language" }), { status: 400, headers: jsonHeaders });
185
+ }
186
+
187
+ const lang = blogLang as Language;
188
+
189
+ if (resource === "posts" && !id) {
190
+ const posts = await getBlogPostsWithDetails(SITE_DIR, lang);
191
+ return new Response(JSON.stringify(posts), { headers: jsonHeaders });
192
+ }
193
+
194
+ if (resource === "posts" && id) {
195
+ const post = await getBlogPostWithDetails(SITE_DIR, lang, id);
196
+ if (!post) return notFoundJson;
197
+ return new Response(JSON.stringify(post), { headers: jsonHeaders });
198
+ }
199
+
200
+ if (resource === "categories") {
201
+ const categories = await getBlogCategories(SITE_DIR, lang);
202
+ return new Response(JSON.stringify(categories), { headers: jsonHeaders });
203
+ }
204
+
205
+ if (resource === "tags") {
206
+ const tags = await getBlogTags(SITE_DIR, lang);
207
+ return new Response(JSON.stringify(tags), { headers: jsonHeaders });
208
+ }
209
+
210
+ if (resource === "authors" && id) {
211
+ const author = await getBlogAuthor(SITE_DIR, lang, id);
212
+ if (!author) return notFoundJson;
213
+ return new Response(JSON.stringify(author), { headers: jsonHeaders });
214
+ }
215
+
216
+ return notFoundJson;
217
+ }
218
+
219
+ // ── Blog SSR pages (/:lang/blog and /:lang/blog/:slug) ───────────────────
220
+ const pathParts = pathname.split("/").filter(Boolean);
221
+ if (pathParts[1] === "blog" && isValidLanguage(pathParts[0])) {
222
+ const pageLang = pathParts[0] as Language;
223
+ const slug = pathParts[2];
224
+ const perPage = SITE_CONFIG.features.blogPostsPerPage;
225
+
226
+ function paginate<T>(allItems: T[], pageNum: number): { items: T[]; pagination: { page: number; totalPages: number; total: number; baseUrl: string } } {
227
+ const totalPages = Math.max(1, Math.ceil(allItems.length / perPage));
228
+ const safePage = Math.min(Math.max(1, pageNum), totalPages);
229
+ const items = allItems.slice((safePage - 1) * perPage, safePage * perPage);
230
+ return { items, pagination: { page: safePage, totalPages, total: allItems.length, baseUrl: "" } };
231
+ }
232
+
233
+ // ── Tag / Category / Author filter pages ────────────────────────────────
234
+ if (slug === "tag" && pathParts[3]) {
235
+ const tagSlug = pathParts[3];
236
+ const pageNum = pathParts[4] === "page" && pathParts[5] ? parseInt(pathParts[5], 10) || 1 : 1;
237
+ const [{ posts: allPosts, tag }, sidebar] = await Promise.all([
238
+ getBlogPostsByTag(SITE_DIR, pageLang, tagSlug),
239
+ getBlogSidebar(pageLang),
240
+ ]);
241
+ const { items: posts, pagination } = paginate(allPosts, pageNum);
242
+ pagination.baseUrl = `/${pageLang}/blog/tag/${tagSlug}`;
243
+ const pageTitle = `${tag?.name ?? tagSlug} — Tag | ${SITE_CONFIG.name}`;
244
+ const pageDesc = `Posts tagged "${tag?.name ?? tagSlug}" on ${SITE_CONFIG.name}.`;
245
+ const data = { posts, tag, sidebar, pagination };
246
+ const bodyHtml = renderToString(
247
+ React.createElement(StaticRouter, { location: pathname },
248
+ React.createElement(InitialDataProvider, { data },
249
+ React.createElement(App)
250
+ )
251
+ )
252
+ );
253
+ return new Response(
254
+ buildBaseHtml(pathname, pageLang, pageTitle, pageDesc, "website", bodyHtml, "", undefined, data),
255
+ { headers: { "Content-Type": "text/html; charset=utf-8" } }
256
+ );
257
+ }
258
+
259
+ if (slug === "category" && pathParts[3]) {
260
+ const categorySlug = pathParts[3];
261
+ const pageNum = pathParts[4] === "page" && pathParts[5] ? parseInt(pathParts[5], 10) || 1 : 1;
262
+ const [{ posts: allPosts, category }, sidebar] = await Promise.all([
263
+ getBlogPostsByCategory(SITE_DIR, pageLang, categorySlug),
264
+ getBlogSidebar(pageLang),
265
+ ]);
266
+ const { items: posts, pagination } = paginate(allPosts, pageNum);
267
+ pagination.baseUrl = `/${pageLang}/blog/category/${categorySlug}`;
268
+ const pageTitle = `${category?.name ?? categorySlug} — Category | ${SITE_CONFIG.name}`;
269
+ const pageDesc = category?.description ?? `Posts in category "${category?.name ?? categorySlug}" on ${SITE_CONFIG.name}.`;
270
+ const data = { posts, category, sidebar, pagination };
271
+ const bodyHtml = renderToString(
272
+ React.createElement(StaticRouter, { location: pathname },
273
+ React.createElement(InitialDataProvider, { data },
274
+ React.createElement(App)
275
+ )
276
+ )
277
+ );
278
+ return new Response(
279
+ buildBaseHtml(pathname, pageLang, pageTitle, pageDesc, "website", bodyHtml, "", undefined, data),
280
+ { headers: { "Content-Type": "text/html; charset=utf-8" } }
281
+ );
282
+ }
283
+
284
+ if (slug === "author" && pathParts[3]) {
285
+ const authorSlug = pathParts[3];
286
+ const pageNum = pathParts[4] === "page" && pathParts[5] ? parseInt(pathParts[5], 10) || 1 : 1;
287
+ const [{ posts: allPosts, author }, sidebar] = await Promise.all([
288
+ getBlogPostsByAuthor(SITE_DIR, pageLang, authorSlug),
289
+ getBlogSidebar(pageLang),
290
+ ]);
291
+ const { items: posts, pagination } = paginate(allPosts, pageNum);
292
+ pagination.baseUrl = `/${pageLang}/blog/author/${authorSlug}`;
293
+ const pageTitle = `${author?.name ?? authorSlug} — Author | ${SITE_CONFIG.name}`;
294
+ const pageDesc = author?.bio?.substring(0, 160) ?? `Posts by ${author?.name ?? authorSlug} on ${SITE_CONFIG.name}.`;
295
+ const data = { posts, author, sidebar, pagination };
296
+ const bodyHtml = renderToString(
297
+ React.createElement(StaticRouter, { location: pathname },
298
+ React.createElement(InitialDataProvider, { data },
299
+ React.createElement(App)
300
+ )
301
+ )
302
+ );
303
+ return new Response(
304
+ buildBaseHtml(pathname, pageLang, pageTitle, pageDesc, "website", bodyHtml, "", undefined, data),
305
+ { headers: { "Content-Type": "text/html; charset=utf-8" } }
306
+ );
307
+ }
308
+
309
+ // ── Blog listing with optional page: /page/:page ─────────────────────
310
+ if (slug === "page" && pathParts[3]) {
311
+ const pageNum = parseInt(pathParts[3], 10) || 1;
312
+ if (pageNum === 1) {
313
+ return new Response(null, { status: 301, headers: { Location: `/${pageLang}/blog` } });
314
+ }
315
+ const [allPosts, sidebar] = await Promise.all([
316
+ getBlogPostsWithDetails(SITE_DIR, pageLang),
317
+ getBlogSidebar(pageLang),
318
+ ]);
319
+ const { items: posts, pagination } = paginate(allPosts, pageNum);
320
+ pagination.baseUrl = `/${pageLang}/blog`;
321
+ const listTitle = `Blog — Page ${pageNum} | ${SITE_CONFIG.name}`;
322
+ const listDesc = `Articles, tutorials, and insights on building modern web applications from ${SITE_CONFIG.name}.`;
323
+ const data = { posts, sidebar, pagination };
324
+ const bodyHtml = renderToString(
325
+ React.createElement(StaticRouter, { location: pathname },
326
+ React.createElement(InitialDataProvider, { data },
327
+ React.createElement(App)
328
+ )
329
+ )
330
+ );
331
+ return new Response(
332
+ buildBaseHtml(pathname, pageLang, listTitle, listDesc, "website", bodyHtml, "", undefined, data),
333
+ { headers: { "Content-Type": "text/html; charset=utf-8" } }
334
+ );
335
+ }
336
+
337
+ if (slug) {
338
+ // Single blog post page
339
+ const [post, sidebar] = await Promise.all([
340
+ getBlogPostWithDetails(SITE_DIR, pageLang, slug),
341
+ getBlogSidebar(pageLang),
342
+ ]);
343
+ if (!post) {
344
+ return renderNotFound(cssContent, clientName, SITE_CONFIG.name, StaticRouter, App);
345
+ }
346
+
347
+ const postTitle = `${escapeHtml(post.title)} | ${SITE_CONFIG.name}`;
348
+ const postDesc = escapeHtml(post.excerpt.substring(0, 160));
349
+ const jsonLd = JSON.stringify({
350
+ "@context": "https://schema.org",
351
+ "@type": "BlogPosting",
352
+ "headline": post.title,
353
+ "description": post.excerpt,
354
+ "image": post.coverImage || `${SITE_CONFIG.url}/images/og-image.png`,
355
+ "datePublished": post.publishedAt,
356
+ "dateModified": post.updatedAt,
357
+ "author": { "@type": "Person", "name": post.author?.name ?? SITE_CONFIG.author },
358
+ "publisher": { "@type": "Organization", "name": SITE_CONFIG.name, "url": SITE_CONFIG.url },
359
+ "mainEntityOfPage": { "@type": "WebPage", "@id": `${SITE_CONFIG.url}${pathname}` },
360
+ });
361
+ const extraHead = ` <script type="application/ld+json">${jsonLd}</script>`;
362
+ const data = { post, sidebar };
363
+ const bodyHtml = renderToString(
364
+ React.createElement(StaticRouter, { location: pathname },
365
+ React.createElement(InitialDataProvider, { data },
366
+ React.createElement(App)
367
+ )
368
+ )
369
+ );
370
+
371
+ return new Response(
372
+ buildBaseHtml(pathname, pageLang, postTitle, postDesc, "article", bodyHtml, extraHead, post.coverImage || undefined, data),
373
+ { headers: { "Content-Type": "text/html; charset=utf-8" } }
374
+ );
375
+ }
376
+
377
+ // Blog listing page (page 1)
378
+ const [allPosts, sidebar] = await Promise.all([
379
+ getBlogPostsWithDetails(SITE_DIR, pageLang),
380
+ getBlogSidebar(pageLang),
381
+ ]);
382
+ const { items: posts, pagination } = paginate(allPosts, 1);
383
+ pagination.baseUrl = `/${pageLang}/blog`;
384
+ const listTitle = `Blog | ${SITE_CONFIG.name}`;
385
+ const listDesc = `Articles, tutorials, and insights on building modern web applications from ${SITE_CONFIG.name}.`;
386
+ const data = { posts, sidebar, pagination };
387
+ const bodyHtml = renderToString(
388
+ React.createElement(StaticRouter, { location: pathname },
389
+ React.createElement(InitialDataProvider, { data },
390
+ React.createElement(App)
391
+ )
392
+ )
393
+ );
394
+
395
+ return new Response(
396
+ buildBaseHtml(pathname, pageLang, listTitle, listDesc, "website", bodyHtml, "", undefined, data),
397
+ { headers: { "Content-Type": "text/html; charset=utf-8" } }
398
+ );
399
+ }
400
+
401
+ if (pathname === "/app-ads.txt") {
402
+ return new Response(APP_ADS_TXT, {
403
+ headers: {
404
+ "Content-Type": "text/plain",
405
+ },
406
+ });
407
+ }
408
+
409
+ // robots.txt
410
+ if (pathname === "/robots.txt") {
411
+ return getRobotsTxt(SITE_CONFIG.url, hostname.includes("dev."))
412
+ }
413
+
414
+ // sitemap.xml
415
+ if (pathname === "/sitemap.xml") {
416
+ const [posts, categories, tags, authors] = await Promise.all([
417
+ getBlogPostsWithDetails(SITE_DIR, DEFAULT_LANGUAGE),
418
+ getBlogCategories(SITE_DIR, DEFAULT_LANGUAGE),
419
+ getBlogTags(SITE_DIR, DEFAULT_LANGUAGE),
420
+ getBlogAuthors(SITE_DIR, DEFAULT_LANGUAGE),
421
+ ]);
422
+
423
+ const pages = [
424
+ { path: "", priority: "1.0", changefreq: "weekly" },
425
+ { path: "/blog", priority: "0.9", changefreq: "daily" },
426
+ ...posts.map((p) => ({ path: `/blog/${p.slug}`, priority: "0.8", changefreq: "monthly" })),
427
+ ...categories.map((c) => ({ path: `/blog/category/${c.slug}`, priority: "0.6", changefreq: "weekly" })),
428
+ ...tags.map((t) => ({ path: `/blog/tag/${t.slug}`, priority: "0.5", changefreq: "weekly" })),
429
+ ...authors.map((a) => ({ path: `/blog/author/${a.slug}`, priority: "0.6", changefreq: "weekly" })),
430
+ { path: "/support", priority: "0.8", changefreq: "monthly" },
431
+ { path: "/privacy", priority: "0.5", changefreq: "yearly" },
432
+ { path: "/terms", priority: "0.5", changefreq: "yearly" },
433
+ ];
434
+
435
+ const sitemap = generateSitemap(
436
+ pages,
437
+ SUPPORTED_LANGUAGES,
438
+ SITE_CONFIG.url,
439
+ DEFAULT_LANGUAGE
440
+ );
441
+
442
+ return new Response(sitemap, {
443
+ headers: {
444
+ "Content-Type": "application/xml; charset=utf-8",
445
+ "Cache-Control": "public, max-age=86400",
446
+ },
447
+ });
448
+ }
449
+
450
+ const { lang, title, description, ogType } = getPageMeta(pathname);
451
+
452
+ const bodyHtml = renderToString(
453
+ React.createElement(StaticRouter, { location: pathname }, React.createElement(App))
454
+ );
455
+
456
+ return new Response(
457
+ buildBaseHtml(pathname, lang, title, description, ogType, bodyHtml),
458
+ { headers: { "Content-Type": "text/html; charset=utf-8" } }
459
+ );
460
+ }