diff --git a/backend/src/index.js b/backend/src/index.js
index 2e35bd9..20a8ebd 100644
--- a/backend/src/index.js
+++ b/backend/src/index.js
@@ -12,6 +12,7 @@ const swaggerUI = require('swagger-ui-express');
const swaggerJsDoc = require('swagger-jsdoc');
const authRoutes = require('./routes/auth');
+const publicRoutes = require('./routes/public');
const fileRoutes = require('./routes/file');
const searchRoutes = require('./routes/search');
const sqlRoutes = require('./routes/sql');
@@ -123,6 +124,7 @@ require('./auth/auth');
app.use(bodyParser.json());
+app.use('/api/public', publicRoutes);
app.use('/api/auth', authRoutes);
app.use('/api/file', fileRoutes);
app.use('/api/pexels', pexelsRoutes);
diff --git a/backend/src/routes/public.js b/backend/src/routes/public.js
new file mode 100644
index 0000000..f73a572
--- /dev/null
+++ b/backend/src/routes/public.js
@@ -0,0 +1,67 @@
+
+const express = require('express');
+const router = express.Router();
+const db = require('../db/models');
+const wrapAsync = require('../helpers').wrapAsync;
+const { LocalAIApi } = require("../ai/LocalAIApi");
+
+// Get Top 3 Products
+router.get('/top-products', wrapAsync(async (req, res) => {
+ const products = await db.products.findAll({
+ where: { status: 'active' },
+ order: [['total_sales_count', 'DESC']],
+ limit: 3,
+ include: [{
+ model: db.file,
+ as: 'images',
+ }]
+ });
+ res.status(200).send(products);
+}));
+
+// Get FAQs for Chatbot
+router.get('/faq', wrapAsync(async (req, res) => {
+ const faqs = await db.faq_articles.findAll({
+ order: [['sort_order', 'ASC']],
+ });
+ res.status(200).send(faqs);
+}));
+
+// Public Chatbot endpoint
+router.post('/chatbot', wrapAsync(async (req, res) => {
+ const { message } = req.body;
+
+ // Fetch FAQs to provide context
+ const faqs = await db.faq_articles.findAll();
+ const faqContext = faqs.map(f => `Q: ${f.question}\nA: ${f.answer}`).join('\n\n');
+
+ const prompt = `
+ You are a helpful customer support assistant for our Shopify store.
+ Use the following FAQ information to answer the user's question.
+ If the answer is not in the FAQ, be polite and ask them to contact support.
+
+ FAQ Context:
+ ${faqContext}
+
+ User Question: ${message}
+ `;
+
+ const resp = await LocalAIApi.createResponse(
+ {
+ input: [
+ { role: "system", content: "You are a helpful customer support assistant." },
+ { role: "user", content: prompt },
+ ],
+ },
+ { poll_interval: 2, poll_timeout: 60 }
+ );
+
+ if (resp.success) {
+ const text = LocalAIApi.extractText(resp);
+ res.status(200).send({ text });
+ } else {
+ res.status(500).send({ error: "AI failed to respond" });
+ }
+}));
+
+module.exports = router;
diff --git a/frontend/src/components/ChatbotWidget.tsx b/frontend/src/components/ChatbotWidget.tsx
new file mode 100644
index 0000000..28774e1
--- /dev/null
+++ b/frontend/src/components/ChatbotWidget.tsx
@@ -0,0 +1,106 @@
+
+import React, { useState, useEffect } from 'react';
+import { mdiChatProcessing, mdiClose, mdiSend } from '@mdi/js';
+import BaseIcon from './BaseIcon';
+import axios from 'axios';
+
+const ChatbotWidget = () => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [messages, setMessages] = useState([
+ { role: 'bot', text: 'Hello! How can I help you today?' },
+ ]);
+ const [input, setInput] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ const toggleChat = () => setIsOpen(!isOpen);
+
+ const sendMessage = async () => {
+ if (!input.trim()) return;
+
+ const userMessage = { role: 'user', text: input };
+ setMessages((prev) => [...prev, userMessage]);
+ setInput('');
+ setIsLoading(true);
+
+ try {
+ const response = await axios.post('/public/chatbot', { message: input });
+ const botMessage = { role: 'bot', text: response.data.text || "I'm sorry, I couldn't understand that." };
+ setMessages((prev) => [...prev, botMessage]);
+ } catch (error) {
+ console.error('Chatbot error:', error);
+ setMessages((prev) => [...prev, { role: 'bot', text: 'Sorry, I am having trouble connecting right now.' }]);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ return (
+
+ {!isOpen && (
+
+ )}
+
+ {isOpen && (
+
+
+
Store Assistant
+
+
+
+
+ {messages.map((msg, idx) => (
+
+ ))}
+ {isLoading && (
+
+ )}
+
+
+
+ setInput(e.target.value)}
+ onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
+ placeholder="Type your question..."
+ className="flex-1 border rounded-full px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
+ />
+
+
+
+ )}
+
+ );
+};
+
+export default ChatbotWidget;
diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx
index 3ad97b4..5320194 100644
--- a/frontend/src/pages/dashboard.tsx
+++ b/frontend/src/pages/dashboard.tsx
@@ -1,6 +1,6 @@
import * as icon from '@mdi/js';
import Head from 'next/head'
-import React from 'react'
+import React, { useState, useEffect } from 'react'
import axios from 'axios';
import type { ReactElement } from 'react'
import LayoutAuthenticated from '../layouts/Authenticated'
@@ -9,6 +9,8 @@ import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton
import BaseIcon from "../components/BaseIcon";
import { getPageTitle } from '../config'
import Link from "next/link";
+import BaseButton from '../components/BaseButton';
+import CardBox from '../components/CardBox';
import { hasPermission } from "../helpers/userPermissions";
import { fetchWidgets } from '../stores/roles/rolesSlice';
@@ -16,6 +18,7 @@ import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
import { SmartWidget } from '../components/SmartWidget/SmartWidget';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
+
const Dashboard = () => {
const dispatch = useAppDispatch();
const iconsColor = useAppSelector((state) => state.style.iconsColor);
@@ -24,34 +27,34 @@ const Dashboard = () => {
const loadingMessage = 'Loading...';
-
- const [users, setUsers] = React.useState(loadingMessage);
- const [roles, setRoles] = React.useState(loadingMessage);
- const [permissions, setPermissions] = React.useState(loadingMessage);
- const [organizations, setOrganizations] = React.useState(loadingMessage);
- const [stores, setStores] = React.useState(loadingMessage);
- const [pages, setPages] = React.useState(loadingMessage);
- const [collections, setCollections] = React.useState(loadingMessage);
- const [products, setProducts] = React.useState(loadingMessage);
- const [product_variants, setProduct_variants] = React.useState(loadingMessage);
- const [customers, setCustomers] = React.useState(loadingMessage);
- const [addresses, setAddresses] = React.useState(loadingMessage);
- const [orders, setOrders] = React.useState(loadingMessage);
- const [order_items, setOrder_items] = React.useState(loadingMessage);
- const [fulfillments, setFulfillments] = React.useState(loadingMessage);
- const [payments, setPayments] = React.useState(loadingMessage);
- const [discount_codes, setDiscount_codes] = React.useState(loadingMessage);
- const [refunds, setRefunds] = React.useState(loadingMessage);
- const [inventory_adjustments, setInventory_adjustments] = React.useState(loadingMessage);
- const [faq_articles, setFaq_articles] = React.useState(loadingMessage);
- const [chatbot_configs, setChatbot_configs] = React.useState(loadingMessage);
- const [chat_sessions, setChat_sessions] = React.useState(loadingMessage);
- const [chat_messages, setChat_messages] = React.useState(loadingMessage);
- const [support_tickets, setSupport_tickets] = React.useState(loadingMessage);
- const [shopify_sync_jobs, setShopify_sync_jobs] = React.useState(loadingMessage);
+ const [users, setUsers] = useState(loadingMessage);
+ const [roles, setRoles] = useState(loadingMessage);
+ const [permissions, setPermissions] = useState(loadingMessage);
+ const [organizations, setOrganizations] = useState(loadingMessage);
+ const [stores, setStores] = useState(loadingMessage);
+ const [pages, setPages] = useState(loadingMessage);
+ const [collections, setCollections] = useState(loadingMessage);
+ const [products, setProducts] = useState(loadingMessage);
+ const [product_variants, setProduct_variants] = useState(loadingMessage);
+ const [customers, setCustomers] = useState(loadingMessage);
+ const [addresses, setAddresses] = useState(loadingMessage);
+ const [orders, setOrders] = useState(loadingMessage);
+ const [order_items, setOrder_items] = useState(loadingMessage);
+ const [fulfillments, setFulfillments] = useState(loadingMessage);
+ const [payments, setPayments] = useState(loadingMessage);
+ const [discount_codes, setDiscount_codes] = useState(loadingMessage);
+ const [refunds, setRefunds] = useState(loadingMessage);
+ const [inventory_adjustments, setInventory_adjustments] = useState(loadingMessage);
+ const [faq_articles, setFaq_articles] = useState(loadingMessage);
+ const [chatbot_configs, setChatbot_configs] = useState(loadingMessage);
+ const [chat_sessions, setChat_sessions] = useState(loadingMessage);
+ const [chat_messages, setChat_messages] = useState(loadingMessage);
+ const [support_tickets, setSupport_tickets] = useState(loadingMessage);
+ const [shopify_sync_jobs, setShopify_sync_jobs] = useState(loadingMessage);
+ const [topProducts, setTopProducts] = useState([]);
- const [widgetsRole, setWidgetsRole] = React.useState({
+ const [widgetsRole, setWidgetsRole] = useState({
role: { value: '', label: '' },
});
const { currentUser } = useAppSelector((state) => state.auth);
@@ -59,22 +62,17 @@ const Dashboard = () => {
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
-
- const organizationId = currentUser?.organizations?.id;
-
async function loadData() {
const entities = ['users','roles','permissions','organizations','stores','pages','collections','products','product_variants','customers','addresses','orders','order_items','fulfillments','payments','discount_codes','refunds','inventory_adjustments','faq_articles','chatbot_configs','chat_sessions','chat_messages','support_tickets','shopify_sync_jobs',];
const fns = [setUsers,setRoles,setPermissions,setOrganizations,setStores,setPages,setCollections,setProducts,setProduct_variants,setCustomers,setAddresses,setOrders,setOrder_items,setFulfillments,setPayments,setDiscount_codes,setRefunds,setInventory_adjustments,setFaq_articles,setChatbot_configs,setChat_sessions,setChat_messages,setSupport_tickets,setShopify_sync_jobs,];
const requests = entities.map((entity, index) => {
-
if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
return axios.get(`/${entity.toLowerCase()}/count`);
} else {
fns[index](null);
return Promise.resolve({data: {count: null}});
}
-
});
Promise.allSettled(requests).then((results) => {
@@ -86,18 +84,26 @@ const Dashboard = () => {
}
});
});
+
+ try {
+ const res = await axios.get('/public/top-products');
+ setTopProducts(res.data);
+ } catch (err) {
+ console.error('Failed to fetch top products:', err);
+ }
}
async function getWidgets(roleId) {
await dispatch(fetchWidgets(roleId));
}
- React.useEffect(() => {
+
+ useEffect(() => {
if (!currentUser) return;
loadData().then();
setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } });
}, [currentUser]);
- React.useEffect(() => {
+ useEffect(() => {
if (!currentUser || !widgetsRole?.role?.value) return;
getWidgets(widgetsRole?.role?.value || '').then();
}, [widgetsRole?.role?.value]);
@@ -117,15 +123,52 @@ const Dashboard = () => {
{''}
+
+
+
+
+ {topProducts.map((product) => (
+
+
+
+
+
{product.product_title}
+
Sales: {product.total_sales_count || 0}
+
Rank #{product.current_sales_rank || 'N/A'}
+
+ ))}
+ {topProducts.length === 0 &&
No sales data available yet.
}
+
+
+
+
+
+
+
+ AI Agent
+
+
+ Online
+
+
+
+ "Our AI bot is currently helping customers using information from your FAQ articles."
+
+
+
+
+
+
{hasPermission(currentUser, 'CREATE_ROLES') && }
+
{!!rolesWidgets.length &&
hasPermission(currentUser, 'CREATE_ROLES') && (
-
+
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
)}
@@ -158,682 +201,54 @@ const Dashboard = () => {
{!!rolesWidgets.length &&
}
-
-
-
- {hasPermission(currentUser, 'READ_USERS') &&
-
-
-
-
- Users
-
-
- {users}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_ROLES') &&
-
-
-
-
- Roles
-
-
- {roles}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_PERMISSIONS') &&
-
-
-
-
- Permissions
-
-
- {permissions}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_ORGANIZATIONS') &&
-
-
-
-
- Organizations
-
-
- {organizations}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_STORES') &&
-
-
-
-
- Stores
-
-
- {stores}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_PAGES') &&
-
-
-
-
- Pages
-
-
- {pages}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_COLLECTIONS') &&
-
-
-
-
- Collections
-
-
- {collections}
-
-
-
-
-
-
-
- }
-
+
{hasPermission(currentUser, 'READ_PRODUCTS') &&
-
+
-
- Products
-
-
- {products}
-
-
-
-
+
Products
+
{products}
+
}
-
- {hasPermission(currentUser, 'READ_PRODUCT_VARIANTS') &&
-
-
-
-
- Product variants
-
-
- {product_variants}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_CUSTOMERS') &&
-
-
-
-
- Customers
-
-
- {customers}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_ADDRESSES') &&
-
-
-
-
- Addresses
-
-
- {addresses}
-
-
-
-
-
-
-
- }
-
+
{hasPermission(currentUser, 'READ_ORDERS') &&
-
+
-
- Orders
-
-
- {orders}
-
-
-
+
}
-
- {hasPermission(currentUser, 'READ_ORDER_ITEMS') &&
-
+
+ {hasPermission(currentUser, 'READ_CUSTOMERS') &&
+
-
- Order items
-
-
- {order_items}
-
-
-
-
+
Customers
+
{customers}
+
}
-
- {hasPermission(currentUser, 'READ_FULFILLMENTS') &&
-
-
-
-
- Fulfillments
-
-
- {fulfillments}
-
-
-
-
-
-
-
- }
-
+
{hasPermission(currentUser, 'READ_PAYMENTS') &&
-
+
-
- Payments
-
-
- {payments}
-
-
-
-
+
Payments
+
{payments}
+
}
-
- {hasPermission(currentUser, 'READ_DISCOUNT_CODES') &&
-
-
-
-
- Discount codes
-
-
- {discount_codes}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_REFUNDS') &&
-
-
-
-
- Refunds
-
-
- {refunds}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_INVENTORY_ADJUSTMENTS') &&
-
-
-
-
- Inventory adjustments
-
-
- {inventory_adjustments}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_FAQ_ARTICLES') &&
-
-
-
-
- Faq articles
-
-
- {faq_articles}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_CHATBOT_CONFIGS') &&
-
-
-
-
- Chatbot configs
-
-
- {chatbot_configs}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_CHAT_SESSIONS') &&
-
-
-
-
- Chat sessions
-
-
- {chat_sessions}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_CHAT_MESSAGES') &&
-
-
-
-
- Chat messages
-
-
- {chat_messages}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_SUPPORT_TICKETS') &&
-
-
-
-
- Support tickets
-
-
- {support_tickets}
-
-
-
-
-
-
-
- }
-
- {hasPermission(currentUser, 'READ_SHOPIFY_SYNC_JOBS') &&
-
-
-
-
- Shopify sync jobs
-
-
- {shopify_sync_jobs}
-
-
-
-
-
-
-
- }
-
-
>
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index 608ae44..6460665 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,4 +1,3 @@
-
import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
@@ -13,7 +12,12 @@ import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
-
+import BaseIcon from '../components/BaseIcon';
+import axios from 'axios';
+import ChatbotWidget from '../components/ChatbotWidget';
+import SectionMain from '../components/SectionMain';
+import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton';
+import { mdiShopping, mdiStar } from '@mdi/js';
export default function Starter() {
const [illustrationImage, setIllustrationImage] = useState({
@@ -24,17 +28,25 @@ export default function Starter() {
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
const [contentType, setContentType] = useState('video');
const [contentPosition, setContentPosition] = useState('left');
+ const [topProducts, setTopProducts] = useState([]);
const textColor = useAppSelector((state) => state.style.linkColor);
const title = 'Shopify Ops Dashboard'
- // Fetch Pexels image/video
+ // Fetch Pexels image/video and top products
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
const video = await getPexelsVideo();
setIllustrationImage(image);
setIllustrationVideo(video);
+
+ try {
+ const res = await axios.get('/public/top-products');
+ setTopProducts(res.data);
+ } catch (err) {
+ console.error('Failed to fetch top products:', err);
+ }
}
fetchData();
}, []);
@@ -94,24 +106,9 @@ export default function Starter() {
};
return (
-
+
-
{getPageTitle('Starter Page')}
+
{getPageTitle('Store Home')}
@@ -126,41 +123,89 @@ export default function Starter() {
{contentType === 'video' && contentPosition !== 'background'
? videoBlock(illustrationVideo)
: null}
-
-
-
-
-
-
This is a React.js/Node.js app generated by the Flatlogic Web App Generator
-
For guides and documentation please check
- your local README.md and the Flatlogic documentation
+
+
+
+ Modern Operations for your Shopify Store
+
+
+ The all-in-one dashboard to manage your products, orders, and customer support with AI.
+
-
-
-
-
-
+
+
+
+
+
Access your products, customers, and order lifecycle in one integrated workspace.
+
+
+
+
+
+
-
-
© 2026 {title}. All rights reserved
-
+
+
+
+ {''}
+
+
+
+ {topProducts.length > 0 ? topProducts.map((product) => (
+
+
+ {product.images?.[0] ? (
+

+ ) : (
+
+
+
+ )}
+
+
+
{product.product_title}
+
{product.description}
+
+
+ Bestseller
+
+ #{product.current_sales_rank || 'N/A'}
+
+
+
+ )) : (
+
+ Loading featured products...
+
+ )}
+
+
+
+
+
© 2026 {title}. All rights reserved
+
Privacy Policy
+
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
return {page};
-};
-
+};
\ No newline at end of file