├── .eslintrc.cjs ├── .github ├── deployment-branches.png └── workflows │ └── github-pages-deploy.yml ├── .gitignore ├── LICENSE ├── README.md ├── assets └── application.png ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── public └── tonconnect-manifest.json ├── src ├── components │ ├── App.tsx │ ├── DisplayData │ │ ├── DisplayData.css │ │ └── DisplayData.tsx │ ├── EnvUnsupported.tsx │ ├── ErrorBoundary.tsx │ ├── Link │ │ ├── Link.css │ │ └── Link.tsx │ ├── Page.tsx │ ├── RGB │ │ ├── RGB.css │ │ └── RGB.tsx │ └── Root.tsx ├── css │ ├── bem.ts │ └── classnames.ts ├── helpers │ └── publicUrl.ts ├── index.css ├── index.tsx ├── init.ts ├── mockEnv.ts ├── navigation │ └── routes.tsx └── pages │ ├── IndexPage │ ├── IndexPage.tsx │ └── ton.svg │ ├── InitDataPage.tsx │ ├── LaunchParamsPage.tsx │ ├── TONConnectPage │ ├── TONConnectPage.css │ └── TONConnectPage.tsx │ └── ThemeParamsPage.tsx ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:react/recommended' 10 | ], 11 | overrides: [ 12 | { 13 | env: { 14 | node: true 15 | }, 16 | files: [ 17 | '.eslintrc.{js,cjs}' 18 | ], 19 | parserOptions: { 20 | 'sourceType': 'script' 21 | } 22 | } 23 | ], 24 | parser: '@typescript-eslint/parser', 25 | parserOptions: { 26 | ecmaVersion: 'latest', 27 | sourceType: 'module' 28 | }, 29 | plugins: [ 30 | '@typescript-eslint', 31 | 'react' 32 | ], 33 | rules: { 34 | 'react/react-in-jsx-scope': 0, 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /.github/deployment-branches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Telegram-Mini-Apps/reactjs-template/59212756e1782ffb962af763063387a1a46a1ea6/.github/deployment-branches.png -------------------------------------------------------------------------------- /.github/workflows/github-pages-deploy.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ['master'] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets the GITHUB_TOKEN permissions to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: 'pages' 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | 34 | - name: Setup node 35 | uses: actions/setup-node@v3 36 | with: 37 | node-version: 18 38 | registry-url: 'https://registry.npmjs.org' 39 | 40 | - name: Install deps 41 | run: npm ci 42 | 43 | - name: Build 44 | run: npm run build 45 | 46 | - name: Setup Pages 47 | uses: actions/configure-pages@v5 48 | 49 | - name: Upload artifact 50 | uses: actions/upload-pages-artifact@v3 51 | with: 52 | # Upload dist repository 53 | path: './dist' 54 | 55 | - name: Deploy to GitHub Pages 56 | id: deployment 57 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | *.pem 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Telegram Mini Apps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Mini Apps React Template 2 | 3 | This template demonstrates how developers can implement a single-page 4 | application on the Telegram Mini Apps platform using the following technologies 5 | and libraries: 6 | 7 | - [React](https://react.dev/) 8 | - [TypeScript](https://www.typescriptlang.org/) 9 | - [TON Connect](https://docs.ton.org/develop/dapps/ton-connect/overview) 10 | - [@telegram-apps SDK](https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk/2-x) 11 | - [Telegram UI](https://github.com/Telegram-Mini-Apps/TelegramUI) 12 | - [Vite](https://vitejs.dev/) 13 | 14 | > The template was created using [npm](https://www.npmjs.com/). Therefore, it is 15 | > required to use it for this project as well. Using other package managers, you 16 | > will receive a corresponding error. 17 | 18 | ## Install Dependencies 19 | 20 | If you have just cloned this template, you should install the project 21 | dependencies using the command: 22 | 23 | ```Bash 24 | npm install 25 | ``` 26 | 27 | ## Scripts 28 | 29 | This project contains the following scripts: 30 | 31 | - `dev`. Runs the application in development mode. 32 | - `dev:https`. Runs the application in development mode using locally created valid SSL-certificates. 33 | - `build`. Builds the application for production. 34 | - `lint`. Runs [eslint](https://eslint.org/) to ensure the code quality meets 35 | the required standards. 36 | - `deploy`. Deploys the application to GitHub Pages. 37 | 38 | To run a script, use the `npm run` command: 39 | 40 | ```Bash 41 | npm run {script} 42 | # Example: npm run build 43 | ``` 44 | 45 | ## Create Bot and Mini App 46 | 47 | Before you start, make sure you have already created a Telegram Bot. Here is 48 | a [comprehensive guide](https://docs.telegram-mini-apps.com/platform/creating-new-app) 49 | on how to do it. 50 | 51 | ## Run 52 | 53 | Although Mini Apps are designed to be opened 54 | within [Telegram applications](https://docs.telegram-mini-apps.com/platform/about#supported-applications), 55 | you can still develop and test them outside of Telegram during the development 56 | process. 57 | 58 | To run the application in the development mode, use the `dev` script: 59 | 60 | ```bash 61 | npm run dev:https 62 | ``` 63 | 64 | > [!NOTE] 65 | > As long as we use [vite-plugin-mkcert](https://www.npmjs.com/package/vite-plugin-mkcert), 66 | > launching the dev mode for the first time, you may see sudo password request. 67 | > The plugin requires it to properly configure SSL-certificates. To disable the plugin, use the `npm run dev` command. 68 | 69 | After this, you will see a similar message in your terminal: 70 | 71 | ```bash 72 | VITE v5.2.12 ready in 237 ms 73 | 74 | ➜ Local: https://localhost:5173/reactjs-template 75 | ➜ Network: https://172.18.16.1:5173/reactjs-template 76 | ➜ Network: https://172.19.32.1:5173/reactjs-template 77 | ➜ Network: https://192.168.0.171:5173/reactjs-template 78 | ➜ press h + enter to show help 79 | ``` 80 | 81 | Here, you can see the `Local` link, available locally, and `Network` links 82 | accessible to all devices in the same network with the current device. 83 | 84 | To view the application, you need to open the `Local` 85 | link (`https://localhost:5173/reactjs-template` in this example) in your 86 | browser: 87 | 88 | ![Application](assets/application.png) 89 | 90 | It is important to note that some libraries in this template, such as 91 | `@telegram-apps/sdk`, are not intended for use outside of Telegram. 92 | 93 | Nevertheless, they appear to function properly. This is because the 94 | `src/mockEnv.ts` file, which is imported in the application's entry point ( 95 | `src/index.ts`), employs the `mockTelegramEnv` function to simulate the Telegram 96 | environment. This trick convinces the application that it is running in a 97 | Telegram-based environment. Therefore, be cautious not to use this function in 98 | production mode unless you fully understand its implications. 99 | 100 | > [!WARNING] 101 | > Because we are using self-signed SSL certificates, the Android and iOS 102 | > Telegram applications will not be able to display the application. These 103 | > operating systems enforce stricter security measures, preventing the Mini App 104 | > from loading. To address this issue, refer to 105 | > [this guide](https://docs.telegram-mini-apps.com/platform/getting-app-link#remote). 106 | 107 | ## Deploy 108 | 109 | This boilerplate uses GitHub Pages as the way to host the application 110 | externally. GitHub Pages provides a CDN which will let your users receive the 111 | application rapidly. Alternatively, you could use such services 112 | as [Heroku](https://www.heroku.com/) or [Vercel](https://vercel.com). 113 | 114 | ### Manual Deployment 115 | 116 | This boilerplate uses the [gh-pages](https://www.npmjs.com/package/gh-pages) 117 | tool, which allows deploying your application right from your PC. 118 | 119 | #### Configuring 120 | 121 | Before running the deployment process, ensure that you have done the following: 122 | 123 | 1. Replaced the `homepage` value in `package.json`. The GitHub Pages deploy tool 124 | uses this value to 125 | determine the related GitHub project. 126 | 2. Replaced the `base` value in `vite.config.ts` and have set it to the name of 127 | your GitHub 128 | repository. Vite will use this value when creating paths to static assets. 129 | 130 | For instance, if your GitHub username is `telegram-mini-apps` and the repository 131 | name is `is-awesome`, the value in the `homepage` field should be the following: 132 | 133 | ```json 134 | { 135 | "homepage": "https://telegram-mini-apps.github.io/is-awesome" 136 | } 137 | ``` 138 | 139 | And `vite.config.ts` should have this content: 140 | 141 | ```ts 142 | export default defineConfig({ 143 | base: '/is-awesome/', 144 | // ... 145 | }); 146 | ``` 147 | 148 | You can find more information on configuring the deployment in the `gh-pages` 149 | [docs](https://github.com/tschaub/gh-pages?tab=readme-ov-file#github-pages-project-sites). 150 | 151 | #### Before Deploying 152 | 153 | Before deploying the application, make sure that you've built it and going to 154 | deploy the fresh static files: 155 | 156 | ```bash 157 | npm run build 158 | ``` 159 | 160 | Then, run the deployment process, using the `deploy` script: 161 | 162 | ```Bash 163 | npm run deploy 164 | ``` 165 | 166 | After the deployment completed successfully, visit the page with data according 167 | to your username and repository name. Here is the page link example using the 168 | data mentioned above: 169 | https://telegram-mini-apps.github.io/is-awesome 170 | 171 | ### GitHub Workflow 172 | 173 | To simplify the deployment process, this template includes a 174 | pre-configured [GitHub workflow](.github/workflows/github-pages-deploy.yml) that 175 | automatically deploys the project when changes are pushed to the `master` 176 | branch. 177 | 178 | To enable this workflow, create a new environment (or edit the existing one) in 179 | the GitHub repository settings and name it `github-pages`. Then, add the 180 | `master` branch to the list of deployment branches. 181 | 182 | You can find the environment settings using this 183 | URL: `https://github.com/{username}/{repository}/settings/environments`. 184 | 185 | ![img.png](.github/deployment-branches.png) 186 | 187 | In case, you don't want to do it automatically, or you don't use GitHub as the 188 | project codebase, remove the `.github` directory. 189 | 190 | ### GitHub Web Interface 191 | 192 | Alternatively, developers can configure automatic deployment using the GitHub 193 | web interface. To do this, follow the link: 194 | `https://github.com/{username}/{repository}/settings/pages`. 195 | 196 | ## TON Connect 197 | 198 | This boilerplate utilizes 199 | the [TON Connect](https://docs.ton.org/develop/dapps/ton-connect/overview) 200 | project to demonstrate how developers can integrate functionality related to TON 201 | cryptocurrency. 202 | 203 | The TON Connect manifest used in this boilerplate is stored in the `public` 204 | folder, where all publicly accessible static files are located. Remember 205 | to [configure](https://docs.ton.org/develop/dapps/ton-connect/manifest) this 206 | file according to your project's information. 207 | 208 | ## Useful Links 209 | 210 | - [Platform documentation](https://docs.telegram-mini-apps.com/) 211 | - [@telegram-apps/sdk-react documentation](https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk-react) 212 | - [Telegram developers community chat](https://t.me/devs) 213 | -------------------------------------------------------------------------------- /assets/application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Telegram-Mini-Apps/reactjs-template/59212756e1782ffb962af763063387a1a46a1ea6/assets/application.png -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import eslint from '@eslint/js'; 4 | import tseslint from 'typescript-eslint'; 5 | import react from 'eslint-plugin-react'; 6 | import reactHooks from 'eslint-plugin-react-hooks'; 7 | import globals from 'globals'; 8 | 9 | export default tseslint.config( 10 | { 11 | files: ['src/**/*.{js,jsx,mjs,cjs,ts,tsx}'], 12 | extends: [ 13 | eslint.configs.recommended, 14 | ...tseslint.configs.recommendedTypeChecked, 15 | ], 16 | plugins: { 17 | react, 18 | 'react-hooks': reactHooks 19 | }, 20 | languageOptions: { 21 | parserOptions: { 22 | ecmaVersion: 'latest', 23 | sourceType: 'module', 24 | ecmaFeatures: { 25 | jsx: true, 26 | }, 27 | }, 28 | globals: { 29 | ...globals.browser, 30 | }, 31 | }, 32 | rules: { 33 | '@typescript-eslint/no-unused-expressions': 0, 34 | }, 35 | } 36 | ); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Telegram Mini App 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactjs-template", 3 | "private": true, 4 | "version": "0.0.1", 5 | "type": "module", 6 | "homepage": "https://telegram-mini-apps.github.io/reactjs-template", 7 | "scripts": { 8 | "deploy": "gh-pages -d dist", 9 | "dev": "vite", 10 | "dev:https": "cross-env HTTPS=true vite", 11 | "build": "tsc --noEmit && vite build", 12 | "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 13 | "lint:fix": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix", 14 | "preview": "vite preview", 15 | "predeploy": "npm run build" 16 | }, 17 | "dependencies": { 18 | "@telegram-apps/sdk-react": "^3.1.7", 19 | "@telegram-apps/telegram-ui": "^2.1.8", 20 | "@tonconnect/ui-react": "^2.1.0", 21 | "eruda": "^3.4.1", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-router-dom": "^6.23.0" 25 | }, 26 | "devDependencies": { 27 | "@eslint/js": "^9.23.0", 28 | "@types/node": "^20.12.11", 29 | "@types/react": "^18.2.0", 30 | "@types/react-dom": "^18.2.0", 31 | "@typescript-eslint/eslint-plugin": "^8.28.0", 32 | "@typescript-eslint/parser": "^8.28.0", 33 | "@vitejs/plugin-react-swc": "^3.8.1", 34 | "cross-env": "^7.0.3", 35 | "eslint": "^9.23.0", 36 | "eslint-plugin-react": "^7.37.4", 37 | "eslint-plugin-react-hooks": "^5.2.0", 38 | "gh-pages": "^6.1.1", 39 | "globals": "^15.2.0", 40 | "typescript": "^5.8.2", 41 | "typescript-eslint": "^8.28.0", 42 | "vite": "^6.2.4", 43 | "vite-plugin-mkcert": "^1.17.8", 44 | "vite-tsconfig-paths": "^5.1.4" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://ton.vote", 3 | "name": "TON Vote", 4 | "iconUrl": "https://ton.vote/logo.png" 5 | } -------------------------------------------------------------------------------- /src/components/App.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { Navigate, Route, Routes, HashRouter } from 'react-router-dom'; 3 | import { retrieveLaunchParams, useSignal, isMiniAppDark } from '@telegram-apps/sdk-react'; 4 | import { AppRoot } from '@telegram-apps/telegram-ui'; 5 | 6 | import { routes } from '@/navigation/routes.tsx'; 7 | 8 | export function App() { 9 | const lp = useMemo(() => retrieveLaunchParams(), []); 10 | const isDark = useSignal(isMiniAppDark); 11 | 12 | return ( 13 | 17 | 18 | 19 | {routes.map((route) => )} 20 | }/> 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/DisplayData/DisplayData.css: -------------------------------------------------------------------------------- 1 | .display-data__header { 2 | font-weight: 400; 3 | } 4 | 5 | .display-data__line { 6 | padding: 16px 24px; 7 | } 8 | 9 | .display-data__line-title { 10 | color: var(--tg-theme-subtitle-text-color); 11 | } 12 | 13 | .display-data__line-value { 14 | word-break: break-word; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/DisplayData/DisplayData.tsx: -------------------------------------------------------------------------------- 1 | import { isRGB } from '@telegram-apps/sdk-react'; 2 | import { Cell, Checkbox, Section } from '@telegram-apps/telegram-ui'; 3 | import type { FC, ReactNode } from 'react'; 4 | 5 | import { RGB } from '@/components/RGB/RGB.tsx'; 6 | import { Link } from '@/components/Link/Link.tsx'; 7 | import { bem } from '@/css/bem.ts'; 8 | 9 | import './DisplayData.css'; 10 | 11 | const [, e] = bem('display-data'); 12 | 13 | export type DisplayDataRow = 14 | & { title: string } 15 | & ( 16 | | { type: 'link'; value?: string } 17 | | { value: ReactNode } 18 | ) 19 | 20 | export interface DisplayDataProps { 21 | header?: ReactNode; 22 | footer?: ReactNode; 23 | rows: DisplayDataRow[]; 24 | } 25 | 26 | export const DisplayData: FC = ({ header, rows }) => ( 27 |
28 | {rows.map((item, idx) => { 29 | let valueNode: ReactNode; 30 | 31 | if (item.value === undefined) { 32 | valueNode = empty; 33 | } else { 34 | if ('type' in item) { 35 | valueNode = Open; 36 | } else if (typeof item.value === 'string') { 37 | valueNode = isRGB(item.value) 38 | ? 39 | : item.value; 40 | } else if (typeof item.value === 'boolean') { 41 | valueNode = ; 42 | } else { 43 | valueNode = item.value; 44 | } 45 | } 46 | 47 | return ( 48 | 55 | 56 | {valueNode} 57 | 58 | 59 | ); 60 | })} 61 |
62 | ); 63 | -------------------------------------------------------------------------------- /src/components/EnvUnsupported.tsx: -------------------------------------------------------------------------------- 1 | import { Placeholder, AppRoot } from '@telegram-apps/telegram-ui'; 2 | import { retrieveLaunchParams, isColorDark, isRGB } from '@telegram-apps/sdk-react'; 3 | import { useMemo } from 'react'; 4 | 5 | export function EnvUnsupported() { 6 | const [platform, isDark] = useMemo(() => { 7 | try { 8 | const lp = retrieveLaunchParams(); 9 | const { bg_color: bgColor } = lp.tgWebAppThemeParams; 10 | return [lp.tgWebAppPlatform, bgColor && isRGB(bgColor) ? isColorDark(bgColor) : false]; 11 | } catch { 12 | return ['android', false]; 13 | } 14 | }, []); 15 | 16 | return ( 17 | 21 | 25 | Telegram sticker 30 | 31 | 32 | ); 33 | } -------------------------------------------------------------------------------- /src/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | type ComponentType, 4 | type GetDerivedStateFromError, 5 | type PropsWithChildren, 6 | type ReactNode, 7 | } from 'react'; 8 | 9 | export interface ErrorBoundaryProps extends PropsWithChildren { 10 | fallback?: ReactNode | ComponentType<{ error: unknown }>; 11 | } 12 | 13 | interface ErrorBoundaryState { 14 | error?: unknown; 15 | } 16 | 17 | export class ErrorBoundary extends Component { 18 | state: ErrorBoundaryState = {}; 19 | 20 | // eslint-disable-next-line max-len 21 | static getDerivedStateFromError: GetDerivedStateFromError = (error) => ({ error }); 22 | 23 | componentDidCatch(error: Error) { 24 | this.setState({ error }); 25 | } 26 | 27 | render() { 28 | const { 29 | state: { 30 | error, 31 | }, 32 | props: { 33 | fallback: Fallback, 34 | children, 35 | }, 36 | } = this; 37 | 38 | return 'error' in this.state 39 | ? typeof Fallback === 'function' 40 | ? 41 | : Fallback 42 | : children; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/Link/Link.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-decoration: none; 3 | color: var(--tg-theme-link-color); 4 | } -------------------------------------------------------------------------------- /src/components/Link/Link.tsx: -------------------------------------------------------------------------------- 1 | import { openLink } from '@telegram-apps/sdk-react'; 2 | import { type FC, type MouseEventHandler, useCallback } from 'react'; 3 | import { Link as RouterLink, type LinkProps } from 'react-router-dom'; 4 | 5 | import { classNames } from '@/css/classnames.ts'; 6 | 7 | import './Link.css'; 8 | 9 | export const Link: FC = ({ 10 | className, 11 | onClick: propsOnClick, 12 | to, 13 | ...rest 14 | }) => { 15 | const onClick = useCallback>((e) => { 16 | propsOnClick?.(e); 17 | 18 | // Compute if target path is external. In this case we would like to open 19 | // link using TMA method. 20 | let path: string; 21 | if (typeof to === 'string') { 22 | path = to; 23 | } else { 24 | const { search = '', pathname = '', hash = '' } = to; 25 | path = `${pathname}?${search}#${hash}`; 26 | } 27 | 28 | const targetUrl = new URL(path, window.location.toString()); 29 | const currentUrl = new URL(window.location.toString()); 30 | const isExternal = targetUrl.protocol !== currentUrl.protocol 31 | || targetUrl.host !== currentUrl.host; 32 | 33 | if (isExternal) { 34 | e.preventDefault(); 35 | openLink(targetUrl.toString()); 36 | } 37 | }, [to, propsOnClick]); 38 | 39 | return ( 40 | 46 | ); 47 | }; 48 | -------------------------------------------------------------------------------- /src/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom'; 2 | import { hideBackButton, onBackButtonClick, showBackButton } from '@telegram-apps/sdk-react'; 3 | import { type PropsWithChildren, useEffect } from 'react'; 4 | 5 | export function Page({ children, back = true }: PropsWithChildren<{ 6 | /** 7 | * True if it is allowed to go back from this page. 8 | */ 9 | back?: boolean 10 | }>) { 11 | const navigate = useNavigate(); 12 | 13 | useEffect(() => { 14 | if (back) { 15 | showBackButton(); 16 | return onBackButtonClick(() => { 17 | navigate(-1); 18 | }); 19 | } 20 | hideBackButton(); 21 | }, [back]); 22 | 23 | return <>{children}; 24 | } -------------------------------------------------------------------------------- /src/components/RGB/RGB.css: -------------------------------------------------------------------------------- 1 | .rgb { 2 | display: inline-flex; 3 | align-items: center; 4 | gap: 5px; 5 | } 6 | 7 | .rgb__icon { 8 | width: 18px; 9 | aspect-ratio: 1; 10 | border: 1px solid #555; 11 | border-radius: 50%; 12 | } -------------------------------------------------------------------------------- /src/components/RGB/RGB.tsx: -------------------------------------------------------------------------------- 1 | import type { RGB as RGBType } from '@telegram-apps/sdk-react'; 2 | import type { FC } from 'react'; 3 | 4 | import { bem } from '@/css/bem.ts'; 5 | import { classNames } from '@/css/classnames.ts'; 6 | 7 | import './RGB.css'; 8 | 9 | const [b, e] = bem('rgb'); 10 | 11 | export type RGBProps = JSX.IntrinsicElements['div'] & { 12 | color: RGBType; 13 | }; 14 | 15 | export const RGB: FC = ({ color, className, ...rest }) => ( 16 | 17 | 18 | {color} 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/components/Root.tsx: -------------------------------------------------------------------------------- 1 | import { TonConnectUIProvider } from '@tonconnect/ui-react'; 2 | 3 | import { App } from '@/components/App.tsx'; 4 | import { ErrorBoundary } from '@/components/ErrorBoundary.tsx'; 5 | import { publicUrl } from '@/helpers/publicUrl.ts'; 6 | 7 | function ErrorBoundaryError({ error }: { error: unknown }) { 8 | return ( 9 |
10 |

An unhandled error occurred:

11 |
12 | 13 | {error instanceof Error 14 | ? error.message 15 | : typeof error === 'string' 16 | ? error 17 | : JSON.stringify(error)} 18 | 19 |
20 |
21 | ); 22 | } 23 | 24 | export function Root() { 25 | return ( 26 | 27 | 30 | 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/css/bem.ts: -------------------------------------------------------------------------------- 1 | import { classNames, isRecord } from '@/css/classnames.js'; 2 | 3 | export interface BlockFn { 4 | (...mods: any): string; 5 | } 6 | 7 | export interface ElemFn { 8 | (elem: string, ...mods: any): string; 9 | } 10 | 11 | /** 12 | * Applies mods to the specified element. 13 | * @param element - element name. 14 | * @param mod - mod to apply. 15 | */ 16 | function applyMods(element: string, mod: any): string { 17 | if (Array.isArray(mod)) { 18 | return classNames(mod.map(m => applyMods(element, m))); 19 | } 20 | if (isRecord(mod)) { 21 | return classNames( 22 | Object.entries(mod).map(([mod, v]) => v && applyMods(element, mod)), 23 | ); 24 | } 25 | const v = classNames(mod); 26 | return v && `${element}--${v}`; 27 | } 28 | 29 | /** 30 | * Computes final classname for the specified element. 31 | * @param element - element name. 32 | * @param mods - mod to apply. 33 | */ 34 | function computeClassnames(element: string, ...mods: any): string { 35 | return classNames(element, applyMods(element, mods)); 36 | } 37 | 38 | /** 39 | * @returns A tuple, containing two functions. The first one generates classnames list for the 40 | * block, the second one generates classnames for its elements. 41 | * @param block - BEM block name. 42 | */ 43 | export function bem(block: string): [BlockFn, ElemFn] { 44 | return [ 45 | (...mods) => computeClassnames(block, mods), 46 | (elem, ...mods) => computeClassnames(`${block}__${elem}`, mods), 47 | ]; 48 | } -------------------------------------------------------------------------------- /src/css/classnames.ts: -------------------------------------------------------------------------------- 1 | export function isRecord(v: unknown): v is Record { 2 | return !!v && typeof v === 'object' && !Array.isArray(v); 3 | } 4 | 5 | /** 6 | * Function which joins passed values with space following these rules: 7 | * 1. If value is non-empty string, it will be added to output. 8 | * 2. If value is object, only those keys will be added, which values are truthy. 9 | * 3. If value is array, classNames will be called with this value spread. 10 | * 4. All other values are ignored. 11 | * 12 | * You can find this function to similar one from the package {@link https://www.npmjs.com/package/classnames|classnames}. 13 | * @param values - values array. 14 | * @returns Final class name. 15 | */ 16 | export function classNames(...values: any[]): string { 17 | return values 18 | .map((value) => { 19 | if (typeof value === 'string') { 20 | return value; 21 | } 22 | 23 | if (isRecord(value)) { 24 | return classNames(Object.entries(value).map((entry) => entry[1] && entry[0])); 25 | } 26 | 27 | if (Array.isArray(value)) { 28 | return classNames(...value); 29 | } 30 | }) 31 | .filter(Boolean) 32 | .join(' '); 33 | } 34 | 35 | type UnionStringKeys = U extends U 36 | ? { [K in keyof U]-?: U[K] extends string | undefined ? K : never }[keyof U] 37 | : never; 38 | 39 | type UnionRequiredKeys = U extends U 40 | ? { [K in UnionStringKeys]: (object extends Pick ? never : K) }[UnionStringKeys] 41 | : never; 42 | 43 | type UnionOptionalKeys = Exclude, UnionRequiredKeys>; 44 | 45 | export type MergeClassNames = 46 | // Removes all types from union that will be ignored by the mergeClassNames function. 47 | Exclude extends infer Union 48 | ? 49 | & { [K in UnionRequiredKeys]: string; } 50 | & { [K in UnionOptionalKeys]?: string; } 51 | : never; 52 | 53 | /** 54 | * Merges two sets of classnames. 55 | * 56 | * The function expects to pass an array of objects with values that could be passed to 57 | * the `classNames` function. 58 | * @returns An object with keys from all objects with merged values. 59 | * @see classNames 60 | */ 61 | export function mergeClassNames(...partials: T): MergeClassNames { 62 | return partials.reduce>((acc, partial) => { 63 | if (isRecord(partial)) { 64 | Object.entries(partial).forEach(([key, value]) => { 65 | const className = classNames((acc as any)[key], value); 66 | if (className) { 67 | (acc as any)[key] = className; 68 | } 69 | }); 70 | } 71 | return acc; 72 | }, {} as MergeClassNames); 73 | } -------------------------------------------------------------------------------- /src/helpers/publicUrl.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @returns A complete public URL prefixed with the public static assets base 3 | * path. 4 | * @param path - path to prepend prefix to 5 | */ 6 | export function publicUrl(path: string): string { 7 | // The baseUrl must be ending with the slash. The reason is if the baseUrl will 8 | // equal to "/my-base", then passing the path equal to "tonconnect-manifest.json" will not 9 | // give us the expected result, it will actually be "/tonconnect-manifest.json", but the expected 10 | // one is "/my-base/tonconnect-manifest.json". This is due to the URL constructor. 11 | let baseUrl = import.meta.env.BASE_URL; 12 | if (!baseUrl.endsWith('/')) { 13 | baseUrl += '/'; 14 | } 15 | 16 | let isBaseAbsolute = false; 17 | try { 18 | new URL(baseUrl); 19 | isBaseAbsolute = true; 20 | } catch { /* empty */ 21 | } 22 | 23 | return new URL( 24 | // The path is not allowed to be starting with the slash as long as it will break the 25 | // base URL. For instance, having the "/my-base/" base URL and path 26 | // equal to "/tonconnect-manifest.json", we will not get the expected result like 27 | // "/my-base/tonconnect-manifest.json", but "/tonconnect-manifest.json". 28 | path.replace(/^\/+/, ''), 29 | isBaseAbsolute 30 | ? baseUrl 31 | : window.location.origin + baseUrl, 32 | ).toString(); 33 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: var(--tg-theme-secondary-bg-color, white); 3 | padding: 0; 4 | margin: 0; 5 | } -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | // Include Telegram UI styles first to allow our code override the package CSS. 2 | import '@telegram-apps/telegram-ui/dist/styles.css'; 3 | 4 | import ReactDOM from 'react-dom/client'; 5 | import { StrictMode } from 'react'; 6 | import { retrieveLaunchParams } from '@telegram-apps/sdk-react'; 7 | 8 | import { Root } from '@/components/Root.tsx'; 9 | import { EnvUnsupported } from '@/components/EnvUnsupported.tsx'; 10 | import { init } from '@/init.ts'; 11 | 12 | import './index.css'; 13 | 14 | // Mock the environment in case, we are outside Telegram. 15 | import './mockEnv.ts'; 16 | 17 | const root = ReactDOM.createRoot(document.getElementById('root')!); 18 | 19 | try { 20 | const launchParams = retrieveLaunchParams(); 21 | const { tgWebAppPlatform: platform } = launchParams; 22 | const debug = (launchParams.tgWebAppStartParam || '').includes('platformer_debug') 23 | || import.meta.env.DEV; 24 | 25 | // Configure all application dependencies. 26 | await init({ 27 | debug, 28 | eruda: debug && ['ios', 'android'].includes(platform), 29 | mockForMacOS: platform === 'macos', 30 | }) 31 | .then(() => { 32 | root.render( 33 | 34 | 35 | , 36 | ); 37 | }); 38 | } catch (e) { 39 | root.render(); 40 | } 41 | -------------------------------------------------------------------------------- /src/init.ts: -------------------------------------------------------------------------------- 1 | import { 2 | setDebug, 3 | mountBackButton, 4 | restoreInitData, 5 | init as initSDK, 6 | mountMiniApp, 7 | bindThemeParamsCssVars, 8 | mountViewport, 9 | bindViewportCssVars, 10 | mockTelegramEnv, 11 | type ThemeParams, 12 | themeParamsState, 13 | retrieveLaunchParams, 14 | emitEvent, 15 | } from '@telegram-apps/sdk-react'; 16 | 17 | /** 18 | * Initializes the application and configures its dependencies. 19 | */ 20 | export async function init(options: { 21 | debug: boolean; 22 | eruda: boolean; 23 | mockForMacOS: boolean; 24 | }): Promise { 25 | // Set @telegram-apps/sdk-react debug mode and initialize it. 26 | setDebug(options.debug); 27 | initSDK(); 28 | 29 | // Add Eruda if needed. 30 | options.eruda && void import('eruda').then(({ default: eruda }) => { 31 | eruda.init(); 32 | eruda.position({ x: window.innerWidth - 50, y: 0 }); 33 | }); 34 | 35 | // Telegram for macOS has a ton of bugs, including cases, when the client doesn't 36 | // even response to the "web_app_request_theme" method. It also generates an incorrect 37 | // event for the "web_app_request_safe_area" method. 38 | if (options.mockForMacOS) { 39 | let firstThemeSent = false; 40 | mockTelegramEnv({ 41 | onEvent(event, next) { 42 | if (event[0] === 'web_app_request_theme') { 43 | let tp: ThemeParams = {}; 44 | if (firstThemeSent) { 45 | tp = themeParamsState(); 46 | } else { 47 | firstThemeSent = true; 48 | tp ||= retrieveLaunchParams().tgWebAppThemeParams; 49 | } 50 | return emitEvent('theme_changed', { theme_params: tp }); 51 | } 52 | 53 | if (event[0] === 'web_app_request_safe_area') { 54 | return emitEvent('safe_area_changed', { left: 0, top: 0, right: 0, bottom: 0 }); 55 | } 56 | 57 | next(); 58 | }, 59 | }); 60 | } 61 | 62 | // Mount all components used in the project. 63 | mountBackButton.ifAvailable(); 64 | restoreInitData(); 65 | await Promise.all([ 66 | mountMiniApp.isAvailable() && mountMiniApp().then(() => { 67 | bindThemeParamsCssVars(); 68 | }), 69 | mountViewport.isAvailable() && mountViewport().then(() => { 70 | bindViewportCssVars(); 71 | }), 72 | ]); 73 | } -------------------------------------------------------------------------------- /src/mockEnv.ts: -------------------------------------------------------------------------------- 1 | import { mockTelegramEnv, isTMA, emitEvent } from '@telegram-apps/sdk-react'; 2 | 3 | // It is important, to mock the environment only for development purposes. When building the 4 | // application, import.meta.env.DEV will become false, and the code inside will be tree-shaken, 5 | // so you will not see it in your final bundle. 6 | if (import.meta.env.DEV) { 7 | if (!await isTMA('complete')) { 8 | const themeParams = { 9 | accent_text_color: '#6ab2f2', 10 | bg_color: '#17212b', 11 | button_color: '#5288c1', 12 | button_text_color: '#ffffff', 13 | destructive_text_color: '#ec3942', 14 | header_bg_color: '#17212b', 15 | hint_color: '#708499', 16 | link_color: '#6ab3f3', 17 | secondary_bg_color: '#232e3c', 18 | section_bg_color: '#17212b', 19 | section_header_text_color: '#6ab3f3', 20 | subtitle_text_color: '#708499', 21 | text_color: '#f5f5f5', 22 | } as const; 23 | const noInsets = { left: 0, top: 0, bottom: 0, right: 0 } as const; 24 | 25 | mockTelegramEnv({ 26 | onEvent(e) { 27 | // Here you can write your own handlers for all known Telegram MIni Apps methods. 28 | if (e[0] === 'web_app_request_theme') { 29 | return emitEvent('theme_changed', { theme_params: themeParams }); 30 | } 31 | if (e[0] === 'web_app_request_viewport') { 32 | return emitEvent('viewport_changed', { 33 | height: window.innerHeight, 34 | width: window.innerWidth, 35 | is_expanded: true, 36 | is_state_stable: true, 37 | }); 38 | } 39 | if (e[0] === 'web_app_request_content_safe_area') { 40 | return emitEvent('content_safe_area_changed', noInsets); 41 | } 42 | if (e[0] === 'web_app_request_safe_area') { 43 | return emitEvent('safe_area_changed', noInsets); 44 | } 45 | }, 46 | launchParams: new URLSearchParams([ 47 | // Discover more launch parameters: 48 | // https://docs.telegram-mini-apps.com/platform/launch-parameters#parameters-list 49 | ['tgWebAppThemeParams', JSON.stringify(themeParams)], 50 | // Your init data goes here. Learn more about it here: 51 | // https://docs.telegram-mini-apps.com/platform/init-data#parameters-list 52 | // 53 | // Note that to make sure, you are using a valid init data, you must pass it exactly as it 54 | // is sent from the Telegram application. The reason is in case you will sort its keys 55 | // (auth_date, hash, user, etc.) or values your own way, init data validation will more 56 | // likely to fail on your server side. So, to make sure you are working with a valid init 57 | // data, it is better to take a real one from your application and paste it here. It should 58 | // look something like this (a correctly encoded URL search params): 59 | // ``` 60 | // user=%7B%22id%22%3A279058397%2C%22first_name%22%3A%22Vladislav%22%2C%22last_name%22... 61 | // ``` 62 | // But in case you don't really need a valid init data, use this one: 63 | ['tgWebAppData', new URLSearchParams([ 64 | ['auth_date', (new Date().getTime() / 1000 | 0).toString()], 65 | ['hash', 'some-hash'], 66 | ['signature', 'some-signature'], 67 | ['user', JSON.stringify({ id: 1, first_name: 'Vladislav' })], 68 | ]).toString()], 69 | ['tgWebAppVersion', '8.4'], 70 | ['tgWebAppPlatform', 'tdesktop'], 71 | ]), 72 | }); 73 | 74 | console.info( 75 | '⚠️ As long as the current environment was not considered as the Telegram-based one, it was mocked. Take a note, that you should not do it in production and current behavior is only specific to the development process. Environment mocking is also applied only in development mode. So, after building the application, you will not see this behavior and related warning, leading to crashing the application outside Telegram.', 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/navigation/routes.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentType, JSX } from 'react'; 2 | 3 | import { IndexPage } from '@/pages/IndexPage/IndexPage'; 4 | import { InitDataPage } from '@/pages/InitDataPage.tsx'; 5 | import { LaunchParamsPage } from '@/pages/LaunchParamsPage.tsx'; 6 | import { ThemeParamsPage } from '@/pages/ThemeParamsPage.tsx'; 7 | import { TONConnectPage } from '@/pages/TONConnectPage/TONConnectPage'; 8 | 9 | interface Route { 10 | path: string; 11 | Component: ComponentType; 12 | title?: string; 13 | icon?: JSX.Element; 14 | } 15 | 16 | export const routes: Route[] = [ 17 | { path: '/', Component: IndexPage }, 18 | { path: '/init-data', Component: InitDataPage, title: 'Init Data' }, 19 | { path: '/theme-params', Component: ThemeParamsPage, title: 'Theme Params' }, 20 | { path: '/launch-params', Component: LaunchParamsPage, title: 'Launch Params' }, 21 | { 22 | path: '/ton-connect', 23 | Component: TONConnectPage, 24 | title: 'TON Connect', 25 | icon: ( 26 | 33 | 37 | 41 | 42 | ), 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /src/pages/IndexPage/IndexPage.tsx: -------------------------------------------------------------------------------- 1 | import { Section, Cell, Image, List } from '@telegram-apps/telegram-ui'; 2 | import type { FC } from 'react'; 3 | 4 | import { Link } from '@/components/Link/Link.tsx'; 5 | import { Page } from '@/components/Page.tsx'; 6 | 7 | import tonSvg from './ton.svg'; 8 | 9 | export const IndexPage: FC = () => { 10 | return ( 11 | 12 | 13 |
17 | 18 | } 20 | subtitle="Connect your TON wallet" 21 | > 22 | TON Connect 23 | 24 | 25 |
26 |
30 | 31 | Init Data 32 | 33 | 34 | Launch Parameters 35 | 36 | 37 | Theme Parameters 38 | 39 |
40 |
41 |
42 | ); 43 | }; 44 | -------------------------------------------------------------------------------- /src/pages/IndexPage/ton.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/InitDataPage.tsx: -------------------------------------------------------------------------------- 1 | import { type FC, useMemo } from 'react'; 2 | import { 3 | initDataRaw as _initDataRaw, 4 | initDataState as _initDataState, 5 | type User, 6 | useSignal, 7 | } from '@telegram-apps/sdk-react'; 8 | import { List, Placeholder } from '@telegram-apps/telegram-ui'; 9 | 10 | import { DisplayData, type DisplayDataRow } from '@/components/DisplayData/DisplayData.tsx'; 11 | import { Page } from '@/components/Page.tsx'; 12 | 13 | function getUserRows(user: User): DisplayDataRow[] { 14 | return Object.entries(user).map(([title, value]) => ({ title, value })); 15 | } 16 | 17 | export const InitDataPage: FC = () => { 18 | const initDataRaw = useSignal(_initDataRaw); 19 | const initDataState = useSignal(_initDataState); 20 | 21 | const initDataRows = useMemo(() => { 22 | if (!initDataState || !initDataRaw) { 23 | return; 24 | } 25 | return [ 26 | { title: 'raw', value: initDataRaw }, 27 | ...Object.entries(initDataState).reduce((acc, [title, value]) => { 28 | if (value instanceof Date) { 29 | acc.push({ title, value: value.toISOString() }); 30 | } else if (!value || typeof value !== 'object') { 31 | acc.push({ title, value }); 32 | } 33 | return acc; 34 | }, []), 35 | ]; 36 | }, [initDataState, initDataRaw]); 37 | 38 | const userRows = useMemo(() => { 39 | return initDataState && initDataState.user 40 | ? getUserRows(initDataState.user) 41 | : undefined; 42 | }, [initDataState]); 43 | 44 | const receiverRows = useMemo(() => { 45 | return initDataState && initDataState.receiver 46 | ? getUserRows(initDataState.receiver) 47 | : undefined; 48 | }, [initDataState]); 49 | 50 | const chatRows = useMemo(() => { 51 | return !initDataState?.chat 52 | ? undefined 53 | : Object.entries(initDataState.chat).map(([title, value]) => ({ title, value })); 54 | }, [initDataState]); 55 | 56 | if (!initDataRows) { 57 | return ( 58 | 59 | 63 | Telegram sticker 68 | 69 | 70 | ); 71 | } 72 | return ( 73 | 74 | 75 | 76 | {userRows && } 77 | {receiverRows && } 78 | {chatRows && } 79 | 80 | 81 | ); 82 | }; 83 | -------------------------------------------------------------------------------- /src/pages/LaunchParamsPage.tsx: -------------------------------------------------------------------------------- 1 | import { retrieveLaunchParams } from '@telegram-apps/sdk-react'; 2 | import { List } from '@telegram-apps/telegram-ui'; 3 | import { type FC, useMemo } from 'react'; 4 | 5 | import { DisplayData } from '@/components/DisplayData/DisplayData.tsx'; 6 | import { Page } from '@/components/Page.tsx'; 7 | 8 | export const LaunchParamsPage: FC = () => { 9 | const lp = useMemo(() => retrieveLaunchParams(), []); 10 | 11 | return ( 12 | 13 | 14 | 25 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/pages/TONConnectPage/TONConnectPage.css: -------------------------------------------------------------------------------- 1 | .ton-connect-page__placeholder { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | box-sizing: border-box; 8 | } 9 | 10 | .ton-connect-page__button { 11 | margin: 16px auto 0; 12 | } 13 | 14 | .ton-connect-page__button-connected { 15 | margin: 16px 24px 16px auto; 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/TONConnectPage/TONConnectPage.tsx: -------------------------------------------------------------------------------- 1 | import { openLink } from '@telegram-apps/sdk-react'; 2 | import { TonConnectButton, useTonWallet } from '@tonconnect/ui-react'; 3 | import { 4 | Avatar, 5 | Cell, 6 | List, 7 | Navigation, 8 | Placeholder, 9 | Section, 10 | Text, 11 | Title, 12 | } from '@telegram-apps/telegram-ui'; 13 | import type { FC } from 'react'; 14 | 15 | import { DisplayData } from '@/components/DisplayData/DisplayData.tsx'; 16 | import { Page } from '@/components/Page.tsx'; 17 | import { bem } from '@/css/bem.ts'; 18 | 19 | import './TONConnectPage.css'; 20 | 21 | const [, e] = bem('ton-connect-page'); 22 | 23 | export const TONConnectPage: FC = () => { 24 | const wallet = useTonWallet(); 25 | 26 | if (!wallet) { 27 | return ( 28 | 29 | 34 | 35 | To display the data related to the TON Connect, it is required to connect your 36 | wallet 37 | 38 | 39 | 40 | } 41 | /> 42 | 43 | ); 44 | } 45 | 46 | const { 47 | account: { chain, publicKey, address }, 48 | device: { 49 | appName, 50 | appVersion, 51 | maxProtocolVersion, 52 | platform, 53 | features, 54 | }, 55 | } = wallet; 56 | 57 | return ( 58 | 59 | 60 | {'imageUrl' in wallet && ( 61 | <> 62 |
63 | 66 | } 67 | after={About wallet} 68 | subtitle={wallet.appName} 69 | onClick={(e) => { 70 | e.preventDefault(); 71 | openLink(wallet.aboutUrl); 72 | }} 73 | > 74 | {wallet.name} 75 | 76 |
77 | 78 | 79 | )} 80 | 88 | typeof f === 'object' ? f.name : undefined) 99 | .filter(v => v) 100 | .join(', '), 101 | }, 102 | ]} 103 | /> 104 |
105 |
106 | ); 107 | }; 108 | -------------------------------------------------------------------------------- /src/pages/ThemeParamsPage.tsx: -------------------------------------------------------------------------------- 1 | import { themeParams, useSignal } from '@telegram-apps/sdk-react'; 2 | import type { FC } from 'react'; 3 | import { List } from '@telegram-apps/telegram-ui'; 4 | 5 | import { DisplayData } from '@/components/DisplayData/DisplayData.tsx'; 6 | import { Page } from '@/components/Page.tsx'; 7 | 8 | export const ThemeParamsPage: FC = () => { 9 | const tp = useSignal(themeParams.state); 10 | 11 | return ( 12 | 13 | 14 | ({ 19 | title: title 20 | .replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`) 21 | .replace(/background/, 'bg'), 22 | value, 23 | })) 24 | } 25 | /> 26 | 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowImportingTsExtensions": true, 4 | "isolatedModules": true, 5 | "jsx": "react-jsx", 6 | "lib": [ 7 | "ES2020", 8 | "DOM", 9 | "DOM.Iterable" 10 | ], 11 | "module": "ESNext", 12 | "moduleResolution": "bundler", 13 | "noEmit": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "paths": { 18 | "@/*": [ 19 | "./src/*" 20 | ] 21 | }, 22 | "resolveJsonModule": true, 23 | "skipLibCheck": true, 24 | "strict": true, 25 | "target": "ES2020", 26 | "useDefineForClassFields": true, 27 | "types": [ 28 | "vite/client" 29 | ] 30 | }, 31 | "include": [ 32 | "src" 33 | ], 34 | "references": [ 35 | { 36 | "path": "./tsconfig.node.json" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "vite.config.ts" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | import react from '@vitejs/plugin-react-swc'; 4 | import mkcert from 'vite-plugin-mkcert'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | base: '/reactjs-template/', 9 | css: { 10 | preprocessorOptions: { 11 | scss: { 12 | api: 'modern', 13 | }, 14 | }, 15 | }, 16 | plugins: [ 17 | // Allows using React dev server along with building a React application with Vite. 18 | // https://npmjs.com/package/@vitejs/plugin-react-swc 19 | react(), 20 | // Allows using the compilerOptions.paths property in tsconfig.json. 21 | // https://www.npmjs.com/package/vite-tsconfig-paths 22 | tsconfigPaths(), 23 | // Creates a custom SSL certificate valid for the local machine. 24 | // Using this plugin requires admin rights on the first dev-mode launch. 25 | // https://www.npmjs.com/package/vite-plugin-mkcert 26 | process.env.HTTPS && mkcert(), 27 | ], 28 | build: { 29 | target: 'esnext', 30 | }, 31 | publicDir: './public', 32 | server: { 33 | // Exposes your dev server and makes it accessible for the devices in the same network. 34 | host: true, 35 | }, 36 | }); 37 | 38 | --------------------------------------------------------------------------------