diff --git a/package-lock.json b/package-lock.json index b2452e3c..1c83eef1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,6 @@ "mathml-tag-names": "^3.0.2", "mobx": "^6.10.2", "mobx-form-lite": "^0.9.61", - "mobx-log": "^2.2.3", "mobx-persist-store": "^1.1.3", "mobx-react-lite": "^4.0.5", "notistack": "^3.0.1", @@ -62,6 +61,7 @@ "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "mobx-log": "^2.2.3", "pre-commit": "^1.2.2", "prettier": "^3.0.3", "typescript": "^5.0.2", @@ -5212,6 +5212,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/mobx-log/-/mobx-log-2.2.3.tgz", "integrity": "sha512-xvEkJEP/8UZXon/yrHfbZ2S+OeNLEZDYuInP7u0SpyIEg9Y915RMpOHcimDXkJ+KGju6r2RTLodJYTBCKTb71Q==", + "dev": true, "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index c39cee26..b3dfd943 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "version": "0.0.0", "type": "module", "scripts": { - "start": "npx concurrently --kill-others-on-fail \"npm run dev:frontend:start\" \"npm run dev:api:start\" \"npm run dev:tunnel\"", + "start:telegram": "npx concurrently --kill-others-on-fail \"npm run dev:frontend:start\" \"npm run dev:api:start\" \"npm run dev:tunnel\"", + "start:browser": "npx concurrently --kill-others-on-fail \"npm run dev:frontend:start\" \"npm run dev:api:start\"", "dev:frontend:start": "vite", "dev:api:start": "npx wrangler pages dev /functions --compatibility-date=2023-09-22 --compatibility-flags=\"nodejs_compat\"", "dev:tunnel": "../ngrok http --domain=causal-magpie-closing.ngrok-free.app 5173", @@ -43,7 +44,6 @@ "mathml-tag-names": "^3.0.2", "mobx": "^6.10.2", "mobx-form-lite": "^0.9.61", - "mobx-log": "^2.2.3", "mobx-persist-store": "^1.1.3", "mobx-react-lite": "^4.0.5", "notistack": "^3.0.1", @@ -82,6 +82,7 @@ "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", + "mobx-log": "^2.2.3", "pre-commit": "^1.2.2", "prettier": "^3.0.3", "typescript": "^5.0.2", diff --git a/src/index.css b/src/index.css index 35462745..916c278f 100644 --- a/src/index.css +++ b/src/index.css @@ -59,3 +59,8 @@ div, a { -webkit-tap-highlight-color: transparent; } + +/* fix for browser version to avoid monospace */ +textarea { + font-family: inherit; +} diff --git a/src/lib/mobx-form-lite-persistable/persistable-field.ts b/src/lib/mobx-form-lite-persistable/persistable-field.ts index ce01c372..000d7020 100644 --- a/src/lib/mobx-form-lite-persistable/persistable-field.ts +++ b/src/lib/mobx-form-lite-persistable/persistable-field.ts @@ -1,5 +1,5 @@ import { makePersistable } from "mobx-persist-store"; -import { storageAdapter } from "../telegram/storage-adapter.ts"; +import { storageAdapter } from "../platform/storage-adapter.ts"; import { FieldWithValue } from "mobx-form-lite"; export const persistableField = >( diff --git a/src/lib/platform/browser/browser-platform.ts b/src/lib/platform/browser/browser-platform.ts new file mode 100644 index 00000000..c4bbe5b8 --- /dev/null +++ b/src/lib/platform/browser/browser-platform.ts @@ -0,0 +1,113 @@ +import { Platform, PlatformTheme } from "../platform.ts"; +import { makeAutoObservable } from "mobx"; +import { assert } from "../../typescript/assert.ts"; +import { BooleanToggle } from "mobx-form-lite"; + +const cssVariables = { + "--tg-theme-hint-color": "#999999", + "--tg-theme-secondary-bg-color": "#efeff3", + "--tg-theme-text-color": "#000000", + "--tg-theme-section-bg-color": "#ffffff", + "--tg-theme-header-bg-color": "#efeff3", + "--tg-theme-accent-text-color": "#2481cc", + "--tg-color-scheme": "light", + "--tg-viewport-height": "100vh", + "--tg-theme-destructive-text-color": "#ff3b30", + "--tg-theme-button-color": "#2481cc", + "--tg-theme-bg-color": "#ffffff", + "--tg-theme-subtitle-text-color": "#999999", + "--tg-theme-button-text-color": "#ffffff", + "--tg-theme-section-header-text-color": "#6d6d71", + "--tg-theme-link-color": "#2481cc", + "--tg-viewport-stable-height": "100vh", +}; + +export class BrowserPlatform implements Platform { + mainButtonInfo?: { + text: string; + onClick: () => void; + condition?: () => boolean; + }; + isMainButtonLoading = new BooleanToggle(false); + backButtonInfo?: { + onClick: () => void; + }; + + constructor() { + makeAutoObservable( + this, + { + getTheme: false, + getInitData: false, + initialize: false, + openInternalLink: false, + openExternalLink: false, + }, + { + autoBind: true, + }, + ); + } + + getTheme(): PlatformTheme { + return { + buttonColor: cssVariables["--tg-theme-button-color"], + hintColor: cssVariables["--tg-theme-hint-color"], + buttonTextColor: cssVariables["--tg-theme-button-text-color"], + }; + } + + showMainButton(text: string, onClick: () => void, condition?: () => boolean) { + this.mainButtonInfo = { + text, + onClick, + condition, + }; + } + + hideMainButton() { + this.mainButtonInfo = undefined; + } + + get isMainButtonVisible() { + return this.mainButtonInfo !== undefined; + } + + showBackButton(onClick: () => void) { + this.backButtonInfo = { + onClick, + }; + } + + hideBackButton() { + this.backButtonInfo = undefined; + } + + get isBackButtonVisible() { + return this.backButtonInfo !== undefined; + } + + getInitData(): string { + const userQuery = import.meta.env.VITE_USER_QUERY; + assert(typeof userQuery === "string", "VITE_USER_QUERY is not defined"); + return userQuery; + } + + initialize() { + for (const variable in cssVariables) { + document.documentElement.style.setProperty( + variable, + // @ts-ignore + cssVariables[variable], + ); + } + } + + openInternalLink(link: string) { + window.location.href = link; + } + + openExternalLink(link: string) { + window.open(link, "_blank"); + } +} diff --git a/src/lib/platform/browser/show-alert-browser.ts b/src/lib/platform/browser/show-alert-browser.ts new file mode 100644 index 00000000..46e1afe6 --- /dev/null +++ b/src/lib/platform/browser/show-alert-browser.ts @@ -0,0 +1,3 @@ +export const showAlertBrowser = (text: string) => { + alert(text); +}; diff --git a/src/lib/platform/browser/show-confirm-browser.tsx b/src/lib/platform/browser/show-confirm-browser.tsx new file mode 100644 index 00000000..f3f8c412 --- /dev/null +++ b/src/lib/platform/browser/show-confirm-browser.tsx @@ -0,0 +1,70 @@ +import React, { useState } from "react"; +import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; +import { theme } from "../../../ui/theme.tsx"; +import { Button } from "../../../ui/button.tsx"; +import { Flex } from "../../../ui/flex.tsx"; +import { t } from "../../../translations/t.ts"; +import { ShowConfirmType } from "../platform.ts"; +import { css } from "@emotion/css"; + +export const showConfirmBrowser: ShowConfirmType = (text) => { + return new Promise((resolve) => { + const Confirmation = () => { + const [isOpen, setIsOpen] = useState(true); + + const handleConfirm = () => { + setIsOpen(false); + resolve(true); + }; + + const handleCancel = () => { + setIsOpen(false); + resolve(false); + }; + + if (!isOpen) { + return null; + } + + return ReactDOM.createPortal( +
+
+

{text}

+ + + + +
+
, + document.body, + ); + }; + + const element = document.createElement("div"); + createRoot(element).render(); + }); +}; diff --git a/src/lib/platform/browser/use-back-button-browser.ts b/src/lib/platform/browser/use-back-button-browser.ts new file mode 100644 index 00000000..7b5ef112 --- /dev/null +++ b/src/lib/platform/browser/use-back-button-browser.ts @@ -0,0 +1,16 @@ +import { platform } from "../platform.ts"; +import { useMount } from "../../react/use-mount.ts"; +import { assert } from "../../typescript/assert.ts"; +import { BrowserPlatform } from "./browser-platform.ts"; + +export const useBackButtonBrowser = (onClick: () => void) => { + useMount(() => { + assert(platform instanceof BrowserPlatform); + platform.showBackButton(onClick); + + return () => { + assert(platform instanceof BrowserPlatform); + platform.hideBackButton(); + }; + }); +}; diff --git a/src/lib/platform/browser/use-main-button-browser.ts b/src/lib/platform/browser/use-main-button-browser.ts new file mode 100644 index 00000000..dfc4a4ab --- /dev/null +++ b/src/lib/platform/browser/use-main-button-browser.ts @@ -0,0 +1,24 @@ +import { platform, UseMainButtonType } from "../platform.ts"; +import { useMount } from "../../react/use-mount.ts"; +import { assert } from "../../typescript/assert.ts"; +import { BrowserPlatform } from "./browser-platform.ts"; + +export const useMainButtonBrowser: UseMainButtonType = ( + text, + onClick, + condition, +) => { + useMount(() => { + assert(platform instanceof BrowserPlatform); + platform.showMainButton( + typeof text === "string" ? text : text(), + onClick, + condition, + ); + + return () => { + assert(platform instanceof BrowserPlatform); + platform.hideMainButton(); + }; + }); +}; diff --git a/src/lib/platform/browser/use-main-button-progress-browser.ts b/src/lib/platform/browser/use-main-button-progress-browser.ts new file mode 100644 index 00000000..d40d788e --- /dev/null +++ b/src/lib/platform/browser/use-main-button-progress-browser.ts @@ -0,0 +1,18 @@ +import { useMount } from "../../react/use-mount.ts"; +import { autorun } from "mobx"; +import { platform } from "../platform.ts"; +import { assert } from "../../typescript/assert.ts"; +import { BrowserPlatform } from "./browser-platform.ts"; + +export const useMainButtonProgressBrowser = (cb: () => boolean) => { + useMount(() => { + return autorun(() => { + assert(platform instanceof BrowserPlatform); + if (cb()) { + platform.isMainButtonLoading.setTrue(); + } else { + platform.isMainButtonLoading.setFalse(); + } + }); + }); +}; diff --git a/src/lib/telegram/is-running-within-telegram.ts b/src/lib/platform/is-running-within-telegram.ts similarity index 100% rename from src/lib/telegram/is-running-within-telegram.ts rename to src/lib/platform/is-running-within-telegram.ts diff --git a/src/lib/platform/platform.ts b/src/lib/platform/platform.ts new file mode 100644 index 00000000..ea0fcaed --- /dev/null +++ b/src/lib/platform/platform.ts @@ -0,0 +1,33 @@ +import { TelegramPlatform } from "./telegram/telegram-platform.ts"; +import { BrowserPlatform } from "./browser/browser-platform.ts"; +import { isRunningWithinTelegram } from "./is-running-within-telegram.ts"; + +export type PlatformTheme = { + buttonColor: string; + hintColor: string; + buttonTextColor: string; +}; + +export interface Platform { + getInitData(): string; + initialize(): void; + openExternalLink(link: string): void; + openInternalLink(link: string): void; + getTheme(): PlatformTheme; +} + +export type UseMainButtonType = ( + text: string | (() => string), + onClick: () => void, + condition?: () => boolean, +) => void; + +export type ShowConfirmType = (text: string) => Promise; + +const createPlatform = (): Platform => { + return isRunningWithinTelegram() + ? new TelegramPlatform() + : new BrowserPlatform(); +}; + +export const platform = createPlatform(); diff --git a/src/lib/platform/show-alert.ts b/src/lib/platform/show-alert.ts new file mode 100644 index 00000000..b039ea49 --- /dev/null +++ b/src/lib/platform/show-alert.ts @@ -0,0 +1,7 @@ +import { platform } from "./platform.ts"; +import { TelegramPlatform } from "./telegram/telegram-platform.ts"; +import { showAlertTelegram } from "./telegram/show-alert-telegram.ts"; +import { showAlertBrowser } from "./browser/show-alert-browser.ts"; + +export const showAlert = + platform instanceof TelegramPlatform ? showAlertTelegram : showAlertBrowser; diff --git a/src/lib/platform/show-confirm.ts b/src/lib/platform/show-confirm.ts new file mode 100644 index 00000000..c52265f7 --- /dev/null +++ b/src/lib/platform/show-confirm.ts @@ -0,0 +1,9 @@ +import { platform } from "./platform.ts"; +import { TelegramPlatform } from "./telegram/telegram-platform.ts"; +import { showConfirmTelegram } from "./telegram/show-confirm-telegram.ts"; +import { showConfirmBrowser } from "./browser/show-confirm-browser.tsx"; + +export const showConfirm = + platform instanceof TelegramPlatform + ? showConfirmTelegram + : showConfirmBrowser; diff --git a/src/lib/platform/storage-adapter.ts b/src/lib/platform/storage-adapter.ts new file mode 100644 index 00000000..dc978696 --- /dev/null +++ b/src/lib/platform/storage-adapter.ts @@ -0,0 +1,8 @@ +import { cloudStorageAdapter } from "./telegram/cloud-storage.ts"; +import { platform } from "./platform.ts"; +import { TelegramPlatform } from "./telegram/telegram-platform.ts"; + +export const storageAdapter = + platform instanceof TelegramPlatform && platform.isCloudStorageAvailable() + ? cloudStorageAdapter + : window.localStorage; diff --git a/src/lib/telegram/cloud-storage.ts b/src/lib/platform/telegram/cloud-storage.ts similarity index 100% rename from src/lib/telegram/cloud-storage.ts rename to src/lib/platform/telegram/cloud-storage.ts diff --git a/src/lib/platform/telegram/css-var-to-value.ts b/src/lib/platform/telegram/css-var-to-value.ts new file mode 100644 index 00000000..91b0dd59 --- /dev/null +++ b/src/lib/platform/telegram/css-var-to-value.ts @@ -0,0 +1,11 @@ +export const cssVarToValue = (cssProperty: string) => { + const cssPropertyClean = cssProperty.replace("var(", "").replace(")", ""); + const result = getComputedStyle(document.documentElement).getPropertyValue( + cssPropertyClean, + ); + if (!result) { + console.warn("Variable " + cssPropertyClean + " is not available"); + return "#00000"; + } + return result; +}; diff --git a/src/lib/telegram/haptics.ts b/src/lib/platform/telegram/haptics.ts similarity index 100% rename from src/lib/telegram/haptics.ts rename to src/lib/platform/telegram/haptics.ts diff --git a/src/lib/telegram/prevent-telegram-swipe-down-closing.tsx b/src/lib/platform/telegram/prevent-telegram-swipe-down-closing.tsx similarity index 97% rename from src/lib/telegram/prevent-telegram-swipe-down-closing.tsx rename to src/lib/platform/telegram/prevent-telegram-swipe-down-closing.tsx index f9401c13..70273ca5 100644 --- a/src/lib/telegram/prevent-telegram-swipe-down-closing.tsx +++ b/src/lib/platform/telegram/prevent-telegram-swipe-down-closing.tsx @@ -1,7 +1,7 @@ import React, { ReactNode, useEffect, useRef } from "react"; import WebApp from "@twa-dev/sdk"; import { css } from "@emotion/css"; -import { throttle } from "../throttle/throttle.ts"; +import { throttle } from "../../throttle/throttle.ts"; type Props = { condition: boolean; diff --git a/src/lib/telegram/show-alert.ts b/src/lib/platform/telegram/show-alert-telegram.ts similarity index 55% rename from src/lib/telegram/show-alert.ts rename to src/lib/platform/telegram/show-alert-telegram.ts index 1defe825..00ccfe9d 100644 --- a/src/lib/telegram/show-alert.ts +++ b/src/lib/platform/telegram/show-alert-telegram.ts @@ -1,5 +1,5 @@ import WebApp from "@twa-dev/sdk"; -export const showAlert = (text: string) => { +export const showAlertTelegram = (text: string) => { WebApp.showAlert(text); }; diff --git a/src/lib/telegram/show-confirm.ts b/src/lib/platform/telegram/show-confirm-telegram.ts similarity index 58% rename from src/lib/telegram/show-confirm.ts rename to src/lib/platform/telegram/show-confirm-telegram.ts index d01adc45..61403334 100644 --- a/src/lib/telegram/show-confirm.ts +++ b/src/lib/platform/telegram/show-confirm-telegram.ts @@ -1,6 +1,7 @@ import WebApp from "@twa-dev/sdk"; +import { ShowConfirmType } from "../platform.ts"; -export const showConfirm = (text: string): Promise => { +export const showConfirmTelegram: ShowConfirmType = (text) => { return new Promise((resolve) => { WebApp.showConfirm(text, (confirmed) => { resolve(confirmed); diff --git a/src/lib/platform/telegram/telegram-platform.ts b/src/lib/platform/telegram/telegram-platform.ts new file mode 100644 index 00000000..0d833f2e --- /dev/null +++ b/src/lib/platform/telegram/telegram-platform.ts @@ -0,0 +1,43 @@ +import WebApp from "@twa-dev/sdk"; +import { Platform, PlatformTheme } from "../platform.ts"; +import { cssVarToValue } from "./css-var-to-value.ts"; + +const buttonColor = "var(--tg-theme-button-color)"; +const buttonTextColor = "var(--tg-theme-button-text-color)"; +const hintColor = "var(--tg-theme-hint-color)"; + +export class TelegramPlatform implements Platform { + getInitData(): string { + return WebApp.initData; + } + + getTheme(): PlatformTheme { + return { + buttonColor: cssVarToValue(buttonColor), + hintColor: cssVarToValue(hintColor), + buttonTextColor: cssVarToValue(buttonTextColor), + }; + } + + initialize() { + WebApp.ready(); + WebApp.setHeaderColor("secondary_bg_color"); + WebApp.expand(); + } + + isOutdated(): boolean { + return !WebApp.isVersionAtLeast("6.1"); + } + + isCloudStorageAvailable(): boolean { + return WebApp.isVersionAtLeast("6.9"); + } + + openInternalLink(link: string) { + WebApp.openTelegramLink(link); + } + + openExternalLink(link: string) { + WebApp.openLink(link); + } +} diff --git a/src/lib/telegram/use-back-button.tsx b/src/lib/platform/telegram/use-back-button-telegram.tsx similarity index 66% rename from src/lib/telegram/use-back-button.tsx rename to src/lib/platform/telegram/use-back-button-telegram.tsx index 955f405e..ec14c153 100644 --- a/src/lib/telegram/use-back-button.tsx +++ b/src/lib/platform/telegram/use-back-button-telegram.tsx @@ -1,7 +1,7 @@ -import { useMount } from "../react/use-mount.ts"; +import { useMount } from "../../react/use-mount.ts"; import WebApp from "@twa-dev/sdk"; -export const useBackButton = (fn: () => void) => { +export const useBackButtonTelegram = (fn: () => void) => { useMount(() => { WebApp.BackButton.show(); WebApp.BackButton.onClick(fn); diff --git a/src/lib/telegram/use-telegram-progress.tsx b/src/lib/platform/telegram/use-main-button-progress-telegram.tsx similarity index 68% rename from src/lib/telegram/use-telegram-progress.tsx rename to src/lib/platform/telegram/use-main-button-progress-telegram.tsx index c8f2c9c8..0665b0db 100644 --- a/src/lib/telegram/use-telegram-progress.tsx +++ b/src/lib/platform/telegram/use-main-button-progress-telegram.tsx @@ -1,8 +1,8 @@ -import { useMount } from "../react/use-mount.ts"; +import { useMount } from "../../react/use-mount.ts"; import { autorun } from "mobx"; import WebApp from "@twa-dev/sdk"; -export const useTelegramProgress = (cb: () => boolean) => { +export const useMainButtonProgressTelegram = (cb: () => boolean) => { return useMount(() => { return autorun(() => { if (cb()) { diff --git a/src/lib/telegram/use-main-button.tsx b/src/lib/platform/telegram/use-main-button-telegram.ts similarity index 86% rename from src/lib/telegram/use-main-button.tsx rename to src/lib/platform/telegram/use-main-button-telegram.ts index 948023e9..a09fb144 100644 --- a/src/lib/telegram/use-main-button.tsx +++ b/src/lib/platform/telegram/use-main-button-telegram.ts @@ -1,7 +1,8 @@ -import { useMount } from "../react/use-mount.ts"; +import { useMount } from "../../react/use-mount.ts"; import WebApp from "@twa-dev/sdk"; import { useHotkeys } from "react-hotkeys-hook"; import { autorun } from "mobx"; +import { UseMainButtonType } from "../platform.ts"; // Track visible state to avoid flickering let isVisible = false; @@ -24,10 +25,10 @@ const hide = () => { }, 100); }; -export const useMainButton = ( - text: string | (() => string), - onClick: () => void, - condition?: () => boolean, +export const useMainButtonTelegram: UseMainButtonType = ( + text, + onClick, + condition, ) => { const hideMainButton = () => { hide(); diff --git a/src/lib/telegram/use-settings-button.ts b/src/lib/platform/telegram/use-settings-button.ts similarity index 87% rename from src/lib/telegram/use-settings-button.ts rename to src/lib/platform/telegram/use-settings-button.ts index 948be159..26bde55b 100644 --- a/src/lib/telegram/use-settings-button.ts +++ b/src/lib/platform/telegram/use-settings-button.ts @@ -1,5 +1,5 @@ import WebApp from "@twa-dev/sdk"; -import { useMount } from "../react/use-mount.ts"; +import { useMount } from "../../react/use-mount.ts"; export const useSettingsButton = (fn: () => void) => { useMount(() => { diff --git a/src/lib/platform/use-back-button.ts b/src/lib/platform/use-back-button.ts new file mode 100644 index 00000000..2e519693 --- /dev/null +++ b/src/lib/platform/use-back-button.ts @@ -0,0 +1,9 @@ +import { useBackButtonBrowser } from "./browser/use-back-button-browser.ts"; +import { useBackButtonTelegram } from "./telegram/use-back-button-telegram.tsx"; +import { platform } from "./platform.ts"; +import { TelegramPlatform } from "./telegram/telegram-platform.ts"; + +export const useBackButton = + platform instanceof TelegramPlatform + ? useBackButtonTelegram + : useBackButtonBrowser; diff --git a/src/lib/platform/use-main-button-progress.tsx b/src/lib/platform/use-main-button-progress.tsx new file mode 100644 index 00000000..a61bad3a --- /dev/null +++ b/src/lib/platform/use-main-button-progress.tsx @@ -0,0 +1,9 @@ +import { platform } from "./platform.ts"; +import { TelegramPlatform } from "./telegram/telegram-platform.ts"; +import { useMainButtonProgressTelegram } from "./telegram/use-main-button-progress-telegram.tsx"; +import { useMainButtonProgressBrowser } from "./browser/use-main-button-progress-browser.ts"; + +export const useMainButtonProgress = + platform instanceof TelegramPlatform + ? useMainButtonProgressTelegram + : useMainButtonProgressBrowser; diff --git a/src/lib/platform/use-main-button.ts b/src/lib/platform/use-main-button.ts new file mode 100644 index 00000000..287744d5 --- /dev/null +++ b/src/lib/platform/use-main-button.ts @@ -0,0 +1,9 @@ +import { platform } from "./platform.ts"; +import { useMainButtonTelegram } from "./telegram/use-main-button-telegram.ts"; +import { useMainButtonBrowser } from "./browser/use-main-button-browser.ts"; +import { TelegramPlatform } from "./telegram/telegram-platform.ts"; + +export const useMainButton = + platform instanceof TelegramPlatform + ? useMainButtonTelegram + : useMainButtonBrowser; diff --git a/src/lib/request/request.ts b/src/lib/request/request.ts index 73de344f..85346966 100644 --- a/src/lib/request/request.ts +++ b/src/lib/request/request.ts @@ -1,5 +1,5 @@ import { trimEnd, trimStart } from "../string/trim.ts"; -import WebApp from "@twa-dev/sdk"; +import { platform } from "../platform/platform.ts"; import { collectClientData } from "./collect-client-data.ts"; import { UserHeaders } from "../../../functions/services/get-user.ts"; @@ -17,7 +17,7 @@ const requestInner = async ( method, body: bodyAsString, headers: { - [UserHeaders.Hash]: WebApp.initData, + [UserHeaders.Hash]: platform.getInitData(), [UserHeaders.Platform]: collectClientData(), }, }); diff --git a/src/lib/telegram/storage-adapter.ts b/src/lib/telegram/storage-adapter.ts deleted file mode 100644 index 9aeeea59..00000000 --- a/src/lib/telegram/storage-adapter.ts +++ /dev/null @@ -1,6 +0,0 @@ -import WebApp from "@twa-dev/sdk"; -import { cloudStorageAdapter } from "./cloud-storage.ts"; - -export const storageAdapter = WebApp.isVersionAtLeast("6.9") - ? cloudStorageAdapter - : window.localStorage; diff --git a/src/main.tsx b/src/main.tsx index 2cb05a69..404170ae 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,10 +3,8 @@ import ReactDOM from "react-dom/client"; import { App } from "./screens/app.tsx"; import "./index.css"; import "@mdi/font/css/materialdesignicons.min.css"; -import WebApp from "@twa-dev/sdk"; +import { platform } from "./lib/platform/platform.ts"; -WebApp.ready(); -WebApp.setHeaderColor("secondary_bg_color"); -WebApp.expand(); +platform.initialize(); ReactDOM.createRoot(document.getElementById("root")!).render(); diff --git a/src/screens/ai-mass-creation/ai-mass-creation-form.tsx b/src/screens/ai-mass-creation/ai-mass-creation-form.tsx index 9df5acca..a5845268 100644 --- a/src/screens/ai-mass-creation/ai-mass-creation-form.tsx +++ b/src/screens/ai-mass-creation/ai-mass-creation-form.tsx @@ -11,9 +11,9 @@ import { Label } from "../../ui/label.tsx"; import { Input } from "../../ui/input.tsx"; import React from "react"; import { ValidationError } from "../../ui/validation-error.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; export const AiMassCreationForm = observer(() => { @@ -28,7 +28,7 @@ export const AiMassCreationForm = observer(() => { screenStore.back(); }); - useTelegramProgress(() => store.aiMassGenerateRequest.isLoading); + useMainButtonProgress(() => store.aiMassGenerateRequest.isLoading); return ( diff --git a/src/screens/ai-mass-creation/api-keys-screen.tsx b/src/screens/ai-mass-creation/api-keys-screen.tsx index 26aa877c..9e04acef 100644 --- a/src/screens/ai-mass-creation/api-keys-screen.tsx +++ b/src/screens/ai-mass-creation/api-keys-screen.tsx @@ -6,15 +6,15 @@ import React from "react"; import { useAiMassCreationStore } from "./store/ai-mass-creation-store-provider.tsx"; import { HintTransparent } from "../../ui/hint-transparent.tsx"; import { ExternalLink } from "../../ui/external-link.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { t } from "../../translations/t.ts"; import { SelectWithChevron } from "../../ui/select-with-chevron.tsx"; import { css } from "@emotion/css"; import { theme } from "../../ui/theme.tsx"; import { Flex } from "../../ui/flex.tsx"; import { chatGptModels } from "./store/ai-mass-creation-store.ts"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { TextField } from "mobx-form-lite"; export const ApiKeysScreen = observer(() => { @@ -29,7 +29,7 @@ export const ApiKeysScreen = observer(() => { store.screen.onChange(null); }); - useTelegramProgress(() => store.upsertUserAiCredentialsRequest.isLoading); + useMainButtonProgress(() => store.upsertUserAiCredentialsRequest.isLoading); const isRegularInput = store.isApiKeyRegularInput; diff --git a/src/screens/ai-mass-creation/cards-generated-screen.tsx b/src/screens/ai-mass-creation/cards-generated-screen.tsx index b59f9743..014a7622 100644 --- a/src/screens/ai-mass-creation/cards-generated-screen.tsx +++ b/src/screens/ai-mass-creation/cards-generated-screen.tsx @@ -1,8 +1,8 @@ import { observer } from "mobx-react-lite"; import { Screen } from "../shared/screen.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { useAiMassCreationStore } from "./store/ai-mass-creation-store-provider.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { List } from "../../ui/list.tsx"; import { ListHeader } from "../../ui/list-header.tsx"; import { assert } from "../../lib/typescript/assert.ts"; @@ -12,7 +12,7 @@ import { theme } from "../../ui/theme.tsx"; import React from "react"; import { t } from "../../translations/t.ts"; import { screenStore } from "../../store/screen-store.ts"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { CardNumber } from "../../ui/card-number.tsx"; import { translateAddCards } from "./translations.ts"; @@ -37,7 +37,7 @@ export const CardsGeneratedScreen = observer(() => { }, ); - useTelegramProgress(() => store.addCardsMultipleRequest.isLoading); + useMainButtonProgress(() => store.addCardsMultipleRequest.isLoading); return ( { }; }); -vi.mock("../../../lib/telegram/show-confirm.ts", () => { +vi.mock("../../../lib/platform/show-confirm.ts", () => { return { showConfirm: vi.fn(), }; diff --git a/src/screens/ai-mass-creation/store/ai-mass-creation-store.ts b/src/screens/ai-mass-creation/store/ai-mass-creation-store.ts index 45b2b0db..684eb010 100644 --- a/src/screens/ai-mass-creation/store/ai-mass-creation-store.ts +++ b/src/screens/ai-mass-creation/store/ai-mass-creation-store.ts @@ -20,7 +20,7 @@ import { screenStore } from "../../../store/screen-store.ts"; import { assert } from "../../../lib/typescript/assert.ts"; import { notifyError, notifySuccess } from "../../shared/snackbar/snackbar.tsx"; import { deckListStore } from "../../../store/deck-list-store.ts"; -import { showConfirm } from "../../../lib/telegram/show-confirm.ts"; +import { showConfirm } from "../../../lib/platform/show-confirm.ts"; export const chatGptModels = [ "gpt-4-0125-preview", diff --git a/src/screens/app.tsx b/src/screens/app.tsx index fb825dd4..9803d5f3 100644 --- a/src/screens/app.tsx +++ b/src/screens/app.tsx @@ -13,7 +13,7 @@ import { FullScreenLoader } from "../ui/full-screen-loader.tsx"; import { PreventTelegramSwipeDownClosingIos, useRestoreFullScreenExpand, -} from "../lib/telegram/prevent-telegram-swipe-down-closing.tsx"; +} from "../lib/platform/telegram/prevent-telegram-swipe-down-closing.tsx"; import { RepeatAllScreen } from "./deck-review/repeat-all-screen.tsx"; import { DeckCatalog } from "./deck-catalog/deck-catalog.tsx"; import { DeckOrFolderChoose } from "./deck-or-folder-choose/deck-or-folder-choose.tsx"; @@ -21,7 +21,7 @@ import { FolderForm } from "./folder-form/folder-form.tsx"; import { DeckCatalogStoreContextProvider } from "./deck-catalog/store/deck-catalog-store-context.tsx"; import { FolderFormStoreProvider } from "./folder-form/store/folder-form-store-context.tsx"; import { FolderScreen } from "./folder-review/folder-screen.tsx"; -import { useSettingsButton } from "../lib/telegram/use-settings-button.ts"; +import { useSettingsButton } from "../lib/platform/telegram/use-settings-button.ts"; import { UserStatisticsStoreProvider } from "./user-statistics/store/user-statistics-store-context.tsx"; import { UserStatisticsScreen } from "./user-statistics/user-statistics-screen.tsx"; import { UserSettingsLazy } from "./user-settings/user-settings-lazy.tsx"; @@ -31,18 +31,20 @@ import { ShareFolderScreenLazy, } from "./share-deck/share-deck-screen-lazy.tsx"; import { PlansScreen } from "./plans/plans-screen.tsx"; -import { isRunningWithinTelegram } from "../lib/telegram/is-running-within-telegram.ts"; +import { isRunningWithinTelegram } from "../lib/platform/is-running-within-telegram.ts"; import { FreezeCardsScreenLazy } from "./freeze-cards/freeze-cards-screen-lazy.tsx"; import { AiMassCreationScreen } from "./ai-mass-creation/ai-mass-creation-screen.tsx"; import { AiMassCreationStoreProvider } from "./ai-mass-creation/store/ai-mass-creation-store-provider.tsx"; import { SnackbarProviderWrapper } from "./shared/snackbar/snackbar-provider-wrapper.tsx"; import { Debug } from "./debug/debug.tsx"; +import { BrowserHeader } from "./shared/browser-platform/browser-header.tsx"; +import { BrowserMainButton } from "./shared/browser-platform/browser-main-button.tsx"; export const App = observer(() => { useRestoreFullScreenExpand(); - if (!isRunningWithinTelegram()) { + if (import.meta.env.PROD && !isRunningWithinTelegram()) { return
This app can only be run within Telegram.
; } @@ -60,6 +62,7 @@ export const App = observer(() => { return (
+ {screenStore.screen.type === "main" && ( @@ -166,6 +169,7 @@ export const App = observer(() => { )} +
); }); diff --git a/src/screens/component-catalog/component-catalog-page.tsx b/src/screens/component-catalog/component-catalog-page.tsx index a334d4a9..4d55ad30 100644 --- a/src/screens/component-catalog/component-catalog-page.tsx +++ b/src/screens/component-catalog/component-catalog-page.tsx @@ -1,6 +1,6 @@ import { Screen } from "../shared/screen.tsx"; import { useState } from "react"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { css } from "@emotion/css"; import { theme } from "../../ui/theme.tsx"; import { Component, components } from "./components.tsx"; diff --git a/src/screens/debug/debug.tsx b/src/screens/debug/debug.tsx index 9157f3f6..c6b6ab22 100644 --- a/src/screens/debug/debug.tsx +++ b/src/screens/debug/debug.tsx @@ -1,10 +1,10 @@ -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; import { screenStore } from "../../store/screen-store.ts"; import { Label } from "../../ui/label.tsx"; import { Input } from "../../ui/input.tsx"; import { observer, useLocalObservable } from "mobx-react-lite"; import { TextField } from "mobx-form-lite"; import { css } from "@emotion/css"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; export const Debug = observer(() => { const store = useLocalObservable(() => ({ diff --git a/src/screens/deck-catalog/deck-catalog.tsx b/src/screens/deck-catalog/deck-catalog.tsx index 5d00979f..e12ed007 100644 --- a/src/screens/deck-catalog/deck-catalog.tsx +++ b/src/screens/deck-catalog/deck-catalog.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react-lite"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; import { css } from "@emotion/css"; import React from "react"; diff --git a/src/screens/deck-form/answer-form-view.tsx b/src/screens/deck-form/answer-form-view.tsx index fdd69631..99529c06 100644 --- a/src/screens/deck-form/answer-form-view.tsx +++ b/src/screens/deck-form/answer-form-view.tsx @@ -8,8 +8,8 @@ import { CardRow } from "../../ui/card-row.tsx"; import { RadioSwitcher } from "../../ui/radio-switcher.tsx"; import { HintTransparent } from "../../ui/hint-transparent.tsx"; import React from "react"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { ButtonGrid } from "../../ui/button-grid.tsx"; import { t } from "../../translations/t.ts"; import { ButtonSideAligned } from "../../ui/button-side-aligned.tsx"; diff --git a/src/screens/deck-form/card-example.tsx b/src/screens/deck-form/card-example.tsx index 58298ca9..9be3e323 100644 --- a/src/screens/deck-form/card-example.tsx +++ b/src/screens/deck-form/card-example.tsx @@ -9,8 +9,8 @@ import { HintTransparent } from "../../ui/hint-transparent.tsx"; import React from "react"; import { userStore } from "../../store/user-store.ts"; import { CardFormType } from "./store/deck-form-store.ts"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; type Props = { cardForm: CardFormType; diff --git a/src/screens/deck-form/card-form-view.tsx b/src/screens/deck-form/card-form-view.tsx index e6b127b6..1c8b7b3a 100644 --- a/src/screens/deck-form/card-form-view.tsx +++ b/src/screens/deck-form/card-form-view.tsx @@ -1,10 +1,10 @@ import { observer } from "mobx-react-lite"; import { CardFormStoreInterface } from "./store/card-form-store-interface.ts"; import { assert } from "../../lib/typescript/assert.ts"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { t } from "../../translations/t.ts"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { isFormValid } from "mobx-form-lite"; import { Screen } from "../shared/screen.tsx"; import { Label } from "../../ui/label.tsx"; @@ -39,7 +39,7 @@ export const CardFormView = observer((props: Props) => { cardFormStore.onSaveCard(); }); - useTelegramProgress(() => cardFormStore.isSending); + useMainButtonProgress(() => cardFormStore.isSending); useBackButton(() => { cardFormStore.onBackCard(); diff --git a/src/screens/deck-form/card-list.tsx b/src/screens/deck-form/card-list.tsx index bf541057..2a73e4eb 100644 --- a/src/screens/deck-form/card-list.tsx +++ b/src/screens/deck-form/card-list.tsx @@ -2,7 +2,7 @@ import { observer } from "mobx-react-lite"; import { useDeckFormStore } from "./store/deck-form-store-context.tsx"; import { screenStore } from "../../store/screen-store.ts"; import { assert } from "../../lib/typescript/assert.ts"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { css, cx } from "@emotion/css"; import { Input } from "../../ui/input.tsx"; import { theme } from "../../ui/theme.tsx"; diff --git a/src/screens/deck-form/card-preview.tsx b/src/screens/deck-form/card-preview.tsx index f1d7c280..0079b8e3 100644 --- a/src/screens/deck-form/card-preview.tsx +++ b/src/screens/deck-form/card-preview.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react-lite"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { css } from "@emotion/css"; import { CardReviewWithControls } from "../deck-review/card-review-with-controls.tsx"; import React, { useState } from "react"; diff --git a/src/screens/deck-form/card-type.tsx b/src/screens/deck-form/card-type.tsx index 5f7210b6..b8bf5c67 100644 --- a/src/screens/deck-form/card-type.tsx +++ b/src/screens/deck-form/card-type.tsx @@ -11,8 +11,8 @@ import { action } from "mobx"; import { CardRow } from "../../ui/card-row.tsx"; import { CardFormType, createAnswerForm } from "./store/deck-form-store.ts"; import React from "react"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { formatCardType, formatCardTypeDescription, diff --git a/src/screens/deck-form/deck-form-screen.tsx b/src/screens/deck-form/deck-form-screen.tsx index 74fae7b6..86ac1b91 100644 --- a/src/screens/deck-form/deck-form-screen.tsx +++ b/src/screens/deck-form/deck-form-screen.tsx @@ -4,7 +4,7 @@ import { DeckForm } from "./deck-form.tsx"; import { useDeckFormStore } from "./store/deck-form-store-context.tsx"; import { CardList } from "./card-list.tsx"; import { CardFormWrapper } from "./card-form-wrapper.tsx"; -import { PreventTelegramSwipeDownClosingIos } from "../../lib/telegram/prevent-telegram-swipe-down-closing.tsx"; +import { PreventTelegramSwipeDownClosingIos } from "../../lib/platform/telegram/prevent-telegram-swipe-down-closing.tsx"; import { SpeakingCards } from "./speaking-cards.tsx"; export const DeckFormScreen = observer(() => { diff --git a/src/screens/deck-form/deck-form.tsx b/src/screens/deck-form/deck-form.tsx index fc8a2f5f..fd555c10 100644 --- a/src/screens/deck-form/deck-form.tsx +++ b/src/screens/deck-form/deck-form.tsx @@ -3,12 +3,12 @@ import { css, cx } from "@emotion/css"; import { Label } from "../../ui/label.tsx"; import { Input } from "../../ui/input.tsx"; import React from "react"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { useDeckFormStore } from "./store/deck-form-store-context.tsx"; import { screenStore } from "../../store/screen-store.ts"; import { useMount } from "../../lib/react/use-mount.ts"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { assert } from "../../lib/typescript/assert.ts"; import { CardRow } from "../../ui/card-row.tsx"; import { Button } from "../../ui/button.tsx"; @@ -43,10 +43,9 @@ export const DeckForm = observer(() => { screenStore.go({ type: "main" }); }); }); - useTelegramProgress(() => deckFormStore.isSending); + useMainButtonProgress(() => deckFormStore.isSending); if (!deckFormStore.form) { - console.log("no deck form"); return null; } diff --git a/src/screens/deck-form/speaking-cards.tsx b/src/screens/deck-form/speaking-cards.tsx index 8ecca35b..26746ab4 100644 --- a/src/screens/deck-form/speaking-cards.tsx +++ b/src/screens/deck-form/speaking-cards.tsx @@ -15,15 +15,14 @@ import { import { DeckSpeakFieldEnum } from "../../../functions/db/deck/decks-with-cards-schema.ts"; import { HintTransparent } from "../../ui/hint-transparent.tsx"; import React from "react"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { useDeckFormStore } from "./store/deck-form-store-context.tsx"; export const SpeakingCards = observer(() => { const deckFormStore = useDeckFormStore(); if (!deckFormStore.form) { - console.log("SpeakingCards: no deck form"); return null; } diff --git a/src/screens/deck-form/store/deck-form-store.test.ts b/src/screens/deck-form/store/deck-form-store.test.ts index f312dd4b..5505f9c1 100644 --- a/src/screens/deck-form/store/deck-form-store.test.ts +++ b/src/screens/deck-form/store/deck-form-store.test.ts @@ -125,13 +125,13 @@ vi.mock("./../../../store/deck-list-store.ts", () => { }; }); -vi.mock("../../../lib/telegram/show-confirm.ts", () => { +vi.mock("../../../lib/platform/show-confirm.ts", () => { return { showConfirm: () => {}, }; }); -vi.mock("../../../lib/telegram/show-alert.ts", () => { +vi.mock("../../../lib/platform/show-alert.ts", () => { return { showAlert: () => {}, }; diff --git a/src/screens/deck-form/store/deck-form-store.ts b/src/screens/deck-form/store/deck-form-store.ts index e78dc88f..982bb0d8 100644 --- a/src/screens/deck-form/store/deck-form-store.ts +++ b/src/screens/deck-form/store/deck-form-store.ts @@ -14,7 +14,7 @@ import { assert } from "../../../lib/typescript/assert.ts"; import { upsertDeckRequest } from "../../../api/api.ts"; import { screenStore } from "../../../store/screen-store.ts"; import { deckListStore } from "../../../store/deck-list-store.ts"; -import { showConfirm } from "../../../lib/telegram/show-confirm.ts"; +import { showConfirm } from "../../../lib/platform/show-confirm.ts"; import { fuzzySearch } from "../../../lib/string/fuzzy-search.ts"; import { DeckCardDbType, diff --git a/src/screens/deck-form/store/quick-add-card-form-store.ts b/src/screens/deck-form/store/quick-add-card-form-store.ts index 9d20bfe3..7719cae2 100644 --- a/src/screens/deck-form/store/quick-add-card-form-store.ts +++ b/src/screens/deck-form/store/quick-add-card-form-store.ts @@ -13,7 +13,7 @@ import { TextField, } from "mobx-form-lite"; import { screenStore } from "../../../store/screen-store.ts"; -import { showConfirm } from "../../../lib/telegram/show-confirm.ts"; +import { showConfirm } from "../../../lib/platform/show-confirm.ts"; import { addCardRequest } from "../../../api/api.ts"; import { assert } from "../../../lib/typescript/assert.ts"; import { AddCardRequest } from "../../../../functions/add-card.ts"; diff --git a/src/screens/deck-list/main-screen.tsx b/src/screens/deck-list/main-screen.tsx index d239f865..62d5fb88 100644 --- a/src/screens/deck-list/main-screen.tsx +++ b/src/screens/deck-list/main-screen.tsx @@ -22,6 +22,7 @@ import { Flex } from "../../ui/flex.tsx"; import { List } from "../../ui/list.tsx"; import { FilledIcon } from "../../ui/filled-icon.tsx"; import { CardsToReview } from "../../ui/cards-to-review.tsx"; +import { platform } from "../../lib/platform/platform.ts"; export const MainScreen = observer(() => { useMount(() => { @@ -168,7 +169,7 @@ export const MainScreen = observer(() => { /> ), onClick: () => { - WebApp.openTelegramLink(links.botChannel); + platform.openInternalLink(links.botChannel); }, }, ]} diff --git a/src/screens/deck-or-folder-choose/deck-or-folder-choose.tsx b/src/screens/deck-or-folder-choose/deck-or-folder-choose.tsx index 40f48032..06e6d336 100644 --- a/src/screens/deck-or-folder-choose/deck-or-folder-choose.tsx +++ b/src/screens/deck-or-folder-choose/deck-or-folder-choose.tsx @@ -1,6 +1,6 @@ import { observer } from "mobx-react-lite"; import { screenStore } from "../../store/screen-store.ts"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { t } from "../../translations/t.ts"; import { Flex } from "../../ui/flex.tsx"; import React from "react"; diff --git a/src/screens/deck-review/deck-finished.tsx b/src/screens/deck-review/deck-finished.tsx index 1e8fdc8b..386218a4 100644 --- a/src/screens/deck-review/deck-finished.tsx +++ b/src/screens/deck-review/deck-finished.tsx @@ -4,8 +4,8 @@ import { DeckFinishedModal } from "./deck-finished-modal.tsx"; import { useReviewStore } from "./store/review-store-context.tsx"; import { useMount } from "../../lib/react/use-mount.ts"; import { screenStore } from "../../store/screen-store.ts"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { t } from "../../translations/t.ts"; import { getEncouragingMessage } from "../../translations/get-encouraging-message.tsx"; import { WantMoreCardsButton } from "./want-more-cards-button.tsx"; @@ -29,7 +29,7 @@ export const DeckFinished = observer((props: Props) => { useMainButton(t("go_back"), () => { screenStore.go({ type: "main" }); }); - useTelegramProgress(() => reviewStore.reviewCardsRequest.isLoading); + useMainButtonProgress(() => reviewStore.reviewCardsRequest.isLoading); return ( diff --git a/src/screens/deck-review/deck-preview.tsx b/src/screens/deck-review/deck-preview.tsx index f6413643..85fe56e1 100644 --- a/src/screens/deck-review/deck-preview.tsx +++ b/src/screens/deck-review/deck-preview.tsx @@ -6,11 +6,11 @@ import React from "react"; import { useReviewStore } from "./store/review-store-context.tsx"; import { screenStore } from "../../store/screen-store.ts"; import { Hint } from "../../ui/hint.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; -import { showConfirm } from "../../lib/telegram/show-confirm.ts"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; +import { showConfirm } from "../../lib/platform/show-confirm.ts"; import { ButtonSideAligned } from "../../ui/button-side-aligned.tsx"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { t } from "../../translations/t.ts"; import { ButtonGrid } from "../../ui/button-grid.tsx"; import { Button } from "../../ui/button.tsx"; @@ -27,7 +27,7 @@ export const DeckPreview = observer(() => { screenStore.back(); }); - useTelegramProgress(() => deckListStore.deckWithCardsRequest.isLoading); + useMainButtonProgress(() => deckListStore.deckWithCardsRequest.isLoading); useScrollToTopOnMount(); useMainButton( diff --git a/src/screens/deck-review/repeat-all-screen.tsx b/src/screens/deck-review/repeat-all-screen.tsx index f90d2006..05564e60 100644 --- a/src/screens/deck-review/repeat-all-screen.tsx +++ b/src/screens/deck-review/repeat-all-screen.tsx @@ -9,7 +9,7 @@ import { Hint } from "../../ui/hint.tsx"; import { t } from "../../translations/t.ts"; import { WantMoreCardsButton } from "./want-more-cards-button.tsx"; import { Flex } from "../../ui/flex.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; export const RepeatAllScreen = observer(() => { diff --git a/src/screens/deck-review/review.tsx b/src/screens/deck-review/review.tsx index 9ae5ce01..22052479 100644 --- a/src/screens/deck-review/review.tsx +++ b/src/screens/deck-review/review.tsx @@ -6,7 +6,7 @@ import throttle from "just-throttle"; import { CardState } from "./store/card-under-review-store.ts"; import { ProgressBar } from "../../ui/progress-bar.tsx"; import { useReviewStore } from "./store/review-store-context.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { useHotkeys } from "react-hotkeys-hook"; import { ReviewDeckName } from "./review-deck-name.tsx"; import { CardReviewWithControls } from "./card-review-with-controls.tsx"; diff --git a/src/screens/deck-review/share-deck-button.tsx b/src/screens/deck-review/share-deck-button.tsx deleted file mode 100644 index 3b24d619..00000000 --- a/src/screens/deck-review/share-deck-button.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import { assert } from "../../lib/typescript/assert.ts"; -import { trimEnd } from "../../lib/string/trim.ts"; -import WebApp from "@twa-dev/sdk"; -import { ButtonSideAligned } from "../../ui/button-side-aligned.tsx"; - -type Props = { - shareId?: string | null; - text?: string; -}; - -export const ShareDeckButton = (props: Props) => { - const { shareId } = props; - const text = props.text || "Share deck"; - - const onClick = async () => { - const botUrl = import.meta.env.VITE_BOT_APP_URL; - assert(botUrl, "Bot URL is not set"); - const botUrlWithDeckId = `${trimEnd(botUrl, "/")}?startapp=${shareId}`; - const shareUrl = `https://t.me/share/url?text=&url=${botUrlWithDeckId}`; - WebApp.openTelegramLink(shareUrl); - }; - - return ( - - {text} - - ); -}; diff --git a/src/screens/deck-review/store/review-store.test.ts b/src/screens/deck-review/store/review-store.test.ts index 000f5c88..3115128a 100644 --- a/src/screens/deck-review/store/review-store.test.ts +++ b/src/screens/deck-review/store/review-store.test.ts @@ -13,7 +13,7 @@ vi.mock("mobx-persist-store", () => { }; }); -vi.mock("../../../lib/telegram/show-confirm.ts", () => { +vi.mock("../../../lib/platform/show-confirm.ts", () => { return { showAlert: () => {}, }; @@ -147,7 +147,7 @@ const newCardsMock: DeckCardDbTypeWithType[] = [ }, ]; -vi.mock("../lib/telegram/storage-adapter.ts", () => { +vi.mock("../lib/platform/storage-adapter.ts", () => { return { storageAdapter: {}, }; @@ -175,7 +175,7 @@ vi.mock("../../../lib/voice-playback/speak.ts", async () => { }; }); -vi.mock("../../../lib/telegram/haptics.ts", () => { +vi.mock("../../../lib/platform/telegram/haptics.ts", () => { return { hapticImpact: () => {}, hapticNotification: () => {}, diff --git a/src/screens/deck-review/store/review-store.ts b/src/screens/deck-review/store/review-store.ts index 663fd08a..ed373b0c 100644 --- a/src/screens/deck-review/store/review-store.ts +++ b/src/screens/deck-review/store/review-store.ts @@ -8,8 +8,8 @@ import { type DeckWithCardsWithReviewType } from "../../../store/deck-list-store import { hapticImpact, hapticNotification, -} from "../../../lib/telegram/haptics.ts"; -import { showConfirm } from "../../../lib/telegram/show-confirm.ts"; +} from "../../../lib/platform/telegram/haptics.ts"; +import { showConfirm } from "../../../lib/platform/show-confirm.ts"; import { t } from "../../../translations/t.ts"; import { RequestStore } from "../../../lib/mobx-request/request-store.ts"; import { notifyError } from "../../shared/snackbar/snackbar.tsx"; diff --git a/src/screens/folder-form/folder-form.tsx b/src/screens/folder-form/folder-form.tsx index 514dbd3f..b499fde9 100644 --- a/src/screens/folder-form/folder-form.tsx +++ b/src/screens/folder-form/folder-form.tsx @@ -4,11 +4,11 @@ import { Label } from "../../ui/label.tsx"; import { t } from "../../translations/t.ts"; import { Input } from "../../ui/input.tsx"; import React from "react"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { useMount } from "../../lib/react/use-mount.ts"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { assert } from "../../lib/typescript/assert.ts"; import { reset } from "../../ui/reset.ts"; import { css, cx } from "@emotion/css"; @@ -37,7 +37,7 @@ export const FolderForm = observer(() => { folderStore.onBack(); }); - useTelegramProgress(() => folderStore.folderUpsertRequest.isLoading); + useMainButtonProgress(() => folderStore.folderUpsertRequest.isLoading); if (!folderForm) { return null; diff --git a/src/screens/folder-form/store/folder-form-store.ts b/src/screens/folder-form/store/folder-form-store.ts index af8e1082..f4558992 100644 --- a/src/screens/folder-form/store/folder-form-store.ts +++ b/src/screens/folder-form/store/folder-form-store.ts @@ -13,10 +13,10 @@ import { screenStore } from "../../../store/screen-store.ts"; import { assert } from "../../../lib/typescript/assert.ts"; import { decksMineRequest, folderUpsertRequest } from "../../../api/api.ts"; import { deckListStore } from "../../../store/deck-list-store.ts"; -import { showConfirm } from "../../../lib/telegram/show-confirm.ts"; +import { showConfirm } from "../../../lib/platform/show-confirm.ts"; import { RequestStore } from "../../../lib/mobx-request/request-store.ts"; import { notifyError } from "../../shared/snackbar/snackbar.tsx"; -import { hapticNotification } from "../../../lib/telegram/haptics.ts"; +import { hapticNotification } from "../../../lib/platform/telegram/haptics.ts"; const createFolderTitleField = (title: string) => { return new TextField(title, { diff --git a/src/screens/folder-review/folder-preview.tsx b/src/screens/folder-review/folder-preview.tsx index 68f63aad..e70e951d 100644 --- a/src/screens/folder-review/folder-preview.tsx +++ b/src/screens/folder-review/folder-preview.tsx @@ -5,11 +5,11 @@ import { theme } from "../../ui/theme.tsx"; import React from "react"; import { screenStore } from "../../store/screen-store.ts"; import { Hint } from "../../ui/hint.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; -import { showConfirm } from "../../lib/telegram/show-confirm.ts"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; +import { showConfirm } from "../../lib/platform/show-confirm.ts"; import { ButtonSideAligned } from "../../ui/button-side-aligned.tsx"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { t } from "../../translations/t.ts"; import { useReviewStore } from "../deck-review/store/review-store-context.tsx"; import { ListHeader } from "../../ui/list-header.tsx"; @@ -29,7 +29,7 @@ export const FolderPreview = observer(() => { screenStore.back(); }); - useTelegramProgress(() => deckListStore.isCatalogItemLoading); + useMainButtonProgress(() => deckListStore.isCatalogItemLoading); useScrollToTopOnMount(); useMainButton( diff --git a/src/screens/freeze-cards/freeze-cards-screen.tsx b/src/screens/freeze-cards/freeze-cards-screen.tsx index bb0e6e06..3f2fc4fa 100644 --- a/src/screens/freeze-cards/freeze-cards-screen.tsx +++ b/src/screens/freeze-cards/freeze-cards-screen.tsx @@ -1,19 +1,19 @@ import { observer } from "mobx-react-lite"; import { Screen } from "../shared/screen.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; import { css } from "@emotion/css"; import { useState } from "react"; import { theme } from "../../ui/theme.tsx"; import { Flex } from "../../ui/flex.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { Input } from "../../ui/input.tsx"; import { DateTime } from "luxon"; import { Chip } from "../../ui/chip.tsx"; import { FreezeCardsStore } from "./store/freeze-cards-store.ts"; import { FilledIcon } from "../../ui/filled-icon.tsx"; import { Accordion } from "../../ui/accordion.tsx"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { t } from "../../translations/t.ts"; import { formatDays } from "./translations.ts"; @@ -30,7 +30,7 @@ export const FreezeCardsScreen = observer(() => { store.freeze, () => store.isFreezeButtonVisible, ); - useTelegramProgress(() => store.cardsFreezeRequest.isLoading); + useMainButtonProgress(() => store.cardsFreezeRequest.isLoading); return ( diff --git a/src/screens/freeze-cards/store/freeze-cards-store.ts b/src/screens/freeze-cards/store/freeze-cards-store.ts index 1ae3f58a..e44066ef 100644 --- a/src/screens/freeze-cards/store/freeze-cards-store.ts +++ b/src/screens/freeze-cards/store/freeze-cards-store.ts @@ -3,7 +3,7 @@ import { makeAutoObservable } from "mobx"; import { cardsFreezeRequest } from "../../../api/api.ts"; import { assert } from "../../../lib/typescript/assert.ts"; import { screenStore } from "../../../store/screen-store.ts"; -import { showConfirm } from "../../../lib/telegram/show-confirm.ts"; +import { showConfirm } from "../../../lib/platform/show-confirm.ts"; import { t } from "../../../translations/t.ts"; import { formatFrozenCards } from "../translations.ts"; import { RequestStore } from "../../../lib/mobx-request/request-store.ts"; diff --git a/src/screens/plans/plans-screen.tsx b/src/screens/plans/plans-screen.tsx index db66eef4..63623b44 100644 --- a/src/screens/plans/plans-screen.tsx +++ b/src/screens/plans/plans-screen.tsx @@ -1,11 +1,11 @@ import { observer } from "mobx-react-lite"; import { Screen } from "../shared/screen.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; import { Flex } from "../../ui/flex.tsx"; import React, { useState } from "react"; import { PlanItem } from "./plan-item.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { Hint } from "../../ui/hint.tsx"; import { useMount } from "../../lib/react/use-mount.ts"; import { FullScreenLoader } from "../../ui/full-screen-loader.tsx"; @@ -15,7 +15,7 @@ import { getPlanTitle, } from "./translations.ts"; import { PlansScreenStore } from "./store/plans-screen-store.ts"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { userStore } from "../../store/user-store.ts"; import { ExternalLink } from "../../ui/external-link.tsx"; import { t } from "../../translations/t.ts"; @@ -39,7 +39,7 @@ export const PlansScreen = observer(() => { () => store.isBuyButtonVisible, ); - useTelegramProgress(() => store.createOrderRequest.isLoading); + useMainButtonProgress(() => store.createOrderRequest.isLoading); if (store.plansRequest.result.status === "loading") { return ; diff --git a/src/screens/plans/store/plans-screen-store.ts b/src/screens/plans/store/plans-screen-store.ts index dbe3111e..a878dbb3 100644 --- a/src/screens/plans/store/plans-screen-store.ts +++ b/src/screens/plans/store/plans-screen-store.ts @@ -2,9 +2,9 @@ import { makeAutoObservable } from "mobx"; import { allPlansRequest, createOrderRequest } from "../../../api/api.ts"; import { getBuyText } from "../translations.ts"; import { assert } from "../../../lib/typescript/assert.ts"; -import WebApp from "@twa-dev/sdk"; import { RequestStore } from "../../../lib/mobx-request/request-store.ts"; import { notifyError } from "../../shared/snackbar/snackbar.tsx"; +import { platform } from "../../../lib/platform/platform.ts"; export class PlansScreenStore { plansRequest = new RequestStore(allPlansRequest); @@ -56,6 +56,6 @@ export class PlansScreenStore { return; } - WebApp.openTelegramLink(result.data.payLink); + platform.openInternalLink(result.data.payLink); } } diff --git a/src/screens/share-deck/redirect-user-to-deck-or-folder-link.tsx b/src/screens/share-deck/redirect-user-to-deck-or-folder-link.tsx index cf703c37..86f51800 100644 --- a/src/screens/share-deck/redirect-user-to-deck-or-folder-link.tsx +++ b/src/screens/share-deck/redirect-user-to-deck-or-folder-link.tsx @@ -1,6 +1,6 @@ import { assert } from "../../lib/typescript/assert.ts"; import { trimEnd } from "../../lib/string/trim.ts"; -import WebApp from "@twa-dev/sdk"; +import { platform } from "../../lib/platform/platform.ts"; export const getDeckOrFolderLink = (shareId: string) => { const botUrl = import.meta.env.VITE_BOT_APP_URL; @@ -11,5 +11,5 @@ export const getDeckOrFolderLink = (shareId: string) => { export const redirectUserToDeckOrFolderLink = (shareId: string) => { const botUrlWithDeckId = getDeckOrFolderLink(shareId); const shareUrl = `https://t.me/share/url?text=&url=${botUrlWithDeckId}`; - WebApp.openTelegramLink(shareUrl); + platform.openInternalLink(shareUrl); }; diff --git a/src/screens/share-deck/share-deck-one-time-links.tsx b/src/screens/share-deck/share-deck-one-time-links.tsx index 081fdc3c..85b1ef21 100644 --- a/src/screens/share-deck/share-deck-one-time-links.tsx +++ b/src/screens/share-deck/share-deck-one-time-links.tsx @@ -2,11 +2,11 @@ import { observer } from "mobx-react-lite"; import { css } from "@emotion/css"; import { t } from "../../translations/t.ts"; import React from "react"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { useMount } from "../../lib/react/use-mount.ts"; import { getDeckOrFolderLink } from "./redirect-user-to-deck-or-folder-link.tsx"; import { copyToClipboard } from "../../lib/copy-to-clipboard/copy-to-clipboard.ts"; -import { showAlert } from "../../lib/telegram/show-alert.ts"; +import { showAlert } from "../../lib/platform/show-alert.ts"; import { theme } from "../../ui/theme.tsx"; import { DateTime } from "luxon"; import { useShareDeckStore } from "./store/share-deck-store-context.tsx"; diff --git a/src/screens/share-deck/share-deck-settings.tsx b/src/screens/share-deck/share-deck-settings.tsx index 30312301..e8466c65 100644 --- a/src/screens/share-deck/share-deck-settings.tsx +++ b/src/screens/share-deck/share-deck-settings.tsx @@ -1,9 +1,9 @@ import { observer } from "mobx-react-lite"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; import { t } from "../../translations/t.ts"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { CardRow } from "../../ui/card-row.tsx"; import { RadioSwitcher } from "../../ui/radio-switcher.tsx"; import { HintTransparent } from "../../ui/hint-transparent.tsx"; @@ -32,7 +32,7 @@ export const ShareDeckSettings = observer(() => { () => store.isSaveButtonVisible, ); - useTelegramProgress(() => store.addDeckAccessRequest.isLoading); + useMainButtonProgress(() => store.addDeckAccessRequest.isLoading); return ( diff --git a/src/screens/shared/browser-platform/browser-header.tsx b/src/screens/shared/browser-platform/browser-header.tsx new file mode 100644 index 00000000..80a59161 --- /dev/null +++ b/src/screens/shared/browser-platform/browser-header.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import { observer } from "mobx-react-lite"; +import { css, cx } from "@emotion/css"; +import { platform } from "../../../lib/platform/platform.ts"; +import { BrowserPlatform } from "../../../lib/platform/browser/browser-platform.ts"; +import { assert } from "../../../lib/typescript/assert.ts"; +import { theme } from "../../../ui/theme.tsx"; + +export const BrowserHeader = observer(() => { + return ( +
+ {platform instanceof BrowserPlatform && platform.isBackButtonVisible ? ( + { + assert(platform instanceof BrowserPlatform); + platform.backButtonInfo?.onClick(); + }} + /> + ) : null} +
+ ); +}); diff --git a/src/screens/shared/browser-platform/browser-main-button.tsx b/src/screens/shared/browser-platform/browser-main-button.tsx new file mode 100644 index 00000000..b064ad7a --- /dev/null +++ b/src/screens/shared/browser-platform/browser-main-button.tsx @@ -0,0 +1,51 @@ +import { observer } from "mobx-react-lite"; +import { Button } from "../../../ui/button.tsx"; +import { theme } from "../../../ui/theme.tsx"; +import { platform } from "../../../lib/platform/platform.ts"; +import { assert } from "../../../lib/typescript/assert.ts"; +import { BrowserPlatform } from "../../../lib/platform/browser/browser-platform.ts"; +import { t } from "../../../translations/t.ts"; +import { css } from "@emotion/css"; + +export const BrowserMainButton = observer(() => { + if (!(platform instanceof BrowserPlatform)) { + return null; + } + + if (!platform.isMainButtonVisible) { + return null; + } + + const { mainButtonInfo } = platform; + assert(mainButtonInfo); + + if (mainButtonInfo.condition && !mainButtonInfo.condition()) { + return null; + } + + return ( +
+ +
+ ); +}); diff --git a/src/screens/shared/card/card.tsx b/src/screens/shared/card/card.tsx index 9220b334..45d080b8 100644 --- a/src/screens/shared/card/card.tsx +++ b/src/screens/shared/card/card.tsx @@ -12,7 +12,7 @@ import { Dropdown } from "../../../ui/dropdown.tsx"; import { t } from "../../../translations/t.ts"; import { boolNarrow } from "../../../lib/typescript/bool-narrow.ts"; import { userStore } from "../../../store/user-store.ts"; -import { hapticSelection } from "../../../lib/telegram/haptics.ts"; +import { hapticSelection } from "../../../lib/platform/telegram/haptics.ts"; export const cardSize = 310; export const IDK_ID = "idk"; diff --git a/src/screens/shared/snackbar/snackbar.tsx b/src/screens/shared/snackbar/snackbar.tsx index fa6c1fde..56e0c283 100644 --- a/src/screens/shared/snackbar/snackbar.tsx +++ b/src/screens/shared/snackbar/snackbar.tsx @@ -3,7 +3,7 @@ import { css } from "@emotion/css"; import { theme } from "../../../ui/theme.tsx"; import { reportHandledError } from "../../../lib/rollbar/rollbar.tsx"; import { userStore } from "../../../store/user-store.ts"; -import { hapticNotification } from "../../../lib/telegram/haptics.ts"; +import { hapticNotification } from "../../../lib/platform/telegram/haptics.ts"; import { t } from "../../../translations/t.ts"; import { ClearSnackbar } from "./clear-snackbar.tsx"; import "./notistack.css"; diff --git a/src/screens/shared/version-warning.tsx b/src/screens/shared/version-warning.tsx index 8622b5e5..8a49f103 100644 --- a/src/screens/shared/version-warning.tsx +++ b/src/screens/shared/version-warning.tsx @@ -1,10 +1,15 @@ -import WebApp from "@twa-dev/sdk"; import { css } from "@emotion/css"; import { theme } from "../../ui/theme.tsx"; import { t } from "../../translations/t.ts"; +import { platform } from "../../lib/platform/platform.ts"; +import { TelegramPlatform } from "../../lib/platform/telegram/telegram-platform.ts"; export const VersionWarning = () => { - if (WebApp.isVersionAtLeast("6.1")) { + if (!(platform instanceof TelegramPlatform)) { + return null; + } + + if (!platform.isOutdated()) { return null; } diff --git a/src/screens/user-settings/user-settings-screen.tsx b/src/screens/user-settings/user-settings-screen.tsx index 2aa947f7..ed1be514 100644 --- a/src/screens/user-settings/user-settings-screen.tsx +++ b/src/screens/user-settings/user-settings-screen.tsx @@ -4,22 +4,22 @@ import { deckListStore } from "../../store/deck-list-store.ts"; import React from "react"; import { useMount } from "../../lib/react/use-mount.ts"; import { generateTimeRange } from "./generate-time-range.tsx"; -import { useMainButton } from "../../lib/telegram/use-main-button.tsx"; -import { useTelegramProgress } from "../../lib/telegram/use-telegram-progress.tsx"; +import { useMainButton } from "../../lib/platform/use-main-button.ts"; +import { useMainButtonProgress } from "../../lib/platform/use-main-button-progress.tsx"; import { RadioSwitcher } from "../../ui/radio-switcher.tsx"; import { theme } from "../../ui/theme.tsx"; import { Select } from "../../ui/select.tsx"; import { css } from "@emotion/css"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; import { HintTransparent } from "../../ui/hint-transparent.tsx"; import { t } from "../../translations/t.ts"; import { Screen } from "../shared/screen.tsx"; -import WebApp from "@twa-dev/sdk"; import { links } from "../shared/links.ts"; import { List } from "../../ui/list.tsx"; import { FilledIcon } from "../../ui/filled-icon.tsx"; import { boolNarrow } from "../../lib/typescript/bool-narrow.ts"; +import { platform } from "../../lib/platform/platform.ts"; export const timeRanges = generateTimeRange(); @@ -34,7 +34,7 @@ export const UserSettingsScreen = observer(() => { useBackButton(() => { screenStore.back(); }); - useTelegramProgress(() => userSettingsStore.userSettingsRequest.isLoading); + useMainButtonProgress(() => userSettingsStore.userSettingsRequest.isLoading); if (!deckListStore.myInfo || !userSettingsStore.form) { return null; @@ -189,7 +189,7 @@ export const UserSettingsScreen = observer(() => { ), text: t("settings_contact_support"), onClick: () => { - WebApp.openTelegramLink(links.supportChat); + platform.openInternalLink(links.supportChat); }, isLinkColor: true, }, diff --git a/src/screens/user-statistics/user-statistics-screen.tsx b/src/screens/user-statistics/user-statistics-screen.tsx index cccb065a..8216fab8 100644 --- a/src/screens/user-statistics/user-statistics-screen.tsx +++ b/src/screens/user-statistics/user-statistics-screen.tsx @@ -2,7 +2,7 @@ import { Screen } from "../shared/screen.tsx"; import { observer } from "mobx-react-lite"; import { useMount } from "../../lib/react/use-mount.ts"; import { useUserStatisticsStore } from "./store/user-statistics-store-context.tsx"; -import { useBackButton } from "../../lib/telegram/use-back-button.tsx"; +import { useBackButton } from "../../lib/platform/use-back-button.ts"; import { screenStore } from "../../store/screen-store.ts"; import { CardRow } from "../../ui/card-row.tsx"; import { HintTransparent } from "../../ui/hint-transparent.tsx"; diff --git a/src/store/deck-list-store.test.ts b/src/store/deck-list-store.test.ts index f60396f6..36203054 100644 --- a/src/store/deck-list-store.test.ts +++ b/src/store/deck-list-store.test.ts @@ -2,7 +2,6 @@ import { afterEach, describe, expect, test, vi } from "vitest"; import { deckListStore } from "./deck-list-store.ts"; import { MyInfoResponse } from "../../functions/my-info.ts"; import { when } from "mobx"; -import { userStore } from "./user-store.ts"; vi.mock("mobx-persist-store", () => { return { @@ -10,7 +9,16 @@ vi.mock("mobx-persist-store", () => { }; }); -vi.mock("../lib/telegram/storage-adapter.ts", () => { +vi.mock('./user-store.ts', () => { + return { + userStore: { + myId: 111, + setUser: () => {}, + }, + }; +}); + +vi.mock("../platform/storage-adapter.ts", () => { return { storageAdapter: {}, }; @@ -158,14 +166,14 @@ vi.mock("./screen-store", () => { }; }); -vi.mock("../lib/telegram/haptics.ts", () => { +vi.mock("../lib/platform/telegram/haptics.ts", () => { return { hapticNotification: () => {}, hapticImpact: () => {}, }; }); -vi.mock("../lib/telegram/show-confirm.ts", () => { +vi.mock("../lib/platform/show-confirm.ts", () => { return { showConfirm: () => { return Promise.resolve(true); @@ -191,12 +199,11 @@ describe("deck list store", () => { vi.clearAllMocks(); }); - test("test 1", async () => { + test("test load decks", async () => { deckListStore.load(); await when(() => !!deckListStore.myInfo); - expect(userStore.myId).toBe(111); expect(deckListStore.publicDecks).toHaveLength(0); expect(deckListStore.newCardsCount).toBe(3); expect(deckListStore.selectedDeck?.cardsToReview).toMatchInlineSnapshot(` diff --git a/src/store/deck-list-store.ts b/src/store/deck-list-store.ts index 816dda7d..16d683ab 100644 --- a/src/store/deck-list-store.ts +++ b/src/store/deck-list-store.ts @@ -26,10 +26,10 @@ import { ReviewStore } from "../screens/deck-review/store/review-store.ts"; import { reportHandledError } from "../lib/rollbar/rollbar.tsx"; import { BooleanToggle } from "mobx-form-lite"; import { userStore } from "./user-store.ts"; -import { showConfirm } from "../lib/telegram/show-confirm.ts"; +import { showConfirm } from "../lib/platform/show-confirm.ts"; import { t } from "../translations/t.ts"; import { canDuplicateDeckOrFolder } from "../../shared/access/can-duplicate-deck-or-folder.ts"; -import { hapticImpact } from "../lib/telegram/haptics.ts"; +import { hapticImpact } from "../lib/platform/telegram/haptics.ts"; import { FolderWithDecksWithCards } from "../../functions/db/folder/get-folder-with-decks-with-cards-db.ts"; import { type FolderWithDeckIdDbType } from "../../functions/db/folder/schema.ts"; import { CatalogFolderDbType } from "../../functions/db/folder/get-public-folders-with-decks-db.ts"; @@ -182,7 +182,9 @@ export class DeckListStore { get canReview() { const deck = this.selectedDeck; - assert(deck, "canReview requires a deck to be selected"); + if (!deck) { + return false; + } return ( deck.cardsToReview.length > 0 || screenStore.screen.type === "deckPublic" @@ -297,8 +299,7 @@ export class DeckListStore { get selectedFolder() { const screen = screenStore.screen; - assert(screen.type === "folderPreview", "screen is not folder preview"); - if (!this.myInfo) { + if (screen.type !== "folderPreview" || !this.myInfo) { return null; } @@ -396,7 +397,11 @@ export class DeckListStore { get selectedDeck(): DeckWithCardsWithReviewType | null { const screen = screenStore.screen; - assert(screen.type === "deckPublic" || screen.type === "deckMine"); + const isSelectedDeckVisible = + screen.type === "deckPublic" || screen.type === "deckMine"; + if (!isSelectedDeckVisible) { + return null; + } if (!screen.deckId || !this.myInfo) { return null; } diff --git a/src/translations/t.ts b/src/translations/t.ts index bc4ac778..f51c8489 100644 --- a/src/translations/t.ts +++ b/src/translations/t.ts @@ -245,6 +245,8 @@ const en = { ai_cards_use_template: "Use template", understood: "Understood", payment_success: "Payment is successful. Enjoy additional features 😊", + confirm_cancel: "Cancel", + confirm_ok: "Confirm", payment_failed: "Payment failed. We're aware of the issue and working on it. Please contact support via Settings > Support.", }; @@ -252,6 +254,8 @@ const en = { type Translation = typeof en; const ru: Translation = { + confirm_cancel: "Отмена", + confirm_ok: "Подтвердить", payment_success: "Оплата прошла успешно. Наслаждайтесь дополнительными возможностями 😊", payment_failed: @@ -498,6 +502,8 @@ const ru: Translation = { }; const es: Translation = { + confirm_ok: "Confirmar", + confirm_cancel: "Cancelar", payment_success: "El pago se ha realizado con éxito. Disfruta de las funciones adicionales 😊", payment_failed: @@ -746,6 +752,8 @@ const es: Translation = { }; const ptBr: Translation = { + confirm_ok: "Confirmar", + confirm_cancel: "Cancelar", payment_success: "Pagamento realizado com sucesso. Aproveite os recursos adicionais 😊", payment_failed: diff --git a/src/ui/external-link.tsx b/src/ui/external-link.tsx index 7938eedc..4eac962d 100644 --- a/src/ui/external-link.tsx +++ b/src/ui/external-link.tsx @@ -1,14 +1,19 @@ import { ReactNode } from "react"; import { css } from "@emotion/css"; -import WebApp from "@twa-dev/sdk"; import { theme } from "./theme.tsx"; +import { platform } from "../lib/platform/platform.ts"; -export const ExternalLink = (props: { href: string; children: ReactNode }) => { +type Props = { + href: string; + children: ReactNode; +}; + +export const ExternalLink = (props: Props) => { const { href, children } = props; return ( { - WebApp.openLink(href); + platform.openExternalLink(href); }} className={css({ color: theme.buttonColor, diff --git a/src/ui/theme.tsx b/src/ui/theme.tsx index d1dbbeeb..283825c2 100644 --- a/src/ui/theme.tsx +++ b/src/ui/theme.tsx @@ -1,16 +1,5 @@ import { colord } from "colord"; - -const cssVarToValue = (cssProperty: string) => { - const cssPropertyClean = cssProperty.replace("var(", "").replace(")", ""); - const result = getComputedStyle(document.documentElement).getPropertyValue( - cssPropertyClean, - ); - if (!result) { - console.warn("Variable " + cssPropertyClean + " is not available"); - return "#00000"; - } - return result; -}; +import { platform } from "../lib/platform/platform.ts"; const secondaryBgColor = "var(--tg-theme-secondary-bg-color)"; const buttonColor = "var(--tg-theme-button-color)"; @@ -20,21 +9,21 @@ const textColor = "var(--tg-theme-text-color)"; const hintColor = "var(--tg-theme-hint-color)"; const linkColor = "var(--tg-theme-link-color)"; -const buttonColorComputed = cssVarToValue(buttonColor); +const platformTheme = platform.getTheme(); export const theme = { bgColor, textColor, hintColor, - hintColorComputed: cssVarToValue(hintColor), + hintColorComputed: platformTheme.hintColor, linkColor, buttonColor, buttonTextColor, secondaryBgColor, - buttonColorComputed: buttonColorComputed, - buttonColorLighter: colord(buttonColorComputed).lighten(0.4).toHex(), - buttonTextColorComputed: cssVarToValue(buttonTextColor), + buttonColorComputed: platformTheme.buttonColor, + buttonColorLighter: colord(platformTheme.buttonColor).lighten(0.4).toHex(), + buttonTextColorComputed: platformTheme.buttonTextColor, success: "#2ecb47", successLight: colord("#2ecb47").alpha(0.4).toHex(), @@ -54,4 +43,9 @@ export const theme = { borderRadius: 12, boxShadow: "0 12px 24px 0 rgba(0, 0, 0, .05)", + + zIndex: { + confirmAlert: 1000, + mainButton: 999, + }, };