create new tenant app
This commit is contained in:
parent
d48b4c4ef3
commit
918a5ee9be
@ -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>
|
||||||
|
|||||||
85
src/components/Tenanat/LogoUpload.jsx
Normal file
85
src/components/Tenanat/LogoUpload.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -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>
|
||||||
)}
|
)}
|
||||||
@ -204,26 +240,33 @@ 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>
|
||||||
|
|||||||
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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",
|
||||||
@ -72,7 +74,7 @@ const TenantForm = () => {
|
|||||||
const isSubscriptionTab = activeTab === 2;
|
const isSubscriptionTab = activeTab === 2;
|
||||||
return (
|
return (
|
||||||
<div id="wizard-property-listing" className="bs-stepper vertical mt-2">
|
<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) => {
|
{newTenantConfig.map((step, index) => {
|
||||||
const isActive = activeTab === index;
|
const isActive = activeTab === index;
|
||||||
const isCompleted = completedTabs.includes(index);
|
const isCompleted = completedTabs.includes(index);
|
||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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"),
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -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`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user