diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..6669542f6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,399 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +# Instructions + +You are an expert in TypeScript, JavaScript, HTML, SCSS and Teact with deep experience in our project's simplified React-like API. You are working on a modern web app for Telegram. + +- **Be concise.** Only change code directly related to the current task; leave unrelated parts untouched. +- **Reuse** existing types, functions and components. Search before creating a new one. +- **No new libraries.** Use existing dependencies only. If a task truly can't be done without a new library, stop and explain why. +- **Do not** write tests. + +- **SCSS modules:** + - Name classes in camelCase. + - Import as `styles` in your component: + ```scss + /* Component.module.scss */ + .myWrapper { /*…*/ } + ``` + ```tsx + /* Component.tsx */ + import styles from "./Component.module.scss"; +
+ ``` + - Use [buildClassName.ts](mdc:src/util/buildClassName.ts) to merge multiple class names. + - **Always extract styles to files** - avoid inline styles unless absolutely necessary. + - **If file already imports styles**, check where they come from and add new styles there - don't create new style files. + - Use rem units for all measurements. + +- **Code Style:** + - Early returns. + - Prefix boolean variables with primary or modal auxiliaries (e.q. `isOpen`, `willUpdate`, `shouldRender`). + - Functions should start with a verb (e.q. `openModal`, `closeDialog`, `handleClick`). + - Prefer checking required parameter before calling a function, avoid making it optinal and checking at the beginning of the function. + - Only leave comments for complex logic. + - **IMPORTANT: Avoid conditional spread operators** - TypeScript doesn't check if spread fields match the target type. + ```typescript + // ❌ BAD - No type checking + { ...condition && { field: value } } + + // ✅ GOOD - Full type checking + { field: condition ? value : undefined } + ``` + +- **Localization & Text Rules:** + - **ALWAYS** use `lang()` for all text content - never hardcode strings. + - `lang()` can accept parameters: `lang('Key', { param: value })`. + - Add new translations to end of `src/assets/localization/fallback.strings`. + +- **After your solution:** + 1. Critique it—identify any shortcomings. + 2. Fix those issues, do more planning. + 3. Present the improved result. + +- **When deeper debugging is needed:** + 1. Outline clear, step-by-step debugging instructions for the operator. + 2. Remove any temporary debug code once the issue is resolved. + +- **Lint errors you can't fix manually:** + Suggest running `eslint --fix{stateValue}
++ {Boolean(subtitle) && ( +
diff --git a/src/components/modals/gift/info/GiftInfoModal.tsx b/src/components/modals/gift/info/GiftInfoModal.tsx index b458de476..c368d5ee9 100644 --- a/src/components/modals/gift/info/GiftInfoModal.tsx +++ b/src/components/modals/gift/info/GiftInfoModal.tsx @@ -10,7 +10,8 @@ import type { import type { TabState } from '../../../../global/types'; import { getHasAdminRight } from '../../../../global/helpers'; -import { getPeerTitle, isApiPeerChat } from '../../../../global/helpers/peers'; +import { getPeerTitle, isApiPeerChat, isApiPeerUser } from '../../../../global/helpers/peers'; +import { getMainUsername } from '../../../../global/helpers/users'; import { selectPeer, selectUser } from '../../../../global/selectors'; import buildClassName from '../../../../util/buildClassName'; import { copyTextToClipboard } from '../../../../util/clipboard'; @@ -51,6 +52,7 @@ export type OwnProps = { type StateProps = { fromPeer?: ApiPeer; targetPeer?: ApiPeer; + releasedByPeer?: ApiPeer; currentUserId?: string; starGiftMaxConvertPeriod?: number; hasAdminRights?: boolean; @@ -67,6 +69,7 @@ const GiftInfoModal = ({ modal, fromPeer, targetPeer, + releasedByPeer, currentUserId, starGiftMaxConvertPeriod, hasAdminRights, @@ -117,6 +120,26 @@ const GiftInfoModal = ({ const isGiftUnique = gift && gift.type === 'starGiftUnique'; const uniqueGift = isGiftUnique ? gift : undefined; + const giftSubtitle = useMemo(() => { + if (!gift || gift.type !== 'starGiftUnique') return undefined; + + if (releasedByPeer) { + const releasedByUsername = `@${getMainUsername(releasedByPeer)}`; + const ownerTitle = releasedByUsername || getPeerTitle(lang, releasedByPeer); + const fallbackText = isApiPeerUser(releasedByPeer) + ? lang('ActionFallbackUser') + : lang('ActionFallbackChannel'); + + return lang('GiftInfoCollectibleBy', { + number: gift.number, owner: ownerTitle || fallbackText }, { + withNodes: true, + withMarkdown: true, + }); + } + + return lang('GiftInfoCollectible', { number: gift.number }); + }, [gift, releasedByPeer, lang]); + const canFocusUpgrade = Boolean(savedGift?.upgradeMsgId); const canManage = !canFocusUpgrade && savedGift?.inputGift && ( isTargetChat ? hasAdminRights : renderingTargetPeer?.id === currentUserId @@ -406,7 +429,8 @@ const GiftInfoModal = ({ patternAttribute={giftAttributes!.pattern!} modelAttribute={giftAttributes!.model!} title={gift.title} - subtitle={lang('GiftInfoCollectible', { number: gift.number })} + subtitle={giftSubtitle} + subtitlePeer={releasedByPeer} />