From c672e0cea061908e58b6d3f5045d16689fa24f02 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Thu, 31 Jul 2025 18:05:36 +0530 Subject: [PATCH 1/5] Creating a Component for Tenant. --- src/components/Tenant/CreateTenant.jsx | 333 +++++++++++++++++++ src/components/Tenant/Tenant.jsx | 210 ++++++++++++ src/components/Tenant/TenantSubscription.jsx | 114 +++++++ src/data/menuData.json | 10 + src/router/AppRoutes.jsx | 6 + 5 files changed, 673 insertions(+) create mode 100644 src/components/Tenant/CreateTenant.jsx create mode 100644 src/components/Tenant/Tenant.jsx create mode 100644 src/components/Tenant/TenantSubscription.jsx diff --git a/src/components/Tenant/CreateTenant.jsx b/src/components/Tenant/CreateTenant.jsx new file mode 100644 index 00000000..8ff61589 --- /dev/null +++ b/src/components/Tenant/CreateTenant.jsx @@ -0,0 +1,333 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import Breadcrumb from "../common/Breadcrumb"; // โœ… Adjust the path if needed +import { Modal } from "react-bootstrap"; // Ensure you have react-bootstrap installed + +const defaultAvatar = "https://via.placeholder.com/100x100.png?text=Avatar"; + +const initialData = { + firstName: "", + lastName: "", + email: "", + phone: "", + domain: "", + organization: "", + description: "", + size: "", + industry: "", + reference: "", + taxId: "", + billingAddress: "", +}; + +const CreateTenant = () => { + const navigate = useNavigate(); + const location = useLocation(); + const formData = location.state?.formData || null; + + const [form, setForm] = useState(initialData); + const [imagePreview, setImagePreview] = useState(defaultAvatar); + const [imageFile, setImageFile] = useState(null); + const [showImageModal, setShowImageModal] = useState(false); + + useEffect(() => { + if (formData) { + setForm({ ...initialData, ...formData }); + + // Load profileImage preview if available + if (formData.profileImage) { + setImagePreview(formData.profileImage); + } + } + }, [formData]); + + const handleChange = (e) => { + const { name, value } = e.target; + setForm((prev) => ({ ...prev, [name]: value })); + }; + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file && file.size <= 800 * 1024) { + setImageFile(file); + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result); + }; + reader.readAsDataURL(file); + } else { + alert("File must be JPG/PNG/GIF and less than 800KB"); + } + }; + + const handleImageReset = () => { + setImageFile(null); + setImagePreview(defaultAvatar); + }; + +// const handleSubmit = (e) => { +// e.preventDefault(); +// const submissionData = { +// ...form, +// profileImage: imagePreview, // Save base64/URL of image +// }; +// console.log("Form submitted:", submissionData); +// navigate("/tenant/profile/subscription", { state: { formData: submissionData } }); +// }; + +const handleSubmit = (e) => { + e.preventDefault(); + const submissionData = { + ...form, + profileImage: imagePreview, // Save base64/URL of image + }; + if (formData?.id) { + navigate("/tenant/profile"); + } else { + navigate("/tenant/profile/subscription", { state: { formData: submissionData } }); + } + }; + + const RequiredLabel = ({ label }) => ( + + ); + + return ( +
+ {/* โœ… Breadcrumb */} + + +
+
+
+ {formData?.id ? "Update Tenant" : "Create Tenant"} +
+ +
+ {/* ๐Ÿ‘ค Image Upload */} +
+ Profile Preview setShowImageModal(true)} + style={{ + width: "100px", + height: "100px", + objectFit: "cover", + borderRadius: "8px", + border: "1px solid #ccc", + cursor: "pointer", + }} + /> +
+
+ + + +
+ Allowed JPG, GIF or PNG. Max size of 800K +
+
+ + {/* ๐Ÿงพ Form Fields */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + {/* ๐Ÿ”˜ Buttons */} +
+ + +
+
+
+
+ + {/* ๐Ÿ” Image Preview Modal */} + setShowImageModal(false)} centered size="lg"> + + Preview + + +
+ ); +}; + +export default CreateTenant; diff --git a/src/components/Tenant/Tenant.jsx b/src/components/Tenant/Tenant.jsx new file mode 100644 index 00000000..98233f5b --- /dev/null +++ b/src/components/Tenant/Tenant.jsx @@ -0,0 +1,210 @@ +import React, { useState, useEffect } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import Breadcrumb from "../common/Breadcrumb"; + +const Tenant = () => { + const [tenants, setTenants] = useState([ + { + id: 1, + firstName: "Albert", + lastName: "Cook", + email: "albert.cook@example.com", + phone: "+1 (555) 123-4567", + organization: "Innovate Corp", + size: "101-500", + industry: "Technology", + domain: "innovate.com", + description: "Innovative solutions for businesses", + }, + { + id: 2, + firstName: "Barry", + lastName: "Hunter", + email: "barry.hunter@example.com", + phone: "+1 (555) 987-6543", + organization: "Creative Solutions", + size: "51-100", + industry: "Marketing", + domain: "creatives.com", + description: "Creative marketing strategies", + }, + { + id: 3, + firstName: "Sophia", + lastName: "Johnson", + email: "sophia.johnson@example.com", + phone: "+1 (555) 678-9012", + organization: "TechWorld Inc", + size: "501-1000", + industry: "IT Services", + domain: "techworld.com", + description: "Cutting-edge tech services", + }, + { + id: 4, + firstName: "Daniel", + lastName: "Lee", + email: "daniel.lee@example.com", + phone: "+1 (555) 345-6789", + organization: "EduPro", + size: "101-500", + industry: "Education", + domain: "edupro.org", + description: "Smart learning platforms", + }, + { + id: 5, + firstName: "Emily", + lastName: "Davis", + email: "emily.davis@example.com", + phone: "+1 (555) 765-4321", + organization: "GreenEarth Solutions", + size: "51-100", + industry: "Environmental", + domain: "greenearth.com", + description: "Eco-friendly innovations", + }, + { + id: 6, + firstName: "Michael", + lastName: "Brown", + email: "michael.brown@example.com", + phone: "+1 (555) 888-1212", + organization: "FinanceLink", + size: "1000+", + industry: "Finance", + domain: "financelink.net", + description: "Reliable financial services", + }, + { + id: 7, + firstName: "Olivia", + lastName: "Taylor", + email: "olivia.taylor@example.com", + phone: "+1 (555) 222-3344", + organization: "HealthPlus", + size: "201-500", + industry: "Healthcare", + domain: "healthplus.com", + description: "Comprehensive health care solutions", + }, + ]); + + const navigate = useNavigate(); + const location = useLocation(); + + // Handle form submission result + useEffect(() => { + const newTenant = location.state?.newTenant; + if (newTenant) { + if (newTenant.id) { + // Update existing tenant + setTenants((prev) => + prev.map((t) => (t.id === newTenant.id ? newTenant : t)) + ); + } else { + // Add new tenant + setTenants((prev) => [...prev, { ...newTenant, id: Date.now() }]); + } + } + }, [location.state]); + + const handleCreate = () => { + navigate("/tenant/profile/create"); + }; + + const handleEdit = (tenant) => { + navigate("/tenant/profile/create", { state: { formData: tenant } }); + }; + + const handleView = (tenant) => { + alert( + `Tenant Info:\n\nName: ${tenant.firstName} ${tenant.lastName}\nEmail: ${tenant.email}\nPhone: ${tenant.phone}\nOrganization: ${tenant.organization}` + ); + }; + + const handleDelete = (id) => { + if (window.confirm("Are you sure to delete this tenant?")) { + setTenants((prev) => prev.filter((t) => t.id !== id)); + } + }; + + return ( +
+ {/* โœ… Breadcrumb added */} + + +
+
+ +
+ +
+ + + + + + + + + + + + + + + {tenants.map((tenant) => ( + + + + + + + + + + + ))} + {tenants.length === 0 && ( + + + + )} + +
NameEmailPhoneDomainOrganizationSizeIndustryActions
{tenant.firstName} {tenant.lastName}{tenant.email}{tenant.phone}{tenant.domain}{tenant.organization}{tenant.size}{tenant.industry} +
+ + + +
+
+ No tenants found. +
+
+
+
+ ); +}; + +export default Tenant; diff --git a/src/components/Tenant/TenantSubscription.jsx b/src/components/Tenant/TenantSubscription.jsx new file mode 100644 index 00000000..81e1713f --- /dev/null +++ b/src/components/Tenant/TenantSubscription.jsx @@ -0,0 +1,114 @@ +import React from "react"; +import Breadcrumb from "../common/Breadcrumb"; // โœ… Adjust path if needed + +const plans = [ + { + name: "Basic", + price: "$0", + per: "/month", + description: "A simple start for everyone", + features: [ + "100 responses a month", + "Unlimited forms and surveys", + "Unlimited fields", + "Basic form creation tools", + "Up to 2 subdomains", + ], + button: "Your Current Plan", + buttonClass: "btn btn-success disabled", + highlight: false, + }, + { + name: "Standard", + price: "$40", + per: "/month", + description: "For small to medium businesses", + subText: "USD 480/year", + features: [ + "Unlimited responses", + "Unlimited forms and surveys", + "Instagram profile page", + "Google Docs integration", + 'Custom "Thank you" page', + ], + button: "Upgrade", + buttonClass: "btn btn-primary", + highlight: true, + badge: "Popular", + }, + { + name: "Enterprise", + price: "$80", + per: "/month", + description: "Solution for big organizations", + subText: "USD 960/year", + features: [ + "PayPal payments", + "Logic Jumps", + "File upload with 5GB storage", + "Custom domain support", + "Stripe integration", + ], + button: "Buy Now", + buttonClass: "btn btn-warning text-white fw-semibold shadow", + highlight: false, + }, +]; + +const TenantSubscription = () => { + return ( +
+ {/* โœ… Breadcrumb */} + + +
+ {plans.map((plan, idx) => ( +
+
+
+ {plan.badge && ( + + {plan.badge} + + )} +
+ +
+
{plan.name}
+

{plan.description}

+

+ {plan.price} {plan.per} +

+ {plan.subText &&

{plan.subText}

} +
    + {plan.features.map((feat, i) => ( +
  • + + {feat} +
  • + ))} +
+ +
+
+
+ ))} +
+
+ ); +}; + +export default TenantSubscription; diff --git a/src/data/menuData.json b/src/data/menuData.json index bbd37e2c..2372de70 100644 --- a/src/data/menuData.json +++ b/src/data/menuData.json @@ -81,6 +81,16 @@ "text": "Masters", "available": true, "link": "/masters" + }, + { + "text": "Manage Subscription", + "available": true, + "link": "/tenant/manage" + }, + { + "text": "Manage Tenants", + "available": true, + "link": "/tenant/profile" } ] }, diff --git a/src/router/AppRoutes.jsx b/src/router/AppRoutes.jsx index 5ce5b6d4..2d4371b0 100644 --- a/src/router/AppRoutes.jsx +++ b/src/router/AppRoutes.jsx @@ -38,6 +38,9 @@ import LegalInfoCard from "../pages/TermsAndConditions/LegalInfoCard"; import ProtectedRoute from "./ProtectedRoute"; import Directory from "../pages/Directory/Directory"; import LoginWithOtp from "../pages/authentication/LoginWithOtp"; +import Tenant from "../components/Tenant/Tenant"; +import CreateTenant from "../components/Tenant/CreateTenant"; +import TenantSubscription from "../components/Tenant/TenantSubscription"; const router = createBrowserRouter( [ @@ -77,6 +80,9 @@ const router = createBrowserRouter( { path: "/activities/reports", element: }, { path: "/gallary", element: }, { path: "/masters", element: }, + { path: "/tenant/profile", element: }, + { path: "/tenant/profile/create", element: }, + { path: "/tenant/profile/subscription", element: }, { path: "/help/support", element: }, { path: "/help/docs", element: }, { path: "/help/connect", element: }, -- 2.43.0 From cb27f8c2596037bb42e12768b0f50e77fa167021 Mon Sep 17 00:00:00 2001 From: Kartik Sharma Date: Fri, 1 Aug 2025 17:01:01 +0530 Subject: [PATCH 2/5] Get Api and Create Api for tenant. --- package-lock.json | 261 +++++++++++++++++++- package.json | 2 + src/components/Tenant/CreateTenant.jsx | 321 +++++++++++++++++-------- src/components/Tenant/Tenant.jsx | 275 ++++++++++----------- src/components/Tenant/ViewTenant.jsx | 256 ++++++++++++++++++++ src/components/Tenant/apiTenant.jsx | 11 + src/components/Tenant/useTenants.js | 60 +++++ src/router/AppRoutes.jsx | 10 +- 8 files changed, 937 insertions(+), 259 deletions(-) create mode 100644 src/components/Tenant/ViewTenant.jsx create mode 100644 src/components/Tenant/apiTenant.jsx create mode 100644 src/components/Tenant/useTenants.js diff --git a/package-lock.json b/package-lock.json index 39e9a3b4..a6396dc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "apexcharts": "^4.5.0", "axios": "^1.7.9", "axios-retry": "^4.5.0", + "bootstrap": "^5.3.7", "dotenv": "^16.4.7", "dotenv-webpack": "^8.1.0", "eventemitter3": "^5.0.1", @@ -29,6 +30,7 @@ "perfect-scrollbar": "^1.5.5", "react": "^18.2.0", "react-apexcharts": "^1.7.0", + "react-bootstrap": "^2.10.10", "react-dom": "^18.2.0", "react-hook-form": "^7.54.2", "react-quill": "^2.0.0", @@ -897,6 +899,31 @@ "pako": "^1.0.10" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz", @@ -928,6 +955,60 @@ "node": ">=14.0.0" } }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", @@ -1416,6 +1497,21 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@swc/helpers/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@swc/types": { "version": "0.1.17", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz", @@ -1562,8 +1658,7 @@ "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "devOptional": true + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" }, "node_modules/@types/quill": { "version": "1.3.10", @@ -1578,7 +1673,6 @@ "version": "18.3.16", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.16.tgz", "integrity": "sha512-oh8AMIC4Y2ciKufU8hnKgs+ufgbA/dhPTACaZPM86AbwX9QwnFtSoPWEeRUj8fge+v6kFt78BXcDhAU1SrrAsw==", - "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1593,11 +1687,26 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", + "license": "MIT" + }, "node_modules/@types/web": { "version": "0.0.216", "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.216.tgz", @@ -2140,6 +2249,25 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bootstrap": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz", + "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2299,6 +2427,12 @@ "node": ">=6.0" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -2401,8 +2535,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -2537,6 +2670,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2549,6 +2691,16 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -3611,6 +3763,15 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -4646,6 +4807,19 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "license": "MIT", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -4765,6 +4939,37 @@ "react": ">=0.13" } }, + "node_modules/react-bootstrap": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", + "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -4798,6 +5003,12 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, "node_modules/react-quill": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-quill/-/react-quill-2.0.0.tgz", @@ -4885,6 +5096,22 @@ "react-dom": "^18 || ^19" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -5746,6 +5973,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/undici-types": { "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", @@ -5876,6 +6118,15 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", diff --git a/package.json b/package.json index 3f4d52fe..3b9e4593 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "apexcharts": "^4.5.0", "axios": "^1.7.9", "axios-retry": "^4.5.0", + "bootstrap": "^5.3.7", "dotenv": "^16.4.7", "dotenv-webpack": "^8.1.0", "eventemitter3": "^5.0.1", @@ -32,6 +33,7 @@ "perfect-scrollbar": "^1.5.5", "react": "^18.2.0", "react-apexcharts": "^1.7.0", + "react-bootstrap": "^2.10.10", "react-dom": "^18.2.0", "react-hook-form": "^7.54.2", "react-quill": "^2.0.0", diff --git a/src/components/Tenant/CreateTenant.jsx b/src/components/Tenant/CreateTenant.jsx index 8ff61589..03f3fda3 100644 --- a/src/components/Tenant/CreateTenant.jsx +++ b/src/components/Tenant/CreateTenant.jsx @@ -1,7 +1,9 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useCallback } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import Breadcrumb from "../common/Breadcrumb"; // โœ… Adjust the path if needed -import { Modal } from "react-bootstrap"; // Ensure you have react-bootstrap installed +import Breadcrumb from "../common/Breadcrumb"; +import { Modal } from "react-bootstrap"; +import { apiTenant } from "./apiTenant"; +import { useCreateTenant } from "./useTenants"; const defaultAvatar = "https://via.placeholder.com/100x100.png?text=Avatar"; @@ -10,14 +12,16 @@ const initialData = { lastName: "", email: "", phone: "", - domain: "", - organization: "", + mobile: "", + domainName: "", + organizationName: "", description: "", - size: "", - industry: "", + organizationSize: "", + industryId: "", reference: "", taxId: "", billingAddress: "", + onBoardingDate: "", }; const CreateTenant = () => { @@ -25,22 +29,69 @@ const CreateTenant = () => { const location = useLocation(); const formData = location.state?.formData || null; + // Assume useCreateTenant also handles updates, so we destructure `updateTenant` as well. + const { createTenant, updateTenant, loading, error, success } = useCreateTenant(); + const [form, setForm] = useState(initialData); const [imagePreview, setImagePreview] = useState(defaultAvatar); const [imageFile, setImageFile] = useState(null); const [showImageModal, setShowImageModal] = useState(false); + const [showImageSizeModal, setShowImageSizeModal] = useState(false); + const [industryOptions, setIndustryOptions] = useState([]); + // Load form data if it's passed via location state useEffect(() => { if (formData) { - setForm({ ...initialData, ...formData }); + const { contactName, contactNumber, logoImage, ...rest } = formData; - // Load profileImage preview if available - if (formData.profileImage) { - setImagePreview(formData.profileImage); + // A more robust way to split the name from the backend data + let firstName = ""; + let lastName = ""; + if (contactName) { + const nameParts = contactName.trim().split(" "); + firstName = nameParts.shift() || ""; // Take the first word + lastName = nameParts.join(" ") || ""; // Join the rest + } + + setForm({ + ...initialData, + ...rest, + firstName, + lastName, + phone: contactNumber || "", + }); + + if (logoImage) { + setImagePreview(logoImage); } } }, [formData]); + // Load industry options from the API when the component mounts + useEffect(() => { + const fetchIndustries = async () => { + try { + const res = await apiTenant.getIndustries(); + if (Array.isArray(res.data)) { + setIndustryOptions(res.data); + if (formData?.industry) { + const matchedIndustry = res.data.find( + (industry) => industry.name === formData.industry.name + ); + if (matchedIndustry) { + setForm((prev) => ({ ...prev, industryId: matchedIndustry.id })); + } + } + } else { + console.error("Unexpected response format for industries", res); + } + } catch (err) { + console.error("Failed to load industries:", err); + } + }; + fetchIndustries(); + }, [formData]); + const handleChange = (e) => { const { name, value } = e.target; setForm((prev) => ({ ...prev, [name]: value })); @@ -48,15 +99,17 @@ const CreateTenant = () => { const handleImageChange = (e) => { const file = e.target.files[0]; - if (file && file.size <= 800 * 1024) { - setImageFile(file); - const reader = new FileReader(); - reader.onloadend = () => { - setImagePreview(reader.result); - }; - reader.readAsDataURL(file); - } else { - alert("File must be JPG/PNG/GIF and less than 800KB"); + if (file) { + if (file.size <= 200 * 1024) { + setImageFile(file); + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result); + }; + reader.readAsDataURL(file); + } else { + setShowImageSizeModal(true); + } } }; @@ -65,28 +118,45 @@ const CreateTenant = () => { setImagePreview(defaultAvatar); }; -// const handleSubmit = (e) => { -// e.preventDefault(); -// const submissionData = { -// ...form, -// profileImage: imagePreview, // Save base64/URL of image -// }; -// console.log("Form submitted:", submissionData); -// navigate("/tenant/profile/subscription", { state: { formData: submissionData } }); -// }; + const handleSubmit = useCallback( + async (e) => { + e.preventDefault(); -const handleSubmit = (e) => { - e.preventDefault(); - const submissionData = { - ...form, - profileImage: imagePreview, // Save base64/URL of image - }; - if (formData?.id) { - navigate("/tenant/profile"); - } else { - navigate("/tenant/profile/subscription", { state: { formData: submissionData } }); - } - }; + // Prepare the data, ensuring firstName and lastName are included + const submissionData = { + ...form, // This spreads all fields from your form state, including firstName and lastName + logoImage: imagePreview, + contactNumber: form.phone, + contactName: `${form.firstName} ${form.lastName}`.trim(), + }; + + let result; + if (formData?.id) { + // This is the update path. Call the update function from the hook. + result = await updateTenant(formData.id, submissionData); + + if (result) { + alert("Tenant updated successfully!"); + // Navigate back to the profile page with the updated tenant data + navigate("/tenant/profile", { state: { newTenant: result } }); + } else { + alert("Failed to update tenant. Please check the form and try again."); + } + } else { + // This is the creation path. Call the create function from the hook. + result = await createTenant(submissionData); + + if (result) { + alert("Tenant created successfully!"); + // Navigate to the subscription page with the new tenant's data + navigate("/tenant/profile/subscription", { state: { formData: result } }); + } else { + alert("Failed to create tenant. Please check the form and try again."); + } + } + }, + [form, imagePreview, formData, navigate, createTenant, updateTenant] + ); const RequiredLabel = ({ label }) => (