From 504232fdadbfb17b6137760b652c62e7db966f38 Mon Sep 17 00:00:00 2001 From: Muhammaddiyor Tohirov Date: Sat, 19 Oct 2024 18:09:55 +0500 Subject: [PATCH 1/5] feat: implement Telegram Mini App integration - Create basic project structure for Telegram Mini Apps - Add core functionality to interact with TMA API - Set up configuration for Telegram Web App integration --- .env.example | 12 +++- .gitignore | 1 + next.config.mjs | 7 ++- package.json | 4 +- pnpm-lock.yaml | 36 +++++++++++ src/components/index.ts | 13 ++++ src/env.mjs | 46 ++++++++++++++ src/hooks/index.ts | 16 +++++ src/hooks/useClientOnce.ts | 28 +++++++++ src/hooks/useDidMount.ts | 24 +++++++ src/hooks/useTelegramMock.ts | 117 +++++++++++++++++++++++++++++++++++ src/init.ts | 64 +++++++++++++++++++ src/utils/index.ts | 16 ++++- tsconfig.json | 3 + 14 files changed, 380 insertions(+), 7 deletions(-) create mode 100644 src/env.mjs create mode 100644 src/hooks/index.ts create mode 100644 src/hooks/useClientOnce.ts create mode 100644 src/hooks/useDidMount.ts create mode 100644 src/hooks/useTelegramMock.ts create mode 100644 src/init.ts diff --git a/.env.example b/.env.example index 2bbcc47..eb02943 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,11 @@ -ANALYZE=false # true | false +# Set the Node.js environment (production: for live deployment, development: for local development) +NODE_ENV=production -NODE_ENV=production # production | development +# Enable or disable bundle analysis (true/false) +ANALYZE=false + +# Open bundle analyzer automatically after build (true/false) +OPEN_ANALYZER=false + +# Set the mode for bundle analyzer output (static: HTML file, json: JSON file) +ANALYZER_MODE=static diff --git a/.gitignore b/.gitignore index 33caad6..3883958 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ # testing /coverage +/analyze # next.js /.next/ diff --git a/next.config.mjs b/next.config.mjs index b0a4641..162c8e1 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,9 +1,10 @@ import bundleAnalyzer from '@next/bundle-analyzer' +import { env } from './src/env.mjs' const withBundleAnalyzer = bundleAnalyzer({ - enabled: process.env.ANALYZE === 'true', - openAnalyzer: false, - analyzerMode: 'static', + enabled: env.ANALYZE, + openAnalyzer: env.OPEN_ANALYZER, + analyzerMode: env.ANALYZER_MODE, }) /** diff --git a/package.json b/package.json index 0548109..9d2d6a0 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "tailwindcss": "^3.4.14", - "typescript": "^5.6.3" + "typescript": "^5.6.3", + "zod": "^3.23.8" }, "devDependencies": { "@eslint/compat": "^1.2.1", @@ -31,6 +32,7 @@ "@eslint/js": "^9.13.0", "@next/bundle-analyzer": "^14.2.15", "@next/eslint-plugin-next": "^14.2.15", + "@t3-oss/env-nextjs": "^0.11.1", "@types/node": "^22.7.6", "@types/react": "^18.3.11", "@types/react-dom": "^18.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9b13c6..4c587ad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ dependencies: typescript: specifier: ^5.6.3 version: 5.6.3 + zod: + specifier: ^3.23.8 + version: 3.23.8 devDependencies: '@eslint/compat': @@ -43,6 +46,9 @@ devDependencies: '@next/eslint-plugin-next': specifier: ^14.2.15 version: 14.2.15 + '@t3-oss/env-nextjs': + specifier: ^0.11.1 + version: 0.11.1(typescript@5.6.3)(zod@3.23.8) '@types/node': specifier: ^22.7.6 version: 22.7.6 @@ -386,6 +392,33 @@ packages: tslib: 2.8.0 dev: false + /@t3-oss/env-core@0.11.1(typescript@5.6.3)(zod@3.23.8): + resolution: {integrity: sha512-MaxOwEoG1ntCFoKJsS7nqwgcxLW1SJw238AJwfJeaz3P/8GtkxXZsPPolsz1AdYvUTbe3XvqZ/VCdfjt+3zmKw==} + peerDependencies: + typescript: '>=5.0.0' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + typescript: 5.6.3 + zod: 3.23.8 + dev: true + + /@t3-oss/env-nextjs@0.11.1(typescript@5.6.3)(zod@3.23.8): + resolution: {integrity: sha512-rx2XL9+v6wtOqLNJbD5eD8OezKlQD1BtC0WvvtHwBgK66jnF5+wGqtgkKK4Ygie1LVmoDClths2T4tdFmRvGrQ==} + peerDependencies: + typescript: '>=5.0.0' + zod: ^3.0.0 + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@t3-oss/env-core': 0.11.1(typescript@5.6.3)(zod@3.23.8) + typescript: 5.6.3 + zod: 3.23.8 + dev: true + /@telegram-apps/bridge@1.3.0: resolution: {integrity: sha512-9P2SEhr1SSo8JQsbfm4fIzYhZY0wwH6PEOmSZ0kNXZ4xX1iSftJ3T7kSbcaQL2zIVrKO8PJxKeH4Etmkk9Oqgg==} dependencies: @@ -2965,3 +2998,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zod@3.23.8: + resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} diff --git a/src/components/index.ts b/src/components/index.ts index e69de29..730ec5a 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -0,0 +1,13 @@ +/** + * This file exports all components from the #components directory. + * It serves as a central point for importing components throughout the application. + * When adding new components, make sure to export them here for easy access. + * + * Example usage: + * Instead of: + * import { Button } from '#components/Button' + * import { Input } from '#components/Input' + * + * You can now use: + * import { Button, Input } from '#components' + */ diff --git a/src/env.mjs b/src/env.mjs new file mode 100644 index 0000000..103497d --- /dev/null +++ b/src/env.mjs @@ -0,0 +1,46 @@ +import { createEnv } from '@t3-oss/env-nextjs' +import { z } from 'zod' + +export const env = createEnv({ + /* + * Server-side Environment variables, not available on the client. + * Will throw if you access these variables on the client. + */ + server: { + NODE_ENV: z.enum(['development', 'production']), + + ANALYZE: z.string().transform(v => v === 'true'), + OPEN_ANALYZER: z.string().transform(v => v === 'true'), + ANALYZER_MODE: z.enum(['static', 'json']), + }, + + /* + * Environment variables available on the client (and server). + */ + client: { + // + }, + + /* + * Due to how Next.js bundles environment variables on Edge and Client, + * we need to manually destructure them to make sure all are included in bundle. + */ + runtimeEnv: { + NODE_ENV: process.env.NODE_ENV, + + ANALYZE: process.env.ANALYZE, + OPEN_ANALYZER: process.env.OPEN_ANALYZER, + ANALYZER_MODE: process.env.ANALYZER_MODE, + }, + + /** + * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. + * This is especially useful for Docker builds. + */ + skipValidation: false, + /** + * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and + * `SOME_VAR=''` will throw an error. + */ + emptyStringAsUndefined: true, +}) diff --git a/src/hooks/index.ts b/src/hooks/index.ts new file mode 100644 index 0000000..f31b6c0 --- /dev/null +++ b/src/hooks/index.ts @@ -0,0 +1,16 @@ +/** + * This file exports all functions from the #hooks directory. + * It serves as a central point for importing hook functions throughout the application. + * When adding new hooks, make sure to export them here for easy access. + * + * Example usage: + * Instead of: + * import { useDidMount } from '#hooks/useDidMount' + * import { useSomeFn } from '#hooks/useSomeFn' + * + * You can now use: + * import { useDidMount, useSomeFn } from '#hooks' + */ + +export { useClientOnce } from '#hooks/useClientOnce' +export { useDidMount } from '#hooks/useDidMount' diff --git a/src/hooks/useClientOnce.ts b/src/hooks/useClientOnce.ts new file mode 100644 index 0000000..628e22f --- /dev/null +++ b/src/hooks/useClientOnce.ts @@ -0,0 +1,28 @@ +import { useEffect, useRef } from 'react' + +type UseClientOnceFn = () => void + +/** + * A custom React hook that ensures a function is called only once on the client side. + * + * @param {UseClientOnceFn} fn - The function to be executed once on the client side. + * + * @example ```ts + * useClientOnce(() => { + * console.log('This will be logged only once on the client side'); + * }); + * ``` + * @see https://github.com/Telegram-Mini-Apps/telegram-apps/blob/master/playgrounds/next/src/hooks/useClientOnce.ts + */ +export function useClientOnce(fn: UseClientOnceFn): void { + const hasRun = useRef(false) + + useEffect(() => { + if (!hasRun.current) { + hasRun.current = true + fn() + } + }, [fn]) + + return +} diff --git a/src/hooks/useDidMount.ts b/src/hooks/useDidMount.ts new file mode 100644 index 0000000..27b7f1d --- /dev/null +++ b/src/hooks/useDidMount.ts @@ -0,0 +1,24 @@ +import { useEffect, useState } from 'react' + +/** + * A custom React hook that determines if a component has mounted. + * + * @returns {boolean} true if the component has mounted, false otherwise. + * + * @example ``` + * const isMounted = useDidMount(); + * if (isMounted) { + * // Perform actions only after component has mounted + * } + * ``` + * @see https://github.com/Telegram-Mini-Apps/telegram-apps/blob/master/playgrounds/next/src/hooks/useDidMount.ts + */ +export function useDidMount(): boolean { + const [didMount, setDidMount] = useState(false) + + useEffect(() => { + setDidMount(true) + }, []) + + return didMount +} diff --git a/src/hooks/useTelegramMock.ts b/src/hooks/useTelegramMock.ts new file mode 100644 index 0000000..80f7ce2 --- /dev/null +++ b/src/hooks/useTelegramMock.ts @@ -0,0 +1,117 @@ +import { parseInitData, isTMA, mockTelegramEnv } from '@telegram-apps/sdk-react' +import { useClientOnce } from '#hooks' + +/** + * A hook for simulating the Telegram environment during development. + * This hook only works on the client side and is only active in development mode. + * + * @returns {void} + * @see https://github.com/Telegram-Mini-Apps/telegram-apps/blob/master/playgrounds/next/src/hooks/useTelegramMock.ts + */ +export function useTelegramMock(): void { + useClientOnce(() => { + if (!shouldMockEnvironment()) { + return + } + + const initDataRaw = createMockInitData() + applyMockEnvironment(initDataRaw) + + logMockWarning() + }) +} + +/** + * Determines if the environment should be mocked. + * + * @returns {boolean} Whether the environment should be mocked or not + */ +function shouldMockEnvironment(): boolean { + const MOCK_KEY = '____mocked' + + // Returns true if the current environment is Telegram Mini Apps. + // We don't mock if we are already in a mini app. + if (isTMA('simple')) { + // We could previously mock the environment. + // In case we did, we should do it again. + // The reason is the page could be reloaded, and we should apply mock again, + // because mocking also enables modifying the window object. + return !!sessionStorage.getItem(MOCK_KEY) + } + + return true +} + +/** + * Creates mocked initData. + * + * @returns {string} initData in URLSearchParams format + */ +function createMockInitData(): string { + return new URLSearchParams([ + [ + 'user', + JSON.stringify({ + id: 99281932, + first_name: 'Ryan', + last_name: 'Gosling', + username: 'gosling', + language_code: 'en', + is_premium: true, + allows_write_to_pm: true, + }), + ], + [ + 'hash', + '89d6079ad6762351f38c6dbbc41bb53048019256a9443988af7a48bcad16ba31', + ], + ['auth_date', '1716922846'], + ['start_param', 'debug'], + ['chat_type', 'sender'], + ['chat_instance', '8428209589180549439'], + ]).toString() +} + +/** + * Applies the mocked Telegram environment. + * + * @param {string} initDataRaw - initData in URLSearchParams format + */ +function applyMockEnvironment(initDataRaw: string): void { + mockTelegramEnv({ + themeParams: { + accentTextColor: '#6ab2f2', + bgColor: '#17212b', + buttonColor: '#5288c1', + buttonTextColor: '#ffffff', + destructiveTextColor: '#ec3942', + headerBgColor: '#17212b', + hintColor: '#708499', + linkColor: '#6ab3f3', + secondaryBgColor: '#232e3c', + sectionBgColor: '#17212b', + sectionHeaderTextColor: '#6ab3f3', + subtitleTextColor: '#708499', + textColor: '#f5f5f5', + }, + initData: parseInitData(initDataRaw), + initDataRaw, + version: '8', + platform: 'tdesktop', + }) + + sessionStorage.setItem('____mocked', '1') +} + +/** + * Logs a warning message about the mocking to the console. + */ +function logMockWarning(): void { + console.info( + '⚠️ As the current environment was not considered Telegram-based, it has been mocked. ' + + 'Please note that you should not do this in production, and this behavior is specific ' + + 'to the development process only. Environment mocking is applied only in development mode. ' + + 'Therefore, after building the application, you will not see this behavior or the related ' + + 'warning, which would lead to the application crashing outside of Telegram.', + ) +} diff --git a/src/init.ts b/src/init.ts new file mode 100644 index 0000000..5094440 --- /dev/null +++ b/src/init.ts @@ -0,0 +1,64 @@ +import { + $debug, + backButton, + initData, + miniApp, + themeParams, + viewport, + init as initSDK, +} from '@telegram-apps/sdk-react' + +/** + * Initializes the Telegram Mini App environment. + * @param {boolean} debug - Whether to enable debug mode. + */ +export function init(debug: boolean): void { + // Enable debug mode if specified + $debug.set(debug) + + // Initialize SDK + initSDK() + + // Mount necessary components + mountComponents() + + // Restore initial data + initData.restore() + + // Mount viewport and handle any errors + void mountViewport() + + // Bind CSS variables + bindCssVariables() +} + +/** + * Mounts necessary components for the Mini App. + */ +function mountComponents(): void { + if (backButton.isSupported()) { + backButton.mount() + } + miniApp.mount() + themeParams.mount() +} + +/** + * Mounts the viewport and logs any errors. + */ +async function mountViewport(): Promise { + try { + await viewport.mount() + } catch (error) { + console.error('Something went wrong mounting the viewport:', error) + } +} + +/** + * Binds CSS variables for various components. + */ +function bindCssVariables(): void { + viewport.bindCssVars() + miniApp.bindCssVars() + themeParams.bindCssVars() +} diff --git a/src/utils/index.ts b/src/utils/index.ts index ca6bf16..61a4c0d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1,15 @@ -export { cn } from '#utils/cn'; +/** + * This file exports all utility functions from the #utils directory. + * It serves as a central point for importing utility functions throughout the application. + * When adding new utility functions, make sure to export them here for easy access. + * + * Example usage: + * Instead of: + * import { cn } from '#utils/cn' + * import { someUtilFn } from '#utils/someUtilFn' + * + * You can now use: + * import { cn, someUtilFn } from '#utils' + */ + +export { cn } from '#utils/cn' diff --git a/tsconfig.json b/tsconfig.json index 5eac31c..6e1ec90 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,9 +18,12 @@ "paths": { "~/*": ["./*"], "@/*": ["./src/*"], + "#env": ["./src/env.mjs"], "#assets/*": ["./src/assets/*"], "#components": ["./src/components"], "#components/*": ["./src/components/*"], + "#hooks": ["./src/hooks"], + "#hooks/*": ["./src/hooks/*"], "#utils": ["./src/utils"], "#utils/*": ["./src/utils/*"] } From 56ddc3c7ea1e8700d6b3cb4bc59f4383a921642e Mon Sep 17 00:00:00 2001 From: Muhammaddiyor Tohirov Date: Fri, 25 Oct 2024 16:16:23 +0500 Subject: [PATCH 2/5] Update src/env.mjs --- src/env.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/env.mjs b/src/env.mjs index 103497d..237d18c 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -37,7 +37,7 @@ export const env = createEnv({ * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. * This is especially useful for Docker builds. */ - skipValidation: false, + skipValidation: !!process.env.SKIP_ENV_VALIDATION, /** * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and * `SOME_VAR=''` will throw an error. From 93d621eb97c24255409489884130516b78d2fa5b Mon Sep 17 00:00:00 2001 From: Muhammaddiyor Tohirov Date: Fri, 25 Oct 2024 16:28:57 +0500 Subject: [PATCH 3/5] chore: minor code cleanup without logic changes --- src/app/layout.tsx | 4 ++-- src/env.mjs | 3 ++- src/hooks/index.ts | 1 + src/init.ts | 28 ++++------------------------ 4 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index da8f3ea..e5a5bb8 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -37,10 +37,10 @@ const RootLayout: React.FC = ({ children }) => { className={cn( GeistSans.variable, GeistMono.variable, - 'top-[-25px] antialiased', + 'bg-black text-white antialiased', )} > - {children} + {children} ) } diff --git a/src/env.mjs b/src/env.mjs index 237d18c..d45c05b 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -18,7 +18,7 @@ export const env = createEnv({ * Environment variables available on the client (and server). */ client: { - // + NEXT_PUBLIC_NODE_ENV: z.enum(['development', 'production']), }, /* @@ -27,6 +27,7 @@ export const env = createEnv({ */ runtimeEnv: { NODE_ENV: process.env.NODE_ENV, + NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV, ANALYZE: process.env.ANALYZE, OPEN_ANALYZER: process.env.OPEN_ANALYZER, diff --git a/src/hooks/index.ts b/src/hooks/index.ts index f31b6c0..743e1cc 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -12,5 +12,6 @@ * import { useDidMount, useSomeFn } from '#hooks' */ +export { useTelegramMock } from '#hooks/useTelegramMock' export { useClientOnce } from '#hooks/useClientOnce' export { useDidMount } from '#hooks/useDidMount' diff --git a/src/init.ts b/src/init.ts index 5094440..f9d1f58 100644 --- a/src/init.ts +++ b/src/init.ts @@ -26,10 +26,10 @@ export function init(debug: boolean): void { initData.restore() // Mount viewport and handle any errors - void mountViewport() - - // Bind CSS variables - bindCssVariables() + void viewport.mount().catch(error => { + console.error('Something went wrong mounting the viewport:', error) + throw new Error('Something went wrong mounting the viewport!') + }) } /** @@ -42,23 +42,3 @@ function mountComponents(): void { miniApp.mount() themeParams.mount() } - -/** - * Mounts the viewport and logs any errors. - */ -async function mountViewport(): Promise { - try { - await viewport.mount() - } catch (error) { - console.error('Something went wrong mounting the viewport:', error) - } -} - -/** - * Binds CSS variables for various components. - */ -function bindCssVariables(): void { - viewport.bindCssVars() - miniApp.bindCssVars() - themeParams.bindCssVars() -} From f940c19f0d393f0dcdef7c1aacbd82d07486d722 Mon Sep 17 00:00:00 2001 From: Muhammaddiyor Tohirov Date: Fri, 25 Oct 2024 16:53:50 +0500 Subject: [PATCH 4/5] refactor/fix: update `useClientOnce` function --- src/hooks/useClientOnce.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/hooks/useClientOnce.ts b/src/hooks/useClientOnce.ts index 628e22f..b9b6a42 100644 --- a/src/hooks/useClientOnce.ts +++ b/src/hooks/useClientOnce.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react' +import { useRef } from 'react' type UseClientOnceFn = () => void @@ -17,12 +17,18 @@ type UseClientOnceFn = () => void export function useClientOnce(fn: UseClientOnceFn): void { const hasRun = useRef(false) - useEffect(() => { - if (!hasRun.current) { - hasRun.current = true - fn() - } - }, [fn]) + /** + * Confirms that the code is running in a browser environment (`typeof window !== 'undefined`), + * which prevents execution on the server side. + * + * By checking outside React lifecycle hooks (like `useEffect`), the hook minimizes unnecessary renders + * and allows `fn` to execute immediately on component initialization, but only once in the client environment. + * The `hasRun` ref ensures `fn` is only called once per component lifecycle. + */ + if (typeof window !== 'undefined' && !hasRun.current) { + hasRun.current = true + fn() + } return } From 89aeeeab0525a732729722f496836aee1a0930d5 Mon Sep 17 00:00:00 2001 From: Muhammaddiyor Tohirov Date: Fri, 25 Oct 2024 17:38:40 +0500 Subject: [PATCH 5/5] feat(draft): TelegramWebApp register components Added the initial version of the Telegram Mini App register components. Some code areas need refinement and optimization. ! This commit serves as a draft to facilitate further development and collaboration. --- src/app/tma__/layout.tsx | 26 ++++++++ src/app/tma__/page.tsx | 25 +++++++ src/env.mjs | 2 + .../components/Register/index.tsx | 0 .../TelegramWebApp/components/index.ts | 0 src/features/TelegramWebApp/index.ts | 0 src/features/TelegramWebApp/register.tsx | 66 +++++++++++++++++++ src/features/index.ts | 15 +++++ tailwind.config.ts | 7 +- tsconfig.json | 2 + 10 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/app/tma__/layout.tsx create mode 100644 src/app/tma__/page.tsx create mode 100644 src/features/TelegramWebApp/components/Register/index.tsx create mode 100644 src/features/TelegramWebApp/components/index.ts create mode 100644 src/features/TelegramWebApp/index.ts create mode 100644 src/features/TelegramWebApp/register.tsx create mode 100644 src/features/index.ts diff --git a/src/app/tma__/layout.tsx b/src/app/tma__/layout.tsx new file mode 100644 index 0000000..62fd039 --- /dev/null +++ b/src/app/tma__/layout.tsx @@ -0,0 +1,26 @@ +import type React from 'react' +import type { PropsWithChildren } from 'react' +import type { Metadata, Viewport } from 'next' + +import { Fragment } from 'react' +import { TelegramMiniAppRegister } from '~/src/features' + +export const metadata: Metadata = { + // +} + +export const viewport: Viewport = { + colorScheme: 'dark', + userScalable: false, +} + +const TelegramWebAppsLayout: React.FC = ({ children }) => { + return ( + + {children} + + ) +} +TelegramWebAppsLayout.displayName = 'Root layout for telegram web app' + +export default TelegramWebAppsLayout diff --git a/src/app/tma__/page.tsx b/src/app/tma__/page.tsx new file mode 100644 index 0000000..0116070 --- /dev/null +++ b/src/app/tma__/page.tsx @@ -0,0 +1,25 @@ +import type React from 'react' + +import { cn } from '#utils' + +const Homepage: React.FC = () => { + return ( +
+ + Goodbye World! + +
+ ) +} + +export default Homepage diff --git a/src/env.mjs b/src/env.mjs index d45c05b..cc673ea 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -45,3 +45,5 @@ export const env = createEnv({ */ emptyStringAsUndefined: true, }) + +export default env diff --git a/src/features/TelegramWebApp/components/Register/index.tsx b/src/features/TelegramWebApp/components/Register/index.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/features/TelegramWebApp/components/index.ts b/src/features/TelegramWebApp/components/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/features/TelegramWebApp/index.ts b/src/features/TelegramWebApp/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/features/TelegramWebApp/register.tsx b/src/features/TelegramWebApp/register.tsx new file mode 100644 index 0000000..0fd82e7 --- /dev/null +++ b/src/features/TelegramWebApp/register.tsx @@ -0,0 +1,66 @@ +'use client' + +import type React from 'react' +import type { PropsWithChildren } from 'react' + +import env from '#env' +import { Fragment } from 'react' +import { init } from '@/init' +import { + initDataUser, + useLaunchParams, + useSignal, +} from '@telegram-apps/sdk-react' +import { useClientOnce, useDidMount, useTelegramMock } from '#hooks' +import { cn } from '#utils' + +const TelegramMiniApp: React.FC = ({ children }) => { + const isDev = env.NEXT_PUBLIC_NODE_ENV === 'development' + + if (isDev) { + // eslint-disable-next-line react-hooks/rules-of-hooks + useTelegramMock() + } + + const lp = useLaunchParams() + const debug = isDev || lp.startParam === 'debug' + + useClientOnce(() => { + init(debug) + }) + + const client = useSignal(initDataUser) + + return ( + +
+
+          {JSON.stringify(client, null, 2)}
+        
+
+ + {children} +
+ ) +} + +const TelegramMiniAppLoader: React.FC = () => { + return ( +
+ ) +} + +export const TelegramMiniAppRegister: React.FC = props => { + const isMounted = useDidMount() + + if (!isMounted) { + return + } + + return +} diff --git a/src/features/index.ts b/src/features/index.ts new file mode 100644 index 0000000..420b263 --- /dev/null +++ b/src/features/index.ts @@ -0,0 +1,15 @@ +/** + * This file exports all features from the #features directory. + * It serves as a central point for importing features throughout the application. + * When adding new features, make sure to export them here for easy access. + * + * Example usage: + * Instead of: + * import { TelegramMiniAppRegister } from '#features/Button' + * import { SomeFeatureRegister } from '#features/Input' + * + * You can now use: + * import { TelegramMiniAppRegister, SomeFeatureRegister } from '#features' + */ + +export { TelegramMiniAppRegister } from '#features/TelegramWebApp/register' diff --git a/tailwind.config.ts b/tailwind.config.ts index bb4dcda..77da026 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -7,6 +7,7 @@ const config: Config = { content: [ 'src/app/**/*.{ts,tsx}', 'src/components/**/*.{ts,tsx}', + 'src/features/**/*.{ts,tsx}', 'src/layouts/**/*.{ts,tsx}', 'src/widgets/**/*.{ts,tsx}', ], @@ -26,10 +27,14 @@ const config: Config = { mono: ['var(--font-geist-mono)', ...fontFamily.mono], }, keyframes: { - shimmer: { + 'shimmer': { from: { transform: 'translateX(-100%)' }, to: { transform: 'translateX(100%)' }, }, + 'shimmer-bg': { + from: { backgroundPositionX: '100%' }, + to: { backgroundPositionX: '0%' }, + }, }, }, }, diff --git a/tsconfig.json b/tsconfig.json index 6e1ec90..d7449c7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,8 @@ "#assets/*": ["./src/assets/*"], "#components": ["./src/components"], "#components/*": ["./src/components/*"], + "#features": ["./src/features"], + "#features/*": ["./src/features/*"], "#hooks": ["./src/hooks"], "#hooks/*": ["./src/hooks/*"], "#utils": ["./src/utils"],