create new tenant app
This commit is contained in:
parent
d48b4c4ef3
commit
918a5ee9be
@ -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>
|
||||
|
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,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>
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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"),
|
||||
|
@ -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;
|
||||
}
|
||||
})
|
||||
}
|
@ -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`)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user