├── 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 |
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 |
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 | 
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 | 
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 |
--------------------------------------------------------------------------------