From 638f1d54560fcd558dc867c03cef246b36d15655 Mon Sep 17 00:00:00 2001 From: PEDZEO Date: Mon, 19 Jan 2026 07:39:09 +0300 Subject: [PATCH] Refactor Dockerfile and Vite configuration to remove VITE_APP_VERSION and streamline build process - Removed VITE_APP_VERSION from Dockerfile and GitHub workflows. - Simplified Vite configuration by eliminating the Git version retrieval function and global constant for app version. - Updated ColorPicker and other components to enhance accessibility with aria-labels. - Introduced unique identifiers for buttons in the AppEditorModal for better React key management. - Improved error handling in the Login component to provide user-friendly messages without exposing sensitive data. --- .github/workflows/docker.yml | 1 - .github/workflows/release.yml | 1 - Dockerfile | 3 - src/api/adminApps.ts | 1 + src/api/wheel.ts | 31 +- src/components/ColorPicker.tsx | 17 +- src/components/ConnectionModal.tsx | 2 +- src/components/InsufficientBalancePrompt.tsx | 2 +- src/components/Onboarding.tsx | 4 +- src/components/TopUpModal.tsx | 83 ++++- src/components/layout/Layout.tsx | 12 +- src/pages/AdminApps.tsx | 10 +- src/pages/AdminDashboard.tsx | 4 +- src/pages/AdminPanel.tsx | 348 +++++++++++-------- src/pages/AdminWheel.tsx | 4 +- src/pages/DeepLinkRedirect.tsx | 34 +- src/pages/Login.tsx | 16 +- src/pages/Support.tsx | 52 ++- src/types/index.ts | 1 + src/vite-env.d.ts | 3 - vite.config.ts | 17 - 21 files changed, 401 insertions(+), 245 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 00c644b..57fbd02 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -62,4 +62,3 @@ jobs: VITE_TELEGRAM_BOT_USERNAME= VITE_APP_NAME=Cabinet VITE_APP_LOGO=V - VITE_APP_VERSION=${{ github.ref_name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8122984..fe4ab04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,7 +34,6 @@ jobs: VITE_TELEGRAM_BOT_USERNAME: VITE_APP_NAME: Bedolaga Cabinet VITE_APP_LOGO: V - VITE_APP_VERSION: ${{ github.ref_name }} - name: Create dist archive run: | diff --git a/Dockerfile b/Dockerfile index 47c7a63..de296d3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,6 @@ WORKDIR /app COPY package.json package-lock.json* ./ # Install dependencies -# Use npm install if no lock file, npm ci if lock file exists RUN if [ -f package-lock.json ]; then npm ci; else npm install; fi # Copy source code @@ -18,14 +17,12 @@ ARG VITE_API_URL=/api ARG VITE_TELEGRAM_BOT_USERNAME ARG VITE_APP_NAME=Cabinet ARG VITE_APP_LOGO=V -ARG VITE_APP_VERSION # Set environment variables for build ENV VITE_API_URL=$VITE_API_URL ENV VITE_TELEGRAM_BOT_USERNAME=$VITE_TELEGRAM_BOT_USERNAME ENV VITE_APP_NAME=$VITE_APP_NAME ENV VITE_APP_LOGO=$VITE_APP_LOGO -ENV VITE_APP_VERSION=$VITE_APP_VERSION # Build the application RUN npm run build diff --git a/src/api/adminApps.ts b/src/api/adminApps.ts index 51a69af..b57a78c 100644 --- a/src/api/adminApps.ts +++ b/src/api/adminApps.ts @@ -9,6 +9,7 @@ export interface LocalizedText { } export interface AppButton { + id?: string // Unique identifier for React key (client-side only) buttonLink: string buttonText: LocalizedText } diff --git a/src/api/wheel.ts b/src/api/wheel.ts index c076e3b..7511f94 100644 --- a/src/api/wheel.ts +++ b/src/api/wheel.ts @@ -98,6 +98,22 @@ export interface WheelPrizeAdmin { updated_at: string | null } +// Type for creating a new prize (excludes id, config_id which are auto-generated) +export interface CreateWheelPrizeData { + prize_type: string + prize_value: number + display_name: string + emoji?: string + color?: string + prize_value_kopeks: number + sort_order?: number + manual_probability?: number | null + is_active?: boolean + promo_balance_bonus_kopeks?: number + promo_subscription_days?: number + promo_traffic_gb?: number +} + export interface AdminWheelConfig { id: number is_enabled: boolean @@ -223,20 +239,7 @@ export const adminWheelApi = { }, // Create prize - createPrize: async (data: { - prize_type: string - prize_value: number - display_name: string - emoji?: string - color?: string - prize_value_kopeks: number - sort_order?: number - manual_probability?: number | null - is_active?: boolean - promo_balance_bonus_kopeks?: number - promo_subscription_days?: number - promo_traffic_gb?: number - }): Promise => { + createPrize: async (data: CreateWheelPrizeData): Promise => { const response = await apiClient.post('/cabinet/admin/wheel/prizes', data) return response.data }, diff --git a/src/components/ColorPicker.tsx b/src/components/ColorPicker.tsx index 9889dd0..e841b7a 100644 --- a/src/components/ColorPicker.tsx +++ b/src/components/ColorPicker.tsx @@ -81,14 +81,15 @@ export function ColorPicker({ value, onChange, label, description, disabled }: C {description &&

{description}

}
- {/* Color preview button */} + {/* Color preview button - min 44px for touch accessibility */}
diff --git a/src/components/ConnectionModal.tsx b/src/components/ConnectionModal.tsx index 2ac3e2b..ef89184 100644 --- a/src/components/ConnectionModal.tsx +++ b/src/components/ConnectionModal.tsx @@ -286,7 +286,7 @@ export default function ConnectionModal({ onClose }: ConnectionModalProps) {

{t('subscription.connection.title')}

- @@ -382,6 +384,8 @@ export default function Layout({ children }: LayoutProps) { @@ -500,14 +504,6 @@ export default function Layout({ children }: LayoutProps) { {children} - {/* Version footer for admin pages */} - {isAdminActive() && ( -
- - Cabinet {__APP_VERSION__} - -
- )} {/* Mobile Bottom Navigation - only core items */} diff --git a/src/pages/AdminApps.tsx b/src/pages/AdminApps.tsx index 0a25598..b702355 100644 --- a/src/pages/AdminApps.tsx +++ b/src/pages/AdminApps.tsx @@ -189,7 +189,13 @@ function AppEditorModal({ app, platform, isNew, onSave, onClose }: AppEditorModa const addButton = (stepKey: 'installationStep' | 'additionalBeforeAddSubscriptionStep' | 'additionalAfterAddSubscriptionStep') => { const step = editedApp[stepKey] || emptyAppStep() const buttons = step.buttons || [] - updateButtons(stepKey, [...buttons, { buttonLink: '', buttonText: emptyLocalizedText() }]) + // Generate unique id for React key + const newButton: AppButton = { + id: `btn-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, + buttonLink: '', + buttonText: emptyLocalizedText() + } + updateButtons(stepKey, [...buttons, newButton]) } const removeButton = (stepKey: 'installationStep' | 'additionalBeforeAddSubscriptionStep' | 'additionalAfterAddSubscriptionStep', index: number) => { @@ -253,7 +259,7 @@ function AppEditorModal({ app, platform, isNew, onSave, onClose }: AppEditorModa {buttons.map((button, index) => ( -
+
{t('admin.apps.button')} #{index + 1} @@ -141,6 +142,29 @@ export default function Support() { const createFileInputRef = useRef(null) const replyFileInputRef = useRef(null) + // Cleanup function to revoke object URLs and prevent memory leaks + const clearAttachment = useCallback(( + attachment: MediaAttachment | null, + setAttachment: (a: MediaAttachment | null) => void + ) => { + if (attachment?.preview) { + URL.revokeObjectURL(attachment.preview) + } + setAttachment(null) + }, []) + + // Cleanup on unmount to prevent memory leaks + useEffect(() => { + return () => { + if (createAttachment?.preview) { + URL.revokeObjectURL(createAttachment.preview) + } + if (replyAttachment?.preview) { + URL.revokeObjectURL(replyAttachment.preview) + } + } + }, []) // Empty deps - only run on unmount + // Get support configuration const { data: supportConfig, isLoading: configLoading } = useQuery({ queryKey: ['support-config'], @@ -162,8 +186,14 @@ export default function Support() { // Handle file selection const handleFileSelect = async ( file: File, - setAttachment: (a: MediaAttachment | null) => void + setAttachment: (a: MediaAttachment | null) => void, + currentAttachment: MediaAttachment | null ) => { + // Revoke old object URL before creating new one + if (currentAttachment?.preview) { + URL.revokeObjectURL(currentAttachment.preview) + } + // Validate file type const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] if (!allowedTypes.includes(file.type)) { @@ -224,7 +254,7 @@ export default function Support() { setShowCreateForm(false) setNewTitle('') setNewMessage('') - setCreateAttachment(null) + clearAttachment(createAttachment, setCreateAttachment) setSelectedTicket(ticket) }, }) @@ -242,7 +272,7 @@ export default function Support() { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['ticket', selectedTicket?.id] }) setReplyMessage('') - setReplyAttachment(null) + clearAttachment(replyAttachment, setReplyAttachment) }, }) @@ -443,7 +473,7 @@ export default function Support() { onClick={() => { setShowCreateForm(true) setSelectedTicket(null) - setCreateAttachment(null) + clearAttachment(createAttachment, setCreateAttachment) }} className="btn-primary" > @@ -469,7 +499,7 @@ export default function Support() { onClick={() => { setSelectedTicket(ticket as unknown as TicketDetail) setShowCreateForm(false) - setReplyAttachment(null) + clearAttachment(replyAttachment, setReplyAttachment) }} className={`w-full text-left p-4 rounded-xl border transition-all ${ selectedTicket?.id === ticket.id @@ -555,14 +585,14 @@ export default function Support() { className="hidden" onChange={(e) => { const file = e.target.files?.[0] - if (file) handleFileSelect(file, setCreateAttachment) + if (file) handleFileSelect(file, setCreateAttachment, createAttachment) e.target.value = '' }} /> {createAttachment ? ( setCreateAttachment(null)} + onRemove={() => clearAttachment(createAttachment, setCreateAttachment)} /> ) : (