create new tenant app

This commit is contained in:
pramod mahajan 2025-08-05 14:23:30 +05:30
parent d48b4c4ef3
commit 918a5ee9be
8 changed files with 224 additions and 64 deletions

View File

@ -96,7 +96,7 @@ const ContactInfro = ({ onNext }) => {
)} )}
</div> </div>
<div className="d-flex justify-content-end mt-3"> <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 Next
</button> </button>
</div> </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,12 +1,19 @@
import React from "react"; import React, { useState } from "react";
import { useFormContext, Controller } from "react-hook-form"; import { useFormContext, Controller } from "react-hook-form";
import Label from "../common/Label"; import Label from "../common/Label";
import DatePicker from "../common/DatePicker"; 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 { const {
register, register,
control, control,
setValue,
getValues,
trigger, trigger,
formState: { errors }, formState: { errors },
} = useFormContext(); } = useFormContext();
@ -24,11 +31,31 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
"reference", "reference",
"logoImage", "logoImage",
]); ]);
if (valid) onNext();
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 ( return (
<div className="row g-6"> <div className="row g-2">
<div className="col-sm-6"> <div className="col-sm-6">
<Label htmlFor="organizationName" required> <Label htmlFor="organizationName" required>
Organization Name Organization Name
@ -112,9 +139,6 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
<div className="invalid-feedback">{errors.onBoardingDate.message}</div> <div className="invalid-feedback">{errors.onBoardingDate.message}</div>
)} */} )} */}
<label htmlFor="onBoardingDate" className="form-label">
Onboarding Date <span className="text-danger">*</span>
</label>
<input <input
type="date" type="date"
id="onBoardingDate" id="onBoardingDate"
@ -134,14 +158,22 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
<Label htmlFor="organizationSize" required> <Label htmlFor="organizationSize" required>
Organization Size Organization Size
</Label> </Label>
<input {/* <input
id="organizationSize" id="organizationSize"
type="number" type="number"
className={`form-control form-control-sm ${ className={`form-control form-control-sm ${
errors.organizationSize ? "is-invalid" : "" errors.organizationSize ? "is-invalid" : ""
}`} }`}
{...register("organizationSize")} {...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 && ( {errors.organizationSize && (
<div className="invalid-feedback"> <div className="invalid-feedback">
{errors.organizationSize.message} {errors.organizationSize.message}
@ -153,23 +185,20 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
<Label htmlFor="industryId" required> <Label htmlFor="industryId" required>
Industry Industry
</Label> </Label>
<Controller <select
name="industryId" className="form-select form-select-sm"
control={control} {...register("industryId")}
render={({ field }) => ( >
<Select {industryLoading ? (
{...field} <option value="">Loading...</option>
id="industryId" ) : (
className={`form-control form-control-sm ${ data?.map((indu) => (
errors.industryId ? "is-invalid" : "" <option key={indu.id} value={indu.id}>
}`} {indu.name}
options={[ </option>
{ label: "Tech", value: 1 }, ))
{ label: "Healthcare", value: 2 },
]} // replace with your data
/>
)} )}
/> </select>
{errors.industryId && ( {errors.industryId && (
<div className="invalid-feedback">{errors.industryId.message}</div> <div className="invalid-feedback">{errors.industryId.message}</div>
)} )}
@ -177,13 +206,20 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
<div className="col-sm-6"> <div className="col-sm-6">
<Label htmlFor="reference">Reference</Label> <Label htmlFor="reference">Reference</Label>
<input {/* <input
id="reference" id="reference"
className={`form-control form-control-sm ${ className={`form-control form-control-sm ${
errors.reference ? "is-invalid" : "" errors.reference ? "is-invalid" : ""
}`} }`}
{...register("reference")} {...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 && ( {errors.reference && (
<div className="invalid-feedback">{errors.reference.message}</div> <div className="invalid-feedback">{errors.reference.message}</div>
)} )}
@ -205,25 +241,32 @@ const OrganizationInfo = ({ onNext, onPrev }) => {
</div> </div>
<div className="col-sm-12"> <div className="col-sm-12">
<Label htmlFor="logoImage">Logo Image</Label> <Label htmlFor="logImage">Logo Image</Label>
<input
id="logoImage" <LogoUpload
type="file" preview={logoPreview}
className={`form-control form-control-sm ${ setPreview={setLogoPreview}
errors.logoImage ? "is-invalid" : "" fileName={logoName}
}`} setFileName={setLogoName}
{...register("logoImage")}
/> />
{errors.logoImage && (
<div className="invalid-feedback">{errors.logoImage.message}</div>
)}
</div> </div>
<div className="d-flex justify-content-between mt-3"> <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 Back
</button> </button>
<button type="button" className="btn btn-primary" onClick={handleNext}> <button
type="button"
className="btn btn-sm btn-primary"
onClick={handleNext}
>
Next Next
</button> </button>
</div> </div>

View File

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

View File

@ -16,6 +16,8 @@ const TenantForm = () => {
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const [completedTabs, setCompletedTabs] = useState([]); const [completedTabs, setCompletedTabs] = useState([]);
const tenantForm = useForm({ const tenantForm = useForm({
resolver: zodResolver(newTenantSchema), resolver: zodResolver(newTenantSchema),
defaultValues: tenantDefaultValues, defaultValues: tenantDefaultValues,
@ -59,7 +61,7 @@ const TenantForm = () => {
name: "Organization", name: "Organization",
icon: "bx bx-home bx-md", icon: "bx bx-home bx-md",
subtitle: "Organization Details", subtitle: "Organization Details",
component: <OrganizationInfo onNext={handleNext} />, component: <OrganizationInfo onNext={handleNext} onSubmitTenant={onSubmitTenant} />,
}, },
{ {
name: "SubScription", name: "SubScription",
@ -104,7 +106,7 @@ const TenantForm = () => {
</button> </button>
</div> </div>
{index < newTenantConfig.length - 1 && ( {index < newTenantConfig.length - 1 && (
<div className="line"></div> <div className="line text-primary"></div>
)} )}
</React.Fragment> </React.Fragment>
); );

View File

@ -4,11 +4,11 @@ export const newTenantSchema = z.object({
firstName: z.string().nonempty("First name is required"), firstName: z.string().nonempty("First name is required"),
lastName: z.string().nonempty("Last name is required"), lastName: z.string().nonempty("Last name is required"),
email: z.string().email("Invalid email address"), 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"), domainName: z.string().nonempty("Domain name is required"),
billingAddress: z.string().nonempty("Billing address is required"), billingAddress: z.string().nonempty("Billing address is required"),
taxId: z.string().nonempty("Tax ID 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"), organizationName: z.string().nonempty("Organization name is required"),
officeNumber: z.string().nonempty("Office number is required"), officeNumber: z.string().nonempty("Office number is required"),
contactNumber: z.string().nonempty("Contact number is required"), contactNumber: z.string().nonempty("Contact number is required"),

View File

@ -1,5 +1,6 @@
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { TenantRepository } from "../repositories/TenantRepository"; import { TenantRepository } from "../repositories/TenantRepository";
import { MarketRepository } from "../repositories/MarketRepository";
export const useTenants = ( export const useTenants = (
pageSize, pageSize,
@ -20,3 +21,24 @@ export const useTenants = (
staleTime: 5 * 60 * 1000, 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)=>{ getTenanatList:(pageSize, pageNumber, filter,searchString)=>{
const payloadJsonString = JSON.stringify(filter); const payloadJsonString = JSON.stringify(filter);
return api.get(`/api/Tenant/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`) return api.get(`/api/Tenant/list?pageSize=${pageSize}&pageNumber=${pageNumber}&filter=${payloadJsonString}&searchString=${searchString}`)
} },
getSubscriptionPlan:()=>api.get(`/api/Tenant/list/subscription-plan`)
} }