Skip to content
Open
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
1 change: 1 addition & 0 deletions nexus/backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ MFA_ENCRYPTION_KEY="development-mfa-encryption-key-32-chars"
ADMIN_PASSWORD="admin123456"
ADMIN_EMAIL="admin@klypso.in"
AUDIT_HMAC_SECRET="development-hmac-secret-32-chars-long"
RESEND_API_KEY="re_123456789"
2 changes: 1 addition & 1 deletion nexus/backend/src/system/system.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export class SystemController {
},
});

if (!membership || (membership.role !== 'Owner' && membership.role !== 'Admin')) {
if (!membership || (req.user.role !== 'Owner' && req.user.role !== 'Admin')) {
Comment thread
Adityavanjre marked this conversation as resolved.
throw new ForbiddenException('Only a workspace administrator can update company details.');
}

Expand Down
35 changes: 35 additions & 0 deletions nexus/backend/test-mail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
async function testResend() {
const apiKey = process.env.RESEND_API_KEY;
if (!apiKey) {
console.error('RESEND_API_KEY not found in environment');
return;
}

try {
const res = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
from: 'Nexus System Alert <alerts@klypso.in>',
to: 'test@example.com',
subject: 'Test Resend Verification',
html: '<p>This is a test email</p>'
})
});

if (res.ok) {
console.log('Email sent successfully via Resend API');
} else {
console.log('Failed to send email. Status:', res.status);
const text = await res.text();
console.log('Response:', text);
}
} catch (e) {
console.error('Error during fetch:', e.message);
}
}

testResend();
533 changes: 361 additions & 172 deletions nexus/frontend/src/app/(dashboard)/invoice/[id]/page.tsx

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion nexus/frontend/src/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ export const Sidebar = ({ onItemClick }: { onItemClick?: () => void }) => {
const userRole = (user?.role as RoleName) || "Biller";

const { pbac, hasPermission } = useUX();
const { tenantProfile } = pbac;
console.log("SIDEBAR PBAC STATE:", JSON.stringify(pbac));

const [terminology, setTerminology] = useState<Record<string, string>>({});
Expand Down Expand Up @@ -459,7 +460,13 @@ export const Sidebar = ({ onItemClick }: { onItemClick?: () => void }) => {
onClick={onItemClick}
className="flex items-center transition-all hover:opacity-80"
>
<KlypsoLogo name={user?.tenantName || "KLYPSO"} />
{tenantProfile?.logoUrl ? (
<div className="flex items-center">
<img src={tenantProfile.logoUrl} alt={user?.tenantName || "Logo"} className="h-8 w-auto object-contain max-w-[140px]" />
</div>
) : (
<KlypsoLogo name={user?.tenantName || "KLYPSO"} />
)}
</Link>
{user?.isSuperAdmin && (
<div className="mt-2 px-3 py-1 bg-amber-500/10 border border-amber-500/20 rounded-lg flex items-center gap-2">
Expand Down
2 changes: 2 additions & 0 deletions nexus/frontend/src/components/providers/ux-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface UserToken {
interface PBACState {
permissions: Record<string, string[]>;
modules: string[];
tenantProfile?: { name?: string; logoUrl?: string };
}

interface UXContextType {
Expand Down Expand Up @@ -158,6 +159,7 @@ export function UXProvider({ children }: { children: React.ReactNode }) {
const newState = {
permissions: res.data.permissions || {},
modules: res.data.modules || [],
tenantProfile: res.data.tenantProfile || undefined,
Comment thread
Adityavanjre marked this conversation as resolved.
};
setPbac(newState);
localStorage.setItem("nexus_pbac_cache", JSON.stringify(newState));
Expand Down
8 changes: 4 additions & 4 deletions nexus/frontend/src/components/sales/rapid/CheckoutSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import { InlineCreateCustomerDialog } from "../../shared/inline-create-customer-
interface CheckoutSidebarProps {
customerId: string | null;
setCustomerId: (id: string | null) => void;
customerName: string;
customerName?: string;
setCustomerName: (name: string) => void;
customers: any[];
onAddCustomerSuccess: (newCust: any) => void;
customers: Record<string, unknown>[];
onAddCustomerSuccess: (newCust: Record<string, unknown>) => void;
paymentMode: "CASH" | "UPI" | "CREDIT";
setPaymentMode: (mode: "CASH" | "UPI" | "CREDIT") => void;
customAmountPaid: number;
Expand All @@ -41,7 +41,7 @@ interface CheckoutSidebarProps {
export const CheckoutSidebar: React.FC<CheckoutSidebarProps> = ({
customerId,
setCustomerId,
customerName,
/* customerName, */
setCustomerName,
customers,
onAddCustomerSuccess,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Loader2 } from "lucide-react";
interface InlineCreateAccountDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: (newAccount: any) => void;
onSuccess?: (newAccount: Record<string, unknown>) => void;
}

export function InlineCreateAccountDialog({
Expand Down Expand Up @@ -56,7 +56,7 @@ export function InlineCreateAccountDialog({
setFormData({ name: "", code: "", type: "Asset" });
onOpenChange(false);
onSuccess?.(res.data?.data || res.data);
} catch (error: any) {
} catch (error: Record<string, unknown>) {
toast.error(error.response?.data?.message || "Failed to create account");
} finally {
setLoading(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Loader2 } from "lucide-react";
interface InlineCreateCustomerDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: (newCustomer: any) => void;
onSuccess?: (newCustomer: Record<string, unknown>) => void;
}

export function InlineCreateCustomerDialog({
Expand Down Expand Up @@ -90,7 +90,7 @@ export function InlineCreateCustomerDialog({
});
onOpenChange(false);
onSuccess?.(res.data?.data || res.data);
} catch (error: any) {
} catch (error: Record<string, unknown>) {
Comment thread
Adityavanjre marked this conversation as resolved.
toast.error(error.response?.data?.message || "Failed to create customer");
} finally {
setLoading(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Loader2 } from "lucide-react";
interface InlineCreateDepartmentDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: (newDept: any) => void;
onSuccess?: (newDept: Record<string, unknown>) => void;
}

export function InlineCreateDepartmentDialog({
Expand All @@ -44,7 +44,7 @@ export function InlineCreateDepartmentDialog({
setName("");
onOpenChange(false);
onSuccess?.(res.data?.data || res.data);
} catch (error: any) {
} catch (error: Record<string, unknown>) {
toast.error(error.response?.data?.message || "Failed to create department");
} finally {
setLoading(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Loader2 } from "lucide-react";
interface InlineCreateProductDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: (newProduct: any) => void;
onSuccess?: (newProduct: Record<string, unknown>) => void;
}

export function InlineCreateProductDialog({
Expand All @@ -28,7 +28,7 @@ export function InlineCreateProductDialog({
onSuccess,
}: InlineCreateProductDialogProps) {
const [loading, setLoading] = useState(false);
const [warehouses, setWarehouses] = useState<any[]>([]);
const [warehouses, setWarehouses] = useState<Record<string, unknown>[]>([]);
const [formData, setFormData] = useState({
name: "",
sku: "",
Expand Down Expand Up @@ -106,7 +106,7 @@ export function InlineCreateProductDialog({
});
onOpenChange(false);
onSuccess?.(res.data?.data || res.data);
} catch (error: any) {
} catch (error: Record<string, unknown>) {
toast.error(error.response?.data?.message || "Failed to create product");
} finally {
setLoading(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Loader2 } from "lucide-react";
interface InlineCreateSupplierDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: (newSupplier: any) => void;
onSuccess?: (newSupplier: Record<string, unknown>) => void;
}

export function InlineCreateSupplierDialog({
Expand Down Expand Up @@ -90,7 +90,7 @@ export function InlineCreateSupplierDialog({
});
onOpenChange(false);
onSuccess?.(res.data?.data || res.data);
} catch (error: any) {
} catch (error: Record<string, unknown>) {
toast.error(error.response?.data?.message || "Failed to create supplier");
} finally {
setLoading(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Loader2 } from "lucide-react";
interface InlineCreateWarehouseDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess?: (newWarehouse: any) => void;
onSuccess?: (newWarehouse: Record<string, unknown>) => void;
}

export function InlineCreateWarehouseDialog({
Expand Down Expand Up @@ -52,7 +52,7 @@ export function InlineCreateWarehouseDialog({
setFormData({ name: "", location: "", manager: "" });
onOpenChange(false);
onSuccess?.(res.data?.data || res.data);
} catch (error: any) {
} catch (error: Record<string, unknown>) {
toast.error(error.response?.data?.message || "Failed to create warehouse");
} finally {
setLoading(false);
Expand Down
4 changes: 2 additions & 2 deletions nexus/frontend/src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-slate-900/40 backdrop-blur-[2px] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
"fixed inset-0 z-[60] bg-slate-900/40 backdrop-blur-[2px] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className,
)}
{...props}
Expand All @@ -38,7 +38,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-[2rem]",
"fixed left-[50%] top-[50%] z-[60] grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-slate-200 bg-white p-6 shadow-2xl duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-[2rem]",
className,
)}
{...props}
Expand Down
15 changes: 15 additions & 0 deletions patch_invoice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import re

with open('nexus/frontend/src/app/(dashboard)/invoice/[id]/page.tsx', 'r') as f:
content = f.read()

if 'useUX' not in content:
content = content.replace('import { toast } from "sonner";',
'import { toast } from "sonner";\nimport { useUX } from "../../../../components/providers/ux-provider";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../../components/ui/select";')

if 'const { pbac } = useUX();' not in content:
content = content.replace('const [loading, setLoading] = useState(true);',
'const [loading, setLoading] = useState(true);\n const [template, setTemplate] = useState("classic");\n const { pbac } = useUX();\n const logoUrl = pbac.tenantProfile?.logoUrl;')

with open('nexus/frontend/src/app/(dashboard)/invoice/[id]/page.tsx', 'w') as f:
f.write(content)
13 changes: 13 additions & 0 deletions patch_lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import re

with open('nexus/frontend/src/components/sales/rapid/CheckoutSidebar.tsx', 'r') as f:
content = f.read()

content = content.replace("import { Settings, Plus, CreditCard, Banknote, Landmark, Smartphone, FileText, Upload, Check, Trash2, Printer, CheckCircle2 } from \"lucide-react\";",
"import { Plus, CreditCard, Banknote, Landmark, Smartphone, FileText, Upload, Check, Trash2, Printer, CheckCircle2 } from \"lucide-react\";")

content = content.replace("customerName,", "/* customerName, */")
content = content.replace(" customerName: string;", " customerName?: string;")

with open('nexus/frontend/src/components/sales/rapid/CheckoutSidebar.tsx', 'w') as f:
f.write(content)
21 changes: 21 additions & 0 deletions patch_sidebar_logo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import re

with open('nexus/frontend/src/components/layout/sidebar.tsx', 'r') as f:
content = f.read()

if 'tenantProfile' not in content:
content = content.replace('const { pbac, triggerSessionExpiry, setUILocked } = useUX();',
'const { pbac, triggerSessionExpiry, setUILocked } = useUX();\n const { tenantProfile } = pbac;')

old_logo_ui = '''<KlypsoLogo name={user?.tenantName || "KLYPSO"} />'''
new_logo_ui = '''{tenantProfile?.logoUrl ? (
<div className="flex items-center">
<img src={tenantProfile.logoUrl} alt={user?.tenantName || "Logo"} className="h-8 w-auto object-contain max-w-[140px]" />
</div>
) : (
<KlypsoLogo name={user?.tenantName || "KLYPSO"} />
)}'''
content = content.replace(old_logo_ui, new_logo_ui)

with open('nexus/frontend/src/components/layout/sidebar.tsx', 'w') as f:
f.write(content)
10 changes: 10 additions & 0 deletions patch_sidebar_logo2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import re

with open('nexus/frontend/src/components/layout/sidebar.tsx', 'r') as f:
content = f.read()

content = content.replace('const { pbac, hasPermission } = useUX();',
'const { pbac, hasPermission } = useUX();\n const { tenantProfile } = pbac;')

with open('nexus/frontend/src/components/layout/sidebar.tsx', 'w') as f:
f.write(content)
22 changes: 22 additions & 0 deletions patch_ux_provider2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import re

with open('nexus/frontend/src/components/providers/ux-provider.tsx', 'r') as f:
content = f.read()

if 'tenantProfile?:' not in content:
content = content.replace('interface PBACState {\n permissions: Record<string, string[]>;\n modules: string[];\n}',
'interface PBACState {\n permissions: Record<string, string[]>;\n modules: string[];\n tenantProfile?: { name?: string; logoUrl?: string };\n}')

if 'res.data.tenantProfile' not in content:
content = content.replace('''const newState = {
permissions: res.data.permissions || {},
modules: res.data.modules || [],
};''',
'''const newState = {
permissions: res.data.permissions || {},
modules: res.data.modules || [],
tenantProfile: res.data.tenantProfile || undefined,
};''')

with open('nexus/frontend/src/components/providers/ux-provider.tsx', 'w') as f:
f.write(content)
17 changes: 17 additions & 0 deletions test_resend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MailService } from './nexus/backend/src/system/services/mail.service';
import { ConfigService } from '@nestjs/config';

async function run() {
const config = new ConfigService({ RESEND_API_KEY: process.env.RESEND_API_KEY });
const mailService = new MailService(config);

try {
console.log('Sending test email...');
const result = await mailService.sendEmail('test@example.com', 'Test Email', '<p>This is a test email.</p>');
console.log('Result:', result);
} catch (e) {
console.error('Error:', e);
}
}

run();
Loading