diff --git a/apps/web/app/_assets/globals.css b/apps/web/app/_assets/globals.css
new file mode 100644
index 0000000..5d83f12
--- /dev/null
+++ b/apps/web/app/_assets/globals.css
@@ -0,0 +1,3 @@
+body {
+ background: var(--tg-theme-secondary-bg-color, white);
+}
diff --git a/apps/web/app/_assets/ton.svg b/apps/web/app/_assets/ton.svg
new file mode 100644
index 0000000..1983df4
--- /dev/null
+++ b/apps/web/app/_assets/ton.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/web/app/error.tsx b/apps/web/app/error.tsx
new file mode 100644
index 0000000..ad511b4
--- /dev/null
+++ b/apps/web/app/error.tsx
@@ -0,0 +1,5 @@
+'use client';
+
+import { ErrorPage } from '@/components/ErrorPage';
+
+export default ErrorPage;
diff --git a/apps/web/app/favicon.ico b/apps/web/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/apps/web/app/favicon.ico differ
diff --git a/apps/web/app/init-data/page.tsx b/apps/web/app/init-data/page.tsx
new file mode 100644
index 0000000..c04b22e
--- /dev/null
+++ b/apps/web/app/init-data/page.tsx
@@ -0,0 +1,121 @@
+'use client';
+
+import { useMemo } from 'react';
+import { useSignal, initData, type User } from '@telegram-apps/sdk-react';
+import { List, Placeholder } from '@telegram-apps/telegram-ui';
+
+import {
+ DisplayData,
+ type DisplayDataRow,
+} from '@/components/DisplayData/DisplayData';
+import { Page } from '@/components/Page';
+
+function getUserRows(user: User): DisplayDataRow[] {
+ return [
+ { title: 'id', value: user.id.toString() },
+ { title: 'username', value: user.username },
+ { title: 'photo_url', value: user.photoUrl },
+ { title: 'last_name', value: user.lastName },
+ { title: 'first_name', value: user.firstName },
+ { title: 'is_bot', value: user.isBot },
+ { title: 'is_premium', value: user.isPremium },
+ { title: 'language_code', value: user.languageCode },
+ { title: 'allows_to_write_to_pm', value: user.allowsWriteToPm },
+ { title: 'added_to_attachment_menu', value: user.addedToAttachmentMenu },
+ ];
+}
+
+export default function InitDataPage() {
+ const initDataRaw = useSignal(initData.raw);
+ const initDataState = useSignal(initData.state);
+
+ const initDataRows = useMemo(() => {
+ if (!initDataState || !initDataRaw) {
+ return;
+ }
+ const {
+ authDate,
+ hash,
+ queryId,
+ chatType,
+ chatInstance,
+ canSendAfter,
+ startParam,
+ } = initDataState;
+ return [
+ { title: 'raw', value: initDataRaw },
+ { title: 'auth_date', value: authDate.toLocaleString() },
+ { title: 'auth_date (raw)', value: authDate.getTime() / 1000 },
+ { title: 'hash', value: hash },
+ {
+ title: 'can_send_after',
+ value: initData.canSendAfterDate()?.toISOString(),
+ },
+ { title: 'can_send_after (raw)', value: canSendAfter },
+ { title: 'query_id', value: queryId },
+ { title: 'start_param', value: startParam },
+ { title: 'chat_type', value: chatType },
+ { title: 'chat_instance', value: chatInstance },
+ ];
+ }, [initDataState, initDataRaw]);
+
+ const userRows = useMemo(() => {
+ return initDataState && initDataState.user
+ ? getUserRows(initDataState.user)
+ : undefined;
+ }, [initDataState]);
+
+ const receiverRows = useMemo(() => {
+ return initDataState && initDataState.receiver
+ ? getUserRows(initDataState.receiver)
+ : undefined;
+ }, [initDataState]);
+
+ const chatRows = useMemo(() => {
+ if (!initDataState?.chat) {
+ return;
+ }
+ const {
+ id,
+ title,
+ type,
+ username,
+ photoUrl,
+ } = initDataState.chat;
+
+ return [
+ { title: 'id', value: id.toString() },
+ { title: 'title', value: title },
+ { title: 'type', value: type },
+ { title: 'username', value: username },
+ { title: 'photo_url', value: photoUrl },
+ ];
+ }, [initData]);
+
+ if (!initDataRows) {
+ return (
+
+
+
+
+
+ );
+ }
+ return (
+
+
+
+ {userRows && }
+ {receiverRows && }
+ {chatRows && }
+
+
+ );
+};
diff --git a/apps/web/app/launch-params/page.tsx b/apps/web/app/launch-params/page.tsx
new file mode 100644
index 0000000..861a2f8
--- /dev/null
+++ b/apps/web/app/launch-params/page.tsx
@@ -0,0 +1,33 @@
+'use client';
+
+import { useLaunchParams } from '@telegram-apps/sdk-react';
+import { List } from '@telegram-apps/telegram-ui';
+
+import { DisplayData } from '@/components/DisplayData/DisplayData';
+import { Page } from '@/components/Page';
+
+export default function LaunchParamsPage() {
+ const lp = useLaunchParams();
+
+ return (
+
+
+
+
+
+ );
+};
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index e305402..e2145a6 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -1,22 +1,26 @@
-import '@repo/ui/globals.css';
+import { Root } from '@/components/Root/Root';
+import { I18nProvider } from '@/core/i18n/provider';
import { type Metadata } from 'next';
-import { Inter } from 'next/font/google';
-
-const inter = Inter({ subsets: ['latin'] });
+import { getLocale } from 'next-intl/server';
+import { type PropsWithChildren } from 'react';
+import '@telegram-apps/telegram-ui/dist/styles.css';
+import './_assets/globals.css';
export const metadata: Metadata = {
- description: 'Generated by create turbo',
- title: 'Docs',
+ description: 'Your application description goes here',
+ title: 'Your Application Title Goes Here',
};
-export default function RootLayout({
- children,
-}: {
- readonly children: React.ReactNode;
-}): JSX.Element {
+export default async function RootLayout({ children }: PropsWithChildren) {
+ const locale = await getLocale();
+
return (
-
- {children}
+
+
+
+ {children}
+
+
);
}
diff --git a/apps/web/app/page.test.tsx b/apps/web/app/page.test.tsx
deleted file mode 100644
index 756448a..0000000
--- a/apps/web/app/page.test.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import Page from './page';
-import { render, screen } from '@testing-library/react';
-import { expect, test } from 'vitest';
-
-test('Page', () => {
- render();
- expect(screen.getByRole('button', { name: 'Click me' })).toBeDefined();
-});
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index 9903988..cadfbb9 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -1,17 +1,35 @@
'use client';
-import { Button } from '@repo/ui/components/ui/button';
-import { useState } from 'react';
-export default function Page() {
- const [clicked, setClicked] = useState(false);
+import { Link } from '@/components/Link/Link';
+import { LocaleSwitcher } from '@/components/LocaleSwitcher/LocaleSwitcher';
+import { Page } from '@/components/Page';
+import { Cell, List, Section } from '@telegram-apps/telegram-ui';
+import { useTranslations } from 'next-intl';
- function handleClick() {
- setClicked(!clicked);
- }
+export default function Home() {
+ const t = useTranslations('i18n');
return (
-
-
-
+
+
+
+
+ | Init Data |
+
+
+ Launch Parameters |
+
+
+ Theme Parameters |
+
+
+
+
+
);
}
diff --git a/apps/web/app/theme-params/page.tsx b/apps/web/app/theme-params/page.tsx
new file mode 100644
index 0000000..7a68c9a
--- /dev/null
+++ b/apps/web/app/theme-params/page.tsx
@@ -0,0 +1,30 @@
+'use client';
+
+import { useSignal, themeParams } from '@telegram-apps/sdk-react';
+import { List } from '@telegram-apps/telegram-ui';
+
+import { DisplayData } from '@/components/DisplayData/DisplayData';
+import { Page } from '@/components/Page';
+
+export default function ThemeParamsPage() {
+ const tp = useSignal(themeParams.state);
+
+ return (
+
+
+ ({
+ title: title
+ .replace(/[A-Z]/g, (m) => `_${m.toLowerCase()}`)
+ .replace(/background/, 'bg'),
+ value,
+ }))
+ }
+ />
+
+
+ );
+};
diff --git a/apps/web/app/ton-connect/page.tsx b/apps/web/app/ton-connect/page.tsx
new file mode 100644
index 0000000..a2c83c1
--- /dev/null
+++ b/apps/web/app/ton-connect/page.tsx
@@ -0,0 +1,105 @@
+'use client';
+
+import { openLink } from '@telegram-apps/sdk-react';
+import { Page } from '@/components/Page';
+import { TonConnectButton, useTonWallet } from '@tonconnect/ui-react';
+import {
+ Avatar,
+ Cell,
+ List,
+ Navigation,
+ Placeholder,
+ Section,
+ Text,
+ Title,
+} from '@telegram-apps/telegram-ui';
+
+import { DisplayData } from '@/components/DisplayData/DisplayData';
+
+import './styles.css';
+
+export default function TONConnectPage() {
+ const wallet = useTonWallet();
+ if (!wallet) {
+ return (
+
+
+
+ To display the data related to the TON Connect, it is required
+ to connect your wallet
+
+
+ >
+ }
+ />
+
+ );
+ }
+
+ const {
+ account: { chain, publicKey, address },
+ device: {
+ appName,
+ appVersion,
+ maxProtocolVersion,
+ platform,
+ features,
+ },
+ } = wallet;
+
+ return (
+
+
+ {'imageUrl' in wallet && (
+ <>
+
+ |
+ }
+ after={About wallet}
+ subtitle={wallet.appName}
+ onClick={(e) => {
+ e.preventDefault();
+ openLink(wallet.aboutUrl);
+ }}
+ >
+ {wallet.name}
+ |
+
+
+ >
+ )}
+
+ typeof f === 'object' ? f.name : undefined)
+ .filter(v => v)
+ .join(', '),
+ },
+ ]}
+ />
+
+
+ );
+};
diff --git a/apps/web/app/ton-connect/styles.css b/apps/web/app/ton-connect/styles.css
new file mode 100644
index 0000000..42eac0b
--- /dev/null
+++ b/apps/web/app/ton-connect/styles.css
@@ -0,0 +1,16 @@
+.ton-connect-page__placeholder {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ box-sizing: border-box;
+}
+
+.ton-connect-page__button {
+ margin: 16px auto 0;
+}
+
+.ton-connect-page__button-connected {
+ margin: 16px 24px 16px auto;
+}
diff --git a/apps/web/components/DisplayData/DisplayData.tsx b/apps/web/components/DisplayData/DisplayData.tsx
new file mode 100644
index 0000000..882c1c0
--- /dev/null
+++ b/apps/web/components/DisplayData/DisplayData.tsx
@@ -0,0 +1,59 @@
+import { isRGB } from '@telegram-apps/sdk-react';
+import { Cell, Checkbox, Section } from '@telegram-apps/telegram-ui';
+import type { FC, ReactNode } from 'react';
+
+import { RGB } from '@/components/RGB/RGB';
+import { Link } from '@/components/Link/Link';
+
+import './styles.css';
+
+export type DisplayDataRow =
+ & { title: string }
+ & (
+ | { type: 'link'; value?: string }
+ | { value: ReactNode }
+ )
+
+export interface DisplayDataProps {
+ header?: ReactNode;
+ footer?: ReactNode;
+ rows: DisplayDataRow[];
+}
+
+export const DisplayData: FC = ({ header, rows }) => (
+
+ {rows.map((item, idx) => {
+ let valueNode: ReactNode;
+
+ if (item.value === undefined) {
+ valueNode = empty;
+ } else {
+ if ('type' in item) {
+ valueNode = Open;
+ } else if (typeof item.value === 'string') {
+ valueNode = isRGB(item.value)
+ ?
+ : item.value;
+ } else if (typeof item.value === 'boolean') {
+ valueNode = ;
+ } else {
+ valueNode = item.value;
+ }
+ }
+
+ return (
+
+
+ {valueNode}
+
+ |
+ );
+ })}
+
+);
diff --git a/apps/web/components/DisplayData/styles.css b/apps/web/components/DisplayData/styles.css
new file mode 100644
index 0000000..e30b7be
--- /dev/null
+++ b/apps/web/components/DisplayData/styles.css
@@ -0,0 +1,15 @@
+.display-data__header {
+ font-weight: 400;
+}
+
+.display-data__line {
+ padding: 16px 24px;
+}
+
+.display-data__line-title {
+ color: var(--tg-theme-subtitle-text-color);
+}
+
+.display-data__line-value {
+ word-break: break-word;
+}
diff --git a/apps/web/components/ErrorBoundary.tsx b/apps/web/components/ErrorBoundary.tsx
new file mode 100644
index 0000000..a9dfed1
--- /dev/null
+++ b/apps/web/components/ErrorBoundary.tsx
@@ -0,0 +1,39 @@
+import {
+ Component,
+ type ComponentType,
+ type GetDerivedStateFromError,
+ type PropsWithChildren,
+} from 'react';
+
+export interface ErrorBoundaryProps extends PropsWithChildren {
+ fallback: ComponentType<{ error: Error }>;
+}
+
+interface ErrorBoundaryState {
+ error?: Error;
+}
+
+export class ErrorBoundary extends Component {
+ state: ErrorBoundaryState = {};
+
+ // eslint-disable-next-line max-len
+ static getDerivedStateFromError: GetDerivedStateFromError = (error) => ({ error });
+
+ componentDidCatch(error: Error) {
+ this.setState({ error });
+ }
+
+ render() {
+ const {
+ state: {
+ error,
+ },
+ props: {
+ fallback: Fallback,
+ children,
+ },
+ } = this;
+
+ return error ? : children;
+ }
+}
diff --git a/apps/web/components/ErrorPage.tsx b/apps/web/components/ErrorPage.tsx
new file mode 100644
index 0000000..1ae3956
--- /dev/null
+++ b/apps/web/components/ErrorPage.tsx
@@ -0,0 +1,26 @@
+import { useEffect } from 'react';
+
+export function ErrorPage({
+ error,
+ reset,
+}: {
+ error: Error & { digest?: string }
+ reset?: () => void
+}) {
+ useEffect(() => {
+ // Log the error to an error reporting service
+ console.error(error);
+ }, [error]);
+
+ return (
+
+
An unhandled error occurred!
+
+
+ {error.message}
+
+
+ {reset &&
}
+
+ );
+}
\ No newline at end of file
diff --git a/apps/web/components/Link/Link.tsx b/apps/web/components/Link/Link.tsx
new file mode 100644
index 0000000..702a204
--- /dev/null
+++ b/apps/web/components/Link/Link.tsx
@@ -0,0 +1,48 @@
+import { openLink, classNames } from '@telegram-apps/sdk-react';
+import { type FC, type MouseEventHandler, type JSX, useCallback } from 'react';
+import { type LinkProps as NextLinkProps, default as NextLink } from 'next/link';
+
+import './styles.css';
+
+export interface LinkProps extends NextLinkProps, Omit {
+}
+
+export const Link: FC = ({
+ className,
+ onClick: propsOnClick,
+ href,
+ ...rest
+}) => {
+ const onClick = useCallback>((e) => {
+ propsOnClick?.(e);
+
+ // Compute if target path is external. In this case we would like to open link using
+ // TMA method.
+ let path: string;
+ if (typeof href === 'string') {
+ path = href;
+ } else {
+ const { search = '', pathname = '', hash = '' } = href;
+ path = `${pathname}?${search}#${hash}`;
+ }
+
+ const targetUrl = new URL(path, window.location.toString());
+ const currentUrl = new URL(window.location.toString());
+ const isExternal = targetUrl.protocol !== currentUrl.protocol
+ || targetUrl.host !== currentUrl.host;
+
+ if (isExternal) {
+ e.preventDefault();
+ openLink(targetUrl.toString());
+ }
+ }, [href, propsOnClick]);
+
+ return (
+
+ );
+};
diff --git a/apps/web/components/Link/styles.css b/apps/web/components/Link/styles.css
new file mode 100644
index 0000000..7251d50
--- /dev/null
+++ b/apps/web/components/Link/styles.css
@@ -0,0 +1,4 @@
+.link {
+ text-decoration: none;
+ color: var(--tg-theme-link-color);
+}
\ No newline at end of file
diff --git a/apps/web/components/LocaleSwitcher/LocaleSwitcher.tsx b/apps/web/components/LocaleSwitcher/LocaleSwitcher.tsx
new file mode 100644
index 0000000..f695d5e
--- /dev/null
+++ b/apps/web/components/LocaleSwitcher/LocaleSwitcher.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import { Select } from '@telegram-apps/telegram-ui';
+import { useLocale } from 'next-intl';
+import { FC } from 'react';
+
+import { localesMap } from '@/core/i18n/config';
+import { setLocale } from '@/core/i18n/locale';
+import { Locale } from '@/core/i18n/types';
+
+export const LocaleSwitcher: FC = () => {
+ const locale = useLocale();
+
+ const onChange = (value: string) => {
+ const locale = value as Locale;
+ setLocale(locale);
+ };
+
+ return (
+
+ );
+};
diff --git a/apps/web/components/Page.tsx b/apps/web/components/Page.tsx
new file mode 100644
index 0000000..481aa28
--- /dev/null
+++ b/apps/web/components/Page.tsx
@@ -0,0 +1,31 @@
+'use client';
+
+import { backButton } from '@telegram-apps/sdk-react';
+import { PropsWithChildren, useEffect } from 'react';
+import { useRouter } from 'next/navigation';
+
+export function Page({ children, back = true }: PropsWithChildren<{
+ /**
+ * True if it is allowed to go back from this page.
+ * @default true
+ */
+ back?: boolean
+}>) {
+ const router = useRouter();
+
+ useEffect(() => {
+ if (back) {
+ backButton.show();
+ } else {
+ backButton.hide();
+ }
+ }, [back]);
+
+ useEffect(() => {
+ return backButton.onClick(() => {
+ router.back();
+ });
+ }, [router]);
+
+ return <>{children}>;
+}
\ No newline at end of file
diff --git a/apps/web/components/RGB/RGB.tsx b/apps/web/components/RGB/RGB.tsx
new file mode 100644
index 0000000..5719bba
--- /dev/null
+++ b/apps/web/components/RGB/RGB.tsx
@@ -0,0 +1,15 @@
+import { classNames, type RGB as RGBType } from '@telegram-apps/sdk-react';
+import type { FC } from 'react';
+
+import './styles.css';
+
+export type RGBProps = JSX.IntrinsicElements['div'] & {
+ color: RGBType;
+};
+
+export const RGB: FC = ({ color, className, ...rest }) => (
+
+
+ {color}
+
+);
diff --git a/apps/web/components/RGB/styles.css b/apps/web/components/RGB/styles.css
new file mode 100644
index 0000000..fec0799
--- /dev/null
+++ b/apps/web/components/RGB/styles.css
@@ -0,0 +1,12 @@
+.rgb {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+}
+
+.rgb__icon {
+ width: 18px;
+ aspect-ratio: 1;
+ border: 1px solid #555;
+ border-radius: 50%;
+}
\ No newline at end of file
diff --git a/apps/web/components/Root/Root.tsx b/apps/web/components/Root/Root.tsx
new file mode 100644
index 0000000..5c90b9c
--- /dev/null
+++ b/apps/web/components/Root/Root.tsx
@@ -0,0 +1,63 @@
+'use client';
+
+import { ErrorBoundary } from '@/components/ErrorBoundary';
+import { ErrorPage } from '@/components/ErrorPage';
+import { setLocale } from '@/core/i18n/locale';
+import { init } from '@/core/init';
+import { useClientOnce } from '@/hooks/useClientOnce';
+import { useDidMount } from '@/hooks/useDidMount';
+import { useTelegramMock } from '@/hooks/useTelegramMock';
+import { initData, miniApp, useLaunchParams, useSignal } from '@telegram-apps/sdk-react';
+import { AppRoot } from '@telegram-apps/telegram-ui';
+import { type PropsWithChildren, useEffect } from 'react';
+import './styles.css';
+
+export function Root(props: PropsWithChildren) {
+ // Unfortunately, Telegram Mini Apps does not allow us to use all features of
+ // the Server Side Rendering. That's why we are showing loader on the server
+ // side.
+ const didMount = useDidMount();
+
+ return didMount ? (
+
+
+
+ ) : (
+ Loading
+ );
+}
+
+function RootInner({ children }: PropsWithChildren) {
+ const isDevelopment = process.env.NODE_ENV === 'development';
+
+ // Mock Telegram environment in development mode if needed.
+ if (isDevelopment) {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ useTelegramMock();
+ }
+
+ const lp = useLaunchParams();
+ const debug = isDevelopment || lp.startParam === 'debug';
+
+ // Initialize the library.
+ useClientOnce(() => {
+ init(debug);
+ });
+
+ const isDark = useSignal(miniApp.isDark);
+ const initDataUser = useSignal(initData.user);
+
+ // Set the user locale.
+ useEffect(() => {
+ initDataUser && setLocale(initDataUser.languageCode);
+ }, [initDataUser]);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/web/components/Root/styles.css b/apps/web/components/Root/styles.css
new file mode 100644
index 0000000..40c6bbc
--- /dev/null
+++ b/apps/web/components/Root/styles.css
@@ -0,0 +1,10 @@
+.root__loading {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
\ No newline at end of file
diff --git a/apps/web/core/i18n/config.ts b/apps/web/core/i18n/config.ts
new file mode 100644
index 0000000..915d153
--- /dev/null
+++ b/apps/web/core/i18n/config.ts
@@ -0,0 +1,10 @@
+export const defaultLocale = 'en';
+
+export const timeZone = 'Europe/Amsterdam';
+
+export const locales = [defaultLocale, 'ru'] as const;
+
+export const localesMap = [
+ { key: 'en', title: 'English' },
+ { key: 'ru', title: 'Русский' },
+];
diff --git a/apps/web/core/i18n/i18n.ts b/apps/web/core/i18n/i18n.ts
new file mode 100644
index 0000000..c0ead13
--- /dev/null
+++ b/apps/web/core/i18n/i18n.ts
@@ -0,0 +1,18 @@
+import { defaultLocale, locales } from './config';
+import { getLocale } from './locale';
+import { type Locale } from './types';
+import { getRequestConfig } from 'next-intl/server';
+
+const i18nRequestConfig = getRequestConfig(async () => {
+ const locale = (await getLocale()) as Locale;
+
+ return {
+ locale,
+ messages:
+ locale === defaultLocale || !locales.includes(locale)
+ ? (await import(`@/public/locales/${defaultLocale}.json`)).default
+ : (await import(`@/public/locales/${locale}.json`)).default,
+ };
+});
+
+export default i18nRequestConfig;
diff --git a/apps/web/core/i18n/locale.ts b/apps/web/core/i18n/locale.ts
new file mode 100644
index 0000000..83246a4
--- /dev/null
+++ b/apps/web/core/i18n/locale.ts
@@ -0,0 +1,21 @@
+//use server is required
+"use server";
+
+import { cookies } from "next/headers";
+
+import { defaultLocale } from "./config";
+import type { Locale } from "./types";
+
+// In this example the locale is read from a cookie. You could alternatively
+// also read it from a database, backend service, or any other source.
+const COOKIE_NAME = "NEXT_LOCALE";
+
+const getLocale = async () => {
+ return cookies().get(COOKIE_NAME)?.value || defaultLocale;
+};
+
+const setLocale = async (locale?: string) => {
+ cookies().set(COOKIE_NAME, locale as Locale || defaultLocale);
+};
+
+export { getLocale, setLocale };
diff --git a/apps/web/core/i18n/provider.tsx b/apps/web/core/i18n/provider.tsx
new file mode 100644
index 0000000..7570aa7
--- /dev/null
+++ b/apps/web/core/i18n/provider.tsx
@@ -0,0 +1,18 @@
+import { NextIntlClientProvider } from "next-intl";
+import { getMessages } from "next-intl/server";
+import React from "react";
+
+import { timeZone } from "./config";
+
+const I18nProvider: React.FC = async ({
+ children,
+}) => {
+ const messages = await getMessages();
+ return (
+
+ {children}
+
+ );
+};
+
+export { I18nProvider };
diff --git a/apps/web/core/i18n/types.ts b/apps/web/core/i18n/types.ts
new file mode 100644
index 0000000..bcd244f
--- /dev/null
+++ b/apps/web/core/i18n/types.ts
@@ -0,0 +1,5 @@
+import type { locales } from "./config";
+
+type Locale = (typeof locales)[number];
+
+export type { Locale };
diff --git a/apps/web/core/init.ts b/apps/web/core/init.ts
new file mode 100644
index 0000000..e24aa84
--- /dev/null
+++ b/apps/web/core/init.ts
@@ -0,0 +1,41 @@
+import {
+ $debug,
+ backButton,
+ initData,
+ init as initSDK,
+ miniApp,
+ themeParams,
+ viewport,
+} from '@telegram-apps/sdk-react';
+
+/**
+ * Initializes the application and configures its dependencies.
+ */
+export function init(debug: boolean): void {
+ // Set @telegram-apps/sdk-react debug mode.
+ $debug.set(debug);
+
+ // Initialize special event handlers for Telegram Desktop, Android, iOS, etc.
+ // Also, configure the package.
+ initSDK();
+
+ // Mount all components used in the project.
+ if (backButton.isSupported()) backButton.mount();
+ miniApp.mount();
+ themeParams.mount();
+ initData.restore();
+ void viewport
+ .mount()
+ .then(() => {
+ // Define components-related CSS variables.
+ viewport.bindCssVars();
+ miniApp.bindCssVars();
+ themeParams.bindCssVars();
+ })
+ .catch((error) => {
+ console.error('Something went wrong mounting the viewport', error);
+ });
+
+ // Add Eruda if needed.
+ debug && import('eruda').then((library) => library.default.init()).catch(console.error);
+}
diff --git a/apps/web/hooks/useClientOnce.ts b/apps/web/hooks/useClientOnce.ts
new file mode 100644
index 0000000..7963599
--- /dev/null
+++ b/apps/web/hooks/useClientOnce.ts
@@ -0,0 +1,9 @@
+import { useRef } from 'react';
+
+export function useClientOnce(fn: () => void): void {
+ const canCall = useRef(true);
+ if (typeof window !== 'undefined' && canCall.current) {
+ canCall.current = false;
+ fn();
+ }
+}
\ No newline at end of file
diff --git a/apps/web/hooks/useDidMount.ts b/apps/web/hooks/useDidMount.ts
new file mode 100644
index 0000000..e77f5ae
--- /dev/null
+++ b/apps/web/hooks/useDidMount.ts
@@ -0,0 +1,14 @@
+import { useEffect, useState } from 'react';
+
+/**
+ * @return True, if component was mounted.
+ */
+export function useDidMount(): boolean {
+ const [didMount, setDidMount] = useState(false);
+
+ useEffect(() => {
+ setDidMount(true);
+ }, []);
+
+ return didMount;
+}
\ No newline at end of file
diff --git a/apps/web/hooks/useTelegramMock.ts b/apps/web/hooks/useTelegramMock.ts
new file mode 100644
index 0000000..c0ca8f1
--- /dev/null
+++ b/apps/web/hooks/useTelegramMock.ts
@@ -0,0 +1,75 @@
+import { useClientOnce } from '@/hooks/useClientOnce';
+import {
+ isTMA,
+ type LaunchParams,
+ mockTelegramEnv,
+ parseInitData,
+ retrieveLaunchParams,
+} from '@telegram-apps/sdk-react';
+
+/**
+ * Mocks Telegram environment in development mode.
+ */
+export function useTelegramMock(): void {
+ useClientOnce(() => {
+ if (!sessionStorage.getItem('env-mocked') && isTMA('simple')) {
+ return;
+ }
+
+ // Determine which launch params should be applied. We could already
+ // apply them previously, or they may be specified on purpose using the
+ // default launch parameters transmission method.
+ let lp: LaunchParams | undefined;
+ try {
+ lp = retrieveLaunchParams();
+ } catch {
+ const initDataRaw = new URLSearchParams([
+ [
+ 'user',
+ JSON.stringify({
+ allows_write_to_pm: true,
+ first_name: 'Andrew',
+ id: 99_281_932,
+ is_premium: true,
+ language_code: 'en',
+ last_name: 'Rogue',
+ username: 'rogue',
+ }),
+ ],
+ ['hash', '89d6079ad6762351f38c6dbbc41bb53048019256a9443988af7a48bcad16ba31'],
+ ['auth_date', '1716922846'],
+ ['start_param', 'debug'],
+ ['chat_type', 'sender'],
+ ['chat_instance', '8428209589180549439'],
+ ]).toString();
+
+ lp = {
+ initData: parseInitData(initDataRaw),
+ initDataRaw,
+ platform: 'tdesktop',
+ 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',
+ },
+ version: '8',
+ };
+ }
+
+ sessionStorage.setItem('env-mocked', '1');
+ mockTelegramEnv(lp);
+ console.warn(
+ '⚠️ 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.',
+ );
+ });
+}
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 0f656ae..f183c05 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -1,7 +1,10 @@
-/** @type {import('next').NextConfig} */
-const nextConfig = {
+import createNextIntlPlugin from 'next-intl/plugin';
+
+const withNextIntl = createNextIntlPlugin('./core/i18n/i18n.ts');
+
+const nextConfig = withNextIntl({
reactStrictMode: true,
transpilePackages: ['@repo/ui'],
-};
+});
export default nextConfig;
diff --git a/apps/web/package.json b/apps/web/package.json
index 9e81b07..b60222d 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -17,8 +17,11 @@
"dependencies": {
"@apollo/client": "catalog:",
"@repo/ui": "workspace:*",
+ "@telegram-apps/sdk-react": "^2.0.19",
+ "@telegram-apps/telegram-ui": "^2.1.8",
"graphql": "catalog:",
"next": "catalog:",
+ "next-intl": "^3.26.0",
"react": "catalog:",
"react-dom": "catalog:"
},
@@ -38,6 +41,7 @@
"@types/react-dom": "catalog:",
"@vitejs/plugin-react": "catalog:",
"autoprefixer": "catalog:",
+ "eruda": "^3.4.1",
"jsdom": "catalog:",
"lint-staged": "catalog:",
"postcss": "catalog:",
diff --git a/apps/web/public/locales/en.json b/apps/web/public/locales/en.json
new file mode 100644
index 0000000..1d633ec
--- /dev/null
+++ b/apps/web/public/locales/en.json
@@ -0,0 +1,6 @@
+{
+ "i18n": {
+ "header": "Application supports i18n",
+ "footer": "You can select a different language from the dropdown menu."
+ }
+}
\ No newline at end of file
diff --git a/apps/web/public/locales/ru.json b/apps/web/public/locales/ru.json
new file mode 100644
index 0000000..fbaf2c3
--- /dev/null
+++ b/apps/web/public/locales/ru.json
@@ -0,0 +1,6 @@
+{
+ "i18n": {
+ "header": "Поддержка i18n",
+ "footer": "Вы можете выбрать другой язык в выпадающем меню."
+ }
+}
\ No newline at end of file
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index 24e7548..4f8e45b 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -5,7 +5,11 @@
{
"name": "next"
}
- ]
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./*"]
+ }
},
"include": [
"next-env.d.ts",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 881c079..241d7d0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -143,12 +143,21 @@ importers:
'@repo/ui':
specifier: workspace:*
version: link:../../packages/ui
+ '@telegram-apps/sdk-react':
+ specifier: ^2.0.19
+ version: 2.0.19(@types/react@19.0.1)(react@19.0.0)
+ '@telegram-apps/telegram-ui':
+ specifier: ^2.1.8
+ version: 2.1.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
graphql:
specifier: 'catalog:'
version: 16.9.0
next:
specifier: 'catalog:'
version: 15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ next-intl:
+ specifier: ^3.26.0
+ version: 3.26.0(next@15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
react:
specifier: 'catalog:'
version: 19.0.0
@@ -201,6 +210,9 @@ importers:
autoprefixer:
specifier: 'catalog:'
version: 10.4.20(postcss@8.4.49)
+ eruda:
+ specifier: ^3.4.1
+ version: 3.4.1
jsdom:
specifier: 'catalog:'
version: 25.0.1
@@ -1123,6 +1135,36 @@ packages:
resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@floating-ui/core@1.6.8':
+ resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==}
+
+ '@floating-ui/dom@1.6.12':
+ resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==}
+
+ '@floating-ui/react-dom@2.1.2':
+ resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@floating-ui/utils@0.2.8':
+ resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
+
+ '@formatjs/ecma402-abstract@2.3.1':
+ resolution: {integrity: sha512-Ip9uV+/MpLXWRk03U/GzeJMuPeOXpJBSB5V1tjA6kJhvqssye5J5LoYLc7Z5IAHb7nR62sRoguzrFiVCP/hnzw==}
+
+ '@formatjs/fast-memoize@2.2.5':
+ resolution: {integrity: sha512-6PoewUMrrcqxSoBXAOJDiW1m+AmkrAj0RiXnOMD59GRaswjXhm3MDhgepXPBgonc09oSirAJTsAggzAGQf6A6g==}
+
+ '@formatjs/icu-messageformat-parser@2.9.7':
+ resolution: {integrity: sha512-cuEHyRM5VqLQobANOjtjlgU7+qmk9Q3fDQuBiRRJ3+Wp3ZoZhpUPtUfuimZXsir6SaI2TaAJ+SLo9vLnV5QcbA==}
+
+ '@formatjs/icu-skeleton-parser@1.8.11':
+ resolution: {integrity: sha512-8LlHHE/yL/zVJZHAX3pbKaCjZKmBIO6aJY1mkVh4RMSEu/2WRZ4Ysvv3kKXJ9M8RJLBHdnk1/dUQFdod1Dt7Dw==}
+
+ '@formatjs/intl-localematcher@0.5.9':
+ resolution: {integrity: sha512-8zkGu/sv5euxbjfZ/xmklqLyDGQSxsLqg8XOq88JW3cmJtzhCP8EtSJXlaKZnVO4beEaoiT9wj4eIoCQ9smwxA==}
+
'@graphql-codegen/add@5.0.3':
resolution: {integrity: sha512-SxXPmramkth8XtBlAHu4H4jYcYXM/o3p01+psU+0NADQowA8jtYkK6MW5rV6T+CxkEaNZItfSmZRPgIuypcqnA==}
peerDependencies:
@@ -1681,6 +1723,9 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ '@radix-ui/primitive@1.1.0':
+ resolution: {integrity: sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==}
+
'@radix-ui/react-compose-refs@1.1.0':
resolution: {integrity: sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==}
peerDependencies:
@@ -1690,6 +1735,111 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-context@1.1.1':
+ resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-dialog@1.1.2':
+ resolution: {integrity: sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-dismissable-layer@1.1.1':
+ resolution: {integrity: sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-focus-guards@1.1.1':
+ resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-focus-scope@1.1.0':
+ resolution: {integrity: sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-id@1.1.0':
+ resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-portal@1.1.2':
+ resolution: {integrity: sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-presence@1.1.1':
+ resolution: {integrity: sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@radix-ui/react-primitive@2.0.0':
+ resolution: {integrity: sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==}
+ peerDependencies:
+ '@types/react': '*'
+ '@types/react-dom': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
'@radix-ui/react-slot@1.1.0':
resolution: {integrity: sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==}
peerDependencies:
@@ -1699,6 +1849,42 @@ packages:
'@types/react':
optional: true
+ '@radix-ui/react-use-callback-ref@1.1.0':
+ resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-controllable-state@1.1.0':
+ resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-escape-keydown@1.1.0':
+ resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@radix-ui/react-use-layout-effect@1.1.0':
+ resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
'@repeaterjs/repeater@3.0.4':
resolution: {integrity: sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==}
@@ -1812,6 +1998,43 @@ packages:
'@swc/helpers@0.5.13':
resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==}
+ '@telegram-apps/bridge@1.7.1':
+ resolution: {integrity: sha512-oRbznpIC4UibMVygQ+tcS0ZSKx7DaI07MXQF42VETQ/VOCKeaWZeQFUifo4A+CzT6XMGo2hyse/CQP9ziX0H7g==}
+
+ '@telegram-apps/navigation@1.0.9':
+ resolution: {integrity: sha512-Ur24luu+fizrKCDQAoJWQzMj+IwNiqtQlrITz42DORKSohj5yvf9kD5AJO2r9sHC+iC2pLXHCn1dV34o6tbeaQ==}
+
+ '@telegram-apps/sdk-react@2.0.19':
+ resolution: {integrity: sha512-mQfBspQd+KbNW9gp26b2QN+T+SFiYfjZV1X91mM68hIPnChPjwCJRTZrNtx+nvT49B/NURMMtTT/eF84IX1ltA==}
+ peerDependencies:
+ '@types/react': ^17.0.0 || ^18.0.0
+ react: ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ '@telegram-apps/sdk@2.9.1':
+ resolution: {integrity: sha512-fW5e0B7yt4jIlEJfXzZwrY2cYufyXQw6xIu2eRfKl1l6N/ETIwZZRr6jOOsRCzzpWUfqePvwItqKIuRf8mGcog==}
+
+ '@telegram-apps/signals@1.1.0':
+ resolution: {integrity: sha512-5qN7cU8t3l7n0cKcnzc/1TYKJTwAggUinfwbLHL1SYmB47pBHjCvfsRiYliFohk6lb635SBmNuVZL6LHFmGZaw==}
+
+ '@telegram-apps/telegram-ui@2.1.8':
+ resolution: {integrity: sha512-Y4mIRzBJDerFvF6yzsfwHpu2tKEMHnnnQgdnLTwIdXNVPq/sJTaY66Qxbh9eeO5+tct01VcAx4E70/1fMvnp8A==}
+ engines: {node: '>=18.0.0', npm: '>=7.0.0'}
+ peerDependencies:
+ react: ^18.2.0
+ react-dom: ^18.2.0
+
+ '@telegram-apps/toolkit@1.0.0':
+ resolution: {integrity: sha512-fSVoveLuMzwRKWeXEufMSXxH+HvjsFKb1DeT3pG5qLpnb2rdtejnNcwAt6WEPtiZ3a4YntYaFuR3KYgVv0ZxeQ==}
+
+ '@telegram-apps/transformers@1.2.0':
+ resolution: {integrity: sha512-RufLdD044RPaAJdh+Mp/98JI+Wkp5mhX3WYCg6IZYFMRwu3QTu2FBwYmU9FdRmBF9utbcymSFrY1cqxh+Vtkfg==}
+
+ '@telegram-apps/types@1.2.0':
+ resolution: {integrity: sha512-HhvnSCsqlJpes5ZGsZP/qbDNq8eLLnjgZKaF5NRsDqAKUPvaIIFT1HdyDII/8EioUgoI4FHsP8MylK2Gzm2efg==}
+
'@testing-library/dom@10.4.0':
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
engines: {node: '>=18'}
@@ -1831,6 +2054,9 @@ packages:
'@types/react-dom':
optional: true
+ '@twa-dev/types@7.10.0':
+ resolution: {integrity: sha512-BXHyNLXy+cPbOZ2qQq5ArPKcP4kUfuX3YBSFT0XUxtMNWAzvyMuYAiq59UTfQ8OnELXlfChrAoIT3c2pujBtcQ==}
+
'@types/aria-query@5.0.4':
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
@@ -2079,6 +2305,12 @@ packages:
resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==}
engines: {node: '>=8'}
+ '@xelene/vaul-with-scroll-fix@0.1.4':
+ resolution: {integrity: sha512-R9J7y92rzZKIQLtFHKtzRsb4x8G26cvwGIDSf7OQZCdROFt7xic4Yz1BlFYjm8oiQsYV4SFSQfOzatZjvxz5XQ==}
+ peerDependencies:
+ react: ^16.8 || ^17.0 || ^18.0
+ react-dom: ^16.8 || ^17.0 || ^18.0
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -2145,6 +2377,10 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ aria-hidden@1.2.4:
+ resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==}
+ engines: {node: '>=10'}
+
aria-query@5.3.0:
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
@@ -2618,6 +2854,9 @@ packages:
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
engines: {node: '>=8'}
+ detect-node-es@1.1.0:
+ resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
+
didyoumean@1.2.2:
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
@@ -2684,6 +2923,9 @@ packages:
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+ eruda@3.4.1:
+ resolution: {integrity: sha512-RmaO5yD97URY/9Q0lye3cmmNPoXNKreeePIw7c/zllbscR92CjGFZFuQ70+0fLIvLcKW3Xha8DS8NFhmeNbEBQ==}
+
es-abstract@1.23.5:
resolution: {integrity: sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==}
engines: {node: '>= 0.4'}
@@ -3216,6 +3458,10 @@ packages:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
+ get-nonce@1.0.1:
+ resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
+ engines: {node: '>=6'}
+
get-set-props@0.1.0:
resolution: {integrity: sha512-7oKuKzAGKj0ag+eWZwcGw2fjiZ78tXnXQoBgY0aU7ZOxTu4bB7hSuQSDgtKy978EDH062P5FmD2EWiDpQS9K9Q==}
engines: {node: '>=0.10.0'}
@@ -3447,6 +3693,9 @@ packages:
resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
engines: {node: '>= 0.4'}
+ intl-messageformat@10.7.10:
+ resolution: {integrity: sha512-hp7iejCBiJdW3zmOe18FdlJu8U/JsADSDiBPQhfdSeI8B9POtvPRvPh3nMlvhYayGMKLv6maldhR7y3Pf1vkpw==}
+
invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
@@ -3935,6 +4184,16 @@ packages:
resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==}
engines: {node: '>=18'}
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
+ next-intl@3.26.0:
+ resolution: {integrity: sha512-gkamnHIANQzeW8xpTGRxd0xiOCztQhY8GDp79fgdlw0GioqrjTEfSWLhHkgaAtvHRbuh/ByJdwiEY5eNK9bUSQ==}
+ peerDependencies:
+ next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+
next@15.0.4:
resolution: {integrity: sha512-nuy8FH6M1FG0lktGotamQDCXhh5hZ19Vo0ht1AOIQWrYJLP598TIUagKtvJrfJ5AGwB/WmDqkKaKhMpVifvGPA==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
@@ -4338,6 +4597,36 @@ packages:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
+ react-remove-scroll-bar@2.3.6:
+ resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-remove-scroll@2.6.0:
+ resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ react-style-singleton@2.2.1:
+ resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
react@19.0.0:
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'}
@@ -5042,6 +5331,31 @@ packages:
urlpattern-polyfill@8.0.2:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
+ use-callback-ref@1.3.2:
+ resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
+ use-intl@3.26.0:
+ resolution: {integrity: sha512-HGXmpjGlbEv1uFZPfm557LK8p/hv0pKF9UwnrJeHUTxQx6bUGzMgpmPRLCVY3zkr7hfjy4LPwQJfk4Fhnn+dIg==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
+
+ use-sidecar@1.1.2:
+ resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -6262,6 +6576,49 @@ snapshots:
dependencies:
levn: 0.4.1
+ '@floating-ui/core@1.6.8':
+ dependencies:
+ '@floating-ui/utils': 0.2.8
+
+ '@floating-ui/dom@1.6.12':
+ dependencies:
+ '@floating-ui/core': 1.6.8
+ '@floating-ui/utils': 0.2.8
+
+ '@floating-ui/react-dom@2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@floating-ui/dom': 1.6.12
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
+ '@floating-ui/utils@0.2.8': {}
+
+ '@formatjs/ecma402-abstract@2.3.1':
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.5
+ '@formatjs/intl-localematcher': 0.5.9
+ decimal.js: 10.4.3
+ tslib: 2.8.1
+
+ '@formatjs/fast-memoize@2.2.5':
+ dependencies:
+ tslib: 2.8.1
+
+ '@formatjs/icu-messageformat-parser@2.9.7':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.1
+ '@formatjs/icu-skeleton-parser': 1.8.11
+ tslib: 2.8.1
+
+ '@formatjs/icu-skeleton-parser@1.8.11':
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.1
+ tslib: 2.8.1
+
+ '@formatjs/intl-localematcher@0.5.9':
+ dependencies:
+ tslib: 2.8.1
+
'@graphql-codegen/add@5.0.3(graphql@16.9.0)':
dependencies:
'@graphql-codegen/plugin-helpers': 5.1.0(graphql@16.9.0)
@@ -7082,12 +7439,108 @@ snapshots:
dependencies:
playwright: 1.49.1
+ '@radix-ui/primitive@1.1.0': {}
+
'@radix-ui/react-compose-refs@1.1.0(@types/react@19.0.1)(react@19.0.0)':
dependencies:
react: 19.0.0
optionalDependencies:
'@types/react': 19.0.1
+ '@radix-ui/react-context@1.1.1(@types/react@19.0.1)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ '@radix-ui/react-dialog@1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.0
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-context': 1.1.1(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-dismissable-layer': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-focus-scope': 1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-id': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-portal': 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-presence': 1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-slot': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ aria-hidden: 1.2.4
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ react-remove-scroll: 2.6.0(@types/react@19.0.1)(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.1
+ '@types/react-dom': 19.0.1
+
+ '@radix-ui/react-dismissable-layer@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/primitive': 1.1.0
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.1
+ '@types/react-dom': 19.0.1
+
+ '@radix-ui/react-focus-guards@1.1.1(@types/react@19.0.1)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ '@radix-ui/react-focus-scope@1.1.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.1
+ '@types/react-dom': 19.0.1
+
+ '@radix-ui/react-id@1.1.0(@types/react@19.0.1)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ '@radix-ui/react-portal@1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.1
+ '@types/react-dom': 19.0.1
+
+ '@radix-ui/react-presence@1.1.1(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.1
+ '@types/react-dom': 19.0.1
+
+ '@radix-ui/react-primitive@2.0.0(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-slot': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.1
+ '@types/react-dom': 19.0.1
+
'@radix-ui/react-slot@1.1.0(@types/react@19.0.1)(react@19.0.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.0(@types/react@19.0.1)(react@19.0.0)
@@ -7095,6 +7548,32 @@ snapshots:
optionalDependencies:
'@types/react': 19.0.1
+ '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.0.1)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.0.1)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.0.1)(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.0.1)(react@19.0.0)
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.0.1)(react@19.0.0)':
+ dependencies:
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.1
+
'@repeaterjs/repeater@3.0.4': {}
'@repeaterjs/repeater@3.0.6': {}
@@ -7174,6 +7653,57 @@ snapshots:
dependencies:
tslib: 2.8.1
+ '@telegram-apps/bridge@1.7.1':
+ dependencies:
+ '@telegram-apps/signals': 1.1.0
+ '@telegram-apps/toolkit': 1.0.0
+ '@telegram-apps/transformers': 1.2.0
+ '@telegram-apps/types': 1.2.0
+
+ '@telegram-apps/navigation@1.0.9':
+ dependencies:
+ '@telegram-apps/bridge': 1.7.1
+ '@telegram-apps/signals': 1.1.0
+ '@telegram-apps/toolkit': 1.0.0
+
+ '@telegram-apps/sdk-react@2.0.19(@types/react@19.0.1)(react@19.0.0)':
+ dependencies:
+ '@telegram-apps/sdk': 2.9.1
+ react: 19.0.0
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ '@telegram-apps/sdk@2.9.1':
+ dependencies:
+ '@telegram-apps/bridge': 1.7.1
+ '@telegram-apps/navigation': 1.0.9
+ '@telegram-apps/signals': 1.1.0
+ '@telegram-apps/toolkit': 1.0.0
+ '@telegram-apps/transformers': 1.2.0
+
+ '@telegram-apps/signals@1.1.0': {}
+
+ '@telegram-apps/telegram-ui@2.1.8(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ '@swc/helpers': 0.5.13
+ '@twa-dev/types': 7.10.0
+ '@xelene/vaul-with-scroll-fix': 0.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+
+ '@telegram-apps/toolkit@1.0.0': {}
+
+ '@telegram-apps/transformers@1.2.0':
+ dependencies:
+ '@telegram-apps/toolkit': 1.0.0
+ '@telegram-apps/types': 1.2.0
+
+ '@telegram-apps/types@1.2.0': {}
+
'@testing-library/dom@10.4.0':
dependencies:
'@babel/code-frame': 7.26.2
@@ -7195,6 +7725,8 @@ snapshots:
'@types/react': 19.0.1
'@types/react-dom': 19.0.1
+ '@twa-dev/types@7.10.0': {}
+
'@types/aria-query@5.0.4': {}
'@types/babel__core@7.20.5':
@@ -7541,6 +8073,15 @@ snapshots:
dependencies:
tslib: 2.8.1
+ '@xelene/vaul-with-scroll-fix@0.1.4(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@radix-ui/react-dialog': 1.1.2(@types/react-dom@19.0.1)(@types/react@19.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+
acorn-jsx@5.3.2(acorn@8.14.0):
dependencies:
acorn: 8.14.0
@@ -7594,6 +8135,10 @@ snapshots:
argparse@2.0.1: {}
+ aria-hidden@1.2.4:
+ dependencies:
+ tslib: 2.8.1
+
aria-query@5.3.0:
dependencies:
dequal: 2.0.3
@@ -8122,6 +8667,8 @@ snapshots:
detect-libc@2.0.3:
optional: true
+ detect-node-es@1.1.0: {}
+
didyoumean@1.2.2: {}
dir-glob@3.0.1:
@@ -8176,6 +8723,8 @@ snapshots:
dependencies:
is-arrayish: 0.2.1
+ eruda@3.4.1: {}
+
es-abstract@1.23.5:
dependencies:
array-buffer-byte-length: 1.0.1
@@ -8993,6 +9542,8 @@ snapshots:
has-symbols: 1.0.3
hasown: 2.0.2
+ get-nonce@1.0.1: {}
+
get-set-props@0.1.0: {}
get-stream@8.0.1: {}
@@ -9246,6 +9797,13 @@ snapshots:
hasown: 2.0.2
side-channel: 1.0.6
+ intl-messageformat@10.7.10:
+ dependencies:
+ '@formatjs/ecma402-abstract': 2.3.1
+ '@formatjs/fast-memoize': 2.2.5
+ '@formatjs/icu-messageformat-parser': 2.9.7
+ tslib: 2.8.1
+
invariant@2.2.4:
dependencies:
loose-envify: 1.4.0
@@ -9717,6 +10275,16 @@ snapshots:
natural-orderby@5.0.0: {}
+ negotiator@1.0.0: {}
+
+ next-intl@3.26.0(next@15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0):
+ dependencies:
+ '@formatjs/intl-localematcher': 0.5.9
+ negotiator: 1.0.0
+ next: 15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ use-intl: 3.26.0(react@19.0.0)
+
next@15.0.4(@babel/core@7.26.0)(@playwright/test@1.49.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
dependencies:
'@next/env': 15.0.4
@@ -10085,6 +10653,34 @@ snapshots:
react-refresh@0.14.2: {}
+ react-remove-scroll-bar@2.3.6(@types/react@19.0.1)(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ react-style-singleton: 2.2.1(@types/react@19.0.1)(react@19.0.0)
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ react-remove-scroll@2.6.0(@types/react@19.0.1)(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ react-remove-scroll-bar: 2.3.6(@types/react@19.0.1)(react@19.0.0)
+ react-style-singleton: 2.2.1(@types/react@19.0.1)(react@19.0.0)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.2(@types/react@19.0.1)(react@19.0.0)
+ use-sidecar: 1.1.2(@types/react@19.0.1)(react@19.0.0)
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ react-style-singleton@2.2.1(@types/react@19.0.1)(react@19.0.0):
+ dependencies:
+ get-nonce: 1.0.1
+ invariant: 2.2.4
+ react: 19.0.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.0.1
+
react@19.0.0: {}
read-cache@1.0.0:
@@ -10846,6 +11442,27 @@ snapshots:
urlpattern-polyfill@8.0.2: {}
+ use-callback-ref@1.3.2(@types/react@19.0.1)(react@19.0.0):
+ dependencies:
+ react: 19.0.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.0.1
+
+ use-intl@3.26.0(react@19.0.0):
+ dependencies:
+ '@formatjs/fast-memoize': 2.2.5
+ intl-messageformat: 10.7.10
+ react: 19.0.0
+
+ use-sidecar@1.1.2(@types/react@19.0.1)(react@19.0.0):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.0.0
+ tslib: 2.8.1
+ optionalDependencies:
+ '@types/react': 19.0.1
+
util-deprecate@1.0.2: {}
validate-npm-package-license@3.0.4: