From 7e0e271be28997c648e9bbae508fb0643f921c5a Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Wed, 22 Apr 2026 17:15:43 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20Hotfixes:=20inline=20PDF=20previ?= =?UTF-8?q?ew=20and=20instant=20profile=20update=20in=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modules/invoices/invoice.controller.ts | 22 ++++++++++++++++++- frontend/src/pages/settings/SettingsPage.tsx | 4 ++++ frontend/src/store/authStore.ts | 6 +++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/backend/src/modules/invoices/invoice.controller.ts b/backend/src/modules/invoices/invoice.controller.ts index 06fcb1e..16d4dcd 100644 --- a/backend/src/modules/invoices/invoice.controller.ts +++ b/backend/src/modules/invoices/invoice.controller.ts @@ -18,7 +18,9 @@ import { UploadedFile, UseInterceptors, ParseUUIDPipe, + Res, } from '@nestjs/common'; +import { Response } from 'express'; import { FileInterceptor } from '@nestjs/platform-express'; import { InvoicesService } from './invoice.service'; import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard'; @@ -111,7 +113,25 @@ export class InvoicesController { async getFile( @CurrentUser() user: any, @Param('id', ParseUUIDPipe) id: string, + @Res({ passthrough: true }) res: Response, ) { - return this.invoicesService.getFile(user.tenantId, id); + const streamableFile = await this.invoicesService.getFile(user.tenantId, id); + + // We need to determine the content type to ensure it opens inline in the browser (iframe) + // We can fetch the invoice details first to get the extension. + const invoice = await this.invoicesService.findOne(user.tenantId, id); + let mimeType = 'application/pdf'; // Default fallback + if (invoice.original_file_path) { + const ext = invoice.original_file_path.split('.').pop()?.toLowerCase(); + if (ext === 'jpg' || ext === 'jpeg') mimeType = 'image/jpeg'; + else if (ext === 'png') mimeType = 'image/png'; + } + + res.set({ + 'Content-Type': mimeType, + 'Content-Disposition': 'inline', // This forces the browser to display it instead of downloading + }); + + return streamableFile; } } diff --git a/frontend/src/pages/settings/SettingsPage.tsx b/frontend/src/pages/settings/SettingsPage.tsx index 6541917..25d9797 100644 --- a/frontend/src/pages/settings/SettingsPage.tsx +++ b/frontend/src/pages/settings/SettingsPage.tsx @@ -21,6 +21,7 @@ import { } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import apiClient from '../../api/client'; +import { useAuthStore } from '../../store/authStore'; export const SettingsPage = () => { const [activeTab, setActiveTab] = useState('profile'); @@ -37,6 +38,8 @@ export const SettingsPage = () => { language: 'العربية' }); + const updateUser = useAuthStore((state) => state.updateUser); + useEffect(() => { const fetchProfile = async () => { try { @@ -62,6 +65,7 @@ export const SettingsPage = () => { setIsSaving(true); try { await apiClient.post('/users/profile', formData); + updateUser({ name: formData.name }); setShowSuccess(true); setTimeout(() => setShowSuccess(false), 3000); } catch (err) { diff --git a/frontend/src/store/authStore.ts b/frontend/src/store/authStore.ts index 8f2066b..221b7fc 100644 --- a/frontend/src/store/authStore.ts +++ b/frontend/src/store/authStore.ts @@ -20,6 +20,7 @@ interface AuthState { isAuthenticated: boolean; setAuth: (user: User, token: string) => void; clearAuth: () => void; + updateUser: (data: Partial) => void; } export const useAuthStore = create()( @@ -35,6 +36,11 @@ export const useAuthStore = create()( localStorage.removeItem('access_token'); set({ user: null, isAuthenticated: false }); }, + updateUser: (data) => { + set((state) => ({ + user: state.user ? { ...state.user, ...data } : null + })); + }, }), { name: 'musadaq-auth-storage',