Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const config = [
{
files: ["**/*.{ts,tsx,mts,cts}"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-expressions": "off",
"react-hooks/set-state-in-effect": "off"
}
Expand Down
39 changes: 24 additions & 15 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,39 @@ const parseAllowedOrigins = (value: string | undefined): Array<string> =>
: []

const allowedDevOrigins = parseAllowedOrigins(
process.env.SPAWNDOCK_ALLOWED_DEV_ORIGINS
process.env["SPAWNDOCK_ALLOWED_DEV_ORIGINS"]
)
const previewPath = process.env.SPAWNDOCK_PREVIEW_PATH
const previewPath = process.env["SPAWNDOCK_PREVIEW_PATH"]
const serverActionOrigins = parseAllowedOrigins(
process.env.SPAWNDOCK_SERVER_ACTIONS_ALLOWED_ORIGINS
process.env["SPAWNDOCK_SERVER_ACTIONS_ALLOWED_ORIGINS"]
)
const normalizedPreviewPath =
previewPath && previewPath.length > 0 ? previewPath : undefined
const githubPagesExport = process.env.SPAWNDOCK_GITHUB_PAGES_EXPORT === "1"

const nextConfig: NextConfig = {
allowedDevOrigins,
assetPrefix: normalizedPreviewPath,
basePath: normalizedPreviewPath,
images: githubPagesExport ? { unoptimized: true } : undefined,
output: githubPagesExport ? "export" : undefined,
trailingSlash: githubPagesExport,
experimental: githubPagesExport
? undefined
: {
const githubPagesExport = process.env["SPAWNDOCK_GITHUB_PAGES_EXPORT"] === "1"
const previewConfig = normalizedPreviewPath
? {
assetPrefix: normalizedPreviewPath,
basePath: normalizedPreviewPath
}
: {}
const exportConfig = githubPagesExport
? {
images: { unoptimized: true },
output: "export" as const
}
: {
experimental: {
serverActions: {
allowedOrigins: serverActionOrigins
}
}
}

const nextConfig: NextConfig = {
allowedDevOrigins,
trailingSlash: githubPagesExport,
...previewConfig,
...exportConfig
}

export default withNextIntl(nextConfig)
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"ws": "^8.19.0"
},
"devDependencies": {
"@effect/language-service": "^0.84.1",
"@prover-coder-ai/component-tagger": "^1.0.31",
"@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26",
"@spawn-dock/dev-tunnel": "1.0.9",
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion spawndock/publish.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { execFileSync, spawnSync } from "node:child_process"
import { spawnSync } from "node:child_process"
import { cpSync, existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join, resolve } from "node:path"
Expand Down
7 changes: 3 additions & 4 deletions src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@ interface ErrorBoundaryState {
}

export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState = {};
override state: ErrorBoundaryState = {};

// eslint-disable-next-line max-len
static getDerivedStateFromError: GetDerivedStateFromError<ErrorBoundaryProps, ErrorBoundaryState> = (error) => ({ error });

componentDidCatch(error: Error) {
override componentDidCatch(error: Error) {
this.setState({ error });
}

render() {
override render() {
const {
state: {
error,
Expand Down
4 changes: 2 additions & 2 deletions src/components/LocaleSwitcher/LocaleSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import { Select } from '@telegram-apps/telegram-ui';
import { useLocale } from 'next-intl';
import { FC } from 'react';
import type { FC } from 'react';

import { localesMap } from '@/core/i18n/config';
import { setLocale } from '@/core/i18n/locale';
import { Locale } from '@/core/i18n/types';
import type { Locale } from '@/core/i18n/types';

export const LocaleSwitcher: FC = () => {
const locale = useLocale();
Expand Down
2 changes: 1 addition & 1 deletion src/components/Page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client';

import { backButton } from '@tma.js/sdk-react';
import { PropsWithChildren, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useEffect, type PropsWithChildren } from 'react';

export function Page({ children, back = true }: PropsWithChildren<{
/**
Expand Down
17 changes: 9 additions & 8 deletions src/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {
miniApp,
viewport,
mockTelegramEnv,
type ThemeParams,
type EventPayload,
themeParams,
retrieveLaunchParams,
emitEvent,
} from '@tma.js/sdk-react';

type ThemeEventParams = EventPayload<'theme_changed'>['theme_params'];

/**
* Initializes the application and configures its dependencies.
*/
Expand Down Expand Up @@ -39,16 +41,15 @@ export async function init(options: {
mockTelegramEnv({
onEvent(event, next) {
if (event.name === 'web_app_request_theme') {
let tp: Partial<ThemeParams> = {};
let tp: ThemeEventParams = {};
if (firstThemeSent) {
const state = themeParams.state;
tp = state as Partial<ThemeParams>;
tp = themeParams.state();
} else {
firstThemeSent = true;
const lp = retrieveLaunchParams();
tp = (lp.tgWebAppThemeParams || {}) as Partial<ThemeParams>;
tp = (lp.tgWebAppThemeParams || {}) as ThemeEventParams;
}
return emitEvent('theme_changed', { theme_params: tp as any });
return emitEvent('theme_changed', { theme_params: tp });
}

if (event.name === 'web_app_request_safe_area') {
Expand All @@ -72,15 +73,15 @@ export async function init(options: {
try {
miniApp.mount();
themeParams.bindCssVars();
} catch (e) {
} catch {
// miniApp not available
}

try {
viewport.mount().then(() => {
viewport.bindCssVars();
});
} catch (e) {
} catch {
// viewport not available
}
}
8 changes: 4 additions & 4 deletions src/css/bem.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { classNames, isRecord } from './classnames';

export interface BlockFn {
(...mods: any): string;
(...mods: readonly unknown[]): string;
}

export interface ElemFn {
(elem: string, ...mods: any): string;
(elem: string, ...mods: readonly unknown[]): string;
}

/**
* Applies mods to the specified element.
* @param element - element name.
* @param mod - mod to apply.
*/
function applyMods(element: string, mod: any): string {
function applyMods(element: string, mod: unknown): string {
if (Array.isArray(mod)) {
return classNames(mod.map((m) => applyMods(element, m)));
}
Expand All @@ -31,7 +31,7 @@ function applyMods(element: string, mod: any): string {
* @param element - element name.
* @param mods - mod to apply.
*/
function computeClassnames(element: string, ...mods: any): string {
function computeClassnames(element: string, ...mods: readonly unknown[]): string {
return classNames(element, applyMods(element, mods));
}

Expand Down
16 changes: 10 additions & 6 deletions src/css/classnames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function isRecord(v: unknown): v is Record<string, unknown> {
* @param values - values array.
* @returns Final class name.
*/
export function classNames(...values: any[]): string {
export function classNames(...values: readonly unknown[]): string {
return values
.map((value) => {
if (typeof value === 'string') {
Expand All @@ -29,6 +29,8 @@ export function classNames(...values: any[]): string {
if (Array.isArray(value)) {
return classNames(...value);
}

return undefined;
})
.filter(Boolean)
.join(' ');
Expand All @@ -46,11 +48,11 @@ type UnionRequiredKeys<U> = U extends U

type UnionOptionalKeys<U> = Exclude<UnionStringKeys<U>, UnionRequiredKeys<U>>;

export type MergeClassNames<Tuple extends any[]> =
export type MergeClassNames<Tuple extends readonly unknown[]> =
// Removes all types from union that will be ignored by the mergeClassNames function.
Exclude<
Tuple[number],
number | string | null | undefined | any[] | boolean
number | string | null | undefined | readonly unknown[] | boolean
> extends infer Union
? { [K in UnionRequiredKeys<Union>]: string } & {
[K in UnionOptionalKeys<Union>]?: string;
Expand All @@ -65,15 +67,17 @@ export type MergeClassNames<Tuple extends any[]> =
* @returns An object with keys from all objects with merged values.
* @see classNames
*/
export function mergeClassNames<T extends any[]>(
export function mergeClassNames<T extends readonly unknown[]>(
...partials: T
): MergeClassNames<T> {
return partials.reduce<MergeClassNames<T>>((acc, partial) => {
if (isRecord(partial)) {
const accRecord = acc as Record<string, string | undefined>;

Object.entries(partial).forEach(([key, value]) => {
const className = classNames((acc as any)[key], value);
const className = classNames(accRecord[key], value);
if (className) {
(acc as any)[key] = className;
accRecord[key] = className;
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/instrumentation-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ mockEnv().then(() => {
const { tgWebAppPlatform: platform } = launchParams;
const debug =
(launchParams.tgWebAppStartParam || '').includes('debug') ||
process.env.NODE_ENV === 'development';
process.env['NODE_ENV'] === 'development';

// Configure all application dependencies.
init({
Expand Down
10 changes: 6 additions & 4 deletions src/mockEnv.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { mockTelegramEnv, isTMA, emitEvent } from '@tma.js/sdk-react';
import { mockTelegramEnv, isTMA, emitEvent, type EventPayload } from '@tma.js/sdk-react';

type ThemeEventParams = EventPayload<'theme_changed'>['theme_params'];

// It is important, to mock the environment only for development purposes. When building the
// application, the code inside will be tree-shaken, so you will not see it in your final bundle.
export async function mockEnv(): Promise<void> {
return process.env.NODE_ENV !== 'development'
return process.env['NODE_ENV'] !== 'development'
? undefined
: isTMA('complete').then((isTma) => {
if (!isTma){
Expand All @@ -21,14 +23,14 @@ export async function mockEnv(): Promise<void> {
section_header_text_color: '#6ab3f3',
subtitle_text_color: '#708499',
text_color: '#f5f5f5',
} as const;
} as const satisfies ThemeEventParams;
const noInsets = { left: 0, top: 0, bottom: 0, right: 0 } as const;

mockTelegramEnv({
onEvent(e, next) {
// Here you can write your own handlers for all known Telegram Mini Apps methods.
if (e.name === 'web_app_request_theme') {
return emitEvent('theme_changed', { theme_params: themeParams as any });
return emitEvent('theme_changed', { theme_params: themeParams });
}
if (e.name === 'web_app_request_viewport') {
return emitEvent('viewport_changed', {
Expand Down
25 changes: 19 additions & 6 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "ES2022"],
"allowJs": true,
"alwaysStrict": true,
"allowSyntheticDefaultImports": true,
"allowUnusedLabels": false,
"allowUnreachableCode": false,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noPropertyAccessFromIndexSignature": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"verbatimModuleSyntax": true,
"plugins": [
{
"name": "next"
},
{
"name": "@effect/language-service"
}
],
"paths": {
Expand All @@ -29,7 +42,7 @@
"./public/*"
]
},
"target": "ES2017"
"target": "ES2022"
},
"include": [
"next-env.d.ts",
Expand Down
Loading