Revert to version 8558d4a

This commit is contained in:
Flatlogic Bot 2026-03-01 10:59:54 +00:00
parent b27bf91054
commit 20cc05a3ca
17 changed files with 333 additions and 1627 deletions

View File

@ -154,7 +154,6 @@ async function awaitResponse(aiRequestId, options = {}) {
const interval = Math.max(Number(options.interval ?? 5), 1);
const deadline = Date.now() + Math.max(timeout, interval) * 1000;
/* eslint-disable no-constant-condition */
while (true) {
const statusResp = await fetchStatus(aiRequestId, {
headers: options.headers,
@ -307,7 +306,7 @@ function buildUrl(pathValue, baseUrl) {
}
function resolveStatusPath(aiRequestId, cfg) {
const basePath = (cfg.responsesPath || "").replace(new RegExp("/+"), "");
const basePath = (cfg.responsesPath || "").replace(/\/+$/, "");
if (!basePath) {
return `/ai-request/${encodeURIComponent(String(aiRequestId))}/status`;
}

View File

@ -56,7 +56,7 @@ passport.use(new MicrosoftStrategy({
));
function socialStrategy(email, profile, provider, done) {
db.users.findOrCreate({where: {email, provider}}).then(([user]) => {
db.users.findOrCreate({where: {email, provider}}).then(([user, created]) => {
const body = {
id: user.id,
email: user.email,
@ -65,4 +65,4 @@ function socialStrategy(email, profile, provider, done) {
const token = helpers.jwtSign({user: body});
return done(null, {token});
});
}
}

View File

@ -1,4 +1,7 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');
@ -153,6 +156,7 @@ module.exports = class AlertsDBApi {
static async update(id, data, options) {
const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined;
const globalAccess = currentUser.app_role?.globalAccess;
const alerts = await db.alerts.findByPk(id, {}, {transaction});
@ -358,6 +362,9 @@ module.exports = class AlertsDBApi {
offset = currentPage * limit;
const orderBy = null;
const transaction = (options && options.transaction) || undefined;
let include = [
@ -652,7 +659,7 @@ module.exports = class AlertsDBApi {
where,
limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined,
order: [['title_text', 'ASC']],
orderBy: [['title_text', 'ASC']],
});
return records.map((record) => ({
@ -662,4 +669,5 @@ module.exports = class AlertsDBApi {
}
};
};

View File

@ -1,79 +0,0 @@
module.exports = {
/**
* @param {QueryInterface} queryInterface
* @returns {Promise<void>}
*/
async up(queryInterface) {
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.createTable('funding_opportunities', {
id: {
type: queryInterface.sequelize.Sequelize.DataTypes.UUID,
defaultValue: queryInterface.sequelize.Sequelize.DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: queryInterface.sequelize.Sequelize.DataTypes.TEXT,
},
focus: {
type: queryInterface.sequelize.Sequelize.DataTypes.TEXT,
},
stage: {
type: queryInterface.sequelize.Sequelize.DataTypes.TEXT,
},
contact_type: {
type: queryInterface.sequelize.Sequelize.DataTypes.TEXT,
},
description: {
type: queryInterface.sequelize.Sequelize.DataTypes.TEXT,
},
website: {
type: queryInterface.sequelize.Sequelize.DataTypes.TEXT,
},
amount_min: {
type: queryInterface.sequelize.Sequelize.DataTypes.DECIMAL,
},
amount_max: {
type: queryInterface.sequelize.Sequelize.DataTypes.DECIMAL,
},
createdById: {
type: queryInterface.sequelize.Sequelize.DataTypes.UUID,
references: {
key: 'id',
model: 'users',
},
},
updatedById: {
type: queryInterface.sequelize.Sequelize.DataTypes.UUID,
references: {
key: 'id',
model: 'users',
},
},
createdAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE },
updatedAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE },
deletedAt: { type: queryInterface.sequelize.Sequelize.DataTypes.DATE },
importHash: {
type: queryInterface.sequelize.Sequelize.DataTypes.STRING(255),
allowNull: true,
unique: true,
},
}, { transaction });
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
async down(queryInterface) {
const transaction = await queryInterface.sequelize.transaction();
try {
await queryInterface.dropTable('funding_opportunities', { transaction });
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
}
};

View File

@ -1,58 +0,0 @@
module.exports = function(sequelize, DataTypes) {
const funding_opportunities = sequelize.define(
'funding_opportunities',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.TEXT,
},
focus: {
type: DataTypes.TEXT,
},
stage: {
type: DataTypes.TEXT,
},
contact_type: {
type: DataTypes.TEXT,
},
description: {
type: DataTypes.TEXT,
},
website: {
type: DataTypes.TEXT,
},
amount_min: {
type: DataTypes.DECIMAL,
},
amount_max: {
type: DataTypes.DECIMAL,
},
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
unique: true,
},
},
{
timestamps: true,
paranoid: true,
freezeTableName: true,
},
);
funding_opportunities.associate = (db) => {
db.funding_opportunities.belongsTo(db.users, {
as: 'createdBy',
});
db.funding_opportunities.belongsTo(db.users, {
as: 'updatedBy',
});
};
return funding_opportunities;
};

View File

@ -1,144 +0,0 @@
'use strict';
module.exports = {
up: async (queryInterface) => {
const opportunities = [
{
id: '10000000-0000-0000-0000-000000000001',
name: 'Sequoia Capital',
focus: 'Enterprise, AI, Consumer',
stage: 'Seed to IPO',
contact_type: 'VC',
description: 'Global venture capital firm helping daring founders build legendary companies.',
website: 'https://www.sequoiacap.com',
amount_min: 500000,
amount_max: 100000000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000002',
name: 'Andreessen Horowitz (a16z)',
focus: 'Fintech, Crypto, AI, Games',
stage: 'All stages',
contact_type: 'VC',
description: 'Venture capital firm that backs bold entrepreneurs building the future through technology.',
website: 'https://a16z.com',
amount_min: 1000000,
amount_max: 500000000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000003',
name: 'Index Ventures',
focus: 'Global SaaS, Commerce',
stage: 'Early to Growth',
contact_type: 'VC',
description: 'Partnering with exceptional entrepreneurs from seed to IPO.',
website: 'https://www.indexventures.com',
amount_min: 2000000,
amount_max: 50000000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000004',
name: 'Naval Ravikant',
focus: 'Deep Tech, AI, Crypto',
stage: 'Seed',
contact_type: 'Angel',
description: 'Founding CEO of AngelList, investor in 200+ companies including Uber, Twitter, and Wish.',
website: 'https://twitter.com/naval',
amount_min: 50000,
amount_max: 500000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000005',
name: 'Tiger Global',
focus: 'Hyper-growth, Consumer, SaaS',
stage: 'Growth',
contact_type: 'Institutional',
description: 'Investment firm focused on public and private companies in the global Internet, software, consumer, and financial technology industries.',
website: 'https://www.tigerglobal.com',
amount_min: 10000000,
amount_max: 1000000000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000006',
name: 'Goldman Sachs Principal',
focus: 'Late Stage, Fintech, Infrastructure',
stage: 'Pre-IPO',
contact_type: 'Bank/Institutional',
description: 'Principal investment arm of Goldman Sachs.',
website: 'https://www.goldmansachs.com',
amount_min: 50000000,
amount_max: 500000000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000007',
name: 'First Round Capital',
focus: 'Tech, SaaS, Mobile',
stage: 'Seed',
contact_type: 'VC',
description: 'Seed-stage venture capital firm focused on building community.',
website: 'https://firstround.com',
amount_min: 250000,
amount_max: 2000000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000008',
name: 'SV Angel',
focus: 'Consumer, Enterprise',
stage: 'Seed',
contact_type: 'Angel Group',
description: 'Early-stage venture capital firm based in San Francisco.',
website: 'https://svangel.com',
amount_min: 50000,
amount_max: 1000000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000009',
name: 'Benchmark',
focus: 'Consumer, Mobile, SaaS',
stage: 'Early',
contact_type: 'VC',
description: 'Venture capital firm that focuses on early-stage startups.',
website: 'https://www.benchmark.com',
amount_min: 1000000,
amount_max: 20000000,
createdAt: new Date(),
updatedAt: new Date(),
},
{
id: '10000000-0000-0000-0000-000000000010',
name: 'General Catalyst',
focus: 'Fintech, Healthcare, AI',
stage: 'Seed to Growth',
contact_type: 'VC',
description: 'Venture capital firm that invests in powerful, positive change.',
website: 'https://www.generalcatalyst.com',
amount_min: 1000000,
amount_max: 100000000,
createdAt: new Date(),
updatedAt: new Date(),
}
];
await queryInterface.bulkInsert('funding_opportunities', opportunities, {});
},
down: async (queryInterface) => {
await queryInterface.bulkDelete('funding_opportunities', null, {});
}
};

View File

@ -1,3 +1,4 @@
const express = require('express');
const cors = require('cors');
const app = express();
@ -15,7 +16,6 @@ const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const sqlRoutes = require('./routes/sql');
const pexelsRoutes = require('./routes/pexels');
const intelligenceRoutes = require('./routes/intelligence');
const organizationForAuthRoutes = require('./routes/organizationLogin');
@ -197,12 +197,6 @@ app.use(
passport.authenticate('jwt', { session: false }),
sqlRoutes);
app.use(
'/api/intelligence',
passport.authenticate('jwt', { session: false }),
intelligenceRoutes,
);
app.use(
'/api/org-for-auth',
organizationForAuthRoutes,
@ -232,4 +226,4 @@ db.sequelize.sync().then(function () {
});
});
module.exports = app;
module.exports = app;

View File

@ -1,54 +0,0 @@
const express = require('express');
const router = express.Router();
const IntelligenceService = require('../services/intelligence');
const Helpers = require('../helpers');
router.get('/', Helpers.wrapAsync(async (req, res) => {
const organizationsId = req.currentUser.organizationsId;
const result = await IntelligenceService.getIntegratedAnalysis(organizationsId);
res.status(200).send(result);
}));
router.get('/modeling', Helpers.wrapAsync(async (req, res) => {
const organizationsId = req.currentUser.organizationsId;
const result = await IntelligenceService.getFinancialModeling(organizationsId);
res.status(200).send(result);
}));
router.get('/opportunities', Helpers.wrapAsync(async (req, res) => {
const organizationsId = req.currentUser.organizationsId;
const result = await IntelligenceService.getMarketOpportunities(organizationsId);
res.status(200).send(result);
}));
router.get('/stewardship', Helpers.wrapAsync(async (req, res) => {
const organizationsId = req.currentUser.organizationsId;
const result = await IntelligenceService.getFinancialStewardship(organizationsId);
res.status(200).send(result);
}));
router.get('/risk', Helpers.wrapAsync(async (req, res) => {
const organizationsId = req.currentUser.organizationsId;
const result = await IntelligenceService.getCashFlowRisk(organizationsId);
res.status(200).send(result);
}));
router.get('/capital', Helpers.wrapAsync(async (req, res) => {
const organizationsId = req.currentUser.organizationsId;
const result = await IntelligenceService.getCapitalStrategy(organizationsId);
res.status(200).send(result);
}));
router.get('/leadership', Helpers.wrapAsync(async (req, res) => {
const organizationsId = req.currentUser.organizationsId;
const result = await IntelligenceService.getStrategicLeadership(organizationsId);
res.status(200).send(result);
}));
router.get('/esg', Helpers.wrapAsync(async (req, res) => {
const organizationsId = req.currentUser.organizationsId;
const result = await IntelligenceService.getESGStrategy(organizationsId);
res.status(200).send(result);
}));
module.exports = router;

View File

@ -1,4 +1,3 @@
const db = require("../db/models");
const UsersDBApi = require('../db/api/users');
const ValidationError = require('./notifications/errors/validation');
const ForbiddenError = require('./notifications/errors/forbidden');

View File

@ -1,354 +0,0 @@
const db = require('../db/models');
const { LocalAIApi } = require('../ai/LocalAIApi');
module.exports = class IntelligenceService {
static async getExternalIntelligence() {
// Fetch real funding opportunities from the database
const funding_opportunities = await db.funding_opportunities.findAll();
// In a real app, this would fetch from a news API or a market data provider.
return {
trends: [
{
title: "Agentic AI in Finance",
content: "AI has moved from assistance to transactional authority, driving autonomous decision-making in risk modeling and fraud detection.",
impact: "High",
date: "2026-02-28"
},
{
title: "Consolidation in Fintech",
content: "Market maturation is leading to traditional banks acquiring challengers. Profitability is prioritized over 'growth at all costs'.",
impact: "Medium",
date: "2026-02-25"
},
{
title: "Real-Time Payments Global Standard",
content: "24/7 settlement and cross-border efficiency are now industry baselines.",
impact: "Medium",
date: "2026-02-20"
}
],
benchmarks: {
saas: {
gross_margin_target: 75,
net_dollar_retention_median: 101,
net_dollar_retention_top: 111,
rule_of_40_target: 40,
valuation_multiple_median: 6.5,
valuation_multiple_top: 12.0
},
fintech: {
cac_payback_months_target: 12,
burn_multiplier_target: 1.5,
ltv_cac_target: 3.5
}
},
market_caps: [
{ segment: "AI-Fintech", multiple_trend: "+15%", opportunity_score: 9.2 },
{ segment: "Traditional SaaS", multiple_trend: "-5%", opportunity_score: 6.5 },
{ segment: "Infrastructure/API", multiple_trend: "+8%", opportunity_score: 8.8 }
],
funding_partners: funding_opportunities,
regulatory_updates: [
{ title: "SEC AI Disclosure Rules", content: "New requirements for disclosing AI-driven financial models.", date: "2026-01-15" },
{ title: "Global ESG Reporting Standard", content: "Mandatory ESG reporting for companies over $5M ARR.", date: "2026-02-01" }
]
};
}
static async getProprietaryData(organizationsId) {
if (!organizationsId) return {};
const metrics = await db.metrics_snapshots.findAll({
where: { organizationsId },
order: [['as_of_at', 'DESC'], ['createdAt', 'DESC']],
limit: 12
});
const latest = metrics[0] || {};
return {
mrr: parseFloat(latest.mrr || 0),
arr: parseFloat(latest.arr || 0),
margin: parseFloat(latest.gross_margin_percent || 0),
burn: parseFloat(latest.net_burn_monthly || 0),
runway: parseFloat(latest.runway_months || 0),
history: metrics.map(m => ({
date: m.as_of_at || m.createdAt,
mrr: parseFloat(m.mrr || 0),
burn: parseFloat(m.net_burn_monthly || 0),
margin: parseFloat(m.gross_margin_percent || 0)
}))
};
}
static async getFinancialStewardship(organizationsId) {
const proprietary = await this.getProprietaryData(organizationsId);
const external = await this.getExternalIntelligence();
const prompt = `
Act as a Virtual CFO specializing in Financial Stewardship and Compliance.
Company State:
- ARR: $${proprietary.arr}
- Margin: ${proprietary.margin}%
Regulatory Environment (2026):
${JSON.stringify(external.regulatory_updates)}
Tasks:
1. **Audit Readiness**: Based on the $${proprietary.arr} ARR, suggest an audit schedule and key control areas (e.g., Revenue Recognition under ASC 606).
2. **Closing the Books**: Provide a checklist for a 'Continuous Close' process to ensure real-time financial reporting accuracy.
3. **Regulatory Compliance**: How do the new ${external.regulatory_updates[0].title} affect this company's AI-driven insights?
4. **Tax Strategy**: Suggest R&D tax credit opportunities and international transfer pricing considerations if scaling globally.
Format as a professional CFO report for the Board of Directors.
IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount.
`;
const aiResponse = await LocalAIApi.createResponse({
input: [{ role: 'user', content: prompt }]
});
return {
stewardship: LocalAIApi.extractText(aiResponse) || "Stewardship analysis unavailable."
};
}
static async getCashFlowRisk(organizationsId) {
const proprietary = await this.getProprietaryData(organizationsId);
const prompt = `
Act as a Risk Management & Cash Flow Expert (CFO role).
Company Financials:
- Monthly Burn: $${proprietary.burn}
- Runway: ${proprietary.runway} months
- Margin: ${proprietary.margin}%
Tasks:
1. **Liquidity Analysis**: Assess the safety of the current ${proprietary.runway}-month runway. When is the "Drop Dead Date"?
2. **Risk Identification**: Identify top 3 financial risks (e.g., Customer Concentration, Currency Risk, or Cost Overruns).
3. **Mitigation Strategy**: Suggest hedging or cost-cutting measures to extend runway by 25% without impacting core growth.
4. **Profitability Path**: Build a high-level roadmap to "Default Alive" status.
Be precise, data-driven, and conservative.
IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount.
`;
const aiResponse = await LocalAIApi.createResponse({
input: [{ role: 'user', content: prompt }]
});
return {
risk: LocalAIApi.extractText(aiResponse) || "Risk analysis unavailable."
};
}
static async getCapitalStrategy(organizationsId) {
const proprietary = await this.getProprietaryData(organizationsId);
const external = await this.getExternalIntelligence();
const prompt = `
Act as a CFO & Head of Capital Markets. Your goal is to structure capital and connect with funding.
Company State:
- ARR: $${proprietary.arr}
- Runway: ${proprietary.runway} months
Available Funding Partners (2026):
${JSON.stringify(external.funding_partners)}
Tasks:
1. **Funding Matchmaker**: Based on the company's profile, identify the top 3 best-fit partners from the list above. Explain WHY they are a match.
2. **Fundraising Strategy**: Structure a "Series A" or "Series B" (as appropriate) pitch focus. Should we lead with "Growth" or "Efficiency"?
3. **Capital Structure**: Advise on 'Venture Debt' vs 'Equity' mix to minimize dilution.
4. **Investor Relations**: Suggest a communication cadence and key KPIs to share with current stakeholders to build trust.
Provide actionable, high-conviction advice.
IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount.
`;
const aiResponse = await LocalAIApi.createResponse({
input: [{ role: 'user', content: prompt }]
});
return {
capital: LocalAIApi.extractText(aiResponse) || "Capital strategy unavailable."
};
}
static async getStrategicLeadership(organizationsId) {
const proprietary = await this.getProprietaryData(organizationsId);
const external = await this.getExternalIntelligence();
const prompt = `
Act as a Strategic CFO & Advisor to the Board.
Company Context:
- ARR: $${proprietary.arr}
- Margin: ${proprietary.margin}%
Market Trends:
${JSON.stringify(external.trends)}
Tasks:
1. **Digital Transformation**: How should the company leverage "Agentic AI" (Trend 1) to reduce OpEx and drive efficiency?
2. **Business Growth Strategy**: Identify "Blue Ocean" opportunities in the fintech/SaaS space for 2026.
3. **C-Suite Advisory**: Draft a 1-page memo for the CEO on "Navigating the 2026 Macro Environment."
Be visionary, bold, and strategic.
IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount.
`;
const aiResponse = await LocalAIApi.createResponse({
input: [{ role: 'user', content: prompt }]
});
return {
leadership: LocalAIApi.extractText(aiResponse) || "Strategic leadership analysis unavailable."
};
}
static async getESGStrategy(organizationsId) {
const proprietary = await this.getProprietaryData(organizationsId);
const external = await this.getExternalIntelligence();
const prompt = `
Act as a CFO specializing in ESG (Environmental, Social, Governance) Strategy.
Regulatory Context:
${external.regulatory_updates[1].title}: ${external.regulatory_updates[1].content}
Company Metrics:
- ARR: $${proprietary.arr}
Tasks:
1. **ESG Framework**: Propose a realistic ESG framework that adds value to valuation rather than just being a cost.
2. **Impact Reporting**: How can we quantify our "Social" impact for potential impact investors?
3. **Governance Audit**: Suggest improvements to board-level oversight for AI ethics and data privacy.
4. **Carbon Footprint Optimization**: For a SaaS company, suggest 3 high-impact areas to reduce carbon footprint.
Format as a professional CFO strategy paper.
IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount.
`;
const aiResponse = await LocalAIApi.createResponse({
input: [{ role: 'user', content: prompt }]
});
return {
esg: LocalAIApi.extractText(aiResponse) || "ESG strategy unavailable."
};
}
static async getFinancialModeling(organizationsId) {
const proprietary = await this.getProprietaryData(organizationsId);
const external = await this.getExternalIntelligence();
const prompt = `
Act as an elite Tier-1 Investment Banker. Build a sophisticated financial model for a company with the following metrics:
Proprietary Financials:
- ARR: $${proprietary.arr}
- Gross Margin: ${proprietary.margin}%
- Net Burn: $${proprietary.burn}/mo
- Historical Data Points: ${proprietary.history.length} months
Market Context (2026):
- Median SaaS Multiples: ${external.benchmarks.saas.valuation_multiple_median}x
- Top Decile Multiples: ${external.benchmarks.saas.valuation_multiple_top}x
Deliver a "Modeling Pack" including:
1. **Valuation Analysis**: Estimate Current Enterprise Value (EV) using Revenue Multiples (Base and Optimistic cases).
2. **3-Year Projection**: Forecast ARR and Burn based on current trends.
3. **DCF Insights**: Discuss required WACC and Terminal Value assumptions for an IPO exit in 36 months.
4. **Capital Efficiency**: Analyze the "Burn Multiplier" and suggest optimizations for 'Rule of 40' alignment.
Format as professional executive-level bullet points.
IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount.
`;
const aiResponse = await LocalAIApi.createResponse({
input: [{ role: 'user', content: prompt }]
});
return {
model: LocalAIApi.extractText(aiResponse) || "Modeling unavailable."
};
}
static async getMarketOpportunities(organizationsId) {
const proprietary = await this.getProprietaryData(organizationsId);
const external = await this.getExternalIntelligence();
const prompt = `
Act as a Lead M&A / Capital Markets Strategist. Analyze capitalization trends and strategic opportunities for this company.
Company State:
- ARR: $${proprietary.arr}
- Runway: ${proprietary.runway} months
- Margin: ${proprietary.margin}%
Capitalization Trends (2026):
${JSON.stringify(external.market_caps)}
Strategic Mandate:
1. **Capitalization Analysis**: Which market segments have the highest multiple expansion right now? How should the company position itself?
2. **Strategic M&A Opportunities**: Should the company be an "Acquirer" or "Target" based on its margin/runway? Suggest potential acquisition categories (e.g., vertical vs horizontal integration).
3. **Funding Strategy**: Evaluate readiness for a 'Down-round' vs 'Flat-round' vs 'Strategic Investment' in the current 2026 climate.
4. **Exit Planning**: Assess feasibility of IPO vs. Strategic Sale in the next 18-24 months.
Be aggressive, insightful, and use investment banking terminology.
IMPORTANT: This analysis is strictly for organizationsId ${organizationsId}. Data isolation is paramount.
`;
const aiResponse = await LocalAIApi.createResponse({
input: [{ role: 'user', content: prompt }]
});
return {
opportunities: LocalAIApi.extractText(aiResponse) || "Strategy insights unavailable."
};
}
static async getIntegratedAnalysis(organizationsId) {
const proprietary = await this.getProprietaryData(organizationsId);
const external = await this.getExternalIntelligence();
const prompt = `
Analyze this company's proprietary data against current 2026 financial market intelligence.
Proprietary Data:
- MRR: $${proprietary.mrr}
- Annual Recurring Revenue: $${proprietary.arr}
- Gross Margin: ${proprietary.margin}%
- Monthly Net Burn: $${proprietary.burn}
- Runway: ${proprietary.runway} months
External Intelligence (2026):
- SaaS Benchmark Gross Margin: ${external.benchmarks.saas.gross_margin_target}%
- Rule of 40 Target: 40% (Growth + Profitability)
- Current Trend: ${external.trends[0].title} - ${external.trends[0].content}
Provide a concise (3-4 bullet points) strategic analysis highlighting:
1. How the company compares to the SaaS Gross Margin benchmark.
2. Assessment of the Rule of 40 performance (Estimated).
3. Strategic advice based on the "Agentic AI" trend.
4. Risk assessment based on runway and market consolidation.
IMPORTANT: This analysis is strictly for the organization with ID ${organizationsId}.
Ensure data isolation in your reasoning.
`;
const aiResponse = await LocalAIApi.createResponse({
input: [{ role: 'user', content: prompt }]
});
return {
proprietary,
external,
analysis: LocalAIApi.extractText(aiResponse) || "Analysis pending..."
};
}
};

View File

@ -3,9 +3,10 @@ import { mdiLogout, mdiClose } from '@mdi/js'
import BaseIcon from './BaseIcon'
import AsideMenuList from './AsideMenuList'
import { MenuAsideItem } from '../interfaces'
import { useAppSelector, useAppDispatch } from '../stores/hooks'
import { useAppSelector } from '../stores/hooks'
import Link from 'next/link';
import { useAppDispatch } from '../stores/hooks';
import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';
@ -90,4 +91,4 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
</div>
</aside>
)
}
}

View File

@ -1,5 +1,6 @@
import React, {useEffect, useRef, useState} from 'react'
import React, {useEffect, useRef} from 'react'
import Link from 'next/link'
import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
@ -128,4 +129,4 @@ export default function NavBarItem({ item }: Props) {
}
return <div className={componentClass} ref={excludedRef}>{NavBarItemComponentContents}</div>
}
}

View File

@ -1,4 +1,5 @@
import React, { ReactNode, useEffect, useState } from 'react'
import React, { ReactNode, useEffect } from 'react'
import { useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
@ -125,4 +126,4 @@ export default function LayoutAuthenticated({
</div>
</div>
)
}
}

View File

@ -2,11 +2,6 @@ import * as icon from '@mdi/js';
import { MenuAsideItem } from './interfaces'
const menuAside: MenuAsideItem[] = [
{
href: '/advisor',
icon: icon.mdiRobotOutline,
label: 'AI Advisor',
},
{
href: '/dashboard',
icon: icon.mdiViewDashboardOutline,
@ -14,12 +9,60 @@ const menuAside: MenuAsideItem[] = [
},
{
href: '/transactions/transactions-list',
label: 'Transactions',
href: '/users/users-list',
label: 'Users',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiSwapHorizontal ?? icon.mdiTable,
permissions: 'READ_TRANSACTIONS'
icon: icon.mdiAccountGroup ?? icon.mdiTable,
permissions: 'READ_USERS'
},
{
href: '/roles/roles-list',
label: 'Roles',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
permissions: 'READ_ROLES'
},
{
href: '/permissions/permissions-list',
label: 'Permissions',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
permissions: 'READ_PERMISSIONS'
},
{
href: '/organizations/organizations-list',
label: 'Organizations',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ORGANIZATIONS'
},
{
href: '/workspaces/workspaces-list',
label: 'Workspaces',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiDomain' in icon ? icon['mdiDomain' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_WORKSPACES'
},
{
href: '/workspace_memberships/workspace_memberships-list',
label: 'Workspace memberships',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_WORKSPACE_MEMBERSHIPS'
},
{
href: '/data_connections/data_connections-list',
label: 'Data connections',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_DATA_CONNECTIONS'
},
{
href: '/financial_accounts/financial_accounts-list',
@ -30,64 +73,147 @@ const menuAside: MenuAsideItem[] = [
permissions: 'READ_FINANCIAL_ACCOUNTS'
},
{
href: '/metrics_snapshots/metrics_snapshots-list',
label: 'Financial Health',
href: '/vendors/vendors-list',
label: 'Vendors',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiGauge ?? icon.mdiTable,
icon: 'mdiStore' in icon ? icon['mdiStore' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_VENDORS'
},
{
href: '/customers/customers-list',
label: 'Customers',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiAccountTie' in icon ? icon['mdiAccountTie' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_CUSTOMERS'
},
{
href: '/transactions/transactions-list',
label: 'Transactions',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiSwapHorizontal' in icon ? icon['mdiSwapHorizontal' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_TRANSACTIONS'
},
{
href: '/budgets/budgets-list',
label: 'Budgets',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiPiggyBank' in icon ? icon['mdiPiggyBank' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_BUDGETS'
},
{
href: '/budget_lines/budget_lines-list',
label: 'Budget lines',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_BUDGET_LINES'
},
{
href: '/forecasts/forecasts-list',
label: 'Forecasts',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiChartTimelineVariant' in icon ? icon['mdiChartTimelineVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_FORECASTS'
},
{
href: '/forecast_scenarios/forecast_scenarios-list',
label: 'Forecast scenarios',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiLayersTriple' in icon ? icon['mdiLayersTriple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_FORECAST_SCENARIOS'
},
{
href: '/metrics_snapshots/metrics_snapshots-list',
label: 'Metrics snapshots',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiGauge' in icon ? icon['mdiGauge' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_METRICS_SNAPSHOTS'
},
{
href: '/alerts/alerts-list',
label: 'Alerts & Anomalies',
href: '/anomalies/anomalies-list',
label: 'Anomalies',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiBellAlert ?? icon.mdiTable,
icon: 'mdiAlertOctagon' in icon ? icon['mdiAlertOctagon' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ANOMALIES'
},
{
href: '/alerts/alerts-list',
label: 'Alerts',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiBellAlert' in icon ? icon['mdiBellAlert' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ALERTS'
},
{
label: 'Settings',
icon: icon.mdiCogOutline,
menu: [
{
href: '/users/users-list',
label: 'Users',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiAccountGroup ?? icon.mdiTable,
permissions: 'READ_USERS'
},
{
href: '/roles/roles-list',
label: 'Roles',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable,
permissions: 'READ_ROLES'
},
{
href: '/data_connections/data_connections-list',
label: 'Data connections',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiLinkVariant' in icon ? icon['mdiLinkVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_DATA_CONNECTIONS'
},
{
href: '/sync_jobs/sync_jobs-list',
label: 'Sync jobs',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiSync' in icon ? icon['mdiSync' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_SYNC_JOBS'
},
]
href: '/recommendations/recommendations-list',
label: 'Recommendations',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiLightbulbOn' in icon ? icon['mdiLightbulbOn' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_RECOMMENDATIONS'
},
{
href: '/chat_threads/chat_threads-list',
label: 'Chat threads',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiChatProcessing' in icon ? icon['mdiChatProcessing' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_CHAT_THREADS'
},
{
href: '/chat_messages/chat_messages-list',
label: 'Chat messages',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiMessageText' in icon ? icon['mdiMessageText' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_CHAT_MESSAGES'
},
{
href: '/message_citations/message_citations-list',
label: 'Message citations',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFileDocumentMultiple' in icon ? icon['mdiFileDocumentMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_MESSAGE_CITATIONS'
},
{
href: '/files_library/files_library-list',
label: 'Files library',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiFileUpload' in icon ? icon['mdiFileUpload' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_FILES_LIBRARY'
},
{
href: '/sync_jobs/sync_jobs-list',
label: 'Sync jobs',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
icon: 'mdiSync' in icon ? icon['mdiSync' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_SYNC_JOBS'
},
{
href: '/profile',
label: 'Profile',
icon: icon.mdiAccountCircle,
},
{
href: '/api-docs',
target: '_blank',
label: 'Swagger API',
icon: icon.mdiFileCode,
permissions: 'READ_API_DOCS'
},
]
export default menuAside
export default menuAside

View File

@ -1,771 +0,0 @@
import * as icon from '@mdi/js';
import Head from 'next/head';
import React, { ReactElement, useEffect, useState, useMemo } from 'react';
import CardBox from '../components/CardBox';
import LayoutAuthenticated from '../layouts/Authenticated';
import SectionMain from '../components/SectionMain';
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
import { getPageTitle } from '../config';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { fetch as fetchMetrics } from '../stores/metrics_snapshots/metrics_snapshotsSlice';
import { fetch as fetchAlerts } from '../stores/alerts/alertsSlice';
import { aiResponse } from '../stores/openAiSlice';
import BaseIcon from '../components/BaseIcon';
import BaseButton from '../components/BaseButton';
import { useTranslation } from 'next-i18next';
import ChartLineSample from '../components/ChartLineSample';
import axios from 'axios';
const AdvisorPage = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation('common');
const { metrics_snapshots } = useAppSelector((state) => state.metrics_snapshots);
const { alerts } = useAppSelector((state) => state.alerts);
const { aiResponse: chatResponse, isAskingResponse } = useAppSelector((state) => state.openAi);
const [chatInput, setChatInput] = useState('');
const [messages, setMessages] = useState<Array<{ role: string; content: string }>>([
{ role: 'assistant', content: 'Hello! I am your AI Virtual CFO & Investment Banker. I have integrated 2026 market intelligence, funding opportunities, and capitalization trends into your hub. How can I help you optimize your stewardship, risk, or capital strategy today?' }
]);
const [activeTab, setActiveTab] = useState<'company' | 'competitors' | 'intelligence'>('company');
const [intelligence, setIntelligence] = useState<any>(null);
const [modeling, setModeling] = useState<any>(null);
const [opportunities, setOpportunities] = useState<any>(null);
const [stewardship, setStewardship] = useState<any>(null);
const [risk, setRisk] = useState<any>(null);
const [capital, setCapital] = useState<any>(null);
const [leadership, setLeadership] = useState<any>(null);
const [esg, setEsg] = useState<any>(null);
const [loadingIntelligence, setLoadingIntelligence] = useState(false);
const [loadingModeling, setLoadingModeling] = useState(false);
const [loadingOpportunities, setLoadingOpportunities] = useState(false);
const [loadingStewardship, setLoadingStewardship] = useState(false);
const [loadingRisk, setLoadingRisk] = useState(false);
const [loadingCapital, setLoadingCapital] = useState(false);
const [loadingLeadership, setLoadingLeadership] = useState(false);
const [loadingEsg, setLoadingEsg] = useState(false);
const [intelSubTab, setIntelSubTab] = useState<'market' | 'modeling' | 'strategy' | 'stewardship' | 'risk' | 'capital' | 'leadership' | 'esg'>('market');
useEffect(() => {
dispatch(fetchMetrics({ query: '?limit=12&sort=as_of_at:ASC' }));
dispatch(fetchAlerts({ query: '?limit=5' }));
fetchIntelligence();
}, [dispatch]);
useEffect(() => {
if (activeTab === 'intelligence') {
if (intelSubTab === 'market' && !intelligence) fetchIntelligence();
if (intelSubTab === 'modeling' && !modeling) fetchModeling();
if (intelSubTab === 'strategy' && !opportunities) fetchOpportunities();
if (intelSubTab === 'stewardship' && !stewardship) fetchStewardship();
if (intelSubTab === 'risk' && !risk) fetchRisk();
if (intelSubTab === 'capital' && !capital) fetchCapital();
if (intelSubTab === 'leadership' && !leadership) fetchLeadership();
if (intelSubTab === 'esg' && !esg) fetchEsg();
}
}, [activeTab, intelSubTab]);
const fetchIntelligence = async () => {
setLoadingIntelligence(true);
try {
const response = await axios.get('/intelligence');
setIntelligence(response.data);
} catch (error) {
console.error('Failed to fetch intelligence:', error);
} finally {
setLoadingIntelligence(false);
}
};
const fetchModeling = async () => {
setLoadingModeling(true);
try {
const response = await axios.get('/intelligence/modeling');
setModeling(response.data);
} catch (error) {
console.error('Failed to fetch modeling:', error);
} finally {
setLoadingModeling(false);
}
};
const fetchOpportunities = async () => {
setLoadingOpportunities(true);
try {
const response = await axios.get('/intelligence/opportunities');
setOpportunities(response.data);
} catch (error) {
console.error('Failed to fetch opportunities:', error);
} finally {
setLoadingOpportunities(false);
}
};
const fetchStewardship = async () => {
setLoadingStewardship(true);
try {
const response = await axios.get('/intelligence/stewardship');
setStewardship(response.data);
} catch (error) {
console.error('Failed to fetch stewardship:', error);
} finally {
setLoadingStewardship(false);
}
};
const fetchRisk = async () => {
setLoadingRisk(true);
try {
const response = await axios.get('/intelligence/risk');
setRisk(response.data);
} catch (error) {
console.error('Failed to fetch risk:', error);
} finally {
setLoadingRisk(false);
}
};
const fetchCapital = async () => {
setLoadingCapital(true);
try {
const response = await axios.get('/intelligence/capital');
setCapital(response.data);
} catch (error) {
console.error('Failed to fetch capital:', error);
} finally {
setLoadingCapital(false);
}
};
const fetchLeadership = async () => {
setLoadingLeadership(true);
try {
const response = await axios.get('/intelligence/leadership');
setLeadership(response.data);
} catch (error) {
console.error('Failed to fetch leadership:', error);
} finally {
setLoadingLeadership(false);
}
};
const fetchEsg = async () => {
setLoadingEsg(true);
try {
const response = await axios.get('/intelligence/esg');
setEsg(response.data);
} catch (error) {
console.error('Failed to fetch esg:', error);
} finally {
setLoadingEsg(false);
}
};
useEffect(() => {
if (chatResponse && chatResponse.success) {
const outputText = chatResponse.data?.output?.[0]?.content?.[0]?.text;
if (outputText) {
setMessages((prev) => [...prev, { role: 'assistant', content: outputText }]);
}
}
}, [chatResponse]);
const handleSendMessage = () => {
if (!chatInput.trim()) return;
const newMessages = [...messages, { role: 'user', content: chatInput }];
setMessages(newMessages);
setChatInput('');
dispatch(aiResponse({
input: newMessages.map(m => ({ role: m.role, content: m.content })),
options: { poll_interval: 2, poll_timeout: 60 }
}));
};
const latestMetrics = useMemo(() => metrics_snapshots?.[metrics_snapshots.length - 1] || {}, [metrics_snapshots]);
const chartData = useMemo(() => {
const labels = metrics_snapshots.map((m: any) => new Date(m.as_of_at).toLocaleDateString(undefined, { month: 'short', year: '2-digit' }));
const mrrData = metrics_snapshots.map((m: any) => Number(m.mrr || 0));
const burnData = metrics_snapshots.map((m: any) => Number(m.net_burn_monthly || 0));
return {
labels,
datasets: [
{
label: 'MRR',
fill: false,
borderColor: '#10b981', // Emerald 500
borderWidth: 3,
data: mrrData,
tension: 0.4,
},
{
label: 'Net Burn',
fill: false,
borderColor: '#3b82f6', // Blue 500
borderWidth: 3,
data: burnData,
tension: 0.4,
}
],
};
}, [metrics_snapshots]);
const competitorData = useMemo(() => {
const labels = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'];
const industryMrr = [50000, 55000, 58000, 65000, 72000, 80000];
const ourMrr = [20000, 25000, 32000, 38000, 45000, 52000];
return {
labels,
datasets: [
{
label: 'Industry Benchmark (Finance)',
borderColor: '#94a3b8', // Slate 400
borderDash: [5, 5],
data: industryMrr,
tension: 0.4,
},
{
label: 'Our Company',
borderColor: '#10b981', // Emerald 500
data: ourMrr,
tension: 0.4,
}
],
};
}, []);
return (
<>
<Head>
<title>{getPageTitle('AI Virtual CFO Hub')}</title>
</Head>
<SectionMain>
<SectionTitleLineWithButton icon={icon.mdiRobotOutline} title="AI Virtual CFO & Advisor" main>
<div className="flex gap-2">
<BaseButton
label="Overview"
color={activeTab === 'company' ? 'info' : 'white'}
onClick={() => setActiveTab('company')}
small
/>
<BaseButton
label="Competitors"
color={activeTab === 'competitors' ? 'info' : 'white'}
onClick={() => setActiveTab('competitors')}
small
/>
<BaseButton
label="Intelligence Hub"
color={activeTab === 'intelligence' ? 'info' : 'white'}
onClick={() => setActiveTab('intelligence')}
icon={icon.mdiNewspaperVariantOutline}
small
/>
</div>
</SectionTitleLineWithButton>
{/* Top Summary Row */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6">
<CardBox className="border-l-4 border-emerald-500">
<p className="text-gray-500 text-xs uppercase font-bold tracking-wider">Monthly Revenue</p>
<h3 className="text-2xl font-bold mt-1">${Number(latestMetrics.mrr || 0).toLocaleString()}</h3>
<div className="flex items-center text-xs text-emerald-600 font-semibold mt-1">
<BaseIcon path={icon.mdiTrendingUp} size={14} className="mr-1" /> +12.5% vs last month
</div>
</CardBox>
<CardBox className="border-l-4 border-blue-500">
<p className="text-gray-500 text-xs uppercase font-bold tracking-wider">Gross Margin</p>
<h3 className="text-2xl font-bold mt-1">{latestMetrics.gross_margin_percent || 0}%</h3>
<div className="flex items-center text-xs text-gray-400 mt-1">
Industry Avg: 75% (SaaS 2026)
</div>
</CardBox>
<CardBox className="border-l-4 border-orange-500">
<p className="text-gray-500 text-xs uppercase font-bold tracking-wider">Cash Runway</p>
<h3 className="text-2xl font-bold mt-1">{latestMetrics.runway_months || 'N/A'} Months</h3>
<div className="flex items-center text-xs text-orange-600 font-semibold mt-1">
<BaseIcon path={icon.mdiAlertCircleOutline} size={14} className="mr-1" /> Market shifting to profitability
</div>
</CardBox>
<CardBox className="border-l-4 border-purple-500">
<p className="text-gray-500 text-xs uppercase font-bold tracking-wider">Burn Multiplier</p>
<h3 className="text-2xl font-bold mt-1">1.2x</h3>
<div className="flex items-center text-xs text-blue-600 font-semibold mt-1">
Target: &lt; 1.5x
</div>
</CardBox>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main Visuals Column */}
<div className="lg:col-span-2 space-y-6">
{activeTab === 'company' && (
<CardBox>
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold">Financial Growth Trend</h2>
<div className="flex gap-4 text-sm">
<span className="flex items-center"><span className="w-3 h-3 bg-emerald-500 rounded-full mr-2"></span> MRR</span>
<span className="flex items-center"><span className="w-3 h-3 bg-blue-500 rounded-full mr-2"></span> Net Burn</span>
</div>
</div>
<div className="h-80">
<ChartLineSample data={chartData} />
</div>
</CardBox>
)}
{activeTab === 'competitors' && (
<CardBox>
<div className="flex items-center justify-between mb-6">
<h2 className="text-xl font-bold">Industry Benchmark Comparison</h2>
<p className="text-sm text-gray-500 italic">Sector: Finance SaaS</p>
</div>
<div className="h-80">
<ChartLineSample data={competitorData} />
</div>
<div className="mt-6 grid grid-cols-2 gap-4">
<div className="p-4 bg-gray-50 dark:bg-dark-800 rounded-lg text-center">
<p className="text-sm text-gray-500">Our Growth Rank</p>
<p className="text-2xl font-bold text-emerald-600">Top 25%</p>
</div>
<div className="p-4 bg-gray-50 dark:bg-dark-800 rounded-lg text-center">
<p className="text-sm text-gray-500">Efficiency Score</p>
<p className="text-2xl font-bold text-blue-600">88/100</p>
</div>
</div>
</CardBox>
)}
{activeTab === 'intelligence' && (
<div className="space-y-6">
<CardBox>
<div className="flex flex-col md:flex-row md:items-center justify-between mb-4 pb-4 border-b gap-4">
<div className="flex items-center">
<BaseIcon path={icon.mdiFinance} className="mr-2 text-blue-500" />
<h2 className="text-xl font-bold text-blue-600 dark:text-blue-400">CFO Intelligence Hub</h2>
</div>
</div>
<div className="flex flex-wrap gap-1 bg-gray-100 dark:bg-dark-900 p-1 rounded-lg mb-6">
<BaseButton
label="Market News"
color={intelSubTab === 'market' ? 'info' : 'white'}
onClick={() => setIntelSubTab('market')}
className="border-none shadow-none"
small
/>
<BaseButton
label="Stewardship"
color={intelSubTab === 'stewardship' ? 'info' : 'white'}
onClick={() => setIntelSubTab('stewardship')}
className="border-none shadow-none"
small
/>
<BaseButton
label="Risk"
color={intelSubTab === 'risk' ? 'info' : 'white'}
onClick={() => setIntelSubTab('risk')}
className="border-none shadow-none"
small
/>
<BaseButton
label="Capital"
color={intelSubTab === 'capital' ? 'info' : 'white'}
onClick={() => setIntelSubTab('capital')}
className="border-none shadow-none"
small
/>
<BaseButton
label="Modeling"
color={intelSubTab === 'modeling' ? 'info' : 'white'}
onClick={() => setIntelSubTab('modeling')}
className="border-none shadow-none"
small
/>
<BaseButton
label="Strategy"
color={intelSubTab === 'strategy' ? 'info' : 'white'}
onClick={() => setIntelSubTab('strategy')}
className="border-none shadow-none"
small
/>
<BaseButton
label="Leadership"
color={intelSubTab === 'leadership' ? 'info' : 'white'}
onClick={() => setIntelSubTab('leadership')}
className="border-none shadow-none"
small
/>
<BaseButton
label="ESG"
color={intelSubTab === 'esg' ? 'info' : 'white'}
onClick={() => setIntelSubTab('esg')}
className="border-none shadow-none"
small
/>
</div>
{intelSubTab === 'market' && intelligence && (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<h3 className="font-bold text-sm uppercase text-gray-400 tracking-widest mb-2">Latest Trends</h3>
{intelligence.external.trends.map((trend: any, idx: number) => (
<div key={idx} className="p-4 bg-gray-50 dark:bg-dark-800 rounded-xl border border-gray-100 dark:border-dark-700">
<div className="flex justify-between items-start mb-1">
<h4 className="font-bold text-blue-500 text-sm">{trend.title}</h4>
<span className={`text-[10px] px-2 py-0.5 rounded font-bold ${trend.impact === 'High' ? 'bg-red-100 text-red-600' : 'bg-blue-100 text-blue-600'}`}>{trend.impact} Impact</span>
</div>
<p className="text-xs text-gray-600 dark:text-gray-400 leading-relaxed">{trend.content}</p>
</div>
))}
</div>
<div className="space-y-4">
<h3 className="font-bold text-sm uppercase text-gray-400 tracking-widest mb-2">AI Strategic Analysis</h3>
<div className="p-5 bg-blue-600 text-white rounded-2xl shadow-lg relative overflow-hidden">
<BaseIcon path={icon.mdiRobot} size={48} className="absolute -right-4 -bottom-4 opacity-10" />
<div className="relative z-10 text-sm leading-relaxed whitespace-pre-wrap italic">
{intelligence.analysis}
</div>
</div>
</div>
</div>
)}
{intelSubTab === 'stewardship' && (
<div className="space-y-4">
{loadingStewardship ? (
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
) : stewardship ? (
<div className="p-6 bg-slate-50 dark:bg-slate-900/10 rounded-2xl border border-slate-100 dark:border-slate-800">
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiShieldCheckOutline} className="text-slate-500 mr-2" />
<h3 className="font-bold text-lg text-slate-800 dark:text-slate-400">Financial Stewardship & Compliance</h3>
</div>
<div className="text-sm leading-relaxed whitespace-pre-wrap text-slate-900 dark:text-slate-200">
{stewardship.stewardship}
</div>
</div>
) : (
<div className="text-center p-12 text-gray-500">Stewardship report unavailable.</div>
)}
</div>
)}
{intelSubTab === 'risk' && (
<div className="space-y-4">
{loadingRisk ? (
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
) : risk ? (
<div className="p-6 bg-red-50 dark:bg-red-900/10 rounded-2xl border border-red-100 dark:border-red-800">
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiShieldAlertOutline} className="text-red-500 mr-2" />
<h3 className="font-bold text-lg text-red-800 dark:text-red-400">Cash Flow & Risk Management</h3>
</div>
<div className="text-sm leading-relaxed whitespace-pre-wrap text-red-900 dark:text-red-200">
{risk.risk}
</div>
</div>
) : (
<div className="text-center p-12 text-gray-500">Risk analysis unavailable.</div>
)}
</div>
)}
{intelSubTab === 'capital' && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="space-y-4">
{loadingCapital ? (
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
) : capital ? (
<div className="p-6 bg-blue-50 dark:bg-blue-900/10 rounded-2xl border border-blue-100 dark:border-blue-800 h-full">
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiAccountGroup} className="text-blue-500 mr-2" />
<h3 className="font-bold text-lg text-blue-800 dark:text-blue-400">Capital Management Strategy</h3>
</div>
<div className="text-sm leading-relaxed whitespace-pre-wrap text-blue-900 dark:text-blue-200">
{capital.capital}
</div>
</div>
) : (
<div className="text-center p-12 text-gray-500">Capital strategy unavailable.</div>
)}
</div>
<div className="space-y-4">
<h3 className="font-bold text-sm uppercase text-gray-400 tracking-widest mb-2">Funding Opportunities</h3>
<div className="grid grid-cols-1 gap-4">
{intelligence?.external?.funding_partners?.map((partner: any, idx: number) => (
<div key={idx} className="p-4 border rounded-xl hover:border-blue-500 transition-colors cursor-pointer group" onClick={() => {
setChatInput(`Draft an outreach email for ${partner.name} focusing on our MRR of $${Number(latestMetrics.mrr || 0).toLocaleString()}`);
}}>
<div className="flex justify-between items-center mb-1">
<h4 className="font-bold text-sm group-hover:text-blue-600">{partner.name}</h4>
<span className="text-[10px] px-2 py-0.5 bg-gray-100 rounded font-bold text-gray-500">{partner.contact_type}</span>
</div>
<div className="flex flex-wrap gap-2 mt-2">
<span className="text-[10px] text-blue-600 bg-blue-50 px-2 py-0.5 rounded italic">{partner.focus}</span>
<span className="text-[10px] text-gray-500 bg-gray-50 px-2 py-0.5 rounded">{partner.stage}</span>
</div>
</div>
))}
</div>
<BaseButton
label="Match me with more investors"
icon={icon.mdiHandshakeOutline}
color="info"
className="w-full"
onClick={() => {
setChatInput("Search for VC and Angel investors specialized in Fintech and SaaS for a Seed/Series A round in 2026.");
handleSendMessage();
}}
/>
</div>
</div>
)}
{intelSubTab === 'modeling' && (
<div className="space-y-4">
{loadingModeling ? (
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
) : modeling ? (
<div className="p-6 bg-emerald-50 dark:bg-emerald-900/10 rounded-2xl border border-emerald-100 dark:border-emerald-800">
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiChartBoxOutline} className="text-emerald-500 mr-2" />
<h3 className="font-bold text-lg text-emerald-800 dark:text-emerald-400">Investment Banking Modeling Pack</h3>
</div>
<div className="text-sm leading-relaxed whitespace-pre-wrap text-emerald-900 dark:text-emerald-200">
{modeling.model}
</div>
</div>
) : (
<div className="text-center p-12 text-gray-500">Modeling pack unavailable.</div>
)}
</div>
)}
{intelSubTab === 'strategy' && (
<div className="space-y-4">
{loadingOpportunities ? (
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
) : opportunities ? (
<div className="p-6 bg-purple-50 dark:bg-purple-900/10 rounded-2xl border border-purple-100 dark:border-purple-800">
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiTrophyOutline} className="text-purple-500 mr-2" />
<h3 className="font-bold text-lg text-purple-800 dark:text-purple-400">Capitalization & M&A Strategy</h3>
</div>
<div className="text-sm leading-relaxed whitespace-pre-wrap text-purple-900 dark:text-purple-200">
{opportunities.opportunities}
</div>
</div>
) : (
<div className="text-center p-12 text-gray-500">Strategy report unavailable.</div>
)}
</div>
)}
{intelSubTab === 'leadership' && (
<div className="space-y-4">
{loadingLeadership ? (
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
) : leadership ? (
<div className="p-6 bg-amber-50 dark:bg-amber-900/10 rounded-2xl border border-amber-100 dark:border-amber-800">
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiAccountStarOutline} className="text-amber-500 mr-2" />
<h3 className="font-bold text-lg text-amber-800 dark:text-amber-400">Strategic Leadership & Board Advisory</h3>
</div>
<div className="text-sm leading-relaxed whitespace-pre-wrap text-amber-900 dark:text-amber-200">
{leadership.leadership}
</div>
</div>
) : (
<div className="text-center p-12 text-gray-500">Leadership advisory unavailable.</div>
)}
</div>
)}
{intelSubTab === 'esg' && (
<div className="space-y-4">
{loadingEsg ? (
<div className="flex justify-center p-12"><BaseIcon path={icon.mdiLoading} className="animate-spin text-blue-500" size={48} /></div>
) : esg ? (
<div className="p-6 bg-green-50 dark:bg-green-900/10 rounded-2xl border border-green-100 dark:border-green-800">
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiLeaf} className="text-green-500 mr-2" />
<h3 className="font-bold text-lg text-green-800 dark:text-green-400">ESG Strategy & Impact Reporting</h3>
</div>
<div className="text-sm leading-relaxed whitespace-pre-wrap text-green-900 dark:text-green-200">
{esg.esg}
</div>
</div>
) : (
<div className="text-center p-12 text-gray-500">ESG strategy unavailable.</div>
)}
</div>
)}
</CardBox>
</div>
)}
{/* AI Advisor Chat (Unified for all tabs) */}
<CardBox className="flex flex-col h-[450px]">
<div className="flex items-center mb-4 pb-4 border-b">
<BaseIcon path={icon.mdiChatProcessing} className="mr-2 text-blue-500" />
<h2 className="text-lg font-bold">Ask Your AI CFO</h2>
</div>
<div className="flex-grow overflow-y-auto space-y-4 mb-4 p-2 scrollbar-thin scrollbar-thumb-gray-200">
{messages.map((m, i) => (
<div key={i} className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[85%] p-4 rounded-2xl shadow-sm ${
m.role === 'user'
? 'bg-blue-600 text-white rounded-tr-none'
: 'bg-white dark:bg-dark-800 border border-gray-100 dark:border-dark-700 text-gray-800 dark:text-gray-200 rounded-tl-none'
}`}>
<p className="text-sm leading-relaxed whitespace-pre-wrap">{m.content}</p>
</div>
</div>
))}
{isAskingResponse && (
<div className="flex justify-start">
<div className="bg-gray-100 dark:bg-dark-800 p-4 rounded-2xl rounded-tl-none animate-pulse">
<div className="flex gap-1">
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:0.2s]"></div>
<div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce [animation-delay:0.4s]"></div>
</div>
</div>
</div>
)}
</div>
<div className="mt-auto pt-4 flex gap-2">
<input
type="text"
className="flex-grow bg-gray-100 dark:bg-dark-900 border-none rounded-full px-5 py-3 text-sm focus:ring-2 focus:ring-blue-500"
placeholder="How can we optimize our R&D tax credits?"
value={chatInput}
onChange={(e) => setChatInput(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
/>
<BaseButton
icon={icon.mdiSend}
color="info"
roundedFull
onClick={handleSendMessage}
disabled={isAskingResponse || !chatInput.trim()}
/>
</div>
</CardBox>
</div>
{/* Side Column: Alerts & Insights */}
<div className="lg:col-span-1 space-y-6">
<CardBox isHoverable>
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiLightningBolt} className="mr-2 text-yellow-500" />
<h2 className="text-lg font-bold">CFO Strategic Insights</h2>
</div>
<div className="space-y-4">
{alerts.length === 0 ? (
<p className="text-gray-500 italic text-sm">Scanning for insights...</p>
) : (
alerts.slice(0, 3).map((alert: any) => (
<div key={alert.id} className="group cursor-pointer" onClick={() => {
const prompt = `Act as a CFO: Explain the financial impact of this alert: ${alert.title || alert.description}`;
setChatInput(prompt);
}}>
<div className="flex items-center justify-between mb-1">
<span className="text-[10px] uppercase font-bold text-orange-500 bg-orange-50 px-2 py-0.5 rounded">Action Required</span>
<span className="text-[10px] text-gray-400">{new Date(alert.createdAt).toLocaleDateString()}</span>
</div>
<p className="text-sm font-bold group-hover:text-blue-500 transition-colors">{alert.title || alert.type}</p>
<p className="text-xs text-gray-500 line-clamp-2 mt-1">{alert.description || alert.message}</p>
</div>
))
)}
</div>
</CardBox>
<CardBox isHoverable>
<div className="flex items-center mb-4">
<BaseIcon path={icon.mdiShieldCheckOutline} className="mr-2 text-blue-500" />
<h2 className="text-lg font-bold">Compliance Status</h2>
</div>
<div className="space-y-4">
<div className="flex justify-between items-center text-sm">
<span className="text-gray-500">Audit Readiness</span>
<span className="font-bold text-emerald-600">85%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5">
<div className="bg-emerald-500 h-1.5 rounded-full w-[85%]"></div>
</div>
<div className="flex justify-between items-center text-sm">
<span className="text-gray-500">Tax Compliance</span>
<span className={`font-bold text-blue-600`}>100%</span>
</div>
<div className="w-full bg-gray-100 rounded-full h-1.5">
<div className={`h-1.5 rounded-full bg-blue-500`} style={{ width: `100%` }}></div>
</div>
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg text-xs text-blue-700 dark:text-blue-300">
<strong>CFO Note:</strong> You are currently aligned with 2026 SEC AI Disclosure requirements. Next audit scheduled for Q3.
</div>
</div>
</CardBox>
<CardBox>
<h2 className="text-lg font-bold mb-4">CFO Strategy Actions</h2>
<div className="space-y-3">
<BaseButton
label="Analyze Risk"
icon={icon.mdiShieldAlertOutline}
className="w-full text-left"
onClick={() => { setActiveTab('intelligence'); setIntelSubTab('risk'); }}
small
/>
<BaseButton
label="Fundraising Match"
icon={icon.mdiAccountGroup}
className="w-full text-left"
onClick={() => { setActiveTab('intelligence'); setIntelSubTab('capital'); }}
small
/>
<BaseButton
label="ESG Strategy"
icon={icon.mdiLeafOutline}
className="w-full text-left"
onClick={() => { setActiveTab('intelligence'); setIntelSubTab('esg'); }}
small
/>
<BaseButton
label="Audit Checklist"
icon={icon.mdiClipboardCheckOutline}
className="w-full text-left"
onClick={() => { setActiveTab('intelligence'); setIntelSubTab('stewardship'); }}
small
/>
</div>
</CardBox>
</div>
</div>
</SectionMain>
</>
);
};
AdvisorPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};
export default AdvisorPage;

View File

@ -13,114 +13,149 @@ import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
import * as icon from '@mdi/js';
import BaseIcon from '../components/BaseIcon';
export default function Starter() {
const [illustrationImage, setIllustrationImage] = useState({
src: undefined,
photographer: undefined,
photographer_url: undefined,
})
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
const [contentType, setContentType] = useState('video');
const [contentPosition, setContentPosition] = useState('right');
const textColor = useAppSelector((state) => state.style.linkColor);
const title = 'CFO.ai'
// Fetch Pexels image/video
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
const video = await getPexelsVideo();
setIllustrationImage(image);
setIllustrationVideo(video);
}
fetchData();
}, []);
const imageBlock = (image) => (
<div
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
style={{
backgroundImage: `${
image
? `url(${image?.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}}
>
<div className='flex justify-center w-full bg-blue-300/20'>
<a
className='text-[8px]'
href={image?.photographer_url}
target='_blank'
rel='noreferrer'
>
Photo by {image?.photographer} on Pexels
</a>
</div>
</div>
);
const videoBlock = (video) => {
if (video?.video_files?.length > 0) {
return (
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
<video
className='absolute top-0 left-0 w-full h-full object-cover'
autoPlay
loop
muted
>
<source src={video?.video_files[0]?.link} type='video/mp4'/>
Your browser does not support the video tag.
</video>
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
<a
className='text-[8px]'
href={video?.user?.url}
target='_blank'
rel='noreferrer'
>
Video by {video.user.name} on Pexels
</a>
</div>
</div>)
}
};
return (
<div className="bg-white dark:bg-dark-900 text-gray-900 dark:text-gray-100 min-h-screen font-sans">
<div
style={
contentPosition === 'background'
? {
backgroundImage: `${
illustrationImage
? `url(${illustrationImage.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}
: {}
}
>
<Head>
<title>{getPageTitle('Intelligent Financial Advisor for SMBs')}</title>
<title>{getPageTitle('Starter Page')}</title>
</Head>
{/* Navigation Header */}
<header className="flex items-center justify-between px-6 py-4 border-b border-gray-100 dark:border-dark-800">
<div className="flex items-center space-x-2">
<div className="bg-blue-600 p-2 rounded-lg">
<BaseIcon path={icon.mdiRobotOutline} size={24} className="text-white" />
</div>
<span className="text-xl font-bold tracking-tight">CFO.ai</span>
</div>
<nav className="hidden md:flex space-x-8 text-sm font-medium">
<a href="#features" className="hover:text-blue-600 transition-colors">Features</a>
<a href="#about" className="hover:text-blue-600 transition-colors">About</a>
</nav>
<div className="flex items-center space-x-4">
<Link href="/login" className="text-sm font-semibold hover:text-blue-600">Log in</Link>
<BaseButton href="/register" label="Get Started" color="info" roundedFull className="px-6" />
</div>
</header>
{/* Hero Section */}
<section className="py-20 px-6 max-w-7xl mx-auto text-center">
<div className="inline-block px-4 py-1.5 mb-6 text-sm font-semibold tracking-wide text-blue-600 uppercase bg-blue-50 rounded-full dark:bg-blue-900/20 dark:text-blue-400">
Meet your new Financial Partner
</div>
<h1 className="text-5xl md:text-7xl font-extrabold mb-8 tracking-tight">
The AI CFO for <span className="text-blue-600">SMBs.</span>
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400 max-w-2xl mx-auto mb-12 leading-relaxed">
CFO.ai interprets your financial data, detects abnormalities, and advises you like a seasoned executive. Stop guessing, start growing.
</p>
<div className="flex flex-col md:flex-row justify-center items-center space-y-4 md:space-y-0 md:space-x-6">
<BaseButton href="/advisor" label="Open Advisor Dashboard" color="info" roundedFull className="px-10 py-4 text-lg" />
<Link href="#features" className="text-lg font-semibold flex items-center group hover:text-blue-600 transition-colors">
How it works <BaseIcon path={icon.mdiArrowRight} size={20} className="ml-2 transform group-hover:translate-x-1 transition-transform" />
</Link>
</div>
<SectionFullScreen bg='violet'>
<div
className={`flex ${
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
} min-h-screen w-full`}
>
{contentType === 'image' && contentPosition !== 'background'
? imageBlock(illustrationImage)
: null}
{contentType === 'video' && contentPosition !== 'background'
? videoBlock(illustrationVideo)
: null}
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<CardBoxComponentTitle title="Welcome to your CFO.ai app!"/>
{/* Mock Dashboard Preview */}
<div className="mt-20 relative rounded-2xl overflow-hidden border border-gray-200 dark:border-dark-700 shadow-2xl max-w-5xl mx-auto">
<div className="bg-gray-50 dark:bg-dark-800 p-4 border-b border-gray-200 dark:border-dark-700 flex space-x-2">
<div className="w-3 h-3 rounded-full bg-red-400"></div>
<div className="w-3 h-3 rounded-full bg-yellow-400"></div>
<div className="w-3 h-3 rounded-full bg-green-400"></div>
<div className="space-y-3">
<p className='text-center text-gray-500'>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
<p className='text-center text-gray-500'>For guides and documentation please check
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
</div>
<div className="bg-white dark:bg-dark-900 p-8 h-[400px] flex items-center justify-center text-gray-400 italic">
<div className="text-center">
<BaseIcon path={icon.mdiChartTimelineVariant} size={64} className="mx-auto mb-4 opacity-20" />
<p>Real-time Financial Insights & AI Advisory</p>
</div>
</div>
</div>
</section>
<BaseButtons>
<BaseButton
href='/login'
label='Login'
color='info'
className='w-full'
/>
{/* Features Grid */}
<section id="features" className="py-20 bg-gray-50 dark:bg-dark-800/50">
<div className="max-w-7xl mx-auto px-6">
<div className="text-center mb-16">
<h2 className="text-3xl font-bold mb-4">Everything you need from a CFO</h2>
<p className="text-gray-600 dark:text-gray-400">Automated, intelligent, and available 24/7.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-12">
<div className="p-8 bg-white dark:bg-dark-900 rounded-2xl shadow-sm border border-gray-100 dark:border-dark-700">
<div className="bg-blue-100 dark:bg-blue-900/20 p-3 rounded-xl inline-block mb-6 text-blue-600">
<BaseIcon path={icon.mdiGauge} size={32} />
</div>
<h3 className="text-xl font-bold mb-3">Real-time Runway</h3>
<p className="text-gray-600 dark:text-gray-400">Always know exactly how many months of cash you have left based on actual spending.</p>
</div>
<div className="p-8 bg-white dark:bg-dark-900 rounded-2xl shadow-sm border border-gray-100 dark:border-dark-700">
<div className="bg-emerald-100 dark:bg-emerald-900/20 p-3 rounded-xl inline-block mb-6 text-emerald-600">
<BaseIcon path={icon.mdiAlertOctagon} size={32} />
</div>
<h3 className="text-xl font-bold mb-3">Anomaly Detection</h3>
<p className="text-gray-600 dark:text-gray-400">Get notified the moment something unusual happensunrecognized charges or spikes in burn.</p>
</div>
<div className="p-8 bg-white dark:bg-dark-900 rounded-2xl shadow-sm border border-gray-100 dark:border-dark-700">
<div className="bg-violet-100 dark:bg-violet-900/20 p-3 rounded-xl inline-block mb-6 text-violet-600">
<BaseIcon path={icon.mdiChatProcessing} size={32} />
</div>
<h3 className="text-xl font-bold mb-3">Ask Your CFO</h3>
<p className="text-gray-600 dark:text-gray-400">Chat with an AI that knows your numbers. Ask &quot;Can we afford another hire?&quot; and get a data-backed answer.</p>
</div>
</div>
</BaseButtons>
</CardBox>
</div>
</section>
</div>
</SectionFullScreen>
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
Privacy Policy
</Link>
</div>
{/* Footer */}
<footer className="py-12 border-t border-gray-100 dark:border-dark-800 text-center text-gray-500 text-sm">
<p>© 2026 {title}. Built for SMBs who want to scale smart.</p>
<div className="mt-4 flex justify-center space-x-6">
<Link href="/privacy-policy" className="hover:text-blue-600">Privacy Policy</Link>
<Link href="/terms-of-use" className="hover:text-blue-600">Terms of Use</Link>
</div>
</footer>
</div>
);
}

View File

@ -1,7 +1,9 @@
import React, { ReactElement, useEffect, useState } from 'react';
import Head from 'next/head';
import 'react-datepicker/dist/react-datepicker.css';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import { useAppDispatch } from '../stores/hooks';
import { useAppSelector } from '../stores/hooks';
import { useRouter } from 'next/router';
import LayoutAuthenticated from '../layouts/Authenticated';
@ -91,4 +93,4 @@ SearchView.getLayout = function getLayout(page: ReactElement) {
);
};
export default SearchView;
export default SearchView;