Unable to show static files from server (React and Node.Js) , works on local but not on real deployed server

I am new to React and Node.Js and I am trying to create a simulation site where a manager can upload promotions into the server to showcase. The images and pdf are able to be showcased on my own local machine but unfortunately , it doesn’t showcase on the real server. May I understand why this is happening and how I should fix this? My local machine is running on windows while the server is running on linux

Client-side App.js:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>//Client App.js
import './App.css';
import { Route, Routes, Link } from 'react-router-dom';
import PromoBannerConf from './PromoBannerConf';
import Home from './Home';
import PromoCarousel from './PromoCarousel';
import BankAccountOfferings from './bank_account_offerings';
import StaffBankAccountOfferings from './staff/staff_bank_accounts_offerings';
import ApproveAccounts from './staff/approve_accounts';
import Login from "./login/Login";
import Register from "./register/Register";
import Dashboard from './customer/Dashboard';
import Deposit from './customer/Deposit';
import Transfer from './customer/Transfer';
import Withdraw from './customer/Withdraw';
const App = () => {
return (
<div className="App">
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/promo-banner-conf">Promo Banner Config</Link>
</li>
<li>
<Link to="/promo-carousel">Promo Carousel</Link>
</li>
<li>
<Link to="/bank-account-offerings">See bank accounts offered</Link>
</li>
<li>
<Link to="/Dashboard">See customer dashboard</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
<li>
<Link to="/Register">Register</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/login" element={<Login/>}/>
<Route path="/register" element={<Register/>}/>
<Route path="/promo-banner-conf" element={<PromoBannerConf />} />
<Route path="/promo-carousel" element={<PromoCarousel />} />
<Route path="/bank_account_offerings" element={<BankAccountOfferings />} />
<Route path="/staff/bank_account_offerings" element={<StaffBankAccountOfferings />} />
<Route path="/staff/approve_accounts" element={<ApproveAccounts />} />
<Route path="/" element={<Home />} />
<Route path="/dashboard" element= {<Dashboard />} />
<Route path="/deposit" element= {<Deposit />} />
<Route path="/transfer" element= {<Transfer />} />
<Route path="/withdraw" element= {<Withdraw />} />
</Routes>
</div>
);
}
export default App;
</code>
<code>//Client App.js import './App.css'; import { Route, Routes, Link } from 'react-router-dom'; import PromoBannerConf from './PromoBannerConf'; import Home from './Home'; import PromoCarousel from './PromoCarousel'; import BankAccountOfferings from './bank_account_offerings'; import StaffBankAccountOfferings from './staff/staff_bank_accounts_offerings'; import ApproveAccounts from './staff/approve_accounts'; import Login from "./login/Login"; import Register from "./register/Register"; import Dashboard from './customer/Dashboard'; import Deposit from './customer/Deposit'; import Transfer from './customer/Transfer'; import Withdraw from './customer/Withdraw'; const App = () => { return ( <div className="App"> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/promo-banner-conf">Promo Banner Config</Link> </li> <li> <Link to="/promo-carousel">Promo Carousel</Link> </li> <li> <Link to="/bank-account-offerings">See bank accounts offered</Link> </li> <li> <Link to="/Dashboard">See customer dashboard</Link> </li> <li> <Link to="/login">Login</Link> </li> <li> <Link to="/Register">Register</Link> </li> </ul> </nav> <Routes> <Route path="/login" element={<Login/>}/> <Route path="/register" element={<Register/>}/> <Route path="/promo-banner-conf" element={<PromoBannerConf />} /> <Route path="/promo-carousel" element={<PromoCarousel />} /> <Route path="/bank_account_offerings" element={<BankAccountOfferings />} /> <Route path="/staff/bank_account_offerings" element={<StaffBankAccountOfferings />} /> <Route path="/staff/approve_accounts" element={<ApproveAccounts />} /> <Route path="/" element={<Home />} /> <Route path="/dashboard" element= {<Dashboard />} /> <Route path="/deposit" element= {<Deposit />} /> <Route path="/transfer" element= {<Transfer />} /> <Route path="/withdraw" element= {<Withdraw />} /> </Routes> </div> ); } export default App; </code>
//Client App.js

import './App.css';
import { Route, Routes, Link } from 'react-router-dom';
import PromoBannerConf from './PromoBannerConf';
import Home from './Home';
import PromoCarousel from './PromoCarousel';
import BankAccountOfferings from './bank_account_offerings';
import StaffBankAccountOfferings from './staff/staff_bank_accounts_offerings';
import ApproveAccounts from './staff/approve_accounts';
import Login from "./login/Login";
import Register from "./register/Register";
import Dashboard from './customer/Dashboard';
import Deposit from './customer/Deposit';
import Transfer from './customer/Transfer';
import Withdraw from './customer/Withdraw';

const App = () => {
    return (
        <div className="App">
            <nav>
                <ul>
                    <li>
                        <Link to="/">Home</Link>
                    </li>
                    <li>
                        <Link to="/promo-banner-conf">Promo Banner Config</Link>
                    </li>
                    <li>
                        <Link to="/promo-carousel">Promo Carousel</Link>
                    </li>
                    <li>
                        <Link to="/bank-account-offerings">See bank accounts offered</Link>
                    </li>
                    <li>
                        <Link to="/Dashboard">See customer dashboard</Link>
                    </li>
                    <li>
                        <Link to="/login">Login</Link>
                    </li>
                    <li>
                        <Link to="/Register">Register</Link>
                    </li>
                </ul>
            </nav>
            <Routes>
                <Route path="/login" element={<Login/>}/>
                <Route path="/register" element={<Register/>}/>
                <Route path="/promo-banner-conf" element={<PromoBannerConf />} />
                <Route path="/promo-carousel" element={<PromoCarousel />} />
                <Route path="/bank_account_offerings" element={<BankAccountOfferings />} />
                    <Route path="/staff/bank_account_offerings" element={<StaffBankAccountOfferings />} />
                    <Route path="/staff/approve_accounts" element={<ApproveAccounts />} />
                <Route path="/" element={<Home />} />
                <Route path="/dashboard" element= {<Dashboard />} />
                <Route path="/deposit" element= {<Deposit />} />
                <Route path="/transfer" element= {<Transfer />} />
                <Route path="/withdraw" element= {<Withdraw />} />
            </Routes>
        </div>
    );
}

export default App;

Server-side App.js:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>const express = require("express");
const cors = require("cors");
const session = require("express-session");
const app = express();
const router = express.Router();
const path = require("path");
const fs = require("fs");
// const authRouter = require("./routes/customer/auth")
// const promoRouter = require("./routes/promo/PromoBannerConf");
// const transactionLimitRouter = require("./routes/transaction_limit");
// const depositRouter = require("./routes/customer/deposit");
// const withdrawRouter = require("./routes/customer/withdraw");
// const transferRouter = require("./routes/customer/transfer");
const staffAuthRouter = require("./routes/staff/staff_auth");
const staffManagementRouter = require("./routes/staff/staff_management")
const securityPolicyConfigurationRouter = require("./routes/staff/security_policy_configuration")
// Add interest!
const schedule = require('node-schedule')
// Base route for testing
router.get("/", (req, res) => {
res.json({ message: "Welcome to SingStride Bank" });
});
// Read environment variable from file (for development)
require("dotenv").config()
const interestTime = require("./interest_credit.js")
// var d = new Date();
// d.setDate(d.getDate() - 1)
// interestTime(d);
schedule.scheduleJob('0 0 * * *', () => {
var d = new Date();
d.setDate(d.getDate() - 1)
interestTime(d);
}) // run everyday at midnight
// Enable json parsing
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.enable("trust proxy");
// Enable session
const devMode = app.get("env") === "development";
app.use(session({
resave: false,
saveUninitialized: true,
secret: process.env.SESSION_SECRET,
cookie: {
path: '/',
httpOnly: true,
secure: !devMode,
maxAge: !devMode ? process.env.SESSION_TIMEOUT_MINUTES * 60000 : null
}
}));
// Configure cors
app.use(cors({
credentials: true,
origin: ["http://localhost:3000", "https://cantrevealthissiteunfortunately.com"]
}));
// Serve static files
app.use('/files', express.static(path.join(__dirname, 'files/promos')));
app.get('/files/:filename', (req, res) => {
const filePath = path.join(__dirname, 'files/promos', req.params.filename);
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
res.status(404).send('File not found');
} else {
res.sendFile(filePath);
}
});
});
// Add routes to router
router.get("/", (req, res) => {
res.json({ message: "Welcome to SingStride Bank" });
});
// Import and use other routes...
const authRouter = require("./routes/customer/auth");
router.use("/auth", authRouter);
const approveAccountRouter = require("./routes/staff/approve_account");
router.use("/staff/approve_account", approveAccountRouter);
const promoRouter = require("./routes/promo/PromoBannerConf");
router.use("/promo", promoRouter); // Add the route for PromoRouter
const transactionLimitRouter = require("./routes/transaction_limit");
router.use("/transaction_limit", transactionLimitRouter);
const dashboardRouter = require("./routes/customer/dashboard");
router.use("/dashboard", dashboardRouter);
const depositRouter = require("./routes/customer/deposit");
router.use("/deposit", depositRouter);
const withdrawRouter = require("./routes/customer/withdraw");
router.use("/withdraw", withdrawRouter);
const transferRouter = require("./routes/customer/transfer");
router.use("/transfer", transferRouter);
router.use("/staff/auth", staffAuthRouter)
router.use("/staff/staff_management", staffManagementRouter)
router.use("/staff/security_policy_configuration", securityPolicyConfigurationRouter)
// Bank account offerings (staff)
const staffBankAccountOfferings = require("./routes/staff/bank_account_offerings.js");
router.use("/staff/bank_account_offerings", staffBankAccountOfferings);
// Bank account offerings (customer)
// Note: NOT A DUPLICATE
const customerBankAccountOfferings = require("./routes/bank_account_offerings");
router.use("/bank_account_offerings", customerBankAccountOfferings);
// Add /api prefix to all routes
app.use("/api", router);
// Overwrite default error handler so it doesn't send error logs to user
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
// Listen on port 5000 only if the script is run directly (not required as a module)
if (require.main === module) {
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
}
module.exports = app;
</code>
<code>const express = require("express"); const cors = require("cors"); const session = require("express-session"); const app = express(); const router = express.Router(); const path = require("path"); const fs = require("fs"); // const authRouter = require("./routes/customer/auth") // const promoRouter = require("./routes/promo/PromoBannerConf"); // const transactionLimitRouter = require("./routes/transaction_limit"); // const depositRouter = require("./routes/customer/deposit"); // const withdrawRouter = require("./routes/customer/withdraw"); // const transferRouter = require("./routes/customer/transfer"); const staffAuthRouter = require("./routes/staff/staff_auth"); const staffManagementRouter = require("./routes/staff/staff_management") const securityPolicyConfigurationRouter = require("./routes/staff/security_policy_configuration") // Add interest! const schedule = require('node-schedule') // Base route for testing router.get("/", (req, res) => { res.json({ message: "Welcome to SingStride Bank" }); }); // Read environment variable from file (for development) require("dotenv").config() const interestTime = require("./interest_credit.js") // var d = new Date(); // d.setDate(d.getDate() - 1) // interestTime(d); schedule.scheduleJob('0 0 * * *', () => { var d = new Date(); d.setDate(d.getDate() - 1) interestTime(d); }) // run everyday at midnight // Enable json parsing app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.enable("trust proxy"); // Enable session const devMode = app.get("env") === "development"; app.use(session({ resave: false, saveUninitialized: true, secret: process.env.SESSION_SECRET, cookie: { path: '/', httpOnly: true, secure: !devMode, maxAge: !devMode ? process.env.SESSION_TIMEOUT_MINUTES * 60000 : null } })); // Configure cors app.use(cors({ credentials: true, origin: ["http://localhost:3000", "https://cantrevealthissiteunfortunately.com"] })); // Serve static files app.use('/files', express.static(path.join(__dirname, 'files/promos'))); app.get('/files/:filename', (req, res) => { const filePath = path.join(__dirname, 'files/promos', req.params.filename); fs.access(filePath, fs.constants.F_OK, (err) => { if (err) { res.status(404).send('File not found'); } else { res.sendFile(filePath); } }); }); // Add routes to router router.get("/", (req, res) => { res.json({ message: "Welcome to SingStride Bank" }); }); // Import and use other routes... const authRouter = require("./routes/customer/auth"); router.use("/auth", authRouter); const approveAccountRouter = require("./routes/staff/approve_account"); router.use("/staff/approve_account", approveAccountRouter); const promoRouter = require("./routes/promo/PromoBannerConf"); router.use("/promo", promoRouter); // Add the route for PromoRouter const transactionLimitRouter = require("./routes/transaction_limit"); router.use("/transaction_limit", transactionLimitRouter); const dashboardRouter = require("./routes/customer/dashboard"); router.use("/dashboard", dashboardRouter); const depositRouter = require("./routes/customer/deposit"); router.use("/deposit", depositRouter); const withdrawRouter = require("./routes/customer/withdraw"); router.use("/withdraw", withdrawRouter); const transferRouter = require("./routes/customer/transfer"); router.use("/transfer", transferRouter); router.use("/staff/auth", staffAuthRouter) router.use("/staff/staff_management", staffManagementRouter) router.use("/staff/security_policy_configuration", securityPolicyConfigurationRouter) // Bank account offerings (staff) const staffBankAccountOfferings = require("./routes/staff/bank_account_offerings.js"); router.use("/staff/bank_account_offerings", staffBankAccountOfferings); // Bank account offerings (customer) // Note: NOT A DUPLICATE const customerBankAccountOfferings = require("./routes/bank_account_offerings"); router.use("/bank_account_offerings", customerBankAccountOfferings); // Add /api prefix to all routes app.use("/api", router); // Overwrite default error handler so it doesn't send error logs to user app.use((err, req, res, next) => { console.error(err.stack); res.status(500).send('Something broke!'); }); // Listen on port 5000 only if the script is run directly (not required as a module) if (require.main === module) { const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); } module.exports = app; </code>
const express = require("express");
const cors = require("cors");
const session = require("express-session");
const app = express();
const router = express.Router();
const path = require("path");
const fs = require("fs");
// const authRouter = require("./routes/customer/auth")
// const promoRouter = require("./routes/promo/PromoBannerConf");
// const transactionLimitRouter = require("./routes/transaction_limit");
// const depositRouter = require("./routes/customer/deposit");
// const withdrawRouter = require("./routes/customer/withdraw");
// const transferRouter = require("./routes/customer/transfer");
const staffAuthRouter = require("./routes/staff/staff_auth");
const staffManagementRouter = require("./routes/staff/staff_management")
const securityPolicyConfigurationRouter = require("./routes/staff/security_policy_configuration")

// Add interest!
const schedule = require('node-schedule')

// Base route for testing
router.get("/", (req, res) => {
    res.json({ message: "Welcome to SingStride Bank" });
});
// Read environment variable from file (for development)
require("dotenv").config()

const interestTime = require("./interest_credit.js")

// var d = new Date();
// d.setDate(d.getDate() - 1)
// interestTime(d);

schedule.scheduleJob('0 0 * * *', () => {
    var d = new Date();
    d.setDate(d.getDate() - 1)
    interestTime(d);
}) // run everyday at midnight
// Enable json parsing

app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.enable("trust proxy");

// Enable session
const devMode = app.get("env") === "development";
app.use(session({
    resave: false,
    saveUninitialized: true,
    secret: process.env.SESSION_SECRET,
    cookie: {
        path: '/',
        httpOnly: true,
        secure: !devMode,
        maxAge: !devMode ? process.env.SESSION_TIMEOUT_MINUTES * 60000 : null
    }
}));

// Configure cors
app.use(cors({
    credentials: true,
    origin: ["http://localhost:3000", "https://cantrevealthissiteunfortunately.com"]
}));

// Serve static files
app.use('/files', express.static(path.join(__dirname, 'files/promos')));

app.get('/files/:filename', (req, res) => {
    const filePath = path.join(__dirname, 'files/promos', req.params.filename);
    fs.access(filePath, fs.constants.F_OK, (err) => {
        if (err) {
            res.status(404).send('File not found');
        } else {
            res.sendFile(filePath);
        }
    });
});

// Add routes to router
router.get("/", (req, res) => {
    res.json({ message: "Welcome to SingStride Bank" });
});

// Import and use other routes...
const authRouter = require("./routes/customer/auth");
router.use("/auth", authRouter);

const approveAccountRouter = require("./routes/staff/approve_account");
router.use("/staff/approve_account", approveAccountRouter);

const promoRouter = require("./routes/promo/PromoBannerConf");
router.use("/promo", promoRouter); // Add the route for PromoRouter

const transactionLimitRouter = require("./routes/transaction_limit");
router.use("/transaction_limit", transactionLimitRouter);

const dashboardRouter = require("./routes/customer/dashboard");
router.use("/dashboard", dashboardRouter);

const depositRouter = require("./routes/customer/deposit");
router.use("/deposit", depositRouter);

const withdrawRouter = require("./routes/customer/withdraw");
router.use("/withdraw", withdrawRouter);

const transferRouter = require("./routes/customer/transfer");
router.use("/transfer", transferRouter);
router.use("/staff/auth", staffAuthRouter)
router.use("/staff/staff_management", staffManagementRouter)
router.use("/staff/security_policy_configuration", securityPolicyConfigurationRouter)

// Bank account offerings (staff)
const staffBankAccountOfferings = require("./routes/staff/bank_account_offerings.js");
router.use("/staff/bank_account_offerings", staffBankAccountOfferings);

// Bank account offerings (customer)
// Note: NOT A DUPLICATE
const customerBankAccountOfferings = require("./routes/bank_account_offerings");
router.use("/bank_account_offerings", customerBankAccountOfferings);

// Add /api prefix to all routes
app.use("/api", router);

// Overwrite default error handler so it doesn't send error logs to user
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Something broke!');
});

// Listen on port 5000 only if the script is run directly (not required as a module)
if (require.main === module) {
    const PORT = process.env.PORT || 5000;
    app.listen(PORT, () => {
        console.log(`Server is running on port ${PORT}`);
    });
}

module.exports = app;

Client-side PromoBannerConf.js:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>import React, { useEffect, useState } from 'react';
import './PromoBannerConf.css';
const PromoBannerConf = () => {
const [selectedImage, setSelectedImage] = useState(null);
const [selectedPDF, setSelectedPDF] = useState(null);
const [message, setMessage] = useState('');
const [promotions, setPromotions] = useState([]);
const [selectedIds, setSelectedIds] = useState([]);
useEffect(() => {
fetchPromotions();
}, []);
const fetchPromotions = () => {
fetch(`${process.env.REACT_APP_API_URL}/api/promo/all`)
.then(response => response.json())
.then(data => {
console.log('Fetched promotions:', data);
setPromotions(data);
})
.catch(error => console.error('Error fetching promotions:', error));
};
const onImageChange = event => {
setSelectedImage(event.target.files[0]);
};
const onPDFChange = event => {
setSelectedPDF(event.target.files[0]);
};
const onFileUpload = () => {
if (!selectedImage || !selectedPDF) {
setMessage('Both image and PDF are required');
return;
}
const formData = new FormData();
formData.append('promoImage', selectedImage);
formData.append('promoPDF', selectedPDF);
fetch(`${process.env.REACT_APP_API_URL}/api/promo/upload`, {
method: 'POST',
body: formData,
})
.then(response => {
if (!response.ok) {
return response.text().then(text => { throw new Error(text) });
}
return response.json();
})
.then(data => {
setMessage(data.message);
fetchPromotions(); // Refresh the promotions list
})
.catch(error => {
console.error('Oh oh something went wrong', error);
setMessage('Oh oh something went wrong');
});
};
const handleStatusChange = (id, newStatus) => {
fetch(`${process.env.REACT_APP_API_URL}/api/promo/update-status`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id, status: newStatus })
})
.then(response => response.json())
.then(data => {
setPromotions(promotions.map(promo =>
promo.id === id ? { ...promo, status: newStatus } : promo
));
})
.catch(error => console.error('Oh oh something went wrong', error));
};
const handleCheckboxChange = (id) => {
setSelectedIds(prevSelectedIds => {
if (prevSelectedIds.includes(id)) {
return prevSelectedIds.filter(selectedId => selectedId !== id);
} else {
return [...prevSelectedIds, id];
}
});
};
const handleDeleteSelected = () => {
fetch(`${process.env.REACT_APP_API_URL}/api/promo/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ ids: selectedIds })
})
.then(response => response.json())
.then(data => {
setMessage(data.message);
fetchPromotions(); // Refresh the promotions list
setSelectedIds([]); // Clear selected IDs
})
.catch(error => console.error('Oh oh something went wrong', error));
};
const getFileURL = (filePath) => {
return `${process.env.REACT_APP_API_URL}/${(filePath)}`;
};
return (
<div className="container">
<h2>Upload Promotion Banner</h2>
<div className="upload-section">
<label>Upload Image</label>
<input type="file" accept="image/*" onChange={onImageChange} />
<label>Upload PDF</label>
<input type="file" accept="application/pdf" onChange={onPDFChange} />
<button onClick={onFileUpload}>
Upload!
</button>
</div>
<p className="message">{message}</p>
<h2>Promotion Banners</h2>
<div className="table-section">
<table>
<thead>
<tr>
<th>ID</th>
<th>Image</th>
<th>Document</th>
<th>Status</th>
<th>Change Status</th>
<th>Select</th>
</tr>
</thead>
<tbody>
{promotions.map(promo => (
<tr key={promo.id}>
<td>{promo.id}</td>
<td><img src={getFileURL(promo.promotion_image)} alt="promotion" width="100" /></td>
<td><a href={getFileURL(promo.promotion_document)} target="_blank" rel="noopener noreferrer">View PDF</a></td>
<td>{promo.status}</td>
<td>
<select
value={promo.status}
onChange={(e) => handleStatusChange(promo.id, e.target.value)}
>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</td>
<td>
<input
type="checkbox"
checked={selectedIds.includes(promo.id)}
onChange={() => handleCheckboxChange(promo.id)}
/>
</td>
</tr>
))}
</tbody>
</table>
<button className="delete-button" onClick={handleDeleteSelected}>
Delete Selected
</button>
</div>
</div>
);
}
export default PromoBannerConf;
</code>
<code>import React, { useEffect, useState } from 'react'; import './PromoBannerConf.css'; const PromoBannerConf = () => { const [selectedImage, setSelectedImage] = useState(null); const [selectedPDF, setSelectedPDF] = useState(null); const [message, setMessage] = useState(''); const [promotions, setPromotions] = useState([]); const [selectedIds, setSelectedIds] = useState([]); useEffect(() => { fetchPromotions(); }, []); const fetchPromotions = () => { fetch(`${process.env.REACT_APP_API_URL}/api/promo/all`) .then(response => response.json()) .then(data => { console.log('Fetched promotions:', data); setPromotions(data); }) .catch(error => console.error('Error fetching promotions:', error)); }; const onImageChange = event => { setSelectedImage(event.target.files[0]); }; const onPDFChange = event => { setSelectedPDF(event.target.files[0]); }; const onFileUpload = () => { if (!selectedImage || !selectedPDF) { setMessage('Both image and PDF are required'); return; } const formData = new FormData(); formData.append('promoImage', selectedImage); formData.append('promoPDF', selectedPDF); fetch(`${process.env.REACT_APP_API_URL}/api/promo/upload`, { method: 'POST', body: formData, }) .then(response => { if (!response.ok) { return response.text().then(text => { throw new Error(text) }); } return response.json(); }) .then(data => { setMessage(data.message); fetchPromotions(); // Refresh the promotions list }) .catch(error => { console.error('Oh oh something went wrong', error); setMessage('Oh oh something went wrong'); }); }; const handleStatusChange = (id, newStatus) => { fetch(`${process.env.REACT_APP_API_URL}/api/promo/update-status`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id, status: newStatus }) }) .then(response => response.json()) .then(data => { setPromotions(promotions.map(promo => promo.id === id ? { ...promo, status: newStatus } : promo )); }) .catch(error => console.error('Oh oh something went wrong', error)); }; const handleCheckboxChange = (id) => { setSelectedIds(prevSelectedIds => { if (prevSelectedIds.includes(id)) { return prevSelectedIds.filter(selectedId => selectedId !== id); } else { return [...prevSelectedIds, id]; } }); }; const handleDeleteSelected = () => { fetch(`${process.env.REACT_APP_API_URL}/api/promo/delete`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids: selectedIds }) }) .then(response => response.json()) .then(data => { setMessage(data.message); fetchPromotions(); // Refresh the promotions list setSelectedIds([]); // Clear selected IDs }) .catch(error => console.error('Oh oh something went wrong', error)); }; const getFileURL = (filePath) => { return `${process.env.REACT_APP_API_URL}/${(filePath)}`; }; return ( <div className="container"> <h2>Upload Promotion Banner</h2> <div className="upload-section"> <label>Upload Image</label> <input type="file" accept="image/*" onChange={onImageChange} /> <label>Upload PDF</label> <input type="file" accept="application/pdf" onChange={onPDFChange} /> <button onClick={onFileUpload}> Upload! </button> </div> <p className="message">{message}</p> <h2>Promotion Banners</h2> <div className="table-section"> <table> <thead> <tr> <th>ID</th> <th>Image</th> <th>Document</th> <th>Status</th> <th>Change Status</th> <th>Select</th> </tr> </thead> <tbody> {promotions.map(promo => ( <tr key={promo.id}> <td>{promo.id}</td> <td><img src={getFileURL(promo.promotion_image)} alt="promotion" width="100" /></td> <td><a href={getFileURL(promo.promotion_document)} target="_blank" rel="noopener noreferrer">View PDF</a></td> <td>{promo.status}</td> <td> <select value={promo.status} onChange={(e) => handleStatusChange(promo.id, e.target.value)} > <option value="active">Active</option> <option value="inactive">Inactive</option> </select> </td> <td> <input type="checkbox" checked={selectedIds.includes(promo.id)} onChange={() => handleCheckboxChange(promo.id)} /> </td> </tr> ))} </tbody> </table> <button className="delete-button" onClick={handleDeleteSelected}> Delete Selected </button> </div> </div> ); } export default PromoBannerConf; </code>
import React, { useEffect, useState } from 'react';
import './PromoBannerConf.css';

const PromoBannerConf = () => {
    const [selectedImage, setSelectedImage] = useState(null);
    const [selectedPDF, setSelectedPDF] = useState(null);
    const [message, setMessage] = useState('');
    const [promotions, setPromotions] = useState([]);
    const [selectedIds, setSelectedIds] = useState([]);

    useEffect(() => {
        fetchPromotions();
    }, []);

    const fetchPromotions = () => {
        fetch(`${process.env.REACT_APP_API_URL}/api/promo/all`)
            .then(response => response.json())
            .then(data => {
                console.log('Fetched promotions:', data);
                setPromotions(data);
            })
            .catch(error => console.error('Error fetching promotions:', error));
    };

    const onImageChange = event => {
        setSelectedImage(event.target.files[0]);
    };

    const onPDFChange = event => {
        setSelectedPDF(event.target.files[0]);
    };

    const onFileUpload = () => {
        if (!selectedImage || !selectedPDF) {
            setMessage('Both image and PDF are required');
            return;
        }

        const formData = new FormData();
        formData.append('promoImage', selectedImage);
        formData.append('promoPDF', selectedPDF);

        fetch(`${process.env.REACT_APP_API_URL}/api/promo/upload`, {
            method: 'POST',
            body: formData,
        })
        .then(response => {
            if (!response.ok) {
                return response.text().then(text => { throw new Error(text) });
            }
            return response.json();
        })
        .then(data => {
            setMessage(data.message);
            fetchPromotions(); // Refresh the promotions list
        })
        .catch(error => {
            console.error('Oh oh something went wrong', error);
            setMessage('Oh oh something went wrong');
        });
    };

    const handleStatusChange = (id, newStatus) => {
        fetch(`${process.env.REACT_APP_API_URL}/api/promo/update-status`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ id, status: newStatus })
        })
        .then(response => response.json())
        .then(data => {
            setPromotions(promotions.map(promo => 
                promo.id === id ? { ...promo, status: newStatus } : promo
            ));
        })
        .catch(error => console.error('Oh oh something went wrong', error));
    };

    const handleCheckboxChange = (id) => {
        setSelectedIds(prevSelectedIds => {
            if (prevSelectedIds.includes(id)) {
                return prevSelectedIds.filter(selectedId => selectedId !== id);
            } else {
                return [...prevSelectedIds, id];
            }
        });
    };

    const handleDeleteSelected = () => {
        fetch(`${process.env.REACT_APP_API_URL}/api/promo/delete`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ ids: selectedIds })
        })
        .then(response => response.json())
        .then(data => {
            setMessage(data.message);
            fetchPromotions(); // Refresh the promotions list
            setSelectedIds([]); // Clear selected IDs
        })
        .catch(error => console.error('Oh oh something went wrong', error));
    };

    const getFileURL = (filePath) => {
        return `${process.env.REACT_APP_API_URL}/${(filePath)}`;
    };

    return (
        <div className="container">
            <h2>Upload Promotion Banner</h2>
            <div className="upload-section">
                <label>Upload Image</label>
                <input type="file" accept="image/*" onChange={onImageChange} />
                <label>Upload PDF</label>
                <input type="file" accept="application/pdf" onChange={onPDFChange} />
                <button onClick={onFileUpload}>
                    Upload!
                </button>
            </div>
            <p className="message">{message}</p>
            <h2>Promotion Banners</h2>
            <div className="table-section">
                <table>
                    <thead>
                        <tr>
                            <th>ID</th>
                            <th>Image</th>
                            <th>Document</th>
                            <th>Status</th>
                            <th>Change Status</th>
                            <th>Select</th>
                        </tr>
                    </thead>
                    <tbody>
                        {promotions.map(promo => (
                            <tr key={promo.id}>
                                <td>{promo.id}</td>
                                <td><img src={getFileURL(promo.promotion_image)} alt="promotion" width="100" /></td>
                                <td><a href={getFileURL(promo.promotion_document)} target="_blank" rel="noopener noreferrer">View PDF</a></td>
                                <td>{promo.status}</td>
                                <td>
                                    <select
                                        value={promo.status}
                                        onChange={(e) => handleStatusChange(promo.id, e.target.value)}
                                    >
                                        <option value="active">Active</option>
                                        <option value="inactive">Inactive</option>
                                    </select>
                                </td>
                                <td>
                                    <input
                                        type="checkbox"
                                        checked={selectedIds.includes(promo.id)}
                                        onChange={() => handleCheckboxChange(promo.id)}
                                    />
                                </td>
                            </tr>
                        ))}
                    </tbody>
                </table>    
                <button className="delete-button" onClick={handleDeleteSelected}>
                    Delete Selected
                </button>
            </div>
        </div>
    );
}

export default PromoBannerConf;

Server side PromoBannerConf.js:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>const express = require("express");
const mysql = require('mysql2');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const router = express.Router();
var dbconfig = {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
connectionLimit: 15
};
console.log("Database configuration:", dbconfig);
var db_pool = mysql.createPool(dbconfig);
db_pool.getConnection((err, connection) => {
if (err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Database connection established');
connection.release();
});
function ensureDirExists(dir) {
if (!fs.existsSync(dir)){
fs.mkdirSync(dir, { recursive: true });
}
}
const storage = multer.diskStorage({
destination: function (req, file, cb) {
const dir = './files/promos';
ensureDirExists(dir);
cb(null, dir);
},
filename: function (req, file, cb) {
cb(null, file.originalname);
}
});
const upload = multer({
storage: storage,
limits: { fileSize: 500 * 10 ** 6 },
fileFilter: function (req, file, cb) {
checkFileType(file, cb);
}
}).fields([{ name: 'promoImage', maxCount: 1 }, { name: 'promoPDF', maxCount: 1 }]);
function checkFileType(file, cb) {
const filetypes = /jpeg|jpg|png|pdf/;
const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
const mimetype = filetypes.test(file.mimetype);
if (mimetype && extname) {
return cb(null, true);
} else {
cb('Error: Incorrect file type');
}
}
function checkDbConnection(req, res, next) {
db_pool.getConnection((err, connection) => {
if (err) {
console.error('Error connecting to the database:', err);
return res.status(500).json({ message: 'Database connection error', error: err });
}
connection.release();
next();
});
}
router.use(checkDbConnection);
router.get('/all', (req, res) => {
const sql = `SELECT * FROM ${process.env.DB_NAME}.promotion`;
db_pool.query(sql, (err, rows, fields) => {
if (err) {
console.error('Error fetching promotions:', err);
res.status(500).json({ message: "Error fetching promotions", error: err });
} else {
res.status(200).json(rows);
}
});
});
router.post('/upload', (req, res) => {
upload(req, res, (err) => {
if (err) {
console.error('Error during file upload:', err);
res.status(400).json({ message: err });
} else {
if (!req.files.promoImage || !req.files.promoPDF) {
res.status(400).json({ message: 'Both image and PDF are required' });
} else {
const imagePath = `./files/promos/${req.files.promoImage[0].originalname}`;
const pdfPath = `./files/promos/${req.files.promoPDF[0].originalname}`;
const sql = 'INSERT INTO promotion (promotion_image, promotion_document, status) VALUES (?, ?, ?)';
const values = [imagePath, pdfPath, 'active'];
db_pool.query(sql, values, (dbErr, result) => {
if (dbErr) {
console.error('Error inserting into database:', dbErr);
res.status(500).json({ message: 'Error inserting into database', error: dbErr });
} else {
res.status(200).json({
message: 'Promotion uploaded successfully',
file: imagePath
});
}
});
}
}
});
});
router.post('/update-status', (req, res) => {
const { id, status } = req.body;
const sql = `UPDATE ${process.env.DB_NAME}.promotion SET status = ? WHERE id = ?`;
db_pool.query(sql, [status, id], (err, result) => {
if (err) {
console.error('Error updating status:', err);
res.status(500).json({ message: 'Error updating status', error: err });
} else {
res.status(200).json({ message: 'Status updated successfully' });
}
});
});
router.post('/delete', (req, res) => {
const { ids } = req.body;
const sqlGetPaths = `SELECT promotion_image, promotion_document FROM ${process.env.DB_NAME}.promotion WHERE id IN (?)`;
db_pool.query(sqlGetPaths, [ids], (err, results) => {
if (err) {
console.error('Error fetching file paths:', err);
res.status(500).json({ message: 'Error fetching file paths', error: err });
return;
}
results.forEach(result => {
const imagePath = result.promotion_image;
const pdfPath = result.promotion_document;
fs.unlink(imagePath, (unlinkErr) => {
if (unlinkErr) {
console.error('Error deleting image file:', unlinkErr);
}
});
fs.unlink(pdfPath, (unlinkErr) => {
if (unlinkErr) {
console.error('Error deleting PDF file:', unlinkErr);
}
});
});
const sqlDelete = `DELETE FROM ${process.env.DB_NAME}.promotion WHERE id IN (?)`;
db_pool.query(sqlDelete, [ids], (dbErr, result) => {
if (dbErr) {
console.error('Error deleting records:', dbErr);
res.status(500).json({ message: 'Error deleting records', error: dbErr });
} else {
res.status(200).json({ message: 'Promotions deleted successfully' });
}
});
});
});
module.exports = router;
</code>
<code>const express = require("express"); const mysql = require('mysql2'); const multer = require('multer'); const path = require('path'); const fs = require('fs'); const router = express.Router(); var dbconfig = { host: process.env.DB_HOST, port: process.env.DB_PORT, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, connectionLimit: 15 }; console.log("Database configuration:", dbconfig); var db_pool = mysql.createPool(dbconfig); db_pool.getConnection((err, connection) => { if (err) { console.error('Error connecting to the database:', err); return; } console.log('Database connection established'); connection.release(); }); function ensureDirExists(dir) { if (!fs.existsSync(dir)){ fs.mkdirSync(dir, { recursive: true }); } } const storage = multer.diskStorage({ destination: function (req, file, cb) { const dir = './files/promos'; ensureDirExists(dir); cb(null, dir); }, filename: function (req, file, cb) { cb(null, file.originalname); } }); const upload = multer({ storage: storage, limits: { fileSize: 500 * 10 ** 6 }, fileFilter: function (req, file, cb) { checkFileType(file, cb); } }).fields([{ name: 'promoImage', maxCount: 1 }, { name: 'promoPDF', maxCount: 1 }]); function checkFileType(file, cb) { const filetypes = /jpeg|jpg|png|pdf/; const extname = filetypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = filetypes.test(file.mimetype); if (mimetype && extname) { return cb(null, true); } else { cb('Error: Incorrect file type'); } } function checkDbConnection(req, res, next) { db_pool.getConnection((err, connection) => { if (err) { console.error('Error connecting to the database:', err); return res.status(500).json({ message: 'Database connection error', error: err }); } connection.release(); next(); }); } router.use(checkDbConnection); router.get('/all', (req, res) => { const sql = `SELECT * FROM ${process.env.DB_NAME}.promotion`; db_pool.query(sql, (err, rows, fields) => { if (err) { console.error('Error fetching promotions:', err); res.status(500).json({ message: "Error fetching promotions", error: err }); } else { res.status(200).json(rows); } }); }); router.post('/upload', (req, res) => { upload(req, res, (err) => { if (err) { console.error('Error during file upload:', err); res.status(400).json({ message: err }); } else { if (!req.files.promoImage || !req.files.promoPDF) { res.status(400).json({ message: 'Both image and PDF are required' }); } else { const imagePath = `./files/promos/${req.files.promoImage[0].originalname}`; const pdfPath = `./files/promos/${req.files.promoPDF[0].originalname}`; const sql = 'INSERT INTO promotion (promotion_image, promotion_document, status) VALUES (?, ?, ?)'; const values = [imagePath, pdfPath, 'active']; db_pool.query(sql, values, (dbErr, result) => { if (dbErr) { console.error('Error inserting into database:', dbErr); res.status(500).json({ message: 'Error inserting into database', error: dbErr }); } else { res.status(200).json({ message: 'Promotion uploaded successfully', file: imagePath }); } }); } } }); }); router.post('/update-status', (req, res) => { const { id, status } = req.body; const sql = `UPDATE ${process.env.DB_NAME}.promotion SET status = ? WHERE id = ?`; db_pool.query(sql, [status, id], (err, result) => { if (err) { console.error('Error updating status:', err); res.status(500).json({ message: 'Error updating status', error: err }); } else { res.status(200).json({ message: 'Status updated successfully' }); } }); }); router.post('/delete', (req, res) => { const { ids } = req.body; const sqlGetPaths = `SELECT promotion_image, promotion_document FROM ${process.env.DB_NAME}.promotion WHERE id IN (?)`; db_pool.query(sqlGetPaths, [ids], (err, results) => { if (err) { console.error('Error fetching file paths:', err); res.status(500).json({ message: 'Error fetching file paths', error: err }); return; } results.forEach(result => { const imagePath = result.promotion_image; const pdfPath = result.promotion_document; fs.unlink(imagePath, (unlinkErr) => { if (unlinkErr) { console.error('Error deleting image file:', unlinkErr); } }); fs.unlink(pdfPath, (unlinkErr) => { if (unlinkErr) { console.error('Error deleting PDF file:', unlinkErr); } }); }); const sqlDelete = `DELETE FROM ${process.env.DB_NAME}.promotion WHERE id IN (?)`; db_pool.query(sqlDelete, [ids], (dbErr, result) => { if (dbErr) { console.error('Error deleting records:', dbErr); res.status(500).json({ message: 'Error deleting records', error: dbErr }); } else { res.status(200).json({ message: 'Promotions deleted successfully' }); } }); }); }); module.exports = router; </code>
const express = require("express");
const mysql = require('mysql2');
const multer = require('multer');
const path = require('path');
const fs = require('fs');

const router = express.Router();

var dbconfig = {
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.DB_NAME,
    connectionLimit: 15
};

console.log("Database configuration:", dbconfig);

var db_pool = mysql.createPool(dbconfig);

db_pool.getConnection((err, connection) => {
    if (err) {
        console.error('Error connecting to the database:', err);
        return;
    }
    console.log('Database connection established');
    connection.release();
});

function ensureDirExists(dir) {
    if (!fs.existsSync(dir)){
        fs.mkdirSync(dir, { recursive: true });
    }
}

const storage = multer.diskStorage({
    destination: function (req, file, cb) {
        const dir = './files/promos';
        ensureDirExists(dir);
        cb(null, dir);
    },
    filename: function (req, file, cb) {
        cb(null, file.originalname);
    }
});

const upload = multer({
    storage: storage,
    limits: { fileSize: 500 * 10 ** 6 },
    fileFilter: function (req, file, cb) {
        checkFileType(file, cb);
    }
}).fields([{ name: 'promoImage', maxCount: 1 }, { name: 'promoPDF', maxCount: 1 }]);

function checkFileType(file, cb) {
    const filetypes = /jpeg|jpg|png|pdf/;
    const extname = filetypes.test(path.extname(file.originalname).toLowerCase());
    const mimetype = filetypes.test(file.mimetype);

    if (mimetype && extname) {
        return cb(null, true);
    } else {
        cb('Error: Incorrect file type');
    }
}

function checkDbConnection(req, res, next) {
    db_pool.getConnection((err, connection) => {
        if (err) {
            console.error('Error connecting to the database:', err);
            return res.status(500).json({ message: 'Database connection error', error: err });
        }
        connection.release();
        next();
    });
}

router.use(checkDbConnection);

router.get('/all', (req, res) => {
    const sql = `SELECT * FROM ${process.env.DB_NAME}.promotion`;
    db_pool.query(sql, (err, rows, fields) => {
        if (err) {
            console.error('Error fetching promotions:', err);
            res.status(500).json({ message: "Error fetching promotions", error: err });
        } else {
            res.status(200).json(rows);
        }
    });
});

router.post('/upload', (req, res) => {
    upload(req, res, (err) => {
        if (err) {
            console.error('Error during file upload:', err);
            res.status(400).json({ message: err });
        } else {
            if (!req.files.promoImage || !req.files.promoPDF) {
                res.status(400).json({ message: 'Both image and PDF are required' });
            } else {
                const imagePath = `./files/promos/${req.files.promoImage[0].originalname}`;
                const pdfPath = `./files/promos/${req.files.promoPDF[0].originalname}`;

                const sql = 'INSERT INTO promotion (promotion_image, promotion_document, status) VALUES (?, ?, ?)';
                const values = [imagePath, pdfPath, 'active'];

                db_pool.query(sql, values, (dbErr, result) => {
                    if (dbErr) {
                        console.error('Error inserting into database:', dbErr);
                        res.status(500).json({ message: 'Error inserting into database', error: dbErr });
                    } else {
                        res.status(200).json({
                            message: 'Promotion uploaded successfully',
                            file: imagePath
                        });
                    }
                });
            }
        }
    });
});

router.post('/update-status', (req, res) => {
    const { id, status } = req.body;
    const sql = `UPDATE ${process.env.DB_NAME}.promotion SET status = ? WHERE id = ?`;
    db_pool.query(sql, [status, id], (err, result) => {
        if (err) {
            console.error('Error updating status:', err);
            res.status(500).json({ message: 'Error updating status', error: err });
        } else {
            res.status(200).json({ message: 'Status updated successfully' });
        }
    });
});

router.post('/delete', (req, res) => {
    const { ids } = req.body;

    const sqlGetPaths = `SELECT promotion_image, promotion_document FROM ${process.env.DB_NAME}.promotion WHERE id IN (?)`;
    db_pool.query(sqlGetPaths, [ids], (err, results) => {
        if (err) {
            console.error('Error fetching file paths:', err);
            res.status(500).json({ message: 'Error fetching file paths', error: err });
            return;
        }

        results.forEach(result => {
            const imagePath = result.promotion_image;
            const pdfPath = result.promotion_document;
            fs.unlink(imagePath, (unlinkErr) => {
                if (unlinkErr) {
                    console.error('Error deleting image file:', unlinkErr);
                }
            });
            fs.unlink(pdfPath, (unlinkErr) => {
                if (unlinkErr) {
                    console.error('Error deleting PDF file:', unlinkErr);
                }
            });
        });

        const sqlDelete = `DELETE FROM ${process.env.DB_NAME}.promotion WHERE id IN (?)`;
        db_pool.query(sqlDelete, [ids], (dbErr, result) => {
            if (dbErr) {
                console.error('Error deleting records:', dbErr);
                res.status(500).json({ message: 'Error deleting records', error: dbErr });
            } else {
                res.status(200).json({ message: 'Promotions deleted successfully' });
            }
        });
    });
});

module.exports = router;

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật