Add all project files - optimization and design changes

This commit is contained in:
Your Name 2025-11-11 19:49:34 +05:30
parent 03ac886ecf
commit 5783d4386e
893 changed files with 186164 additions and 0 deletions

15
.eslintrc.cjs Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:react/recommended',
'plugin:react/jsx-runtime',
'plugin:react-hooks/recommended',
],
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
settings: { react: { version: '18.2' } },
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
},
}

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

BIN
Before Integrated Code.zip Normal file

Binary file not shown.

46
config.js Normal file
View File

@ -0,0 +1,46 @@
// CDN Configuration for React + Vite + JavaScript
// Auto detect localhost vs production - No environment variable needed!
const getCDNBase = () => {
// Vite environment variable check first (optional)
if (import.meta.env.VITE_CDN_URL) {
return import.meta.env.VITE_CDN_URL;
}
// Browser la automatic detection
if (typeof window !== "undefined") {
const hostname = window.location.hostname;
// Local development
if (hostname === "localhost" || hostname === "127.0.0.1") {
return "http://192.168.1.26/home/assets";
}
// Production - pozo.dev or pozo.app
if (hostname.includes("pozo.dev") || hostname.includes("pozo.app")) {
return "http://192.168.1.26/home/assets";
}
// Default - use current protocol and hostname
return `${window.location.protocol}//${hostname}/home/assets`;
}
// Fallback
return "http://192.168.1.26/home/assets";
};
// Export CDN base URL (ready to use!)
export const CDN_BASE = getCDNBase();
// Helper function to get full CDN URL
export const getCDNUrl = (filename) => {
// Remove leading slash if present
const cleanFilename = filename.startsWith("/") ? filename.slice(1) : filename;
return `${CDN_BASE}/${cleanFilename}`;
};
// Default export
export default {
CDN_BASE,
getCDNUrl,
};

28
context/AuthContext Normal file
View File

@ -0,0 +1,28 @@
// AuthContext.jsx
import { createContext, useContext, useEffect, useState } from "react";
const AuthCtx = createContext(null);
export const useAuth = () => useContext(AuthCtx);
export function AuthProvider({ children }) {
const [hasAuthorizedSession, setHasAuthorizedSession] = useState(false);
const [isAuthResolved, setIsAuthResolved] = useState(false);
useEffect(() => {
(async () => {
try {
// simulate or implement real check (localStorage/cookie/ping)
const token = localStorage.getItem("pozo_token");
setHasAuthorizedSession(!!token);
} finally {
setIsAuthResolved(true);
}
})();
}, []);
return (
<AuthCtx.Provider value={{ hasAuthorizedSession, isAuthResolved }}>
{children}
</AuthCtx.Provider>
);
}

71
index.html Normal file
View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.cdnfonts.com/css/wasted-vindey" rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,400;0,500;0,600;0,700;1,200&display=swap"
rel="stylesheet">
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Manrope:wght@200;300;400;500;600;700;800&family=Montserrat:wght@400;500;600;700&family=Poppins:ital,wght@1,200&display=swap"
rel="stylesheet">
<!-- <link href="https://vjs.zencdn.net/8.3.0/video-js.css" rel="stylesheet" /> -->
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<link rel="shortcut icon" href="./src/Images/PozoAppFavicon.png" type="image/x-icon">
<title>PozoApp - Complete Business Management Solution | POS & Inventory</title>
<!-- mohan -->
<!-- <script>
// Disable right-click menu and common hotkeys
document.addEventListener("contextmenu", (event) => event.preventDefault());
document.addEventListener("keydown", (event) => {
if (
(event.ctrlKey && event.shiftKey && ["I", "J", "C"].includes(event.key.toUpperCase())) ||
(event.ctrlKey && ["U", "S"].includes(event.key.toUpperCase())) ||
event.key === "F12"
) {
event.preventDefault();
return false;
}
});
</script> -->
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<!-- <script src="https://vjs.zencdn.net/8.3.0/video.min.js"></script> -->
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<!-- Start of LiveChat (www.livechat.com) code -->
<!-- <script>
window.__lc = window.__lc || {};
window.__lc.license = 18861501;
window.__lc.integration_name = "manual_onboarding";
window.__lc.product_name = "livechat";
;(function(n,t,c){function i(n){return e._h?e._h.apply(null,n):e._q.push(n)}var e={_q:[],_h:null,_v:"2.0",on:function(){i(["on",c.call(arguments)])},once:function(){i(["once",c.call(arguments)])},off:function(){i(["off",c.call(arguments)])},get:function(){if(!e._h)throw new Error("[LiveChatWidget] You can't use getters before load.");return i(["get",c.call(arguments)])},call:function(){i(["call",c.call(arguments)])},init:function(){var n=t.createElement("script");n.async=!0,n.type="text/javascript",n.src="https://cdn.livechatinc.com/tracking.js",t.head.appendChild(n)}};!n.__lc.asyncInit&&e.init(),n.LiveChatWidget=n.LiveChatWidget||e}(window,document,[].slice))
</script> -->
<noscript><a href="https://www.livechat.com/chat-with/18861501/" rel="nofollow">Chat with us</a>, powered by <a
href="https://www.livechat.com/?welcome" rel="noopener nofollow" target="_blank">LiveChat</a></noscript>
<!-- End of LiveChat code -->
</body>
<iframe id="ifmcontentstoprint" style="
height: 0px;
width: 0px;
position: absolute;
padding: 0px;
margin: 0px;
font-family: 'Poppins', sans-serif;
"></iframe>
</html>

12575
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

96
package.json Normal file
View File

@ -0,0 +1,96 @@
{
"name": "pozo",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"postbuild": "echo 'Skipping react-snap'",
"lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"reactSnap": {
"source": "dist",
"inlineCss": false,
"puppeteerArgs": [
"--no-sandbox"
],
"skipThirdPartyRequests": true,
"routes": [
"/",
"/signin",
"/signup",
"/dashboard",
"/features",
"/pricing",
"/contact-us",
"/blog"
]
},
"dependencies": {
"@editorjs/code": "^2.9.3",
"@editorjs/editorjs": "^2.31.0",
"@editorjs/embed": "^2.7.6",
"@editorjs/header": "^2.8.8",
"@editorjs/image": "^2.10.3",
"@editorjs/link": "^2.6.2",
"@editorjs/list": "^2.0.8",
"@editorjs/paragraph": "^2.11.7",
"@editorjs/quote": "^2.7.6",
"@reduxjs/toolkit": "^1.9.5",
"@studio-freight/lenis": "^1.0.42",
"@weekwood/editorjs-video": "^1.0.2",
"antd": "^5.4.4",
"aos": "^2.3.4",
"axios": "^1.4.0",
"bowser": "^2.11.0",
"classnames": "^2.3.2",
"crypto-js": "^4.1.1",
"devtools-detect": "^4.0.2",
"exceljs": "^4.3.0",
"framer-motion": "^12.17.0",
"google-maps-react": "^2.0.6",
"gsap": "^3.13.0",
"html2canvas": "^1.4.1",
"html2pdf.js": "^0.10.3",
"informed": "^4.44.1",
"jquery": "^3.7.1",
"jspdf": "^2.5.1",
"moment": "^2.29.4",
"qrcode.react": "^4.2.0",
"react": "^18.2.0",
"react-color": "^2.19.3",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-excel-renderer": "^1.1.0",
"react-helmet-async": "^2.0.5",
"react-icons": "^4.8.0",
"react-qr-code": "^2.0.15",
"react-redux": "^8.0.5",
"react-router-dom": "^6.11.0",
"react-slick": "^0.29.0",
"rollup-plugin-terser": "^7.0.2",
"sass": "^1.62.0",
"slick-carousel": "^1.8.1",
"split-type": "^0.3.4",
"swiper": "^10.2.0",
"universal-cookie": "^4.0.4",
"uuid": "^11.1.0",
"webfontloader": "^1.6.28",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@vitejs/plugin-react": "^4.0.0-beta.0",
"eslint": "^8.38.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.3.4",
"react-snap": "^1.23.0",
"vite": "^4.3.0",
"vite-plugin-prerender": "^1.0.8",
"vite-plugin-sitemap": "^0.8.2"
}
}

BIN
public/fav.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/favlogo 1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
public/og/Signin-og.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
public/og/blog-og.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

BIN
public/og/contact-og.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
public/og/default-og.jpg Normal file

Binary file not shown.

BIN
public/og/home.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

BIN
public/og/pricing-og.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

33
public/robots.txt Normal file
View File

@ -0,0 +1,33 @@
User-agent: *
# Allow top-level site content and important pages
Allow: /
Allow: /features
Allow: /pricing
Allow: /contact-us
Allow: /blog
# Allow common public asset folders
Allow: /assets/
Allow: /images/
Allow: /css/
Allow: /js/
# Disallow admin, build and development files
Disallow: /admin
Disallow: /AdminPanel
Disallow: /*.env
Disallow: /src/
Disallow: /dist/
Disallow: /node_modules/
# Sitemap location (replace if your production domain differs)
Sitemap: https://www.pozo.app/sitemap.xml
# Crawl delay (optional)
Crawl-delay: 1
# Notes:
# - This file is served from /public when using Vite. Ensure production deploy serves this file at https://your-domain/robots.txt
# - Robots rules are advisory; protect sensitive routes server-side as well.

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

28
robots.txt Normal file
View File

@ -0,0 +1,28 @@
User-agent: *
# Allow important pages
Allow: /
Allow: /features
Allow: /pricing
Allow: /contact-us
Allow: /blog
# Allow assets
Allow: /assets/
Allow: /images/
Allow: /css/
Allow: /js/
# Disallow sensitive folders
Disallow: /admin
Disallow: /AdminPanel
Disallow: /*.env
Disallow: /src/
Disallow: /dist/
Disallow: /node_modules/
# Sitemap
Sitemap: https://www.pozo.app/sitemap.xml
# Crawl delay
Crawl-delay: 1

104
server.js Normal file
View File

@ -0,0 +1,104 @@
// server.js - IIS Node.js Proxy for React + .NET Backend
const express = require('express');
const path = require('path');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
const PORT = process.env.PORT || 3001;
const isProduction = process.env.NODE_ENV === 'production';
// Configuration
const config = {
distPath: path.join(__dirname, 'Update Common - Vignesh', 'dist'),
dotnetApiUrl: process.env.DOTNET_API_URL || 'http://localhost:5000'
};
console.log('🚀 Starting PozoApp Server...');
console.log('📦 Environment:', isProduction ? 'Production' : 'Development');
console.log('📁 Serving React from:', config.distPath);
// Middleware - Serve static files from React build
app.use(express.static(config.distPath, {
maxAge: isProduction ? '1d' : 0,
etag: true
}));
// Proxy .NET API calls
app.use('/api', createProxyMiddleware({
target: config.dotnetApiUrl,
changeOrigin: true,
pathRewrite: {
'^/api': '/api' // Keep /api prefix
},
onProxyReq: (proxyReq, req, res) => {
console.log('🔗 API Proxy:', req.method, req.url, '→', config.dotnetApiUrl);
},
onError: (err, req, res) => {
console.error('❌ Proxy error:', err.message);
res.status(500).send('Backend API unavailable');
}
}));
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'OK',
environment: process.env.NODE_ENV,
timestamp: new Date().toISOString()
});
});
// React SPA - Handle all frontend routes
// Clean URLs without /home/ prefix
const reactRoutes = [
'/',
'/blog',
'/pricing',
'/contact-us',
'/signin',
'/faq',
'/testimonials',
'/privacy-policy',
'/cookie-policy',
'/about-us',
'/live-session',
'/adminpanel',
'/landing-page/*',
'/setting/*',
'/:appName' // Dynamic app routes (bakery, restaurant, etc.)
];
// Serve index.html for all non-API routes
app.get('*', (req, res) => {
const indexPath = path.join(config.distPath, 'index.html');
// Log for debugging
console.log('📄 Serving React SPA for:', req.url);
res.sendFile(indexPath, (err) => {
if (err) {
console.error('❌ Error serving index.html:', err);
res.status(500).send('Error loading application');
}
});
});
// Error handling
app.use((err, req, res, next) => {
console.error('❌ Server Error:', err);
res.status(500).send('Internal Server Error');
});
// Start server
app.listen(PORT, () => {
console.log(`✅ PozoApp Server listening on port ${PORT}`);
console.log(`🌐 Access at: http://localhost:${PORT}`);
console.log(`📊 Clean URLs enabled (no /home/ prefix)`);
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('🛑 SIGTERM received, shutting down gracefully...');
process.exit(0);
});

60
sitemap.xml Normal file
View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">
<!-- Home -->
<url>
<loc>https://www.pozo.app/</loc>
<lastmod>2025-10-13</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<!-- Core pages (adjust if your slugs differ) -->
<!-- <url>
<loc>https://www.pozo.app/features</loc>
<lastmod>2025-10-13</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url> -->
<url>
<loc>https://www.pozo.app/pricing</loc>
<lastmod>2025-10-13</lastmod>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://www.pozo.app/contact-us</loc>
<lastmod>2025-10-13</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<!-- Blog hub -->
<url>
<loc>https://www.pozo.app/blog</loc>
<lastmod>2025-10-13</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<!-- Sign In -->
<url>
<loc>https://www.pozo.app/signin</loc>
<lastmod>2025-10-13</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
<!-- Example blog posts (replace with your real slugs weekly) -->
<!-- <url>
<loc>https://www.pozo.app/blog/pos-billing-for-retail</loc>
<lastmod>2025-10-13</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://www.pozo.app/blog/restaurant-billing-with-pozoapp</loc>
<lastmod>2025-10-13</lastmod>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url> -->
</urlset>

18
src/.env.development Normal file
View File

@ -0,0 +1,18 @@
ENV_BASE_URL='/'
ENV_API_URL='http://192.168.1.37:8013'
ENV_API_URL_TOKEN='http://192.168.1.37:8001'
# ENV_API_URL_TOKEN='https://www.pozo.dev/JwtToken'
# ENV_API_URL='https://www.pozo.dev/pozo-common-api'
# ENV_IMAGE_UPLOAD_API_URL="https://api.pozo.app/pozo-common-image-api/"
ENV_API_URL_RETAIL='http://192.168.1.37:8012/'
# ENV_API_URL_TOKEN='https://api.pozo.app/JwtToken'
# ENV_API_URL='https://api.pozo.app/pozo-common-api'
# ENV_API_URL_RETAIL='https://api.pozo.app/pozo-retail-api'
ENV_IMAGE_UPLOAD_API_URL="http://192.168.1.38/"
ENV_SECRET_KEY="3a939a2eed2cca4b64cd47f51b23b135b2b9b765273f695d47682ea803c8429d"
ENV_URL_SECRET_KEY="f2ef79b8b2d4fc1cc569726a5a753552257b4ff2872d6a71cf3119bce3f07c1b"
ENV_MAIN_BASE_URL='http://192.168.1.16:3000'
ENV_MAIN_REDIRECT_URL='/apps/retail/app-page/home'
ENV_EMAIL_API='http://192.168.1.37:8014'
ENV_IMAGE_UPLOAD_API_URL="http://192.168.1.38/"

42
src/.env.production Normal file
View File

@ -0,0 +1,42 @@
#221 server
# ENV_BASE_URL='/home/'
# ENV_API_URL='http://api.pozo.co.in/Pozocommonapi_UnderTest'
# # ENV_API_URL='http://api.pozo.co.in/Pozocommonapi'
# ENV_IMAGE_UPLOAD_API_URL="https://api.pozo.app/pozo-common-image-api"
# ENV_SECRET_KEY="3a939a2eed2cca4b64cd47f51b23b135b2b9b765273f695d47682ea803c8429d"
# ENV_URL_SECRET_KEY="f2ef79b8b2d4fc1cc569726a5a753552257b4ff2872d6a71cf3119bce3f07c1b"
# ENV_MAIN_BASE_URL='http://pozo.co.in/'
# ENV_MAIN_REDIRECT_URL='/apps/retail/app-page/home'
# live server(#173 server)
# ENV_BASE_URL='/home/'
# ENV_API_URL_TOKEN='https://api.pozo.app/JwtToken'
# ENV_API_URL='https://api.pozo.app/pozo-common-api'
# ENV_IMAGE_UPLOAD_API_URL="https://api.pozo.app/pozo-common-image-api"
# ENV_SECRET_KEY="3a939a2eed2cca4b64cd47f51b23b135b2b9b765273f695d47682ea803c8429d"
# ENV_URL_SECRET_KEY="f2ef79b8b2d4fc1cc569726a5a753552257b4ff2872d6a71cf3119bce3f07c1b"
# ENV_MAIN_BASE_URL='https://www.pozo.app'
# ENV_MAIN_REDIRECT_URL='/apps/retail/app-page/home'
# ENV_PAYMENT_URL='https://pozo.app/paymentgateway/PozoPaymentGateway'
# ENV_CUSTOM_PAYMENT_URL='https://pozo.app/CustomPaymentGateway/CustomPaymentGateway'
# ENV_EMAIL_API='https://api.pozo.app/pozo-sms-email-template-api'
# ENV_API_URL_RETAIL='https://api.pozo.app/pozo-retail-api'
#172 server
ENV_BASE_URL='/home/'
ENV_API_URL_TOKEN='https://www.pozo.dev/JwtToken'
ENV_API_URL='https://www.pozo.dev/pozo-common-api'
ENV_API_URL_RETAIL='https://www.pozo.dev/pozo-retail-api'
ENV_IMAGE_UPLOAD_API_URL="https://www.pozo.dev/pozo-common-image-api"
ENV_SECRET_KEY="3a939a2eed2cca4b64cd47f51b23b135b2b9b765273f695d47682ea803c8429d"
ENV_URL_SECRET_KEY="f2ef79b8b2d4fc1cc569726a5a753552257b4ff2872d6a71cf3119bce3f07c1b"
ENV_MAIN_BASE_URL='https://www.pozo.dev'
ENV_MAIN_REDIRECT_URL='/apps/retail/app-page/home'
ENV_PAYMENT_URL='https://pozo.app/paymentgateway/PozoPaymentGateway'
ENV_CUSTOM_PAYMENT_URL='https://pozo.app/CustomPaymentGateway/CustomPaymentGateway'
ENV_EMAIL_API='https://www.pozo.dev/pozo-sms-email-template-api'

View File

@ -0,0 +1,298 @@
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import "./AdminPanel.scss";
import {
FaTh,
FaUser,
FaUsers,
FaCalendarAlt,
FaBullseye,
FaVideo,
FaChevronRight,
FaPlus,
FaList,
FaSortDown,
FaArrowLeft,
FaRedo,
FaSignOutAlt,
FaCog,
FaGift,
FaGlobe,
FaImage,
FaMobile,
FaQuestionCircle,
FaBullhorn,
FaFootballBall,
FaBars,
FaTimes,
} from "react-icons/fa";
import { BsPostcardFill } from "react-icons/bs";
import { HiOutlineHome } from "react-icons/hi2";
import { useAdminPanel } from "./AdminPanelContext";
import BannerSectionForm from "./AdminPanelForms/BannerSectionForm";
import CTASectionForm from "./AdminPanelForms/CTASectionForm";
import EcosystemForm from "./AdminPanelForms/EcosystemForm";
import FaqSectionForm from "./AdminPanelForms/FaqSectionForm";
import FooterForm from "./AdminPanelForms/FooterForm";
import HeroSectionForm from "./AdminPanelForms/HeroSectionForm";
import OfferingsForm from "./AdminPanelForms/OfferingsForm";
import TrendingAppsForm from "./AdminPanelForms/TrendingAppsForm";
import AppDemo from "./AdminPanelForms/AppDemo";
import BlogForm from "./AdminPanelForms/BlogForm";
import SeoForm from "./AdminPanelForms/SeoForm";
import SEO from "../Components/SEO/SEO";
import { clearSession, getSession } from "../Services/others";
import { GenerateLogout } from "../features/signInPage/signInPage";
import { useDispatch } from "react-redux";
const subDirectory = import.meta.env.BASE_URL;
const AdminPanel = () => {
// Default to first option (HeroSectionForm)
const [selectedId, setSelectedId] = useState(0);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const UserType = getSession("UserType");
console.log(UserType, "UserTypeUserType")
const sidebarOptions = [
// { id: 1, title: "Dashboard", subtitle: "Overview", icon: <FaTh /> },
{
id: 0,
title: "Blog",
subtitle: "Post Content Control",
icon: <BsPostcardFill size={25} />,
component: BlogForm,
key: "BlogSection",
},
{
id: 10,
title: "SEO",
subtitle: "Content Management",
icon: <FaUser />,
component: SeoForm,
key: "SEOForm",
},
// {
// id: 1,
// title: "HeroSection",
// subtitle: "Hero Section Management",
// icon: <FaUser />,
// component: HeroSectionForm,
// key: "HeroSection",
// },
// {
// id: 2,
// title: "Offerings",
// subtitle: "Offering Section Management",
// icon: <FaGift />,
// component: OfferingsForm,
// key: "OfferingSection",
// },
// {
// id: 3,
// title: "Industries",
// subtitle: "Ecosystem Management",
// icon: <FaGlobe />,
// component: EcosystemForm,
// key: "Industries",
// },
// {
// id: 4,
// title: "AppDemo",
// subtitle: "Video Section Management",
// icon: <FaVideo />,
// component: AppDemo,
// key: "VideoDemo",
// },
// {
// id: 5,
// title: "BannerSection",
// subtitle: "Banner Section Management",
// icon: <FaImage />,
// component: BannerSectionForm,
// key: "BannerSection",
// },
// {
// id: 6,
// title: "TrendingApps",
// subtitle: "Trending Apps Management",
// icon: <FaMobile />,
// component: TrendingAppsForm,
// key: "TrendingApps",
// },
// {
// id: 7,
// title: "FaqSection",
// subtitle: "FAQ Management",
// icon: <FaQuestionCircle />,
// component: FaqSectionForm,
// key: "FAQSection",
// },
// {
// id: 8,
// title: "CTASection",
// subtitle: "Call-to-Action Management",
// icon: <FaBullhorn />,
// component: CTASectionForm,
// key: "CTASection",
// },
// {
// id: 9,
// title: "Footer",
// subtitle: "Footer Management",
// icon: <FaFootballBall />,
// component: FooterForm,
// key: "FooterSection",
// },
];
const currentSection = sidebarOptions.find(
(option) => option.id === selectedId
);
const handleSideBarSelect = (option) => {
setSelectedId(option?.id);
setIsMobileMenuOpen(false); // Close mobile menu on selection
};
const handleSignOut = async () => {
const UserId = getSession("UserId");
const status = "N";
const res = await dispatch(GenerateLogout({ UserId, status })).unwrap();
if (res?.data?.statusCode === 1) {
clearSession();
navigate(`${subDirectory}`);
}
}
return (
<>
<SEO
title="PozoApp Admin Panel - Manage Your Business Settings"
description="Access PozoApp's comprehensive admin panel to configure your business settings, manage users, and customize your experience."
keywords="admin panel, business settings, user management, configuration"
url={`${subDirectory}admin-panel`}
image="/og/admin-og.jpg"
type="website"
noindex={true}
/>
<div className="admin-panel-container">
{/* Mobile Menu Toggle */}
<div className="admin-panel-mobile-header">
<div className="admin-panel-mobile-logo">
<FaCog /> Admin Panel
</div>
<button
className="admin-panel-mobile-toggle"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
>
{isMobileMenuOpen ? <FaTimes /> : <FaBars />}
</button>
</div>
{/* Sidebar Navigation */}
<div
className={`admin-panel-sidebar ${isMobileMenuOpen ? "mobile-open" : ""
}`}
>
<div className="admin-panel-sidebar-header">
<div className="admin-panel-logo">
<FaCog />
<h1>Admin Panel</h1>
</div>
</div>
<nav className="admin-panel-sidebar-nav">
{sidebarOptions.map((option) => (
<div
key={option.id}
className={`admin-panel-nav-item ${selectedId === option.id ? "active" : ""
}`}
onClick={() => handleSideBarSelect(option)}
>
<div className="admin-panel-nav-icon">{option.icon}</div>
<div className="admin-panel-nav-content">
<span className="admin-panel-nav-title">{option.title}</span>
<span className="admin-panel-nav-subtitle">
{option.subtitle}
</span>
</div>
{selectedId === option.id && (
<div className="admin-panel-active-indicator"></div>
)}
</div>
))}
</nav>
<div className="admin-panel-sidebar-footer">
<div className="admin-panel-user-profile">
<div className="admin-panel-user-avatar">SA</div>
<div className="admin-panel-user-info">
<span className="admin-panel-user-name">
Marketing Administrator
</span>
<span className="admin-panel-user-role">Super Admin</span>
</div>
</div>
<button
className="admin-panel-signout-btn"
onClick={handleSignOut}
>
<FaSignOutAlt /> Sign Out
</button>
</div>
</div>
{/* Mobile Overlay */}
{isMobileMenuOpen && (
<div
className="admin-panel-mobile-overlay"
onClick={() => setIsMobileMenuOpen(false)}
></div>
)}
{/* Main Content */}
<div className="admin-panel-main-content">
{/* Top Header with Breadcrumbs */}
<div className="admin-panel-top-header">
<div className="admin-panel-breadcrumbs">
<span>Dashboard</span>
{currentSection && (
<>
<FaChevronRight className="admin-panel-breadcrumb-arrow" />
<span>{currentSection.title} Management</span>
</>
)}
</div>
<div
className="backtohomefromAdminpanel"
onClick={() => UserType === "Super Admin" ? navigate(`${subDirectory}landing-page/home`) : navigate(`${subDirectory}`)}
>
Back to Home <HiOutlineHome size={18} />
</div>
</div>
{/* Content Area */}
<div className="admin-panel-content-area">
{currentSection?.component ? (
<currentSection.component sectionKey={currentSection.key} />
) : (
<div className="admin-panel-placeholder-content">
<div className="admin-panel-placeholder-icon">
<FaTh />
</div>
<h2>Welcome to Admin Panel</h2>
<p>Select a section from the sidebar to get started</p>
</div>
)}
</div>
</div>
</div>
</>
);
};
export default AdminPanel;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,976 @@
// Modern Admin Panel with Trending UI/UX
.admin-panel {
display: flex;
height: 100vh;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
color: #1a202c;
letter-spacing: -0.01em;
// Sidebar Styles - Modern Dark Theme
.sidebar {
width: 290px;
background: linear-gradient(180deg, #0f172a 0%, #1e1b4b 100%);
color: white;
display: flex;
flex-direction: column;
box-shadow: 8px 0 40px rgba(0, 0, 0, 0.15);
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #5255c8, #8b5cf6, #ec4899);
}
.sidebar-header {
padding: 2.5rem 1.75rem 1.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
.logo {
display: flex;
align-items: center;
gap: 0.875rem;
svg {
font-size: 2rem;
color: #818cf8;
filter: drop-shadow(0 0 8px rgba(129, 140, 248, 0.4));
}
h1 {
margin: 0;
font-size: 1.5rem;
font-weight: 700;
background: linear-gradient(135deg, #ffffff 0%, #a5b4fc 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -0.02em;
}
}
}
.sidebar-nav {
flex: 1;
padding: 1rem 0;
overflow-y: auto;
.nav-item {
display: flex;
align-items: center;
padding: 1rem 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
margin: 0.25rem 0;
&:hover {
background: rgba(255, 255, 255, 0.05);
}
&.active {
background: linear-gradient(135deg, #5255c8, #8b5cf6);
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);
.nav-icon {
color: white;
}
.nav-content {
.nav-title {
color: white;
}
.nav-subtitle {
color: rgba(255, 255, 255, 0.8);
}
}
}
.nav-icon {
font-size: 1.25rem;
color: #94a3b8;
margin-right: 1rem;
transition: color 0.3s ease;
}
.nav-content {
flex: 1;
.nav-title {
display: block;
font-size: 0.95rem;
font-weight: 600;
color: #e2e8f0;
margin-bottom: 0.25rem;
}
.nav-subtitle {
display: block;
font-size: 0.8rem;
color: #94a3b8;
}
}
.active-indicator {
width: 4px;
height: 20px;
background: white;
border-radius: 2px;
position: absolute;
right: 0;
}
}
}
.sidebar-footer {
padding: 1.5rem;
border-top: 1px solid rgba(255, 255, 255, 0.1);
.user-profile {
display: flex;
align-items: center;
margin-bottom: 1rem;
.user-avatar {
width: 40px;
height: 40px;
background: linear-gradient(135deg, #8b5cf6, #ec4899);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.9rem;
margin-right: 0.75rem;
}
.user-info {
flex: 1;
.user-name {
display: block;
font-size: 0.9rem;
font-weight: 600;
color: white;
margin-bottom: 0.25rem;
}
.user-role {
display: block;
font-size: 0.8rem;
color: #94a3b8;
}
}
}
.signout-btn {
width: 100%;
background: transparent;
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
padding: 0.75rem 1rem;
border-radius: 8px;
cursor: pointer;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.3s ease;
&:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.3);
}
}
}
}
// Main Content Styles
.main-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
// Top Header
.top-header {
background: white;
padding: 1rem 2rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
.breadcrumbs {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
color: #64748b;
.breadcrumb-arrow {
font-size: 0.75rem;
color: #94a3b8;
}
span:last-child {
color: #1e293b;
font-weight: 500;
}
}
.header-actions {
display: flex;
gap: 0.75rem;
button {
background: transparent;
border: 1px solid #d1d5db;
color: #374151;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.875rem;
display: flex;
align-items: center;
gap: 0.5rem;
transition: all 0.2s ease;
&:hover {
background: #f9fafb;
border-color: #9ca3af;
}
}
}
}
// Main Header
.main-header {
background: white;
padding: 2rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
.header-left {
display: flex;
align-items: center;
gap: 1rem;
.header-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.25rem;
}
.header-content {
h1 {
margin: 0;
font-size: 1.75rem;
font-weight: 700;
color: #1e293b;
}
p {
margin: 0.25rem 0 0 0;
color: #64748b;
font-size: 0.95rem;
}
}
}
.create-btn {
background: linear-gradient(135deg, #1e40af, #1d4ed8);
color: white;
border: none;
padding: 0.875rem 1.5rem;
border-radius: 10px;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 0.5rem;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(30, 64, 175, 0.3);
}
}
}
// Tab Navigation
.tab-navigation {
background: white;
padding: 1rem 2rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
.tab-buttons {
display: flex;
gap: 0.5rem;
.tab-btn {
padding: 0.5rem 1rem;
border: none;
background: transparent;
color: #64748b;
font-weight: 500;
cursor: pointer;
border-radius: 6px;
transition: all 0.2s ease;
&.active {
background: #f1f5f9;
color: #1e293b;
}
&:hover:not(.active) {
background: #f8fafc;
color: #475569;
}
}
}
.tab-controls {
display: flex;
align-items: center;
gap: 1rem;
.sort-dropdown {
position: relative;
display: flex;
align-items: center;
.dropdown-icon {
position: absolute;
right: 0.75rem;
color: #64748b;
font-size: 0.875rem;
pointer-events: none;
z-index: 1;
}
select {
appearance: none;
background: white;
border: 1px solid #d1d5db;
padding: 0.5rem 2rem 0.5rem 0.75rem;
border-radius: 6px;
font-size: 0.875rem;
color: #374151;
cursor: pointer;
min-width: 140px;
&:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
}
}
.view-toggle {
display: flex;
background: #f1f5f9;
border-radius: 6px;
padding: 2px;
.view-btn {
background: transparent;
border: none;
padding: 0.5rem;
cursor: pointer;
border-radius: 4px;
color: #64748b;
transition: all 0.2s ease;
&.active {
background: white;
color: #1e293b;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
&:hover:not(.active) {
color: #475569;
}
}
}
}
}
// Content Area
.content-area {
flex: 1;
padding: 2rem;
overflow-y: auto;
background: #f8fafc;
// Remove existing form styling and make it clean
.hero-section-form,
.offerings-form,
.ecosystem-form,
.trending-apps-form,
.faq-form,
.CTASectionFormMaster,
.FooterFormMaster,
.banner-section-form {
background: transparent;
padding: 0;
border-radius: 0;
box-shadow: none;
margin: 0;
.hero-section-header,
.offerings-header,
.ecosystem-header,
.trending-apps-header,
.faq-header,
.cta-header,
.footer-header,
.banner-section-header {
display: none;
}
.form-container,
.offeringsContent,
.ecosystemContent,
.form-inputs,
.faq-inputs,
.cta-form,
.footer-form,
.banner-content {
background: transparent;
padding: 0;
border-radius: 0;
box-shadow: none;
border: none;
}
}
}
// Placeholder Content
.placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
text-align: center;
.placeholder-icon {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #e2e8f0, #cbd5e1);
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
color: #64748b;
font-size: 2rem;
margin-bottom: 2rem;
}
h2 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
font-weight: 600;
color: #1e293b;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
}
}
// Modal Overlay
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 2rem;
}
.create-modal {
background: white;
border-radius: 12px;
width: 100%;
max-width: 600px;
max-height: 90vh;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
.modal-header {
padding: 1.5rem 2rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
background: #f8fafc;
h2 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
color: #1e293b;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
color: #64748b;
cursor: pointer;
padding: 0.25rem;
border-radius: 4px;
transition: all 0.2s ease;
&:hover {
background: #e2e8f0;
color: #374151;
}
}
}
.modal-content {
padding: 2rem;
overflow-y: auto;
max-height: calc(90vh - 80px);
// Style the form components inside modal
.hero-section-form,
.offerings-form,
.ecosystem-form,
.trending-apps-form,
.faq-form,
.CTASectionFormMaster,
.FooterFormMaster,
.banner-section-form {
.form-container,
.offeringsContent,
.ecosystemContent,
.form-inputs,
.faq-inputs,
.cta-form,
.footer-form,
.banner-content {
background: transparent;
padding: 0;
border: none;
box-shadow: none;
}
// Hide data display sections in modal
.hero-data-display,
.apps-display,
.added-titles,
.saved-posters,
.added-banners,
.added-faqs,
.apps-display,
.admin-cards-container {
display: none;
}
}
}
}
}
// Responsive Design
@media (max-width: 1024px) {
.sidebar {
width: 240px;
}
.main-content {
.main-header,
.tab-navigation,
.top-header {
padding: 1rem 1.5rem;
}
.content-area {
padding: 1.5rem;
}
}
}
@media (max-width: 768px) {
flex-direction: column;
.sidebar {
width: 100%;
height: auto;
flex-direction: row;
overflow-x: auto;
.sidebar-nav {
flex: 1;
display: flex;
padding: 0;
.nav-item {
min-width: 150px;
flex-shrink: 0;
}
}
.sidebar-footer {
display: none;
}
}
.main-content {
.main-header {
flex-direction: column;
gap: 1rem;
align-items: stretch;
.header-left {
justify-content: center;
}
.create-btn {
width: 100%;
justify-content: center;
}
}
.tab-navigation {
flex-direction: column;
gap: 1rem;
align-items: stretch;
.tab-controls {
justify-content: space-between;
}
}
.content-area {
padding: 1rem;
}
.modal-overlay {
padding: 1rem;
}
.create-modal {
max-height: 95vh;
}
}
}
}
// Modal Form Styles
.create-modal-form {
.form-group {
margin-bottom: 1.5rem;
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #374151;
font-size: 0.875rem;
}
input,
textarea,
select {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 0.875rem;
transition: all 0.2s ease;
font-family: inherit;
&:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
&.error {
border-color: #ef4444;
}
&::placeholder {
color: #9ca3af;
}
}
textarea {
resize: vertical;
min-height: 100px;
}
.error-text {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 0.25rem;
}
.image-mode-toggle {
display: flex;
margin-bottom: 0.75rem;
background: #f3f4f6;
border-radius: 6px;
padding: 2px;
.mode-btn {
flex: 1;
padding: 0.5rem 1rem;
border: none;
background: transparent;
color: #6b7280;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
border-radius: 4px;
transition: all 0.2s ease;
&.active {
background: white;
color: #374151;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
&:hover:not(.active) {
color: #374151;
}
}
}
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid #e5e7eb;
button {
flex: 1;
padding: 0.75rem 1.5rem;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
&.cancel-btn {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
&:hover {
background: #e5e7eb;
}
}
&.save-btn {
background: #3b82f6;
color: white;
border: 1px solid #3b82f6;
&:hover {
background: #2563eb;
}
}
}
}
}
// Card-based layout for data display
.admin-cards-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1.5rem;
margin-top: 1.5rem;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
}
.admin-card {
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 1px solid #e2e8f0;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}
.card-image {
width: 100%;
height: 180px;
overflow: hidden;
background: #f1f5f9;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.card-content {
padding: 1.25rem;
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
margin-bottom: 0.75rem;
h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
color: #1e293b;
line-height: 1.4;
}
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
&.active {
background: #dcfce7;
color: #166534;
}
&.inactive {
background: #fef3c7;
color: #92400e;
}
}
}
.card-description {
color: #64748b;
font-size: 0.875rem;
line-height: 1.5;
margin-bottom: 1rem;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.card-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-top: 0.75rem;
border-top: 1px solid #f1f5f9;
.priority-badge {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
&.high {
background: #fee2e2;
color: #991b1b;
}
&.medium {
background: #fef3c7;
color: #92400e;
}
&.low {
background: #dcfce7;
color: #166534;
}
}
.date {
color: #94a3b8;
font-size: 0.75rem;
}
}
.card-actions {
display: flex;
gap: 0.5rem;
button {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
background: white;
color: #374151;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
background: #f9fafb;
border-color: #9ca3af;
}
&.primary {
background: #3b82f6;
color: white;
border-color: #3b82f6;
&:hover {
background: #2563eb;
}
}
&.danger {
background: #ef4444;
color: white;
border-color: #ef4444;
&:hover {
background: #dc2626;
}
}
}
}
}
}

View File

@ -0,0 +1,223 @@
import React, { createContext, useContext, useState } from 'react'
const AdminPanelContext = createContext()
export const useAdminPanel = () => {
const context = useContext(AdminPanelContext)
if (!context) {
throw new Error('useAdminPanel must be used within AdminPanelProvider')
}
return context
}
export const AdminPanelProvider = ({ children }) => {
const [sectionData, setSectionData] = useState({
BannerSectionForm: [
{
id: 1,
title: 'Summer Sale Banner',
description: 'Promotional banner for summer sale campaign',
image: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop',
active: true,
priority: 'high',
date: 'Dec 22, 2024'
}
],
CTASectionForm: [
{
id: 1,
title: 'Get Started CTA',
description: 'Primary call-to-action for new users',
image: 'https://images.unsplash.com/photo-1559136555-9303baea8ebd?w=400&h=300&fit=crop',
active: true,
priority: 'high',
date: 'Dec 21, 2024'
}
],
EcosystemForm: [
{
id: 1,
title: 'Smart City Solutions',
description: 'Comprehensive urban development platform integrating IoT, data analytics, and citizen engagement.',
image: 'https://images.unsplash.com/photo-1449824913935-59a10b8d2000?w=400&h=300&fit=crop',
active: true,
priority: 'high',
date: 'Dec 21, 2024'
},
{
id: 2,
title: 'Healthcare Innovation Hub',
description: 'Connected healthcare ecosystem with telemedicine, AI diagnostics, and patient management.',
image: 'https://images.unsplash.com/photo-1559757148-5c350d0d3c56?w=400&h=300&fit=crop',
active: true,
priority: 'medium',
date: 'Dec 20, 2024'
}
],
FaqSectionForm: [
{
id: 1,
title: 'How to Get Started',
description: 'Step-by-step guide for new users to begin using our platform',
image: 'https://images.unsplash.com/photo-1522202176988-66273c2fd55f?w=400&h=300&fit=crop',
active: true,
priority: 'high',
date: 'Dec 20, 2024'
}
],
FooterForm: [
{
id: 1,
title: 'Main Footer',
description: 'Primary footer with contact information and links',
image: 'https://images.unsplash.com/photo-1557804506-669a67965ba0?w=400&h=300&fit=crop',
active: true,
priority: 'medium',
date: 'Dec 19, 2024'
}
],
HeroSectionForm: [
{
id: 1,
title: 'Support the Environment',
description: 'Environmental conservation through waste management, and sustainable practices for a better future.',
image: 'https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?w=400&h=300&fit=crop',
active: true,
priority: 'medium',
date: 'Dec 19, 2024'
},
{
id: 2,
title: 'Water, Sanitation & Hygiene',
description: 'Providing clean water access, sanitation facilities, hygiene education to improve community health and wellbeing.',
image: 'https://images.unsplash.com/photo-1559027615-cd4628902d4a?w=400&h=300&fit=crop',
active: true,
priority: 'high',
date: 'Dec 18, 2024'
}
],
OfferingsForm: [
{
id: 1,
title: 'Digital Transformation',
description: 'Comprehensive digital solutions to modernize your business operations and enhance customer experience.',
image: 'https://images.unsplash.com/photo-1551434678-e076c223a692?w=400&h=300&fit=crop',
active: true,
priority: 'high',
date: 'Dec 18, 2024'
},
{
id: 2,
title: 'Cloud Infrastructure',
description: 'Scalable cloud solutions for secure data storage, processing, and seamless business operations.',
image: 'https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=400&h=300&fit=crop',
active: true,
priority: 'medium',
date: 'Dec 17, 2024'
}
],
TrendingAppsForm: [
{
id: 1,
name: 'PozoPay',
description: 'Secure mobile payment solution with advanced encryption and fraud protection.',
icon: 'https://images.unsplash.com/photo-1563013544-824ae1b704d3?w=100&h=100&fit=crop',
category: 'Finance',
rating: 4.8,
downloads: '1.2M',
status: 'active',
priority: 'high',
date: 'Dec 22, 2024'
},
{
id: 2,
name: 'SmartInventory',
description: 'AI-powered inventory management system with real-time tracking and analytics.',
icon: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=100&h=100&fit=crop',
category: 'Business',
rating: 4.6,
downloads: '850K',
status: 'active',
priority: 'medium',
date: 'Dec 21, 2024'
},
{
id: 3,
name: 'HealthTracker',
description: 'Comprehensive health monitoring app with wearable device integration.',
icon: 'https://images.unsplash.com/photo-1559757175-0eb30cd8c063?w=100&h=100&fit=crop',
category: 'Health',
rating: 4.9,
downloads: '2.1M',
status: 'active',
priority: 'high',
date: 'Dec 20, 2024'
}
],
AppDemo: [
{
id: 1,
title: 'Product Demo Video',
description: 'Comprehensive demonstration of our latest product features and capabilities',
image: 'https://images.unsplash.com/photo-1574717024653-61fd2cf4d44d?w=400&h=300&fit=crop',
active: true,
priority: 'high',
date: 'Dec 19, 2024'
}
]
})
const addItem = (sectionKey, item) => {
setSectionData(prev => ({
...prev,
[sectionKey]: [...prev[sectionKey], { ...item, id: Date.now() }]
}))
}
const updateItem = (sectionKey, itemId, updatedItem) => {
setSectionData(prev => ({
...prev,
[sectionKey]: prev[sectionKey].map(item =>
item.id === itemId ? { ...item, ...updatedItem } : item
)
}))
}
const deleteItem = (sectionKey, itemId) => {
setSectionData(prev => ({
...prev,
[sectionKey]: prev[sectionKey].filter(item => item.id !== itemId)
}))
}
const toggleItemStatus = (sectionKey, itemId) => {
setSectionData(prev => ({
...prev,
[sectionKey]: prev[sectionKey].map(item =>
item.id === itemId ? { ...item, active: !item.active } : item
)
}))
}
const updateSectionData = (sectionKey, newData) => {
setSectionData(prev => ({
...prev,
[sectionKey]: newData
}))
}
const value = {
sectionData,
addItem,
updateItem,
deleteItem,
toggleItemStatus,
updateSectionData
}
return (
<AdminPanelContext.Provider value={value}>
{children}
</AdminPanelContext.Provider>
)
}

View File

@ -0,0 +1,280 @@
import React, { useState, useEffect } from 'react'
import { Popconfirm, message } from 'antd'
import { useAdminPanel } from '../AdminPanelContext'
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import "../Styles/AppDemo.scss"
const AppDemo = () => {
const { sectionData, updateSectionData } = useAdminPanel()
const videoData = sectionData.AppDemo || []
const [isModalOpen, setIsModalOpen] = useState(false)
const [title, setTitle] = useState('')
const [file, setFile] = useState(null)
const [editingIndex, setEditingIndex] = useState(-1)
const [fileError, setFileError] = useState('')
const [previewUrls, setPreviewUrls] = useState(new Map())
useEffect(() => {
return () => {
previewUrls.forEach(url => URL.revokeObjectURL(url))
}
}, [])
const openModal = () => {
setIsModalOpen(true)
}
const closeModal = () => {
// Clean up preview URL
const prevUrl = previewUrls.get('preview')
if (prevUrl) {
URL.revokeObjectURL(prevUrl)
setPreviewUrls(new Map(previewUrls.delete('preview')))
}
setIsModalOpen(false)
setTitle('')
setFile(null)
setEditingIndex(-1)
setFileError('')
}
const handleClear = () => {
setTitle('')
setFile(null)
setFileError('')
}
const handleFileChange = (e) => {
const selectedFile = e.target.files[0]
if (selectedFile) {
// Clean up previous preview URL
const prevUrl = previewUrls.get('preview')
if (prevUrl) URL.revokeObjectURL(prevUrl)
// Create new preview URL
const newUrl = URL.createObjectURL(selectedFile)
setPreviewUrls(new Map(previewUrls.set('preview', newUrl)))
setFile(selectedFile)
if (fileError) setFileError('')
if (!title) {
setTitle(selectedFile.name.replace(/\.[^/.]+$/, ''))
}
}
}
const handleSave = () => {
if (!file) {
setFileError('Video file is required')
message.error('Please select a video file')
return
}
setFileError('')
const newVideo = {
id: editingIndex >= 0 ? videoData[editingIndex].id : Date.now(),
title: title.trim() || file.name,
file: file,
active: true,
createdAt: editingIndex >= 0 ? videoData[editingIndex].createdAt : new Date().toLocaleDateString()
}
if (editingIndex >= 0) {
const updatedVideos = videoData.map((video, index) => index === editingIndex ? newVideo : video)
updateSectionData('AppDemo', updatedVideos)
setEditingIndex(-1)
message.success('Video updated!')
} else {
updateSectionData('AppDemo', [...videoData, newVideo])
message.success('Video added!')
}
closeModal()
}
const handleDelete = (index) => {
const updatedVideos = videoData.filter((_, i) => i !== index)
updateSectionData('AppDemo', updatedVideos)
message.success('Video deleted')
}
const handleEdit = (index) => {
const video = videoData[index]
setTitle(video.title)
setFile(video.file)
setEditingIndex(index)
openModal()
}
const handleToggleActive = (index) => {
const updated = [...videoData]
updated[index].active = !updated[index].active
updateSectionData('AppDemo', updated)
message.success('Status updated')
}
return (
<div className='video-section-form-master'>
{/* Header Section */}
<div className='video-section-form-header'>
<div className='video-section-header-left'>
{/* <div className='video-section-header-icon'>
<span>🎥</span>
</div> */}
<div className='video-section-header-content'>
<h2>Video Management</h2>
<p>Manage and organize your video content.</p>
</div>
</div>
<button onClick={openModal} className='video-section-create-btn'>
<span>+</span> Upload Video
</button>
</div>
{/* Tab Navigation */}
<div className='video-section-tab-navigation'>
<div className='video-section-tab-buttons'>
<button className='video-section-tab-btn video-section-active'>
Videos ({videoData.length})
</button>
<button className='video-section-tab-btn'>
All Status
</button>
</div>
<div className='video-section-tab-controls'>
<div className='video-section-sort-dropdown'>
<select>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
<div className='video-section-view-toggle'>
<button className='video-section-view-btn video-section-active'>
<span></span>
</button>
<button className='video-section-view-btn'>
<span></span>
</button>
</div>
</div>
</div>
{/* Content Area */}
{videoData.length > 0 ? (
<div className='video-section-display'>
<div className='video-section-grid'>
{videoData.map((video, index) => (
<div key={video.id} className={`video-section-card ${!video.active ? 'video-section-inactive' : ''}`}>
<div className='video-section-card-header'>
<h3>{video.title || video.file?.name || 'Untitled Video'}</h3>
<div className='video-section-card-actions'>
<button className='video-section-edit-btn' onClick={() => handleEdit(index)}>Edit</button>
<Popconfirm
title="Delete Video"
description="Are you sure you want to delete this video?"
onConfirm={() => handleDelete(index)}
okText="Yes"
cancelText="No"
>
<button className='video-section-delete-btn'>Delete</button>
</Popconfirm>
<button
className={`video-section-status-btn ${video.active ? 'video-section-deactivate' : 'video-section-activate'}`}
onClick={() => handleToggleActive(index)}
>
{video.active ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
<div className='video-section-card-content'>
<div className='video-section-video-preview'>
{video.file instanceof File ? (
<video width='100%' controls>
<source src={previewUrls.get(video.id) || URL.createObjectURL(video.file)} type={video.file.type} />
</video>
) : (
<div className='video-placeholder'>Video not available</div>
)}
</div>
<p className='video-section-created-date'>Created: {video.date || video.createdAt}</p>
<p className={`video-section-status ${video.active ? 'video-section-active' : 'video-section-inactive'}`}>
Status: {video.active ? 'Active' : 'Inactive'}
</p>
</div>
</div>
))}
</div>
</div>
) : (
<div className='video-section-placeholder-content'>
<div className='video-section-placeholder-icon'>
<span>🎥</span>
</div>
<h2>No Videos Found</h2>
<p>Get started by uploading your first video</p>
</div>
)}
{/* Modal */}
<DefaultModal
open={isModalOpen}
title={editingIndex >= 0 ? 'Edit Video' : 'Upload New Video'}
handleCancel={closeModal}
handleSubmit={handleSave}
buttonText={editingIndex >= 0 ? 'Update Video' : 'Add Video'}
width={600}
destroyOnClose={true}
>
<div className='video-section-form'>
<div className='video-section-form-group'>
<label htmlFor='videoTitle'>Video Title</label>
<input
type='text'
id='videoTitle'
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder='Enter video title'
autoFocus
/>
</div>
<div className='video-section-form-group'>
<label htmlFor='videoFile'>Video Upload</label>
<input
type='file'
id='videoFile'
accept='video/*'
onChange={handleFileChange}
className={`video-section-file-input ${fileError ? 'error' : ''}`}
/>
{fileError && <span className='error-message'>{fileError}</span>}
{file && file instanceof File && (
<div className='video-section-video-preview'>
<p>Selected: {file.name}</p>
<video width='100%' controls>
<source src={previewUrls.get('preview') || URL.createObjectURL(file)} type={file.type} />
</video>
</div>
)}
</div>
<div className='video-section-form-actions'>
<button type='button' onClick={handleSave} className='video-section-save-btn'>
{editingIndex >= 0 ? 'Update' : 'Save'}
</button>
<button type='button' onClick={closeModal} className='video-section-cancel-btn'>
Cancel
</button>
<button type='button' onClick={handleClear} className='video-section-clear-btn'>
Clear
</button>
</div>
</div>
</DefaultModal>
</div>
)
}
export default AppDemo

View File

@ -0,0 +1,259 @@
import React, { useState } from 'react'
import { Popconfirm, message } from 'antd'
import { useAdminPanel } from '../AdminPanelContext'
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import "../Styles/BannerSectionForm.scss"
const BannerSectionForm = () => {
const { sectionData, updateSectionData } = useAdminPanel()
const bannerData = sectionData.BannerSectionForm || []
const [isModalOpen, setIsModalOpen] = useState(false)
const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const [editingIndex, setEditingIndex] = useState(-1)
const [titleError, setTitleError] = useState('')
const [descriptionError, setDescriptionError] = useState('')
const openModal = () => {
setIsModalOpen(true)
}
const closeModal = () => {
setIsModalOpen(false)
setTitle('')
setDescription('')
setEditingIndex(-1)
setTitleError('')
setDescriptionError('')
}
const handleClear = () => {
setTitle('')
setDescription('')
setTitleError('')
setDescriptionError('')
}
const handleSave = () => {
let hasError = false
if (!title.trim()) {
setTitleError('Title is required')
hasError = true
} else {
setTitleError('')
}
if (!description.trim()) {
setDescriptionError('Description is required')
hasError = true
} else {
setDescriptionError('')
}
if (hasError) {
message.error('Please fix the highlighted fields')
return
}
const newBanner = {
id: editingIndex >= 0 ? bannerData[editingIndex].id : Date.now(),
title: title.trim(),
description: description.trim(),
active: true,
createdAt: editingIndex >= 0 ? bannerData[editingIndex].createdAt : new Date().toLocaleDateString()
}
if (editingIndex >= 0) {
const updatedBanners = bannerData.map((banner, index) => index === editingIndex ? newBanner : banner)
updateSectionData('BannerSectionForm', updatedBanners)
setEditingIndex(-1)
message.success('Banner updated!')
} else {
updateSectionData('BannerSectionForm', [...bannerData, newBanner])
message.success('Banner added!')
}
closeModal()
}
const handleDelete = (index) => {
const updatedBanners = bannerData.filter((_, i) => i !== index)
updateSectionData('BannerSectionForm', updatedBanners)
message.success('Banner deleted')
}
const handleEdit = (index) => {
const banner = bannerData[index]
setTitle(banner.title)
setDescription(banner.description)
setEditingIndex(index)
openModal()
}
const handleToggleActive = (index) => {
const updated = [...bannerData]
updated[index].active = !updated[index].active
updateSectionData('BannerSectionForm', updated)
message.success('Status updated')
}
return (
<div className='banner-section-form-master'>
{/* Header Section */}
<div className='banner-section-form-header'>
<div className='banner-section-header-left'>
{/* <div className='banner-section-header-icon'>
<span>🏷</span>
</div> */}
<div className='banner-section-header-content'>
<h2>Banner Management</h2>
<p>Manage and organize your banner content and settings.</p>
</div>
</div>
<button onClick={openModal} className='banner-section-create-btn'>
<span>+</span> Create Banner
</button>
</div>
{/* Tab Navigation */}
<div className='banner-section-tab-navigation'>
<div className='banner-section-tab-buttons'>
<button className='banner-section-tab-btn banner-section-active'>
Banners ({bannerData.length})
</button>
<button className='banner-section-tab-btn'>
All Status
</button>
</div>
<div className='banner-section-tab-controls'>
<div className='banner-section-sort-dropdown'>
<select>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
<div className='banner-section-view-toggle'>
<button className='banner-section-view-btn banner-section-active'>
<span></span>
</button>
<button className='banner-section-view-btn'>
<span></span>
</button>
</div>
</div>
</div>
{/* Content Area */}
{bannerData.length > 0 ? (
<div className='banner-section-display'>
<div className='banner-section-grid'>
{bannerData.map((banner, index) => (
<div key={banner.id} className={`banner-section-card ${!banner.active ? 'banner-section-inactive' : ''}`}>
<div className='banner-section-card-header'>
<h3>{banner.title}</h3>
<div className='banner-section-card-actions'>
<button className='banner-section-edit-btn' onClick={() => handleEdit(index)}>Edit</button>
<Popconfirm
title="Delete Banner"
description="Are you sure you want to delete this banner?"
onConfirm={() => handleDelete(index)}
okText="Yes"
cancelText="No"
>
<button className='banner-section-delete-btn'>Delete</button>
</Popconfirm>
<button
className={`banner-section-status-btn ${banner.active ? 'banner-section-deactivate' : 'banner-section-activate'}`}
onClick={() => handleToggleActive(index)}
>
{banner.active ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
<div className='banner-section-card-content'>
<p><strong>Description:</strong> {banner.description || 'No description'}</p>
<p className='banner-section-created-date'>Created: {banner.date || banner.createdAt}</p>
<p className={`banner-section-status ${banner.active ? 'banner-section-active' : 'banner-section-inactive'}`}>
Status: {banner.active ? 'Active' : 'Inactive'}
</p>
</div>
</div>
))}
</div>
</div>
) : (
<div className='banner-section-placeholder-content'>
<div className='banner-section-placeholder-icon'>
<span>🏷</span>
</div>
<h2>No Banners Found</h2>
<p>Get started by creating your first banner</p>
</div>
)}
{/* Modal */}
<DefaultModal
open={isModalOpen}
title={editingIndex >= 0 ? 'Edit Banner' : 'Create New Banner'}
handleCancel={closeModal}
handleSubmit={handleSave}
buttonText={editingIndex >= 0 ? 'Update Banner' : 'Add Banner'}
width={700}
destroyOnClose={true}
>
<div className='banner-section-form'>
<div className='banner-section-form-group'>
<label htmlFor='bannerTitle'>Title</label>
<input
type='text'
id='bannerTitle'
value={title}
onChange={(e) => {
setTitle(e.target.value)
if (titleError) setTitleError('')
}}
placeholder='Enter banner title'
className={titleError ? 'error' : ''}
autoFocus
/>
{titleError && <span className='error-message'>{titleError}</span>}
</div>
<div className='banner-section-form-group'>
<label htmlFor='bannerDescription'>Description</label>
<textarea
id='bannerDescription'
value={description}
onChange={(e) => {
setDescription(e.target.value)
if (descriptionError) setDescriptionError('')
}}
placeholder='Enter banner description'
rows='4'
className={descriptionError ? 'error' : ''}
/>
{descriptionError && <span className='error-message'>{descriptionError}</span>}
</div>
<div className='banner-section-form-actions'>
<button type='button' onClick={handleSave} className='banner-section-save-btn'>
{editingIndex >= 0 ? 'Update' : 'Save'}
</button>
<button type='button' onClick={closeModal} className='banner-section-cancel-btn'>
Cancel
</button>
<button type='button' onClick={handleClear} className='banner-section-clear-btn'>
Clear
</button>
</div>
</div>
</DefaultModal>
</div>
)
}
export default BannerSectionForm

View File

@ -0,0 +1,930 @@
import React, { useState, useEffect, useCallback } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { Popconfirm, message } from 'antd'
import "../Styles/BlogForm.scss"
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import { Messages } from '../../Components/Notifications/Messages.jsx'
import { getSession } from "../../Services/others.js"
import { deleteBlog, getBlog, postBlog, putBlog } from '../../features/Blog/Blog.js'
import { Await, useNavigate } from 'react-router-dom'
import { uploadImage } from '../../features/applications/bannerImage'
import { RiImageLine, RiVideoLine, RiArticleLine, RiPlayFill, RiUserLine, RiSearchLine } from 'react-icons/ri'
import { getConfigNames } from '../../features/configmasterPage/configmasterPage.js'
import { AiOutlineDelete, AiOutlineEdit } from "react-icons/ai";
const subDirectory = import.meta.env.ENV_BASE_URL
const BlogForm = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [showForm, setShowForm] = useState(false)
const [blogTitle, setBlogTitle] = useState('')
const [blogSubtitle, setBlogSubtitle] = useState('')
const [posterImage, setPosterImage] = useState(null)
const [publishDate, setPublishDate] = useState('')
const [publishTime, setPublishTime] = useState('')
const [detailSections, setDetailSections] = useState([{
DtlTitle: '',
DtlDescription: [''],
DtlImages: [null],
DtlVideos: [null]
}])
console.log(detailSections, 'detailSections')
const userId = getSession("UserId")
// const [moreDetails, setMoreDetails] = useState(false);
const [savedBlogs, setSavedBlogs] = useState([])
const [editingIndex, setEditingIndex] = useState(-1)
const [moreDetails, setMoreDetails] = useState(false)
const [modalOpen, setModalOpen] = useState(false)
const [modalContent, setModalContent] = useState(null)
const [modalType, setModalType] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [filterStatus, setFilterStatus] = useState('all')
const [searchTerm, setSearchTerm] = useState('')
const [sortOrder, setSortOrder] = useState('newest')
const [dateSortFilter, setDateSortFilter] = useState('newest')
const addDescription = (sectionIndex) => {
const updated = [...detailSections]
updated[sectionIndex].DtlDescription.push('')
setDetailSections(updated)
}
const removeDescription = (sectionIndex, descIndex) => {
const updated = [...detailSections]
updated[sectionIndex].DtlDescription.splice(descIndex, 1)
setDetailSections(updated)
}
const addImage = (sectionIndex) => {
const updated = [...detailSections]
updated[sectionIndex].DtlImages.push(null)
setDetailSections(updated)
}
const removeImage = (sectionIndex, imgIndex) => {
const updated = [...detailSections]
updated[sectionIndex].DtlImages.splice(imgIndex, 1)
setDetailSections(updated)
}
const addVideo = (sectionIndex) => {
const updated = [...detailSections]
updated[sectionIndex].DtlVideos.push(null)
setDetailSections(updated)
}
const removeVideo = (sectionIndex, vidIndex) => {
const updated = [...detailSections]
updated[sectionIndex].DtlVideos.splice(vidIndex, 1)
setDetailSections(updated)
}
const addDetailSection = () => {
setDetailSections([...detailSections, {
DtlTitle: '',
DtlDescription: [''],
DtlImages: [null],
DtlVideos: [null]
}])
}
const removeDetailSection = (sectionIndex) => {
const updated = [...detailSections]
updated.splice(sectionIndex, 1)
setDetailSections(updated)
}
useEffect(() => {
checkScheduledBlogs()
}, [])
const checkScheduledBlogs = async () => {
try {
let Res = await dispatch(getBlog()).unwrap();
if (Res?.data?.statusCode == 1) {
const blogs = Res?.data?.data
setSavedBlogs(blogs)
} else {
setSavedBlogs([])
}
}
catch (error) {
console.error('Error checking scheduled blogs:', error)
}
}
const validateForm = () => {
if (!blogTitle.trim()) {
message.error('Blog title is required')
return false
}
if (!blogSubtitle.trim()) {
message.error('Blog subtitle is required')
return false
}
return true
}
const handleSave = async () => {
if (!validateForm()) return;
setIsSubmitting(true);
// debugger
try {
// First, ensure all images/videos are uploaded
const updatedDetails = detailSections.map((section) => {
return {
...(editingIndex > -1 ? { BlogId: section?.BlogId } : {}),
...(editingIndex > -1 ? { UniqueId: section?.UniqueId } : {}),
DtlTitle: section?.DtlTitle ?? "",
DtlDescription: section?.DtlDescription ?? [],
DtlImages: section.DtlImages ?? [],
DtlVideos: section.DtlVideos ?? [],
ActiveStatus: "A",
...(editingIndex > -1 ? { UpdatedBy: userId || 1 } : { CreatedBy: userId || 1 }),
};
});
// Prepare blog data
const PostBlogData = {
BlogTitle: blogTitle,
BlogSubtitle: blogSubtitle,
HeaderImage: posterImage,
PublishDate: publishDate || '',
PublishTime: publishTime || '',
ActiveStatus: savedBlogs[editingIndex]?.ActiveStatus ? savedBlogs[editingIndex]?.ActiveStatus : "A",
...(editingIndex > -1
? { BlogId: savedBlogs[editingIndex].BlogId, UpdatedBy: userId || 1 }
: { CreatedBy: userId || 1 }),
BlogDetails: updatedDetails,
IsPublished: savedBlogs[editingIndex]?.IsPublished ? savedBlogs[editingIndex]?.IsPublished : "N"
};
// Send API request
const Response = editingIndex > -1
? await dispatch(putBlog(PostBlogData)).unwrap()
: await dispatch(postBlog(PostBlogData)).unwrap();
if (Response) {
message.success('Blog post saved successfully!');
await checkScheduledBlogs();
handleClear();
setShowForm(false);
} else {
message.error('No response from server');
}
} catch (error) {
console.error('Save error:', error);
message.error(`Failed to save: ${error?.message || 'Please check your connection and try again'}`);
} finally {
setIsSubmitting(false);
setEditingIndex(-1);
}
};
const handleEdit = (index) => {
const blog = savedBlogs[index];
navigate(`${subDirectory}PostEditorPage`, {
state: { blog, isEdit: true }
});
};
const handleDelete = async (blogId, activeStatus) => {
// if (confirm('Are you sure you want to delete this blog post?')) {
// const updated = savedBlogs.filter((_, i) => i !== index)
// setSavedBlogs(updated)
// }
const updatedStatus = activeStatus === 'A' ? 'D' : 'A';
const res = await dispatch(deleteBlog({
blogId: blogId,
activeStatus: updatedStatus,
updatedBy: userId
})).unwrap();
if (res?.data?.statusCode == 1) {
message.success('Blog status deleted successfully!')
await checkScheduledBlogs();
} else {
message.error('Failed to update blog status. Please try again.')
}
setEditingIndex(-1);
}
const togglePublish = async (index) => {
const updated = [...savedBlogs];
const isCurrentlyPublished = updated[index].IsPublished === 'Y';
// Toggle publish status
updated[index].IsPublished = isCurrentlyPublished ? 'N' : 'Y';
// If unpublishing (Y -> N), clear the publish date and time
if (isCurrentlyPublished) {
updated[index].PublishDate = null;
updated[index].PublishTime = null;
}
const res = await dispatch(putBlog(updated[index]))?.unwrap();
if (res?.data?.statusCode == 1) {
message.success('Publish status updated successfully!');
await checkScheduledBlogs();
} else {
message.error('Failed to update blog status. Please try again.');
}
};
const handleCancel = () => {
handleClear()
setEditingIndex(-1)
setShowForm(false)
}
const handleClear = () => {
setBlogTitle('')
setBlogSubtitle('')
setPosterImage(null)
setPublishDate('')
setPublishTime('')
setDetailSections([{
DtlTitle: '',
DtlDescription: [''],
DtlImages: [null],
DtlVideos: [null]
}])
}
const handleAddmoreDetails = () => {
setMoreDetails(!moreDetails)
}
const openModal = (content, type) => {
setModalContent(content)
setModalType(type)
setModalOpen(true)
}
const closeModal = () => {
setModalOpen(false)
setModalContent(null)
setModalType('')
}
const handleFileUpload = async (file, type, sectionIndex = null, mediaIndex = null) => {
if (!file) return;
try {
const res = await dispatch(uploadImage(file)).unwrap();
const fileUrl = res?.data?.status ? res?.data?.image : file;
// Update based on type
if (type === 'poster') {
setPosterImage(fileUrl);
}
else if (type === 'image' && sectionIndex !== null && mediaIndex !== null) {
const updated = [...detailSections];
updated[sectionIndex].DtlImages[mediaIndex] = fileUrl;
setDetailSections(updated);
}
else if (type === 'video' && sectionIndex !== null && mediaIndex !== null) {
const updated = [...detailSections];
updated[sectionIndex].DtlVideos[mediaIndex] = fileUrl;
setDetailSections(updated);
}
// Feedback
if (res?.data?.status) {
message.success(`${type === 'video' ? 'Video' : 'Image'} uploaded successfully!`);
} else {
message.warning(`${type === 'video' ? 'Video' : 'Image'} upload failed. Using local preview.`);
}
} catch (err) {
console.error(`Error uploading ${type}:`, err);
message.error(`Error uploading ${type}. Please try again.`);
// Fallback to local preview
if (type === 'poster') {
setPosterImage(file);
}
else if (type === 'image' && sectionIndex !== null && mediaIndex !== null) {
const updated = [...detailSections];
updated[sectionIndex].DtlImages[mediaIndex] = file;
setDetailSections(updated);
}
else if (type === 'video' && sectionIndex !== null && mediaIndex !== null) {
const updated = [...detailSections];
updated[sectionIndex].DtlVideos[mediaIndex] = file;
setDetailSections(updated);
}
}
};
const getPreviewURL = (fileOrUrl) => {
if (!fileOrUrl) return ''; // null/undefined empty string
if (typeof fileOrUrl === 'string') return fileOrUrl; // backend URL use directly
if (fileOrUrl instanceof File || fileOrUrl instanceof Blob) {
return URL.createObjectURL(fileOrUrl); // local file preview
}
return ''; // fallback for anything else
};
const setShowFormNavigate = () => {
navigate(`${subDirectory}PostEditorPage`);
}
function formatTo12Hour(time24) {
// Split "HH:MM:SS" into parts
const [hourStr, minuteStr] = time24.split(":");
let hour = parseInt(hourStr, 10);
const minute = minuteStr;
// Determine AM/PM
const ampm = hour >= 12 ? "PM" : "AM";
// Convert hour to 12-hour format
hour = hour % 12;
hour = hour === 0 ? 12 : hour; // 0 should be 12
// Return formatted string
return `${hour.toString().padStart(2, "0")}:${minute} ${ampm}`;
}
// Filter blogs based on status and search term
const filteredBlogs = savedBlogs?.filter(blog => {
// Status filter
let statusMatch = true;
if (filterStatus === 'published') statusMatch = blog.IsPublished === 'Y';
else if (filterStatus === 'unpublished') statusMatch = blog.IsPublished === 'N';
else if (filterStatus === 'draft') statusMatch = !blog.PublishDate && blog.IsPublished === 'N';
// Search filter
const searchMatch = !searchTerm ||
blog.BlogTitle?.toLowerCase().includes(searchTerm.toLowerCase()) ||
blog.BlogSubtitle?.toLowerCase().includes(searchTerm.toLowerCase()) ||
blog.Author?.toLowerCase().includes(searchTerm.toLowerCase());
return statusMatch && searchMatch;
}) || [];
// New date sorting function
const finalSortedBlogs = [...filteredBlogs].sort((a, b) => {
const dateA = new Date(a.CreatedDate);
const dateB = new Date(b.CreatedDate);
return dateSortFilter === 'newest' ? dateB - dateA : dateA - dateB;
});
return (
<div className='blog-form-master'>
<div className='blog-form-header'>
<div className='blog-form-header-content'>
<h2>Blog Management</h2>
<p>Create and manage your blog posts and content.</p>
</div>
<button className='blog-form-create-btn'
// onClick={() => setShowForm(!showForm)}
onClick={setShowFormNavigate}
>
{showForm ? (
<><span></span> Back</>
) : (
<><span>+</span> Create Blog Post</>
)}
</button>
</div>
<div className='blog-form-layout'>
{showForm && (
<div className='blog-form-left'>
<>
{/* Poster Section */}
<div className='blog-form-container'>
<h3>Poster Section</h3>
<div className='blog-form-field'>
<label>Blog Title</label>
<input
type="text"
value={blogTitle}
onChange={(e) => setBlogTitle(e.target.value)}
autoFocus
/>
</div>
<div className='blog-form-field'>
<label>Blog Subtitle</label>
<input
type="text"
value={blogSubtitle}
onChange={(e) => setBlogSubtitle(e.target.value)}
/>
</div>
<div className='blog-form-field'>
<label>Upload Image</label>
<input
type="file"
accept="image/*"
// onChange={(e) => setPosterImage(e.target.files[0]) }
onChange={(e) => handleFileUpload(e.target.files[0], 'poster')}
/>
{posterImage && (
<div className='file-preview'>
<img
src={getPreviewURL(posterImage)}
alt="Poster preview"
style={{
maxWidth: '100px',
maxHeight: '100px',
marginTop: '10px',
borderRadius: '8px'
}}
/>
</div>
)}
</div>
{/* <div className='blog-form-field'>
<label>Publish Date (optional - leave empty for immediate publish)</label>
<input
type="date"
value={publishDate}
onChange={(e) => setPublishDate(e.target.value || '')}
/>
</div>
<div className='blog-form-field'>
<label>Publish Time (optional)</label>
<input
type="time"
value={publishTime}
onChange={(e) => setPublishTime(e.target.value || '')}
/>
</div> */}
{/* Details Section */}
{/* <div className='blog-form-container'> */}
<div className='section-header' style={{ marginTop: "3rem" }}>
{/* <h3 onClick={handleAddmoreDetails} style={{ cursor: 'pointer' }}>
{moreDetails ? '▼ Hide Details' : '▶ Add More Details'}
</h3> */}
{!moreDetails ?
<h3 onClick={handleAddmoreDetails} style={{ cursor: 'pointer' }}>
Add More Details
</h3> :
<div onClick={handleAddmoreDetails} style={{ cursor: 'pointer' }}>
Hide Details
</div>}
</div>
{moreDetails && (
<>
{detailSections.map((section, sectionIndex) => (
<div key={sectionIndex} className='detail-section'>
<div>More Details ({detailSections.length})</div>
<div className='blog-form-field'>
<label>Title</label>
<input
type="text"
value={section.DtlTitle}
onChange={(e) => {
const updated = [...detailSections]
updated[sectionIndex].DtlTitle = e.target.value
setDetailSections(updated)
}}
/>
</div>
<div className='dynamic-inputs'>
<div className='input-group'>
<div className='group-header'>
<label>Description</label>
<button className='add-btn' onClick={() => addDescription(sectionIndex)}>+ Add</button>
</div>
{section?.DtlDescription.map((desc, descIndex) => (
<div key={descIndex} className='input-wrapper'>
<textarea
value={desc}
onChange={(e) => {
const updated = [...detailSections]
updated[sectionIndex].DtlDescription[descIndex] = e.target.value
setDetailSections(updated)
}}
/>
{section?.DtlDescription.length > 1 && (
<button
className='remove-btn'
onClick={() => removeDescription(sectionIndex, descIndex)}
>
Remove
</button>
)}
</div>
))}
</div>
<div className='input-group'>
<div className='group-header'>
<label>Images</label>
</div>
{(section?.DtlImages || [null])?.map((img, imgIndex) => (
<div key={imgIndex} className='input-wrapper'>
<input
type="file"
accept="image/*"
onChange={(e) => handleFileUpload(e.target.files[0], 'image', sectionIndex, imgIndex)}
/>
{img && (
<div className='file-preview'>
<img
src={getPreviewURL(img)}
alt={`Image ${imgIndex + 1}`}
style={{
maxWidth: '150px',
maxHeight: '100px',
marginTop: '8px',
borderRadius: '6px'
}}
/>
</div>
)}
<div className='button-group'>
<button className='add-btn' onClick={() => addImage(sectionIndex)}>Add</button>
{section?.DtlImages?.length > 1 && (
<button
className='remove-btn'
onClick={() => removeImage(sectionIndex, imgIndex)}
>
Remove
</button>
)}
</div>
</div>
))}
</div>
<div className='input-group'>
<div className='group-header'>
<label>Videos</label>
</div>
{(section?.DtlVideos || [null])?.map((vid, vidIndex) => (
<div key={vidIndex} className='input-wrapper'>
<input
type="file"
accept="video/*"
onChange={(e) => handleFileUpload(e.target.files[0], 'video', sectionIndex, vidIndex)}
/>
{vid && (
<div className='file-preview'>
<video
controls
style={{
maxWidth: '200px',
maxHeight: '120px',
marginTop: '8px',
borderRadius: '6px'
}}
>
<source
src={typeof vid === 'string' ? vid : URL.createObjectURL(vid)}
type={typeof vid === 'string' ? 'video/mp4' : vid.type}
/>
</video>
</div>
)}
<div className='button-group'>
<button
type='button'
className='add-btn'
onClick={() => addVideo(sectionIndex)}
>
Add
</button>
{section?.DtlVideos?.length > 1 && (
<button
type='button'
className='remove-btn'
onClick={() => removeVideo(sectionIndex, vidIndex)}
>
Remove
</button>
)}
</div>
</div>
))}
</div>
</div>
</div>
))}
<div className='section-buttons'>
<button className='add-btn' onClick={addDetailSection}>+ Add More</button>
{detailSections.length > 1 && (
<button className='remove-btn' onClick={() => removeDetailSection(detailSections.length - 1)}>Remove</button>
)}
</div>
</>
)}
{/* </div> */}
</div>
<div className='blog-form-actions'>
<button
className='blog-form-btn-primary'
onClick={handleSave}
disabled={isSubmitting}
>
{isSubmitting ? 'Saving...' : 'Save'}
</button>
<button className='blog-form-btn-secondary' onClick={handleCancel}>Cancel</button>
<Popconfirm
title="Clear form?"
description="This will clear all your work "
onConfirm={handleClear}
okText="Yes"
cancelText="No"
>
<button className='blog-form-btn-secondary'>Clear</button>
</Popconfirm>
</div>
</>
</div>
)}
{!showForm && (
<div className='blog-form-right'>
<div className="blogFilter">
{/* Search Bar */}
<div className="search-container">
<RiSearchLine className="search-icon" />
<input
type="text"
className="search-input"
placeholder="Search blogs by title, subtitle, or author..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
{/* Status Filter */}
<div className="filter-buttons">
<span className="filter-label">Filter:</span>
{[
{ value: 'all', label: 'All', count: savedBlogs?.length || 0 },
{ value: 'published', label: 'Published', count: savedBlogs?.filter(b => b.IsPublished === 'Y')?.length || 0 },
{ value: 'unpublished', label: 'Unpublished', count: savedBlogs?.filter(b => b.IsPublished === 'N')?.length || 0 },
{ value: 'draft', label: 'Draft', count: savedBlogs?.filter(b => !b.PublishDate && b.IsPublished === 'N')?.length || 0 }
].map(filter => (
<button
key={filter.value}
className={`filter-btn ${filterStatus === filter.value ? 'active' : ''}`}
onClick={() => setFilterStatus(filter.value)}
>
{filter.label}
<span className={`count-badge ${filterStatus === filter.value ? 'active' : ''}`}>
{filter.count}
</span>
</button>
))}
</div>
<div className="filterAssDcc">
<button
className={`date-sort-btn ${dateSortFilter === 'newest' ? 'active' : ''}`}
onClick={() => setDateSortFilter('newest')}
>
Newest
</button>
<button
className={`date-sort-btn ${dateSortFilter === 'oldest' ? 'active' : ''}`}
onClick={() => setDateSortFilter('oldest')}
>
Oldest
</button>
</div>
</div>
{finalSortedBlogs?.length > 0 ? (
<div className='blog-display'>
<div className='blog-grid'>
{finalSortedBlogs?.map((blog, index) => (
<div key={blog.BlogId} className={`blog-card ${blog.IsPublished === 'N' ? 'blog-inactive' : ''}`}>
{/* Blog Header */}
<div className='blog-card-header'>
<h3>{blog.BlogTitle}</h3>
</div>
{/* Blog Content */}
<div className='blog-card-content'>
{blog.BlogSubtitle && <p> {blog.BlogSubtitle}</p>}
{/* Poster Image */}
{blog.HeaderImage && (
<div className='poster-preview'>
<img
src={getPreviewURL(blog.HeaderImage)}
alt="Poster"
style={{
width: '80px',
maxHeight: '120px',
objectFit: 'cover',
borderRadius: '6px',
cursor: 'pointer',
marginBottom: '10px'
}}
onClick={() => openModal(blog.HeaderImage, 'image')}
/>
</div>
)}
{/* Blog Details */}
{blog?.BlogDetails?.map((section, sIndex) => (
(section.DtlTitle || section.DtlDescription?.length > 0 || section.DtlImages?.length > 0 || section.DtlVideos?.length > 0) && (
<div key={sIndex}>
{section.DtlTitle && <p><strong>Section {sIndex + 1}:</strong> {section.DtlTitle}</p>}
{/* Descriptions */}
{section.DtlDescription?.map((desc, i) => (
<p key={i}> {desc.substring(0, 50)}{desc.length > 50 ? '...' : ''}</p>
))}
{/* Section Images */}
{section.DtlImages?.length > 0 && (
<div className='media-preview'>
<p><RiImageLine style={{ marginRight: '5px' }} /> {section.DtlImages.length} image(s)</p>
<div style={{ display: 'flex', gap: '5px', flexWrap: 'wrap', marginBottom: '8px' }}>
{section.DtlImages.slice(0, 3).map((img, imgIdx) => (
<img
key={imgIdx}
src={getPreviewURL(img)}
alt={`Image ${imgIdx + 1}`}
style={{
maxWidth: '150px',
maxHeight: '100px',
marginTop: '8px',
borderRadius: '6px',
cursor: 'pointer'
}}
onClick={() => openModal(img, 'image')}
/>
))}
{section.DtlImages.length > 3 && (
<div style={{
width: '50px',
height: '50px',
background: '#f0f0f0',
borderRadius: '4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '12px',
color: '#666'
}}>
+{section.DtlImages.length - 3}
</div>
)}
</div>
</div>
)}
{/* Section Videos */}
{section.DtlVideos?.length > 0 && (
<div className='media-preview'>
<p><RiVideoLine style={{ marginRight: '5px' }} /> {section.DtlVideos.length} video(s)</p>
<div style={{ display: 'flex', gap: '5px', flexWrap: 'wrap', marginBottom: '8px' }}>
{section.DtlVideos.slice(0, 2).map((vid, vidIdx) => (
<div
key={vidIdx}
style={{
width: '60px',
height: '40px',
background: '#000',
borderRadius: '4px',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
fontSize: '16px'
}}
onClick={() => openModal(vid, 'video')}
>
<RiPlayFill />
</div>
))}
{section.DtlVideos.length > 2 && (
<div style={{
width: '60px',
height: '40px',
background: '#f0f0f0',
borderRadius: '4px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '12px',
color: '#666'
}}>
+{section.DtlVideos.length - 2}
</div>
)}
</div>
</div>
)}
</div>
)
))}
{/* Blog Status & Schedule */}
<p className={`blog-status ${blog.IsPublished === 'Y' ? 'blog-active' : 'blog-inactive'}`}>
Status: {blog.IsPublished === 'Y' ? 'Published' : 'Unpublished'}
</p>
{blog.PublishDate && (
<p><strong>Scheduled:</strong> {blog.PublishDate?.split('T')?.[0]} {formatTo12Hour(blog.PublishTime) || '00:00'}</p>
)}
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', fontSize: '12px', color: '#666', fontFamily: "Poppins" }}>
Posted By: {blog.Author || "Admin"}
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px', fontSize: '12px', color: '#666', fontFamily: "Poppins" }}>
Created: {blog.CreatedDate ? new Date(blog.CreatedDate).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) : 'N/A'} {blog.CreatedDate ? formatTo12Hour(blog.CreatedDate.split('T')[1]?.split('.')[0] || '00:00:00') : ''}
</div>
</div>
{/* Actions */}
<div className='blog-card-actions'>
<button className='blog-edit-btn' onClick={() => handleEdit(index)}><AiOutlineEdit size={18} /></button>
<Popconfirm
title="Delete Blog"
description="Are you sure you want to delete this blog post?"
onConfirm={() => handleDelete(blog?.BlogId, blog?.ActiveStatus)}
okText="Yes"
cancelText="No"
>
<button className='blog-delete-btn'><AiOutlineDelete size={18} /></button>
</Popconfirm>
<button
className={`blog-status-btn ${blog.IsPublished === 'Y' ? 'blog-deactivate' : 'blog-activate'}`}
onClick={() => togglePublish(index)}
>
{blog.IsPublished === 'Y' ? 'Unpublish' : 'Publish'}
</button>
</div>
</div>
))}
</div>
</div>
) : (
<div className='blog-placeholder-content'>
<div className='blog-placeholder-icon'>
<RiArticleLine />
</div>
<h2>No Blog Posts Found</h2>
<p>Click "Create Blog Post" to start creating a new blog post</p>
</div>
)}
</div>
)}
</div>
<DefaultModal
open={modalOpen}
title={modalType === 'image' ? 'Image Preview' : 'Video Preview'}
handleCancel={closeModal}
width={800}
footer={null}
>
{modalContent && modalType === 'image' && (
<img
src={getPreviewURL(modalContent)}
alt="Preview"
style={{ width: '100%', maxHeight: '500px', objectFit: 'contain' }}
/>
)}
{modalContent && modalType === 'video' && (
<video
controls
style={{
maxWidth: '200px',
maxHeight: '120px',
marginTop: '8px',
borderRadius: '6px'
}}
>
<source
src={getPreviewURL(modalContent)}
type={typeof vid === 'string' ? 'video/mp4' : modalContent.type}
/>
</video>
)}
</DefaultModal>
</div>
)
}
export default BlogForm

View File

@ -0,0 +1,330 @@
import React, { useState, useEffect, useCallback } from 'react'
import { Popconfirm, message } from 'antd'
import { useAdminPanel } from '../AdminPanelContext'
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import "../Styles/CTASectionForm.scss"
import { getSession } from "../../Services/others.js"
import { getAdminPanel, postAdminPanel, putAdminPanel } from '../../features/AdminPanel/AdminPanel.js'
import { useDispatch } from 'react-redux'
import { Messages } from '../../Components/Notifications/Messages.jsx'
const CTASectionForm = ({ sectionKey = null }) => {
const [editState, setEditState] = useState(false);
const dispatch = useDispatch();
// const { sectionData, updateSectionData } = useAdminPanel()
// const ctaData = sectionData.CTASectionForm || [];
const [messageData, setMessageData] = useState(null);
const [messageType, setMessageType] = useState(null);
const [ctaSectionData, setCTASectionData] = useState([]);
const [isModalOpen, setIsModalOpen] = useState(false)
const [title, setTitle] = useState('')
const [subtitle, setSubtitle] = useState('')
const [editingIndex, setEditingIndex] = useState(-1);
const [editData, setEditData] = useState(null);
const [titleError, setTitleError] = useState('')
const [subtitleError, setSubtitleError] = useState('')
const UserId = getSession('UserId') || 1;
console.log(editingIndex, "editingIndex")
// UUID generator fallback for browser compatibility
const generateUUID = () => {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID()
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
useEffect(() => {
const getCTASectionData = async () => {
const res = await dispatch(getAdminPanel({ sectionName: sectionKey }))?.unwrap();
if (res?.data?.statusCode === 1) {
setCTASectionData(res?.data?.data);
} else {
setCTASectionData([]);
setMessageData(res?.data?.response);
setMessageType("error");
}
}
if (sectionKey) {
getCTASectionData();
}
}, [sectionKey])
const openModal = (isEdit = false) => {
setEditState(isEdit)
setIsModalOpen(true)
}
const closeModal = () => {
setIsModalOpen(false)
setTitle('')
setSubtitle('')
setEditingIndex(-1)
setTitleError('')
setSubtitleError('')
}
const handleClear = () => {
setTitle('')
setSubtitle('')
setTitleError('')
setSubtitleError('')
}
const handleSave = async () => {
// debugger
let hasError = false;
if (!title.trim()) {
setTitleError('Title is required');
hasError = true;
} else setTitleError('');
if (!subtitle.trim()) {
setSubtitleError('Subtitle is required');
hasError = true;
} else setSubtitleError('');
if (hasError) {
message.error('Please fix the highlighted fields');
return;
}
const data = {
SectionName: sectionKey,
SectionHdr: title,
SectionDesc: subtitle,
HomePageDetails: [],
CreatedBy: UserId,
...(editingIndex > -1 && editData ? { SectionId: editData.SectionId } : {}),
};
try {
const apiAction = editingIndex > -1 ? putAdminPanel : postAdminPanel;
const res = await dispatch(apiAction(data))?.unwrap();
console.log(`${editingIndex > -1 ? 'Put' : 'Post'} response:`, res);
const success = res?.data?.statusCode === 1;
const messageText = editingIndex > -1
? "Section Updated Successfully"
: "Section Added Successfully";
if (success) {
setMessageData(messageText);
setMessageType("success");
// Refresh data
const refreshRes = await dispatch(getAdminPanel({ sectionName: sectionKey }))?.unwrap();
if (refreshRes?.data?.statusCode === 1) {
setCTASectionData(refreshRes.data.data);
}
} else {
const errorMessage = res?.response || res?.data?.response || res?.message || "Error saving section";
setMessageData(errorMessage);
setMessageType("error");
}
} catch (error) {
console.error('API Error:', error);
setMessageData(error?.message || "Network error occurred");
setMessageType("error");
}
closeModal();
};
const handleDelete = (index) => {
const updatedCTAs = ctaSectionData.filter((_, i) => i !== index)
setCTASectionData(updatedCTAs)
message.success('CTA section deleted')
}
const handleEdit = (ctaData, index) => {
setTitle(ctaSectionData[index].SectionHdr || '')
setSubtitle(ctaSectionData[index].SectionDesc || '')
setEditingIndex(index);
setEditData(ctaData)
openModal(true)
}
const handleToggleActive = (index) => {
const updated = [...ctaSectionData]
updated[index].active = !updated[index].active
setCTASectionData(updated)
message.success('Status updated')
}
const onComplete = useCallback(() => {
setMessageData(null);
setMessageType(null);
}, []);
return (
<div className='cta-section-form-master'>
<Messages
messageType={messageType}
messageData={messageData}
onComplete={onComplete}
/>
{/* Header Section */}
<div className='cta-section-form-header'>
<div className='cta-section-header-left'>
{/* <div className='cta-section-header-icon'>
<span>📢</span>
</div> */}
<div className='cta-section-header-content'>
<h2>CTA Section Management</h2>
<p>Manage and organize your call-to-action content and settings.</p>
</div>
</div>
<button onClick={() => openModal(false)} className='cta-section-create-btn'>
<span>+</span> Create CTA Section
</button>
</div>
{/* Tab Navigation */}
<div className='cta-section-tab-navigation'>
<div className='cta-section-tab-buttons'>
<button className='cta-section-tab-btn cta-section-active'>
CTA Sections ({ctaSectionData.length})
</button>
<button className='cta-section-tab-btn'>
All Status
</button>
</div>
<div className='cta-section-tab-controls'>
<div className='cta-section-sort-dropdown'>
<select>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
<div className='cta-section-view-toggle'>
<button className='cta-section-view-btn cta-section-active'>
<span></span>
</button>
<button className='cta-section-view-btn'>
<span></span>
</button>
</div>
</div>
</div>
{/* Content Area */}
{ctaSectionData.length > 0 ? (
<div className='cta-section-display'>
<div className='cta-section-grid'>
{ctaSectionData.map((cta, index) => (
<div key={cta.id || index} className={`cta-section-card ${!cta.active ? 'cta-section-inactive' : ''}`}>
<div className='cta-section-card-header'>
<h3>{cta.SectionHdr}</h3>
</div>
<div className='cta-section-card-content'>
<p><strong>Subtitle : </strong> {cta.SectionDesc || 'No subtitle'}</p>
{/* <p className='cta-section-created-date'>Created: {cta.date || cta.createdAt}</p> */}
<p className={`cta-section-status ${cta.active ? 'cta-section-active' : 'cta-section-inactive'}`}>
Status: {cta.active ? 'Active' : 'Inactive'}
</p>
</div>
<div className='cta-section-card-actions'>
<button className='cta-section-edit-btn' onClick={() => handleEdit(cta, index)}>Edit</button>
<Popconfirm
title="Delete CTA Section"
description="Are you sure you want to delete this CTA section?"
onConfirm={() => handleDelete(index)}
okText="Yes"
cancelText="No"
>
<button className='cta-section-delete-btn'>Delete</button>
</Popconfirm>
<button
className={`cta-section-status-btn ${cta.active ? 'cta-section-deactivate' : 'cta-section-activate'}`}
onClick={() => handleToggleActive(index)}
>
{cta.active ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
))}
</div>
</div>
) : (
<div className='cta-section-placeholder-content'>
<div className='cta-section-placeholder-icon'>
<span>📢</span>
</div>
<h2>No CTA Sections Found</h2>
<p>Get started by creating your first call-to-action section</p>
</div>
)
}
{/* Modal */}
<DefaultModal
open={isModalOpen}
title={editingIndex >= 0 ? 'Edit CTA Section' : 'Create New CTA Section'}
handleCancel={closeModal}
handleSubmit={handleSave}
buttonText={editingIndex >= 0 ? 'Update CTA' : 'Add CTA'}
width={600}
destroyOnClose={true}
>
<div className='cta-section-form'>
<div className='cta-section-form-group'>
<label htmlFor='ctaTitle'>Title</label>
<input
type='text'
id='ctaTitle'
value={title?.trim()}
onChange={(e) => {
setTitle(e.target.value)
if (titleError) setTitleError('')
}}
placeholder='Enter CTA title'
className={titleError ? 'error' : ''}
autoFocus
/>
{titleError && <span className='error-message'>{titleError}</span>}
</div>
<div className='cta-section-form-group'>
<label htmlFor='ctaSubtitle'>Subtitle</label>
<input
type='text'
id='ctaSubtitle'
value={subtitle?.trim()}
onChange={(e) => {
setSubtitle(e.target.value)
if (subtitleError) setSubtitleError('')
}}
placeholder='Enter CTA subtitle'
className={subtitleError ? 'error' : ''}
/>
{subtitleError && <span className='error-message'>{subtitleError}</span>}
</div>
<div className='cta-section-form-actions'>
<button type='button' onClick={handleSave} className='cta-section-save-btn'>
{editingIndex >= 0 ? 'Update' : 'Save'}
</button>
<button type='button' onClick={closeModal} className='cta-section-cancel-btn'>
Cancel
</button>
<button type='button' onClick={handleClear} className='cta-section-clear-btn'>
Clear
</button>
</div>
</div>
</DefaultModal>
</div>
)
}
export default CTASectionForm

View File

@ -0,0 +1,181 @@
import React, { useState } from 'react'
import { FaSave, FaTimes, FaImage, FaHeading, FaAlignLeft, FaUpload } from 'react-icons/fa'
import '../Styles/CreateModalForm.scss'
const CreateModalForm = ({ sectionType, onClose, onSave }) => {
const [formData, setFormData] = useState({
title: '',
description: '',
priority: 'medium',
image: null,
imagePreview: ''
})
const [errors, setErrors] = useState({})
const handleImageChange = (e) => {
const file = e.target.files[0]
if (file) {
setFormData({ ...formData, image: file })
const reader = new FileReader()
reader.onloadend = () => {
setFormData({ ...formData, image: file, imagePreview: reader.result })
}
reader.readAsDataURL(file)
}
}
const validateForm = () => {
const newErrors = {}
if (!formData.title.trim()) newErrors.title = 'Title is required'
if (!formData.description.trim()) newErrors.description = 'Description is required'
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = (e) => {
e.preventDefault()
if (!validateForm()) {
return
}
const newItem = {
id: Date.now(),
title: formData.title,
description: formData.description,
priority: formData.priority,
image: formData.imagePreview || 'https://images.unsplash.com/photo-1559027615-cd4628902d4a?w=400&h=300&fit=crop',
active: true,
date: new Date().toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
}
onSave(newItem)
// Reset form
setFormData({
title: '',
description: '',
priority: 'medium',
image: null,
imagePreview: ''
})
setErrors({})
}
const getSectionIcon = () => {
switch (sectionType) {
case 'BannerSectionForm': return <FaImage />
case 'CTASectionForm': return <FaHeading />
case 'EcosystemForm': return <FaImage />
case 'FaqSectionForm': return <FaAlignLeft />
case 'FooterForm': return <FaHeading />
case 'HeroSectionForm': return <FaImage />
case 'OfferingsForm': return <FaImage />
case 'TrendingAppsForm': return <FaImage />
case 'AppDemo': return <FaImage />
default: return <FaImage />
}
}
return (
<form onSubmit={handleSubmit} className="create-modal-form">
<div className="form-header">
<div className="header-icon">
{getSectionIcon()}
</div>
<div className="header-content">
<h3>Create New {sectionType?.replace('Form', '')}</h3>
<p>Add a new item to your {sectionType?.replace('Form', '').toLowerCase()} collection</p>
</div>
</div>
<div className="form-body">
<div className="form-group">
<label htmlFor="title">
<FaHeading className="label-icon" />
Title *
</label>
<input
type="text"
id="title"
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
placeholder="Enter title..."
className={errors.title ? 'error' : ''}
/>
{errors.title && <span className="error-text">{errors.title}</span>}
</div>
<div className="form-group">
<label htmlFor="description">
<FaAlignLeft className="label-icon" />
Description *
</label>
<textarea
id="description"
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
placeholder="Enter description..."
rows="4"
className={errors.description ? 'error' : ''}
/>
{errors.description && <span className="error-text">{errors.description}</span>}
</div>
<div className="form-group">
<label htmlFor="priority">Priority</label>
<select
id="priority"
value={formData.priority}
onChange={(e) => setFormData({ ...formData, priority: e.target.value })}
>
<option value="low">Low Priority</option>
<option value="medium">Medium Priority</option>
<option value="high">High Priority</option>
</select>
</div>
<div className="form-group">
<label htmlFor="image">
<FaImage className="label-icon" />
Image
</label>
<div className="image-upload">
<input
type="file"
id="image"
accept="image/*"
onChange={handleImageChange}
className="file-input"
/>
<label htmlFor="image" className="file-label">
<FaUpload />
Choose Image
</label>
</div>
{formData.imagePreview && (
<div className="image-preview">
<img src={formData.imagePreview} alt="Preview" />
</div>
)}
</div>
</div>
<div className="form-actions">
<button type="button" className="cancel-btn" onClick={onClose}>
<FaTimes /> Cancel
</button>
<button type="submit" className="save-btn">
<FaSave /> Create Item
</button>
</div>
</form>
)
}
export default CreateModalForm

View File

@ -0,0 +1,387 @@
import React, { useState } from 'react'
import { Popconfirm, message } from 'antd'
import { useAdminPanel } from '../AdminPanelContext'
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import "../Styles/EcosystemForm.scss"
const EcosystemForm = () => {
const { sectionData, updateSectionData } = useAdminPanel()
const ecosystemData = sectionData.EcosystemForm || []
const [isModalOpen, setIsModalOpen] = useState(false)
const [title, setTitle] = useState('')
const [posters, setPosters] = useState([{ id: 1, title: '', description: '', image: '' }])
const [editingIndex, setEditingIndex] = useState(-1)
const [titleError, setTitleError] = useState('')
const [posterTitleError, setPosterTitleError] = useState('')
const openModal = () => {
setIsModalOpen(true)
}
const closeModal = () => {
setIsModalOpen(false)
setTitle('')
setPosters([{ id: 1, title: '', description: '', image: '' }])
setEditingIndex(-1)
setTitleError('')
setPosterTitleError('')
}
const handleClear = () => {
setTitle('')
setPosters([{ id: 1, title: '', description: '', image: '' }])
setTitleError('')
setPosterTitleError('')
}
const addPoster = () => {
if (posters.length >= 4) {
message.warning('Maximum 4 posters allowed per ecosystem')
return
}
const newId = Math.max(...posters.map(p => p.id)) + 1
setPosters([...posters, { id: newId, title: '', description: '', image: '' }])
}
const removePoster = (id) => {
if (posters.length === 1) {
message.warning('At least one poster is required')
return
}
setPosters(posters.filter(p => p.id !== id))
}
const handlePosterChange = (id, field, value) => {
setPosters(posters.map(p => p.id === id ? { ...p, [field]: value } : p))
}
const handleImageChange = (id, e) => {
const file = e.target.files[0]
if (file) {
const reader = new FileReader()
reader.onloadend = () => {
handlePosterChange(id, 'image', reader.result)
}
reader.readAsDataURL(file)
}
}
const handleSave = () => {
let hasError = false
if (!title.trim()) {
setTitleError('Title is required')
hasError = true
} else {
setTitleError('')
}
const hasEmptyPoster = posters.some(p => !p.title.trim())
if (hasEmptyPoster) {
setPosterTitleError('All poster titles are required')
hasError = true
} else {
setPosterTitleError('')
}
if (hasError) {
message.error('Please fix the highlighted fields')
return
}
const newEcosystem = {
id: editingIndex >= 0 ? ecosystemData[editingIndex].id : Date.now(),
title: title.trim(),
posters: posters.map(p => ({
id: p.id,
title: p.title.trim(),
description: p.description.trim(),
image: p.image
})),
active: true,
createdAt: editingIndex >= 0 ? ecosystemData[editingIndex].createdAt : new Date().toLocaleDateString()
}
if (editingIndex >= 0) {
const updatedEcosystems = ecosystemData.map((eco, index) => index === editingIndex ? newEcosystem : eco)
updateSectionData('EcosystemForm', updatedEcosystems)
setEditingIndex(-1)
message.success('Ecosystem updated!')
} else {
updateSectionData('EcosystemForm', [...ecosystemData, newEcosystem])
message.success('Ecosystem added!')
}
closeModal()
}
const handleDelete = (index) => {
const updatedEcosystems = ecosystemData.filter((_, i) => i !== index)
updateSectionData('EcosystemForm', updatedEcosystems)
message.success('Ecosystem deleted')
}
const handleEdit = (index) => {
const eco = ecosystemData[index]
setTitle(eco.title)
if (eco.posters && eco.posters.length > 0) {
setPosters(eco.posters)
} else {
// Handle old format
setPosters([{
id: 1,
title: eco.posterTitle || '',
description: eco.posterDescription || '',
image: eco.image || ''
}])
}
setEditingIndex(index)
openModal()
}
const handleToggleActive = (index) => {
const updated = [...ecosystemData]
updated[index].active = !updated[index].active
updateSectionData('EcosystemForm', updated)
message.success('Status updated')
}
return (
<div className='ecosystem-section-form-master'>
{/* Header Section */}
<div className='ecosystem-section-form-header'>
<div className='ecosystem-section-header-left'>
{/* <div className='ecosystem-section-header-icon'>
<span>🌍</span>
</div> */}
<div className='ecosystem-section-header-content'>
<h2>Ecosystem Management</h2>
<p>Manage and organize your ecosystem content and settings.</p>
</div>
</div>
<button onClick={openModal} className='ecosystem-section-create-btn'>
<span>+</span> Create Ecosystem
</button>
</div>
{/* Tab Navigation */}
<div className='ecosystem-section-tab-navigation'>
<div className='ecosystem-section-tab-buttons'>
<button className='ecosystem-section-tab-btn ecosystem-section-active'>
Ecosystems ({ecosystemData.length})
</button>
<button className='ecosystem-section-tab-btn'>
All Status
</button>
</div>
<div className='ecosystem-section-tab-controls'>
<div className='ecosystem-section-sort-dropdown'>
<select>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
<div className='ecosystem-section-view-toggle'>
<button className='ecosystem-section-view-btn ecosystem-section-active'>
<span></span>
</button>
<button className='ecosystem-section-view-btn'>
<span></span>
</button>
</div>
</div>
</div>
{/* Content Area */}
{ecosystemData.length > 0 ? (
<div className='ecosystem-section-display'>
<div className='ecosystem-section-grid'>
{ecosystemData.map((eco, index) => (
<div key={eco.id} className={`ecosystem-section-card ${!eco.active ? 'ecosystem-section-inactive' : ''}`}>
<div className='ecosystem-section-card-header'>
<h3>{eco.title}</h3>
<div className='ecosystem-section-card-actions'>
<button className='ecosystem-section-edit-btn' onClick={() => handleEdit(index)}>Edit</button>
<Popconfirm
title="Delete Ecosystem"
description="Are you sure you want to delete this ecosystem?"
onConfirm={() => handleDelete(index)}
okText="Yes"
cancelText="No"
>
<button className='ecosystem-section-delete-btn'>Delete</button>
</Popconfirm>
<button
className={`ecosystem-section-status-btn ${eco.active ? 'ecosystem-section-deactivate' : 'ecosystem-section-activate'}`}
onClick={() => handleToggleActive(index)}
>
{eco.active ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
<div className='ecosystem-section-card-content'>
{eco.posters && eco.posters.length > 0 ? (
<div className='ecosystem-section-posters'>
<strong>Posters ({eco.posters.length}):</strong>
{eco.posters.map(poster => (
<div key={poster.id} className='ecosystem-section-poster-item'>
{poster.image && (
<div className='ecosystem-section-poster-image'>
<img src={poster.image} alt={poster.title} />
</div>
)}
<div className='ecosystem-section-poster-content'>
<div className='ecosystem-section-poster-title'>{poster.title}</div>
{poster.description && <div className='ecosystem-section-poster-desc'>{poster.description}</div>}
</div>
</div>
))}
</div>
) : (
// Fallback for old format
<div>
{eco.image && (
<div className='ecosystem-section-poster-image'>
<img src={eco.image} alt={eco.posterTitle} />
</div>
)}
<p><strong>Poster Title:</strong> {eco.posterTitle}</p>
<p><strong>Description:</strong> {eco.posterDescription || 'No description'}</p>
</div>
)}
<p className='ecosystem-section-created-date'>Created: {eco.date || eco.createdAt}</p>
<p className={`ecosystem-section-status ${eco.active ? 'ecosystem-section-active' : 'ecosystem-section-inactive'}`}>
Status: {eco.active ? 'Active' : 'Inactive'}
</p>
</div>
</div>
))}
</div>
</div>
) : (
<div className='ecosystem-section-placeholder-content'>
{/* <div className='ecosystem-section-placeholder-icon'>
<span>🌍</span>
</div> */}
<h2>No Ecosystems Found</h2>
<p>Get started by creating your first ecosystem</p>
</div>
)}
{/* Modal */}
<DefaultModal
open={isModalOpen}
title={editingIndex >= 0 ? 'Edit Ecosystem' : 'Create New Ecosystem'}
handleCancel={closeModal}
handleSubmit={handleSave}
buttonText={editingIndex >= 0 ? 'Update Ecosystem' : 'Add Ecosystem'}
width={700}
destroyOnClose={true}
>
<div className='ecosystem-section-form'>
<div className='ecosystem-section-form-group'>
<label htmlFor='ecosystemTitle'>Title</label>
<input
type='text'
id='ecosystemTitle'
value={title}
onChange={(e) => {
setTitle(e.target.value)
if (titleError) setTitleError('')
}}
placeholder='Enter ecosystem title'
className={titleError ? 'error' : ''}
autoFocus
/>
{titleError && <span className='error-message'>{titleError}</span>}
</div>
<div className='ecosystem-section-posters-container'>
<div className='ecosystem-section-posters-header'>
<h3>Ecosystem Posters</h3>
<p>Add up to 4 posters for this ecosystem</p>
</div>
{posters.map((poster, idx) => (
<div key={poster.id} className='ecosystem-section-poster'>
<div className='ecosystem-section-poster-number'>
Poster {idx + 1}
{posters.length > 1 && (
<button
type='button'
className='ecosystem-section-remove-poster-btn'
onClick={() => removePoster(poster.id)}
>
</button>
)}
</div>
<div className='ecosystem-section-form-group'>
<label>Poster Title</label>
<input
type='text'
placeholder='Enter poster title'
value={poster.title}
onChange={(e) => handlePosterChange(poster.id, 'title', e.target.value)}
className={posterTitleError ? 'error' : ''}
/>
</div>
<div className='ecosystem-section-form-group'>
<label>Image Upload</label>
<input
type='file'
accept='image/*'
onChange={(e) => handleImageChange(poster.id, e)}
className='ecosystem-section-file-input'
/>
{poster.image && (
<div className='ecosystem-section-image-preview'>
<img src={poster.image} alt='Preview' />
</div>
)}
</div>
<div className='ecosystem-section-form-group'>
<label>Description</label>
<textarea
placeholder='Enter poster description'
value={poster.description}
onChange={(e) => handlePosterChange(poster.id, 'description', e.target.value)}
rows='3'
/>
</div>
</div>
))}
{posterTitleError && <span className='error-message'>{posterTitleError}</span>}
<button
type='button'
className='ecosystem-section-add-poster-btn'
onClick={addPoster}
disabled={posters.length >= 4}
>
+ Add Poster
</button>
</div>
<div className='ecosystem-section-form-actions'>
<button type='button' onClick={handleSave} className='ecosystem-section-save-btn'>
{editingIndex >= 0 ? 'Update' : 'Save'}
</button>
<button type='button' onClick={closeModal} className='ecosystem-section-cancel-btn'>
Cancel
</button>
<button type='button' onClick={handleClear} className='ecosystem-section-clear-btn'>
Clear
</button>
</div>
</div>
</DefaultModal>
</div>
)
}
export default EcosystemForm

View File

@ -0,0 +1,268 @@
import React, { useState, useRef, useEffect } from 'react'
import { Popconfirm, message } from 'antd'
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import "../Styles/FaqSectionForm.scss"
const FaqSectionForm = () => {
const [question, setQuestion] = useState('')
const [answer, setAnswer] = useState('')
const [faqs, setFaqs] = useState([])
const [editingId, setEditingId] = useState(null)
const [isModalOpen, setIsModalOpen] = useState(false)
const [questionError, setQuestionError] = useState('')
const [answerError, setAnswerError] = useState('')
const questionInputRef = useRef(null)
// UUID generator fallback for browser compatibility
const generateUUID = () => {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID()
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
const openModal = () => {
setIsModalOpen(true)
}
const closeModal = () => {
setIsModalOpen(false)
handleClear()
}
const handleSave = () => {
let hasError = false
if (!question.trim()) {
setQuestionError('Question is required')
hasError = true
} else {
setQuestionError('')
}
if (!answer.trim()) {
setAnswerError('Answer is required')
hasError = true
} else {
setAnswerError('')
}
if (hasError) {
message.error('Please fix the highlighted fields')
return
}
const newFaq = {
id: editingId || generateUUID(),
question: question.trim(),
answer: answer.trim(),
isActive: true,
createdAt: new Date().toLocaleDateString('en-US')
}
if (editingId) {
setFaqs(prev => prev.map(faq => faq.id === editingId ? newFaq : faq))
setEditingId(null)
message.success('FAQ updated!')
} else {
setFaqs(prev => [...prev, newFaq])
message.success('FAQ added!')
}
closeModal()
}
const handleClear = () => {
setQuestion('')
setAnswer('')
setEditingId(null)
setQuestionError('')
setAnswerError('')
}
const handleEdit = (faq) => {
setQuestion(faq.question)
setAnswer(faq.answer)
setEditingId(faq.id)
openModal()
}
useEffect(() => {
if (editingId && questionInputRef.current) {
questionInputRef.current.focus()
}
}, [editingId])
const handleDelete = (id) => {
setFaqs(prev => prev.filter(faq => faq.id !== id))
message.success('FAQ deleted')
}
const handleToggleStatus = (id) => {
setFaqs(prev => prev.map(faq =>
faq.id === id ? { ...faq, isActive: !faq.isActive } : faq
))
}
return (
<div className='faq-form-master'>
{/* Header Section */}
<div className='faq-form-header'>
<div className='faq-header-left'>
{/* <div className='faq-header-icon'>
<span></span>
</div> */}
<div className='faq-header-content'>
<h2>FAQ Management</h2>
<p>Manage and organize your frequently asked questions.</p>
</div>
</div>
<button onClick={openModal} className='faq-create-btn'>
<span>+</span> Create FAQ
</button>
</div>
{/* Tab Navigation */}
<div className='faq-tab-navigation'>
<div className='faq-tab-buttons'>
<button className='faq-tab-btn faq-active'>
FAQs ({faqs.length})
</button>
<button className='faq-tab-btn'>
All Status
</button>
</div>
<div className='faq-tab-controls'>
<div className='faq-sort-dropdown'>
<select>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
<div className='faq-view-toggle'>
<button className='faq-view-btn faq-active'>
<span></span>
</button>
<button className='faq-view-btn'>
<span></span>
</button>
</div>
</div>
</div>
{/* Content Area */}
{faqs.length > 0 ? (
<div className='faq-display'>
<div className='faq-grid'>
{faqs.map((faq, index) => (
<div key={faq.id} className={`faq-card ${!faq.isActive ? 'faq-inactive' : ''}`}>
<div className='faq-card-header'>
<h3>{faq.question}</h3>
<div className='faq-card-actions'>
<button className='faq-edit-btn' onClick={() => handleEdit(faq)}>Edit</button>
<Popconfirm
title="Delete FAQ"
description="Are you sure you want to delete this FAQ?"
onConfirm={() => handleDelete(faq.id)}
okText="Yes"
cancelText="No"
>
<button className='faq-delete-btn'>Delete</button>
</Popconfirm>
<button
className={`faq-status-btn ${faq.isActive ? 'faq-deactivate' : 'faq-activate'}`}
onClick={() => handleToggleStatus(faq.id)}
>
{faq.isActive ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
<div className='faq-card-content'>
<p><strong>Answer:</strong> {faq.answer}</p>
<p className='faq-created-date'>Created: {faq.createdAt}</p>
<p className={`faq-status ${faq.isActive ? 'faq-active' : 'faq-inactive'}`}>
Status: {faq.isActive ? 'Active' : 'Inactive'}
</p>
</div>
</div>
))}
</div>
</div>
) : (
<div className='faq-placeholder-content'>
<div className='faq-placeholder-icon'>
<span></span>
</div>
<h2>No FAQs Found</h2>
<p>Get started by creating your first FAQ</p>
</div>
)}
{/* Modal */}
<DefaultModal
open={isModalOpen}
title={editingId ? 'Edit FAQ' : 'Create New FAQ'}
handleCancel={closeModal}
handleSubmit={handleSave}
buttonText={editingId ? 'Update FAQ' : 'Add FAQ'}
width={600}
destroyOnClose={true}
>
<div className='faq-form'>
<div className='faq-form-group'>
<label htmlFor='question'>Question</label>
<input
type='text'
id='question'
ref={questionInputRef}
value={question}
onChange={(e) => {
setQuestion(e.target.value)
if (questionError) setQuestionError('')
}}
placeholder='Enter FAQ question'
className={questionError ? 'error' : ''}
autoFocus
/>
{questionError && <span className='error-message'>{questionError}</span>}
</div>
<div className='faq-form-group'>
<label htmlFor='answer'>Answer</label>
<textarea
id='answer'
value={answer}
onChange={(e) => {
setAnswer(e.target.value)
if (answerError) setAnswerError('')
}}
placeholder='Enter FAQ answer'
className={answerError ? 'error' : ''}
rows='4'
/>
{answerError && <span className='error-message'>{answerError}</span>}
</div>
<div className='faq-form-actions'>
<button type='button' onClick={handleSave} className='faq-save-btn'>
{editingId ? 'Update' : 'Save'}
</button>
<button type='button' onClick={closeModal} className='faq-cancel-btn'>
Cancel
</button>
<button type='button' onClick={handleClear} className='faq-clear-btn'>
Clear
</button>
</div>
</div>
</DefaultModal>
</div>
)
}
export default FaqSectionForm

View File

@ -0,0 +1,226 @@
import React, { useState } from 'react'
import "../Styles/FooterForm.scss"
const FooterForm = () => {
const [mobileNumber, setMobileNumber] = useState('')
const [savedNumbers, setSavedNumbers] = useState([])
const [editingIndex, setEditingIndex] = useState(-1)
const [socialImage, setSocialImage] = useState('')
const [socialLink, setSocialLink] = useState('')
const [savedSocialLinks, setSavedSocialLinks] = useState([])
const [editingSocialIndex, setEditingSocialIndex] = useState(-1)
const handleNumberChange = (e) => {
const value = e.target.value.replace(/[^0-9]/g, '')
setMobileNumber(value)
}
const handleSave = () => {
if (mobileNumber.trim()) {
const newNumber = {
number: mobileNumber,
active: true,
id: Date.now()
}
if (editingIndex >= 0) {
const updated = [...savedNumbers]
updated[editingIndex] = { ...updated[editingIndex], number: mobileNumber }
setSavedNumbers(updated)
setEditingIndex(-1)
} else {
setSavedNumbers([...savedNumbers, newNumber])
}
setMobileNumber('')
}
}
const handleClear = () => {
setMobileNumber('')
setEditingIndex(-1)
}
const handleEdit = (index) => {
setMobileNumber(savedNumbers[index].number)
setEditingIndex(index)
}
const handleDelete = (index) => {
setSavedNumbers(savedNumbers.filter((_, i) => i !== index))
}
const handleToggleActive = (index) => {
const updated = [...savedNumbers]
updated[index].active = !updated[index].active
setSavedNumbers(updated)
}
const handleSocialSave = () => {
if (socialImage.trim() && socialLink.trim()) {
const newSocialLink = {
image: socialImage,
link: socialLink,
active: true,
id: Date.now()
}
if (editingSocialIndex >= 0) {
const updated = [...savedSocialLinks]
updated[editingSocialIndex] = { ...updated[editingSocialIndex], image: socialImage, link: socialLink }
setSavedSocialLinks(updated)
setEditingSocialIndex(-1)
} else {
setSavedSocialLinks([...savedSocialLinks, newSocialLink])
}
setSocialImage('')
setSocialLink('')
}
}
const handleSocialClear = () => {
setSocialImage('')
setSocialLink('')
setEditingSocialIndex(-1)
}
const handleSocialEdit = (index) => {
setSocialImage(savedSocialLinks[index].image)
setSocialLink(savedSocialLinks[index].link)
setEditingSocialIndex(index)
}
const handleSocialDelete = (index) => {
setSavedSocialLinks(savedSocialLinks.filter((_, i) => i !== index))
}
const handleSocialToggleActive = (index) => {
const updated = [...savedSocialLinks]
updated[index].active = !updated[index].active
setSavedSocialLinks(updated)
}
return (
<div className='FooterFormMaster'>
<div className="footer-header">
<h1>Footer Settings</h1>
</div>
<div className="footer-form">
<div className="input-group">
<label htmlFor="mobileNumber">Mobile Number</label>
<input
type="tel"
id="mobileNumber"
value={mobileNumber}
onChange={handleNumberChange}
placeholder="Enter mobile number"
/>
</div>
<div className="form-buttons">
<button onClick={handleSave} className="save-btn">Save</button>
<button onClick={handleClear} className="clear-btn">Clear</button>
</div>
<div className="saved-numbers">
<h3>Saved Mobile Numbers:</h3>
{savedNumbers.length > 0 ? (
<div className="numbers-cards">
{savedNumbers.map((numberObj, index) => (
<div key={numberObj.id} className={`number-card ${!numberObj.active ? 'inactive' : ''}`}>
<div className="card-content">
<div className="number-header">
<h4>{numberObj.number}</h4>
<span className={`status-badge ${numberObj.active ? 'active' : 'inactive'}`}>
{numberObj.active ? 'Active' : 'Inactive'}
</span>
</div>
<div className="card-footer">
<span className="card-badge">Number #{index + 1}</span>
<div className="number-actions">
<button onClick={() => handleEdit(index)} className="edit-btn">Edit</button>
<button onClick={() => handleDelete(index)} className="delete-btn">Delete</button>
<button onClick={() => handleToggleActive(index)} className="toggle-btn">
{numberObj.active ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
</div>
</div>
))}
</div>
) : (
<div className="no-data">
<p>No mobile numbers found. Add your first number above.</p>
</div>
)}
</div>
<div className="social-media-section">
<div className="input-group">
<label htmlFor="socialImage">Social Media Image URL</label>
<input
type="url"
id="socialImage"
value={socialImage}
onChange={(e) => setSocialImage(e.target.value)}
placeholder="Enter image URL"
/>
</div>
<div className="input-group">
<label htmlFor="socialLink">Social Media Link</label>
<input
type="url"
id="socialLink"
value={socialLink}
onChange={(e) => setSocialLink(e.target.value)}
placeholder="Enter social media link"
/>
</div>
<div className="form-buttons">
<button onClick={handleSocialSave} className="save-btn">Save</button>
<button onClick={handleSocialClear} className="clear-btn">Clear</button>
</div>
<div className="saved-numbers">
<h3>Saved Social Media Links:</h3>
{savedSocialLinks.length > 0 ? (
<div className="numbers-cards">
{savedSocialLinks.map((socialObj, index) => (
<div key={socialObj.id} className={`number-card ${!socialObj.active ? 'inactive' : ''}`}>
<div className="card-content">
<div className="number-header">
<img src={socialObj.image} alt="Social" style={{width: '40px', height: '40px', borderRadius: '4px'}} />
<h4>{socialObj.link}</h4>
<span className={`status-badge ${socialObj.active ? 'active' : 'inactive'}`}>
{socialObj.active ? 'Active' : 'Inactive'}
</span>
</div>
<div className="card-footer">
<span className="card-badge">Link #{index + 1}</span>
<div className="number-actions">
<button onClick={() => handleSocialEdit(index)} className="edit-btn">Edit</button>
<button onClick={() => handleSocialDelete(index)} className="delete-btn">Delete</button>
<button onClick={() => handleSocialToggleActive(index)} className="toggle-btn">
{socialObj.active ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
</div>
</div>
))}
</div>
) : (
<div className="no-data">
<p>No social media links found. Add your first link above.</p>
</div>
)}
</div>
</div>
</div>
</div>
)
}
export default FooterForm

View File

@ -0,0 +1,415 @@
import React, { useState, useEffect } from 'react'
import { Popconfirm, message } from 'antd'
import { useAdminPanel } from '../AdminPanelContext'
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import "../Styles/HeroSectionForm.scss"
const HeroSectionForm = () => {
const { sectionData, updateSectionData } = useAdminPanel()
const heroData = sectionData.HeroSectionForm || []
const [isModalOpen, setIsModalOpen] = useState(false)
const [title, setTitle] = useState('')
const [description, setDescription] = useState('')
const [subsections, setSubsections] = useState([{ id: 1, image: null, previewUrl: null, heading: '', subheading: '' }])
const [editingIndex, setEditingIndex] = useState(-1)
const [titleError, setTitleError] = useState('')
const [descriptionError, setDescriptionError] = useState('')
// UUID generator fallback for browser compatibility
const generateUUID = () => {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID()
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
useEffect(() => {
return () => {
subsections.forEach(s => { if (s.previewUrl) URL.revokeObjectURL(s.previewUrl) })
}
}, [])
const openModal = () => {
setIsModalOpen(true)
}
const closeModal = () => {
setIsModalOpen(false)
setTitle('')
setDescription('')
setSubsections([{ id: 1, image: null, previewUrl: null, heading: '', subheading: '' }])
setEditingIndex(-1)
setTitleError('')
setDescriptionError('')
}
const handleClear = () => {
setTitle('')
setDescription('')
setSubsections([{ id: 1, image: null, previewUrl: null, heading: '', subheading: '' }])
setTitleError('')
setDescriptionError('')
}
const addSubsection = () => {
if (subsections.length >= 3) {
message.warning('Only 3 subsections allowed')
return
}
const newId = Math.max(...subsections.map(s => s.id)) + 1
setSubsections([...subsections, { id: newId, image: null, previewUrl: null, heading: '', subheading: '' }])
}
const removeSubsection = (id) => {
if (subsections.length === 1) {
message.warning('At least one subsection is required')
return
}
const toRemove = subsections.find(s => s.id === id)
if (toRemove?.previewUrl) {
URL.revokeObjectURL(toRemove.previewUrl)
}
setSubsections(subsections.filter(s => s.id !== id))
}
const handleImageUpload = (id, file) => {
setSubsections(subsections.map(s => {
if (s.id !== id) return s
if (s.previewUrl) {
URL.revokeObjectURL(s.previewUrl)
}
const nextUrl = file ? URL.createObjectURL(file) : null
return { ...s, image: file || null, previewUrl: nextUrl }
}))
}
const handleSubsectionChange = (id, field, value) => {
setSubsections(subsections.map(s =>
s.id === id ? { ...s, [field]: value } : s
))
}
const handleSave = () => {
let hasError = false
if (!title.trim()) {
setTitleError('Title is required')
hasError = true
} else {
setTitleError('')
}
if (!description.trim()) {
setDescriptionError('Description is required')
hasError = true
} else {
setDescriptionError('')
}
if (hasError) {
message.error('Please fix the highlighted fields')
return
}
const newHero = {
id: editingIndex >= 0 ? heroData[editingIndex].id : generateUUID(),
title: title.trim(),
description: description.trim(),
subsections: subsections.map(s => ({
id: s.id,
heading: s.heading,
subheading: s.subheading,
displayUrl: s.previewUrl || (s.image ? URL.createObjectURL(s.image) : null),
})),
active: true,
createdAt: editingIndex >= 0 ? heroData[editingIndex].createdAt : new Date().toLocaleDateString()
}
if (editingIndex >= 0) {
const updatedHeros = heroData.map((hero, index) => index === editingIndex ? newHero : hero)
updateSectionData('HeroSectionForm', updatedHeros)
setEditingIndex(-1)
message.success('Hero section updated!')
} else {
updateSectionData('HeroSectionForm', [...heroData, newHero])
message.success('Hero section added!')
}
closeModal()
}
const handleDelete = (index) => {
const updatedHeros = heroData.filter((_, i) => i !== index)
updateSectionData('HeroSectionForm', updatedHeros)
message.success('Hero section deleted')
}
const handleEdit = (index) => {
setTitle(heroData[index].title)
setDescription(heroData[index].description)
if (heroData[index].subsections && heroData[index].subsections.length > 0) {
setSubsections(heroData[index].subsections.map(ss => ({
id: ss.id,
image: null,
previewUrl: ss.displayUrl || null,
heading: ss.heading || '',
subheading: ss.subheading || ''
})))
}
setEditingIndex(index)
openModal()
}
const handleToggleActive = (index) => {
const updated = [...heroData]
updated[index].active = !updated[index].active
updateSectionData('HeroSectionForm', updated)
message.success('Status updated')
}
return (
<div className='hero-section-form-master'>
{/* Header Section */}
<div className='hero-section-form-header'>
<div className='hero-section-header-left'>
<div className='hero-section-header-content'>
<h2>Hero Section Management</h2>
<p>Manage and organize your hero section content and settings.</p>
</div>
</div>
<button onClick={openModal} className='hero-section-create-btn'>
<span>+</span> Create Hero Section
</button>
</div>
{/* Tab Navigation */}
<div className='hero-section-tab-navigation'>
<div className='hero-section-tab-buttons'>
<button className='hero-section-tab-btn hero-section-active'>
Hero Sections ({heroData.length})
</button>
<button className='hero-section-tab-btn'>
All Status
</button>
</div>
<div className='hero-section-tab-controls'>
<div className='hero-section-sort-dropdown'>
<select>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
<div className='hero-section-view-toggle'>
<button className='hero-section-view-btn hero-section-active'>
<span></span>
</button>
<button className='hero-section-view-btn'>
<span></span>
</button>
</div>
</div>
</div>
{/* Content Area */}
{heroData.length > 0 ? (
<div className='hero-section-display'>
<div className='hero-section-grid'>
{heroData.map((hero, index) => (
<div key={hero.id} className={`hero-section-card ${!hero.active ? 'hero-section-inactive' : ''}`}>
<div className='hero-section-card-header'>
<h3>{hero.title}</h3>
<div className='hero-section-card-actions'>
<button className='hero-section-edit-btn' onClick={() => handleEdit(index)}>Edit</button>
<Popconfirm
title="Delete Hero Section"
description="Are you sure you want to delete this hero section?"
onConfirm={() => handleDelete(index)}
okText="Yes"
cancelText="No"
>
<button className='hero-section-delete-btn'>Delete</button>
</Popconfirm>
<button
className={`hero-section-status-btn ${hero.active ? 'hero-section-deactivate' : 'hero-section-activate'}`}
onClick={() => handleToggleActive(index)}
>
{hero.active ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
<div className='hero-section-card-content'>
<p><strong>Description:</strong> {hero.description || 'No description'}</p>
<p className='hero-section-created-date'>Created: {hero.date || hero.createdAt}</p>
<p className={`hero-section-status ${hero.active ? 'hero-section-active' : 'hero-section-inactive'}`}>
Status: {hero.active ? 'Active' : 'Inactive'}
</p>
{hero.subsections && hero.subsections.length > 0 && (
<div className='hero-section-subsections'>
<strong>Subsections:</strong>
{hero.subsections.map(ss => (
<div key={ss.id} className='hero-section-subsection-item'>
{ss.displayUrl && (
<div className='hero-section-thumb'>
<img src={ss.displayUrl} alt={ss.heading || 'Subsection'} />
</div>
)}
<div className='hero-section-texts'>
{ss.heading && <div className='hero-section-heading'>{ss.heading}</div>}
{ss.subheading && <div className='hero-section-subheading'>{ss.subheading}</div>}
</div>
</div>
))}
</div>
)}
</div>
</div>
))}
</div>
</div>
) : (
<div className='hero-section-placeholder-content'>
<div className='hero-section-placeholder-icon'>
<span>🎯</span>
</div>
<h2>No Hero Sections Found</h2>
<p>Get started by creating your first hero section</p>
</div>
)
}
{/* Modal */}
<DefaultModal
open={isModalOpen}
title={editingIndex >= 0 ? 'Edit Hero Section' : 'Create New Hero Section'}
handleCancel={closeModal}
handleSubmit={handleSave}
buttonText={editingIndex >= 0 ? 'Update Hero' : 'Add Hero'}
width={700}
destroyOnClose={true}
>
<div className='hero-section-form'>
<div className='hero-section-form-group'>
<label htmlFor='heroTitle'>Title</label>
<input
type='text'
id='heroTitle'
value={title}
onChange={(e) => {
setTitle(e.target.value)
if (titleError) setTitleError('')
}}
placeholder='Enter hero title'
className={titleError ? 'error' : ''}
autoFocus
/>
{titleError && <span className='error-message'>{titleError}</span>}
</div>
<div className='hero-section-form-group'>
<label htmlFor='heroDescription'>Description</label>
<textarea
id='heroDescription'
value={description}
onChange={(e) => {
setDescription(e.target.value)
if (descriptionError) setDescriptionError('')
}}
placeholder='Enter hero description'
rows='4'
className={descriptionError ? 'error' : ''}
/>
{descriptionError && <span className='error-message'>{descriptionError}</span>}
</div>
<div className='hero-section-subsections-container'>
<div className='hero-section-subsections-header'>
<h3>Subsections</h3>
<p>Add up to 3 subsections with images and content</p>
</div>
{subsections.map((subsection, idx) => (
<div key={subsection.id} className='hero-section-subsection'>
<div className='hero-section-subsection-number'>
Subsection {idx + 1}
{subsections.length > 1 && (
<button
type='button'
className='hero-section-remove-subsection-btn'
onClick={() => removeSubsection(subsection.id)}
>
</button>
)}
</div>
<div className='hero-section-form-group'>
<label>Image Upload</label>
<input
type='file'
accept='image/*'
onChange={(e) => handleImageUpload(subsection.id, e.target.files[0])}
className='hero-section-file-input'
aria-label={`Upload image for subsection ${idx + 1}`}
/>
{subsection.previewUrl && (
<div className='hero-section-image-preview'>
<img src={subsection.previewUrl} alt='Preview' />
</div>
)}
</div>
<div className='hero-section-form-group'>
<label>Heading</label>
<input
type='text'
placeholder='Enter heading'
value={subsection.heading}
onChange={(e) => handleSubsectionChange(subsection.id, 'heading', e.target.value)}
/>
</div>
<div className='hero-section-form-group'>
<label>Sub Heading</label>
<input
type='text'
placeholder='Enter sub heading'
value={subsection.subheading}
onChange={(e) => handleSubsectionChange(subsection.id, 'subheading', e.target.value)}
/>
</div>
</div>
))}
<button
type='button'
className='hero-section-add-subsection-btn'
onClick={addSubsection}
disabled={subsections.length >= 3}
>
+ Add Subsection
</button>
</div>
<div className='hero-section-form-actions'>
<button type='button' onClick={handleSave} className='hero-section-save-btn'>
{editingIndex >= 0 ? 'Update' : 'Save'}
</button>
<button type='button' onClick={closeModal} className='hero-section-cancel-btn'>
Cancel
</button>
<button type='button' onClick={handleClear} className='hero-section-clear-btn'>
Clear
</button>
</div>
</div>
</DefaultModal>
</div>
)
}
export default HeroSectionForm

View File

@ -0,0 +1,381 @@
import React, { useState } from 'react'
import { Popconfirm } from 'antd'
import { useAdminPanel } from '../AdminPanelContext'
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import "../Styles/OfferingsForm.scss"
const OfferingsForm = () => {
const { sectionData, updateSectionData } = useAdminPanel()
const ecosystemData = sectionData.EcosystemForm || []
const [isModalOpen, setIsModalOpen] = useState(false)
const [title, setTitle] = useState('')
const [posters, setPosters] = useState([{ id: 1, title: '', description: '', image: '' }])
const [editingIndex, setEditingIndex] = useState(-1)
const [titleError, setTitleError] = useState('')
const [posterErrors, setPosterErrors] = useState({})
const openModal = () => {
setIsModalOpen(true)
}
const closeModal = () => {
setIsModalOpen(false)
setTitle('')
setPosters([{ id: 1, title: '', description: '', image: '' }])
setEditingIndex(-1)
setTitleError('')
setPosterErrors({})
}
const handleClear = () => {
setTitle('')
setPosters([{ id: 1, title: '', description: '', image: '' }])
setTitleError('')
setPosterErrors({})
}
const addPoster = () => {
const newId = Math.max(...posters.map(p => p.id)) + 1
setPosters([...posters, { id: newId, title: '', description: '', image: '' }])
}
const removePoster = (id) => {
if (posters.length > 1) {
setPosters(posters.filter(p => p.id !== id))
const newErrors = { ...posterErrors }
delete newErrors[id]
setPosterErrors(newErrors)
}
}
const handlePosterChange = (id, field, value) => {
setPosters(posters.map(p => p.id === id ? { ...p, [field]: value } : p))
if (posterErrors[id]) {
const newErrors = { ...posterErrors }
delete newErrors[id]
setPosterErrors(newErrors)
}
}
const handleImageChange = (id, e) => {
const file = e.target.files[0]
if (file) {
const reader = new FileReader()
reader.onloadend = () => {
handlePosterChange(id, 'image', reader.result)
}
reader.readAsDataURL(file)
}
}
const handleSave = () => {
let hasError = false
const errors = {}
if (!title.trim()) {
setTitleError('Title is required')
hasError = true
} else {
setTitleError('')
}
posters.forEach(poster => {
if (!poster.title.trim()) {
errors[poster.id] = 'Poster title is required'
hasError = true
}
})
setPosterErrors(errors)
if (hasError) return
const newEcosystem = {
id: editingIndex >= 0 ? ecosystemData[editingIndex].id : Date.now(),
title: title.trim(),
posters: posters.map(p => ({
id: p.id,
title: p.title.trim(),
description: p.description.trim(),
image: p.image
})),
active: true,
createdAt: editingIndex >= 0 ? ecosystemData[editingIndex].createdAt : new Date().toLocaleDateString()
}
if (editingIndex >= 0) {
const updatedEcosystems = ecosystemData.map((ecosystem, index) => index === editingIndex ? newEcosystem : ecosystem)
updateSectionData('EcosystemForm', updatedEcosystems)
setEditingIndex(-1)
} else {
updateSectionData('EcosystemForm', [...ecosystemData, newEcosystem])
}
closeModal()
}
const handleDelete = (index) => {
const updatedEcosystems = ecosystemData.filter((_, i) => i !== index)
updateSectionData('EcosystemForm', updatedEcosystems)
}
const handleEdit = (index) => {
setTitle(ecosystemData[index].title)
if (ecosystemData[index].posters && ecosystemData[index].posters.length > 0) {
setPosters(ecosystemData[index].posters)
} else {
// Handle legacy data structure
setPosters([{
id: 1,
title: ecosystemData[index].posterTitle || '',
description: ecosystemData[index].posterDescription || '',
image: ecosystemData[index].posterImage || ''
}])
}
setEditingIndex(index)
openModal()
}
const handleToggleActive = (index) => {
const updated = [...ecosystemData]
updated[index].active = !updated[index].active
updateSectionData('EcosystemForm', updated)
}
return (
<div className='OfferingsFormMaster'>
{/* Header Section */}
<div className='ecosystem-form-header'>
<div className='ecosystem-header-left'>
{/* <div className='ecosystem-header-icon'>
<span>🌱</span>
</div> */}
<div className='ecosystem-header-content'>
<h2>Offerings Management</h2>
<p>Manage and organize your ecosystem content and settings.</p>
</div>
</div>
<button onClick={openModal} className='ecosystem-create-btn'>
<span>+</span> Create Ecosystem
</button>
</div>
{/* Tab Navigation */}
<div className='ecosystem-tab-navigation'>
<div className='ecosystem-tab-buttons'>
<button className='ecosystem-tab-btn ecosystem-active'>
Offerings ({ecosystemData.length})
</button>
<button className='ecosystem-tab-btn'>
All Status
</button>
</div>
<div className='ecosystem-tab-controls'>
<div className='ecosystem-sort-dropdown'>
<select>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
<div className='ecosystem-view-toggle'>
<button className='ecosystem-view-btn ecosystem-active'>
<span></span>
</button>
<button className='ecosystem-view-btn'>
<span></span>
</button>
</div>
</div>
</div>
{/* Content Area */}
{ecosystemData.length > 0 ? (
<div className='ecosystem-display'>
<div className='ecosystem-grid'>
{ecosystemData.map((ecosystem, index) => (
<div key={ecosystem.id} className={`ecosystem-card ${!ecosystem.active ? 'ecosystem-inactive' : ''}`}>
<div className='ecosystem-card-header'>
<h3>{ecosystem.title}</h3>
<div className='ecosystem-card-actions'>
<button className='ecosystem-edit-btn' onClick={() => handleEdit(index)}>Edit</button>
<Popconfirm
title="Delete Ecosystem"
description="Are you sure you want to delete this ecosystem?"
onConfirm={() => handleDelete(index)}
okText="Yes"
cancelText="No"
>
<button className='ecosystem-delete-btn'>Delete</button>
</Popconfirm>
<button
className={`ecosystem-status-btn ${ecosystem.active ? 'ecosystem-deactivate' : 'ecosystem-activate'}`}
onClick={() => handleToggleActive(index)}
>
{ecosystem.active ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
<div className='ecosystem-card-content'>
{ecosystem.posters && ecosystem.posters.length > 0 ? (
<div className='ecosystem-posters'>
<strong>Posters ({ecosystem.posters.length}):</strong>
{ecosystem.posters.map(poster => (
<div key={poster.id} className='ecosystem-poster-item'>
{poster.image && (
<div className='ecosystem-poster-image'>
<img src={poster.image} alt={poster.title} />
</div>
)}
<div className='ecosystem-poster-content'>
<p><strong>{poster.title}</strong></p>
{poster.description && <p>{poster.description}</p>}
</div>
</div>
))}
</div>
) : (
// Legacy data structure support
<>
{ecosystem.posterImage && (
<div className='ecosystem-poster-image'>
<img src={ecosystem.posterImage} alt={ecosystem.posterTitle} />
</div>
)}
<p><strong>Poster Title:</strong> {ecosystem.posterTitle}</p>
<p><strong>Description:</strong> {ecosystem.posterDescription || 'No description'}</p>
</>
)}
<p className='ecosystem-created-date'>Created: {ecosystem.date || ecosystem.createdAt}</p>
<p className={`ecosystem-status ${ecosystem.active ? 'ecosystem-active' : 'ecosystem-inactive'}`}>
Status: {ecosystem.active ? 'Active' : 'Inactive'}
</p>
</div>
</div>
))}
</div>
</div>
) : (
<div className='ecosystem-placeholder-content'>
<div className='ecosystem-placeholder-icon'>
<span>🌱</span>
</div>
<h2>No Ecosystems Found</h2>
<p>Get started by creating your first ecosystem</p>
</div>
)}
{/* Modal */}
<DefaultModal
open={isModalOpen}
title={editingIndex >= 0 ? 'Edit Ecosystem' : 'Create New Ecosystem'}
handleCancel={closeModal}
handleSubmit={handleSave}
buttonText={editingIndex >= 0 ? 'Update Ecosystem' : 'Add Ecosystem'}
width={600}
destroyOnClose={true}
>
<div className='ecosystem-form'>
<div className='ecosystem-form-group'>
<label htmlFor='ecosystemTitle'>Title</label>
<input
type='text'
id='ecosystemTitle'
value={title}
onChange={(e) => {
setTitle(e.target.value)
if (titleError) setTitleError('')
}}
placeholder='Enter ecosystem title'
className={titleError ? 'error' : ''}
autoFocus
/>
{titleError && <span className='error-message'>{titleError}</span>}
</div>
<div className='posters-container'>
<div className='posters-header'>
<h3>Poster Sections</h3>
<p>Add multiple poster sections for your ecosystem</p>
</div>
{posters.map((poster, idx) => (
<div key={poster.id} className='poster-section'>
<div className='poster-section-header'>
<span>Poster {idx + 1}</span>
{posters.length > 1 && (
<button
type='button'
className='remove-poster-btn'
onClick={() => removePoster(poster.id)}
>
</button>
)}
</div>
<div className='ecosystem-form-group'>
<label>Poster Title</label>
<input
type='text'
value={poster.title}
onChange={(e) => handlePosterChange(poster.id, 'title', e.target.value)}
placeholder='Enter poster title'
className={posterErrors[poster.id] ? 'error' : ''}
/>
{posterErrors[poster.id] && <span className='error-message'>{posterErrors[poster.id]}</span>}
</div>
<div className='ecosystem-form-group'>
<label>Poster Description</label>
<textarea
value={poster.description}
onChange={(e) => handlePosterChange(poster.id, 'description', e.target.value)}
placeholder='Enter poster description'
rows='3'
/>
</div>
<div className='ecosystem-form-group'>
<label>Poster Image</label>
<input
type='file'
accept='image/*'
onChange={(e) => handleImageChange(poster.id, e)}
className='ecosystem-file-input'
/>
{poster.image && (
<div className='ecosystem-image-preview'>
<img src={poster.image} alt='Preview' />
</div>
)}
</div>
</div>
))}
<button
type='button'
className='add-poster-btn'
onClick={addPoster}
>
+ Add More Poster
</button>
</div>
<div className='ecosystem-form-actions'>
<button type='button' onClick={handleSave} className='ecosystem-save-btn'>
{editingIndex >= 0 ? 'Update' : 'Save'}
</button>
<button type='button' onClick={closeModal} className='ecosystem-cancel-btn'>
Cancel
</button>
<button type='button' onClick={handleClear} className='ecosystem-clear-btn'>
Clear
</button>
</div>
</div>
</DefaultModal>
</div>
)
}
export default OfferingsForm

View File

@ -0,0 +1,218 @@
import React, { useState, useEffect } from "react";
import { postSeo, getSeo, putSeo } from "../../features/SEO/seo.js";
import { useDispatch } from "react-redux";
import { getConfigNames } from "../../features/configmasterPage/configmasterPage.js";
import { message } from 'antd';
import { RiSettings4Line } from 'react-icons/ri';
import "../Styles/SeoForm.scss"
const SeoForm = () => {
const dispatch = useDispatch();
const [showForm, setShowForm] = useState(true);
const [detailSections, setDetailSections] = useState([{}]);
const [formData, setFormData] = useState({
pageName: "",
metaTitle: "",
metaDescription: "",
keywords: "",
imageAltText: "",
});
const [allpagesData, setAllpagesData] = useState([]);
const [isExistingData, setIsExistingData] = useState(false);
useEffect(() => {
getPagesName();
}, []);
const getPagesName = async () => {
let Res = await dispatch(getConfigNames({ TypeName: "SEO" })).unwrap();
if (Res?.data?.statusCode == 1) {
setAllpagesData(Res?.data?.data);
}
};
const handleChange = async (e) => {
const { name, value } = e.target;
// If page is selected, fetch existing SEO data
if (name === 'pageName') {
console.log('Page selected:', value);
if (value) {
try {
console.log('Fetching SEO data for PageId:', value);
const response = await dispatch(getSeo({ PageId: value })).unwrap();
console.log('SEO API Response:', response);
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
const seoData = response.data.data[0];
console.log('Found existing SEO data:', seoData);
setFormData({
pageName: value,
metaTitle: seoData.MetaTitle || '',
metaDescription: seoData.MetaDesc || '',
keywords: seoData.Keywords || '',
imageAltText: seoData.ImgAltText || '',
});
setIsExistingData(true);
} else {
console.log('No existing SEO data found');
setFormData({
pageName: value,
metaTitle: '',
metaDescription: '',
keywords: '',
imageAltText: '',
});
setIsExistingData(false);
}
} catch (error) {
console.error('Error fetching SEO data:', error);
setFormData({
pageName: value,
metaTitle: '',
metaDescription: '',
keywords: '',
imageAltText: '',
});
setIsExistingData(false);
}
} else {
setFormData({
pageName: '',
metaTitle: '',
metaDescription: '',
keywords: '',
imageAltText: '',
});
setIsExistingData(false);
}
} else {
setFormData({ ...formData, [name]: value });
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!formData.pageName) {
message.error("Please select a page");
return;
}
const postData = {
PageId: formData.pageName,
MetaTitle: formData.metaTitle,
MetaDesc: formData.metaDescription,
Keywords: formData.keywords,
ImgAltText: formData.imageAltText,
ImageUrl: "",
ActiveStatus: "A",
CreatedBy: 1,
};
try {
if (isExistingData) {
await dispatch(putSeo(postData)).unwrap();
message.success("SEO Settings updated successfully!");
} else {
await dispatch(postSeo(postData)).unwrap();
message.success("SEO Settings created successfully!");
setIsExistingData(true);
}
} catch (error) {
message.error("Failed to save SEO settings");
}
};
return (
<div className='seo-form-master'>
<div className='seo-form-header'>
<div className='seo-form-header-content'>
<h2>SEO Management</h2>
<p>Configure SEO settings for pages and content.</p>
</div>
</div>
<div className='seo-form-layout'>
<div className='seo-form-left'>
<div className='seo-form-container'>
<h3>SEO Configuration</h3>
<form onSubmit={handleSubmit}>
<div className='seo-form-field'>
<label>Select Page *</label>
<select
name="pageName"
value={formData.pageName}
onChange={handleChange}
required
>
<option value="">Choose a page...</option>
{allpagesData?.map((item) => (
<option key={item.ConfigId} value={item.ConfigId}>
{item.ConfigName}
</option>
))}
</select>
</div>
<div className='seo-form-field'>
<label>Meta Title</label>
<input
type="text"
name="metaTitle"
value={formData.metaTitle}
onChange={handleChange}
placeholder="Enter meta title..."
/>
</div>
<div className='seo-form-field'>
<label>Meta Description</label>
<textarea
name="metaDescription"
value={formData.metaDescription}
onChange={handleChange}
rows="4"
placeholder="Enter meta description..."
/>
</div>
<div className='seo-form-field'>
<label>Keywords</label>
<input
type="text"
name="keywords"
value={formData.keywords}
onChange={handleChange}
placeholder="Enter keywords separated by commas..."
/>
</div>
<div className='seo-form-field'>
<label>Image Alt Text</label>
<input
type="text"
name="imageAltText"
value={formData.imageAltText}
onChange={handleChange}
placeholder="Enter image alt text..."
/>
</div>
<div className='seo-form-actions'>
<button type="submit" className='seo-form-btn-primary'>
{isExistingData ? 'Update SEO Settings' : 'Create SEO Settings'}
</button>
<button type="button" className='seo-form-btn-secondary' onClick={() => setShowForm(false)}>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default SeoForm;

View File

@ -0,0 +1,322 @@
import React, { useState, useRef, useEffect } from 'react'
import { Popconfirm, message } from 'antd'
import { DefaultModal } from '../../Components/Modal/DefaultModal'
import "../Styles/TrendingAppsForm.scss"
const TrendingAppsForm = () => {
const [title, setTitle] = useState('')
const [secondTitle, setSecondTitle] = useState('')
const [selectedIndustries, setSelectedIndustries] = useState([])
const [trendingApps, setTrendingApps] = useState([])
const [editingId, setEditingId] = useState(null)
const [isModalOpen, setIsModalOpen] = useState(false)
const [titleError, setTitleError] = useState('')
const [secondTitleError, setSecondTitleError] = useState('')
const [industriesError, setIndustriesError] = useState('')
const titleInputRef = useRef(null)
// UUID generator fallback for browser compatibility
const generateUUID = () => {
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
return crypto.randomUUID()
}
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
const industries = [
'Bakery', 'Restaurant', 'Boating', 'Healthcare', 'Education',
'Retail', 'Technology', 'Finance', 'Real Estate', 'Automotive'
]
const handleIndustryChange = (industry) => {
setSelectedIndustries(prev =>
prev.includes(industry)
? prev.filter(item => item !== industry)
: [...prev, industry]
)
}
const openModal = () => {
setIsModalOpen(true)
}
const closeModal = () => {
setIsModalOpen(false)
handleClear()
}
const handleSave = () => {
let hasError = false
if (!title.trim()) {
setTitleError('Title is required')
hasError = true
} else {
setTitleError('')
}
if (!secondTitle.trim()) {
setSecondTitleError('Second title is required')
hasError = true
} else {
setSecondTitleError('')
}
if (selectedIndustries.length === 0) {
setIndustriesError('Please select at least one industry')
hasError = true
} else {
setIndustriesError('')
}
if (hasError) {
message.error('Please fix the highlighted fields')
return
}
const newApp = {
id: editingId || generateUUID(),
title: title.trim(),
secondTitle: secondTitle.trim(),
industries: [...selectedIndustries],
isActive: true,
createdAt: new Date().toLocaleDateString('en-US')
}
if (editingId) {
setTrendingApps(prev => prev.map(app => app.id === editingId ? newApp : app))
setEditingId(null)
message.success('Trending app updated!')
} else {
setTrendingApps(prev => [...prev, newApp])
message.success('Trending app added!')
}
closeModal()
}
const handleClear = () => {
setTitle('')
setSecondTitle('')
setSelectedIndustries([])
setEditingId(null)
setTitleError('')
setSecondTitleError('')
setIndustriesError('')
}
const handleEdit = (app) => {
setTitle(app.title)
setSecondTitle(app.secondTitle)
setSelectedIndustries(app.industries)
setEditingId(app.id)
openModal()
}
useEffect(() => {
if (editingId && titleInputRef.current) {
titleInputRef.current.focus()
}
}, [editingId])
const handleDelete = (id) => {
setTrendingApps(prev => prev.filter(app => app.id !== id))
message.success('Trending app deleted')
}
const handleToggleStatus = (id) => {
setTrendingApps(prev => prev.map(app =>
app.id === id ? { ...app, isActive: !app.isActive } : app
))
}
return (
<div className='trending-apps-form-master'>
{/* Header Section */}
<div className='trending-apps-form-header'>
<div className='trending-apps-header-left'>
{/* <div className='trending-apps-header-icon'>
<span>📱</span>
</div> */}
<div className='trending-apps-header-content'>
<h2>Trending Apps Management</h2>
<p>Manage and organize your trending apps content and settings.</p>
</div>
</div>
<button onClick={openModal} className='trending-apps-create-btn'>
<span>+</span> Create Trending App
</button>
</div>
{/* Tab Navigation */}
<div className='trending-apps-tab-navigation'>
<div className='trending-apps-tab-buttons'>
<button className='trending-apps-tab-btn trending-apps-active'>
Trending Apps ({trendingApps.length})
</button>
<button className='trending-apps-tab-btn'>
All Status
</button>
</div>
<div className='trending-apps-tab-controls'>
<div className='trending-apps-sort-dropdown'>
<select>
<option value="newest">Newest First</option>
<option value="oldest">Oldest First</option>
<option value="alphabetical">Alphabetical</option>
</select>
</div>
<div className='trending-apps-view-toggle'>
<button className='trending-apps-view-btn trending-apps-active'>
<span></span>
</button>
<button className='trending-apps-view-btn'>
<span></span>
</button>
</div>
</div>
</div>
{/* Content Area */}
{trendingApps.length > 0 ? (
<div className='trending-apps-display'>
<div className='trending-apps-grid'>
{trendingApps.map((app) => (
<div key={app.id} className={`trending-apps-card ${!app.isActive ? 'trending-apps-inactive' : ''}`}>
<div className='trending-apps-card-header'>
<h3>{app.title}</h3>
<div className='trending-apps-card-actions'>
<button className='trending-apps-edit-btn' onClick={() => handleEdit(app)}>Edit</button>
<Popconfirm
title="Delete Trending App"
description="Are you sure you want to delete this trending app?"
onConfirm={() => handleDelete(app.id)}
okText="Yes"
cancelText="No"
>
<button className='trending-apps-delete-btn'>Delete</button>
</Popconfirm>
<button
className={`trending-apps-status-btn ${app.isActive ? 'trending-apps-deactivate' : 'trending-apps-activate'}`}
onClick={() => handleToggleStatus(app.id)}
>
{app.isActive ? 'Deactivate' : 'Activate'}
</button>
</div>
</div>
<div className='trending-apps-card-content'>
<p><strong>Second Title:</strong> {app.secondTitle}</p>
<div className='trending-apps-industries-display'>
<strong>Industries:</strong>
<div className='trending-apps-industry-tags'>
{app.industries.map((industry) => (
<span key={industry} className='trending-apps-industry-tag'>{industry}</span>
))}
</div>
</div>
<p className='trending-apps-created-date'>Created: {app.createdAt}</p>
<p className={`trending-apps-status ${app.isActive ? 'trending-apps-active' : 'trending-apps-inactive'}`}>
Status: {app.isActive ? 'Active' : 'Inactive'}
</p>
</div>
</div>
))}
</div>
</div>
) : (
<div className='trending-apps-placeholder-content'>
<div className='trending-apps-placeholder-icon'>
<span>📱</span>
</div>
<h2>No Trending Apps Found</h2>
<p>Get started by creating your first trending app</p>
</div>
)}
{/* Modal */}
<DefaultModal
open={isModalOpen}
title={editingId ? 'Edit Trending App' : 'Create New Trending App'}
handleCancel={closeModal}
handleSubmit={handleSave}
buttonText={editingId ? 'Update App' : 'Add App'}
width={600}
destroyOnClose={true}
>
<div className='trending-apps-form'>
<div className='trending-apps-form-group'>
<label htmlFor='title'>Title</label>
<input
type='text'
id='title'
ref={titleInputRef}
value={title}
onChange={(e) => {
setTitle(e.target.value)
if (titleError) setTitleError('')
}}
placeholder='Enter title'
className={titleError ? 'error' : ''}
autoFocus
/>
{titleError && <span className='error-message'>{titleError}</span>}
</div>
<div className='trending-apps-form-group'>
<label htmlFor='secondTitle'>Second Title</label>
<input
type='text'
id='secondTitle'
value={secondTitle}
onChange={(e) => {
setSecondTitle(e.target.value)
if (secondTitleError) setSecondTitleError('')
}}
placeholder='Enter second title'
className={secondTitleError ? 'error' : ''}
/>
{secondTitleError && <span className='error-message'>{secondTitleError}</span>}
</div>
<div className='trending-apps-form-group'>
<label>Select Industries</label>
<div className={`trending-apps-industries-grid ${industriesError ? 'error' : ''}`}>
{industries.map((industry) => (
<label key={industry} className='trending-apps-checkbox-item'>
<input
type='checkbox'
checked={selectedIndustries.includes(industry)}
onChange={() => {
handleIndustryChange(industry)
if (industriesError) setIndustriesError('')
}}
/>
<span>{industry}</span>
</label>
))}
</div>
{industriesError && <span className='error-message'>{industriesError}</span>}
</div>
<div className='trending-apps-form-actions'>
<button type='button' onClick={handleSave} className='trending-apps-save-btn'>
{editingId ? 'Update' : 'Save'}
</button>
<button type='button' onClick={closeModal} className='trending-apps-cancel-btn'>
Cancel
</button>
<button type='button' onClick={handleClear} className='trending-apps-clear-btn'>
Clear
</button>
</div>
</div>
</DefaultModal>
</div>
)
}
export default TrendingAppsForm

View File

@ -0,0 +1,13 @@
import React from 'react'
import { AdminPanelProvider } from './AdminPanelContext'
import AdminPanel from './AdminPanel'
const AdminPanelWrapper = () => {
return (
<AdminPanelProvider>
<AdminPanel />
</AdminPanelProvider>
)
}
export default AdminPanelWrapper

View File

@ -0,0 +1,433 @@
import React, { useEffect, useRef, useState } from 'react';
import './Blog.scss';
import Navbar from '../../PozoApp/Components/Navbar';
import IndustriesList from '../../PozoApp/Components/IndustriesList';
import CompanyList from '../../PozoApp/Components/companyList';
import { lazy, Suspense } from 'react';
const SolutionsList = lazy(() => import('../../PozoApp/Components/SolutionsList'));
import Lenis from "@studio-freight/lenis";
import Loader from '../../PozoApp/Components/Loader/PozoLoader';
import { RiInstagramFill, RiTwitterXFill } from 'react-icons/ri';
import { FaFacebook, FaYoutube } from 'react-icons/fa';
import { message } from 'antd';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { getBlog, getPublishedBlogs } from '../../features/Blog/Blog';
import { getAllApplicationData } from '../../features/publicHome/publicHome';
import { getSeo } from '../../features/SEO/seo';
import { getConfigNames } from '../../features/configmasterPage/configmasterPage';
import SEO from '../../Components/SEO/SEO';
import { PiInstagramLogoFill } from 'react-icons/pi';
import BookDemo from '../../PozoApp/Components/BookDemo';
const subDirectory = import.meta.env.ENV_BASE_URL;
const Blogs = () => {
const [isScrolled, setIsScrolled] = useState(false);
const [industries, setAppCategory] = useState([]);
const [closingIndustry, setClosingIndustry] = useState(false);
const [industry, setIndustry] = useState(false);
const [company, setCompany] = useState(false);
const companyRef = useRef(null);
const industryRef = useRef(null);
const [closingCompany, setClosingCompany] = useState(false);
const [solutionsOpen, setSolutionsOpen] = useState(false);
const [closingSolutions, setClosingSolutions] = useState(false);
const solutionsRef = useRef(null);
const [loading, setLoading] = useState(true);
const [blogPosts, setBlogPosts] = useState([]);
const [seoData, setSeoData] = useState({});
const dispatch = useDispatch();
const navigate = useNavigate();
const [demoModal, setDemoModal] = useState(false);
const handleDemoModal = () => setDemoModal(true);
const handleDemoModalClose = () => setDemoModal(false);
const getAppData = async () => {
try {
const response = await dispatch(getAllApplicationData()).unwrap();
if (response?.data?.statusCode === 1) {
let finalsubCategory = response?.data?.data?.filter(
(value) => value.ActiveStatus === "A",
);
setAppCategory(finalsubCategory);
}
} catch (error) {
console.error('Error fetching app data:', error);
}
};
useEffect(() => {
const timer = setTimeout(() => {
setLoading(false);
}, 300);
getAppData();
return () => clearTimeout(timer);
}, []);
useEffect(() => {
fetchBlogPosts();
}, [])
useEffect(() => {
const fetchSEOData = async () => {
try {
const res = await dispatch(getConfigNames({ TypeName: "SEO" })).unwrap();
if (res?.data?.statusCode === 1) {
const seoId = res?.data?.data?.find(e => e.ConfigName === "blog")?.ConfigId;
if (!seoId) {
console.warn("Blog ConfigId not found");
return;
}
const response = await dispatch(getSeo({ PageId: seoId })).unwrap();
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
const data = response.data.data[0];
setSeoData({
metaTitle: data?.MetaTitle,
metaDescription: data?.MetaDesc,
keywords: data?.Keywords,
imageAltText: data?.ImgAltText
});
}
}
} catch (error) {
console.log('No SEO data found for blog page');
}
};
fetchSEOData();
}, [dispatch]);
const fetchBlogPosts = async () => {
try {
const response = await dispatch(getPublishedBlogs()).unwrap();
if (response?.data.statusCode == 1) {
setBlogPosts(response?.data?.data?.map((blog) => ({
id: blog?.BlogId,
title: blog?.BlogTitle,
excerpt: blog?.BlogSubtitle,
author: blog?.BlogAuthor,
date: (() => {
if (!blog?.PublishDate) return '';
// --- Format date ---
const dateStr = blog.PublishDate.split('T')[0]; // "2025-10-24"
const [year, month, day] = dateStr.split('-');
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const monthName = months[parseInt(month, 10) - 1];
const formattedDate = `${parseInt(day, 10)} ${monthName} ${year}`; // "24 Oct 2025"
// --- Format time ---
if (!blog?.PublishTime) return formattedDate; // If no time, return date only
const [hourStr, minuteStr] = blog.PublishTime.split(':'); // "17:25:00" ["17","25","00"]
let hour = parseInt(hourStr, 10);
const minute = minuteStr;
const ampm = hour >= 12 ? "PM" : "AM";
hour = hour % 12 || 12; // Convert 0 12
const formattedTime = `${hour.toString().padStart(2, "0")}:${minute} ${ampm}`; // "05:25 PM"
return `${formattedDate} ${formattedTime}`; // "24 Oct 2025 05:25 PM"
})(),
readTime: '5 min read',
CreatedDate: (() => {
if (!blog?.CreatedDate) return '';
const dateStr = blog.CreatedDate.split('T')[0]; // Get date part
const [year, month, day] = dateStr.split('-');
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const monthName = months[parseInt(month) - 1];
return `${parseInt(day)} ${monthName} ${year}`;
})(),
imageUrl: blog?.HeaderImage,
slug: blog?.Slug
})) || []);
}
else {
setBlogPosts([]);
}
}
catch (err) {
message.error('Failed to fetch blog posts. Please try again later.');
}
}
// const blogPosts = [
// {
// id: 1,
// title: "Modern Web Development Trends in 2024",
// excerpt: "Explore the latest technologies and frameworks shaping the future of web development with React, Next.js and modern CSS.",
// author: "John Doe",
// date: "Dec 15, 2024",
// readTime: "5 min read",
// imageUrl: "https://images.unsplash.com/photo-1555066931-4365d14bab8c?w=400",
// },
// {
// id: 2,
// title: "Mastering React Hooks in 2024",
// excerpt: "Learn how to effectively use React Hooks to build modern, efficient React applications with best practices.",
// author: "Sarah Wilson",
// date: "Dec 12, 2024",
// readTime: "8 min read",
// imageUrl: "https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=400",
// },
// {
// id: 3,
// title: "CSS Grid vs Flexbox: When to Use What",
// excerpt: "A comprehensive guide to understanding the differences between CSS Grid and Flexbox with practical examples.",
// author: "Mike Chen",
// date: "Dec 10, 2024",
// readTime: "6 min read",
// imageUrl: "https://images.unsplash.com/photo-1633356122544-f134324a6cee?w=400",
// }
// ];
const redirectApp = (AppName, AppId) => {
console.log('Redirect to app:', AppName, AppId);
};
const handleIndustry = () => {
if (industry) {
setClosingIndustry(true);
setTimeout(() => {
setIndustry(false);
setClosingIndustry(false);
}, 200);
} else {
setIndustry(true);
}
};
const handleCompanyList = () => {
if (company) {
setClosingCompany(true);
setTimeout(() => {
setCompany(false);
setClosingCompany(false);
}, 200);
} else {
setCompany(true);
}
};
useEffect(() => {
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smoothWheel: true,
smoothTouch: true,
});
lenis.on("scroll", (e) => {
setIsScrolled(e.scroll > 50);
});
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
return () => lenis.destroy();
}, []);
useEffect(() => {
if (industry || company || solutionsOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "unset";
}
return () => {
document.body.style.overflow = "unset";
};
}, [industry, company, solutionsOpen]);
const BlogCard = ({ post }) => {
const handleClick = () => {
navigate(`${subDirectory}blog/${post.slug}`);
};
return (
<div className="pozo-blog-card" onClick={handleClick}>
<div className="pozo-blog-card-image">
<img src={post.imageUrl} alt={post.title} />
<div className="pozo-blog-card-overlay"></div>
</div>
<div className="pozo-blog-card-content">
<div className="pozo-blog-tags-container">Recent Updates & Blogs
Announcements
</div>
<p className="pozo-blog-card-excerpt">{post.excerpt}</p>
<div className="pozo-blog-card-meta">
{/* <span className="pozo-blog-date">{post.date}</span> */}
<div>
<span className="pozo-blog-date">{`Created Date ${post.CreatedDate}`}</span>
<div className="pozo-blog-date">{post.Author || "Admin"}</div>
</div>
</div>
<div className="pozo-blog-publish-time">
<span className="pozo-blog-read-time">{post.readTime}</span>
</div>
</div>
</div>
);
};
if (loading) {
return <Loader />;
}
const handlePrivacyPolicy = () => {
navigate(`${subDirectory}privacy-policy`);
}
const handleNavigateCookie = () => {
navigate(`${subDirectory}cookie-policy`);
}
const handleNavigateFaq = () => {
navigate(`${subDirectory}faq`);
}
return (
<>
<SEO
title={seoData.metaTitle || "Recent Updates & Blogs - PozoApp"}
description={seoData.metaDescription || "Stay updated with the latest news, announcements, and blog posts from PozoApp. Discover insights on business management, technology trends, and more."}
keywords={seoData.keywords || "blog, updates, announcements, PozoApp, business management, technology"}
url={`${subDirectory}blog`}
image={seoData.imageAltText || "/og/blog-og.jpg"}
type="website"
/>
<div className='pozo-blog-master-card'>
<Navbar
isScrolled={isScrolled}
forceTopZero={true}
AppCategory={industries}
closingIndustry={closingIndustry}
setClosingIndustry={setClosingIndustry}
industry={industry}
setIndustry={setIndustry}
companyRef={companyRef}
closingCompany={closingCompany}
setClosingCompany={setClosingCompany}
company={company}
setCompany={setCompany}
industryRef={industryRef}
handleIndustry={handleIndustry}
handleCompanyList={handleCompanyList}
solutionsOpen={solutionsOpen}
setSolutionsOpen={setSolutionsOpen}
closingSolutions={closingSolutions}
setClosingSolutions={setClosingSolutions}
solutionsRef={solutionsRef}
handleDemoModal={handleDemoModal}
handleDemoModalClose={handleDemoModalClose}
demoModal={demoModal}
/>
<div className="pozo-blogs-page">
<h1 className="pozo-blog-page-title">Recent Updates & Blogs</h1>
<p className="pozo-blog-page-subtitle">Stay updated with the latest news, announcements, and blog posts from PozoApp. Discover insights on business management, technology trends, and more.</p>
<div className="pozo-blogs-grid">
{blogPosts.length > 0 ? (
blogPosts.map((post) => (
<BlogCard key={post.id} post={post} />
))
) : (
<div className="pozo-blog-empty-state">
<p>No blog posts available at the moment. Please check back later for updates.</p>
</div>
)}
</div>
</div>
{/* Navbar IndustriesList */}
{industry && (
<IndustriesList
closingIndustry={closingIndustry}
AppCategory={industries}
redirectApp={redirectApp}
/>
)}
{company && (
<CompanyList
companyRef={companyRef}
closingCompany={closingCompany}
/>
)}
{solutionsOpen && (
<Suspense fallback={null}>
<SolutionsList
solutionsRef={solutionsRef}
closingSolutions={closingSolutions}
/>
</Suspense>
)}
<div className="liveFooterMain" style={{ marginTop: "5rem" }}>
<div className="liveFooterLeft">
<p className="listTitle">Quick Links</p>
<div className="LiveFooterList">
<p>About Pozo</p>
<p>All Products </p>
<p>Pricing Plans </p>
<p>Contact Us </p>
<p onClick={handleNavigateFaq}>FAQs </p>
</div>
</div>
<div className="liveFooterRight">
<p className="listTitle">Support & Help</p>
<div className="LiveFooterList">
<p>info@pozo.app</p>
<a href="tel:7324000011">73 24 00 00 11</a>
<a href="tel:7324000012">73 24 00 00 12</a>
&nbsp;&nbsp;&nbsp;&nbsp;
<span>
Let's get social
<a href="https://www.instagram.com/pozomind/" target="_blank" rel="noopener noreferrer">
<PiInstagramLogoFill size={18} />
</a>
<a href="https://www.facebook.com/pozomind/" target="_blank" rel="noopener noreferrer">
<FaFacebook />
</a>
<a href="https://x.com/pozomind" target="_blank" rel="noopener noreferrer">
<RiTwitterXFill />
</a>
<a href="https://www.youtube.com/@pozomind" target="_blank" rel="noopener noreferrer">
<FaYoutube />
</a>
</span>
</div>
</div>
</div>
<div className="liveFooterRights">
<div>
© 2025 PozoApp
<div className='linktoPrivayCookie' onClick={handlePrivacyPolicy}>
Privacy Policy
</div>
<div className='linktoPrivayCookie' onClick={handleNavigateCookie}>
Cookie Policy
</div>
</div>
<p>
For queries contact us: Manager, No 51 Step Colony, Dharga,Hosur,
Krishnagiri, Tamilnadu-635126, India
</p>
</div>
{demoModal && (
<BookDemo onClose={handleDemoModalClose} />
)}
</div>
</>
);
};
export default Blogs;

View File

@ -0,0 +1,261 @@
.pozo-blogs-page {
padding: 20px 0;
.pozo-blog-page-title {
margin-top: 5rem;
text-align: left;
font-size: 1.8rem;
font-weight: 600;
color: #1a1a1a;
margin-bottom: 10px;
max-width: 100%;
padding: 0 108px;
}
.pozo-blog-page-subtitle {
text-align: left;
font-size: 1rem;
font-weight: 400;
color: #666;
margin-bottom: 20px;
max-width: 100%;
padding: 0 108px;
line-height: 1.5;
}
}
.pozo-blogs-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 24px;
padding: 24px;
max-width: 1200px;
margin: 0 auto;
}
.pozo-blog-card {
background: #ffffff;
border-radius: 16px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
overflow: hidden;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
cursor: pointer;
width: 100%;
&:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
.pozo-blog-card-image img {
transform: scale(1.02);
}
}
.pozo-blog-card-image {
position: relative;
height: 220px;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.pozo-blog-card-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(74, 144, 226, 0.1) 0%, rgba(148, 86, 210, 0.1) 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
&:hover .pozo-blog-card-overlay {
opacity: 1;
}
}
.pozo-blog-card-content {
padding: 24px;
.pozo-blog-tags-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 6px;
font-size: 12px;
font-family: "Poppins";
font-weight: 400;
color: #525252;
}
.pozo-blog-card-excerpt {
color: #000000;
line-height: 1.6;
margin-bottom: 20px;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
height: 80px;
}
.pozo-blog-card-meta {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 16px;
border-top: 1px solid #f0f0f0;
.pozo-blog-date {
font-size: 12px;
color: #505050;
font-family: "Poppins";
font-weight: 400;
}
.pozo-blog-author-info {
display: flex;
flex-direction: column;
gap: 4px;
.pozo-blog-author {
font-weight: 600;
color: #1a1a1a;
font-size: 0.9rem;
}
.pozo-blog-date {
color: #888;
font-size: 0.2rem !important;
}
}
.pozo-blog-read-time {
background: #f8f9fa;
color: #666;
padding: 6px 12px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 500;
}
}
.pozo-blog-publish-time {
display: flex;
justify-content: flex-end;
padding-top: 8px;
border-top: 1px solid #f0f0f0;
margin-top: 8px;
.pozo-blog-read-time {
background: #4a90e2;
color: #ffffff;
padding: 6px 12px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 500;
display: none;
}
}
}
}
// Responsive design
@media (max-width: 768px) {
.pozo-blogs-page {
padding: 10px 0;
.pozo-blog-page-title {
font-size: 2rem;
margin-bottom: 10px;
padding: 0 18px !important;
}
.pozo-blog-page-subtitle {
font-size: 0.9rem;
margin-bottom: 20px;
padding: 0 18px !important;
}
}
.pozo-blogs-grid {
grid-template-columns: 1fr;
gap: 16px;
padding: 16px;
}
.pozo-blog-card .pozo-blog-card-content {
padding: 16px;
}
}
.pozo-blog-master-card {
width: 100%;
.Navbar-Master,
.appNameLeft {
color: #000 !important;
}
.ResNavHeaderClose div {
color: #000;
}
}
.pozo-blog-empty-state {
grid-column: 1 / -1;
display: flex;
align-items: center;
justify-content: center;
height: 300px;
text-align: center;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border-radius: 16px;
border: 2px dashed #e0e0e0;
margin: 20px 0;
p {
color: #666;
font-size: 1.1rem;
font-weight: 500;
margin: 0;
line-height: 1.6;
}
}
.pozo-no-blogs-message {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
text-align: center;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border-radius: 16px;
border: 2px dashed #e0e0e0;
margin: 20px 0;
.pozo-no-blogs-icon {
font-size: 4rem;
opacity: 0.7;
}
h3 {
color: #1a1a1a;
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 12px;
}
p {
color: #c40000;
font-size: 1rem;
line-height: 1.6;
margin: 0;
}
}

View File

@ -0,0 +1,525 @@
import React, { useState, useEffect, useRef } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { message } from 'antd';
import Lenis from "@studio-freight/lenis";
import {
RiArrowLeftLine, RiCalendarLine, RiTimeLine, RiUserLine, RiHomeLine, RiArticleLine,
RiShareLine,
RiCloseLine,
} from 'react-icons/ri';
import { RxHamburgerMenu } from 'react-icons/rx';
import { FaFacebook, FaInstagram, FaWhatsapp, FaCopy, FaLinkedin, FaTelegram } from "react-icons/fa";
import { FaXTwitter } from "react-icons/fa6";
import { getPublishedBlogs, getPublishedBlogsBySlug } from '../../features/Blog/Blog.js';
import Loader from '../../PozoApp/Components/Loader/PozoLoader';
import PozoAppLogo from '../../Images/PozoAppLogo.svg';
import "./BlogDetail.scss"
import BlogExtras from "./BlogExtras.jsx";
import SEO from '../../Components/SEO/SEO.jsx';
const mainDirectory = import.meta.env.ENV_MAIN_BASE_URL
const subDirectory = import.meta.env.ENV_BASE_URL
const BlogDetail = () => {
const { slug } = useParams();
const navigate = useNavigate();
const dispatch = useDispatch();
const [blog, setBlog] = useState(null);
const [loading, setLoading] = useState(true);
const [drawerOpen, setDrawerOpen] = useState(false);
const [shareOpen, setShareOpen] = useState(false);
const shareRef = useRef(null);
console.log(blog, "blogblogblogblogblog")
useEffect(() => {
const handleClickOutside = (event) => {
if (shareRef.current && !shareRef.current.contains(event.target)) {
setShareOpen(false);
}
};
const handleEscKey = (event) => {
if (event.key === 'Escape') {
setShareOpen(false);
}
};
if (shareOpen) {
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleEscKey);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleEscKey);
};
}, [shareOpen]);
useEffect(() => {
fetchBlogDetail();
}, [slug]);
useEffect(() => {
const lenis = new Lenis({
duration: 1,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smoothWheel: true,
smoothTouch: true,
});
function raf(time) {
lenis.raf(time);
requestAnimationFrame(raf);
}
requestAnimationFrame(raf);
return () => lenis.destroy();
}, []);
const fetchBlogDetail = async () => {
try {
const response = await dispatch(getPublishedBlogsBySlug({ slug })).unwrap();
if (response?.data?.statusCode === 1) {
const blogs = response.data.data?.[0];
setBlog(blogs);
} else {
message.error('Failed to load blog posts');
}
setLoading(false);
} catch (error) {
console.error('Error fetching blog:', error);
message.error('Failed to load blog post');
setLoading(false);
}
};
const handleBack = () => {
navigate('/blog');
};
if (loading) {
return <Loader />;
}
if (!blog) {
return (
<div className='BlogDetailMaster'>
<div className='blog-detail-error'>Blog post not found</div>
</div>
);
}
const handleBackHome = () => {
navigate(`${subDirectory}`);
}
const handleBackBlog = () => {
navigate(`${subDirectory}blog`);
}
const handlePrivacyPolicy = () => {
navigate(`${subDirectory}privacy-policy`);
}
const handleNavigateCookie = () => {
navigate(`${subDirectory}cookie-policy`);
}
return (
<>
<SEO
title={blog.BlogTitle}
description={blog.BlogSubtitle || "Read the latest blog on PozoApp"}
image={blog.HeaderImage}
url={`${mainDirectory}/home/blog/${slug}`}
type="article"
publishedTime={blog.CreatedDate}
modifiedTime={blog.UpdatedDate || blog.CreatedDate}
keywords={blog.Keywords?.join(', ') || "PozoApp, Blog, Technology, Articles"}
/>
{drawerOpen && <div className='drawer-overlay' onClick={() => setDrawerOpen(false)}></div>}
<div className={`drawer ${drawerOpen ? 'open' : ''}`}>
<div className='drawer-content'>
{/* <Link to='/' onClick={() => setDrawerOpen(false)}> */}
<div onClick={handleBackHome}>
<RiHomeLine /> Home
</div>
{/* </Link> */}
{/* <Link to='/blog' onClick={() => setDrawerOpen(false)}> */}
<div onClick={handleBackBlog}>
<RiArticleLine /> Blog
</div>
{/* </Link> */}
</div>
</div>
<div className='BlogDetailMaster'>
<header className='blog-detail-header'>
<nav className='navbar'>
<div className='navbar-left'>
<div className='hamburger-menu' onClick={() => setDrawerOpen(!drawerOpen)}>
<RxHamburgerMenu size={20} />
</div>
<div className='Blognavbar-logo' onClick={handleBackHome}>
{/* <Link to='/'> */}
<img src={PozoAppLogo} alt='Logo' className='logo-img' />
{/* </Link> */}
<div className='blogdetailsNavbarText'>
| Blog
</div>
</div>
</div>
</nav>
</header>
<div className="blog-detail-contentMain">
<article className='blog-detail-content'>
<header className='blog-header'>
<h1 className='blog-title'>{blog.BlogTitle}</h1>
<p className='blog-subtitle'>{blog.BlogSubtitle}</p>
<div className='blog-meta'>
<div className='meta-item'>
<RiUserLine />{blog.Author || "Admin"}
</div>
{blog.CreatedDate && (
<div className='meta-item'>
<RiCalendarLine /> {(() => {
if (!blog?.CreatedDate) return '';
const dateStr = blog.CreatedDate.split('T')[0]; // Get date part
const [year, month, day] = dateStr.split('-');
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const monthName = months[parseInt(month) - 1];
return `${parseInt(day)} ${monthName} ${year}`;
})()}
</div>
)}
{/* <div className={`status ${blog.IsPublished === 'Y' ? 'published' : 'draft'}`}>
{blog.IsPublished === 'Y' ? 'Published' : 'Draft'}
</div> */}
</div>
</header>
{blog.HeaderImage && (
<div >
<img className='blog-poster' src={blog.HeaderImage} alt={blog.BlogTitle} />
</div>
)}
<div className='blog-body'>
{blog.BlogContent && blog.BlogContent.map((block, index) => {
switch (block.type) {
case 'header':
case "oldHeader":
const HeaderTag = `h${block.data.level || 2}`;
return (
<HeaderTag
key={index}
className={`blog-header-block level-${block.data.level}`}
dangerouslySetInnerHTML={{ __html: block.data.text }}
/>
);
case 'paragraph':
return (
<p
key={index}
className="blog-paragraph"
dangerouslySetInnerHTML={{ __html: block.data.text }}
/>
);
case 'list':
if (block.data.style === 'checklist') {
return (
<ul key={index} className='blog-list checklist'>
{block.data.items.map((item, i) => (
<li key={i} className='checklist-item'>
<input type="checkbox" checked={item.meta?.checked} readOnly />
<span>{item.content}</span>
</li>
))}
</ul>
);
} else if (block.data.style === 'ordered') {
return (
<ol key={index} className='blog-list ordered'>
{block.data.items.map((item, i) => (
<li key={i}>{item.content}</li>
))}
</ol>
);
} else {
return (
<ul key={index} className='blog-list unordered'>
{block.data.items.map((item, i) => (
<li key={i}>{item.content}</li>
))}
</ul>
);
}
case "image":
return (
<div key={index} className="blog-image">
<img
src={block.data.file?.url}
alt={block.data.caption || "Blog image"}
/>
{block.data.caption && (
<p className="caption">{block.data.caption}</p>
)}
</div>
);
case "separator":
return (
<hr
key={index}
className={`blog-separator ${block.data.style || "line"
}`}
/>
);
case "video":
return (
<div key={index} className="blog-video">
{block.data.file?.url && (
<video
src={block.data.file.url}
controls
className="video-player"
/>
)}
{block.data.caption && (
<p className="caption">{block.data.caption}</p>
)}
</div>
);
case "link":
return (
<div key={index} className="blog-link">
<a href={block.data.url} target={block.data.target || "_self"}>
{block.data.text}
</a>
</div>
);
case "quote":
return (
<blockquote key={index} className="blog-quote">
<p>{block.data.text}</p>
{block.data.caption && (
<cite>{block.data.caption}</cite>
)}
</blockquote>
);
case "code":
return (
<pre key={index} className="blog-code">
<code>{block.data.code}</code>
</pre>
);
case "cta":
return (
<div key={index} className="blog-cta">
<h3>{block.data.heading}</h3>
<p>{block.data.subheading}</p>
<a href={block.data.buttonUrl} className={`cta-button ${block.data.style}`}>
{block.data.buttonText}
</a>
</div>
);
case "email":
return (
<div key={index} className="blog-email">
<h4>Subject: {block.data.subject}</h4>
<div dangerouslySetInnerHTML={{ __html: block.data.bodyHtml }} />
</div>
);
case "bookmark":
return (
<div key={index} className="blog-bookmark">
<a href={block.data.url} target="_blank" rel="noopener noreferrer" style={{ display: 'flex', alignItems: 'center', gap: '16px', textDecoration: 'none', border: '1px solid #e5e7eb', borderRadius: '8px', padding: '16px' }}>
<div className="bookmark-content" style={{ flex: 1 }}>
<h4 style={{ margin: '0 0 8px 0', color: '#1f2937' }}>{block.data.title}</h4>
<p style={{ margin: '0 0 8px 0', color: '#6b7280', fontSize: '14px' }}>{block.data.description}</p>
<span className="bookmark-url" style={{ color: '#3b82f6', fontSize: '12px' }}>{block.data.url}</span>
</div>
{block.data.image && (
<img src={block.data.image} alt={block.data.title} style={{ width: '120px', height: '90px', objectFit: 'cover', borderRadius: '6px', flexShrink: 0 }} />
)}
</a>
</div>
);
case "callout":
return (
<div key={index} className="blog-callout">
<span className="callout-emoji">{block.data.emoji || "💡"}</span>
<p>{block.data.text}</p>
</div>
);
case "index":
const IndexListTag = block.data.style === "numbered" ? "ol" : "ul";
return (
<div key={index} className="blog-index">
<h4>Table of Contents</h4>
<IndexListTag>
{(block.data.items || []).map((item, i) => (
<li key={i}>
<a href={`#${item.anchor}`}>{item.title}</a>
</li>
))}
</IndexListTag>
</div>
);
default:
return null;
}
})}
<div className="shareBlog">
<button
className="share-btn"
onClick={() => setShareOpen(!shareOpen)}
>
<RiShareLine /> Share
</button>
{shareOpen && (
<div className="share-options" ref={shareRef}>
<div className="share-header">
<span>Share to</span>
<button onClick={() => setShareOpen(false)}>
<RiCloseLine size={20} />
</button>
</div>
<div className="share-buttons">
<button
onClick={() => {
const url = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(
window.location.href
)}`;
window.open(url, "_blank");
}}
className="share-facebook"
>
<FaFacebook />
</button>
<button
onClick={() => {
const url = `https://wa.me/?text=${encodeURIComponent(
blog.BlogTitle + " " + window.location.href
)}`;
window.open(url, "_blank");
}}
className="share-whatsapp"
>
<FaWhatsapp />
</button>
<button
onClick={() => {
const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(
blog.BlogTitle
)}&url=${encodeURIComponent(window.location.href)}`;
window.open(url, "_blank");
}}
className="share-twitter"
>
<FaXTwitter />
</button>
<button
onClick={() => {
const url = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(
window.location.href
)}`;
window.open(url, "_blank");
}}
className="share-linkedin"
>
<FaLinkedin />
</button>
<button
onClick={() => {
const url = `https://t.me/share/url?url=${encodeURIComponent(
window.location.href
)}&text=${encodeURIComponent(blog.BlogTitle)}`;
window.open(url, "_blank");
}}
className="share-telegram"
>
<FaTelegram />
</button>
<button
onClick={() => {
window.open("https://www.instagram.com/", "_blank");
navigator.clipboard.writeText(window.location.href);
message.success(
"Instagram opened! Link copied to paste in your story or bio."
);
}}
className="share-instagram"
>
<FaInstagram />
</button>
<button
onClick={async () => {
try {
await navigator.clipboard.writeText(
window.location.href
);
message.success("Link copied to clipboard!");
} catch (err) {
// Fallback for older browsers
const textArea = document.createElement("textarea");
textArea.value = window.location.href;
document.body.appendChild(textArea);
textArea.select();
document.execCommand("copy");
document.body.removeChild(textArea);
message.success("Link copied to clipboard!");
}
}}
className="share-copy"
>
<FaCopy /> Copy Link
</button>
</div>
</div>
)}
</div>
</div>
</article>
<BlogExtras allowComments={blog?.AllowComments} blogId={blog?.BlogId} />
</div>
</div>
<div className="liveFooterRights">
<div>
© 2025 PozoApp
<div className='linktoPrivayCookie' onClick={handlePrivacyPolicy}>
Privacy Policy
</div>
<div className='linktoPrivayCookie' onClick={handleNavigateCookie}>
Cookie Policy
</div>
</div>
<p>
For queries contact us: Manager, No 51 Step Colony, Dharga,Hosur,
Krishnagiri, Tamilnadu-635126, India
</p>
</div>
</>
);
};
export default BlogDetail;

View File

@ -0,0 +1,583 @@
.drawer-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
.drawer {
position: fixed;
top: 0;
left: -300px;
width: 250px;
height: 100%;
background: white;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transition: left 0.3s ease;
z-index: 1000;
&.open {
left: 0;
}
.drawer-content {
display: flex;
flex-direction: column;
padding: 10px;
margin-top: 5rem;
div {
display: flex;
align-items: center;
gap: 12px;
text-decoration: none;
color: #374151;
padding: 15px 20px;
border-radius: 8px;
transition: background-color 0.2s;
font-size: 16px;
font-weight: 500;
cursor: pointer;
&:hover {
background-color: #f3f4f6;
}
svg {
font-size: 20px;
}
}
}
}
.BlogDetailMaster {
width: 100%;
min-height: 100vh;
// background: linear-gradient(180deg, #fafafa 0%, #f5f7fa 100%);
display: flex;
justify-content: center;
align-items: flex-start;
padding: 40px 16px;
font-family: "Poppins", sans-serif;
flex-direction: column;
.blog-detail-header {
width: 100%;
max-width: 950px;
margin: 0 auto 25px auto;
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
background: white;
padding: 6px 16px;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
.navbar-left {
display: flex;
align-items: center;
gap: -10px;
.hamburger-menu {
cursor: pointer;
padding: 8px;
border-radius: 4px;
transition: background-color 0.2s;
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
}
.Blognavbar-logo {
display: flex;
align-items: center;
cursor: pointer;
.logo-img {
height: auto;
width: 6rem;
filter: invert(1) brightness(0);
/* Makes the logo black */
}
}
}
.navbar-nav {
display: none;
}
}
}
.blog-detail-content {
border-radius: 12px;
padding: 0 26px;
margin: 2rem 0;
animation: fadeIn 0.6s ease-out;
width: 65%;
.blog-header {
text-align: left;
margin-bottom: 40px;
.blog-title {
font-weight: 600;
line-height: 1.5;
color: #000000;
font-size: 32px;
font-family: "Poppins";
margin-bottom: 8px;
margin: 0;
}
.blog-subtitle {
font-size: 14px;
color: #333;
margin: 10px 0;
}
.blog-meta {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 24px;
flex-wrap: wrap;
font-size: 14px;
color: #6b7280;
margin-top: 1rem;
.meta-item {
display: flex;
align-items: center;
gap: 6px;
svg {
color: #9ca3af;
}
}
.status {
padding: 4px 10px;
border-radius: 20px;
font-weight: 600;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.6px;
&.published {
background: #daffe7;
color: #065f46;
}
&.draft {
background: #fef3c7;
color: #92400e;
}
}
}
}
.blog-featured-image {
text-align: center;
margin-bottom: 50px;
display: flex;
align-items: center;
justify-content: center;
img {
width: 90%;
height: 400px;
border-radius: 12px;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
cursor: pointer;
object-fit: contain;
&:hover {
transform: scale(1.02);
}
}
}
.blog-poster {
width: 95%;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
}
.blog-body {
margin: 1rem 0;
.blog-section {
margin: 50px 0;
.section-title {
font-size: 1.75rem;
font-weight: 600;
color: #111827;
border-left: 5px solid #2563eb;
padding-left: 12px;
margin-bottom: 24px;
transition: all 0.3s ease;
&:hover {
color: #2563eb;
}
}
.section-description {
font-size: 1.05rem;
color: #374151;
margin-bottom: 24px;
text-align: justify;
}
.section-images {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin: 34px 0;
.section-image {
width: 80%;
height: 450px;
object-fit: cover;
border-radius: 10px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
transition: transform 0.25s ease;
&:hover {
transform: scale(1.03);
}
}
}
.section-videos {
display: flex;
grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
gap: 24px;
align-items: center;
justify-content: center;
margin: 24px 0;
width: 100%;
.section-video {
width: 80%;
height: 450px;
border-radius: 10px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
object-fit: cover;
}
}
}
}
}
@keyframes fadeIn {
0% {
opacity: 0;
transform: translateY(20px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 768px) {
padding: 20px;
.blog-detail-content {
padding: 28px 12px 0px 12px;
width: 100%;
margin: 0;
.blog-header {
.blog-title {
font-size: 2rem;
}
.blog-subtitle {
font-size: 1rem;
}
}
.blog-body {
margin: 0;
.blog-section {
.section-title {
font-size: 1.5rem;
}
.section-description {
font-size: 1rem;
}
}
}
}
}
}
.blog-body {
line-height: 1.7;
font-size: 1rem;
color: #333;
padding: 1rem 0;
font-family: "Poppins";
.blog-header-block {
margin: 2.2rem 0 16px 0rem;
font-weight: 600;
line-height: 1.5;
color: #000000;
font-size: 32px;
font-family: "Poppins";
}
.blog-paragraph {
margin: 1.5rem 0 1rem 0;
text-align: justify;
font-family: "Poppins";
color: #333;
font-size: 16px;
a {
color: #2783da;
}
}
.blog-list {
margin: 2rem 0 2rem 1rem;
&.ordered {
list-style-type: decimal;
}
&.unordered {
list-style-type: disc;
}
li {
margin-bottom: 0.5rem;
font-size: 20px;
font-weight: 600;
font-family: "Poppins";
color: #000000;
}
}
.blog-image {
margin: 1.5rem 0;
img {
width: 100%;
border-radius: 12px;
margin: 3rem 0 1rem 0;
}
.caption {
text-align: center;
font-size: 0.85rem;
color: #777;
margin: 1rem 0;
}
}
.blog-video {
margin: 1.5rem 0;
.video-player {
width: 100%;
border-radius: 12px;
margin: 3rem 0 1rem 0;
}
.caption {
text-align: center;
font-size: 0.85rem;
color: #777;
margin: 1rem 0;
}
}
}
.blog-detail-contentMain {
display: flex;
align-items: flex-start;
width: 100%;
justify-content: space-between;
gap: 1rem;
@media (max-width: 768px) {
flex-direction: column;
}
}
.blog-separator {
margin: 2rem 0;
}
.shareBlog {
position: relative;
margin: 2rem 0;
.share-btn {
display: flex;
align-items: center;
gap: 8px;
background: #1d4ed8;
outline: none;
color: white;
border: none;
padding: 12px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 400;
transition: all 0.3s ease;
font-family: "Poppins";
margin: 5rem 0 1rem 0;
&:hover {
transform: translateY(-2px);
}
svg {
font-size: 16px;
}
}
.share-options {
position: absolute;
top: -120px;
left: 0;
background: white;
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
padding: 16px;
min-width: 250px;
z-index: 100;
animation: slideDown 0.3s ease;
.share-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f3f4f6;
span {
font-weight: 600;
color: #374151;
font-size: 14px;
}
button {
background: none;
border: none;
cursor: pointer;
border-radius: 4px;
color: #6b7280;
&:hover {
background: #f3f4f6;
}
}
}
.share-buttons {
display: flex;
flex-direction: row;
gap: 16px;
font-family: "Poppins";
button {
display: flex;
align-items: center;
gap: 12px;
padding: 2px 2px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 400;
font-family: "Poppins";
transition: all 0.2s ease;
background-color: unset;
svg {
font-size: 18px;
}
&.share-facebook {
color: #1877f2;
}
&.share-whatsapp {
color: #25d366;
}
&.share-twitter {
color: #000000;
}
&.share-linkedin {
color: #0077b5;
}
&.share-telegram {
color: #0088cc;
}
&.share-instagram {
color: #f03333;
}
&.share-copy {
background: #f2f9ff;
color: #374151;
&:hover {
background: #f3f4f6;
}
}
}
}
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.blogdetailsNavbarText {
font-weight: 400;
color: #000000;
font-family: "Poppins";
font-size: 18px;
}
.linktoPrivayCookie {
color: #ffffff;
cursor: pointer;
transition: all 0.2s;
&:hover {
color: #2563eb;
}
}

View File

@ -0,0 +1,152 @@
import React, { useState, useEffect } from "react";
import { FaFacebookF, FaInstagram, FaWhatsapp } from "react-icons/fa";
import { message } from "antd";
import "./BlogExtras.scss";
import { getPublishedBlogs, postComments } from "../../features/Blog/Blog";
import { useDispatch } from "react-redux";
import { getSession } from "../../Services/others";
import { useNavigate } from "react-router-dom";
const subDirectory = import.meta.env.ENV_BASE_URL;
const BlogExtras = ({ allowComments, blogId }) => {
const [blogData, setBlogData] = useState(null);
const dispatch = useDispatch();
const navigate = useNavigate();
const UserId = getSession("UserId");
const [formData, setFormData] = useState({
name: "",
email: "",
comment: ""
});
console.log(blogData, "blogData")
useEffect(() => {
const fetchBlogData = async () => {
try {
const response = await dispatch(getPublishedBlogs()).unwrap();
if (response.data.statusCode === 1) {
const data = response.data.data;
setBlogData(data);
}
} catch (error) {
console.error('Error fetching blog data:', error);
}
};
// if (blogId) {
fetchBlogData();
// }
}, []);
const handleInputChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
const data = {
Name: formData?.name,
Email: formData?.email,
Comment: formData?.comment,
CreatedBy: UserId || 0
}
const response = await dispatch(postComments(data))?.unwrap();
if (response?.data?.statusCode === 1) {
message.success('Comment submitted successfully!');
setFormData({ name: "", email: "", comment: "" });
} else {
message.error('Failed to submit comment. Please try again.');
}
} catch (error) {
console.error('Error submitting comment:', error);
message.error('Failed to submit comment. Please try again.');
}
};
return (
<div className="blog-extras">
{/* Comments Form */}
{allowComments === "Y" && (
<div className="comments-section">
<h3>Leave a Comment</h3>
<form onSubmit={handleSubmit} className="comment-form">
<div className="form-group">
<input
type="text"
name="name"
placeholder="Your Name"
value={formData.name}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<input
type="email"
name="email"
placeholder="Your Email"
value={formData.email}
onChange={handleInputChange}
required
/>
</div>
<div className="form-group">
<textarea
name="comment"
placeholder="Write your comment..."
value={formData.comment}
onChange={handleInputChange}
rows="4"
required
/>
</div>
<button type="submit" className="submit-btn">
Post Comment
</button>
</form>
</div>
)}
{/* Featured Posts */}
{blogData && blogData?.filter(blog => blog?.Popular === "Y" && blog?.BlogId !== blogId)?.length > 0 && (
<div className="featured-posts">
<h3>Featured Posts</h3>
{blogData.filter(blog => blog.Popular === "Y" && blog?.BlogId !== blogId).slice(0, 3).map((blog) => (
<div key={blog.BlogId} className="featured-post-item" onClick={() => navigate(`${subDirectory}blog/${blog.Slug}`)} style={{ cursor: 'pointer' }}>
<img src={blog.HeaderImage} alt="Post" />
<div className="featured-post-content">
<h4>{blog.BlogTitle}</h4>
<span>{new Date(blog.CreatedDate).toLocaleDateString()}</span>
</div>
</div>
))}
</div>
)}
{/* Social Media Follow */}
<div className="social-section">
<h3>Follow Us</h3>
<div className="social-links">
<a href="https://www.facebook.com/pozomind/" className="social-link facebook" target="_blank" rel="noopener noreferrer">
<FaFacebookF />
</a>
<a href="https://www.instagram.com/pozomind/" className="social-link instagram" target="_blank" rel="noopener noreferrer">
<FaInstagram />
</a>
<a href="https://wa.me/917324000012" className="social-link whatsapp" target="_blank" rel="noopener noreferrer">
<FaWhatsapp />
</a>
</div>
</div>
</div>
);
};
export default BlogExtras;

View File

@ -0,0 +1,209 @@
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap");
.blog-extras {
margin: 2rem 0;
width: 30%;
font-family: "Poppins", sans-serif;
position: sticky;
top: 5rem;
.comments-section {
background: #fff;
border-radius: 12px;
padding: 1rem 1.5rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid #f0f0f0;
h3 {
color: #2c3e50;
margin-bottom: 0.5rem;
font-size: 1.4rem;
font-weight: 500;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
.comment-form {
.form-group {
margin-bottom: 0.5rem;
input,
textarea {
width: 100%;
padding: 0.385rem 1rem;
border: 1px solid #e8ecef;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
font-family: "Poppins", sans-serif;
font-weight: 400;
&:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}
&::placeholder {
color: #6c757d;
font-family: "Poppins", sans-serif;
font-weight: 300;
}
}
textarea {
resize: vertical;
min-height: 100px;
}
}
.submit-btn {
background: linear-gradient(190deg, #007bff, #007bff);
color: white;
border: none;
padding: 10px 16px;
border-radius: 8px;
font-size: 14px;
font-weight: 400;
font-family: "Poppins", sans-serif;
cursor: pointer;
transition: all 0.3s ease;
width: 100%;
&:hover {
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
}
}
}
.featured-posts {
background: #fff;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
border: 1px solid #f0f0f0;
margin-top: 5px;
h3 {
color: #2c3e50;
margin-bottom: 1.5rem;
font-size: 1.4rem;
font-weight: 400;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
.featured-post-item {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: all 0.3s ease;
&:last-child {
border-bottom: none;
}
&:hover {
background: #f8f9fa;
border-radius: 8px;
padding: 12px;
margin: 0 -12px;
}
img {
width: 60px;
height: 60px;
border-radius: 8px;
object-fit: cover;
flex-shrink: 0;
}
.featured-post-content {
flex: 1;
h4 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 500;
color: #2c3e50;
line-height: 1.4;
font-family: "Poppins", sans-serif;
}
span {
font-size: 12px;
color: #6c757d;
font-family: "Poppins", sans-serif;
font-weight: 400;
}
}
}
}
.social-section {
border-radius: 12px;
padding: 1rem;
border: 1px solid #f0f0f0;
text-align: left;
h3 {
color: #000000;
font-size: 1.5rem;
font-weight: 600;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
.social-links {
display: flex;
justify-content: flex-start;
.social-link {
display: flex;
align-items: center;
justify-content: center;
width: 35px;
height: 35px;
border-radius: 50%;
color: #000000;
text-decoration: none;
transition: all 0.3s ease;
font-size: 1.25rem;
background-color: #ffffff;
&:hover {
transform: translateY(-3px);
}
}
}
}
}
@media (max-width: 768px) {
.blog-extras {
margin: 1rem auto;
width: 100%;
.comments-section,
.social-section {
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.social-links {
.social-link {
width: 45px;
height: 45px;
font-size: 1.1rem;
}
}
}
}

View File

@ -0,0 +1,752 @@
// AppDemo Styles - Clean and Modern Design
.video-section-form-master {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
// Header Section
.video-section-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 40px 40px 32px;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
.video-section-header-left {
display: flex;
align-items: center;
gap: 20px;
.video-section-header-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.video-section-header-content {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.03em;
line-height: 1.2;
font-family: "Poppins", sans-serif;
}
p {
margin: 0;
font-size: 0.9375rem;
color: #64748b;
font-weight: 400;
letter-spacing: -0.01em;
font-family: "Poppins", sans-serif;
}
}
}
.video-section-create-btn {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 16px 32px;
border-radius: 12px;
font-weight: 600;
font-size: 0.9375rem;
cursor: pointer;
font-family: "Poppins", sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
}
}
}
// Tab Navigation
.video-section-tab-navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 40px;
border-bottom: 1px solid #e5e7eb;
background: #f8fafc;
.video-section-tab-buttons {
display: flex;
gap: 16px;
.video-section-tab-btn {
padding: 8px 16px;
border: none;
background: transparent;
color: #64748b;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.video-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.video-section-active) {
background: #f1f5f9;
color: #475569;
}
}
}
.video-section-tab-controls {
display: flex;
align-items: center;
gap: 16px;
.video-section-sort-dropdown {
select {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: white;
color: #374151;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
cursor: pointer;
&:focus {
outline: none;
border-color: #3b82f6;
}
}
}
.video-section-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
.video-section-view-btn {
padding: 8px 12px;
border: none;
background: white;
color: #64748b;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.video-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.video-section-active) {
background: #f8fafc;
}
}
}
}
}
// Apps Display Section
.video-section-display {
padding: 32px;
.video-section-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 16px;
}
}
}
// App Card Styles
.video-section-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
&.video-section-inactive {
opacity: 0.7;
border-color: #f6ad55;
.video-section-status {
color: #991b1b;
}
}
.video-section-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 1.125rem;
font-weight: 700;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
flex: 1;
margin-right: 16px;
}
.video-section-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
button {
padding: 6px 12px;
border: none;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
&.video-section-edit-btn {
background: #dbeafe;
color: #1e40af;
&:hover {
background: #bfdbfe;
transform: translateY(-1px);
}
}
&.video-section-delete-btn {
background: #fee2e2;
color: #991b1b;
&:hover {
background: #fecaca;
transform: translateY(-1px);
}
}
&.video-section-status-btn {
&.video-section-activate {
background: #dcfce7;
color: #166534;
&:hover {
background: #bbf7d0;
transform: translateY(-1px);
}
}
&.video-section-deactivate {
background: #fef3c7;
color: #92400e;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
}
}
.video-section-card-content {
.video-section-video-preview {
margin-bottom: 16px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
video {
width: 100%;
display: block;
}
}
p {
margin: 0 0 12px 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
strong {
color: #374151;
font-weight: 600;
}
&.video-section-created-date {
font-size: 0.75rem;
color: #94a3b8;
margin-bottom: 8px;
}
&.video-section-status {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
&.video-section-active {
color: #166534;
}
&.video-section-inactive {
color: #991b1b;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
// Placeholder Content
.video-section-placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 32px;
text-align: center;
background: transparent;
.video-section-placeholder-icon {
width: 120px;
height: 120px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 3.5rem;
margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h2 {
margin: 0 0 16px 0;
font-size: 1.75rem;
font-weight: 600;
color: #1e293b;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
font-family: "Poppins", sans-serif;
line-height: 1.5;
max-width: 400px;
}
}
// Modal Form Styles
.video-section-form {
padding: 20px 0;
.video-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
&.error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
}
.video-section-file-input {
cursor: pointer;
background: white;
&::-webkit-file-upload-button {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
margin-right: 12px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
}
}
}
.video-section-video-preview {
margin-top: 12px;
padding: 12px;
background: #f8fafc;
border-radius: 8px;
border: 2px solid #e2e8f0;
p {
margin: 0 0 8px 0;
font-size: 0.875rem;
color: #64748b;
font-weight: 500;
}
video {
width: 100%;
border-radius: 6px;
}
}
.error-message {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 4px;
font-family: "NeueMontreal", sans-serif;
}
}
}
}
.video-section-form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 32px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.video-section-save-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
}
&.video-section-cancel-btn {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
&:hover {
background: #e5e7eb;
transform: translateY(-1px);
}
}
&.video-section-clear-btn {
background: #fef3c7;
color: #92400e;
border: 1px solid #fbbf24;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
// Global modal overrides for video section form
.ant-modal {
.video-section-form {
padding: 20px 0;
.video-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
}
}
}
}
// Popconfirm styling for video section
.ant-popover.ant-popconfirm {
.ant-popover-inner {
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border: 1px solid #e5e7eb;
overflow: hidden;
}
.ant-popover-inner-content {
padding: 0;
}
.ant-popconfirm-inner-content {
padding: 20px;
.ant-popconfirm-message {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
.ant-popconfirm-message-icon {
margin-top: 2px;
.anticon {
color: #f59e0b;
font-size: 18px;
}
}
.ant-popconfirm-message-text {
flex: 1;
.ant-popconfirm-title {
font-size: 16px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
font-family: "Poppins", sans-serif;
}
.ant-popconfirm-description {
font-size: 14px;
color: #64748b;
line-height: 1.4;
font-family: "NeueMontreal", sans-serif;
}
}
}
.ant-popconfirm-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 16px;
.ant-btn {
border-radius: 8px;
font-weight: 600;
font-size: 13px;
height: 36px;
padding: 0 16px;
font-family: "NeueMontreal", sans-serif;
transition: all 0.2s ease;
&.ant-btn-primary {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border: none;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
&:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}
}
&.ant-btn-default {
background: #f8fafc;
border: 1px solid #e2e8f0;
color: #475569;
&:hover {
background: #f1f5f9;
border-color: #cbd5e1;
color: #334155;
transform: translateY(-1px);
}
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
}
}
}
}
.ant-popover-arrow {
&::before {
background: white;
border: 1px solid #e5e7eb;
}
}
}
// Responsive Design
@media (max-width: 768px) {
.video-section-form-master {
.video-section-display {
padding: 16px;
h2 {
font-size: 1.5rem;
margin-bottom: 24px;
}
}
.video-section-card {
padding: 16px;
.video-section-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
h3 {
margin-right: 0;
margin-bottom: 0;
}
.video-section-card-actions {
width: 100%;
justify-content: flex-start;
}
}
}
.video-section-placeholder-content {
margin: 20px 0;
padding: 60px 16px;
.video-section-placeholder-icon {
width: 100px;
height: 100px;
font-size: 3rem;
margin-bottom: 32px;
}
h2 {
font-size: 1.5rem;
}
p {
font-size: 0.875rem;
max-width: 300px;
}
}
}
}

View File

@ -0,0 +1,723 @@
// BannerSectionForm Styles - Clean and Modern Design
.banner-section-form-master {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
// Header Section
.banner-section-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 40px 40px 32px;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
.banner-section-header-left {
display: flex;
align-items: center;
gap: 20px;
.banner-section-header-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.banner-section-header-content {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.03em;
line-height: 1.2;
font-family: "Poppins", sans-serif;
}
p {
margin: 0;
font-size: 0.9375rem;
color: #64748b;
font-weight: 400;
letter-spacing: -0.01em;
font-family: "Poppins", sans-serif;
}
}
}
.banner-section-create-btn {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 16px 32px;
border-radius: 12px;
font-weight: 600;
font-size: 0.9375rem;
cursor: pointer;
font-family: "Poppins", sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
}
}
}
// Tab Navigation
.banner-section-tab-navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 40px;
border-bottom: 1px solid #e5e7eb;
background: #f8fafc;
.banner-section-tab-buttons {
display: flex;
gap: 16px;
.banner-section-tab-btn {
padding: 8px 16px;
border: none;
background: transparent;
color: #64748b;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.banner-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.banner-section-active) {
background: #f1f5f9;
color: #475569;
}
}
}
.banner-section-tab-controls {
display: flex;
align-items: center;
gap: 16px;
.banner-section-sort-dropdown {
select {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: white;
color: #374151;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
cursor: pointer;
&:focus {
outline: none;
border-color: #3b82f6;
}
}
}
.banner-section-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
.banner-section-view-btn {
padding: 8px 12px;
border: none;
background: white;
color: #64748b;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.banner-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.banner-section-active) {
background: #f8fafc;
}
}
}
}
}
// Apps Display Section
.banner-section-display {
padding: 32px;
.banner-section-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 16px;
}
}
}
// App Card Styles
.banner-section-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
&.banner-section-inactive {
opacity: 0.7;
border-color: #f6ad55;
.banner-section-status {
color: #991b1b;
}
}
.banner-section-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 1.125rem;
font-weight: 700;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
flex: 1;
margin-right: 16px;
}
.banner-section-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
button {
padding: 6px 12px;
border: none;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
&.banner-section-edit-btn {
background: #dbeafe;
color: #1e40af;
&:hover {
background: #bfdbfe;
transform: translateY(-1px);
}
}
&.banner-section-delete-btn {
background: #fee2e2;
color: #991b1b;
&:hover {
background: #fecaca;
transform: translateY(-1px);
}
}
&.banner-section-status-btn {
&.banner-section-activate {
background: #dcfce7;
color: #166534;
&:hover {
background: #bbf7d0;
transform: translateY(-1px);
}
}
&.banner-section-deactivate {
background: #fef3c7;
color: #92400e;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
}
}
.banner-section-card-content {
p {
margin: 0 0 12px 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
strong {
color: #374151;
font-weight: 600;
}
&.banner-section-created-date {
font-size: 0.75rem;
color: #94a3b8;
margin-bottom: 8px;
}
&.banner-section-status {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
&.banner-section-active {
color: #166534;
}
&.banner-section-inactive {
color: #991b1b;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
// Placeholder Content
.banner-section-placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 32px;
text-align: center;
background: transparent;
.banner-section-placeholder-icon {
width: 120px;
height: 120px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 3.5rem;
margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h2 {
margin: 0 0 16px 0;
font-size: 1.75rem;
font-weight: 600;
color: #1e293b;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
font-family: "Poppins", sans-serif;
line-height: 1.5;
max-width: 400px;
}
}
// Modal Form Styles
.banner-section-form {
padding: 20px 0;
.banner-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input,
textarea {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
}
textarea {
resize: vertical;
min-height: 100px !important;
}
}
}
}
.banner-section-form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 32px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.banner-section-save-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
}
&.banner-section-cancel-btn {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
&:hover {
background: #e5e7eb;
transform: translateY(-1px);
}
}
&.banner-section-clear-btn {
background: #fef3c7;
color: #92400e;
border: 1px solid #fbbf24;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
// Global modal overrides for banner section form
.ant-modal {
.banner-section-form {
padding: 20px 0;
.banner-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input,
textarea {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
}
textarea {
resize: vertical;
min-height: 100px !important;
}
}
}
}
// Responsive Design
@media (max-width: 768px) {
.banner-section-form-master {
.banner-section-display {
padding: 16px;
h2 {
font-size: 1.5rem;
margin-bottom: 24px;
}
}
.banner-section-card {
padding: 16px;
.banner-section-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
h3 {
margin-right: 0;
margin-bottom: 0;
}
.banner-section-card-actions {
width: 100%;
justify-content: flex-start;
}
}
}
.banner-section-placeholder-content {
margin: 20px 0;
padding: 60px 16px;
.banner-section-placeholder-icon {
width: 100px;
height: 100px;
font-size: 3rem;
margin-bottom: 32px;
}
h2 {
font-size: 1.5rem;
}
p {
font-size: 0.875rem;
max-width: 300px;
}
}
}
}
// Error styles for form validation
.banner-section-form {
input.error,
textarea.error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
.error-message {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 4px;
font-family: "NeueMontreal", sans-serif;
}
}
// Popconfirm styling for banner section
.ant-popover.ant-popconfirm {
.ant-popover-inner {
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border: 1px solid #e5e7eb;
overflow: hidden;
}
.ant-popover-inner-content {
padding: 0;
}
.ant-popconfirm-inner-content {
padding: 20px;
.ant-popconfirm-message {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
.ant-popconfirm-message-icon {
margin-top: 2px;
.anticon {
color: #f59e0b;
font-size: 18px;
}
}
.ant-popconfirm-message-text {
flex: 1;
.ant-popconfirm-title {
font-size: 16px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
font-family: "Poppins", sans-serif;
}
.ant-popconfirm-description {
font-size: 14px;
color: #64748b;
line-height: 1.4;
font-family: "NeueMontreal", sans-serif;
}
}
}
.ant-popconfirm-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 16px;
.ant-btn {
border-radius: 8px;
font-weight: 600;
font-size: 13px;
height: 36px;
padding: 0 16px;
font-family: "NeueMontreal", sans-serif;
transition: all 0.2s ease;
&.ant-btn-primary {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border: none;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
&:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}
}
&.ant-btn-default {
background: #f8fafc;
border: 1px solid #e2e8f0;
color: #475569;
&:hover {
background: #f1f5f9;
border-color: #cbd5e1;
color: #334155;
transform: translateY(-1px);
}
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
}
}
}
}
.ant-popover-arrow {
&::before {
background: white;
border: 1px solid #e5e7eb;
}
}
}
// Custom styling for banner section popconfirm specifically
.banner-section-card .ant-popover.ant-popconfirm {
.ant-popconfirm-inner-content {
.ant-popconfirm-message {
.ant-popconfirm-message-text {
.ant-popconfirm-title {
color: #991b1b;
}
}
}
}
}

View File

@ -0,0 +1,833 @@
// BlogForm Styles - Clean and Modern Design (Flat Structure)
.blog-form-master {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
}
.blog-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 16px 16px;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
@media (max-width: 500px) {
padding: 10px;
}
}
.blog-form-header-content h2 {
font-size: 1.5rem;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.03em;
line-height: 1.2;
font-family: "Poppins", sans-serif;
margin: 0 0 5px 0;
@media (max-width: 600px) {
font-size: 1.3rem;
}
}
.blog-form-header-content p {
margin: 0;
font-size: 0.7rem;
color: #64748b;
font-weight: 400;
letter-spacing: -0.01em;
font-family: "Poppins", sans-serif;
@media (max-width: 600px) {
font-size: 0.6rem;
}
}
.blog-form-create-btn {
background-color: #5255c8;
color: white;
border: none;
padding: 10px 16px;
border-radius: 8px;
font-weight: 400;
font-size: 14px;
cursor: pointer;
font-family: "Poppins", sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
}
.blog-form-create-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(82, 85, 200, 0.4);
}
.blog-form-layout {
display: flex;
gap: 30px;
padding: 2px;
flex: 1;
align-items: flex-start;
justify-content: center;
}
.blog-form-left {
width: 60%;
@media (max-width: 768px) {
width: 100%;
}
}
.blog-form-right {
flex: 1;
}
.blog-form-container {
border-radius: 16px;
padding: 20px 20px;
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
margin-bottom: 20px;
border: 1px solid #00000014;
}
.blog-form-container:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
.blog-form-container h3 {
margin: 0 0 10px 0;
font-size: 1rem;
font-weight: 600;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
}
.section-header {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 20px;
gap: 12px;
}
.section-header h3 {
margin: 0;
font-size: 0.8rem;
text-decoration: underline;
font-weight: 500;
cursor: pointer;
color: #1e40af;
font-family: "Poppins", sans-serif;
}
.section-header div {
margin: 0;
font-size: 0.8rem;
text-decoration: underline;
font-weight: 500;
cursor: pointer;
color: #bd0a0a;
font-family: "Poppins", sans-serif;
}
.add-btn {
background: #000000;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-family: "Poppins", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.add-btn:hover {
background: #333333;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.section-buttons {
display: flex;
gap: 12px;
margin-top: 20px;
}
.remove-btn {
background: #ef4444;
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-family: "Poppins", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.remove-btn:hover {
background: #dc2626;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
.blog-form-field {
margin-bottom: 24px;
}
.blog-form-field label {
display: block;
margin-bottom: 4px;
font-weight: 500;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
.blog-form-field input,
.blog-form-field textarea,
.blog-form-field select {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
}
.blog-form-field input:focus,
.blog-form-field textarea:focus,
.blog-form-field select:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
.blog-form-field input::placeholder,
.blog-form-field textarea::placeholder {
color: #9ca3af !important;
}
.blog-form-field textarea {
resize: vertical;
min-height: 120px;
}
.detail-section {
padding: 20px 0;
margin-bottom: 20px;
border-radius: 12px;
}
.dynamic-inputs {
display: flex;
flex-direction: column;
gap: 20px;
textarea {
height: 4rem;
border: 1px solid #d1d5db !important;
border-radius: 8px;
}
}
.input-group {
margin-bottom: 15px;
}
.group-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.group-header label {
margin: 0;
font-weight: 600;
color: #374151;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
.input-wrapper {
display: flex;
align-items: flex-start;
gap: 10px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.input-wrapper input,
.input-wrapper textarea {
flex: 1;
margin-bottom: 0;
}
.button-group {
display: flex;
gap: 8px;
align-items: flex-start;
}
.button-group .add-btn {
background: #5255c8;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-family: "Poppins", sans-serif;
min-width: 60px;
height: fit-content;
}
.button-group .add-btn:hover {
background: #059669;
transform: translateY(-1px);
}
.button-group .remove-btn {
background: #ef4444;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-family: "Poppins", sans-serif;
min-width: 60px;
height: fit-content;
}
.button-group .remove-btn:hover {
background: #dc2626;
transform: translateY(-1px);
}
.preview-section {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
position: sticky;
top: 20px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.preview-section:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
.preview-section h3 {
margin: 0 0 20px 0;
font-size: 1rem;
font-weight: 700;
color: #1e293b;
font-family: "Poppins", sans-serif;
}
.preview-content {
display: flex;
flex-direction: column;
gap: 15px;
}
.preview-content p {
margin: 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
}
.blog-form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 32px;
}
.blog-form-btn-primary {
background: linear-gradient(135deg, #5255c8 0%, #5255c8 100%);
color: white;
border: none;
padding: 10px 20px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
}
.blog-form-btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
.blog-form-btn-secondary {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
padding: 10px 20px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
}
.blog-form-btn-secondary:hover {
background: #e5e7eb;
transform: translateY(-1px);
}
// Blog Display Section (CTA-style)
.blog-display {
padding: 32px;
.blog-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 16px;
}
}
}
// Blog Card Styles (CTA-style)
.blog-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
&.blog-inactive {
opacity: 0.7;
border-color: #ffffff;
.blog-status {
color: #991b1b;
margin-top: 15px;
}
}
.blog-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 6px;
h3 {
margin: 0;
font-size: 1rem;
font-weight: 700;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
flex: 1;
margin-right: 16px;
}
}
.blog-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 10px;
align-items: center;
justify-content: flex-end;
button {
padding: 2px 6px;
border: none;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 400;
cursor: pointer;
transition: all 0.2s ease;
font-family: "Poppins", sans-serif;
letter-spacing: 0.5px;
background: none;
&.blog-edit-btn {
color: #000000;
}
&.blog-delete-btn {
color: #000000 !important;
}
&.blog-status-btn {
&.blog-activate {
color: #009238;
&:hover {
background: #bbf7d0;
transform: translateY(-1px);
}
}
&.blog-deactivate {
color: #cc0000;
&:hover {
transform: translateY(-1px);
}
}
}
}
}
.blog-card-content {
p {
margin: 0 0 12px 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
display: -webkit-box;
-webkit-line-clamp: 4;
-webkit-box-orient: vertical;
overflow: hidden;
height: max-content;
max-height: 80px;
strong {
color: #374151;
font-weight: 600;
margin-right: 4px;
}
&.blog-status {
font-size: 11px;
font-family: "Poppins";
font-weight: 400;
letter-spacing: 0.5px;
background-color: #166534;
width: max-content;
padding: 0px 6px;
border-radius: 3px;
&.blog-active {
color: #ffffff;
margin-top: 15px;
}
&.blog-inactive {
background-color: #c20000;
color: #ffffff;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
// Blog Placeholder Content (CTA-style)
.blog-placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 32px;
text-align: center;
background: transparent;
.blog-placeholder-icon {
width: 70px;
height: 70px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: #363a3f;
font-size: 2.5rem;
margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h2 {
margin: 0 0 16px 0;
font-size: 1.75rem;
font-weight: 600;
color: #1e293b;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
font-family: "Poppins", sans-serif;
line-height: 1.5;
max-width: 400px;
}
}
// Blog Filter Styles
.blogFilter {
display: flex;
gap: 16px;
align-items: center;
padding: 10px 10px;
border-bottom: 1px solid #e5e7eb;
flex-wrap: wrap;
.search-container {
position: relative;
flex: 1;
min-width: 250px;
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #9ca3af;
font-size: 16px;
}
.search-input {
width: 100%;
padding: 10px 12px 10px 40px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
outline: none;
transition: border-color 0.2s;
font-family: "NeueMontreal", sans-serif;
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
&::placeholder {
color: #9ca3af;
}
}
}
.filter-buttons {
display: flex;
gap: 8px;
align-items: center;
.filter-label {
font-size: 14px;
color: #6b7280;
font-weight: 500;
font-family: "Poppins", sans-serif;
}
.filter-btn {
padding: 6px 12px;
border: 1px solid #d1d5db;
background: white;
color: #374151;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 6px;
font-family: "Poppins", sans-serif;
&.active {
border-color: #3b82f6;
background: #5c9aff;
color: white;
}
&:hover {
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.count-badge {
padding: 2px 6px;
border-radius: 10px;
font-size: 10px;
font-weight: 600;
background: #f3f4f6;
color: #6b7280;
&.active {
background: rgba(255, 255, 255, 0.2);
color: white;
}
}
}
}
@media (max-width: 768px) {
flex-direction: column;
align-items: stretch;
gap: 12px;
.search-container {
min-width: auto;
}
.filter-buttons {
justify-content: center;
flex-wrap: wrap;
}
}
}
// Responsive Design
@media (max-width: 768px) {
.blog-form-layout {
flex-direction: column;
gap: 20px;
padding: 16px;
}
.blog-form-container {
padding: 16px;
}
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.blog-display {
padding: 6px;
}
.blog-card {
padding: 16px;
.blog-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
h3 {
margin-right: 0;
margin-bottom: 0;
}
}
.blog-card-actions {
width: 100%;
justify-content: flex-start;
}
}
.blog-placeholder-content {
padding: 60px 16px;
.blog-placeholder-icon {
width: 100px;
height: 100px;
font-size: 3rem;
margin-bottom: 32px;
}
h2 {
font-size: 1.5rem;
}
p {
font-size: 0.875rem;
max-width: 300px;
}
}
.blog-form-actions {
flex-direction: column;
gap: 8px;
}
.blog-form-btn-primary,
.blog-form-btn-secondary {
width: 100%;
justify-content: center;
}
}
.filterAssDcc {
display: flex;
align-items: center;
gap: 4px;
button {
width: max-content;
padding: 8px 16px;
border: none;
font-family: "Poppins", sans-serif;
font-size: 14px;
font-weight: 500;
background-color: #e0ecff;
color: #1e6ae4;
border-radius: 6px;
cursor: pointer;
outline: none;
}
}

View File

@ -0,0 +1,713 @@
// CTASectionForm Styles - Clean and Modern Design
.cta-section-form-master {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
// Header Section
.cta-section-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 20px 20px;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
.cta-section-header-left {
display: flex;
align-items: center;
gap: 20px;
.cta-section-header-icon {
width: 45px;
height: 45px;
background: linear-gradient(135deg, #5255c8 0%, #5e62d3 50%, #8d91fa 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.3rem;
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.cta-section-header-content {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
font-size: 1.5rem;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.03em;
line-height: 1.2;
font-family: "Poppins", sans-serif;
}
p {
margin: 0;
font-size: 0.7rem;
color: #64748b;
font-weight: 400;
letter-spacing: -0.01em;
font-family: "Poppins", sans-serif;
}
}
}
.cta-section-create-btn {
background-color: #5255c8;
color: white;
border: none;
padding: 10px 16px;
border-radius: 8px;
font-weight: 400;
font-size: 14px;
cursor: pointer;
font-family: "Poppins", sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(245, 158, 11, 0.4);
}
}
}
// Tab Navigation
.cta-section-tab-navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
border-bottom: 1px solid #e5e7eb;
background: #f8fafc;
.cta-section-tab-buttons {
display: flex;
gap: 16px;
.cta-section-tab-btn {
padding: 8px 16px;
border: none;
background: transparent;
color: #64748b;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.cta-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.cta-section-active) {
background: #f1f5f9;
color: #475569;
}
}
}
.cta-section-tab-controls {
display: flex;
align-items: center;
gap: 16px;
.cta-section-sort-dropdown {
select {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: white;
color: #374151;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
cursor: pointer;
&:focus {
outline: none;
border-color: #3b82f6;
}
}
}
.cta-section-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
.cta-section-view-btn {
padding: 8px 12px;
border: none;
background: white;
color: #64748b;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.cta-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.cta-section-active) {
background: #f8fafc;
}
}
}
}
}
// Apps Display Section
.cta-section-display {
padding: 32px;
.cta-section-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 16px;
}
}
}
// App Card Styles
.cta-section-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
&.cta-section-inactive {
opacity: 0.7;
border-color: #ffffff;
.cta-section-status {
color: #991b1b;
margin-top: 15px;
}
}
.cta-section-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 6px;
h3 {
margin: 0;
font-size: 1rem;
font-weight: 700;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
flex: 1;
margin-right: 16px;
}
}
.cta-section-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-top: 10px;
align-items: flex-end;
justify-content: flex-end;
button {
padding: 2px 6px;
border: none;
border-radius: 4px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-family: "Poppins", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
background-color: none;
background: none;
&.cta-section-edit-btn {
color: #1e40af;
&:hover {
background: #bfdbfe;
transform: translateY(-1px);
}
}
&.cta-section-delete-btn {
color: #991b1b !important;
&:hover {
background: #fecaca !important;
transform: translateY(-1px);
}
}
&.cta-section-status-btn {
&.cta-section-activate {
color: #166534;
&:hover {
background: #bbf7d0;
transform: translateY(-1px);
}
}
&.cta-section-deactivate {
// background: #fef3c7;
color: #92400e;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
}
.cta-section-card-content {
p {
margin: 0 0 12px 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
strong {
color: #374151;
font-weight: 600;
}
&.cta-section-created-date {
font-size: 0.75rem;
color: #94a3b8;
margin-bottom: 8px;
}
&.cta-section-status {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
&.cta-section-active {
color: #166534;
margin-top: 15px;
}
&.cta-section-inactive {
color: #991b1b;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
// Placeholder Content
.cta-section-placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 32px;
text-align: center;
background: transparent;
.cta-section-placeholder-icon {
width: 120px;
height: 120px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 3.5rem;
margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h2 {
margin: 0 0 16px 0;
font-size: 1.75rem;
font-weight: 600;
color: #1e293b;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
font-family: "Poppins", sans-serif;
line-height: 1.5;
max-width: 400px;
}
}
// Modal Form Styles
.cta-section-form {
padding: 20px 0;
.cta-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
&.error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
}
.error-message {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 4px;
font-family: "NeueMontreal", sans-serif;
}
}
}
}
.cta-section-form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 32px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.cta-section-save-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
}
&.cta-section-cancel-btn {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
&:hover {
background: #e5e7eb;
transform: translateY(-1px);
}
}
&.cta-section-clear-btn {
background: #fef3c7;
color: #92400e;
border: 1px solid #fbbf24;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
// Global modal overrides for cta section form
.ant-modal {
.cta-section-form {
padding: 20px 0;
.cta-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
}
}
}
}
// Popconfirm styling for cta section
.ant-popover.ant-popconfirm {
.ant-popover-inner {
border-radius: 12px !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12) !important;
border: 1px solid #e5e7eb !important;
overflow: hidden;
}
.ant-popover-inner-content {
padding: 0;
}
.ant-popconfirm-inner-content {
padding: 20px;
.ant-popconfirm-message {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
.ant-popconfirm-message-icon {
margin-top: 2px;
.anticon {
color: #f59e0b;
font-size: 18px;
}
}
.ant-popconfirm-message-text {
flex: 1;
.ant-popconfirm-title {
font-size: 16px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
font-family: "Poppins", sans-serif;
}
.ant-popconfirm-description {
font-size: 14px;
color: #64748b;
line-height: 1.4;
font-family: "NeueMontreal", sans-serif;
}
}
}
.ant-popconfirm-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 16px;
.ant-btn {
border-radius: 8px;
font-weight: 600;
font-size: 13px;
height: 36px;
padding: 0 16px;
font-family: "NeueMontreal", sans-serif;
transition: all 0.2s ease;
&.ant-btn-primary {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border: none;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
&:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}
}
&.ant-btn-default {
background: #f8fafc;
border: 1px solid #e2e8f0;
color: #475569;
&:hover {
background: #f1f5f9;
border-color: #cbd5e1;
color: #334155;
transform: translateY(-1px);
}
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
}
}
}
}
.ant-popover-arrow {
&::before {
background: white;
border: 1px solid #e5e7eb;
}
}
}
// Custom styling for cta section popconfirm specifically
.cta-section-card .ant-popover.ant-popconfirm {
.ant-popconfirm-inner-content {
.ant-popconfirm-message {
.ant-popconfirm-message-text {
.ant-popconfirm-title {
color: #991b1b;
}
}
}
}
}
// Responsive Design
@media (max-width: 768px) {
.cta-section-form-master {
.cta-section-display {
padding: 16px;
h2 {
font-size: 1.5rem;
margin-bottom: 24px;
}
}
.cta-section-card {
padding: 16px;
.cta-section-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
h3 {
margin-right: 0;
margin-bottom: 0;
}
.cta-section-card-actions {
width: 100%;
justify-content: flex-start;
}
}
}
.cta-section-placeholder-content {
margin: 20px 0;
padding: 60px 16px;
.cta-section-placeholder-icon {
width: 100px;
height: 100px;
font-size: 3rem;
margin-bottom: 32px;
}
h2 {
font-size: 1.5rem;
}
p {
font-size: 0.875rem;
max-width: 300px;
}
}
}
}

View File

@ -0,0 +1,286 @@
.create-modal-form {
padding: 0;
width: 100%;
max-width: 600px;
margin: 0 auto;
.form-header {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 2rem;
background: linear-gradient(135deg, #5255c8 0%, #8b5cf6 50%, #a855f7 100%);
color: white;
border-radius: 16px 16px 0 0;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at top right, rgba(255, 255, 255, 0.1), transparent);
pointer-events: none;
}
.header-icon {
width: 56px;
height: 56px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.75rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
position: relative;
z-index: 1;
}
.header-content {
flex: 1;
position: relative;
z-index: 1;
h3 {
margin: 0;
font-size: 1.625rem;
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.2;
}
p {
margin: 0.5rem 0 0 0;
opacity: 0.95;
font-size: 0.9375rem;
font-weight: 400;
letter-spacing: -0.01em;
}
}
}
.form-body {
padding: 2rem;
background: white;
}
.form-group {
margin-bottom: 1.5rem;
label {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
color: #374151;
margin-bottom: 0.5rem;
font-size: 0.9rem;
.label-icon {
color: #667eea;
font-size: 0.8rem;
}
}
input, textarea, select {
width: 100%;
padding: 1rem 1.25rem;
border: 1.5px solid #e5e7eb;
border-radius: 12px;
font-size: 0.9375rem;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background: white;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
color: #1e293b;
font-weight: 400;
letter-spacing: -0.01em;
&:focus {
outline: none;
border-color: #5255c8;
background: white;
box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1), 0 1px 3px rgba(0, 0, 0, 0.05);
transform: translateY(-1px);
}
&:hover:not(:focus) {
border-color: #cbd5e1;
}
&.error {
border-color: #ef4444;
background: #fef2f2;
&:focus {
box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1);
}
}
&::placeholder {
color: #94a3b8;
font-weight: 400;
}
}
textarea {
resize: vertical;
min-height: 100px;
}
select {
cursor: pointer;
}
.error-text {
color: #ef4444;
font-size: 0.8rem;
margin-top: 0.25rem;
display: block;
}
}
.image-upload {
position: relative;
.file-input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.file-label {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
border: none;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
}
}
.image-preview {
margin-top: 1rem;
img {
width: 100%;
max-width: 200px;
height: 150px;
object-fit: cover;
border-radius: 8px;
border: 2px solid #e5e7eb;
}
}
.form-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
padding: 1.5rem 2rem;
background: #f9fafb;
border-radius: 0 0 12px 12px;
button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.9rem;
&.cancel-btn {
background: #6b7280;
color: white;
&:hover {
background: #4b5563;
transform: translateY(-1px);
}
}
&.save-btn {
background: linear-gradient(135deg, #5255c8, #8b5cf6);
color: white;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
&:hover {
background: linear-gradient(135deg, #5558e3, #7c3aed);
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.35);
&::before {
left: 100%;
}
}
&:active {
transform: translateY(0);
}
}
}
}
}
// Responsive design
@media (max-width: 768px) {
.create-modal-form {
.form-header {
padding: 1rem;
.header-icon {
width: 40px;
height: 40px;
font-size: 1.2rem;
}
.header-content h3 {
font-size: 1.25rem;
}
}
.form-body {
padding: 1.5rem;
}
.form-actions {
padding: 1rem 1.5rem;
flex-direction: column;
button {
width: 100%;
justify-content: center;
}
}
}
}

View File

@ -0,0 +1,781 @@
// EcosystemForm Styles - Clean and Modern Design
.ecosystem-section-form-master {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
// Header Section
.ecosystem-section-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 40px 40px 32px;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
.ecosystem-section-header-left {
display: flex;
align-items: center;
gap: 20px;
.ecosystem-section-header-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.ecosystem-section-header-content {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.03em;
line-height: 1.2;
font-family: "Poppins", sans-serif;
}
p {
margin: 0;
font-size: 0.9375rem;
color: #64748b;
font-weight: 400;
letter-spacing: -0.01em;
font-family: "Poppins", sans-serif;
}
}
}
.ecosystem-section-create-btn {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 16px 32px;
border-radius: 12px;
font-weight: 600;
font-size: 0.9375rem;
cursor: pointer;
font-family: "Poppins", sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
}
}
}
// Tab Navigation
.ecosystem-section-tab-navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 40px;
border-bottom: 1px solid #e5e7eb;
background: #f8fafc;
.ecosystem-section-tab-buttons {
display: flex;
gap: 16px;
.ecosystem-section-tab-btn {
padding: 8px 16px;
border: none;
background: transparent;
color: #64748b;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.ecosystem-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.ecosystem-section-active) {
background: #f1f5f9;
color: #475569;
}
}
}
.ecosystem-section-tab-controls {
display: flex;
align-items: center;
gap: 16px;
.ecosystem-section-sort-dropdown {
select {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: white;
color: #374151;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
cursor: pointer;
&:focus {
outline: none;
border-color: #3b82f6;
}
}
}
.ecosystem-section-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
.ecosystem-section-view-btn {
padding: 8px 12px;
border: none;
background: white;
color: #64748b;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.ecosystem-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.ecosystem-section-active) {
background: #f8fafc;
}
}
}
}
}
// Apps Display Section
.ecosystem-section-display {
padding: 32px;
.ecosystem-section-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 16px;
}
}
}
// App Card Styles
.ecosystem-section-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
&.ecosystem-section-inactive {
opacity: 0.7;
border-color: #f6ad55;
.ecosystem-section-status {
color: #991b1b;
}
}
.ecosystem-section-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 1.125rem;
font-weight: 700;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
flex: 1;
margin-right: 16px;
}
.ecosystem-section-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
button {
padding: 6px 12px;
border: none;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
&.ecosystem-section-edit-btn {
background: #dbeafe;
color: #1e40af;
&:hover {
background: #bfdbfe;
transform: translateY(-1px);
}
}
&.ecosystem-section-delete-btn {
background: #fee2e2;
color: #991b1b;
&:hover {
background: #fecaca;
transform: translateY(-1px);
}
}
&.ecosystem-section-status-btn {
&.ecosystem-section-activate {
background: #dcfce7;
color: #166534;
&:hover {
background: #bbf7d0;
transform: translateY(-1px);
}
}
&.ecosystem-section-deactivate {
background: #fef3c7;
color: #92400e;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
}
}
.ecosystem-section-card-content {
.ecosystem-section-poster-image {
margin-bottom: 16px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
}
p {
margin: 0 0 12px 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
strong {
color: #374151;
font-weight: 600;
}
&.ecosystem-section-created-date {
font-size: 0.75rem;
color: #94a3b8;
margin-bottom: 8px;
}
&.ecosystem-section-status {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
&.ecosystem-section-active {
color: #166534;
}
&.ecosystem-section-inactive {
color: #991b1b;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
// Placeholder Content
.ecosystem-section-placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 32px;
text-align: center;
background: transparent;
.ecosystem-section-placeholder-icon {
width: 120px;
height: 120px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 3.5rem;
margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h2 {
margin: 0 0 16px 0;
font-size: 1.75rem;
font-weight: 600;
color: #1e293b;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
font-family: "Poppins", sans-serif;
line-height: 1.5;
max-width: 400px;
}
}
// Modal Form Styles
.ecosystem-section-form {
padding: 20px 0;
.ecosystem-section-poster-section-title {
font-size: 1.125rem;
font-weight: 600;
color: #1e293b;
margin: 24px 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
font-family: "Poppins", sans-serif;
}
.ecosystem-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input,
textarea {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
&.error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
}
textarea {
resize: vertical;
min-height: 100px !important;
}
.ecosystem-section-file-input {
cursor: pointer;
background: white;
&::-webkit-file-upload-button {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
margin-right: 12px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
}
}
}
.ecosystem-section-image-preview {
margin-top: 12px;
padding: 12px;
background: #f8fafc;
border-radius: 8px;
border: 2px solid #e2e8f0;
img {
max-width: 100%;
max-height: 200px;
border-radius: 6px;
object-fit: cover;
}
}
.error-message {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 4px;
font-family: "NeueMontreal", sans-serif;
}
}
}
}
.ecosystem-section-form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 32px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.ecosystem-section-save-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
}
&.ecosystem-section-cancel-btn {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
&:hover {
background: #e5e7eb;
transform: translateY(-1px);
}
}
&.ecosystem-section-clear-btn {
background: #fef3c7;
color: #92400e;
border: 1px solid #fbbf24;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
// Global modal overrides for ecosystem section form
.ant-modal {
.ecosystem-section-form {
padding: 20px 0;
.ecosystem-section-poster-section-title {
font-size: 1.125rem;
font-weight: 600;
color: #1e293b;
margin: 24px 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
font-family: "Poppins", sans-serif;
}
.ecosystem-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input,
textarea {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
}
textarea {
resize: vertical;
min-height: 100px !important;
}
}
}
}
// Popconfirm styling for ecosystem section
.ant-popover.ant-popconfirm {
.ant-popover-inner {
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border: 1px solid #e5e7eb;
overflow: hidden;
}
.ant-popover-inner-content {
padding: 0;
}
.ant-popconfirm-inner-content {
padding: 20px;
.ant-popconfirm-message {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
.ant-popconfirm-message-icon {
margin-top: 2px;
.anticon {
color: #f59e0b;
font-size: 18px;
}
}
.ant-popconfirm-message-text {
flex: 1;
.ant-popconfirm-title {
font-size: 16px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
font-family: "Poppins", sans-serif;
}
.ant-popconfirm-description {
font-size: 14px;
color: #64748b;
line-height: 1.4;
font-family: "NeueMontreal", sans-serif;
}
}
}
.ant-popconfirm-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 16px;
.ant-btn {
border-radius: 8px;
font-weight: 600;
font-size: 13px;
height: 36px;
padding: 0 16px;
font-family: "NeueMontreal", sans-serif;
transition: all 0.2s ease;
&.ant-btn-primary {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border: none;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
&:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}
}
&.ant-btn-default {
background: #f8fafc;
border: 1px solid #e2e8f0;
color: #475569;
&:hover {
background: #f1f5f9;
border-color: #cbd5e1;
color: #334155;
transform: translateY(-1px);
}
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
}
}
}
}
.ant-popover-arrow {
&::before {
background: white;
border: 1px solid #e5e7eb;
}
}
}
// Responsive Design
@media (max-width: 768px) {
.ecosystem-section-form-master {
.ecosystem-section-display {
padding: 16px;
h2 {
font-size: 1.5rem;
margin-bottom: 24px;
}
}
.ecosystem-section-card {
padding: 16px;
.ecosystem-section-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
h3 {
margin-right: 0;
margin-bottom: 0;
}
.ecosystem-section-card-actions {
width: 100%;
justify-content: flex-start;
}
}
}
.ecosystem-section-placeholder-content {
margin: 20px 0;
padding: 60px 16px;
.ecosystem-section-placeholder-icon {
width: 100px;
height: 100px;
font-size: 3rem;
margin-bottom: 32px;
}
h2 {
font-size: 1.5rem;
}
p {
font-size: 0.875rem;
max-width: 300px;
}
}
}
}

View File

@ -0,0 +1,455 @@
// FAQ Form Master Container
.faq-form-master {
padding: 24px;
background: #f8f9fa;
min-height: 100vh;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
// Header Section
.faq-form-header {
display: flex;
justify-content: space-between;
align-items: center;
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
margin-bottom: 24px;
.faq-header-left {
display: flex;
align-items: center;
gap: 16px;
.faq-header-icon {
width: 48px;
height: 48px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.faq-header-content {
h2 {
margin: 0 0 4px 0;
font-size: 24px;
font-weight: 600;
color: #1a1a1a;
}
p {
margin: 0;
color: #6b7280;
font-size: 14px;
}
}
}
.faq-create-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
span {
font-size: 18px;
font-weight: 600;
}
}
}
// Tab Navigation
.faq-tab-navigation {
display: flex;
justify-content: space-between;
align-items: center;
background: white;
padding: 16px 24px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
margin-bottom: 24px;
.faq-tab-buttons {
display: flex;
gap: 8px;
.faq-tab-btn {
padding: 8px 16px;
border: 1px solid #e5e7eb;
background: white;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: #6b7280;
transition: all 0.2s ease;
&.faq-active {
background: #667eea;
color: white;
border-color: #667eea;
}
&:hover:not(.faq-active) {
background: #f3f4f6;
}
}
}
.faq-tab-controls {
display: flex;
align-items: center;
gap: 16px;
.faq-sort-dropdown select {
padding: 8px 12px;
border: 1px solid #e5e7eb;
border-radius: 6px;
background: white;
font-size: 14px;
cursor: pointer;
}
.faq-view-toggle {
display: flex;
gap: 4px;
.faq-view-btn {
width: 32px;
height: 32px;
border: 1px solid #e5e7eb;
background: white;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
&.faq-active {
background: #667eea;
color: white;
border-color: #667eea;
}
&:hover:not(.faq-active) {
background: #f3f4f6;
}
}
}
}
}
// Content Display
.faq-display {
.faq-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 20px;
}
}
// FAQ Cards
.faq-card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
overflow: hidden;
transition: all 0.2s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
&.faq-inactive {
opacity: 0.6;
background: #f9fafb;
}
.faq-card-header {
padding: 20px 20px 0 20px;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
color: #1a1a1a;
line-height: 1.4;
flex: 1;
}
.faq-card-actions {
display: flex;
gap: 8px;
flex-shrink: 0;
button {
padding: 6px 12px;
border: none;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
&.faq-edit-btn {
background: #f0f9ff;
color: #0369a1;
&:hover {
background: #e0f2fe;
}
}
&.faq-delete-btn {
background: #fef2f2;
color: #dc2626;
&:hover {
background: #fee2e2;
}
}
&.faq-status-btn {
&.faq-deactivate {
background: #fef3c7;
color: #d97706;
&:hover {
background: #fde68a;
}
}
&.faq-activate {
background: #dcfce7;
color: #16a34a;
&:hover {
background: #bbf7d0;
}
}
}
}
}
}
.faq-card-content {
padding: 16px 20px 20px 20px;
p {
margin: 0 0 12px 0;
font-size: 14px;
line-height: 1.5;
&:last-child {
margin-bottom: 0;
}
strong {
color: #374151;
}
}
.faq-created-date {
color: #6b7280;
font-size: 12px;
}
.faq-status {
font-size: 12px;
font-weight: 500;
&.faq-active {
color: #16a34a;
}
&.faq-inactive {
color: #dc2626;
}
}
}
}
// Placeholder Content
.faq-placeholder-content {
background: white;
border-radius: 12px;
padding: 60px 40px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.faq-placeholder-icon {
font-size: 48px;
margin-bottom: 16px;
}
h2 {
margin: 0 0 8px 0;
font-size: 20px;
font-weight: 600;
color: #1a1a1a;
}
p {
margin: 0;
color: #6b7280;
font-size: 14px;
}
}
// Modal Form Styles
.faq-form {
.faq-form-group {
margin-bottom: 20px;
label {
display: block;
margin-bottom: 6px;
font-weight: 500;
color: #374151;
font-size: 14px;
}
input, textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
transition: all 0.2s ease;
box-sizing: border-box;
&:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
&.error {
border-color: #dc2626;
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1);
}
&::placeholder {
color: #9ca3af;
}
}
textarea {
resize: vertical;
min-height: 80px;
}
.error-message {
display: block;
margin-top: 4px;
font-size: 12px;
color: #dc2626;
}
}
.faq-form-actions {
display: flex;
gap: 12px;
margin-top: 24px;
button {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
&.faq-save-btn {
background: #667eea;
color: white;
&:hover {
background: #5a67d8;
}
}
&.faq-cancel-btn {
background: #f3f4f6;
color: #374151;
&:hover {
background: #e5e7eb;
}
}
&.faq-clear-btn {
background: #fef2f2;
color: #dc2626;
&:hover {
background: #fee2e2;
}
}
}
}
}
// Responsive Design
@media (max-width: 768px) {
.faq-form-master {
padding: 16px;
}
.faq-form-header {
flex-direction: column;
gap: 16px;
text-align: center;
.faq-header-left {
justify-content: center;
}
}
.faq-tab-navigation {
flex-direction: column;
gap: 16px;
.faq-tab-buttons {
justify-content: center;
}
}
.faq-grid {
grid-template-columns: 1fr;
}
.faq-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
.faq-card-actions {
width: 100%;
justify-content: flex-end;
}
}
}

View File

@ -0,0 +1,272 @@
.FooterFormMaster {
width: 100%;
max-width: 1400px;
margin: 0 auto;
padding: 0;
font-family: "NeueMontreal", -apple-system, BlinkMacSystemFont, sans-serif;
background: #ffffff;
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
.footer-header {
background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%);
padding: 2.5rem 2rem;
border-radius: 20px 20px 0 0;
color: white;
margin-bottom: 0;
box-shadow: 0 4px 20px rgba(45, 55, 72, 0.3);
h1 {
margin: 0;
font-size: 2rem;
font-weight: 700;
letter-spacing: -0.5px;
}
}
.footer-form {
padding: 2.5rem;
.input-group {
margin-bottom: 1.5rem;
label {
display: block;
margin-bottom: 0.75rem;
font-weight: 600;
color: #2d3748;
font-size: 0.95rem;
}
input {
width: 100%;
padding: 0.875rem 1rem;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 0.95rem;
transition: all 0.3s ease;
background: #f8fafc;
font-family: inherit;
&:focus {
outline: none;
border-color: #4a5568;
background: #ffffff;
box-shadow: 0 0 0 4px rgba(74, 85, 104, 0.1);
}
&::placeholder {
color: #a0aec0;
}
}
}
.form-buttons {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 2px solid #e2e8f0;
button {
flex: 1;
padding: 0.875rem 1.5rem;
border: none;
border-radius: 12px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
&.save-btn {
background: linear-gradient(135deg, #2d3748, #1a202c);
color: white;
box-shadow: 0 4px 12px rgba(45, 55, 72, 0.2);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(45, 55, 72, 0.3);
}
}
&.clear-btn {
background: #edf2f7;
color: #4a5568;
border: 2px solid #cbd5e1;
&:hover {
background: #e2e8f0;
}
}
}
}
.saved-numbers {
margin-bottom: 2.5rem;
h3 {
margin: 0 0 1.5rem 0;
font-size: 1.5rem;
font-weight: 700;
color: #2d3748;
}
.no-data {
text-align: center;
padding: 3rem 2rem;
background: linear-gradient(135deg, #f8fafc, #edf2f7);
border-radius: 12px;
border: 2px dashed #cbd5e1;
p {
margin: 0;
color: #718096;
}
}
.numbers-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
@media (max-width: 768px) {
grid-template-columns: 1fr;
}
.number-card {
background: linear-gradient(135deg, #ffffff, #f8fafc);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s ease;
&.active {
border-color: #48bb78;
}
&.inactive {
border-color: #f6ad55;
opacity: 0.85;
}
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12);
}
.card-content {
padding: 1.5rem;
.number-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
img {
flex-shrink: 0;
}
h4 {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
color: #2d3748;
flex: 1;
word-break: break-all;
}
.status-badge {
padding: 0.375rem 0.875rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
flex-shrink: 0;
&.active {
background: #c6f6d5;
color: #22543d;
}
&.inactive {
background: #fed7d7;
color: #742a2a;
}
}
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 2px solid #e2e8f0;
flex-wrap: wrap;
gap: 0.75rem;
.card-badge {
font-size: 0.8rem;
color: #718096;
font-weight: 500;
}
.number-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 8px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
&.edit-btn {
background: #4299e1;
color: white;
&:hover {
background: #3182ce;
transform: translateY(-1px);
}
}
&.delete-btn {
background: #fc8181;
color: white;
&:hover {
background: #f56565;
transform: translateY(-1px);
}
}
&.toggle-btn {
background: #f6ad55;
color: white;
&:hover {
background: #ed8936;
transform: translateY(-1px);
}
}
}
}
}
}
}
}
}
.social-media-section {
padding-top: 2.5rem;
border-top: 3px solid #e2e8f0;
margin-top: 2.5rem;
}
}
}

View File

@ -0,0 +1,921 @@
// HeroSectionForm Styles - Clean and Modern Design
.hero-section-form-master {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
// Header Section
.hero-section-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 40px 40px 32px;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
.hero-section-header-left {
display: flex;
align-items: center;
gap: 20px;
.hero-section-header-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.hero-section-header-content {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.03em;
line-height: 1.2;
font-family: "Poppins", sans-serif;
}
p {
margin: 0;
font-size: 0.9375rem;
color: #64748b;
font-weight: 400;
letter-spacing: -0.01em;
font-family: "Poppins", sans-serif;
}
}
}
.hero-section-create-btn {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 16px 32px;
border-radius: 12px;
font-weight: 600;
font-size: 0.9375rem;
cursor: pointer;
font-family: "Poppins", sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
}
}
}
// Tab Navigation
.hero-section-tab-navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 40px;
border-bottom: 1px solid #e5e7eb;
background: #f8fafc;
.hero-section-tab-buttons {
display: flex;
gap: 16px;
.hero-section-tab-btn {
padding: 8px 16px;
border: none;
background: transparent;
color: #64748b;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.hero-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.hero-section-active) {
background: #f1f5f9;
color: #475569;
}
}
}
.hero-section-tab-controls {
display: flex;
align-items: center;
gap: 16px;
.hero-section-sort-dropdown {
select {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: white;
color: #374151;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
cursor: pointer;
&:focus {
outline: none;
border-color: #3b82f6;
}
}
}
.hero-section-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
.hero-section-view-btn {
padding: 8px 12px;
border: none;
background: white;
color: #64748b;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.hero-section-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.hero-section-active) {
background: #f8fafc;
}
}
}
}
}
// Apps Display Section
.hero-section-display {
padding: 32px;
.hero-section-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 16px;
}
}
}
// App Card Styles
.hero-section-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
&.hero-section-inactive {
opacity: 0.7;
border-color: #f6ad55;
.hero-section-status {
color: #991b1b;
}
}
.hero-section-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 1.125rem;
font-weight: 700;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
flex: 1;
margin-right: 16px;
}
.hero-section-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
button {
padding: 6px 12px;
border: none;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
&.hero-section-edit-btn {
background: #dbeafe;
color: #1e40af;
&:hover {
background: #bfdbfe;
transform: translateY(-1px);
}
}
&.hero-section-delete-btn {
background: #fee2e2 !important;
color: #991b1b !important;
border: 1px solid #fca5a5 !important;
&:hover {
background: #fecaca !important;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(153, 27, 27, 0.2) !important;
}
}
&.hero-section-status-btn {
&.hero-section-activate {
background: #dcfce7;
color: #166534;
&:hover {
background: #bbf7d0;
transform: translateY(-1px);
}
}
&.hero-section-deactivate {
background: #fef3c7;
color: #92400e;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
}
}
.hero-section-card-content {
p {
margin: 0 0 12px 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
strong {
color: #374151;
font-weight: 600;
}
&.hero-section-created-date {
font-size: 0.75rem;
color: #94a3b8;
margin-bottom: 8px;
}
&.hero-section-status {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
&.hero-section-active {
color: #166534;
}
&.hero-section-inactive {
color: #991b1b;
}
}
&:last-child {
margin-bottom: 0;
}
}
.hero-section-subsections {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #e5e7eb;
strong {
display: block;
margin-bottom: 12px;
color: #374151;
font-size: 0.875rem;
}
.hero-section-subsection-item {
background: #f8fafc;
padding: 12px;
border-radius: 8px;
margin-bottom: 8px;
display: flex;
gap: 12px;
align-items: flex-start;
&:last-child {
margin-bottom: 0;
}
.hero-section-thumb {
flex-shrink: 0;
img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 6px;
border: 2px solid #e2e8f0;
}
}
.hero-section-texts {
flex: 1;
.hero-section-heading {
font-weight: 600;
color: #1e293b;
font-size: 0.875rem;
margin-bottom: 4px;
}
.hero-section-subheading {
color: #64748b;
font-size: 0.75rem;
}
}
}
}
}
}
// Placeholder Content
.hero-section-placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 32px;
text-align: center;
background: transparent;
.hero-section-placeholder-icon {
width: 120px;
height: 120px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 3.5rem;
margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h2 {
margin: 0 0 16px 0;
font-size: 1.75rem;
font-weight: 600;
color: #1e293b;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
font-family: "Poppins", sans-serif;
line-height: 1.5;
max-width: 400px;
}
}
// Modal Form Styles
.hero-section-form {
padding: 20px 0;
.hero-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input,
textarea {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
&.error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
}
textarea {
resize: vertical;
min-height: 100px !important;
}
.hero-section-file-input {
cursor: pointer;
background: white;
&::-webkit-file-upload-button {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
margin-right: 12px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
}
}
}
.hero-section-image-preview {
margin-top: 12px;
padding: 12px;
background: #f8fafc;
border-radius: 8px;
border: 2px solid #e2e8f0;
img {
max-width: 200px;
max-height: 150px;
border-radius: 6px;
object-fit: cover;
}
}
.error-message {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 4px;
font-family: "NeueMontreal", sans-serif;
}
}
.hero-section-subsections-container {
margin: 32px 0;
.hero-section-subsections-header {
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 2px solid #e5e7eb;
h3 {
margin: 0 0 8px 0;
font-size: 1.125rem;
font-weight: 700;
color: #1e293b;
font-family: "Poppins", sans-serif;
}
p {
margin: 0;
font-size: 0.875rem;
color: #64748b;
font-family: "NeueMontreal", sans-serif;
}
}
.hero-section-subsection {
background: linear-gradient(135deg, #f8fafc 0%, #edf2f7 100%);
padding: 20px;
border-radius: 12px;
border: 2px solid #e2e8f0;
margin-bottom: 16px;
transition: all 0.3s ease;
&:hover {
border-color: #cbd5e1;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.hero-section-subsection-number {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #e2e8f0;
font-weight: 700;
color: #1e293b;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
font-family: "Poppins", sans-serif;
.hero-section-remove-subsection-btn {
background: #fee2e2;
color: #991b1b;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
&:hover {
background: #fecaca;
transform: scale(1.1);
}
}
}
}
}
}
}
.hero-section-add-subsection-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 8px;
justify-content: center;
width: 100%;
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
font-family: "NeueMontreal", sans-serif;
&:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(16, 185, 129, 0.4);
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
.hero-section-form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 32px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.hero-section-save-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
}
&.hero-section-cancel-btn {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
&:hover {
background: #e5e7eb;
transform: translateY(-1px);
}
}
&.hero-section-clear-btn {
background: #fef3c7;
color: #92400e;
border: 1px solid #fbbf24;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
// Global modal overrides for hero section form
.ant-modal {
.hero-section-form {
padding: 20px 0;
.hero-section-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input,
textarea {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
}
textarea {
resize: vertical;
min-height: 100px !important;
}
}
}
}
// Popconfirm styling for hero section
.ant-popover.ant-popconfirm {
.ant-popover-inner {
border-radius: 12px !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12) !important;
border: 1px solid #e5e7eb !important;
overflow: hidden;
}
.ant-popover-inner-content {
padding: 0;
}
.ant-popconfirm-inner-content {
padding: 20px;
.ant-popconfirm-message {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
.ant-popconfirm-message-icon {
margin-top: 2px;
.anticon {
color: #f59e0b;
font-size: 18px;
}
}
.ant-popconfirm-message-text {
flex: 1;
.ant-popconfirm-title {
font-size: 16px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
font-family: "Poppins", sans-serif;
}
.ant-popconfirm-description {
font-size: 14px;
color: #64748b;
line-height: 1.4;
font-family: "NeueMontreal", sans-serif;
}
}
}
.ant-popconfirm-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 16px;
.ant-btn {
border-radius: 8px;
font-weight: 600;
font-size: 13px;
height: 36px;
padding: 0 16px;
font-family: "NeueMontreal", sans-serif;
transition: all 0.2s ease;
&.ant-btn-primary {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border: none;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
&:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}
}
&.ant-btn-default {
background: #f8fafc;
border: 1px solid #e2e8f0;
color: #475569;
&:hover {
background: #f1f5f9;
border-color: #cbd5e1;
color: #334155;
transform: translateY(-1px);
}
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
}
}
}
}
.ant-popover-arrow {
&::before {
background: white;
border: 1px solid #e5e7eb;
}
}
}
// Custom styling for hero section popconfirm specifically
.hero-section-card .ant-popover.ant-popconfirm {
.ant-popconfirm-inner-content {
.ant-popconfirm-message {
.ant-popconfirm-message-text {
.ant-popconfirm-title {
color: #991b1b;
}
}
}
}
}
// Responsive Design
@media (max-width: 768px) {
.hero-section-form-master {
.hero-section-display {
padding: 16px;
h2 {
font-size: 1.5rem;
margin-bottom: 24px;
}
}
.hero-section-card {
padding: 16px;
.hero-section-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
h3 {
margin-right: 0;
margin-bottom: 0;
}
.hero-section-card-actions {
width: 100%;
justify-content: flex-start;
}
}
}
.hero-section-placeholder-content {
margin: 20px 0;
padding: 60px 16px;
.hero-section-placeholder-icon {
width: 100px;
height: 100px;
font-size: 3rem;
margin-bottom: 32px;
}
h2 {
font-size: 1.5rem;
}
p {
font-size: 0.875rem;
max-width: 300px;
}
}
}
}

View File

@ -0,0 +1,612 @@
// OfferingsForm Styles - Clean and Modern Design
.OfferingsFormMaster {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
// Header Section
.ecosystem-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 40px 40px 32px;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
.ecosystem-header-left {
display: flex;
align-items: center;
gap: 20px;
.ecosystem-header-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.ecosystem-header-content {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.03em;
line-height: 1.2;
font-family: "Poppins", sans-serif;
}
p {
margin: 0;
font-size: 0.9375rem;
color: #64748b;
font-weight: 400;
letter-spacing: -0.01em;
font-family: "Poppins", sans-serif;
}
}
}
.ecosystem-create-btn {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 16px 32px;
border-radius: 12px;
font-weight: 600;
font-size: 0.9375rem;
cursor: pointer;
font-family: "Poppins", sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
}
}
}
// Tab Navigation
.ecosystem-tab-navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 40px;
border-bottom: 1px solid #e5e7eb;
background: #f8fafc;
.ecosystem-tab-buttons {
display: flex;
gap: 16px;
.ecosystem-tab-btn {
padding: 8px 16px;
border: none;
background: transparent;
color: #64748b;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.ecosystem-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.ecosystem-active) {
background: #f1f5f9;
color: #475569;
}
}
}
.ecosystem-tab-controls {
display: flex;
align-items: center;
gap: 16px;
.ecosystem-sort-dropdown {
select {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: white;
color: #374151;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
cursor: pointer;
&:focus {
outline: none;
border-color: #3b82f6;
}
}
}
.ecosystem-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
.ecosystem-view-btn {
padding: 8px 12px;
border: none;
background: white;
color: #64748b;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.ecosystem-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.ecosystem-active) {
background: #f8fafc;
}
}
}
}
}
// Apps Display Section
.ecosystem-display {
padding: 32px;
.ecosystem-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 16px;
}
}
}
// App Card Styles
.ecosystem-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
&.ecosystem-inactive {
opacity: 0.7;
border-color: #f6ad55;
.ecosystem-status {
color: #991b1b;
}
}
.ecosystem-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 1.125rem;
font-weight: 700;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
flex: 1;
margin-right: 16px;
}
.ecosystem-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
button {
padding: 6px 12px;
border: none;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
&.ecosystem-edit-btn {
background: #dbeafe;
color: #1e40af;
&:hover {
background: #bfdbfe;
transform: translateY(-1px);
}
}
&.ecosystem-delete-btn {
background: #fee2e2;
color: #991b1b;
&:hover {
background: #fecaca;
transform: translateY(-1px);
}
}
&.ecosystem-status-btn {
&.ecosystem-activate {
background: #dcfce7;
color: #166534;
&:hover {
background: #bbf7d0;
transform: translateY(-1px);
}
}
&.ecosystem-deactivate {
background: #fef3c7;
color: #92400e;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
}
}
.ecosystem-card-content {
.ecosystem-poster-image {
margin-bottom: 16px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
img {
width: 100%;
height: 200px;
object-fit: cover;
display: block;
}
}
p {
margin: 0 0 12px 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
strong {
color: #374151;
font-weight: 600;
}
&.ecosystem-created-date {
font-size: 0.75rem;
color: #94a3b8;
margin-bottom: 8px;
}
&.ecosystem-status {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
&.ecosystem-active {
color: #166534;
}
&.ecosystem-inactive {
color: #991b1b;
}
}
&:last-child {
margin-bottom: 0;
}
}
}
}
// Placeholder Content
.ecosystem-placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 32px;
text-align: center;
background: transparent;
.ecosystem-placeholder-icon {
width: 120px;
height: 120px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 3.5rem;
margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h2 {
margin: 0 0 16px 0;
font-size: 1.75rem;
font-weight: 600;
color: #1e293b;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
font-family: "Poppins", sans-serif;
line-height: 1.5;
max-width: 400px;
}
}
// Modal Form Styles
.ecosystem-form {
padding: 20px 0;
.poster-section-title {
font-size: 1.125rem;
font-weight: 600;
color: #1e293b;
margin: 24px 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
font-family: "Poppins", sans-serif;
}
.ecosystem-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input,
textarea {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
&.error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
}
textarea {
resize: vertical;
min-height: 100px !important;
}
.error-message {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 4px;
font-family: "NeueMontreal", sans-serif;
}
}
}
}
// Global modal overrides for ecosystem form
.ant-modal {
.ecosystem-form {
padding: 20px 0;
.poster-section-title {
font-size: 1.125rem;
font-weight: 600;
color: #1e293b;
margin: 24px 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
font-family: "Poppins", sans-serif;
}
.ecosystem-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input,
textarea {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
&.error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
}
textarea {
resize: vertical;
min-height: 100px !important;
}
.error-message {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 4px;
font-family: "NeueMontreal", sans-serif;
}
}
}
}
// Responsive Design
@media (max-width: 768px) {
.OfferingsFormMaster {
.ecosystem-form-header {
flex-direction: column;
align-items: flex-start;
gap: 20px;
padding: 24px;
.ecosystem-header-left {
.ecosystem-header-content {
h2 {
font-size: 1.5rem;
}
}
}
}
.ecosystem-tab-navigation {
flex-direction: column;
align-items: flex-start;
gap: 16px;
padding: 16px 24px;
.ecosystem-tab-controls {
width: 100%;
justify-content: space-between;
}
}
.ecosystem-display {
padding: 16px;
}
.ecosystem-card {
padding: 16px;
.ecosystem-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
h3 {
margin-right: 0;
margin-bottom: 0;
}
.ecosystem-card-actions {
width: 100%;
justify-content: flex-start;
}
}
}
.ecosystem-placeholder-content {
padding: 60px 16px;
.ecosystem-placeholder-icon {
width: 100px;
height: 100px;
font-size: 3rem;
margin-bottom: 32px;
}
h2 {
font-size: 1.5rem;
}
p {
font-size: 0.875rem;
max-width: 300px;
}
}
}
}

View File

@ -0,0 +1,203 @@
.seo-form-master {
background: #f8f9fa;
min-height: 100vh;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
overflow: auto;
margin-bottom: 2rem;
.seo-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 10px 16px;
background: white;
border-bottom: 1px solid #0000001a;
width: 100%;
.seo-form-header-content {
h2 {
color: #2c3e50;
font-size: 24px;
font-weight: 600;
}
p {
margin: 0;
color: #6c757d;
font-size: 13px;
}
}
}
.seo-form-layout {
display: flex;
gap: 30px;
align-items: flex-start;
justify-content: center;
.seo-form-left {
flex: 1;
max-width: 800px;
.seo-form-container {
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
h3 {
margin: 0 0 25px 0;
color: #2c3e50;
font-size: 18px;
font-weight: 500;
border-bottom: 2px solid #e9ecef;
padding-bottom: 10px;
}
.seo-form-field {
margin-bottom: 20px;
label {
display: block;
margin-bottom: 8px;
color: #495057;
font-weight: 500;
font-size: 14px;
}
input,
select,
textarea {
width: 100%;
padding: 12px 16px;
border: 1px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
transition: all 0.3s ease;
background: #fff;
&:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
&::placeholder {
color: #adb5bd;
}
}
textarea {
resize: vertical;
min-height: 100px;
}
select {
cursor: pointer;
text-transform: capitalize !important;
}
}
.seo-form-actions {
display: flex;
gap: 15px;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e9ecef;
justify-content: center;
.seo-form-btn-primary,
.seo-form-btn-secondary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 10px 24px;
border-radius: 8px;
font-size: 14px;
font-weight: 400;
cursor: pointer;
font-family: "NeueMontreal";
transition: all 0.3s ease;
}
.seo-form-btn-secondary {
background: #6c757d;
color: white;
}
}
}
}
.seo-form-right {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
.seo-placeholder-content {
text-align: center;
padding: 60px 40px;
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
max-width: 400px;
.seo-placeholder-icon {
font-size: 64px;
color: #667eea;
margin-bottom: 20px;
}
h2 {
margin: 0 0 15px 0;
color: #2c3e50;
font-size: 24px;
font-weight: 600;
}
p {
margin: 0;
color: #6c757d;
font-size: 16px;
line-height: 1.5;
}
}
}
}
}
@media (max-width: 768px) {
.seo-form-master {
.seo-form-header {
flex-direction: column;
gap: 20px;
text-align: center;
}
.seo-form-layout {
gap: 20px;
.seo-form-left {
max-width: 95%;
}
.seo-form-right {
.seo-placeholder-content {
padding: 40px 20px;
}
}
}
.seo-form-actions {
flex-direction: column;
button {
width: 100%;
}
}
}
}

View File

@ -0,0 +1,777 @@
// TrendingAppsForm Styles - Clean and Modern Design
.trending-apps-form-master {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
font-family:
"NeueMontreal",
-apple-system,
BlinkMacSystemFont,
sans-serif;
// Header Section
.trending-apps-form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0;
padding: 40px 40px 32px;
border-bottom: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
.trending-apps-header-left {
display: flex;
align-items: center;
gap: 20px;
.trending-apps-header-icon {
width: 50px;
height: 50px;
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 50%, #c084fc 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.trending-apps-header-content {
display: flex;
flex-direction: column;
align-items: flex-start;
h2 {
font-size: 2rem;
font-weight: 700;
color: #1e293b;
letter-spacing: -0.03em;
line-height: 1.2;
font-family: "Poppins", sans-serif;
}
p {
margin: 0;
font-size: 0.9375rem;
color: #64748b;
font-weight: 400;
letter-spacing: -0.01em;
font-family: "Poppins", sans-serif;
}
}
}
.trending-apps-create-btn {
background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%);
color: white;
border: none;
padding: 16px 32px;
border-radius: 12px;
font-weight: 600;
font-size: 0.9375rem;
cursor: pointer;
font-family: "Poppins", sans-serif;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 10px;
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
&:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(139, 92, 246, 0.4);
}
}
}
// Tab Navigation
.trending-apps-tab-navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 40px;
border-bottom: 1px solid #e5e7eb;
background: #f8fafc;
.trending-apps-tab-buttons {
display: flex;
gap: 16px;
.trending-apps-tab-btn {
padding: 8px 16px;
border: none;
background: transparent;
color: #64748b;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.trending-apps-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.trending-apps-active) {
background: #f1f5f9;
color: #475569;
}
}
}
.trending-apps-tab-controls {
display: flex;
align-items: center;
gap: 16px;
.trending-apps-sort-dropdown {
select {
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: white;
color: #374151;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
cursor: pointer;
&:focus {
outline: none;
border-color: #3b82f6;
}
}
}
.trending-apps-view-toggle {
display: flex;
border: 1px solid #d1d5db;
border-radius: 6px;
overflow: hidden;
.trending-apps-view-btn {
padding: 8px 12px;
border: none;
background: white;
color: #64748b;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.trending-apps-active {
background: #e2e8f0;
color: #1e293b;
}
&:hover:not(.trending-apps-active) {
background: #f8fafc;
}
}
}
}
}
// Apps Display Section
.trending-apps-display {
padding: 32px;
.trending-apps-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 24px;
@media (max-width: 768px) {
grid-template-columns: 1fr;
gap: 16px;
}
}
}
// App Card Styles
.trending-apps-card {
background: white;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
border: 2px solid transparent;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
border-color: #e2e8f0;
}
&.trending-apps-inactive {
opacity: 0.7;
border-color: #f6ad55;
.trending-apps-status {
color: #991b1b;
}
}
.trending-apps-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
h3 {
margin: 0;
font-size: 1.125rem;
font-weight: 700;
color: #1e293b;
line-height: 1.4;
letter-spacing: -0.02em;
font-family: "Poppins", sans-serif;
flex: 1;
margin-right: 16px;
}
.trending-apps-card-actions {
display: flex;
gap: 8px;
flex-wrap: wrap;
button {
padding: 6px 12px;
border: none;
border-radius: 8px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
text-transform: uppercase;
letter-spacing: 0.5px;
&.trending-apps-edit-btn {
background: #dbeafe;
color: #1e40af;
&:hover {
background: #bfdbfe;
transform: translateY(-1px);
}
}
&.trending-apps-delete-btn {
background: #fee2e2 !important;
color: #991b1b !important;
border: 1px solid #fca5a5 !important;
&:hover {
background: #fecaca !important;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgba(153, 27, 27, 0.2) !important;
}
}
&.trending-apps-status-btn {
&.trending-apps-activate {
background: #dcfce7;
color: #166534;
&:hover {
background: #bbf7d0;
transform: translateY(-1px);
}
}
&.trending-apps-deactivate {
background: #fef3c7;
color: #92400e;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
}
}
.trending-apps-card-content {
p {
margin: 0 0 12px 0;
font-size: 0.875rem;
color: #64748b;
line-height: 1.5;
font-family: "NeueMontreal", sans-serif;
strong {
color: #374151;
font-weight: 600;
}
&.trending-apps-created-date {
font-size: 0.75rem;
color: #94a3b8;
margin-bottom: 8px;
}
&.trending-apps-status {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
&.trending-apps-active {
color: #166534;
}
&.trending-apps-inactive {
color: #991b1b;
}
}
&:last-child {
margin-bottom: 0;
}
}
.trending-apps-industries-display {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #e5e7eb;
strong {
display: block;
margin-bottom: 12px;
color: #374151;
font-size: 0.875rem;
}
.trending-apps-industry-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
.trending-apps-industry-tag {
background: #f1f5f9;
color: #475569;
padding: 4px 8px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 500;
border: 1px solid #e2e8f0;
}
}
}
}
}
// Placeholder Content
.trending-apps-placeholder-content {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80px 32px;
text-align: center;
background: transparent;
.trending-apps-placeholder-icon {
width: 120px;
height: 120px;
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
color: #94a3b8;
font-size: 3.5rem;
margin-bottom: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
h2 {
margin: 0 0 16px 0;
font-size: 1.75rem;
font-weight: 600;
color: #1e293b;
font-family: "Poppins", sans-serif;
letter-spacing: -0.02em;
}
p {
margin: 0;
color: #64748b;
font-size: 1rem;
font-family: "Poppins", sans-serif;
line-height: 1.5;
max-width: 400px;
}
}
// Modal Form Styles
.trending-apps-form {
padding: 20px 0;
.trending-apps-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
&.error {
border-color: #ef4444 !important;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
}
}
.trending-apps-industries-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 12px;
padding: 16px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
&.error {
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
}
.trending-apps-checkbox-item {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 8px;
border-radius: 6px;
transition: all 0.2s ease;
&:hover {
background: #e2e8f0;
}
input[type="checkbox"] {
width: auto !important;
margin: 0;
cursor: pointer;
}
span {
font-size: 0.875rem;
color: #374151;
font-family: "NeueMontreal", sans-serif;
}
}
}
.error-message {
display: block;
color: #ef4444;
font-size: 0.75rem;
margin-top: 4px;
font-family: "NeueMontreal", sans-serif;
}
}
}
}
.trending-apps-form-actions {
display: flex;
gap: 12px;
justify-content: flex-end;
margin-top: 32px;
padding-top: 20px;
border-top: 1px solid #e5e7eb;
button {
padding: 10px 20px;
border: none;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 400;
letter-spacing: 0.3px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif;
&.trending-apps-save-btn {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.4);
}
}
&.trending-apps-cancel-btn {
background: #f3f4f6;
color: #374151;
border: 1px solid #d1d5db;
&:hover {
background: #e5e7eb;
transform: translateY(-1px);
}
}
&.trending-apps-clear-btn {
background: #fef3c7;
color: #92400e;
border: 1px solid #fbbf24;
&:hover {
background: #fde68a;
transform: translateY(-1px);
}
}
}
}
// Global modal overrides for trending apps form
.ant-modal {
.trending-apps-form {
padding: 20px 0;
.trending-apps-form-group {
margin-bottom: 24px;
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #374151 !important;
font-size: 0.875rem;
font-family: "NeueMontreal", sans-serif;
}
input {
width: 100% !important;
padding: 12px !important;
border: 1px solid #d1d5db !important;
border-radius: 6px !important;
font-size: 0.875rem !important;
transition: all 0.2s ease;
font-family: "NeueMontreal", sans-serif !important;
box-sizing: border-box;
&:focus {
outline: none !important;
border-color: #3b82f6 !important;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
&::placeholder {
color: #9ca3af !important;
}
}
}
}
}
// Popconfirm styling for trending apps
.ant-popover.ant-popconfirm {
.ant-popover-inner {
border-radius: 12px !important;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12) !important;
border: 1px solid #e5e7eb !important;
overflow: hidden;
}
.ant-popover-inner-content {
padding: 0;
}
.ant-popconfirm-inner-content {
padding: 20px;
.ant-popconfirm-message {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
.ant-popconfirm-message-icon {
margin-top: 2px;
.anticon {
color: #f59e0b;
font-size: 18px;
}
}
.ant-popconfirm-message-text {
flex: 1;
.ant-popconfirm-title {
font-size: 16px;
font-weight: 600;
color: #1e293b;
margin-bottom: 4px;
font-family: "Poppins", sans-serif;
}
.ant-popconfirm-description {
font-size: 14px;
color: #64748b;
line-height: 1.4;
font-family: "NeueMontreal", sans-serif;
}
}
}
.ant-popconfirm-buttons {
display: flex;
gap: 8px;
justify-content: flex-end;
margin-top: 16px;
.ant-btn {
border-radius: 8px;
font-weight: 600;
font-size: 13px;
height: 36px;
padding: 0 16px;
font-family: "NeueMontreal", sans-serif;
transition: all 0.2s ease;
&.ant-btn-primary {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
border: none;
box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
}
&:focus {
box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.2);
}
}
&.ant-btn-default {
background: #f8fafc;
border: 1px solid #e2e8f0;
color: #475569;
&:hover {
background: #f1f5f9;
border-color: #cbd5e1;
color: #334155;
transform: translateY(-1px);
}
&:focus {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
}
}
}
}
.ant-popover-arrow {
&::before {
background: white;
border: 1px solid #e5e7eb;
}
}
}
// Custom styling for trending apps popconfirm specifically
.trending-apps-card .ant-popover.ant-popconfirm {
.ant-popconfirm-inner-content {
.ant-popconfirm-message {
.ant-popconfirm-message-text {
.ant-popconfirm-title {
color: #991b1b;
}
}
}
}
}
// Responsive Design
@media (max-width: 768px) {
.trending-apps-form-master {
.trending-apps-display {
padding: 16px;
}
.trending-apps-card {
padding: 16px;
.trending-apps-card-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
h3 {
margin-right: 0;
margin-bottom: 0;
}
.trending-apps-card-actions {
width: 100%;
justify-content: flex-start;
}
}
}
.trending-apps-placeholder-content {
margin: 20px 0;
padding: 60px 16px;
.trending-apps-placeholder-icon {
width: 100px;
height: 100px;
font-size: 3rem;
margin-bottom: 32px;
}
h2 {
font-size: 1.5rem;
}
p {
font-size: 0.875rem;
max-width: 300px;
}
}
}
}

25
src/App.css Normal file
View File

@ -0,0 +1,25 @@
@font-face {
font-family: "Wasted-Vindey";
src: url("./fonts/wasted-vindey-cdnfonts/wasted-vindey.ttf") format("truetype");
font-weight: 400;
font-style: normal;
}
/* Ensure editor content is actually interactable */
#pozo-editor,
#pozo-editor * {
pointer-events: auto !important;
user-select: text !important;
}
/* app.css */
.dark #pozo-editor { background: #0b1220; color: #e5e7eb; }
.dark input, .dark textarea { background: #0f172a; color: #e5e7eb; border-color: #1f2937; }
/* If you have overlays/spinners, ensure they don't block input */
.overlay, .backdrop, .spinner {
pointer-events: none;
}
#pozo-editor, #pozo-editor * { pointer-events: auto !important; user-select: text !important; }
.overlay, .backdrop, .spinner { pointer-events: none; }
.preview-content img { display: block; max-width: 100%; height: auto; }

315
src/App.jsx Normal file
View File

@ -0,0 +1,315 @@
import { Routes, Route, useNavigate } from "react-router-dom";
import { lazy, Suspense, useEffect } from "react";
import { useDispatch } from "react-redux";
import { checkSession } from "./features/signInPage/signInPage.js";
import { clearSession, getSession } from "./Services/others.js";
import { PostToken } from "./features/publicHome/publicHome.js";
import LazyLoader from "./components/LazyLoader.jsx";
// Lazy load components
const AppPage = lazy(() => import('./Pages/appPage/AppPage.jsx'));
const CompanyForm = lazy(() => import('./Pages/company/companyForm.jsx'));
const CompanyList = lazy(() => import('./Pages/company/companyList.jsx'));
const AdminTax = lazy(() => import('./Pages/adminTax/adminTaxForm.jsx'));
const BranchForm = lazy(() => import('./Pages/branch/branchForm.jsx'));
const BranchList = lazy(() => import('./Pages/branch/branchList.jsx'));
const ApplicationForm = lazy(() => import('./Pages/application/applicationForm.jsx'));
const ApplicationList = lazy(() => import('./Pages/application/applicationList.jsx'));
const CommonMaster = lazy(() => import('./Pages/commonMaster/CommonMaster.jsx'));
const ConfigTypeForm = lazy(() => import('./Pages/configType/configtypeForm.jsx'));
const ApplicationImageForm = lazy(() => import('./Pages/applicationImage/applicationImageForm.jsx'));
const AppPriceFeatureMapping = lazy(() => import('./Pages/appPriceFeatureMapping/appPriceFeatureMappingList.jsx'));
const AppPriceFeatureMappingForm = lazy(() => import('./Pages/appPriceFeatureMapping/appPriceFeatureMappingForm.jsx'));
const AppMenu = lazy(() => import('./Pages/appMenu/appMenuList.jsx'));
const AppMenuForm = lazy(() => import('./Pages/appMenu/appMenuForm.jsx'));
const Pricing = lazy(() => import('./Pages/pricingType/pricingTypeList.jsx'));
const PricingForm = lazy(() => import('./Pages/pricingType/pricingTypeForm.jsx'));
const CarouselForm = lazy(() => import('./Pages/carousel/carouselForm.jsx'));
const FeatureList = lazy(() => import('./Pages/feature/featureList.jsx'));
const FeatureForm = lazy(() => import('./Pages/feature/featureForm.jsx'));
const UserForm = lazy(() => import('./Pages/user/userForm.jsx'));
const UserList = lazy(() => import('./Pages/user/userList.jsx'));
const LandignPageLayout = lazy(() => import('./Pages/LandingPage/landingPageLayout.jsx'));
const Home = lazy(() => import('./Pages/homeApp/home.jsx'));
const PublicSignup = lazy(() => import('./Pages/PublicSignup/Signup.jsx'));
const PublicHome = lazy(() => import('./Pages/publichome/HomePage.jsx'));
const Signin = lazy(() => import('./Pages/publicsignin/Signin.jsx'));
const ModuleAccess = lazy(() => import('./Pages/ModuleAccess/ModuleAccess.jsx'));
const Application = lazy(() => import('./Pages/applications/application.jsx'));
const UserAccount = lazy(() => import('./Pages/userAccount/userAccount.jsx'));
const MessageTemplateForm = lazy(() => import('./Pages/MessageTemplate/MessageTemplateForm.jsx'));
const MessageTemplateList = lazy(() => import('./Pages/MessageTemplate/MessageTemplateList.jsx'));
const PaymentUPIDetailsForm = lazy(() => import('./Pages/PaymentUPIDetails/PaymentUPIDetailsForm.jsx'));
const PaymentUPIDetailsList = lazy(() => import('./Pages/PaymentUPIDetails/PaymentUPIDetailsList.jsx'));
const PdfPageCommon = lazy(() => import('./Pages/paymentpdfPage/PaymentPdfCommon.jsx'));
const PricingDetails = lazy(() => import('./Pages/pricingTypeRestaurant/Pricing.jsx'));
const InvoiceDetail = lazy(() => import('./Pages/InvoiceDetail/InvoiceDetail'));
const PaymentPage = lazy(() => import('./Pages/PaymentMobilePage/PaymentPage'));
const AppTemplate = lazy(() => import('./Pages/themeTemplate/AppTemplate.jsx'));
const Feature1 = lazy(() => import('./Pages/themeTemplate/TemplateComps/Features/Features1.jsx'));
const OverView2 = lazy(() => import('./Pages/themeTemplate/TemplateComps/OverVIews/OverView2.jsx'));
const Template = lazy(() => import('./Pages/themeTemplate/Template.jsx'));
const AllTemplates = lazy(() => import('./Pages/themeTemplate/AllTemplates.jsx'));
const AllTemplatesEdit = lazy(() => import('./Pages/themeTemplate/AllTemplateEdit.jsx'));
const PdfPage = lazy(() => import('./Pages/paymentpdfPage/paymentpdfpage.jsx'));
const PdfPageDownload = lazy(() => import('./Pages/PdfDoc/PdfDocDownload.jsx'));
const CurrencyForm = lazy(() => import('./Pages/currency/currencyForm.jsx'));
const ComingSoon = lazy(() => import('./Pages/comingSoon.jsx'));
const PaymentMethod = lazy(() => import('./Pages/ccavenueCustom/paymentMethod.jsx'));
const PaymentDetail = lazy(() => import('./Pages/ccavenueCustom/paymentDetails.jsx'));
const CustomPaymentGateway = lazy(() => import('./Pages/InvoiceDetail/customPaymentGateway'));
const PaymentHistory = lazy(() => import('./Pages/Payment/PaymentHistory.jsx'));
const FeatureAddon = lazy(() => import('./Pages/FeaturesPricing/FeatureAddon.jsx'));
const FeatureAddonForm = lazy(() => import('./Pages/FeaturesPricing/FeatureAddonForm.jsx'));
const FeatureInvoice = lazy(() => import('./Pages/FeaturesPricing/FeatureInvoice.jsx'));
const FeaturesPricing = lazy(() => import('./Pages/FeaturesPricing/featurespricing.jsx'));
const FeaturesPricingForm = lazy(() => import('./Pages/FeaturesPricing/FeaturePriceForm.jsx'));
const FailedPaymentHistory = lazy(() => import('./Pages/Payment/FailedPaymentHistory.jsx'));
const PaymentGatewayConfigForm = lazy(() => import('./Pages/paymentGatewayConfig/PaymentGatewayConfigForm.jsx').then(module => ({ default: module.PaymentGatewayConfigForm })));
const PaymentGatewayConfigList = lazy(() => import('./Pages/paymentGatewayConfig/PaymentGatewayConfigList.jsx'));
const PaymentDeviceConfigList = lazy(() => import('./Pages/paymentDeviceConfig/PaymentDeviceConfigList.jsx'));
const PaymentDeviceConfigForm = lazy(() => import('./Pages/paymentDeviceConfig/PaymentDeviceConfigForm.jsx'));
const DeviceAllocationlist = lazy(() => import('./Pages/kisokDevice/DeviceAllocationList.jsx'));
const DeviceAllocationform = lazy(() => import('./Pages/kisokDevice/DeviceAllocationForm.jsx'));
const DeviceInformation = lazy(() => import('./Pages/kisokDevice/DeviceInformation.jsx'));
const LoyaltysettingList = lazy(() => import('./Pages/LoyaltySetting/LoyaltysettingList.jsx'));
const LoyaltysettingsForm = lazy(() => import('./Pages/LoyaltySetting/LoyaltysettingsForm.jsx'));
const ActivationkeyGeneration = lazy(() => import('./Pages/ActivationkeyGeneration/ActivationkeyGeneration.jsx'));
const FaqShow = lazy(() => import('./Pages/themeTemplate/TemplateComps/Faq/FaqShow.jsx'));
const SignIn = lazy(() => import('./PozoApp/Components/SignIn.jsx'));
const PostEditorPage = lazy(() => import('./Pages/PostEditorPage.jsx'));
const subDirectory = import.meta.env.ENV_BASE_URL
const fieldData = [
{
'FieldName': 'Overview2Title',
'FieldValue': 'POS App For Restaurant with & without GST fastest e-bill & normal Billing + KOT and Stock Management',
'FieldAddData': ''
},
{
'FieldName': 'Overview2SubTitle',
'FieldValue': 'Restaurant POS Smart Ordering via QR Code and Send Bill Via through SMS or Whatsapp very fast within min.',
'FieldAddData': ''
},
{
'FieldName': 'Overview2ButtonText',
'FieldValue': 'Get Started',
'FieldAddData': ''
},
{
'FieldName': 'Overview2AppLinkText',
'FieldValue': 'Download POZO Apps From',
'FieldAddData': ''
},
{
'FieldName': 'Overview2PlayStoreLink',
'FieldValue': 'http://playstore.com',
'FieldAddData': ''
},
{
'FieldName': 'Overview2AppStoreLink',
'FieldValue': 'http://appstore.com',
'FieldAddData': ''
},
{
'FieldName': 'Overview2PosLink',
'FieldValue': 'http://posapp.com',
'FieldAddData': ''
},
{
'FieldName': 'Overview2BannerImage',
'FieldValue': 'https://saveme.live/paypre-image-api/upload?fileId=649bfe6906bfb6b6b99227ac.png',
'FieldAddData': ''
},
{
'FieldName': 'Overview2BannerVideo',
'FieldValue': 'http://saveme.live/paypre-image-api/upload?fileId=64f083a906bfb6b6b992341b.mp4',
'FieldAddData': ''
},
]
const App = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
// 🔑 Global token generation for direct URL access
useEffect(() => {
const ensureToken = async () => {
const existingToken = sessionStorage.getItem("auth");
if (!existingToken) {
try {
console.log("🔑 App: Generating global auth token...");
await dispatch(PostToken({
username: "1000000001",
password: "1234"
})).unwrap();
console.log("✅ App: Token generated successfully");
} catch (error) {
console.error("❌ App: Token generation failed:", error);
}
}
};
ensureToken();
sessionCheckFun();
}, [navigate, dispatch]);
const sessionCheckFun=async()=>{
const UserId = getSession('UserId')
const SessionId = getSession('SessionId')
const res = await dispatch(checkSession({UserId,SessionId})).unwrap();
if (res?.data?.statusCode === 1) {
if(res?.data?.response==='False'){
clearSession();
navigate(`${subDirectory}`);
}
}
}
return (
<Suspense fallback={<LazyLoader />}>
<Routes>
<Route path={`${subDirectory}`} element={<PublicHome />} />
<Route path={`${subDirectory}development`} element={<ComingSoon />} />
<Route path={`${subDirectory}PostEditorPage`} element={<PostEditorPage />} />
<Route path={`${subDirectory}:appName`} element={<Template />} />
<Route path='feature1' element={<Feature1 />} />
<Route path={`${subDirectory}pdf`} element={<PdfPage />} />
<Route path={`${subDirectory}payment-page-download`} element={<PdfPageDownload />} />
<Route path={`${subDirectory}public-signup`} element={<PublicSignup />} />
<Route path={`${subDirectory}signinOld`} element={<Signin />} />
<Route path={`${subDirectory}signin`} element={<SignIn />} />
<Route path={`${subDirectory}payment-pdf`} element={<PdfPageCommon />} />
<Route path={`${subDirectory}pricing-details`} element={<PricingDetails />} />
<Route path={`${subDirectory}invoice-detail`} element={<InvoiceDetail />} />
<Route path={`${subDirectory}payment-page`} element={<PaymentPage />} />
<Route path={`${subDirectory}overview2`} element={<OverView2 data={fieldData} />} />
<Route path={`${subDirectory}FeatureInvoice`} element={<FeatureInvoice />} />
{/* <Route path={`${subDirectory}customPayment`} element={<CustomPaymentGateway/>}/> */}
<Route path={`${subDirectory}Faq`} element={<FaqShow />} />
<Route path={`${subDirectory}landing-page`} element={<LandignPageLayout />}>
<Route path={'home'} element={<Home />} />
<Route path={'apps'} element={<Application />} />
<Route path={'user-account'} element={<UserAccount />} />
<Route path={'user-account-edit'} element={<UserAccount status='edit'/>} />
</Route>
<Route path={`${subDirectory}setting`} element={<AppPage />}>
<Route path='themes' element={<AppTemplate />} />
<Route path='payment-gateway-config' element={<PaymentGatewayConfigForm />} />
<Route path='payment-gateway-configlist' element={<PaymentGatewayConfigList />} />
<Route path='all-templates' element={<AllTemplates />} />
<Route path='all-templates/update' element={<AllTemplatesEdit />} />
<Route path='payment-device-config'>
<Route index element={<PaymentDeviceConfigList/>} />
<Route path='new' element={<PaymentDeviceConfigForm formType={"add"} />} />
<Route path='update' element={<PaymentDeviceConfigForm formType={"edit"} />} />
</Route>
<Route path='company-master'>
<Route index element={<CompanyList />} />
<Route path='new' element={<CompanyForm formType={"add"} />} />
<Route path='update' element={<CompanyForm formType={"edit"} />} />
</Route>
<Route path='branch-master'>
<Route index element={<BranchList />} />
<Route path='new' element={<BranchForm formType={"add"} />} />
<Route path='update' element={<BranchForm formType={"edit"} />} />
</Route>
<Route path='application-master'>
<Route index element={<ApplicationList />} />
<Route path='new' element={<ApplicationForm formType={"add"} />} />
<Route path='update' element={<ApplicationForm formType={"edit"} />} />
</Route>
<Route path='app-menu'>
<Route index element={<AppMenu />} />
<Route path='new' element={<AppMenuForm formType={"add"} />} />
<Route path='update' element={<AppMenuForm formType={"edit"} />} />
</Route>
<Route path='feature-mapping'>
<Route index element={<AppPriceFeatureMapping />} />
<Route path='new' element={<AppPriceFeatureMappingForm formType={"add"} />} />
<Route path='update' element={<AppPriceFeatureMappingForm formType={"edit"} />} />
</Route>
<Route path='device-allocation'>
<Route index element={<DeviceAllocationlist />} />
<Route path='new' element={<DeviceAllocationform formType={"add"} />} />
<Route path='edit' element={<DeviceAllocationform formType={"edit"} />} />
</Route>
<Route path='device-information' element={<DeviceInformation />} />
<Route path='feat-addon'>
<Route index element={<FeatureAddon />} />
</Route>
<Route path='feat-addon-form'>
<Route index element={<FeatureAddonForm />} />
</Route>
<Route path='pricing'>
<Route index element={<Pricing />} />
<Route path='new' element={<PricingForm formType={"add"} />} />
<Route path='update' element={<Pricing formType={"edit"} />} />
</Route>
<Route path='feature-master'>
<Route index element={<FeatureList />} />
<Route path='new' element={<FeatureForm formType={"add"} />} />
<Route path='update' element={<FeatureForm formType={"edit"} />
} />
</Route>
<Route path='user-master'>
<Route index element={<UserList />} />
<Route path='new' element={<UserForm formType={"add"} />} />
<Route path='update' element={<UserForm formType={"edit"} />} />
</Route>
<Route path='payment'>
<Route path='payment-history' element={<PaymentHistory />} />
<Route path='failed-payment-history' element={<FailedPaymentHistory />} />
</Route>
<Route path='message-template'>
<Route index element={<MessageTemplateList />} />
<Route path='new' element={<MessageTemplateForm formType={"add"} />} />
<Route path='update' element={<MessageTemplateForm formType={"edit"} />} />
</Route>
<Route path='PaymentUPIDetails'>
<Route index element={<PaymentUPIDetailsList />} />
<Route path='new' element={<PaymentUPIDetailsForm formType={"add"} />} />
<Route path='update' element={<PaymentUPIDetailsForm formType={"edit"} />} />
</Route>
<Route path='featurepricing'>
<Route index element={<FeaturesPricing />} />
{<Route path='new' element={<FeaturesPricingForm formType={"add"} />} />}
<Route path='update' element={<FeaturesPricingForm formType={"edit"} />} />
</Route>
<Route path='app-access' element={<ModuleAccess />} />
<Route path='config-master' element={<CommonMaster />} />
<Route path='config-type' element={<ConfigTypeForm />} />
<Route path='admin-tax' element={< AdminTax />} />
<Route path='application-image' element={< ApplicationImageForm />} />
<Route path='carousel' element={<CarouselForm />} />
<Route path='currency' element={<CurrencyForm />} />
<Route path='payment-data' element={<CustomPaymentGateway />} />
<Route path='payment-method' element={<PaymentMethod />} />
<Route path='payment-details' element={<PaymentDetail />} />
<Route path='activationkey-generation' element={<ActivationkeyGeneration />} />
<Route path='Referral-setting'>
<Route index element={<LoyaltysettingList />} />
<Route path='new' element={<LoyaltysettingsForm formType={"add"} />} />
<Route path='update' element={<LoyaltysettingsForm formType={"edit"} />} />
</Route>
</Route>
<Route path={`${subDirectory}check`} element={<div>check</div>} />
</Routes>
</Suspense>
)
}
export default App

121
src/AppTest.jsx Normal file
View File

@ -0,0 +1,121 @@
// AppRoutes.jsx
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import ProtectedRoutes from "./ProtectedRoutes";
import { isMobile, isIOS } from "react-device-detect";
import { routesConfig } from "./routesConfig";
import { checkSession } from "./features/signInPage/signInPage.js";
import { clearSession, getSession } from "./Services/others.js";
import devtools from "devtools-detect";
import useVisitLogger from "./useVisitLogger.jsx";
import { useSearchParams } from "react-router-dom";
const subDirectory = import.meta.env.ENV_MAIN_BASE_URL;
const AppRoutes = () => {
const [searchParams] = useSearchParams();
useVisitLogger();
const navigate = useNavigate();
const dispatch = useDispatch();
const [devToolsOpen, setDevToolsOpen] = useState(false);
useEffect(() => {
console.log("search raw:", window.location.search);
console.log("queryParams size:", [...searchParams.entries()].length);
console.log("queryParams entries:", [...searchParams.entries()]);
console.log("token:", searchParams.get("token"));
console.log("id:", searchParams.get("id"));
}, [searchParams]);
useEffect(() => {
const handlePopState = (e) => {
// Skip session clearing for PostEditorPage
if (window.location.pathname.includes("PostEditorPage")) {
return;
}
const SessionId = getSession("SessionId");
if (!SessionId) {
clearSession();
window.location.replace(`${subDirectory}`);
} else {
window.history.go(1);
}
};
window.addEventListener("popstate", handlePopState);
return () => {
window.removeEventListener("popstate", handlePopState);
};
}, []);
useEffect(() => {
const hasAuthorizedSession = new URLSearchParams(
window.location.search
).has("SD");
console.log("hasAuthorizedSession", hasAuthorizedSession);
if (!hasAuthorizedSession) {
sessionCheckFun();
} else {
const url = new URL(window.location);
url.search = "";
window.history.replaceState({}, document.title, url.toString());
}
}, [navigate]);
// mohan
// useEffect(() => {
// if (isMobile || isIOS) {
// console.log("📱 Mobile/iOS device skipping DevTools detection");
// return;
// }
// const checkDevTools = setInterval(() => {
// if (devtools.isOpen && !devToolsOpen) {
// setDevToolsOpen(true);
// document.body.innerHTML =
// "<h1 style='color: red; text-align: center;'>Close Inspect to continue using the application.</h1>";
// }
// else if (!devtools.isOpen && devToolsOpen) {
// setDevToolsOpen(false);
// setTimeout(() => {
// window.location.reload();
// }, 100);
// clearInterval(checkDevTools);
// }
// if (!devtools.isOpen) {
// let before = performance.now();
// let after = performance.now();
// let executionDelay = after - before;
// if (executionDelay > 100) {
// setDevToolsOpen(true);
// document.body.innerHTML =
// "<h1 style='color: red; text-align: center;'>Close Inspect to continue using the application.</h1>";
// } else {
// setDevToolsOpen(false);
// }
// }
// }, 1000);
// return () => clearInterval(checkDevTools);
// }, [devToolsOpen]);
const sessionCheckFun = async () => {
const UserId = getSession("UserId");
const SessionId = getSession("SessionId");
if (UserId && SessionId && sessionStorage.getItem("auth")) {
const res = await dispatch(checkSession({ UserId, SessionId })).unwrap();
if (res?.data?.statusCode === 1) {
if (res?.data?.response === "False") {
clearSession();
window.location.replace(`${subDirectory}`);
}
}
}
};
return <ProtectedRoutes routesConfig={routesConfig} />;
};
export default AppRoutes;

View File

@ -0,0 +1,159 @@
import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { BsArrowLeft, BsArrowRight } from 'react-icons/bs';
const Slider = ({ children, showLeftButton, showRightButton }) => {
const scrollContainerRef = useRef(null);
const [showLeftButton1, setShowLeftButton] = useState(false);
const [showRightButton1, setShowRightButton] = useState(true);
const [isDragging, setIsDragging] = useState(false);
const [startX, setStartX] = useState(0);
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, [width]);
const handleMouseDown = (e) => {
setIsDragging(true);
setStartX(e.pageX - scrollContainerRef.current.offsetLeft);
};
const handleTouchStart = (e) => {
setIsDragging(true);
setStartX(e.touches[0].clientX);
};
const handleTouchMove = (e) => {
if (!isDragging) return;
const scrollContainer = scrollContainerRef.current;
if (!scrollContainer) return;
const deltaX = e.touches[0].clientX - startX;
scrollContainer.scrollLeft -= deltaX;
setStartX(e.touches[0].clientX);
};
const handleMouseMove = (e) => {
if (!isDragging) return;
const scrollContainer = scrollContainerRef.current;
if (!scrollContainer) return;
const deltaX = e.clientX - startX;
scrollContainer.scrollLeft -= deltaX;
setStartX(e.clientX);
};
const handleTouchEnd = () => {
setIsDragging(false);
};
const handleScrollLeft = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollLeft -= 200;
}
};
const handleScrollRight = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollLeft += 200;
}
};
const handleMouseUp = () => {
setIsDragging(false);
};
const handleScroll = () => {
const container = scrollContainerRef.current;
if (container) {
setShowLeftButton(container.scrollLeft > 0);
setShowRightButton(
container.scrollWidth - container.clientWidth - container.scrollLeft > 1
);
}
};
useEffect(() => {
const container = scrollContainerRef.current;
if (container) {
const isOverflowing = container.scrollWidth > container.clientWidth;
setShowLeftButton(isOverflowing);
setShowRightButton(isOverflowing);
}
}, [children,width]);
return (
<div className="AppSliderDiv">
<div style={{ width: "2rem" }}>
{showLeftButton1 && (
<button className="swipee-icon" onClick={handleScrollLeft}>
<BsArrowLeft />
</button>
)}
</div>
<div
style={{
overflowX: "hidden",
overflowY: "hidden",
display: "flex",
alignItems: "center",
justifyContent: 'start',
flexDirection: "row",
columnGap: "0.5rem",
cursor: "grab",
}}
ref={scrollContainerRef}
onScroll={handleScroll}
onMouseDown={handleMouseDown}
onTouchStart={handleTouchStart}
onMouseMove={handleMouseMove}
onTouchMove={handleTouchMove}
onMouseUp={handleMouseUp}
onTouchEnd={handleTouchEnd}
onMouseLeave={handleMouseUp}
>
{children}
</div>
<div style={{ width: "2rem" }}>
{showRightButton1 && (
<button className="swipee-icon" onClick={handleScrollRight}>
<BsArrowRight />
</button>
)}
</div>
</div>
);
};
Slider.propTypes = {
children: PropTypes.node.isRequired,
};
Slider.propTypes = {
children: PropTypes.node.isRequired,
showLeftButton: PropTypes.bool,
showRightButton: PropTypes.bool,
};
Slider.defaultProps = {
showLeftButton: true,
showRightButton: true,
};
export default Slider;

View File

@ -0,0 +1,26 @@
.slider-container {
position: relative;
overflow: hidden;
width: 100%;
}
.slider-content {
display: flex;
will-change: transform;
animation: scroll-left infinite linear;
animation-duration: var(--scroll-speed);
}
.slider-content.paused {
animation-play-state: paused; /* Pause on hover */
}
@keyframes scroll-left {
0% {
transform: translateX(0); /* Start position */
}
100% {
transform: translateX(-100%); /* Move by the full width of the content */
}
}

View File

@ -0,0 +1,49 @@
import React, { useState, useEffect, useRef } from 'react';
import PropTypes from 'prop-types';
import './CommonSlider.scss';
const Slider = ({ children, scrollSpeed }) => {
const [isHovered, setIsHovered] = useState(false);
const containerRef = useRef(null);
const contentRef = useRef(null);
// Dynamically calculate content width
useEffect(() => {
if (contentRef.current && containerRef.current) {
const contentWidth = contentRef.current.scrollWidth;
containerRef.current.style.setProperty('--content-width', `${contentWidth}px`);
}
}, [children]);
console.log(children,"childrenjkhsdf")
return (
<div
ref={containerRef}
className="slider-container"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div
ref={contentRef}
className={`slider-content ${isHovered ? 'paused' : ''}`}
style={{ animationDuration: `${scrollSpeed}s` }}
>
{children}
{children}
{children} {/* Add more children to create continuous loop */}
</div>
</div>
);
};
Slider.propTypes = {
children: PropTypes.node.isRequired,
scrollSpeed: PropTypes.number,
};
Slider.defaultProps = {
scrollSpeed: 10,
};
export default Slider;

View File

@ -0,0 +1,286 @@
import React, { useEffect, useRef } from 'react';
import EditorJS from '@editorjs/editorjs';
import Header from '@editorjs/header';
import List from '@editorjs/list';
import Paragraph from '@editorjs/paragraph';
import Image from '@editorjs/image';
import Video from '@weekwood/editorjs-video';
import Quote from '@editorjs/quote';
import Code from '@editorjs/code';
import Embed from '@editorjs/embed';
import LinkTool from '@editorjs/link';
import { useDispatch } from 'react-redux';
import { useEditor } from '../context/EditorContext';
import {
Callout,
CTA,
EmailContent,
Separator,
BetterHeader,
SimpleLink,
Bookmark
} from '../editor/tools/index.js';
import InlineLink from '../editor/tools/InlineLink.js';
import { useLocation } from 'react-router-dom';
import { uploadImage } from '../features/upload/upload.js';
// --- Custom Video Tool with robust controls enforcement ---
class CustomVideo extends Video {
render() {
const wrapper = super.render();
// Helper to set controls and other attributes
const setVideoAttributes = (video) => {
if (!video) return;
video.controls = true;
video.setAttribute('controls', '');
video.playsInline = true;
video.preload = 'metadata';
video.style.width = '100%';
video.style.height = 'auto';
video.crossOrigin = 'anonymous';
// Remove loader when video is ready
video.addEventListener('canplay', () => {
removeSkeleton();
});
video.addEventListener('error', () => {
removeSkeleton();
});
// ...existing code for source/type...
const srcEl = video.querySelector('source');
const srcUrl = video.currentSrc || video.src || (srcEl && srcEl.src);
if (srcEl && !srcEl.type && srcUrl) {
const ext = (srcUrl.split('.').pop() || '').split('?')[0].toLowerCase();
if (ext === 'mp4') srcEl.type = 'video/mp4';
}
if (!video.src && srcEl && srcEl.src) {
video.src = srcEl.src;
}
video.load();
};
// --- Skeleton Loader ---
let skeleton = null;
const showSkeleton = () => {
if (skeleton) return;
skeleton = document.createElement('div');
skeleton.className = 'custom-video-skeleton';
skeleton.style.cssText = `
position:absolute;
top:0;left:0;width:100%;height:100%;
display:flex;align-items:center;justify-content:center;
background:rgba(240,240,240,0.9);
z-index:10;
font-size:18px;
color:#888;
`;
skeleton.innerHTML = `<div>
<svg width="48" height="48" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="20" fill="none" stroke="#bbb" stroke-width="5" stroke-linecap="round" stroke-dasharray="31.4 31.4" transform="rotate(-90 25 25)">
<animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="1s" repeatCount="indefinite"/>
</circle>
</svg>
<div>Loading video...</div>
</div>`;
// Find the video container
const videoContainer = wrapper.querySelector('.video-tool__video > div');
if (videoContainer) {
videoContainer.style.position = 'relative';
videoContainer.appendChild(skeleton);
}
};
const removeSkeleton = () => {
if (skeleton && skeleton.parentNode) {
skeleton.parentNode.removeChild(skeleton);
skeleton = null;
}
};
// Initial skeleton logic
const video = wrapper.querySelector('video');
if (!video || !video.src) {
showSkeleton();
} else {
setVideoAttributes(video);
}
// Observe for video element or src changes
const observer = new MutationObserver(() => {
const video = wrapper.querySelector('video');
if (video) {
if (!video.src) {
showSkeleton();
} else {
removeSkeleton();
setVideoAttributes(video);
}
}
});
observer.observe(wrapper, { childList: true, subtree: true });
return wrapper;
}
}
const ContentEditor = () => {
const editorRef = useRef(null);
const { post, setField } = useEditor();
const location = useLocation();
const { isEdit = false } = location?.state || {};
const varOcg = useRef(false);
const dispatch = useDispatch();
const customVideo = {
class: CustomVideo,
config: {
inlineToolbar: true,
player: {
pip: false,
},
uploader: {
async uploadByFile(file) {
try {
const res = await dispatch(uploadImage(file)).unwrap();
if (res?.data?.image) {
if (res.data.image.startsWith('data:')) {
alert('Video upload returned a data URL. This will not work for large files. Please check your backend.');
throw new Error('Video upload returned a data URL');
}
return {
success: 1,
file: { url: res.data.image },
};
} else {
throw new Error('Invalid response');
}
} catch (error) {
console.error('Video upload failed:', error);
return { success: 0, message: 'Video upload failed' };
}
},
uploadByUrl(url) {
return Promise.resolve({
success: 1,
file: { url },
});
},
},
},
};
useEffect(() => {
let editorInstance;
const initEditor = async () => {
if (editorRef.current) return; // prevent multiple inits
editorInstance = new EditorJS({
holder: 'editorjs',
tools: {
link: LinkTool,
inlineLink: InlineLink,
header: BetterHeader,
paragraph: {
class: Paragraph,
config: {
// placeholder: ' Start typing...',
placeholder: 'Click the + button below to add content blocks (headings, images, paragraphs, etc.)...',
},
toolbox: {
title: 'Paragraph',
icon: '<svg width="17" height="15" viewBox="0 0 336 276" xmlns="http://www.w3.org/2000/svg"><path d="M291 150V79c0-19-15-34-34-34H79c-19 0-34 15-34 34v42l67-44 81 72 103-32z"/></svg>'
},
inlineToolbar: ['inlineLink', 'bold', 'italic']
},
list: List,
image: {
class: Image,
config: {
withCaption: true,
captionPlaceholder: 'Enter alternative text for accessibility...',
uploader: {
async uploadByFile(file) {
try {
// 🔹 Dispatch Redux thunk to upload image
const res = await dispatch(uploadImage(file)).unwrap();
if (res?.data?.image) {
return {
success: 1,
file: { url: res.data.image },
};
} else {
throw new Error('Invalid response from upload API');
}
} catch (error) {
console.error('Image upload failed:', error);
return {
success: 0,
message: 'Image upload failed',
};
}
},
uploadByUrl(url) {
return Promise.resolve({
success: 1,
file: { url },
});
},
},
},
},
quote: Quote,
code: Code,
embed: Embed,
callout: Callout,
cta: CTA,
email: EmailContent,
separator: Separator,
bookmark: Bookmark,
video: customVideo,
link: SimpleLink,
},
placeholder:
'✨ Click the + button below to add content blocks (headings, images, paragraphs, etc.)...',
data: isEdit && post?.blocks?.length > 0 ? { blocks: post.blocks } : {},
onChange: async () => {
const content = await editorInstance.save();
setField('blocks', content?.blocks);
},
});
editorRef.current = editorInstance;
};
initEditor();
return () => {
if (editorRef.current) {
editorRef.current.destroy();
editorRef.current = null;
}
};
}, [isEdit, dispatch]);
// handle late-arriving post.blocks (e.g., async fetch)
useEffect(() => {
if (
isEdit &&
editorRef.current &&
post?.blocks?.length > 0 &&
!varOcg.current
) {
editorRef.current.isReady.then(() => {
editorRef.current.render({ blocks: post.blocks });
varOcg.current = true;
});
}
}, [post?.blocks, isEdit]);
return <div id="editorjs" style={{ minHeight: '400px', padding: '20px' }} />;
};
export default ContentEditor;

View File

@ -0,0 +1,69 @@
import React, { useMemo } from 'react';
import { useEditor } from '../context/EditorContext';
import { countWordsFromBlocks, minutesRead } from '../utils/textUtils';
import { FaPen, FaFont, FaClock, FaCube, FaHeading, FaFileAlt, FaImage, FaList, FaChartBar } from 'react-icons/fa';
const DetailedMetrics = () => {
const { post } = useEditor();
const metrics = useMemo(() => {
const blocks = post.blocks || [];
const words = countWordsFromBlocks(post.blocks);
const chars = blocks.reduce((count, block) => {
if (block.data?.text) return count + block.data.text.length;
return count;
}, 0);
const headings = blocks.filter(b => b.type === 'header');
const paragraphs = blocks.filter(b => b.type === 'paragraph');
const images = blocks.filter(b => b.type === 'image');
const lists = blocks.filter(b => b.type === 'list');
return {
words,
characters: chars,
charactersNoSpaces: chars - (post.title || '').split(' ').length + 1,
readingTime: minutesRead(words),
blocks: blocks.length,
headings: headings.length,
paragraphs: paragraphs.length,
images: images.length,
lists: lists.length
};
}, [post]);
const items = [
{ label: 'Words', value: metrics.words, icon: <FaPen />, color: '#3b82f6' },
{ label: 'Characters', value: metrics.characters, icon: <FaFont />, color: '#8b5cf6' },
{ label: 'Reading Time', value: `${metrics.readingTime} min`, icon: <FaClock />, color: '#10b981' },
{ label: 'Blocks', value: metrics.blocks, icon: <FaCube />, color: '#f59e0b' },
{ label: 'Headings', value: metrics.headings, icon: <FaHeading />, color: '#ef4444' },
{ label: 'Paragraphs', value: metrics.paragraphs, icon: <FaFileAlt />, color: '#06b6d4' },
{ label: 'Images', value: metrics.images, icon: <FaImage />, color: '#84cc16' },
{ label: 'Lists', value: metrics.lists, icon: <FaList />, color: '#f97316' }
];
return (
<div style={{ border: "1px solid #e5e7eb", borderRadius: 8, padding: 12 }}>
<div style={{ fontSize: 14, fontWeight: 400, marginBottom: 12, color: '#007bff', display: 'flex', alignItems: 'center', gap: 6 }}>
<FaChartBar style={{ color: '#3b82f6', fontSize: "14px" }} /> Content Metrics
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8 }}>
{items.map((item, index) => (
<div key={index} style={{
display: 'flex', alignItems: 'center', gap: 6,
padding: '6px 8px', background: '#f8fafc', borderRadius: 4
}}>
<span style={{ fontSize: 12, color: item.color }}>{item.icon}</span>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 11, color: '#64748b' }}>{item.label}</div>
<div style={{ fontSize: 13, fontWeight: 600, color: '#1f2937' }}>{item.value}</div>
</div>
</div>
))}
</div>
</div>
);
};
export default DetailedMetrics;

View File

@ -0,0 +1,19 @@
import { Drawer} from 'antd';
export const Drawers = ({open,placement,title,children,onClose}) => {
return (
<>
<Drawer
title={title}
placement={placement}
onClose={onClose}
open={open}
key={placement}
>
{children}
</Drawer>
</>
);
};

View File

@ -0,0 +1,86 @@
import React, { useState } from 'react';
import { useEditor } from '../context/EditorContext';
import { uploadImage } from '../features/upload/upload';
import { useDispatch } from 'react-redux';
import { FaCamera, FaSpinner } from 'react-icons/fa';
import './FeatureImagePicker.scss';
import { IoCameraOutline } from "react-icons/io5";
const FeatureImagePicker = () => {
const { post, setField } = useEditor();
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const handleImageChange = async (e) => {
const file = e.target.files?.[0];
if (!file) return;
setLoading(true);
try {
// dispatch your async thunk
const res = await dispatch(uploadImage(file)).unwrap(); // unwrap to get payload
if (res?.data?.image) {
setField('featureImage', res?.data?.image); // set uploaded URL
}
} catch (err) {
console.error('Image upload failed:', err);
alert('Image upload failed. Try again.');
} finally {
setLoading(false);
}
};
return (
<div className="editor-panel feature-image-picker">
<h3 className="panel-title">
<FaCamera /> Feature Image
</h3>
<p className="panel-instruction">
Upload a featured image for your post. Recommended size: 1200x630px for optimal social media sharing.
</p>
{post.featureImage && (
<div className="image-preview">
<img
src={post.featureImage}
alt={post.featureImageAlt || "Feature"}
className="preview-image"
/>
<input
type="text"
placeholder="Enter alternative text for accessibility..."
value={post.featureImageAlt || ''}
onChange={(e) => setField('featureImageAlt', e.target.value)}
className="alt-text-input"
/>
</div>
)}
<div className="upload-section">
<input
type="file"
accept="image/*"
onChange={handleImageChange}
className="file-input"
disabled={loading}
id="feature-image-input"
/>
<label htmlFor="feature-image-input" className={`upload-button ${loading ? 'loading' : ''}`}>
{loading ? (
<>
<FaSpinner className="spinner" /> Uploading...
</>
) : (
<>
<FaCamera /> {post.featureImage ? 'Change Image' : 'Upload Image'}
</>
)}
</label>
</div>
</div>
);
};
export default FeatureImagePicker;

View File

@ -0,0 +1,104 @@
.feature-image-picker {
.panel-instruction {
font-size: 11px;
margin: 0px 0 16px 0;
line-height: 1.4;
color: #2e2e2e;
}
.image-preview {
margin-bottom: 16px;
.preview-image {
width: 100%;
height: 140px;
object-fit: cover;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.3s ease;
&:hover {
transform: scale(1.02);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
}
.alt-text-input {
width: 100%;
margin-top: 12px;
padding: 8px 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 12px;
color: #374151;
background: rgba(255, 255, 255, 0.9);
transition: all 0.2s ease;
box-sizing: border-box;
&::placeholder {
color: #9ca3af;
font-style: italic;
}
&:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
background: white;
}
}
}
.upload-section {
position: relative;
.file-input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.upload-button {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
padding: 12px 16px;
border: 2px dashed #d1d5db;
border-radius: 12px;
background: rgba(255, 255, 255, 0.5);
color: #6b7280;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
&:hover:not(.loading) {
border-color: #667eea;
background: rgba(102, 126, 234, 0.05);
color: #667eea;
transform: translateY(-1px);
}
&.loading {
cursor: not-allowed;
opacity: 0.7;
.spinner {
animation: spin 1s linear infinite;
}
}
}
}
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -0,0 +1,216 @@
// import { useState } from 'react';
// import { useDispatch } from 'react-redux';
// import { Upload, Modal, message } from 'antd';
// import { UploadOutlined } from '@ant-design/icons';
// import { uploadApk, uploadImage } from '../../features/upload/upload';
// const ApkUplaod = ({ updateAppUrl, ImageLink }) => {
// const dispatch = useDispatch();
// const [fileList, setFileList] = useState([]);
// const [previewOpen, setPreviewOpen] = useState(false);
// const allowedTypes = ['application/vnd.android.package-archive'];
// const handleChange = async ({ fileList: newFileList }) => {
// if (newFileList.length > 0) {
// const file = newFileList[0].originFileObj;
// const isApk = file && allowedTypes.includes(file.type);
// if (!isApk) {
// Modal.error({
// title: 'Invalid File Type',
// content: 'Only .apk files are allowed.',
// });
// setFileList([]);
// return;
// }
// try {
// const data = await dispatch(uploadApk(file)).unwrap();
// if (data?.data?.status) {
// const uploadedUrl = data.data.image;
// updateAppUrl(uploadedUrl); // This sends the uploaded URL to parent/component state
// setFileList([{ url: uploadedUrl, name: file.name }]);
// message.success('APK uploaded successfully!');
// } else {
// message.error(data?.data?.message || 'Upload failed');
// }
// } catch (error) {
// console.error('Upload error:', error);
// message.error('Upload failed');
// }
// } else {
// updateAppUrl('');
// setFileList([]);
// }
// };
// const handlePreview = () => {
// Modal.info({
// title: 'APK File',
// content: 'APK files cannot be previewed.',
// });
// };
// const beforeUpload = (file) => {
// const isApk = allowedTypes.includes(file.type);
// if (!isApk) {
// Modal.error({
// title: 'Invalid File Type',
// content: 'Only .apk files are allowed.',
// });
// }
// return isApk || Upload.LIST_IGNORE;
// };
// return (
// <>
// <Upload
// fileList={fileList}
// onChange={handleChange}
// onPreview={handlePreview}
// beforeUpload={beforeUpload}
// showUploadList={{ showPreviewIcon: false }}
// customRequest={({ file, onSuccess }) => {
// setTimeout(() => onSuccess("ok"), 0);
// }}
// >
// {fileList.length === 0 ? (
// <div style={{ border: "1px dashed gray", borderRadius: "50%", height: "4.5rem", width: "4.5rem", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", cursor: "pointer" }}>
// <UploadOutlined />
// <div style={{ marginTop: 8 }}>Upload</div>
// </div>
// ) : null}
// </Upload>
// </>
// );
// };
// export default ApkUplaod;
import { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Upload, Modal, message, Spin } from 'antd';
import { UploadOutlined, LoadingOutlined } from '@ant-design/icons';
import { uploadApk } from '../../features/upload/upload';
const ApkUplaod = ({ updateAppUrl, AppLinkLink }) => {
const dispatch = useDispatch();
const [fileList, setFileList] = useState([]);
const [uploading, setUploading] = useState(false); // <-- loader state
const allowedTypes = ['application/vnd.android.package-archive','application/x-msdownload'];
useEffect(() => {
if (AppLinkLink == null) {
updateAppUrl('');
setFileList([]);
}
}, [AppLinkLink])
const handleChange = async ({ fileList: newFileList }) => {
if (newFileList.length > 0) {
const file = newFileList[0].originFileObj;
const isApk = file && allowedTypes.includes(file.type);
if (!isApk) {
Modal.error({
title: 'Invalid File Type',
content: 'Only .apk files are allowed.',
});
setFileList([]);
return;
}
setUploading(true); // start loader
try {
const data = await dispatch(uploadApk(file)).unwrap();
if (data?.data?.status) {
const uploadedUrl = data.data.image;
updateAppUrl(uploadedUrl);
setFileList([{ url: uploadedUrl, name: file.name }]);
message.success('APK uploaded successfully!');
} else {
message.error(data?.data?.message || 'Upload failed');
}
} catch (error) {
console.error('Upload error:', error);
message.error('Upload failed');
} finally {
setUploading(false); // stop loader
}
} else {
updateAppUrl('');
setFileList([]);
}
};
const handlePreview = () => {
Modal.info({
title: 'APK File',
content: 'APK files cannot be previewed.',
});
};
const beforeUpload = (file) => {
const isApk = allowedTypes.includes(file.type);
if (!isApk) {
Modal.error({
title: 'Invalid File Type',
content: 'Only .apk files are allowed.',
});
}
return isApk || Upload.LIST_IGNORE;
};
return (
<Upload
fileList={fileList}
onChange={handleChange}
onPreview={handlePreview}
beforeUpload={beforeUpload}
showUploadList={{ showPreviewIcon: false }}
customRequest={({ file, onSuccess }) => {
setTimeout(() => onSuccess("ok"), 0);
}}
>
{fileList.length === 0 ? (
<div
style={{
border: "1px dashed gray",
borderRadius: "50%",
height: "4.5rem",
width: "4.5rem",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
cursor: "pointer",
position: 'relative'
}}
>
{uploading ? (
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
) : (
<>
<UploadOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</>
)}
</div>
) : null}
</Upload>
);
};
export default ApkUplaod;

View File

@ -0,0 +1,21 @@
import { Button } from 'antd';
import './main.scss'
const Buttons = ({ buttonText, color, handleSubmit, icon, disabled }) =>(
<Button
type="primary"
htmlType="submit"
className="primary_Button"
onClick={handleSubmit}
color={color}
disabled={disabled ? disabled : false}
>
{buttonText}{icon}
</Button>
);
export default Buttons;

View File

@ -0,0 +1,37 @@
import { Checkbox, Divider } from 'antd';
import { useState } from 'react';
import classnames from "classnames"
const CheckboxGroup = Checkbox.Group;
export const CheckBoxGroup = ({ fieldState, fieldApi, faClass,style,ListOfCheckBox,defaultCheckedList, ...props }) => {
const plainOptions = ListOfCheckBox ;
const { field, onBlur, initialValue, forwardedRef, className, content, ...rest} = props.props
const [checkedList, setCheckedList] = useState(defaultCheckedList);
const [indeterminate, setIndeterminate] = useState(true);
const [checkAll, setCheckAll] = useState(false);
const onChange = (list) => {
setCheckedList(list);
setIndeterminate(!!list.length && list.length < plainOptions.length);
setCheckAll(list.length === plainOptions.length);
};
const onCheckAllChange = (e) => {
setCheckedList(e.target.checked ? plainOptions : []);
setIndeterminate(false);
setCheckAll(e.target.checked);
};
return (
<>
<Checkbox indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll}>
Check all
</Checkbox>
<Divider />
<CheckboxGroup
options={plainOptions}
value={checkedList}
onChange={onChange}
className={classnames(` ${className}`, { "is-invalid": fieldState.error })}
/>
</>
);
};

View File

@ -0,0 +1,37 @@
import { DatePicker, Space } from 'antd';
import classnames from "classnames"
export const DatePic = ({ fieldState, fieldApi, ...props }) => {
const { value } = fieldState;
const { required,field, onChange, onBlur, canSelectPast,initialValue, forwardedRef, min, className, content, faClass, icon, ...rest } = props;
const disabledDate = (current) => {
var date = new Date();
date.setDate(date.getDate() + 1);
return current.valueOf() <= date.setDate(date.getDate() - 2)
};
return(
<Space direction="vertical">
<DatePicker
{...rest}
id={field}
type="date"
ref={forwardedRef}
required={required}
value={value}
disabledDate={canSelectPast ? "": disabledDate}
format="DD-MM-YYYY"
className={classnames(`form-control ${className}`, {
"is-invalid": fieldState.error,
})}
onChange={onChange}
/>
</Space>
);
}

View File

@ -0,0 +1,58 @@
import { DatePicker, Space } from 'antd';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
dayjs.extend(customParseFormat);
export const DatePicProd = ({
onChange,
canSelectPast,
valueData,
disabled,
cancelFuture,
minDate,
dontallowfeature,
}) => {
const disabledDate = (current) => {
var date = new Date();
date.setDate(date.getDate() + 1);
return current.valueOf() <= date.setDate(date.getDate() - 2);
};
const disabledFutureDate = (current) => {
// Can not select days before today and today
return current && current > dayjs().endOf('day');
};
const disabledMinDate = (current) => {
if (minDate) {
return (
current &&
current < dayjs(minDate, 'YYYY-MM-DDTHH:mm:ss').startOf('day')
);
}
return false;
};
return (
<Space direction="vertical">
<DatePicker
format="DD-MM-YYYY"
value={valueData ? dayjs(valueData, 'YYYY-MM-DDTHH:mm:ss') : ''}
disabledDate={(current) => {
if (dontallowfeature) {
return current && current > dayjs().endOf('day');
} else if (canSelectPast) {
if (cancelFuture) {
return disabledFutureDate(current) || disabledMinDate(current);
}
return disabledMinDate(current);
}
return disabledDate(current) || disabledMinDate(current);
}}
disabled={disabled}
onChange={onChange}
inputReadOnly={true}
/>
</Space>
);
};

View File

@ -0,0 +1,72 @@
import { Select } from 'antd';
import classnames from "classnames"
import FloatLabel from "./FloatLabel/index";
import React, { useState } from "react";
export const DropDowns = ({ options, onChangeFunction,
isOnchanges,
className,
defaultValue,
searchKeys = ["label"],
disabled, ...props }) => {
const [inpValue, setInputValue] = useState('')
const [isOnchange, setisOnchange] = useState(isOnchanges)
const changeStatus = async () => {
await setisOnchange(true)
}
const {
field,
onChange,
label,
onBlur,
forwardedRef,
required,
valueData,
labelChange,
...rest
} = props;
return (
<FloatLabel
label={label}
value={inpValue}
isOnChange={valueData != null ? true : isOnchange}
>
<Select
showSearch
style={{
width: 250
}}
defaultValue={defaultValue ? defaultValue : null}
optionFilterProp="children"
filterOption={
!labelChange
? (input, option) => {
return searchKeys.some((key) =>
(option?.[key] ?? "")
.toString()
.toLowerCase()
.includes(input.toLowerCase())
);
}
: null
}
filterSort={!labelChange ? (optionA, optionB) =>
(optionA?.label ?? '')?.toLowerCase()?.localeCompare((optionB?.label ?? '').toLowerCase()) : null
}
value={valueData}
onChange={(e) => onChangeFunction(e)}
onSelect={(e) => changeStatus(e)}
options={options}
className={classnames(` ${className}`)}
disabled={disabled ? true : false}
/>
</FloatLabel>
)
};

View File

@ -0,0 +1,65 @@
import { Select } from 'antd';
import classnames from "classnames"
import FloatLabel from "./FloatLabel/index";
import React, { useState } from "react";
export const FilterButton = ({options,onChangeFunction,isOnchanges,className,defaultValue,disabled,onSearch,onSearchChange,...props}) => {
const [inpValue , setInputValue] = useState('')
const [isOnchange,setisOnchange] =useState(isOnchanges)
const changeStatus =async()=>{
await setisOnchange(true)
}
const {
field,
onChange,
label,
onBlur,
forwardedRef,
required,
valueData,
...rest
} = props;
return(
<FloatLabel
label={label}
value = {inpValue}
isOnChange ={isOnchange}
>
<Select
showSearch
style={{
width: 220,
}}
defaultValue={defaultValue?defaultValue:null}
optionFilterProp="children"
filterOption={(input, option) => (option?.label ?? '').includes(input)}
filterSort={(optionA, optionB) =>
(optionA?.label ?? '')?.toLowerCase()?.localeCompare((optionB?.label ?? '').toLowerCase())
}
value={valueData}
onChange={(e) =>onChangeFunction(e)}
onSelect={(e)=>changeStatus(e)}
options={options}
className={classnames(` ${className}`)}
disabled={disabled ? true : false}
/>
</FloatLabel>
)
};
export default FilterButton;

View File

@ -0,0 +1,28 @@
.float-label {
position: relative;
margin-bottom: 12px;
}
.label {
font-size: 14px !important;
font-weight: normal;
position: absolute;
pointer-events: none;
left: 12px;
font-family: Arial, Helvetica, sans-serif;
top: 14px;
transition: 0.2s ease all;
}
.label-float {
font-size: 12px !important;
top:2px;
left: 12px;
font-style: normal;
color: #007eb5;
z-index: 23;
}

View File

@ -0,0 +1,24 @@
import React, { useState } from "react";
import "./index.css";
const FloatLabel = props => {
const [focus, setFocus] = useState(false);
const { children, label, value, isOnChange } = props;
const labelClass =
focus || (value && value.length !== 0) || isOnChange? "label label-float" : "label" ;
return (
<div
className="float-label"
onBlur={() => setFocus(false)}
onFocus={() => setFocus(true)}
>
{children}
<label className={labelClass}>{label}</label>
</div>
);
};
export default FloatLabel;

View File

@ -0,0 +1,67 @@
import React, { useState } from "react";
import { Input } from "antd";
import FloatLabel from "./FloatLabel/index.jsx";
import "./main.scss";
export const InputField = ({ fieldState, fieldApi, ...props }) => {
const { value } = fieldState;
const [inpValue, setInputValue] = useState('')
const {
field,
onChange,
isOnChange,
onBlur,
label,
forwardedRef,
required,
...rest
} = props;
return (
<div className="example">
<FloatLabel
label={label}
value={inpValue}
isOnChange={isOnChange}
>
<Input
{...rest}
spellCheck={false}
id={field}
ref={forwardedRef}
defaultValue={value?.toString()}
required={required}
onChange={(e) => {
setInputValue(e?.target?.value)
if (onChange) {
onChange(e);
}
}}
onBlur={(e) => {
if (onBlur) {
onBlur(e);
}
}}
/>
</FloatLabel>
</div>
);
};

View File

@ -0,0 +1,23 @@
.ant-radio-wrapper .ant-radio-checked .ant-radio-inner{
border-color: #000000;
background-color:#000000
}
.ant-radio-wrapper .ant-radio-inner {
box-sizing: border-box;
position: relative;
inset-block-start: 0;
inset-inline-start: 0;
display: block;
width: 16px;
height: 16px;
background-color: #ffffff;
border-color: #000000;
border-style: solid;
border-width: 1px;
border-radius: 50%;
transition: all 0.2s;
}

View File

@ -0,0 +1,76 @@
import { Radio } from 'antd';
import { useState } from 'react';
import './RadioBtn.scss'
export const RadioGrpButton = ({ fieldState, style, ...props }) => {
const { disabled = false, field, onBlur, initialValue, forwardedRef, className, onSelectFuntion, content, Header, defaultSelect, ...rest } = props
const [value, setValue] = useState(defaultSelect);
const onChange = (e) => {
setValue(e?.target?.value);
if (onSelectFuntion) {
onSelectFuntion(e?.target?.value)
}
};
return (
<div>
<p style={{ paddingBottom: "10px", fontWeight: "500" }}>{Header}</p>
<Radio.Group disabled={disabled} onChange={onChange} value={value} options={content}>
{content?.map((key, index) =>
<Radio
value={key.value}
key={index}
>{key.label}</Radio>
)}
</Radio.Group>
</div>
);
};
// import { Radio } from 'antd';
// import './RadioBtn.scss';
// export const RadioGrpButton = ({
// fieldState,
// style,
// value,
// disabled = false,
// className,
// onSelectFuntion,
// content,
// Header,
// ...rest
// }) => {
// const onChange = (e) => {
// if (onSelectFuntion) {
// onSelectFuntion(e?.target?.value);
// }
// };
// return (
// <div>
// <p style={{ paddingBottom: "10px", fontWeight: "500" }}>{Header}</p>
// <Radio.Group
// disabled={disabled}
// onChange={onChange}
// value={value} // Controlled by parent
// options={content}
// >
// {content?.map((key, index) => (
// <Radio value={key.value} key={index}>
// {key.label}
// </Radio>
// ))}
// </Radio.Group>
// </div>
// );
// };

View File

@ -0,0 +1,35 @@
import { Radio } from 'antd';
import classnames from "classnames"
export const RadioButton = ({ fieldState, fieldApi, faClass,style, ...props }) => {
const { field, onChange, onBlur, initialValue, forwardedRef, className, content, ...rest } = props
return (
<Radio
{...rest}
id={field}
ref={forwardedRef}
name={props.props.name}
required={false}
value={props.props.value}
className={classnames(` ${className}`, { "is-invalid": fieldState.error })}
onChange={(e) => {
if (onChange) {
onChange(e)
}
}}
onBlur={(e) => {
if (onBlur) {
onBlur(e)
}
}}
>
{props.props.label}
</Radio>
);
}

View File

@ -0,0 +1,40 @@
import React, { useEffect, useState } from 'react';
import { Input } from 'antd';
import FloatLabel from './FloatLabel/index.jsx';
import './main.scss';
const RetailTextAreaInput = ({ ...props }) => {
const [inpValue, setInputValue] = useState(props?.defaultValue || '');
const { TextArea } = Input;
useEffect(() => {
setInputValue(props?.defaultValue);
}, [props?.defaultValue]);
const { onChange, isOnChange, onBlur, label,width = false, ...rest } = props;
return (
<div className={width? "" : "example"} style={{ width: width ? '100%' : 'auto' }}>
<FloatLabel label={label} value={inpValue} isOnChange={isOnChange}>
<TextArea
{...rest}
onChange={(e) => {
setInputValue(e?.target?.value);
if (onChange) {
onChange(e);
}
}}
onBlur={(e) => {
if (onBlur) {
onBlur(e);
}
}}
/>
</FloatLabel>
</div>
);
};
export default RetailTextAreaInput;

View File

@ -0,0 +1,18 @@
import { Input } from "antd";
import "./main.scss";
export const Search = ({ placeholder, onSearch, onSearchChange, prefix, value }) => (
<Input.Search
className="searchDiv"
spellCheck={false}
placeholder={placeholder}
onSearch={onSearch}
onChange={onSearchChange}
prefix={prefix}
value={value && value}
/>
);
export default Search;

View File

@ -0,0 +1,12 @@
import { Switch } from 'antd';
export const Toggle = ({ style, defaultChecked,functionName,...props }) => {
return (
<Switch
defaultChecked={defaultChecked}
onChange={functionName}
/>
);
}

View File

@ -0,0 +1,15 @@
import { Switch } from 'antd';
export const SwitchModified = ({ style, defaultChecked, functionName, disabled, ...props }) => {
return (
<Switch
style={style}
checked={defaultChecked}
onChange={functionName}
disabled={disabled}
{...props}
/>
);
};

View File

@ -0,0 +1,67 @@
import React, { useState } from "react";
import { Input } from "antd";
import FloatLabel from "./FloatLabel/index.jsx";
import "./main.scss";
export const TextAreaInput = ({ fieldState, fieldApi, ...props }) => {
const { value } = fieldState;
const [inpValue, setInputValue] = useState('')
const { TextArea } = Input;
const {
field,
onChange,
isOnChange,
onBlur,
label,
forwardedRef,
required,
width = false,
...rest
} = props;
return (
<div className={width ? "" : "example"} style={{ width: width ? '100%' : 'auto' }}>
<FloatLabel
label={label}
value={inpValue}
isOnChange={isOnChange}
>
<TextArea
{...rest}
id={field}
ref={forwardedRef}
defaultValue={value?.toString()}
required={required}
onChange={(e) => {
setInputValue(e?.target?.value)
if (onChange) {
onChange(e);
}
}}
onBlur={(e) => {
if (onBlur) {
onBlur(e);
}
}}
/>
</FloatLabel>
</div>
);
};
export default TextAreaInput;

View File

@ -0,0 +1,32 @@
import { TimePicker } from 'antd';
import FloatLabel from "./FloatLabel/index.jsx";
import "./main.scss";
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat'
dayjs.extend(customParseFormat)
export const TimePickers = ({label,isOnChange,onChange,valueData}) => (
<div className="example">
<FloatLabel
label={label}
isOnChange= {isOnChange}
>
<TimePicker className="TimePickerDiv"
use12Hours format="h:mm a"
onChange={onChange}
value={valueData != undefined ? dayjs(valueData, 'HH:mm:ss') :""}
/>
</FloatLabel>
</div>
);
export default TimePickers;

View File

@ -0,0 +1,116 @@
import { useEffect, useState } from 'react';
import { useDispatch} from 'react-redux'
import { PlusOutlined } from '@ant-design/icons';
import { Modal, Upload } from 'antd';
import { uploadImage } from '../../features/upload/upload';
const getBase64 = (file) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = (error) => reject(error);
});
const allowedTypes = [
'image/jpeg',
'image/png',
'image/webp',
'image/svg+xml',
'image/gif',
'image/avif'
];
const App = ({singleImage,updateImageUrl,ImageLink}) => {
const dispatch = useDispatch();
const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState('');
const [previewTitle, setPreviewTitle] = useState('');
const [fileList, setFileList] = useState([]);
useEffect(() => {
if (ImageLink !="" && ImageLink !=null ) {
setFileList([{url:ImageLink}])
} else {
setFileList([])
}
}, [ImageLink])
const handleCancel = () => setPreviewOpen(false);
const handlePreview = async (file) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj);
}
setPreviewImage(file.url || file.preview);
setPreviewOpen(true);
setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1));
};
const handleChange = async ({ fileList: newFileList }) => {
if (newFileList.length > 0){
let data=await dispatch(uploadImage(newFileList[0].originFileObj)).unwrap()
if (data?.data?.status){
updateImageUrl(data?.data?.image);
setFileList([{url:data?.data?.image}])
}
}else{
updateImageUrl("");
setFileList(newFileList)
}
};
const uploadButton = (
<div>
<PlusOutlined />
<div
style={{
marginTop: 8,
}}
>
Upload
</div>
</div>
);
const beforeUpload = (file) => {
const isAllowed = allowedTypes.includes(file.type);
if (!isAllowed) {
Modal.error({
title: 'Invalid File Type',
content: 'Only JPG, JPEG, PNG, WEBP, SVG, GIF, and AVIF files are allowed.',
});
}
return isAllowed || Upload.LIST_IGNORE; // prevents adding to fileList if invalid
};
return (
<>
<Upload
// https://www.mocky.io/v2/5cc8019d300000980a055e76
action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188"
listType="picture-circle"
fileList={fileList}
onPreview={handlePreview}
onChange={handleChange}
beforeUpload={beforeUpload}
>
{(singleImage && fileList.length==0) ? uploadButton :(!singleImage && fileList.length <= 8) ? uploadButton : null}
{/* {fileList.length >= 8 ? null : uploadButton} */}
</Upload>
<Modal open={previewOpen} title={''} footer={null} onCancel={handleCancel}>
<img
alt="example"
style={{
width: '100%',
}}
src={previewImage}
/>
</Modal>
</>
);
};
export default App;

View File

@ -0,0 +1,68 @@
import React, { useEffect, useState } from "react";
import { Input } from "antd";
import FloatLabel from "./FloatLabel/index.jsx";
import "./main.scss";
export const VersionUpdateInputField = ({ fieldState, fieldApi, ...props }) => {
const { value } = fieldState;
const [inpValue, setInputValue] = useState('')
const {
field,
onChange,
isOnChange,
onBlur,
label,
forwardedRef,
required,
valueData,
...rest
} = props;
useEffect(() => {
if (!valueData) {
setInputValue(false)
}
}, [valueData])
return (
<div className="example">
<FloatLabel
label={label}
value={inpValue}
isOnChange={isOnChange}
>
<Input
{...rest}
id={field}
ref={forwardedRef}
defaultValue={value?.toString()}
required={required}
onChange={(e) => {
setInputValue(e?.target?.value)
if (onChange) {
onChange(e);
}
}}
onBlur={(e) => {
if (onBlur) {
onBlur(e);
}
}}
/>
</FloatLabel>
</div>
);
};

View File

@ -0,0 +1,56 @@
import { Select } from 'antd';
import classnames from "classnames"
import FloatLabel from "./FloatLabel/index";
import React, { useState } from "react";
export const VersionupdateDropDown = ({ options, onChangeFunction, isOnchanges, className, defaultValue, disabled, ...props }) => {
const [inpValue, setInputValue] = useState('')
const [isOnchange, setisOnchange] = useState(isOnchanges)
const changeStatus = async () => {
await setisOnchange(true)
}
const {
field,
onChange,
label,
onBlur,
forwardedRef,
required,
valueData,
labelChange,
...rest
} = props;
return (
<FloatLabel
label={label}
value={inpValue}
isOnChange={valueData != null ? true : false}
>
<Select
showSearch
style={{
width: 250
}}
defaultValue={defaultValue ? defaultValue : null}
optionFilterProp="children"
filterOption={!labelChange ? (input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()) : null}
filterSort={!labelChange ? (optionA, optionB) =>
(optionA?.label ?? '')?.toLowerCase()?.localeCompare((optionB?.label ?? '').toLowerCase()) : null
}
value={valueData}
onChange={(e) => onChangeFunction(e)}
onSelect={(e) => changeStatus(e)}
options={options}
className={classnames(` ${className}`)}
disabled={disabled ? true : false}
/>
</FloatLabel>
)
};

View File

@ -0,0 +1,66 @@
import React, {useEffect, useRef, useState} from "react";
import { useDispatch} from 'react-redux';
import { uploadImage } from '../../features/upload/upload';
import videoBg from "../../Images/TemplateImages/videobg.svg"
import './VideoUpload.scss';
export default function VideoInput({updateVideoUrl,videoUrl}) {
const inputRef = useRef();
const dispatch = useDispatch();
const [source, setSource] = useState();
useEffect(() => {
if(videoUrl){
setSource(videoUrl)
}else{
if (inputRef.current) {
inputRef.current.value = null;
}
setSource()
}
}, [videoUrl])
const handleFileChange = async (event) => {
let file = event.target.files[0];
if (event.target.files.length > 0){
let data=await dispatch(uploadImage(file)).unwrap()
if (data?.data?.status){
updateVideoUrl(data?.data?.image);
setSource(data?.data?.image)
}
}else{
updateVideoUrl("");
setSource("")
}
};
const handleChoose = (event) => {
inputRef.current.click();
};
return (
<div>
<input
ref={inputRef}
className="VideoInput_input"
type="file"
onChange={handleFileChange}
accept=".mov,.mp4"
/>
{(!source) && <button type="button" style={{backgroundColor:"#cfcfcf",borderColor:"#cfcfcf"}} onClick={handleChoose}><img src={videoBg} style={{width:"150px",height:"85px"}}></img></button>}
{source && (
<video
className="VideoInput_video"
controls
src={source}
/>
)}
</div>
);
}

View File

@ -0,0 +1,19 @@
.VideoInput_input {
display: none;
}
.VideoInput_video {
display: block;
margin: 0;
height: 85px;
width: 150px;
}
.VideoInput_footer {
background: #eee;
width: 100%;
min-height: 40px;
line-height: 40px;
text-align: center;
}

View File

@ -0,0 +1,846 @@
.example {
width: var(--INPUT_FIELD_WIDTH);
}
/* Force update ant style */
.ant-input {
padding: 18px 14px 6px 11px !important;
font-size: 16px;
font-weight: 490;
color: #000;
//vicky
// margin-top: 8px;
}
.ant-input:hover {
padding: 18px 14px 6px 11px;
font-size: 16px;
border: var(--DEFAULT_SELECTED_COLOR) solid 1px;
}
.ant-input:focus {
padding: 18px 14px 6px 11px;
font-size: 16px;
border: var(--DEFAULT_SELECTED_COLOR) solid 1px;
}
.ant-select .ant-select-selector {
padding: 16px 10px 4px 11px;
}
.ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
padding: 8px 10px 4px 11px;
height: 53px;
font-size: 16px;
padding: 10px;
font-weight: 600;
}
.ant-select .ant-select-selector {
padding: 16px 10px 4px 11px;
}
.ant-select-single:not(.ant-select-customize-input) .ant-select-selector {
padding: 8px 10px 4px 11px;
height: 48px;
font-size: 16px;
padding: 10px;
font-weight: 600;
}
.ant-select-single .ant-select-selector .ant-select-selection-search {
top: 16px;
font-size: 16px;
}
:where(.css-dev-only-do-not-override-1fviqcj).ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
color: rgba(255, 255, 255, 0.88);
font-weight: 600;
background-color: var(--PRIMARY_BUTTON_BG_COLOR);
}
.primary_Button {
width: 170px;
height: 45px;
background-color: var(--PRIMARY_BUTTON_BG_COLOR) !important;
color: #fff;
font-family: var(--HEADING_FONT_FAMILY);
font-style: normal;
font-weight: 500;
font-size: 14px;
border-radius: 8px;
display: flex;
letter-spacing: 0.5px;
justify-content: space-between;
align-items: center;
z-index: 2;
}
.primary_Button {
border: 1px solid;
overflow: hidden;
span {
z-index: 20;
}
&:after {
background: #fff;
content: "";
height: 155px;
left: -75px;
opacity: 0.2;
position: absolute;
top: -50px;
transform: rotate(35deg);
transition: all 550ms cubic-bezier(0.19, 1, 0.22, 1);
width: 50px;
z-index: -10;
}
}
.primary_Button:hover {
&:after {
left: 120%;
transition: all 550ms cubic-bezier(0.19, 1, 0.22, 1);
}
}
.primary_Button:focus {
border: 1.8px solid var(--PRIMARY_BUTTON_BG_COLOR);
background-color: #ffffff !important;
color: #901d77;
font-weight: 600;
font-size: 14px;
}
.primary_Button:focus:hover {
border: 1.8px solid var(--PRIMARY_BUTTON_BG_COLOR);
background-color: white !important;
color: var(--PRIMARY_BUTTON_BG_COLOR);
font-weight: 600;
font-size: 14px;
}
.secondary_Button {
width: 170px;
height: 45px;
background-color: black !important;
color: #fff;
font-family: var(--HEADING_FONT_FAMILY);
font-style: normal;
font-weight: 500;
font-size: 14px;
border-radius: 8px;
display: flex;
letter-spacing: 0.5px;
justify-content: space-between;
align-items: center;
z-index: 2;
}
.secondary_Button {
border: 1px solid;
overflow: hidden;
span {
z-index: 20;
}
&:after {
background: #fff;
content: "";
height: 155px;
left: -75px;
opacity: 0.2;
position: absolute;
top: -50px;
transform: rotate(35deg);
transition: all 550ms cubic-bezier(0.19, 1, 0.22, 1);
width: 50px;
z-index: -10;
}
}
.secondary_Button:hover {
&:after {
left: 120%;
transition: all 550ms cubic-bezier(0.19, 1, 0.22, 1);
}
}
.secondary_Button:focus {
border: 1.8px solid var(--PRIMARY_BUTTON_BG_COLOR);
background-color: #ffffff !important;
color: #901d77;
font-weight: 600;
font-size: 14px;
}
.secondary_Button:focus:hover {
border: 1.8px solid var(--PRIMARY_BUTTON_BG_COLOR);
background-color: white !important;
color: var(--PRIMARY_BUTTON_BG_COLOR);
font-weight: 600;
font-size: 14px;
}
.tertiary_Button {
width: 100px;
height: 30px;
background-color: var(--PRIMARY_BUTTON_BG_COLOR) !important;
color: #fff;
font-family: var(--HEADING_FONT_FAMILY);
font-style: normal;
font-weight: 400;
font-size: 14;
line-height: 18px;
border-radius: 8px;
display: flex;
letter-spacing: 0.5px;
justify-content: center;
align-items: center;
z-index: 2;
left: 450px;
}
.tertiary_Button {
border: 1px solid;
overflow: hidden;
span {
z-index: 20;
}
&:after {
// background: #fff;
content: "";
height: 155px;
left: -15px;
opacity: 0.2;
position: absolute;
top: -50px;
transform: rotate(35deg);
transition: all 550ms cubic-bezier(0.19, 1, 0.22, 1);
width: 50px;
z-index: -10;
}
}
.tertiary_Button:hover {
&:after {
left: 120%;
transition: all 550ms cubic-bezier(0.19, 1, 0.22, 1);
}
}
Button:disabled,
Button[disabled] {
width: 170px;
height: 45px;
background-color: grey;
color: #fff;
font-family: var(--HEADING_FONT_FAMILY);
font-style: normal;
font-weight: 500;
font-size: 14px;
border-radius: 8px;
display: flex;
letter-spacing: 0.5px;
justify-content: space-between;
align-items: center;
z-index: 2;
}
.primary_Button {
// border: 1px solid black;
background-color: var(--ERROR_COLOR);
color: white;
padding: 15px;
}
.ant-btn-primary:disabled,
.ant-btn-primary:disabled {
width: 170px;
height: 45px;
background-color: grey !important;
color: #fff;
font-family: var(--HEADING_FONT_FAMILY);
font-style: normal;
font-weight: 500;
font-size: 14px;
border-radius: 8px;
display: flex;
letter-spacing: 0.5px;
justify-content: space-between;
align-items: center;
z-index: 2;
}
.ant-btn-primary:disabled {
// width: 35px;
// height: 25px;
background-color: white !important;
color: #fff;
font-family: var(--HEADING_FONT_FAMILY);
font-style: normal;
font-weight: 500;
font-size: 12px;
border-radius: 8px;
display: flex;
letter-spacing: 0.5px;
justify-content: center;
z-index: 2;
}
.ant-btn-primary:disabled {
/* border: 1px solid black; */
background-color: var(--ERROR_COLOR);
color: #858585;
/* padding: 5px; */
}
.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-today .ant-picker-cell-inner::before {
position: absolute;
top: 0;
inset-inline-end: 0;
bottom: 0;
inset-inline-start: 0;
z-index: 1;
border: 1px solid var(--PRIMARY_BUTTON_BG_COLOR);
border-radius: 4px;
}
.ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-selected .ant-picker-cell-inner,
:where(.css-dev-only-do-not-override-1fviqcj).ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-start .ant-picker-cell-inner,
:where(.css-dev-only-do-not-override-1fviqcj).ant-picker-dropdown .ant-picker-cell-in-view.ant-picker-cell-range-end .ant-picker-cell-inner {
color: #fff;
background: #901d77;
}
.ant-picker-dropdown .ant-picker-today-btn {
color: var(--DEFAULT_SELECTED_COLOR);
}
.ant-checkbox .ant-checkbox-inner {
box-sizing: border-box;
position: relative;
top: 0;
inset-inline-start: 0;
display: block;
width: 16px;
height: 16px;
direction: ltr;
background-color: #ffffff;
border: 1px solid #000;
border-radius: 4px;
border-collapse: separate;
transition: all 0.3s;
}
.ant-checkbox-checked .ant-checkbox-inner {
background-color: #000;
border-color: #000;
}
.ant-pagination .ant-pagination-item-active {
font-weight: 600;
background-color: #ffffff;
border-color: #000000;
}
.ant-pagination .ant-pagination-item-active a {
color: #000000;
}
.ant-table-wrapper .ant-table-thead>tr>th,
.ant-table-wrapper .ant-table-thead>tr>td {
position: relative;
// color: rgba(0, 0, 0, 0.88);
font-weight: 600;
text-align: start;
font-size: 12px;
color: rgb(118, 118, 118);
background: #fafafa;
border-bottom: 1px solid #f0f0f0;
font-size: 13px;
/* transition: background 0.2s ease; */
}
.ant-checkbox-wrapper:not(.ant-checkbox-wrapper-disabled):hover .ant-checkbox-checked:not(.ant-checkbox-disabled) .ant-checkbox-inner {
background-color: #000000;
border-color: transparent;
}
.ant-checkbox-wrapper:not(.ant-checkbox-wrapper-disabled):hover .ant-checkbox-inner,
:where(.css-dev-only-do-not-override-1fviqcj).ant-checkbox:not(.ant-checkbox-disabled):hover .ant-checkbox-inner {
border-color: #000000;
}
.ant-switch.ant-switch-checked {
background: var(--SELECTED_COLOR);
}
.ant-switch.ant-switch-checked:hover:not(.ant-switch-disabled) {
background: var(--SELECTED_COLOR);
}
.ant-table-wrapper .ant-table-thead>tr>th,
.ant-table-wrapper .ant-table-thead>tr>td {
position: relative;
color: rgba(64, 64, 64, 0.88);
font-weight: 600;
text-transform: uppercase;
text-align: start;
background: none;
border-bottom: 1px solid #f0f0f0;
font-family: var(--HEADING_FONT_FAMILY);
transition: background 0.2s ease;
transition-duration: 0.6s;
font-size: 13px !important;
}
.ant-table-wrapper .ant-table {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgb(14 14 14 / 88%);
font-size: 14px;
line-height: 1.5714285714285714;
list-style: none;
font-family: var(--HEADING_FONT_FAMILY);
background: none;
background-color: rgba(255, 255, 255, 0.036);
// box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
border-radius: 5px;
transition-duration: 0.6s;
}
.ant-table-wrapper table {
width: 100%;
text-align: start;
border-radius: 8px 8px 0 0;
border-collapse: separate;
border-spacing: 0;
}
.ant-table-wrapper .ant-table:hover {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgb(14 14 14 / 88%);
font-size: 14px;
line-height: 1.5714285714;
list-style: none;
border-radius: 8px 8px 0 0;
transition-duration: 0.6s;
}
.ant-table-cell-row-hover {
font-size: 14px;
font-weight: 600;
transition-duration: 0.6s;
}
.ant-table-wrapper .ant-table-pagination-right {
justify-content: flex-start;
}
.ant-pagination .ant-pagination-item {
display: inline-block;
min-width: 32px;
height: 32px;
margin-inline-end: 8px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
line-height: 30px;
text-align: center;
vertical-align: middle;
list-style: none;
background-color: white;
/* border: 1px solid transparent; */
border-radius: 6px;
outline: 0;
cursor: pointer;
user-select: none;
}
.ant-menu .ant-menu-item .ant-menu-item-icon,
.ant-menu .ant-menu-submenu-title .ant-menu-item-icon,
:where(.css-dev-only-do-not-override-1fviqcj).ant-menu .ant-menu-item .anticon,
:where(.css-dev-only-do-not-override-1fviqcj).ant-menu .ant-menu-submenu-title .anticon {
min-width: 14px;
font-size: 20px;
position: relative;
top: 13px;
transition: font-size 0.2s cubic-bezier(0.215, 0.61, 0.355, 1),
margin 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), color 0.3s;
}
.ant-menu-light,
.ant-menu-light>.ant-menu {
color: rgba(0, 0, 0, 0.88);
background: none;
padding: 1vw 0vh;
}
.ant-form-item .ant-form-item-explain-error {
color: var(--ERROR_COLOR);
position: relative;
// left: 11rem;
// width: 220px;
}
.ant-table-cell {
background: #fafafa7a;
//vicky
padding: 8px 8px !important;
}
.ant-input-affix-wrapper>input.ant-input {
/* padding: 0; */
font-size: inherit;
border: none;
border-radius: 0;
outline: none;
transition-duration: 0.6s;
}
.ant-input-affix-wrapper:not(.ant-input-affix-wrapper-disabled):hover {
border-color: var(--DEFAULT_SELECTED_COLOR);
border-inline-end-width: 1px;
background-color: none;
z-index: 0;
transition-duration: 0.6s;
}
.ant-input-affix-wrapper {
position: relative;
display: inline-flex;
width: 100%;
min-width: 0;
color: rgba(0, 0, 0, 0.88);
height: 50px;
line-height: 1.5714285714285714;
background-color: #ffffff;
background-image: none;
border-width: 1px;
border-style: solid;
border-color: #d9d9d9;
border-radius: 6px;
font-size: 16px;
transition: all 0.2s;
}
.ant-input-affix-wrapper>input.ant-input {
// padding: 0;
font-size: inherit;
border: none;
border-radius: 0;
outline: none;
}
.ant-input-affix-wrapper>input.ant-input {
font-size: inherit;
border: none;
padding: 23px -2px;
padding-top: 1rem;
border-radius: 0;
outline: none;
}
.ant-upload-drag-icon {
color: #000;
}
.ant-upload-text {
font-family: var(--HEADING_FONT_FAMILY);
}
.ant-upload-hint {
font-family: var(--PARA_FONT_FAMILY);
}
.ant-upload-wrapper .ant-upload-drag p.ant-upload-drag-icon .anticon {
color: #6a6a6a;
font-size: 48px;
}
.ant-table table {
border-spacing: 0 10px;
transition-duration: 0.6s;
// margin-top: 3rem;
// box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
}
.ant-table-wrapper .ant-table-tbody>tr.ant-table-row>td,
.ant-table-wrapper .ant-table-tbody>tr>th.ant-table-cell-row>td.ant-table-cell-row {
background: #ffffff8e;
box-shadow: rgba(100, 100, 111, 0.018) 0px 7px 29px 0px;
}
.ant-table-wrapper .ant-table-tbody>tr.ant-table-row:hover>td,
.ant-table-wrapper .ant-table-tbody>tr>th.ant-table-cell-row-hover>td.ant-table-cell-row-hover {
background: rgb(224, 223, 223);
font-size: 15px;
transition-duration: 0.6s;
// transform: translateY(-8px);
}
.ant-modal .ant-modal-content {
position: relative;
background-color: #ffffff;
background-clip: padding-box;
border: 0;
// width:700px;
// right: 8rem;
font-family: var(--HEADING_FONT_FAMILY);
border-radius: 8px;
box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
pointer-events: auto;
padding: 54px 54px;
color: #000;
}
.ant-modal-footer {
position: relative;
// left: 24rem;
display: flex;
justify-content: flex-end;
// display: none;
}
.ant-pagination .ant-pagination-next {
font-family: Arial, Helvetica, sans-serif;
outline: 1;
background-color: #fff;
// border: black solid 1px;
}
.ant-pagination .ant-pagination-prev,
.ant-pagination .ant-pagination-next {
font-family: Arial, Helvetica, sans-serif;
background-color: #fff;
// border: black solid 1px;
}
.ant-pagination .ant-pagination-item {
// display:none;
min-width: 32px;
height: 32px;
margin-inline-end: 8px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
line-height: 30px;
text-align: center;
vertical-align: middle;
list-style: none;
background-color: white;
/* border: 1px solid transparent; */
border-radius: 6px;
outline: 0;
cursor: pointer;
user-select: none;
}
.ant-pagination .ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-ellipsis {
position: absolute;
top: 0;
inset-inline-end: 0;
bottom: 0;
inset-inline-start: 0;
// display: none;
margin: auto;
color: rgba(0, 0, 0, 0.25);
font-family: Arial, Helvetica, sans-serif;
letter-spacing: 2px;
text-align: center;
text-indent: 0.13em;
opacity: 1;
transition: all 0.2s;
}
.ant-pagination .ant-pagination-jump-next .ant-pagination-item-container .ant-pagination-item-link-icon {
color: var(--ERROR_COLOR);
font-size: 12px;
opacity: 0;
transition: all 0.2s;
display: none;
}
.ant-select-single.ant-select-show-arrow .ant-select-selection-item,
.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder {
padding-inline-end: 18px;
text-align: initial;
}
.ant-select-single.ant-select-show-arrow .ant-select-selection-item,
.ant-select-single.ant-select-show-arrow .ant-select-selection-placeholder {
padding-inline-end: 59px;
text-align: initial;
font-size: 14px;
color: #626262;
font-weight: 400;
font-family: "Poppins", sans-serif;
}
.ant-select .ant-select-arrow {
display: flex;
align-items: center;
color: rgba(0, 0, 0, 0.517);
font-style: normal;
line-height: 1;
text-align: center;
text-transform: none;
vertical-align: -0.125em;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: absolute;
top: 50%;
inset-inline-start: auto;
inset-inline-end: 11px;
height: 110p;
margin-top: -6px;
font-size: 16px;
pointer-events: none;
}
.ant-select:not(.ant-select-customize-input) .ant-select-selector .ant-select-selection-search-input {
padding: 0;
border: none;
height: 20px;
font-size: 16px;
position: relative;
bottom: 0.5rem;
appearance: none;
}
.ant-table-wrapper .ant-table-column-sorter {
margin-inline-start: 4px;
color: rgba(0, 0, 0, 0.29);
font-size: 0;
transition: color 0.3s;
}
.ant-input:disabled {
color: #000;
// background-color: grey;
}
.ant-form-item-row .ant-form-item-control .ant-form-item-control-input .ant-form-item-control-input-content .ant-input-textarea-affix-wrapper {
height: 100%;
}
.ant-input-search-button {
display: none;
}
.ant-input-search {
// width: var(--INPUT_FIELD_WIDTH) !important;
width: min(60vw, 250px);
}
.searchDiv .ant-input {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem !important;
width: inherit;
// height: 45px;
border-radius: 10px !important;
font-size: 14px;
height: 45px;
}
.ant-collapse {
box-sizing: border-box;
margin: 0;
// width: 300px;
padding: 0;
// color: rgba(0, 0, 0, 0.88);
font-size: 14px;
line-height: 1.5714285714285714;
list-style: none;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
// background-color: rgba(0, 0, 0, 0.02);
border: none;
border-bottom: 0;
border-radius: 8px;
}
.ie_btn {
// margin-left:3rem ;
position: relative;
margin-left: 7px;
// bottom:1rem
}
.download-link {
background: none;
border: none;
color: var(--DEFAULT_SELECTED_COLOR);
// text-decoration: underline;
cursor: pointer;
}
.radio-buttons {
display: flex;
justify-content: center;
align-items: center;
height: 100px;
/* Adjust the height as needed */
margin-top: -43px;
/* Adjust the margin-top value as needed */
}
// .radio-buttons-payment {
// display: flex;
// justify-content: center;
// align-items: center;
// }
.radio-buttons-Payment {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
}
.cancel-link {
background: none;
border: none;
color: black;
// text-decoration: underline;
cursor: pointer;
}
.xltable {
width: 100%;
}
.xltable .ant-table-thead th {
font-size: 0.6rem !important;
font-weight: bold !important;
}
@media all and (max-width: 899px) {
.primary_Button {
width: 138px;
}
}
// vicky
.formSearch {
.ant-input {
padding: 6px 14px 6px 11px !important;
}
}
.ant-select-selection-item {
margin-top: 10px;
}

View File

@ -0,0 +1,19 @@
import { Button} from 'antd';
import './main.scss'
const TertiaryButton = ({buttonText,handleSubmit,icon, htmlType, disabled}) =>
(
<Button type="primary"
className="tertiary_Button"
onClick={handleSubmit}
htmlType= {htmlType ?"submit" : ''}
disabled={disabled ? disabled: false}
>{buttonText}{icon}
</Button>
);
export default TertiaryButton;

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 192.2 191.6" style="enable-background:new 0 0 192.2 191.6;" xml:space="preserve">
<g>
<g>
<path d="M132.1,179.6c0-1.8,0-3.2,0-4.5c0-20.1,0-40.3,0-60.4c0-11.5-7.6-19.1-19-19.1c-11.3,0-22.5,0-33.8,0
c-11.7,0-19.3,7.5-19.3,19.2c0,20.1,0,40.3,0,60.4c0,1.3,0,2.7,0,4.4c-10.5,0-20.7,0.2-30.9-0.1c-3.4-0.1-5.1-2.9-5.1-7.1
c0-21.9,0-43.8,0-65.7c0-1.5,0-3,0-4.5c-0.1-4.1-2.5-6.7-6.1-6.6c-3.6,0-5.9,2.6-5.9,6.8c0,20.5,0,41,0,61.6c0,3.4-0.1,6.8,0,10.1
c0.2,9.5,8,17.5,17.5,17.6c44.4,0.1,88.8,0.1,133.2,0c9.8,0,17.4-8.3,17.4-18.4c0-29.3,0-58.6,0-87.8c0-1.4,0-2.7,0-4.5
c0.8,0.5,1.3,0.7,1.8,1c3.6,2.4,6.8,2.1,9-0.7c2.1-2.8,1.6-6.2-1.6-8.9c-6-5.1-12.1-10.1-18.2-15.1c-1.1-0.9-2-2-3.2-2.6
c-7.6-4.2-10.3-10.5-8.9-18.9c0.5-2.9,0.2-6,0.1-9c-0.1-3.7-2.7-6.3-6.1-6.2c-3.3,0.1-5.7,2.5-5.8,6.1c-0.1,3.2,0,6.4,0,10.5
c-6.5-5.4-12.4-10.3-18.3-15.2c-6.6-5.5-13.2-11.1-19.9-16.6c-8.6-7.1-17.2-7.1-25.7,0C56.5,27.6,29.8,49.9,3.1,72.1
c-3.4,2.8-4,6.2-1.8,9.1c2.4,3,6,3,9.5,0C37.6,59,64.3,36.8,91,14.5c4.1-3.4,6.1-3.4,10.2,0c21.7,18.1,43.5,36.1,65.1,54.3
c1,0.8,1.7,2.5,1.8,3.7c0.1,33.4,0.1,66.8,0.1,100.2c0,4.1-2.1,6.7-5.9,6.8C152.4,179.7,142.5,179.6,132.1,179.6z M120.1,179.4
c-16.3,0-32.1,0-48,0c0-22.1,0-44,0-65.9c0-3.8,2.6-5.9,6.7-5.9c11.5,0,23,0,34.5,0c4.4,0,6.7,2.3,6.8,6.7c0.1,6,0,12,0,18
C120.1,147.9,120.1,163.5,120.1,179.4z"/>
<path d="M96.1,143.7c0.1-3.2-2.5-6-5.8-6.1c-3.4-0.1-6.2,2.6-6.2,6c0,3.2,2.7,5.9,5.9,5.9C93.3,149.6,96,146.9,96.1,143.7z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 192.2 191.6" style="enable-background:new 0 0 192.2 191.6;" xml:space="preserve">
<g>
<g>
<path d="M356.2,185c0-1.8,0-3.2,0-4.5c0-20.1,0-40.3,0-60.4c0-11.5-7.6-19.1-19-19.1c-11.3,0-22.5,0-33.8,0
c-11.7,0-19.3,7.5-19.3,19.2c0,20.1,0,40.3,0,60.4c0,1.3,0,2.7,0,4.4c-10.5,0-20.7,0.2-30.9-0.1c-3.4-0.1-5.1-2.9-5.1-7.1
c0-21.9,0-43.8,0-65.7c0-1.5,0-3,0-4.5c-0.1-4.1-2.5-6.7-6.1-6.6c-3.6,0-5.9,2.6-5.9,6.8c0,20.5,0,41,0,61.6c0,3.4-0.1,6.8,0,10.1
c0.2,9.5,8,17.5,17.5,17.6c44.4,0.1,88.8,0.1,133.2,0c9.8,0,17.4-8.3,17.4-18.4c0-29.3,0-58.6,0-87.8c0-1.4,0-2.7,0-4.5
c0.8,0.5,1.3,0.7,1.8,1c3.6,2.4,6.8,2.1,9-0.7c2.1-2.8,1.6-6.2-1.6-8.9c-6-5.1-12.1-10.1-18.2-15.1c-1.1-0.9-2-2-3.2-2.6
c-7.6-4.2-10.3-10.5-8.9-18.9c0.5-2.9,0.2-6,0.1-9c-0.1-3.7-2.7-6.3-6.1-6.2c-3.3,0.1-5.7,2.5-5.8,6.1c-0.1,3.2,0,6.4,0,10.5
c-6.5-5.4-12.4-10.3-18.3-15.2c-6.6-5.5-13.2-11.1-19.9-16.6c-8.6-7.1-17.2-7.1-25.7,0c-26.7,22.2-53.4,44.5-80.1,66.8
c-3.4,2.8-4,6.2-1.8,9.1c2.4,3,6,3,9.5,0c26.7-22.2,53.4-44.5,80.1-66.7c4.1-3.4,6.1-3.4,10.2,0c21.7,18.1,43.5,36.1,65.1,54.3
c1,0.8,1.7,2.5,1.8,3.7c0.1,33.4,0.1,66.8,0.1,100.2c0,4.1-2.1,6.7-5.9,6.8C376.5,185.1,366.6,185,356.2,185z M344.2,184.8
c-16.3,0-32.1,0-48,0c0-22.1,0-44,0-65.9c0-3.8,2.6-5.9,6.7-5.9c11.5,0,23,0,34.5,0c4.4,0,6.7,2.3,6.8,6.7c0.1,6,0,12,0,18
C344.2,153.3,344.2,168.9,344.2,184.8z"/>
<path d="M320.2,149.1c0.1-3.2-2.5-6-5.8-6.1c-3.4-0.1-6.2,2.6-6.2,6c0,3.2,2.7,5.9,5.9,5.9C317.4,155,320.1,152.4,320.2,149.1z"/>
</g>
</g>
<g>
<g>
<path d="M46.5,188.5c8.2,0,16.4,0.9,24.4-0.2c11-1.5,18.6-11.7,18.6-22.8c0-13,0-25.9,0-38.9c0-13.4-10.5-23.9-24.1-23.9
c-12.9,0-25.7,0-38.6,0c-13.5,0-23.9,10.5-23.9,24c0,12.8,0,25.7,0,38.5C3,178.6,13.5,189,27,189.1c6.5,0,13,0,19.5,0
C46.5,188.9,46.5,188.7,46.5,188.5z M46.3,114.2c6.4,0,12.8-0.1,19.1,0c7.3,0.1,12.6,5.2,12.6,12.4c0.1,12.8,0.1,25.7,0,38.5
c0,7.2-5.4,12.4-12.6,12.4c-12.8,0.1-25.5,0.1-38.3,0c-7.4,0-12.6-5.3-12.7-12.8c-0.1-12.6-0.1-25.2,0-37.8
c0-7.6,5.3-12.8,13-12.8C33.8,114.1,40,114.2,46.3,114.2z"/>
<path d="M46.2,5.3c-8.2,0-16.4-0.9-24.4,0.2C10.5,7.1,3,17.1,3,28.5c0,13,0,25.9,0,38.9C3,80.6,13.6,91,26.9,91.1
c12.9,0,25.7,0,38.6,0c13.7,0,24.1-10.5,24.1-24.2c0-12.7,0-25.4,0-38.2c0-13.7-10.5-24-24.3-24.1c-6.4,0-12.7,0-19.1,0
C46.2,4.9,46.2,5.1,46.2,5.3z M46.1,16.2c6.1,0,12.3,0,18.4,0c8.5,0,13.5,5,13.6,13.3c0,12.2,0,24.5,0,36.7
c0,8.2-5.1,13.3-13.2,13.3c-12.4,0-24.8,0-37.2,0c-8,0-13.2-5.1-13.2-13c0-12.5,0-25,0-37.5c0-7.9,5.2-12.9,13.2-13
C33.8,16.1,40,16.2,46.1,16.2z"/>
<path d="M101.8,48.2c0,8.1-0.9,16.4,0.2,24.4c1.5,10.8,11.6,18.5,22.6,18.5c13.2,0.1,26.5,0.1,39.7,0
c12.8-0.1,23.4-10.6,23.5-23.4c0.1-13.2,0.1-26.4,0-39.6c-0.1-12.8-10.6-23.4-23.4-23.4c-13.2-0.1-26.5-0.1-39.7,0
c-12.8,0.1-23.4,10.6-23.5,23.4c-0.1,6.7,0,13.4,0,20.2C101.4,48.2,101.6,48.2,101.8,48.2z M112.7,48c0-6.1,0-12.2,0-18.4
c0-8.4,5-13.5,13.4-13.5c12.3,0,24.5,0,36.8,0c8.2,0,13.3,5.1,13.3,13.2c0,12.4,0,24.7,0,37.1c0,7.9-5.1,13.1-13,13.1
c-12.5,0-25,0-37.5,0c-7.7,0-12.9-5.2-13-12.8C112.7,60.5,112.7,54.3,112.7,48z"/>
<path d="M144.7,189.1c23.8,0,43.1-19.5,43.1-43.4c0-23.7-19.6-43-43.5-43c-23.8,0-43.1,19.5-43.1,43.4
C101.2,169.8,120.8,189.1,144.7,189.1z M144.5,177.6c-17.4,0-31.7-14.2-31.8-31.6c-0.1-17.6,14.2-31.9,31.8-31.9
c17.4,0,31.7,14.2,31.8,31.5C176.3,163.3,162,177.6,144.5,177.6z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 192.2 191.6" xml:space="preserve">
<g>
<g id="shape-codepen">
<path d="M356.2,185c0-1.8,0-3.2,0-4.5c0-20.1,0-40.3,0-60.4c0-11.5-7.6-19.1-19-19.1c-11.3,0-22.5,0-33.8,0
c-11.7,0-19.3,7.5-19.3,19.2c0,20.1,0,40.3,0,60.4c0,1.3,0,2.7,0,4.4c-10.5,0-20.7,0.2-30.9-0.1c-3.4-0.1-5.1-2.9-5.1-7.1
c0-21.9,0-43.8,0-65.7c0-1.5,0-3,0-4.5c-0.1-4.1-2.5-6.7-6.1-6.6c-3.6,0-5.9,2.6-5.9,6.8c0,20.5,0,41,0,61.6c0,3.4-0.1,6.8,0,10.1
c0.2,9.5,8,17.5,17.5,17.6c44.4,0.1,88.8,0.1,133.2,0c9.8,0,17.4-8.3,17.4-18.4c0-29.3,0-58.6,0-87.8c0-1.4,0-2.7,0-4.5
c0.8,0.5,1.3,0.7,1.8,1c3.6,2.4,6.8,2.1,9-0.7c2.1-2.8,1.6-6.2-1.6-8.9c-6-5.1-12.1-10.1-18.2-15.1c-1.1-0.9-2-2-3.2-2.6
c-7.6-4.2-10.3-10.5-8.9-18.9c0.5-2.9,0.2-6,0.1-9c-0.1-3.7-2.7-6.3-6.1-6.2c-3.3,0.1-5.7,2.5-5.8,6.1c-0.1,3.2,0,6.4,0,10.5
c-6.5-5.4-12.4-10.3-18.3-15.2c-6.6-5.5-13.2-11.1-19.9-16.6c-8.6-7.1-17.2-7.1-25.7,0c-26.7,22.2-53.4,44.5-80.1,66.8
c-3.4,2.8-4,6.2-1.8,9.1c2.4,3,6,3,9.5,0c26.7-22.2,53.4-44.5,80.1-66.7c4.1-3.4,6.1-3.4,10.2,0c21.7,18.1,43.5,36.1,65.1,54.3
c1,0.8,1.7,2.5,1.8,3.7c0.1,33.4,0.1,66.8,0.1,100.2c0,4.1-2.1,6.7-5.9,6.8C376.5,185.1,366.6,185,356.2,185z M344.2,184.8
c-16.3,0-32.1,0-48,0c0-22.1,0-44,0-65.9c0-3.8,2.6-5.9,6.7-5.9c11.5,0,23,0,34.5,0c4.4,0,6.7,2.3,6.8,6.7c0.1,6,0,12,0,18
C344.2,153.3,344.2,168.9,344.2,184.8z"/>
<path d="M320.2,149.1c0.1-3.2-2.5-6-5.8-6.1c-3.4-0.1-6.2,2.6-6.2,6c0,3.2,2.7,5.9,5.9,5.9C317.4,155,320.1,152.4,320.2,149.1z"/>
</g>
</g>
<g>
<g>
<path d="M482.2,205.8c8.2,0,16.4,0.9,24.4-0.2c11-1.5,18.6-11.7,18.6-22.8c0-13,0-25.9,0-38.9c0-13.4-10.5-23.9-24.1-23.9
c-12.9,0-25.7,0-38.6,0c-13.5,0-23.9,10.5-23.9,24c0,12.8,0,25.7,0,38.5c0,13.4,10.6,23.8,24.1,23.9c6.5,0,13,0,19.5,0
C482.2,206.2,482.2,206,482.2,205.8z M482,131.5c6.4,0,12.8-0.1,19.1,0c7.3,0.1,12.6,5.2,12.6,12.4c0.1,12.8,0.1,25.7,0,38.5
c0,7.2-5.4,12.4-12.6,12.4c-12.8,0.1-25.5,0.1-38.3,0c-7.4,0-12.6-5.3-12.7-12.8c-0.1-12.6-0.1-25.2,0-37.8
c0-7.6,5.3-12.8,13-12.8C469.4,131.4,475.7,131.5,482,131.5z"/>
<path d="M481.9,22.6c-8.2,0-16.4-0.9-24.4,0.2c-11.3,1.6-18.8,11.6-18.8,23c0,13,0,25.9,0,38.9c0.1,13.2,10.6,23.7,23.9,23.7
c12.9,0,25.7,0,38.6,0c13.7,0,24.1-10.5,24.1-24.2c0-12.7,0-25.4,0-38.2c0-13.7-10.5-24-24.3-24.1c-6.4,0-12.7,0-19.1,0
C481.9,22.2,481.9,22.4,481.9,22.6z M481.8,33.5c6.1,0,12.3,0,18.4,0c8.5,0,13.5,5,13.6,13.3c0,12.2,0,24.5,0,36.7
c0,8.2-5.1,13.3-13.2,13.3c-12.4,0-24.8,0-37.2,0c-8,0-13.2-5.1-13.2-13c0-12.5,0-25,0-37.5c0-7.9,5.2-12.9,13.2-13
C469.5,33.4,475.6,33.5,481.8,33.5z"/>
<path d="M537.5,65.5c0,8.1-0.9,16.4,0.2,24.4c1.5,10.8,11.6,18.5,22.6,18.5c13.2,0.1,26.5,0.1,39.7,0
c12.8-0.1,23.4-10.6,23.5-23.4c0.1-13.2,0.1-26.4,0-39.6c-0.1-12.8-10.6-23.4-23.4-23.4c-13.2-0.1-26.5-0.1-39.7,0
C547.5,22,537,32.6,536.9,45.3c-0.1,6.7,0,13.4,0,20.2C537.1,65.5,537.3,65.5,537.5,65.5z M548.4,65.3c0-6.1,0-12.2,0-18.4
c0-8.4,5-13.5,13.4-13.5c12.3,0,24.5,0,36.8,0c8.2,0,13.3,5.1,13.3,13.2c0,12.4,0,24.7,0,37.1c0,7.9-5.1,13.1-13,13.1
c-12.5,0-25,0-37.5,0c-7.7,0-12.9-5.2-13-12.8C548.3,77.8,548.4,71.6,548.4,65.3z"/>
<path d="M580.4,206.4c23.8,0,43.1-19.5,43.1-43.4c0-23.7-19.6-43-43.5-43c-23.8,0-43.1,19.5-43.1,43.4
C536.9,187.1,556.4,206.4,580.4,206.4z M580.2,194.9c-17.4,0-31.7-14.2-31.8-31.6c-0.1-17.6,14.2-31.9,31.8-31.9
c17.4,0,31.7,14.2,31.8,31.5C612,180.6,597.7,194.9,580.2,194.9z"/>
</g>
</g>
<g>
<g>
<path d="M748.6,148.1c0.9-0.2,2-0.5,3.2-0.9c24.9-8.3,49.9-16.5,74.8-24.8c5.4-1.8,6.4-3.7,4.6-9.1C823,88.6,814.8,63.8,806.5,39
c-1.6-5-3.7-6-8.7-4.3c-25.2,8.3-50.4,16.7-75.5,25c-4.8,1.6-5.9,3.7-4.3,8.4c8.3,25,16.7,50,24.9,75
C743.8,145.9,745.2,147.9,748.6,148.1z M820.6,113.7c-23.3,7.7-46.1,15.2-69.1,22.8c-7.6-23-15.1-45.5-22.6-68.2
c10.1-3.3,19.8-6.5,29.6-9.8c0.5,1.6,0.9,2.6,1.2,3.6c1.6,4.9,3.2,9.7,4.9,14.6c1.3,3.5,3.9,4.9,6.9,3.7c2.6-0.9,3.6-3.7,2.7-6.9
c-0.3-1.1-0.7-2.3-1-3.4c-1.6-4.9-3.3-9.8-4.9-14.9c10.1-3.3,19.8-6.5,29.7-9.7C805.6,68.5,813,90.8,820.6,113.7z"/>
<path d="M722,153c-4.2,3.1-7.7,7-9,12.3c-2.3,8.6-0.4,16.2,6.2,22.3c6.7,6.1,14.7,7.5,23.1,4.2c8.4-3.4,13-10,13.7-19
c0.2-2.8,1.2-3.6,3.5-4.3c25.1-8.2,50.2-16.5,75.2-24.8c5.2-1.7,7.3-4.3,6-7.6c-1.2-3.3-4-4-9.2-2.3c-25.1,8.3-50.1,16.6-75.2,25
c-2.5,0.8-4,0.6-5.7-1.6c-4.7-5.8-11.2-7.9-18.7-7.8c-4.7-14.1-9.4-28.1-14-42.1c-5.8-17.5-11.7-35.1-17.4-52.6
c-1-3.1-2.7-5.1-5.6-6.5c-9.9-4.8-19.7-9.8-29.5-14.8c-2.7-1.4-5.3-1.7-7.5,0.9c-2.3,2.8-1.3,6.2,2.6,8.1
c9.2,4.7,18.4,9.2,27.5,13.9c1.3,0.7,2.7,2.1,3.2,3.4c10.3,30.8,20.5,61.6,30.8,92.3C722.1,152.3,722,152.5,722,153z M722.3,171.6
c-0.1-6.2,5.2-11.7,11.5-11.9c6.4-0.1,12.1,5.4,12,11.8c0,6.2-5.5,11.6-11.8,11.7C727.8,183.2,722.4,177.9,722.3,171.6z"/>
</g>
</g>
<g>
<g>
<path d="M129.5,28c0-2.1,0-3.8,0-5.5c0-4.1-1.2-5.5-5.3-5.7c-3.4-0.2-6.8-0.2-10.2,0c-2,0.1-3-0.5-3.9-2.4
C106.3,6.7,99.7,3.7,91.4,3.5C83.2,3.4,77,7.1,73,14.3c-1,1.9-2,2.6-4.1,2.5c-2.9-0.2-5.8-0.1-8.7,0C55,16.8,53.9,18,53.9,23
c0,1.4,0,2.9,0,5c-5.1,0-10,0-14.9,0c-4.9-0.1-9.4,0.9-13.3,4c-5,4-7.3,9.3-7.3,15.6c0,41.4-0.1,82.7,0,124.1
c0,10.6,7.6,18.1,18.2,18.1c36.8,0.1,73.5,0.1,110.3,0c10.4,0,18-7.5,18-17.9c0.1-41.5,0.1-83,0-124.5c0-11.4-7.7-19.1-19.2-19.4
C140.4,27.8,135.1,28,129.5,28z M129.5,36.9c5.1,0,9.7,0,14.3,0c8.8,0,12.1,3.3,12.1,12c0,40.4,0,80.7,0,121.1
c0,8-3.1,11.1-11.2,11.1c-35.4,0-70.8,0-106.2,0c-7.9,0-11-3.1-11-10.9c0-40.4,0-80.7,0-121.1c0-1.2,0-2.4,0.1-3.6
c0.4-4.1,3.8-8.1,7.9-8.3c6-0.3,12-0.1,18.5-0.1c0,3.4-0.1,6.5,0,9.6c0.1,3.9,1.7,5.7,5.5,5.7c21.5,0,42.9,0,64.4,0
c3.9,0,5.6-1.9,5.7-5.9C129.5,43.4,129.5,40.4,129.5,36.9z M120.4,43.3c-19.3,0-38.3,0-57.3,0c0-5.9,0-11.5,0-17.6
c3.6,0,7,0,10.3,0c3.2,0,5.1-1.2,6.1-4.5c1.7-5.7,6.6-9.5,11.9-9.6c5.2-0.1,10.2,3.7,11.9,9.5c0.9,3.2,2.8,4.6,6,4.5
c3.6,0,7.2,0,11.1,0C120.4,31.8,120.4,37.4,120.4,43.3z"/>
<path d="M112.8,112.1c-7.6,0-15.2,0-22.9,0c-1.1,0-2.2,0-3.2,0.2c-2.4,0.5-4.3,2.1-3.6,4.6c0.5,1.6,2.2,3.3,3.8,4
c1.8,0.7,4,0.3,6.1,0.3c14,0,28.1,0,42.1,0c1.2,0,2.7,0.3,3.6-0.2c1.5-1,3.4-2.4,3.8-4c0.7-2.4-1.2-4-3.6-4.5
c-1-0.2-2.2-0.2-3.2-0.2C128,112.1,120.4,112.1,112.8,112.1z"/>
<path d="M112.8,147.6c-8.2,0-16.5-0.2-24.7,0.1c-1.7,0.1-3.9,1.7-4.9,3.2c-0.6,0.9,0.7,3.3,1.6,4.6c0.5,0.7,2.2,0.8,3.3,0.8
c16.3,0.1,32.7,0.1,49,0c3.6,0,5.5-1.6,5.4-4.4c-0.2-3.4-2.4-4.4-5.5-4.4C129.1,147.7,120.9,147.6,112.8,147.6z"/>
<path d="M112.4,76.8c-8,0-16,0-24,0c-3,0-5.3,0.8-5.5,4.3c-0.2,3,1.8,4.6,5.7,4.6c16.1,0,32.2,0,48.3,0c3.8,0,5.8-1.7,5.8-4.5
c0-2.9-1.9-4.3-5.9-4.3C128.7,76.8,120.6,76.8,112.4,76.8z"/>
<path d="M54.3,115.9c-2.1-2.1-3.9-4-5.8-5.9c-2-1.9-4.3-2.5-6.4-0.3c-2,2-1.8,4.4,0.2,6.5c2.8,3,5.7,5.9,8.6,8.9
c2,2.1,4.3,2.2,6.4,0.3c5.8-5.3,11.5-10.8,17.1-16.2c1.9-1.9,2.1-4.2,0.3-6.2c-1.9-2.1-4.3-2.2-6.4-0.3c-3.5,3.2-6.8,6.5-10.3,9.8
C56.8,113.5,55.6,114.7,54.3,115.9z"/>
<path d="M54.2,80.4c-2-2-3.8-4-5.7-5.8c-2-2-4.3-2.5-6.4-0.5c-2.1,2-1.8,4.4,0.1,6.5c2.8,3,5.7,5.9,8.6,8.9
c2.2,2.2,4.6,2.2,6.8,0.1c5.7-5.3,11.3-10.6,16.8-16c2.1-2,2.2-4.4,0.2-6.4c-2-2-4.4-1.8-6.4,0.2C63.5,71.7,58.9,76,54.2,80.4z"/>
<path d="M54.3,151.4c-2.2-2.2-4.1-4.1-6-6c-1.9-1.8-4.2-2.2-6.1-0.3c-2.1,2-1.9,4.4,0,6.4c2.9,3.1,5.9,6.1,8.9,9.1
c1.9,2,4.1,2.1,6.1,0.2c5.8-5.4,11.5-10.8,17.2-16.2c2-2,2.3-4.3,0.3-6.4c-2-2.1-4.4-1.9-6.4,0C63.5,142.5,59,146.9,54.3,151.4z"
/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 192.2 191.6" style="fill:none; stroke-width:3;stroke:#491EC4" xml:space="preserve">
<g>
<g>
<path d="M356.2,185c0-1.8,0-3.2,0-4.5c0-20.1,0-40.3,0-60.4c0-11.5-7.6-19.1-19-19.1c-11.3,0-22.5,0-33.8,0
c-11.7,0-19.3,7.5-19.3,19.2c0,20.1,0,40.3,0,60.4c0,1.3,0,2.7,0,4.4c-10.5,0-20.7,0.2-30.9-0.1c-3.4-0.1-5.1-2.9-5.1-7.1
c0-21.9,0-43.8,0-65.7c0-1.5,0-3,0-4.5c-0.1-4.1-2.5-6.7-6.1-6.6c-3.6,0-5.9,2.6-5.9,6.8c0,20.5,0,41,0,61.6c0,3.4-0.1,6.8,0,10.1
c0.2,9.5,8,17.5,17.5,17.6c44.4,0.1,88.8,0.1,133.2,0c9.8,0,17.4-8.3,17.4-18.4c0-29.3,0-58.6,0-87.8c0-1.4,0-2.7,0-4.5
c0.8,0.5,1.3,0.7,1.8,1c3.6,2.4,6.8,2.1,9-0.7c2.1-2.8,1.6-6.2-1.6-8.9c-6-5.1-12.1-10.1-18.2-15.1c-1.1-0.9-2-2-3.2-2.6
c-7.6-4.2-10.3-10.5-8.9-18.9c0.5-2.9,0.2-6,0.1-9c-0.1-3.7-2.7-6.3-6.1-6.2c-3.3,0.1-5.7,2.5-5.8,6.1c-0.1,3.2,0,6.4,0,10.5
c-6.5-5.4-12.4-10.3-18.3-15.2c-6.6-5.5-13.2-11.1-19.9-16.6c-8.6-7.1-17.2-7.1-25.7,0c-26.7,22.2-53.4,44.5-80.1,66.8
c-3.4,2.8-4,6.2-1.8,9.1c2.4,3,6,3,9.5,0c26.7-22.2,53.4-44.5,80.1-66.7c4.1-3.4,6.1-3.4,10.2,0c21.7,18.1,43.5,36.1,65.1,54.3
c1,0.8,1.7,2.5,1.8,3.7c0.1,33.4,0.1,66.8,0.1,100.2c0,4.1-2.1,6.7-5.9,6.8C376.5,185.1,366.6,185,356.2,185z M344.2,184.8
c-16.3,0-32.1,0-48,0c0-22.1,0-44,0-65.9c0-3.8,2.6-5.9,6.7-5.9c11.5,0,23,0,34.5,0c4.4,0,6.7,2.3,6.8,6.7c0.1,6,0,12,0,18
C344.2,153.3,344.2,168.9,344.2,184.8z"/>
<path d="M320.2,149.1c0.1-3.2-2.5-6-5.8-6.1c-3.4-0.1-6.2,2.6-6.2,6c0,3.2,2.7,5.9,5.9,5.9C317.4,155,320.1,152.4,320.2,149.1z"/>
</g>
</g>
<g>
<g>
<path d="M482.2,205.8c8.2,0,16.4,0.9,24.4-0.2c11-1.5,18.6-11.7,18.6-22.8c0-13,0-25.9,0-38.9c0-13.4-10.5-23.9-24.1-23.9
c-12.9,0-25.7,0-38.6,0c-13.5,0-23.9,10.5-23.9,24c0,12.8,0,25.7,0,38.5c0,13.4,10.6,23.8,24.1,23.9c6.5,0,13,0,19.5,0
C482.2,206.2,482.2,206,482.2,205.8z M482,131.5c6.4,0,12.8-0.1,19.1,0c7.3,0.1,12.6,5.2,12.6,12.4c0.1,12.8,0.1,25.7,0,38.5
c0,7.2-5.4,12.4-12.6,12.4c-12.8,0.1-25.5,0.1-38.3,0c-7.4,0-12.6-5.3-12.7-12.8c-0.1-12.6-0.1-25.2,0-37.8
c0-7.6,5.3-12.8,13-12.8C469.4,131.4,475.7,131.5,482,131.5z"/>
<path d="M481.9,22.6c-8.2,0-16.4-0.9-24.4,0.2c-11.3,1.6-18.8,11.6-18.8,23c0,13,0,25.9,0,38.9c0.1,13.2,10.6,23.7,23.9,23.7
c12.9,0,25.7,0,38.6,0c13.7,0,24.1-10.5,24.1-24.2c0-12.7,0-25.4,0-38.2c0-13.7-10.5-24-24.3-24.1c-6.4,0-12.7,0-19.1,0
C481.9,22.2,481.9,22.4,481.9,22.6z M481.8,33.5c6.1,0,12.3,0,18.4,0c8.5,0,13.5,5,13.6,13.3c0,12.2,0,24.5,0,36.7
c0,8.2-5.1,13.3-13.2,13.3c-12.4,0-24.8,0-37.2,0c-8,0-13.2-5.1-13.2-13c0-12.5,0-25,0-37.5c0-7.9,5.2-12.9,13.2-13
C469.5,33.4,475.6,33.5,481.8,33.5z"/>
<path d="M537.5,65.5c0,8.1-0.9,16.4,0.2,24.4c1.5,10.8,11.6,18.5,22.6,18.5c13.2,0.1,26.5,0.1,39.7,0
c12.8-0.1,23.4-10.6,23.5-23.4c0.1-13.2,0.1-26.4,0-39.6c-0.1-12.8-10.6-23.4-23.4-23.4c-13.2-0.1-26.5-0.1-39.7,0
C547.5,22,537,32.6,536.9,45.3c-0.1,6.7,0,13.4,0,20.2C537.1,65.5,537.3,65.5,537.5,65.5z M548.4,65.3c0-6.1,0-12.2,0-18.4
c0-8.4,5-13.5,13.4-13.5c12.3,0,24.5,0,36.8,0c8.2,0,13.3,5.1,13.3,13.2c0,12.4,0,24.7,0,37.1c0,7.9-5.1,13.1-13,13.1
c-12.5,0-25,0-37.5,0c-7.7,0-12.9-5.2-13-12.8C548.3,77.8,548.4,71.6,548.4,65.3z"/>
<path d="M580.4,206.4c23.8,0,43.1-19.5,43.1-43.4c0-23.7-19.6-43-43.5-43c-23.8,0-43.1,19.5-43.1,43.4
C536.9,187.1,556.4,206.4,580.4,206.4z M580.2,194.9c-17.4,0-31.7-14.2-31.8-31.6c-0.1-17.6,14.2-31.9,31.8-31.9
c17.4,0,31.7,14.2,31.8,31.5C612,180.6,597.7,194.9,580.2,194.9z"/>
</g>
</g>
<g>
<g>
<path d="M748.6,148.1c0.9-0.2,2-0.5,3.2-0.9c24.9-8.3,49.9-16.5,74.8-24.8c5.4-1.8,6.4-3.7,4.6-9.1C823,88.6,814.8,63.8,806.5,39
c-1.6-5-3.7-6-8.7-4.3c-25.2,8.3-50.4,16.7-75.5,25c-4.8,1.6-5.9,3.7-4.3,8.4c8.3,25,16.7,50,24.9,75
C743.8,145.9,745.2,147.9,748.6,148.1z M820.6,113.7c-23.3,7.7-46.1,15.2-69.1,22.8c-7.6-23-15.1-45.5-22.6-68.2
c10.1-3.3,19.8-6.5,29.6-9.8c0.5,1.6,0.9,2.6,1.2,3.6c1.6,4.9,3.2,9.7,4.9,14.6c1.3,3.5,3.9,4.9,6.9,3.7c2.6-0.9,3.6-3.7,2.7-6.9
c-0.3-1.1-0.7-2.3-1-3.4c-1.6-4.9-3.3-9.8-4.9-14.9c10.1-3.3,19.8-6.5,29.7-9.7C805.6,68.5,813,90.8,820.6,113.7z"/>
<path d="M722,153c-4.2,3.1-7.7,7-9,12.3c-2.3,8.6-0.4,16.2,6.2,22.3c6.7,6.1,14.7,7.5,23.1,4.2c8.4-3.4,13-10,13.7-19
c0.2-2.8,1.2-3.6,3.5-4.3c25.1-8.2,50.2-16.5,75.2-24.8c5.2-1.7,7.3-4.3,6-7.6c-1.2-3.3-4-4-9.2-2.3c-25.1,8.3-50.1,16.6-75.2,25
c-2.5,0.8-4,0.6-5.7-1.6c-4.7-5.8-11.2-7.9-18.7-7.8c-4.7-14.1-9.4-28.1-14-42.1c-5.8-17.5-11.7-35.1-17.4-52.6
c-1-3.1-2.7-5.1-5.6-6.5c-9.9-4.8-19.7-9.8-29.5-14.8c-2.7-1.4-5.3-1.7-7.5,0.9c-2.3,2.8-1.3,6.2,2.6,8.1
c9.2,4.7,18.4,9.2,27.5,13.9c1.3,0.7,2.7,2.1,3.2,3.4c10.3,30.8,20.5,61.6,30.8,92.3C722.1,152.3,722,152.5,722,153z M722.3,171.6
c-0.1-6.2,5.2-11.7,11.5-11.9c6.4-0.1,12.1,5.4,12,11.8c0,6.2-5.5,11.6-11.8,11.7C727.8,183.2,722.4,177.9,722.3,171.6z"/>
</g>
</g>
<g>
<g>
<path d="M971.6,40.2c0-2.1,0-3.8,0-5.5c0-4.1-1.2-5.5-5.3-5.7c-3.4-0.2-6.8-0.2-10.2,0c-2,0.1-3-0.5-3.9-2.4
c-3.8-7.7-10.4-10.8-18.7-10.9c-8.3-0.1-14.5,3.5-18.5,10.7c-1,1.9-2,2.6-4.1,2.5c-2.9-0.2-5.8-0.1-8.7,0
c-5.1,0.1-6.2,1.2-6.2,6.3c0,1.4,0,2.9,0,5c-5.1,0-10,0-14.9,0c-4.9-0.1-9.4,0.9-13.3,4c-5,4-7.3,9.3-7.3,15.6
c0,41.4-0.1,82.7,0,124.1c0,10.6,7.6,18.1,18.2,18.1c36.8,0.1,73.5,0.1,110.3,0c10.4,0,18-7.5,18-17.9c0.1-41.5,0.1-83,0-124.5
c0-11.4-7.7-19.1-19.2-19.4C982.6,40.1,977.3,40.2,971.6,40.2z M971.6,49.2c5.1,0,9.7,0,14.3,0c8.8,0,12.1,3.3,12.1,12
c0,40.4,0,80.7,0,121.1c0,8-3.1,11.1-11.2,11.1c-35.4,0-70.8,0-106.2,0c-7.9,0-11-3.1-11-10.9c0-40.4,0-80.7,0-121.1
c0-1.2,0-2.4,0.1-3.6c0.4-4.1,3.8-8.1,7.9-8.3c6-0.3,12-0.1,18.5-0.1c0,3.4-0.1,6.5,0,9.6c0.1,3.9,1.7,5.7,5.5,5.7
c21.5,0,42.9,0,64.4,0c3.9,0,5.6-1.9,5.7-5.9C971.7,55.7,971.6,52.6,971.6,49.2z M962.5,55.5c-19.3,0-38.3,0-57.3,0
c0-5.9,0-11.5,0-17.6c3.6,0,7,0,10.3,0c3.2,0,5.1-1.2,6.1-4.5c1.7-5.7,6.6-9.5,11.9-9.6c5.2-0.1,10.2,3.7,11.9,9.5
c0.9,3.2,2.8,4.6,6,4.5c3.6,0,7.2,0,11.1,0C962.5,44,962.5,49.7,962.5,55.5z"/>
<path d="M954.9,124.4c-7.6,0-15.2,0-22.9,0c-1.1,0-2.2,0-3.2,0.2c-2.4,0.5-4.3,2.1-3.6,4.6c0.5,1.6,2.2,3.3,3.8,4
c1.8,0.7,4,0.3,6.1,0.3c14,0,28.1,0,42.1,0c1.2,0,2.7,0.3,3.6-0.2c1.5-1,3.4-2.4,3.8-4c0.7-2.4-1.2-4-3.6-4.5
c-1-0.2-2.2-0.2-3.2-0.2C970.2,124.4,962.5,124.4,954.9,124.4z"/>
<path d="M955,159.9c-8.2,0-16.5-0.2-24.7,0.1c-1.7,0.1-3.9,1.7-4.9,3.2c-0.6,0.9,0.7,3.3,1.6,4.6c0.5,0.7,2.2,0.8,3.3,0.8
c16.3,0.1,32.7,0.1,49,0c3.6,0,5.5-1.6,5.4-4.4c-0.2-3.4-2.4-4.4-5.5-4.4C971.2,159.9,963.1,159.9,955,159.9z"/>
<path d="M954.6,89.1c-8,0-16,0-24,0c-3,0-5.3,0.8-5.5,4.3c-0.2,3,1.8,4.6,5.7,4.6c16.1,0,32.2,0,48.3,0c3.8,0,5.8-1.7,5.8-4.5
c0-2.9-1.9-4.3-5.9-4.3C970.8,89.1,962.7,89.1,954.6,89.1z"/>
<path d="M896.5,128.1c-2.1-2.1-3.9-4-5.8-5.9c-2-1.9-4.3-2.5-6.4-0.3c-2,2-1.8,4.4,0.2,6.5c2.8,3,5.7,5.9,8.6,8.9
c2,2.1,4.3,2.2,6.4,0.3c5.8-5.3,11.5-10.8,17.1-16.2c1.9-1.9,2.1-4.2,0.3-6.2c-1.9-2.1-4.3-2.2-6.4-0.3c-3.5,3.2-6.8,6.5-10.3,9.8
C899,125.8,897.8,126.9,896.5,128.1z"/>
<path d="M896.4,92.7c-2-2-3.8-4-5.7-5.8c-2-2-4.3-2.5-6.4-0.5c-2.1,2-1.8,4.4,0.1,6.5c2.8,3,5.7,5.9,8.6,8.9
c2.2,2.2,4.6,2.2,6.8,0.1c5.7-5.3,11.3-10.6,16.8-16c2.1-2,2.2-4.4,0.2-6.4c-2-2-4.4-1.8-6.4,0.2
C905.6,83.9,901.1,88.2,896.4,92.7z"/>
<path d="M896.5,163.6c-2.2-2.2-4.1-4.1-6-6c-1.9-1.8-4.2-2.2-6.1-0.3c-2.1,2-1.9,4.4,0,6.4c2.9,3.1,5.9,6.1,8.9,9.1
c1.9,2,4.1,2.1,6.1,0.2c5.8-5.4,11.5-10.8,17.2-16.2c2-2,2.3-4.3,0.3-6.4c-2-2.1-4.4-1.9-6.4,0
C905.7,154.8,901.2,159.2,896.5,163.6z"/>
</g>
</g>
<g>
<g>
<path d="M1026.8,118.7c0,22.8,0.3,45.6-0.1,68.4c-0.2,13.2,9,19.3,19.2,19.2c36.6-0.4,73.3-0.1,109.9-0.2c11,0,17.8-6.4,17.8-17.4
c0.1-46.8,0.1-93.6,0-140.4c0-10.9-6.8-17.4-17.8-17.4c-2.4,0-4.8-0.1-7.2,0c-6.5,0.3-12.5,0.2-17.8-5.1c-4-4-10-5-15.8-5
c-9.8,0-19.5,0-29.3,0c-7.8,0-14.5,2.8-20.3,8.1c-1.3,1.1-3.3,1.9-5,2c-5.4,0.2-10.8,0.1-16.3,0.1c-10.2,0-17,6.5-17.2,16.8
c-0.2,10.5-0.1,21-0.1,31.5C1026.8,92.4,1026.8,105.5,1026.8,118.7z M1143.1,41.8c4.2,0,8.1,0,11.9,0c6,0,7.7,1.7,7.7,7.8
c0,46,0,91.9,0,137.9c0,6.3-1.6,7.8-8,7.8c-36.1,0-72.3,0-108.4,0c-7.3,0-8.7-1.3-8.7-8.6c0-45.5,0-91,0-136.4
c0-1.2,0-2.4,0.1-3.6c0.4-2.9,2.2-4.7,5-4.8c4.8-0.2,9.6-0.1,14.4-0.1c-0.4,2.6-0.8,4.8-1,7.1c-0.3,4.2,1.9,6.6,6.2,6.6
c25.2,0,50.4,0,75.5,0c4.2,0,6.5-2.4,6.2-6.6C1143.9,46.6,1143.5,44.4,1143.1,41.8z M1068.4,44.5c1.5-6.9,8.5-12.6,16.1-12.8
c10.5-0.2,20.9-0.2,31.4,0c7.7,0.2,14.9,6.2,15.8,12.8C1110.7,44.5,1089.6,44.5,1068.4,44.5z"/>
<path d="M1118.6,83.8c-4.1,4.1-8.1,8.1-12.4,12.3c-4.7-4.7-9.6-9.7-14.5-14.6c-4.1-4.1-6.5-4.1-10.6,0.1
c-5.7,5.7-11.4,11.4-17.1,17.1c-2.9,3-3.2,6.2-0.9,8.6c2.5,2.5,5.3,2.2,8.5-0.9c4.9-4.8,9.7-9.7,14.8-14.8c5,5,9.9,9.9,14.8,14.8
c4.4,4.4,6.5,4.4,10.8,0c4.8-4.8,9.7-9.7,14.7-14.7c0.5,1.2,0.7,2.5,1.5,3c1.6,1.1,3.7,2.6,5.2,2.3c1.7-0.4,4.1-2.5,4.2-4
c0.5-5.4,0.4-10.8,0.1-16.2c-0.2-2.8-2.4-4-5.2-4c-4.3,0-8.7,0-13,0c-3,0-5.2,1.1-5.8,4.2c-0.6,3.2,0.8,5.3,3.9,6.4
C1118,83.4,1118.3,83.6,1118.6,83.8z"/>
<path d="M1118.3,164.3c-7,0-14-0.1-20.9,0c-4.5,0.1-7,3.2-5.9,7c0.7,2.5,2.6,3.7,5.1,3.8c14.6,0,29.1,0.1,43.7,0
c3.2,0,5.3-2.3,5.4-5.1c0.1-3.1-2.1-5.6-5.8-5.6C1132.7,164.2,1125.5,164.3,1118.3,164.3C1118.3,164.3,1118.3,164.3,1118.3,164.3z
"/>
<path d="M1118.6,144c6.9,0,13.7,0,20.6,0c4,0,6.4-2,6.5-5.2c0.1-3.4-2.4-5.6-6.5-5.7c-13.8-0.1-27.7-0.1-41.5,0
c-4.1,0-6.5,2.3-6.4,5.7c0.1,3.3,2.3,5.1,6.5,5.2C1104.7,144,1111.6,144,1118.6,144z"/>
<path d="M1065.9,168.1c-4.7-3.9-8-4.3-10.3-1.7c-2.2,2.6-1.5,5.6,2.2,9.3c7.3,7.4,7.9,7.4,15.2,0.1c2.6-2.6,5.3-5.2,7.9-7.9
c2.4-2.6,2.4-5.5,0.3-7.7c-2.2-2.4-5.3-2.4-8,0.2C1070.7,162.8,1068.4,165.4,1065.9,168.1z"/>
<path d="M1065.6,136.9c-1-1-1.6-1.6-2.3-2.1c-2.4-1.9-5.5-1.9-7.5,0.2c-2,2-2,5.3,0,7.5c1.9,2.1,3.8,4,5.9,5.9
c2.4,2.1,5.4,2.1,7.7-0.2c3.9-3.8,7.7-7.6,11.5-11.5c2.4-2.5,2.4-5.5,0.2-7.8c-2.3-2.3-5.3-2.3-8,0.3
C1070.6,131.6,1068.3,134.2,1065.6,136.9z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 192.2 191.6" style="enable-background:new 0 0 192.2 191.6;" xml:space="preserve">
<g>
<g>
<path d="M356.2,185c0-1.8,0-3.2,0-4.5c0-20.1,0-40.3,0-60.4c0-11.5-7.6-19.1-19-19.1c-11.3,0-22.5,0-33.8,0
c-11.7,0-19.3,7.5-19.3,19.2c0,20.1,0,40.3,0,60.4c0,1.3,0,2.7,0,4.4c-10.5,0-20.7,0.2-30.9-0.1c-3.4-0.1-5.1-2.9-5.1-7.1
c0-21.9,0-43.8,0-65.7c0-1.5,0-3,0-4.5c-0.1-4.1-2.5-6.7-6.1-6.6c-3.6,0-5.9,2.6-5.9,6.8c0,20.5,0,41,0,61.6c0,3.4-0.1,6.8,0,10.1
c0.2,9.5,8,17.5,17.5,17.6c44.4,0.1,88.8,0.1,133.2,0c9.8,0,17.4-8.3,17.4-18.4c0-29.3,0-58.6,0-87.8c0-1.4,0-2.7,0-4.5
c0.8,0.5,1.3,0.7,1.8,1c3.6,2.4,6.8,2.1,9-0.7c2.1-2.8,1.6-6.2-1.6-8.9c-6-5.1-12.1-10.1-18.2-15.1c-1.1-0.9-2-2-3.2-2.6
c-7.6-4.2-10.3-10.5-8.9-18.9c0.5-2.9,0.2-6,0.1-9c-0.1-3.7-2.7-6.3-6.1-6.2c-3.3,0.1-5.7,2.5-5.8,6.1c-0.1,3.2,0,6.4,0,10.5
c-6.5-5.4-12.4-10.3-18.3-15.2c-6.6-5.5-13.2-11.1-19.9-16.6c-8.6-7.1-17.2-7.1-25.7,0c-26.7,22.2-53.4,44.5-80.1,66.8
c-3.4,2.8-4,6.2-1.8,9.1c2.4,3,6,3,9.5,0c26.7-22.2,53.4-44.5,80.1-66.7c4.1-3.4,6.1-3.4,10.2,0c21.7,18.1,43.5,36.1,65.1,54.3
c1,0.8,1.7,2.5,1.8,3.7c0.1,33.4,0.1,66.8,0.1,100.2c0,4.1-2.1,6.7-5.9,6.8C376.5,185.1,366.6,185,356.2,185z M344.2,184.8
c-16.3,0-32.1,0-48,0c0-22.1,0-44,0-65.9c0-3.8,2.6-5.9,6.7-5.9c11.5,0,23,0,34.5,0c4.4,0,6.7,2.3,6.8,6.7c0.1,6,0,12,0,18
C344.2,153.3,344.2,168.9,344.2,184.8z"/>
<path d="M320.2,149.1c0.1-3.2-2.5-6-5.8-6.1c-3.4-0.1-6.2,2.6-6.2,6c0,3.2,2.7,5.9,5.9,5.9C317.4,155,320.1,152.4,320.2,149.1z"/>
</g>
</g>
<g>
<g>
<path d="M482.2,205.8c8.2,0,16.4,0.9,24.4-0.2c11-1.5,18.6-11.7,18.6-22.8c0-13,0-25.9,0-38.9c0-13.4-10.5-23.9-24.1-23.9
c-12.9,0-25.7,0-38.6,0c-13.5,0-23.9,10.5-23.9,24c0,12.8,0,25.7,0,38.5c0,13.4,10.6,23.8,24.1,23.9c6.5,0,13,0,19.5,0
C482.2,206.2,482.2,206,482.2,205.8z M482,131.5c6.4,0,12.8-0.1,19.1,0c7.3,0.1,12.6,5.2,12.6,12.4c0.1,12.8,0.1,25.7,0,38.5
c0,7.2-5.4,12.4-12.6,12.4c-12.8,0.1-25.5,0.1-38.3,0c-7.4,0-12.6-5.3-12.7-12.8c-0.1-12.6-0.1-25.2,0-37.8
c0-7.6,5.3-12.8,13-12.8C469.4,131.4,475.7,131.5,482,131.5z"/>
<path d="M481.9,22.6c-8.2,0-16.4-0.9-24.4,0.2c-11.3,1.6-18.8,11.6-18.8,23c0,13,0,25.9,0,38.9c0.1,13.2,10.6,23.7,23.9,23.7
c12.9,0,25.7,0,38.6,0c13.7,0,24.1-10.5,24.1-24.2c0-12.7,0-25.4,0-38.2c0-13.7-10.5-24-24.3-24.1c-6.4,0-12.7,0-19.1,0
C481.9,22.2,481.9,22.4,481.9,22.6z M481.8,33.5c6.1,0,12.3,0,18.4,0c8.5,0,13.5,5,13.6,13.3c0,12.2,0,24.5,0,36.7
c0,8.2-5.1,13.3-13.2,13.3c-12.4,0-24.8,0-37.2,0c-8,0-13.2-5.1-13.2-13c0-12.5,0-25,0-37.5c0-7.9,5.2-12.9,13.2-13
C469.5,33.4,475.6,33.5,481.8,33.5z"/>
<path d="M537.5,65.5c0,8.1-0.9,16.4,0.2,24.4c1.5,10.8,11.6,18.5,22.6,18.5c13.2,0.1,26.5,0.1,39.7,0
c12.8-0.1,23.4-10.6,23.5-23.4c0.1-13.2,0.1-26.4,0-39.6c-0.1-12.8-10.6-23.4-23.4-23.4c-13.2-0.1-26.5-0.1-39.7,0
C547.5,22,537,32.6,536.9,45.3c-0.1,6.7,0,13.4,0,20.2C537.1,65.5,537.3,65.5,537.5,65.5z M548.4,65.3c0-6.1,0-12.2,0-18.4
c0-8.4,5-13.5,13.4-13.5c12.3,0,24.5,0,36.8,0c8.2,0,13.3,5.1,13.3,13.2c0,12.4,0,24.7,0,37.1c0,7.9-5.1,13.1-13,13.1
c-12.5,0-25,0-37.5,0c-7.7,0-12.9-5.2-13-12.8C548.3,77.8,548.4,71.6,548.4,65.3z"/>
<path d="M580.4,206.4c23.8,0,43.1-19.5,43.1-43.4c0-23.7-19.6-43-43.5-43c-23.8,0-43.1,19.5-43.1,43.4
C536.9,187.1,556.4,206.4,580.4,206.4z M580.2,194.9c-17.4,0-31.7-14.2-31.8-31.6c-0.1-17.6,14.2-31.9,31.8-31.9
c17.4,0,31.7,14.2,31.8,31.5C612,180.6,597.7,194.9,580.2,194.9z"/>
</g>
</g>
<g>
<g>
<path d="M748.6,148.1c0.9-0.2,2-0.5,3.2-0.9c24.9-8.3,49.9-16.5,74.8-24.8c5.4-1.8,6.4-3.7,4.6-9.1C823,88.6,814.8,63.8,806.5,39
c-1.6-5-3.7-6-8.7-4.3c-25.2,8.3-50.4,16.7-75.5,25c-4.8,1.6-5.9,3.7-4.3,8.4c8.3,25,16.7,50,24.9,75
C743.8,145.9,745.2,147.9,748.6,148.1z M820.6,113.7c-23.3,7.7-46.1,15.2-69.1,22.8c-7.6-23-15.1-45.5-22.6-68.2
c10.1-3.3,19.8-6.5,29.6-9.8c0.5,1.6,0.9,2.6,1.2,3.6c1.6,4.9,3.2,9.7,4.9,14.6c1.3,3.5,3.9,4.9,6.9,3.7c2.6-0.9,3.6-3.7,2.7-6.9
c-0.3-1.1-0.7-2.3-1-3.4c-1.6-4.9-3.3-9.8-4.9-14.9c10.1-3.3,19.8-6.5,29.7-9.7C805.6,68.5,813,90.8,820.6,113.7z"/>
<path d="M722,153c-4.2,3.1-7.7,7-9,12.3c-2.3,8.6-0.4,16.2,6.2,22.3c6.7,6.1,14.7,7.5,23.1,4.2c8.4-3.4,13-10,13.7-19
c0.2-2.8,1.2-3.6,3.5-4.3c25.1-8.2,50.2-16.5,75.2-24.8c5.2-1.7,7.3-4.3,6-7.6c-1.2-3.3-4-4-9.2-2.3c-25.1,8.3-50.1,16.6-75.2,25
c-2.5,0.8-4,0.6-5.7-1.6c-4.7-5.8-11.2-7.9-18.7-7.8c-4.7-14.1-9.4-28.1-14-42.1c-5.8-17.5-11.7-35.1-17.4-52.6
c-1-3.1-2.7-5.1-5.6-6.5c-9.9-4.8-19.7-9.8-29.5-14.8c-2.7-1.4-5.3-1.7-7.5,0.9c-2.3,2.8-1.3,6.2,2.6,8.1
c9.2,4.7,18.4,9.2,27.5,13.9c1.3,0.7,2.7,2.1,3.2,3.4c10.3,30.8,20.5,61.6,30.8,92.3C722.1,152.3,722,152.5,722,153z M722.3,171.6
c-0.1-6.2,5.2-11.7,11.5-11.9c6.4-0.1,12.1,5.4,12,11.8c0,6.2-5.5,11.6-11.8,11.7C727.8,183.2,722.4,177.9,722.3,171.6z"/>
</g>
</g>
<g>
<g>
<path d="M971.6,40.2c0-2.1,0-3.8,0-5.5c0-4.1-1.2-5.5-5.3-5.7c-3.4-0.2-6.8-0.2-10.2,0c-2,0.1-3-0.5-3.9-2.4
c-3.8-7.7-10.4-10.8-18.7-10.9c-8.3-0.1-14.5,3.5-18.5,10.7c-1,1.9-2,2.6-4.1,2.5c-2.9-0.2-5.8-0.1-8.7,0
c-5.1,0.1-6.2,1.2-6.2,6.3c0,1.4,0,2.9,0,5c-5.1,0-10,0-14.9,0c-4.9-0.1-9.4,0.9-13.3,4c-5,4-7.3,9.3-7.3,15.6
c0,41.4-0.1,82.7,0,124.1c0,10.6,7.6,18.1,18.2,18.1c36.8,0.1,73.5,0.1,110.3,0c10.4,0,18-7.5,18-17.9c0.1-41.5,0.1-83,0-124.5
c0-11.4-7.7-19.1-19.2-19.4C982.6,40.1,977.3,40.2,971.6,40.2z M971.6,49.2c5.1,0,9.7,0,14.3,0c8.8,0,12.1,3.3,12.1,12
c0,40.4,0,80.7,0,121.1c0,8-3.1,11.1-11.2,11.1c-35.4,0-70.8,0-106.2,0c-7.9,0-11-3.1-11-10.9c0-40.4,0-80.7,0-121.1
c0-1.2,0-2.4,0.1-3.6c0.4-4.1,3.8-8.1,7.9-8.3c6-0.3,12-0.1,18.5-0.1c0,3.4-0.1,6.5,0,9.6c0.1,3.9,1.7,5.7,5.5,5.7
c21.5,0,42.9,0,64.4,0c3.9,0,5.6-1.9,5.7-5.9C971.7,55.7,971.6,52.6,971.6,49.2z M962.5,55.5c-19.3,0-38.3,0-57.3,0
c0-5.9,0-11.5,0-17.6c3.6,0,7,0,10.3,0c3.2,0,5.1-1.2,6.1-4.5c1.7-5.7,6.6-9.5,11.9-9.6c5.2-0.1,10.2,3.7,11.9,9.5
c0.9,3.2,2.8,4.6,6,4.5c3.6,0,7.2,0,11.1,0C962.5,44,962.5,49.7,962.5,55.5z"/>
<path d="M954.9,124.4c-7.6,0-15.2,0-22.9,0c-1.1,0-2.2,0-3.2,0.2c-2.4,0.5-4.3,2.1-3.6,4.6c0.5,1.6,2.2,3.3,3.8,4
c1.8,0.7,4,0.3,6.1,0.3c14,0,28.1,0,42.1,0c1.2,0,2.7,0.3,3.6-0.2c1.5-1,3.4-2.4,3.8-4c0.7-2.4-1.2-4-3.6-4.5
c-1-0.2-2.2-0.2-3.2-0.2C970.2,124.4,962.5,124.4,954.9,124.4z"/>
<path d="M955,159.9c-8.2,0-16.5-0.2-24.7,0.1c-1.7,0.1-3.9,1.7-4.9,3.2c-0.6,0.9,0.7,3.3,1.6,4.6c0.5,0.7,2.2,0.8,3.3,0.8
c16.3,0.1,32.7,0.1,49,0c3.6,0,5.5-1.6,5.4-4.4c-0.2-3.4-2.4-4.4-5.5-4.4C971.2,159.9,963.1,159.9,955,159.9z"/>
<path d="M954.6,89.1c-8,0-16,0-24,0c-3,0-5.3,0.8-5.5,4.3c-0.2,3,1.8,4.6,5.7,4.6c16.1,0,32.2,0,48.3,0c3.8,0,5.8-1.7,5.8-4.5
c0-2.9-1.9-4.3-5.9-4.3C970.8,89.1,962.7,89.1,954.6,89.1z"/>
<path d="M896.5,128.1c-2.1-2.1-3.9-4-5.8-5.9c-2-1.9-4.3-2.5-6.4-0.3c-2,2-1.8,4.4,0.2,6.5c2.8,3,5.7,5.9,8.6,8.9
c2,2.1,4.3,2.2,6.4,0.3c5.8-5.3,11.5-10.8,17.1-16.2c1.9-1.9,2.1-4.2,0.3-6.2c-1.9-2.1-4.3-2.2-6.4-0.3c-3.5,3.2-6.8,6.5-10.3,9.8
C899,125.8,897.8,126.9,896.5,128.1z"/>
<path d="M896.4,92.7c-2-2-3.8-4-5.7-5.8c-2-2-4.3-2.5-6.4-0.5c-2.1,2-1.8,4.4,0.1,6.5c2.8,3,5.7,5.9,8.6,8.9
c2.2,2.2,4.6,2.2,6.8,0.1c5.7-5.3,11.3-10.6,16.8-16c2.1-2,2.2-4.4,0.2-6.4c-2-2-4.4-1.8-6.4,0.2
C905.6,83.9,901.1,88.2,896.4,92.7z"/>
<path d="M896.5,163.6c-2.2-2.2-4.1-4.1-6-6c-1.9-1.8-4.2-2.2-6.1-0.3c-2.1,2-1.9,4.4,0,6.4c2.9,3.1,5.9,6.1,8.9,9.1
c1.9,2,4.1,2.1,6.1,0.2c5.8-5.4,11.5-10.8,17.2-16.2c2-2,2.3-4.3,0.3-6.4c-2-2.1-4.4-1.9-6.4,0
C905.7,154.8,901.2,159.2,896.5,163.6z"/>
</g>
</g>
<g>
<g>
<path d="M1026.8,118.7c0,22.8,0.3,45.6-0.1,68.4c-0.2,13.2,9,19.3,19.2,19.2c36.6-0.4,73.3-0.1,109.9-0.2c11,0,17.8-6.4,17.8-17.4
c0.1-46.8,0.1-93.6,0-140.4c0-10.9-6.8-17.4-17.8-17.4c-2.4,0-4.8-0.1-7.2,0c-6.5,0.3-12.5,0.2-17.8-5.1c-4-4-10-5-15.8-5
c-9.8,0-19.5,0-29.3,0c-7.8,0-14.5,2.8-20.3,8.1c-1.3,1.1-3.3,1.9-5,2c-5.4,0.2-10.8,0.1-16.3,0.1c-10.2,0-17,6.5-17.2,16.8
c-0.2,10.5-0.1,21-0.1,31.5C1026.8,92.4,1026.8,105.5,1026.8,118.7z M1143.1,41.8c4.2,0,8.1,0,11.9,0c6,0,7.7,1.7,7.7,7.8
c0,46,0,91.9,0,137.9c0,6.3-1.6,7.8-8,7.8c-36.1,0-72.3,0-108.4,0c-7.3,0-8.7-1.3-8.7-8.6c0-45.5,0-91,0-136.4
c0-1.2,0-2.4,0.1-3.6c0.4-2.9,2.2-4.7,5-4.8c4.8-0.2,9.6-0.1,14.4-0.1c-0.4,2.6-0.8,4.8-1,7.1c-0.3,4.2,1.9,6.6,6.2,6.6
c25.2,0,50.4,0,75.5,0c4.2,0,6.5-2.4,6.2-6.6C1143.9,46.6,1143.5,44.4,1143.1,41.8z M1068.4,44.5c1.5-6.9,8.5-12.6,16.1-12.8
c10.5-0.2,20.9-0.2,31.4,0c7.7,0.2,14.9,6.2,15.8,12.8C1110.7,44.5,1089.6,44.5,1068.4,44.5z"/>
<path d="M1118.6,83.8c-4.1,4.1-8.1,8.1-12.4,12.3c-4.7-4.7-9.6-9.7-14.5-14.6c-4.1-4.1-6.5-4.1-10.6,0.1
c-5.7,5.7-11.4,11.4-17.1,17.1c-2.9,3-3.2,6.2-0.9,8.6c2.5,2.5,5.3,2.2,8.5-0.9c4.9-4.8,9.7-9.7,14.8-14.8c5,5,9.9,9.9,14.8,14.8
c4.4,4.4,6.5,4.4,10.8,0c4.8-4.8,9.7-9.7,14.7-14.7c0.5,1.2,0.7,2.5,1.5,3c1.6,1.1,3.7,2.6,5.2,2.3c1.7-0.4,4.1-2.5,4.2-4
c0.5-5.4,0.4-10.8,0.1-16.2c-0.2-2.8-2.4-4-5.2-4c-4.3,0-8.7,0-13,0c-3,0-5.2,1.1-5.8,4.2c-0.6,3.2,0.8,5.3,3.9,6.4
C1118,83.4,1118.3,83.6,1118.6,83.8z"/>
<path d="M1118.3,164.3c-7,0-14-0.1-20.9,0c-4.5,0.1-7,3.2-5.9,7c0.7,2.5,2.6,3.7,5.1,3.8c14.6,0,29.1,0.1,43.7,0
c3.2,0,5.3-2.3,5.4-5.1c0.1-3.1-2.1-5.6-5.8-5.6C1132.7,164.2,1125.5,164.3,1118.3,164.3C1118.3,164.3,1118.3,164.3,1118.3,164.3z
"/>
<path d="M1118.6,144c6.9,0,13.7,0,20.6,0c4,0,6.4-2,6.5-5.2c0.1-3.4-2.4-5.6-6.5-5.7c-13.8-0.1-27.7-0.1-41.5,0
c-4.1,0-6.5,2.3-6.4,5.7c0.1,3.3,2.3,5.1,6.5,5.2C1104.7,144,1111.6,144,1118.6,144z"/>
<path d="M1065.9,168.1c-4.7-3.9-8-4.3-10.3-1.7c-2.2,2.6-1.5,5.6,2.2,9.3c7.3,7.4,7.9,7.4,15.2,0.1c2.6-2.6,5.3-5.2,7.9-7.9
c2.4-2.6,2.4-5.5,0.3-7.7c-2.2-2.4-5.3-2.4-8,0.2C1070.7,162.8,1068.4,165.4,1065.9,168.1z"/>
<path d="M1065.6,136.9c-1-1-1.6-1.6-2.3-2.1c-2.4-1.9-5.5-1.9-7.5,0.2c-2,2-2,5.3,0,7.5c1.9,2.1,3.8,4,5.9,5.9
c2.4,2.1,5.4,2.1,7.7-0.2c3.9-3.8,7.7-7.6,11.5-11.5c2.4-2.5,2.4-5.5,0.2-7.8c-2.3-2.3-5.3-2.3-8,0.3
C1070.6,131.6,1068.3,134.2,1065.6,136.9z"/>
</g>
</g>
<g>
<g>
<path d="M1209.7,71.7c0.3,4.7,1.8,8.9,5,12.3c1.9,2,3.2,4.1,1.8,7.1c-1.3,2.8-3.6,3.4-6.4,3.5c-9.5,0.3-16.8,7.9-16.9,17.5
c-0.1,4.4-0.1,8.8,0,13.1c0.2,9.3,7.5,16.9,16.8,17.3c3,0.1,5.4,0.7,6.7,3.7c1.3,3-0.3,4.9-2.1,7c-6.7,7.7-6.4,17.4,0.6,24.6
c2.7,2.8,5.4,5.5,8.2,8.2c7.6,7.5,17.1,7.9,25.1,0.9c2.5-2.2,5.2-3.6,7.9-1.2c1.5,1.3,2.4,3.8,2.5,5.8c0.6,9.6,8,16.9,17.5,17
c4.4,0.1,8.7,0.1,13.1,0c9.4-0.1,17-7.4,17.4-16.7c0.1-2.9,0.6-5.4,3.6-6.8c2.9-1.3,4.9,0.2,7,1.9c7.9,6.8,17.4,6.5,24.9-0.8
c2.9-2.8,5.7-5.6,8.5-8.5c6.9-7.1,7.2-16.8,0.8-24.4c-1.8-2.2-3.6-4.2-2.2-7.3c1.4-3.2,4-3.5,7.1-3.7c8.9-0.4,16.1-8,16.3-16.9
c0.1-4.5,0.1-9,0-13.5c-0.1-9.6-7.4-17.1-17-17.5c-2.8-0.1-5.1-0.7-6.4-3.5c-1.3-2.9,0.1-4.9,1.9-7c6.8-7.8,6.5-17.5-0.6-24.9
c-2.7-2.8-5.4-5.5-8.2-8.2c-7.4-7.3-16.9-7.6-24.9-1c-1.5,1.2-3.4,2.3-5.2,2.5c-3,0.3-5.2-2.4-5.5-5.9c-0.8-11.6-7.6-18-19.1-18
c-3.7,0-7.5,0-11.2,0c-9.6,0.1-17.2,7.4-17.6,16.9c-0.1,3-0.8,5.4-3.8,6.6c-2.8,1.1-4.7-0.2-6.7-1.9c-8-7-17.6-6.6-25.1,0.8
c-2.9,2.9-5.8,5.8-8.7,8.7C1211.4,63,1210,67.2,1209.7,71.7z M1360.9,118.4c0,1.9,0,3.8,0,5.6c-0.1,4.2-2.1,6.2-6.1,6.4
c-7.7,0.3-13.3,3.9-16.4,11c-3,7.1-1.4,13.4,3.5,19.1c3.2,3.7,3.2,6-0.2,9.4c-2.6,2.7-5.2,5.4-8,7.9c-2.9,2.7-5.4,2.7-8.3,0.1
c-5.8-5.2-12.3-7.1-19.6-4c-7.3,3.1-10.7,8.9-11,16.8c-0.1,3.5-2.2,5.6-5.7,5.7c-4.1,0.1-8.2,0.1-12.4,0c-3.7-0.1-5.8-2.2-5.9-5.9
c-0.3-7.9-3.7-13.6-11.1-16.7c-7.2-3-13.6-1.2-19.3,3.9c-3.2,2.9-5.7,2.8-8.8-0.2c-2.7-2.6-5.4-5.2-7.9-8c-2.9-3.1-2.9-5.6,0-8.9
c5.1-5.7,6.8-12.2,3.7-19.4c-3.1-7.1-8.7-10.6-16.4-10.9c-3.9-0.2-6-2.2-6-6.1c-0.1-4-0.1-8,0-12c0.1-3.6,2.2-5.8,5.9-5.9
c7.7-0.3,13.4-3.7,16.5-10.8c3.2-7.3,1.4-13.9-3.8-19.7c-2.7-3.1-2.7-5.6,0.1-8.6c2.6-2.8,5.4-5.6,8.2-8.2c3-2.8,5.5-2.9,8.6-0.1
c5.8,5.2,12.3,7,19.6,3.8c7.1-3.1,10.5-8.8,10.8-16.5c0.1-3.7,2.2-5.8,5.8-5.9c4.1-0.1,8.2-0.1,12.4,0c3.5,0.1,5.6,2.2,5.7,5.6
c0.2,7.9,3.7,13.7,10.9,16.8c7.3,3.2,13.8,1.3,19.6-3.9c3-2.7,5.5-2.6,8.6,0.2c2.3,2.1,4.4,4.4,6.6,6.6c4.7,4.7,4.7,6.1,0.6,11.3
c-4.4,5.5-5.6,11.6-2.9,18.2c2.7,6.4,7.7,10.1,14.5,11c7.3,1,8.1,1.7,8.1,9.1C1360.9,116.4,1360.9,117.4,1360.9,118.4z"/>
<path d="M1325,118.2c-0.1-23.1-19-41.8-42.4-41.7c-22.9,0.1-41.5,19.1-41.4,42.3c0,23.2,19,41.8,42.4,41.7
C1306.4,160.4,1325,141.4,1325,118.2z M1313,118.2c0.1,16.5-13.3,30.2-29.7,30.3c-16.6,0.1-30.1-13.3-30.2-29.8
c-0.1-16.6,13.3-30.2,29.7-30.3C1299.3,88.4,1312.9,101.7,1313,118.2z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

Some files were not shown because too many files have changed in this diff Show More