create new tenant app

This commit is contained in:
pramod mahajan 2025-08-05 14:23:30 +05:30
parent 5e28f9e372
commit 12cbf576e1
8 changed files with 224 additions and 64 deletions

View File

@ -96,7 +96,7 @@ const ContactInfro = ({ onNext }) => {
)}
</div>
<div className="d-flex justify-content-end mt-3">
<button type="button" className="btn btn-primary" onClick={handleNext}>
<button type="button" className="btn btn-sm btn-primary" onClick={handleNext}>
Next
</button>
</div>

View File

@ -0,0 +1,85 @@
import React from "react";
import { useFormContext } from "react-hook-form";
const toBase64 = (file) =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
export const LogoUpload = ({ preview, setPreview, fileName, setFileName }) => {
const {
register,
setValue,
formState: { errors },
} = useFormContext();
const handleUpload = async (e) => {
const file = e.target.files?.[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) {
alert("File exceeds 5MB limit");
return;
}
const base64 = await toBase64(file);
setValue("logoImage", base64, { shouldValidate: true });
setFileName(file.name);
setPreview(URL.createObjectURL(file));
e.target.value = "";
};
const handleClear = () => {
setValue("logoImage", "", { shouldValidate: true });
setPreview(null);
setFileName("");
};
return (
<div className="col-sm-12 mb-3">
<div
className="border border-secondary border-dashed rounded p-2 text-center position-relative"
style={{ cursor: "pointer" }}
onClick={() => document.getElementById("logoImageInput")?.click()}
>
<i className="bx bx-cloud-upload d-block bx-lg mb-2"></i>
<span className="text-muted">Click or browse to upload</span>
<input
type="file"
id="logoImageInput"
accept="image/png, image/jpeg"
style={{ display: "none" }}
{...register("logoImage")}
onChange={handleUpload}
/>
</div>
{errors.logoImage && (
<small className="danger-text">{errors.logoImage.message}</small>
)}
{preview && (
<div className="mt-2">
<img
src={preview}
alt="Preview"
className="img-thumbnail"
style={{ maxHeight: "40px" }}
/>
<div className="d-flex align-items-center gap-2 mt-1">
<span className="small text-muted">{fileName}</span>
<i
className="bx bx-trash bx-sm text-danger"
style={{ cursor: "pointer" }}
onClick={handleClear}
></i>
</div>
</div>
)}
</div>
);
};

View File

@ -1,34 +1,61 @@
import React from "react";
import React, { useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
import Label from "../common/Label";
import DatePicker from "../common/DatePicker";
import { useIndustries } from "../../hooks/useTenant";
import { LogoUpload } from "./LogoUpload";
const OrganizationInfo = ({ onNext, onPrev }) => {
const OrganizationInfo = ({ onNext, onPrev,onSubmitTenant }) => {
const { data, isError, isLoading: industryLoading } = useIndustries();
const [logoPreview, setLogoPreview] = useState(null);
const [logoName, setLogoName] = useState("");
const {
register,
control,
setValue,
getValues,
trigger,
formState: { errors },
} = useFormContext();
const handleNext = async () => {
const valid = await trigger([
"organizationName",
"officeNumber",
"domainName",
"description",
"onBoardingDate",
"organizationSize",
"taxId",
"industryId",
"reference",
"logoImage",
]);
if (valid) onNext();
};
const valid = await trigger([
"organizationName",
"officeNumber",
"domainName",
"description",
"onBoardingDate",
"organizationSize",
"taxId",
"industryId",
"reference",
"logoImage",
]);
if (valid) {
const data = getValues();
onSubmitTenant(data);
onNext();
}
};
const Reference = [
{ val: "google", name: "Google" },
{ val: "frined", name: "Friend" },
{ val: "advertisement", name: "Advertisement" },
{ val: "root tenant", name: "Root Tenant" },
];
const orgSize = [
{ val: "50", name: "1-50" },
{ val: "100", name: "51-100" },
{ val: "500", name: "101-500" },
{ val: "600", name: "500+" },
];
return (
<div className="row g-6">
<div className="row g-2">
<div className="col-sm-6">
<Label htmlFor="organizationName" required>
Organization Name
@ -112,9 +139,6 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
<div className="invalid-feedback">{errors.onBoardingDate.message}</div>
)} */}
<label htmlFor="onBoardingDate" className="form-label">
Onboarding Date <span className="text-danger">*</span>
</label>
<input
type="date"
id="onBoardingDate"
@ -134,14 +158,22 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
<Label htmlFor="organizationSize" required>
Organization Size
</Label>
<input
{/* <input
id="organizationSize"
type="number"
className={`form-control form-control-sm ${
errors.organizationSize ? "is-invalid" : ""
}`}
{...register("organizationSize")}
/>
/> */}
<select className="form-select form-select-sm" {...register("organizationSize")}>
{orgSize.map((org) => (
<option key={org.val} value={org.val}>
{org.name}
</option>
))}
</select>
{errors.organizationSize && (
<div className="invalid-feedback">
{errors.organizationSize.message}
@ -153,23 +185,20 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
<Label htmlFor="industryId" required>
Industry
</Label>
<Controller
name="industryId"
control={control}
render={({ field }) => (
<Select
{...field}
id="industryId"
className={`form-control form-control-sm ${
errors.industryId ? "is-invalid" : ""
}`}
options={[
{ label: "Tech", value: 1 },
{ label: "Healthcare", value: 2 },
]} // replace with your data
/>
<select
className="form-select form-select-sm"
{...register("industryId")}
>
{industryLoading ? (
<option value="">Loading...</option>
) : (
data?.map((indu) => (
<option key={indu.id} value={indu.id}>
{indu.name}
</option>
))
)}
/>
</select>
{errors.industryId && (
<div className="invalid-feedback">{errors.industryId.message}</div>
)}
@ -177,13 +206,20 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
<div className="col-sm-6">
<Label htmlFor="reference">Reference</Label>
<input
{/* <input
id="reference"
className={`form-control form-control-sm ${
errors.reference ? "is-invalid" : ""
}`}
{...register("reference")}
/>
/> */}
<select className="form-select form-select-sm" {...register("reference")}>
{Reference.map((org) => (
<option key={org.val} value={org.val}>
{org.name}
</option>
))}
</select>
{errors.reference && (
<div className="invalid-feedback">{errors.reference.message}</div>
)}
@ -204,26 +240,33 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
)}
</div>
<div className="col-sm-12">
<Label htmlFor="logoImage">Logo Image</Label>
<input
id="logoImage"
type="file"
className={`form-control form-control-sm ${
errors.logoImage ? "is-invalid" : ""
}`}
{...register("logoImage")}
/>
{errors.logoImage && (
<div className="invalid-feedback">{errors.logoImage.message}</div>
)}
</div>
<div className="col-sm-12">
<Label htmlFor="logImage">Logo Image</Label>
<LogoUpload
preview={logoPreview}
setPreview={setLogoPreview}
fileName={logoName}
setFileName={setLogoName}
/>
</div>
<div className="d-flex justify-content-between mt-3">
<button type="button" className="btn btn-secondary" onClick={onPrev}>
<button
type="button"
className="btn btn-sm btn-secondary"
onClick={onPrev}
>
Back
</button>
<button type="button" className="btn btn-primary" onClick={handleNext}>
<button
type="button"
className="btn btn-sm btn-primary"
onClick={handleNext}
>
Next
</button>
</div>

View File

@ -1,8 +1,14 @@
import React from 'react'
import { useSubscriptionPlan } from '../../hooks/useTenant'
const SubScription = () => {
const {data,isError,isPending} = useSubscriptionPlan()
console.log(data)
return (
<div>SubScription</div>
<div>
<h1>Scription</h1>
</div>
)
}

View File

@ -16,6 +16,8 @@ const TenantForm = () => {
const [activeTab, setActiveTab] = useState(0);
const [completedTabs, setCompletedTabs] = useState([]);
const tenantForm = useForm({
resolver: zodResolver(newTenantSchema),
defaultValues: tenantDefaultValues,
@ -59,7 +61,7 @@ const TenantForm = () => {
name: "Organization",
icon: "bx bx-home bx-md",
subtitle: "Organization Details",
component: <OrganizationInfo onNext={handleNext} />,
component: <OrganizationInfo onNext={handleNext} onSubmitTenant={onSubmitTenant} />,
},
{
name: "SubScription",
@ -72,7 +74,7 @@ const TenantForm = () => {
const isSubscriptionTab = activeTab === 2;
return (
<div id="wizard-property-listing" className="bs-stepper vertical mt-2">
<div className="bs-stepper-header border-end text-start">
<div className="bs-stepper-header border-end text-start ">
{newTenantConfig.map((step, index) => {
const isActive = activeTab === index;
const isCompleted = completedTabs.includes(index);
@ -104,7 +106,7 @@ const TenantForm = () => {
</button>
</div>
{index < newTenantConfig.length - 1 && (
<div className="line"></div>
<div className="line text-primary"></div>
)}
</React.Fragment>
);

View File

@ -4,11 +4,11 @@ export const newTenantSchema = z.object({
firstName: z.string().nonempty("First name is required"),
lastName: z.string().nonempty("Last name is required"),
email: z.string().email("Invalid email address"),
description: z.string().nonempty("Description is required"),
description: z.string().optional(),
domainName: z.string().nonempty("Domain name is required"),
billingAddress: z.string().nonempty("Billing address is required"),
taxId: z.string().nonempty("Tax ID is required"),
logoImage: z.string().nonempty("Logo image is required"),
logoImage: z.string().optional(),
organizationName: z.string().nonempty("Organization name is required"),
officeNumber: z.string().nonempty("Office number is required"),
contactNumber: z.string().nonempty("Contact number is required"),

View File

@ -1,5 +1,6 @@
import { useQuery } from "@tanstack/react-query";
import { TenantRepository } from "../repositories/TenantRepository";
import { MarketRepository } from "../repositories/MarketRepository";
export const useTenants = (
pageSize,
@ -20,3 +21,24 @@ export const useTenants = (
staleTime: 5 * 60 * 1000,
});
};
export const useIndustries=()=>{
return useQuery({
queryKey:['Industries'],
queryFn: async()=>{
const res = await MarketRepository.getIndustries();
return res.data;
}
})
}
export const useSubscriptionPlan=()=>{
return useQuery({
queryKey:['SubscriptionPlan'],
queryFn:async()=>{
const res = await TenantRepository.getSubscriptionPlan();
return res.data;
}
})
}

View File

@ -5,6 +5,8 @@ export const TenantRepository = {
getTenanatList:(pageSize, pageNumber, filter,searchString)=>{
const payloadJsonString = JSON.stringify(filter);
return api.get(`/api/Tenant/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`)
}
},
getSubscriptionPlan:()=>api.get(`/api/Tenant/list/subscription-plan`)
}