├── src ├── components │ ├── Link │ │ ├── Link.css │ │ └── Link.jsx │ ├── RGB │ │ ├── RGB.css │ │ └── RGB.jsx │ ├── DisplayData │ │ ├── DisplayData.css │ │ └── DisplayData.jsx │ ├── Page.jsx │ ├── App.jsx │ ├── Root.jsx │ └── ErrorBoundary.jsx ├── index.css ├── helpers │ └── publicUrl.js ├── pages │ ├── TONConnectPage │ │ ├── TONConnectPage.css │ │ └── TONConnectPage.jsx │ ├── ThemeParamsPage.jsx │ ├── IndexPage │ │ ├── ton.svg │ │ └── IndexPage.jsx │ ├── LaunchParamsPage.jsx │ └── InitDataPage.jsx ├── index.jsx ├── init.js ├── navigation │ └── routes.jsx └── mockEnv.js ├── assets └── application.png ├── .github ├── deployment-branches.png └── workflows │ └── github-pages-deploy.yml ├── public └── tonconnect-manifest.json ├── jsconfig.json ├── .gitignore ├── index.html ├── .eslintrc.cjs ├── vite.config.js ├── package.json └── README.md /src/components/Link/Link.css: -------------------------------------------------------------------------------- 1 | .link { 2 | text-decoration: none; 3 | color: var(--tg-theme-link-color); 4 | } -------------------------------------------------------------------------------- /assets/application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Telegram-Mini-Apps/reactjs-js-template/HEAD/assets/application.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: var(--tg-theme-secondary-bg-color, white); 3 | padding: 0; 4 | margin: 0; 5 | } -------------------------------------------------------------------------------- /.github/deployment-branches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Telegram-Mini-Apps/reactjs-js-template/HEAD/.github/deployment-branches.png -------------------------------------------------------------------------------- /public/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://ton.vote", 3 | "name": "TON Vote", 4 | "iconUrl": "https://ton.vote/logo.png" 5 | } -------------------------------------------------------------------------------- /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/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/helpers/publicUrl.js: -------------------------------------------------------------------------------- 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) { 7 | return new URL( 8 | path.replace(/^\/+/, ''), 9 | window.location.origin + import.meta.env.BASE_URL 10 | ).toString(); 11 | } -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "module": "ESNext", 5 | "moduleResolution": "bundler", 6 | "paths": { 7 | "@/*": [ 8 | "./src/*" 9 | ] 10 | }, 11 | "target": "ES2020" 12 | }, 13 | "include": [ 14 | "src/**/*" 15 | ], 16 | "exclude": [ 17 | "node_modules" 18 | ] 19 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React + JS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/Page.jsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom'; 2 | import { backButton } from '@telegram-apps/sdk-react'; 3 | import { useEffect } from 'react'; 4 | 5 | export function Page({ children, back = true }) { 6 | const navigate = useNavigate(); 7 | 8 | useEffect(() => { 9 | if (back) { 10 | backButton.show(); 11 | return backButton.onClick(() => { 12 | navigate(-1); 13 | }); 14 | } 15 | backButton.hide(); 16 | }, [back]); 17 | 18 | return <>{children}; 19 | } -------------------------------------------------------------------------------- /src/components/RGB/RGB.jsx: -------------------------------------------------------------------------------- 1 | import { classNames } from '@telegram-apps/sdk-react'; 2 | 3 | import './RGB.css'; 4 | 5 | /** 6 | * @param {import('@telegram-apps/sdk-react').RGB} color 7 | * @param {string} [className] 8 | * @param rest 9 | * @return {JSX.Element} 10 | */ 11 | export function RGB({ color, className, ...rest }) { 12 | return ( 13 | 14 | 15 | {color} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | import { StrictMode } from 'react'; 3 | import { retrieveLaunchParams } from '@telegram-apps/sdk-react'; 4 | 5 | import { Root } from '@/components/Root'; 6 | import { init } from '@/init.js'; 7 | 8 | import '@telegram-apps/telegram-ui/dist/styles.css'; 9 | import './index.css'; 10 | 11 | // Mock the environment in case, we are outside Telegram. 12 | import './mockEnv.js'; 13 | 14 | // Configure all application dependencies. 15 | init(retrieveLaunchParams().startParam === 'debug' || import.meta.env.DEV); 16 | 17 | ReactDOM.createRoot(document.getElementById('root')).render( 18 | 19 | 20 | , 21 | ); 22 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true 5 | }, 6 | extends: [ 7 | 'eslint:recommended', 8 | 'plugin:react/recommended' 9 | ], 10 | overrides: [ 11 | { 12 | env: { 13 | node: true 14 | }, 15 | files: [ 16 | '.eslintrc.{js,cjs}' 17 | ], 18 | parserOptions: { 19 | 'sourceType': 'script' 20 | } 21 | } 22 | ], 23 | parser: '@babel/eslint-parser', 24 | parserOptions: { 25 | requireConfigFile: false, 26 | babelOptions: { 27 | babelrc: false, 28 | configFile: false, 29 | presets: ['@babel/preset-env', '@babel/preset-react'], 30 | }, 31 | }, 32 | plugins: [ 33 | 'react' 34 | ], 35 | rules: { 36 | 'react/react-in-jsx-scope': 0, 37 | 'react/prop-types': 0, 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import { useLaunchParams, miniApp, useSignal } from '@telegram-apps/sdk-react'; 2 | import { AppRoot } from '@telegram-apps/telegram-ui'; 3 | import { Navigate, Route, Routes, HashRouter } from 'react-router-dom'; 4 | 5 | import { routes } from '@/navigation/routes.jsx'; 6 | 7 | export function App() { 8 | const lp = useLaunchParams(); 9 | const isDark = useSignal(miniApp.isDark); 10 | 11 | return ( 12 | 16 | 17 | 18 | {routes.map((route) => )} 19 | }/> 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/ThemeParamsPage.jsx: -------------------------------------------------------------------------------- 1 | import { themeParams, useSignal } from '@telegram-apps/sdk-react'; 2 | import { List } from '@telegram-apps/telegram-ui'; 3 | 4 | import { DisplayData } from '@/components/DisplayData/DisplayData.jsx'; 5 | import { Page } from '@/components/Page.jsx'; 6 | 7 | export function ThemeParamsPage() { 8 | const tp = useSignal(themeParams.state); 9 | 10 | return ( 11 | 12 | 13 | ({ 18 | title: title 19 | .replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`) 20 | .replace(/background/, 'bg'), 21 | value, 22 | })) 23 | } 24 | /> 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Root.jsx: -------------------------------------------------------------------------------- 1 | import { TonConnectUIProvider } from '@tonconnect/ui-react'; 2 | 3 | import { App } from '@/components/App.jsx'; 4 | import { ErrorBoundary } from '@/components/ErrorBoundary.jsx'; 5 | import { publicUrl } from '@/helpers/publicUrl.js'; 6 | 7 | function ErrorBoundaryError({ error }) { 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 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from 'node:path'; 2 | import { fileURLToPath } from 'node:url'; 3 | import react from '@vitejs/plugin-react-swc'; 4 | import { defineConfig } from 'vite'; 5 | import mkcert from 'vite-plugin-mkcert'; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | base: '/reactjs-js-template', 10 | plugins: [ 11 | // Allows using React dev server along with building a React application with Vite. 12 | // https://npmjs.com/package/@vitejs/plugin-react-swc 13 | react(), 14 | // Create a custom SSL certificate valid for the local machine. 15 | // https://www.npmjs.com/package/vite-plugin-mkcert 16 | mkcert(), 17 | ], 18 | publicDir: './public', 19 | server: { 20 | // Exposes your dev server and makes it accessible for the devices in the same network. 21 | host: true, 22 | }, 23 | resolve: { 24 | alias: { 25 | '@': resolve(dirname(fileURLToPath(import.meta.url)), './src'), 26 | } 27 | }, 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /src/pages/IndexPage/ton.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/LaunchParamsPage.jsx: -------------------------------------------------------------------------------- 1 | import { useLaunchParams } from '@telegram-apps/sdk-react'; 2 | import { List } from '@telegram-apps/telegram-ui'; 3 | 4 | import { DisplayData } from '@/components/DisplayData/DisplayData.jsx'; 5 | import { Page } from '@/components/Page.jsx'; 6 | 7 | export function LaunchParamsPage() { 8 | const lp = useLaunchParams(); 9 | 10 | return ( 11 | 12 | 13 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | 3 | /** 4 | * @typedef {Object} ErrorBoundaryProps 5 | * @property {import('react').ReactNode} [children] 6 | * @property {import('react').ReactNode | import('react').ComponentType<{ error: unknown }>} fallback 7 | */ 8 | 9 | /** 10 | * @typedef {Object} ErrorBoundaryState 11 | * @property [error] 12 | */ 13 | 14 | export class ErrorBoundary extends Component { 15 | /** 16 | * @type ErrorBoundaryState 17 | */ 18 | state = {}; 19 | 20 | /** 21 | * @param error 22 | * @returns {ErrorBoundaryState} 23 | */ 24 | static getDerivedStateFromError = (error) => ({ error }); 25 | 26 | componentDidCatch(error) { 27 | this.setState({ error }); 28 | } 29 | 30 | render() { 31 | const { 32 | state: { 33 | error, 34 | }, 35 | props: { 36 | fallback: Fallback, 37 | children, 38 | }, 39 | } = this; 40 | 41 | return 'error' in this.state 42 | ? typeof Fallback === 'function' 43 | ? 44 | : Fallback 45 | : children; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/init.js: -------------------------------------------------------------------------------- 1 | import { 2 | backButton, 3 | viewport, 4 | themeParams, 5 | miniApp, 6 | initData, 7 | $debug, 8 | init as initSDK, 9 | } from '@telegram-apps/sdk-react'; 10 | 11 | /** 12 | * Initializes the application and configures its dependencies. 13 | */ 14 | export function init(debug) { 15 | // Set @telegram-apps/sdk-react debug mode. 16 | $debug.set(debug); 17 | 18 | // Initialize special event handlers for Telegram Desktop, Android, iOS, etc. 19 | // Also, configure the package. 20 | initSDK(); 21 | 22 | // Mount all components used in the project. 23 | backButton.isSupported() && backButton.mount(); 24 | miniApp.mount(); 25 | themeParams.mount(); 26 | initData.restore(); 27 | void viewport.mount().catch(e => { 28 | console.error('Something went wrong mounting the viewport', e); 29 | }); 30 | 31 | // Define components-related CSS variables. 32 | viewport.bindCssVars(); 33 | miniApp.bindCssVars(); 34 | themeParams.bindCssVars(); 35 | 36 | // Add Eruda if needed. 37 | debug && import('eruda') 38 | .then((lib) => lib.default.init()) 39 | .catch(console.error); 40 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reactjs-js-template", 3 | "private": true, 4 | "version": "0.0.1", 5 | "type": "module", 6 | "homepage": "https://telegram-mini-apps.github.io/reactjs-js-template", 7 | "scripts": { 8 | "deploy": "gh-pages -d dist", 9 | "dev": "vite", 10 | "build": "vite build", 11 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0", 12 | "lint:fix": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0 --fix", 13 | "preview": "vite preview", 14 | "predeploy": "npm run build" 15 | }, 16 | "dependencies": { 17 | "@telegram-apps/sdk-react": "^2.0.5", 18 | "@telegram-apps/telegram-ui": "^2.1.5", 19 | "@tonconnect/ui-react": "^2.0.5", 20 | "eruda": "^3.0.1", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0", 23 | "react-router-dom": "^6.24.0" 24 | }, 25 | "devDependencies": { 26 | "@babel/core": "^7.24.4", 27 | "@babel/eslint-parser": "^7.24.1", 28 | "@babel/preset-env": "^7.24.4", 29 | "@babel/preset-react": "^7.24.1", 30 | "@vitejs/plugin-basic-ssl": "^1.1.0", 31 | "@vitejs/plugin-react-swc": "^3.6.0", 32 | "eslint": "^8.57.0", 33 | "eslint-plugin-react": "^7.34.1", 34 | "eslint-plugin-react-hooks": "^4.6.0", 35 | "gh-pages": "^6.1.1", 36 | "globals": "^15.2.0", 37 | "vite": "^5.1.5", 38 | "vite-plugin-mkcert": "^1.17.6" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/Link/Link.jsx: -------------------------------------------------------------------------------- 1 | import { classNames, openLink } from '@telegram-apps/sdk-react'; 2 | import { useCallback } from 'react'; 3 | import { Link as RouterLink } from 'react-router-dom'; 4 | 5 | import './Link.css'; 6 | 7 | /** 8 | * @param {import('react-router-dom').LinkProps} props 9 | * @return {JSX.Element} 10 | */ 11 | export function Link({ 12 | className, 13 | onClick: propsOnClick, 14 | to, 15 | ...rest 16 | }) { 17 | const onClick = useCallback((e) => { 18 | propsOnClick?.(e); 19 | 20 | // Compute if target path is external. In this case we would like to open link using 21 | // TMA method. 22 | let path; 23 | if (typeof to === 'string') { 24 | path = to; 25 | } else { 26 | const { search = '', pathname = '', hash = '' } = to; 27 | path = `${pathname}?${search}#${hash}`; 28 | } 29 | 30 | const targetUrl = new URL(path, window.location.toString()); 31 | const currentUrl = new URL(window.location.toString()); 32 | const isExternal = targetUrl.protocol !== currentUrl.protocol 33 | || targetUrl.host !== currentUrl.host; 34 | 35 | if (isExternal) { 36 | e.preventDefault(); 37 | openLink(targetUrl.toString()); 38 | } 39 | }, [to, propsOnClick]); 40 | 41 | return ( 42 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /.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@v3 48 | 49 | - name: Upload artifact 50 | uses: actions/upload-pages-artifact@v2 51 | with: 52 | # Upload dist repository 53 | path: './dist' 54 | 55 | - name: Deploy to GitHub Pages 56 | id: deployment 57 | uses: actions/deploy-pages@v2 -------------------------------------------------------------------------------- /src/pages/IndexPage/IndexPage.jsx: -------------------------------------------------------------------------------- 1 | import { Section, Cell, Image, List } from '@telegram-apps/telegram-ui'; 2 | 3 | import { Link } from '@/components/Link/Link.jsx'; 4 | import { Page } from '@/components/Page.jsx'; 5 | 6 | import tonSvg from './ton.svg'; 7 | 8 | export function IndexPage() { 9 | return ( 10 | 11 | 12 |
16 | 17 | } 19 | subtitle="Connect your TON wallet" 20 | > 21 | TON Connect 22 | 23 | 24 |
25 |
29 | 30 | Init Data 31 | 32 | 33 | Launch Parameters 34 | 35 | 36 | Theme Parameters 37 | 38 |
39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/components/DisplayData/DisplayData.jsx: -------------------------------------------------------------------------------- 1 | import { isRGB } from '@telegram-apps/sdk-react'; 2 | import { Cell, Checkbox, Section } from '@telegram-apps/telegram-ui'; 3 | 4 | import { RGB } from '@/components/RGB/RGB.jsx'; 5 | import { Link } from '@/components/Link/Link.jsx'; 6 | 7 | import './DisplayData.css'; 8 | 9 | /** 10 | * @typedef {object} DisplayDataRow 11 | * @property {string} title 12 | * @property {string | boolean | import('react').ReactNode | import('@telegram-apps/sdk-react').RGB} [value] 13 | */ 14 | 15 | /** 16 | * @param {DisplayDataRow[]} rows - list of rows to be displayed. 17 | * @return {JSX.Element} 18 | */ 19 | export function DisplayData({ header, rows }) { 20 | return ( 21 |
22 | {rows.map((item, idx) => { 23 | let valueNode; 24 | 25 | if (item.value === undefined) { 26 | valueNode = empty; 27 | } else { 28 | if ('type' in item) { 29 | valueNode = Open; 30 | } else if (typeof item.value === 'string') { 31 | valueNode = isRGB(item.value) 32 | ? 33 | : item.value; 34 | } else if (typeof item.value === 'boolean') { 35 | valueNode = ; 36 | } else { 37 | valueNode = item.value; 38 | } 39 | } 40 | 41 | return ( 42 | 49 | 50 | {valueNode} 51 | 52 | 53 | ); 54 | })} 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/navigation/routes.jsx: -------------------------------------------------------------------------------- 1 | import { IndexPage } from '@/pages/IndexPage/IndexPage.jsx'; 2 | import { InitDataPage } from '@/pages/InitDataPage.jsx'; 3 | import { LaunchParamsPage } from '@/pages/LaunchParamsPage.jsx'; 4 | import { ThemeParamsPage } from '@/pages/ThemeParamsPage.jsx'; 5 | import { TONConnectPage } from '@/pages/TONConnectPage/TONConnectPage.jsx'; 6 | 7 | /** 8 | * @typedef {object} Route 9 | * @property {string} path 10 | * @property {import('react').ComponentType} Component 11 | * @property {string} [title] 12 | * @property {import('react').JSX.Element} [icon] 13 | */ 14 | 15 | /** 16 | * @type {Route[]} 17 | */ 18 | export const routes = [ 19 | { path: '/', Component: IndexPage }, 20 | { path: '/init-data', Component: InitDataPage, title: 'Init Data' }, 21 | { path: '/theme-params', Component: ThemeParamsPage, title: 'Theme Params' }, 22 | { path: '/launch-params', Component: LaunchParamsPage, title: 'Launch Params' }, 23 | { 24 | path: '/ton-connect', 25 | Component: TONConnectPage, 26 | title: 'TON Connect', 27 | icon: ( 28 | 35 | 39 | 43 | 44 | ), 45 | }, 46 | ]; 47 | -------------------------------------------------------------------------------- /src/mockEnv.js: -------------------------------------------------------------------------------- 1 | import { 2 | mockTelegramEnv, 3 | isTMA, 4 | parseInitData, 5 | retrieveLaunchParams 6 | } from '@telegram-apps/sdk-react'; 7 | 8 | // It is important, to mock the environment only for development purposes. 9 | // When building the application the import.meta.env.DEV will value become 10 | // `false` and the code inside will be tree-shaken (removed), so you will not 11 | // see it in your final bundle. 12 | if (import.meta.env.DEV) { 13 | await (async () => { 14 | if (await isTMA()) { 15 | return; 16 | } 17 | 18 | // Determine which launch params should be applied. We could already 19 | // apply them previously, or they may be specified on purpose using the 20 | // default launch parameters transmission method. 21 | let lp 22 | try { 23 | lp = retrieveLaunchParams(); 24 | } catch (e) { 25 | const initDataRaw = new URLSearchParams([ 26 | ['user', JSON.stringify({ 27 | id: 99281932, 28 | first_name: 'Andrew', 29 | last_name: 'Rogue', 30 | username: 'rogue', 31 | language_code: 'en', 32 | is_premium: true, 33 | allows_write_to_pm: true, 34 | })], 35 | ['hash', '89d6079ad6762351f38c6dbbc41bb53048019256a9443988af7a48bcad16ba31'], 36 | ['auth_date', '1716922846'], 37 | ['start_param', 'debug'], 38 | ['chat_type', 'sender'], 39 | ['chat_instance', '8428209589180549439'], 40 | ]).toString(); 41 | 42 | lp = { 43 | themeParams: { 44 | accentTextColor: '#6ab2f2', 45 | bgColor: '#17212b', 46 | buttonColor: '#5288c1', 47 | buttonTextColor: '#ffffff', 48 | destructiveTextColor: '#ec3942', 49 | headerBgColor: '#17212b', 50 | hintColor: '#708499', 51 | linkColor: '#6ab3f3', 52 | secondaryBgColor: '#232e3c', 53 | sectionBgColor: '#17212b', 54 | sectionHeaderTextColor: '#6ab3f3', 55 | subtitleTextColor: '#708499', 56 | textColor: '#f5f5f5', 57 | }, 58 | initData: parseInitData(initDataRaw), 59 | initDataRaw, 60 | version: '8', 61 | platform: 'tdesktop', 62 | } 63 | } 64 | 65 | mockTelegramEnv(lp); 66 | console.warn( 67 | '⚠️ 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.', 68 | ); 69 | })(); 70 | } -------------------------------------------------------------------------------- /src/pages/TONConnectPage/TONConnectPage.jsx: -------------------------------------------------------------------------------- 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 | 14 | import { DisplayData } from '@/components/DisplayData/DisplayData.jsx'; 15 | import { Page } from '@/components/Page.jsx'; 16 | 17 | import './TONConnectPage.css'; 18 | 19 | export function TONConnectPage() { 20 | const wallet = useTonWallet(); 21 | 22 | if (!wallet) { 23 | return ( 24 | 25 | 30 | 31 | To display the data related to the TON Connect, it is required to connect your 32 | wallet 33 | 34 | 35 | 36 | } 37 | /> 38 | 39 | ); 40 | } 41 | 42 | const { 43 | account: { chain, publicKey, address }, 44 | device: { 45 | appName, 46 | appVersion, 47 | maxProtocolVersion, 48 | platform, 49 | features, 50 | }, 51 | } = wallet; 52 | 53 | return ( 54 | 55 | 56 | {'imageUrl' in wallet && ( 57 | <> 58 |
59 | 62 | } 63 | after={About wallet} 64 | subtitle={wallet.appName} 65 | onClick={(e) => { 66 | e.preventDefault(); 67 | openLink(wallet.aboutUrl); 68 | }} 69 | > 70 | {wallet.name} 71 | 72 |
73 | 74 | 75 | )} 76 | 84 | typeof f === 'object' ? f.name : undefined) 95 | .filter(v => v) 96 | .join(', '), 97 | }, 98 | ]} 99 | /> 100 |
101 |
102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /src/pages/InitDataPage.jsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { initData, useSignal } from '@telegram-apps/sdk-react'; 3 | import { List, Placeholder } from '@telegram-apps/telegram-ui'; 4 | 5 | import { DisplayData } from '@/components/DisplayData/DisplayData.jsx'; 6 | import { Page } from '@/components/Page.jsx'; 7 | 8 | function getUserRows(user) { 9 | return [ 10 | { title: 'id', value: user.id.toString() }, 11 | { title: 'username', value: user.username }, 12 | { title: 'photo_url', value: user.photoUrl }, 13 | { title: 'last_name', value: user.lastName }, 14 | { title: 'first_name', value: user.firstName }, 15 | { title: 'is_bot', value: user.isBot }, 16 | { title: 'is_premium', value: user.isPremium }, 17 | { title: 'language_code', value: user.languageCode }, 18 | { title: 'allows_to_write_to_pm', value: user.allowsWriteToPm }, 19 | { title: 'added_to_attachment_menu', value: user.addedToAttachmentMenu }, 20 | ]; 21 | } 22 | 23 | export function InitDataPage() { 24 | const initDataRaw = useSignal(initData.raw); 25 | const initDataState = useSignal(initData.state); 26 | 27 | const initDataRows = useMemo(() => { 28 | if (!initDataState || !initDataRaw) { 29 | return; 30 | } 31 | const { 32 | authDate, 33 | hash, 34 | queryId, 35 | chatType, 36 | chatInstance, 37 | canSendAfter, 38 | startParam, 39 | } = initDataState; 40 | return [ 41 | { title: 'raw', value: initDataRaw }, 42 | { title: 'auth_date', value: authDate.toLocaleString() }, 43 | { title: 'auth_date (raw)', value: authDate.getTime() / 1000 }, 44 | { title: 'hash', value: hash }, 45 | { title: 'can_send_after', value: initData.canSendAfterDate()?.toISOString() }, 46 | { title: 'can_send_after (raw)', value: canSendAfter }, 47 | { title: 'query_id', value: queryId }, 48 | { title: 'start_param', value: startParam }, 49 | { title: 'chat_type', value: chatType }, 50 | { title: 'chat_instance', value: chatInstance }, 51 | ]; 52 | }, [initDataState, initDataRaw]); 53 | 54 | const userRows = useMemo(() => { 55 | return initDataState && initDataState.user 56 | ? getUserRows(initDataState.user) 57 | : undefined; 58 | }, [initDataState]); 59 | 60 | const receiverRows = useMemo(() => { 61 | return initDataState && initDataState.receiver 62 | ? getUserRows(initDataState.receiver) 63 | : undefined; 64 | }, [initDataState]); 65 | 66 | const chatRows = useMemo(() => { 67 | if (!initDataState?.chat) { 68 | return; 69 | } 70 | const { 71 | id, 72 | title, 73 | type, 74 | username, 75 | photoUrl, 76 | } = initDataState.chat; 77 | 78 | return [ 79 | { title: 'id', value: id.toString() }, 80 | { title: 'title', value: title }, 81 | { title: 'type', value: type }, 82 | { title: 'username', value: username }, 83 | { title: 'photo_url', value: photoUrl }, 84 | ]; 85 | }, [initData]); 86 | 87 | if (!initDataRows) { 88 | return ( 89 | 90 | 94 | Telegram sticker 99 | 100 | 101 | ); 102 | } 103 | return ( 104 | 105 | 106 | 107 | {userRows && } 108 | {receiverRows && } 109 | {chatRows && } 110 | 111 | 112 | ); 113 | } 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telegram Mini Apps React Template 2 | 3 | > [!WARNING] 4 | > This template is archived and is more likely to be out of date. Consider using its supported [TypeScript alternative](https://github.com/Telegram-Mini-Apps/reactjs-template). 5 | 6 | This template demonstrates how developers can implement a single-page 7 | application on the Telegram Mini Apps platform using the following technologies 8 | and libraries: 9 | 10 | - [React](https://react.dev/) 11 | - [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) 12 | - [TON Connect](https://docs.ton.org/develop/dapps/ton-connect/overview) 13 | - [@telegram-apps SDK](https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk/2-x) 14 | - [Telegram UI](https://github.com/Telegram-Mini-Apps/TelegramUI) 15 | - [Vite](https://vitejs.dev/) 16 | 17 | > The template was created using [npm](https://www.npmjs.com/). Therefore, it is 18 | > required to use it for this project as well. Using other package managers, you 19 | > will receive a corresponding error. 20 | 21 | ## Install Dependencies 22 | 23 | If you have just cloned this template, you should install the project 24 | dependencies using the command: 25 | 26 | ```Bash 27 | npm install 28 | ``` 29 | 30 | ## Scripts 31 | 32 | This project contains the following scripts: 33 | 34 | - `dev`. Runs the application in development mode. 35 | - `build`. Builds the application for production. 36 | - `lint`. Runs [eslint](https://eslint.org/) to ensure the code quality meets 37 | the required standards. 38 | - `deploy`. Deploys the application to GitHub Pages. 39 | 40 | To run a script, use the `npm run` command: 41 | 42 | ```Bash 43 | npm run {script} 44 | # Example: npm run build 45 | ``` 46 | 47 | ## Create Bot and Mini App 48 | 49 | Before you start, make sure you have already created a Telegram Bot. Here is 50 | a [comprehensive guide](https://docs.telegram-mini-apps.com/platform/creating-new-app) 51 | on how to do it. 52 | 53 | ## Run 54 | 55 | Although Mini Apps are designed to be opened 56 | within [Telegram applications](https://docs.telegram-mini-apps.com/platform/about#supported-applications), 57 | you can still develop and test them outside of Telegram during the development 58 | process. 59 | 60 | To run the application in the development mode, use the `dev` script: 61 | 62 | ```bash 63 | npm run dev 64 | ``` 65 | 66 | After this, you will see a similar message in your terminal: 67 | 68 | ```bash 69 | VITE v5.2.12 ready in 237 ms 70 | 71 | ➜ Local: https://localhost:5173/reactjs-js-template 72 | ➜ Network: https://172.18.16.1:5173/reactjs-js-template 73 | ➜ Network: https://172.19.32.1:5173/reactjs-js-template 74 | ➜ Network: https://192.168.0.171:5173/reactjs-js-template 75 | ➜ press h + enter to show help 76 | ``` 77 | 78 | Here, you can see the `Local` link, available locally, and `Network` links 79 | accessible to all devices in the same network with the current device. 80 | 81 | To view the application, you need to open the `Local` 82 | link (`https://localhost:5173/reactjs-js-template` in this example) in your 83 | browser: 84 | 85 | ![Application](assets/application.png) 86 | 87 | It is important to note that some libraries in this template, such as 88 | `@telegram-apps/sdk`, are not intended for use outside of Telegram. 89 | 90 | Nevertheless, they appear to function properly. This is because the 91 | `src/mockEnv.js` file, which is imported in the application's entry point ( 92 | `src/index.js`), employs the `mockTelegramEnv` function to simulate the Telegram 93 | environment. This trick convinces the application that it is running in a 94 | Telegram-based environment. Therefore, be cautious not to use this function in 95 | production mode unless you fully understand its implications. 96 | 97 | > [!WARNING] 98 | > Because we are using self-signed SSL certificates, the Android and iOS 99 | > Telegram applications will not be able to display the application. These 100 | > operating systems enforce stricter security measures, preventing the Mini App 101 | > from loading. To address this issue, refer to 102 | > [this guide](https://docs.telegram-mini-apps.com/platform/getting-app-link#remote). 103 | 104 | ## Deploy 105 | 106 | This boilerplate uses GitHub Pages as the way to host the application 107 | externally. GitHub Pages provides a CDN which will let your users receive the 108 | application rapidly. Alternatively, you could use such services 109 | as [Heroku](https://www.heroku.com/) or [Vercel](https://vercel.com). 110 | 111 | ### Manual Deployment 112 | 113 | This boilerplate uses the [gh-pages](https://www.npmjs.com/package/gh-pages) 114 | tool, which allows deploying your application right from your PC. 115 | 116 | #### Configuring 117 | 118 | Before running the deployment process, ensure that you have done the following: 119 | 120 | 1. Replaced the `homepage` value in `package.json`. The GitHub Pages deploy tool 121 | uses this value to 122 | determine the related GitHub project. 123 | 2. Replaced the `base` value in `vite.config.js` and have set it to the name of 124 | your GitHub 125 | repository. Vite will use this value when creating paths to static assets. 126 | 127 | For instance, if your GitHub username is `telegram-mini-apps` and the repository 128 | name is `is-awesome`, the value in the `homepage` field should be the following: 129 | 130 | ```json 131 | { 132 | "homepage": "https://telegram-mini-apps.github.io/is-awesome" 133 | } 134 | ``` 135 | 136 | And `vite.config.js` should have this content: 137 | 138 | ```ts 139 | export default defineConfig({ 140 | base: '/is-awesome/', 141 | // ... 142 | }); 143 | ``` 144 | 145 | You can find more information on configuring the deployment in the `gh-pages` 146 | [docs](https://github.com/tschaub/gh-pages?tab=readme-ov-file#github-pages-project-sites). 147 | 148 | #### Before Deploying 149 | 150 | Before deploying the application, make sure that you've built it and going to 151 | deploy the fresh static files: 152 | 153 | ```bash 154 | npm run build 155 | ``` 156 | 157 | Then, run the deployment process, using the `deploy` script: 158 | 159 | ```Bash 160 | npm run deploy 161 | ``` 162 | 163 | After the deployment completed successfully, visit the page with data according 164 | to your username and repository name. Here is the page link example using the 165 | data mentioned above: 166 | https://telegram-mini-apps.github.io/is-awesome 167 | 168 | ### GitHub Workflow 169 | 170 | To simplify the deployment process, this template includes a 171 | pre-configured [GitHub workflow](.github/workflows/github-pages-deploy.yml) that 172 | automatically deploys the project when changes are pushed to the `master` 173 | branch. 174 | 175 | To enable this workflow, create a new environment (or edit the existing one) in 176 | the GitHub repository settings and name it `github-pages`. Then, add the 177 | `master` branch to the list of deployment branches. 178 | 179 | You can find the environment settings using this 180 | URL: `https://github.com/{username}/{repository}/settings/environments`. 181 | 182 | ![img.png](.github/deployment-branches.png) 183 | 184 | In case, you don't want to do it automatically, or you don't use GitHub as the 185 | project codebase, remove the `.github` directory. 186 | 187 | ### GitHub Web Interface 188 | 189 | Alternatively, developers can configure automatic deployment using the GitHub 190 | web interface. To do this, follow the link: 191 | `https://github.com/{username}/{repository}/settings/pages`. 192 | 193 | ## TON Connect 194 | 195 | This boilerplate utilizes 196 | the [TON Connect](https://docs.ton.org/develop/dapps/ton-connect/overview) 197 | project to demonstrate how developers can integrate functionality related to TON 198 | cryptocurrency. 199 | 200 | The TON Connect manifest used in this boilerplate is stored in the `public` 201 | folder, where all publicly accessible static files are located. Remember 202 | to [configure](https://docs.ton.org/develop/dapps/ton-connect/manifest) this 203 | file according to your project's information. 204 | 205 | ## Useful Links 206 | 207 | - [Platform documentation](https://docs.telegram-mini-apps.com/) 208 | - [@telegram-apps/sdk-react documentation](https://docs.telegram-mini-apps.com/packages/telegram-apps-sdk-react) 209 | - [Telegram developers community chat](https://t.me/devs) 210 | --------------------------------------------------------------------------------