From b024fbd9e52afddefeb74c02f415dcc40351d204 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 9 Feb 2026 17:35:54 +0000 Subject: [PATCH] vv --- backend/src/db/db.config.js | 11 +- backend/src/db/migrations/1770657891319.js | 48 +++ backend/src/index.js | 4 +- .../components/WebPageComponents/Footer.tsx | 36 ++ .../components/WebPageComponents/Header.tsx | 44 +++ frontend/src/pages/index.tsx | 350 +++++++++++------- 6 files changed, 342 insertions(+), 151 deletions(-) create mode 100644 backend/src/db/migrations/1770657891319.js create mode 100644 frontend/src/components/WebPageComponents/Footer.tsx create mode 100644 frontend/src/components/WebPageComponents/Header.tsx diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js index 46565e4..d87d268 100644 --- a/backend/src/db/db.config.js +++ b/backend/src/db/db.config.js @@ -1,5 +1,3 @@ - - module.exports = { production: { dialect: 'postgres', @@ -12,11 +10,12 @@ module.exports = { seederStorage: 'sequelize', }, development: { - username: 'postgres', dialect: 'postgres', - password: '', - database: 'db_mobile_commerce_store', + username: process.env.DB_USER || 'postgres', + password: process.env.DB_PASS || '', + database: process.env.DB_NAME || 'db_mobile_commerce_store', host: process.env.DB_HOST || 'localhost', + port: process.env.DB_PORT || 5432, logging: console.log, seederStorage: 'sequelize', }, @@ -30,4 +29,4 @@ module.exports = { logging: console.log, seederStorage: 'sequelize', } -}; +}; \ No newline at end of file diff --git a/backend/src/db/migrations/1770657891319.js b/backend/src/db/migrations/1770657891319.js new file mode 100644 index 0000000..bc06082 --- /dev/null +++ b/backend/src/db/migrations/1770657891319.js @@ -0,0 +1,48 @@ +module.exports = { + async up(queryInterface, Sequelize) { + const [roles] = await queryInterface.sequelize.query( + `SELECT id FROM "roles" WHERE name = 'Public' LIMIT 1;` + ); + const [readProductsPerm] = await queryInterface.sequelize.query( + `SELECT id FROM "permissions" WHERE name = 'READ_PRODUCTS' LIMIT 1;` + ); + const [readCategoriesPerm] = await queryInterface.sequelize.query( + `SELECT id FROM "permissions" WHERE name = 'READ_CATEGORIES' LIMIT 1;` + ); + + if (roles.length && readProductsPerm.length) { + const [existing] = await queryInterface.sequelize.query( + `SELECT * FROM "rolesPermissionsPermissions" WHERE "roles_permissionsId" = '${roles[0].id}' AND "permissionId" = '${readProductsPerm[0].id}';` + ); + if (!existing.length) { + await queryInterface.bulkInsert('rolesPermissionsPermissions', [ + { + createdAt: new Date(), + updatedAt: new Date(), + roles_permissionsId: roles[0].id, + permissionId: readProductsPerm[0].id, + }, + ]); + } + } + + if (roles.length && readCategoriesPerm.length) { + const [existing] = await queryInterface.sequelize.query( + `SELECT * FROM "rolesPermissionsPermissions" WHERE "roles_permissionsId" = '${roles[0].id}' AND "permissionId" = '${readCategoriesPerm[0].id}';` + ); + if (!existing.length) { + await queryInterface.bulkInsert('rolesPermissionsPermissions', [ + { + createdAt: new Date(), + updatedAt: new Date(), + roles_permissionsId: roles[0].id, + permissionId: readCategoriesPerm[0].id, + }, + ]); + } + } + }, + + async down(queryInterface, Sequelize) { + } +}; \ No newline at end of file diff --git a/backend/src/index.js b/backend/src/index.js index 730ce1b..c1eb115 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -131,9 +131,9 @@ app.use('/api/addresses', passport.authenticate('jwt', {session: false}), addres app.use('/api/brands', passport.authenticate('jwt', {session: false}), brandsRoutes); -app.use('/api/categories', passport.authenticate('jwt', {session: false}), categoriesRoutes); +app.use('/api/categories', categoriesRoutes); -app.use('/api/products', passport.authenticate('jwt', {session: false}), productsRoutes); +app.use('/api/products', productsRoutes); app.use('/api/product_variants', passport.authenticate('jwt', {session: false}), product_variantsRoutes); diff --git a/frontend/src/components/WebPageComponents/Footer.tsx b/frontend/src/components/WebPageComponents/Footer.tsx new file mode 100644 index 0000000..65b81d3 --- /dev/null +++ b/frontend/src/components/WebPageComponents/Footer.tsx @@ -0,0 +1,36 @@ + +import React from 'react'; +import Link from 'next/link'; + +export default function WebSiteFooter({ projectName }: { projectName: string }) { + return ( + + ); +} diff --git a/frontend/src/components/WebPageComponents/Header.tsx b/frontend/src/components/WebPageComponents/Header.tsx new file mode 100644 index 0000000..c8de219 --- /dev/null +++ b/frontend/src/components/WebPageComponents/Header.tsx @@ -0,0 +1,44 @@ + +import React, { useState } from 'react'; +import Link from 'next/link'; +import { mdiAccount, mdiCart, mdiMenu, mdiClose } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; + +export default function WebSiteHeader({ projectName }: { projectName: string }) { + const [isMenuOpen, setIsMenuOpen] = useState(false); + + return ( +
+
+ + {projectName} + + + {/* Desktop Nav */} + + +
+ + + + +
+
+ + {/* Mobile Nav */} + {isMenuOpen && ( +
+ Home + Products + Login +
+ )} +
+ ); +} diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 5fa075f..6012c0f 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,230 @@ - import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; -import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; +import axios from 'axios'; +import { mdiMagnify, mdiCartOutline, mdiArrowRight, mdiStar } from '@mdi/js'; +import BaseIcon from '../components/BaseIcon'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; -import { getPageTitle } from '../config'; +import WebSiteHeader from '../components/WebPageComponents/Header'; +import WebSiteFooter from '../components/WebPageComponents/Footer'; +import LoadingSpinner from '../components/LoadingSpinner'; import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +export default function LandingPage() { + const [products, setProducts] = useState([]); + const [categories, setCategories] = useState([]); + const [loading, setLoading] = useState(true); + const [activeCategory, setActiveCategory] = useState('All'); + const [searchQuery, setSearchQuery] = useState(''); + const projectName = useAppSelector((state) => state.style.projectName) || 'MobileStore'; -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('left'); - const textColor = useAppSelector((state) => state.style.linkColor); + useEffect(() => { + async function fetchData() { + try { + setLoading(true); + const [productsRes, categoriesRes] = await Promise.all([ + axios.get('/products'), + axios.get('/categories') + ]); + setProducts(productsRes.data.rows || []); + setCategories(categoriesRes.data.rows || []); + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + } + fetchData(); + }, []); - const title = 'Mobile Commerce Store' + const filteredProducts = products.filter(product => { + const matchesCategory = activeCategory === 'All' || + product.categories?.some(c => c.name === activeCategory); + const matchesSearch = product.name?.toLowerCase().includes(searchQuery.toLowerCase()); + return matchesCategory && matchesSearch; + }); - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
-
- ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } - }; + const featuredProducts = products.filter(p => p.is_featured).slice(0, 4); return ( -
+
- {getPageTitle('Starter Page')} + {projectName} - Best Mobile Shop - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {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

+ + +
+ {/* Hero Section */} +
+
+
+

+ Your Next Favorite Tech
is Just a Click Away +

+

+ Browse the latest smartphones, tablets, and accessories. Fast delivery and secure payments guaranteed. +

+ +
+
+ +
+ setSearchQuery(e.target.value)} + /> +
- - - +
+
- - -
-
- -
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
+ {/* Categories Chips */} +
+
+
+ + {categories.map(cat => ( + + ))} +
+
+
+ {/* Product Grid */} +
+
+

+ {activeCategory === 'All' ? 'Featured Products' : activeCategory} +

+ + View All + +
+ + {loading ? ( +
+ +
+ ) : ( +
+ {filteredProducts.length > 0 ? filteredProducts.map(product => ( +
+ + {/* Fallback image if no gallery_images */} + {product.name} + {product.is_featured && ( + + Featured + + )} + +
+
+ + {product.categories?.[0]?.name || 'Electronics'} + +
+ + 4.8 +
+
+ +

+ {product.name} +

+ +

+ {product.short_description || 'High-quality product for your everyday needs.'} +

+
+
+ + ${product.base_price} + + {product.compare_at_price && ( + + ${product.compare_at_price} + + )} +
+ +
+
+
+ )) : ( +
+

No products found for this category.

+
+ )} +
+ )} +
+ + {/* Features Section */} +
+
+
+
+ +
+

Premium Quality

+

We only sell products from trusted brands with high customer ratings.

+
+
+
+ +
+

Fast Delivery

+

Get your tech delivered to your doorstep within 24-48 hours.

+
+
+
+ +
+

Secure Payments

+

All transactions are encrypted and processed through secure gateways.

+
+
+
+ + +
); } -Starter.getLayout = function getLayout(page: ReactElement) { +LandingPage.getLayout = function getLayout(page: ReactElement) { return {page}; -}; - +}; \ No newline at end of file