From 6f65a883b4d16c45f61a8db3fde1b1dae854c3a2 Mon Sep 17 00:00:00 2001 From: RAHEEL Date: Tue, 26 Aug 2025 00:13:44 +0500 Subject: [PATCH 1/2] added request license modal --- .../lowcoder/src/api/licenseRequestApi.ts | 84 +++ .../packages/lowcoder/src/i18n/locales/en.ts | 36 ++ .../src/pages/setting/licenseRequestModal.tsx | 527 ++++++++++++++++++ .../src/pages/setting/settingHome.tsx | 6 +- 4 files changed, 650 insertions(+), 3 deletions(-) create mode 100644 client/packages/lowcoder/src/api/licenseRequestApi.ts create mode 100644 client/packages/lowcoder/src/pages/setting/licenseRequestModal.tsx diff --git a/client/packages/lowcoder/src/api/licenseRequestApi.ts b/client/packages/lowcoder/src/api/licenseRequestApi.ts new file mode 100644 index 000000000..9af8d42c6 --- /dev/null +++ b/client/packages/lowcoder/src/api/licenseRequestApi.ts @@ -0,0 +1,84 @@ +import axios from "axios"; + +export interface LicenseRequestData { + contactData: { + companyName: string; + address: string; + registerNumber: string; + contactName: string; + contactEmail: string; + contactPhone: string; + taxId?: string; + vatId?: string; + organizationId: string; + deploymentIds: string[]; + }; + licenseType: 'per-api-calls' | 'per-instance'; + licenseData: { + apiCallLimit?: number; + instanceCount?: number; + currentApiUsage?: number; + lastMonthApiUsage?: number; + }; + organizationId: string; + deploymentIds: string[]; + submittedAt: string; + submittedBy: string; +} + +export interface LicenseRequestResponse { + success: boolean; + message: string; + requestId?: string; + estimatedResponseTime?: string; +} + +/** + * Submit a license request to flow.lowcoder.cloud + * @param data The license request data + * @returns Promise with the response + */ +export const submitLicenseRequest = async (data: LicenseRequestData): Promise => { + try { + // TODO: Replace with actual endpoint when available + const response = await axios.post('https://flow.lowcoder.cloud/api/license-requests', data, { + headers: { + 'Content-Type': 'application/json', + }, + timeout: 30000, // 30 second timeout + }); + + return response.data; + } catch (error) { + console.error('License request submission failed:', error); + + // For now, simulate a successful response since the endpoint doesn't exist yet + if (axios.isAxiosError(error) && error.code === 'ECONNREFUSED') { + // Simulate successful submission for development/testing + return { + success: true, + message: 'License request submitted successfully (simulated)', + requestId: `sim-${Date.now()}`, + estimatedResponseTime: '24-48 hours', + }; + } + + throw new Error('Failed to submit license request. Please try again later.'); + } +}; + +/** + * Get the status of a license request + * @param requestId The request ID + * @returns Promise with the status + */ +export const getLicenseRequestStatus = async (requestId: string): Promise => { + try { + // TODO: Replace with actual endpoint when available + const response = await axios.get(`https://flow.lowcoder.cloud/api/license-requests/${requestId}`); + return response.data; + } catch (error) { + console.error('Failed to get license request status:', error); + throw new Error('Failed to get request status'); + } +}; diff --git a/client/packages/lowcoder/src/i18n/locales/en.ts b/client/packages/lowcoder/src/i18n/locales/en.ts index 80f667288..2a31924fd 100644 --- a/client/packages/lowcoder/src/i18n/locales/en.ts +++ b/client/packages/lowcoder/src/i18n/locales/en.ts @@ -2475,6 +2475,42 @@ export const en = { "readMoreButton" : "Enterprise Edition Details", "requestLicense" : "Request Enterprise Edition Licenses", "requestLicensesBtton" : "Unlock Enterprise Features", + "licenseRequest": { + "title": "Request Enterprise Licenses", + "companyDetails": "Company Details", + "licenseSelection": "License Selection", + "reviewSubmit": "Review & Submit", + "requestSubmitted": "Request Submitted", + "companyName": "Company Name", + "address": "Address", + "registerNumber": "Register Number", + "contactPerson": "Contact Person", + "contactEmail": "Contact Email", + "contactPhone": "Contact Phone", + "taxId": "Tax ID", + "vatId": "VAT ID", + "licenseType": "License Type", + "perApiCalls": "Per API Calls", + "perInstance": "Per Instance", + "apiCallLimit": "Monthly API Call Limit", + "instanceCount": "Number of Instances", + "additionalNotes": "Additional Notes", + "currentUsage": "Current Usage", + "lastMonthUsage": "Last Month Usage", + "currentDeployments": "Current Deployments", + "payBasedOnUsage": "Pay based on API usage volume", + "payPerInstance": "Pay per deployment instance", + "thankYouMessage": "Your license request has been submitted successfully. Our team will review your request and contact you within 24-48 hours.", + "nextSteps": "Next Steps", + "nextStepsDescription": "While you wait for your license, you can:", + "reviewDocs": "Review our Enterprise Edition documentation", + "downloadRelease": "Download the latest Enterprise Edition release", + "checkInstallGuide": "Check out our installation guide", + "submitRequest": "Submit Request", + "back": "Back", + "next": "Next", + "close": "Close" + }, "AuditLogsTitle": "Audit Logs", "AuditLogsIntroTitle": "Powerful visibility into your workspace activity", "AuditLogsIntro1": "Audit Logs enable administrators to track exactly what happens across the entire Lowcoder platform. From user sign-ins to app modifications, every relevant action is captured and stored.", diff --git a/client/packages/lowcoder/src/pages/setting/licenseRequestModal.tsx b/client/packages/lowcoder/src/pages/setting/licenseRequestModal.tsx new file mode 100644 index 000000000..f5e5d4d7f --- /dev/null +++ b/client/packages/lowcoder/src/pages/setting/licenseRequestModal.tsx @@ -0,0 +1,527 @@ +import { useState, useEffect } from "react"; +import { Modal, Typography, Card, Space, Divider, Form, Input, Select, Radio, Button, message, Steps, Progress } from "antd"; +import styled from "styled-components"; +import Title from "antd/es/typography/Title"; +import { trans } from "i18n"; +import { useSelector } from "react-redux"; +import { getUser } from "redux/selectors/usersSelectors"; +import { getCurrentOrg } from "redux/selectors/orgSelectors"; +import { getOrgApiUsage, getOrgLastMonthApiUsage } from "redux/selectors/orgSelectors"; +import { getDeploymentId } from "@lowcoder-ee/redux/selectors/configSelectors"; +import { submitLicenseRequest, LicenseRequestData } from "api/licenseRequestApi"; + +const { Paragraph, Text } = Typography; +const { Option } = Select; +const { TextArea } = Input; + +const LicenseFormContainer = styled.div` + max-width: 100%; + width: 100%; +`; + +const FormSection = styled.div` + margin-bottom: 24px; +`; + +const SectionTitle = styled(Title)` + margin-bottom: 16px !important; + color: #1890ff; +`; + +const LicenseCard = styled(Card)` + margin-bottom: 16px; + border: 2px solid #f0f0f0; + transition: all 0.3s; + + &:hover { + border-color: #1890ff; + box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1); + } + + &.selected { + border-color: #1890ff; + background-color: #f6ffed; + } +`; + +const UsageDisplay = styled.div` + background: #f5f5f5; + padding: 12px; + border-radius: 6px; + margin: 8px 0; +`; + +const ThankYouContent = styled.div` + text-align: center; + padding: 40px 20px; +`; + +const DownloadSection = styled.div` + margin-top: 32px; + padding: 24px; + background: #f9f9f9; + border-radius: 8px; +`; + +interface Props { + open: boolean; + onClose: () => void; + orgId: string; + deploymentIds: string[]; +} + +interface CompanyDetails { + companyName: string; + address: string; + registerNumber: string; + contactName: string; + contactEmail: string; + contactPhone: string; + taxId: string; + vatId: string; +} + +interface LicenseSelection { + licenseType: 'per-api-calls' | 'per-instance'; + apiCallLimit?: number; + instanceCount?: number; +} + +interface FormData { + companyDetails: CompanyDetails; + licenseSelection: LicenseSelection; + additionalNotes: string; +} + +enum ModalStep { + CompanyDetails = 0, + LicenseSelection = 1, + Review = 2, + ThankYou = 3, +} + +export function LicenseRequestModal({ open, onClose, orgId, deploymentIds }: Props) { + const [form] = Form.useForm(); + const [currentStep, setCurrentStep] = useState(ModalStep.CompanyDetails); + const [loading, setLoading] = useState(false); + const [formData, setFormData] = useState({ + companyDetails: { + companyName: '', + address: '', + registerNumber: '', + contactName: '', + contactEmail: '', + contactPhone: '', + taxId: '', + vatId: '', + }, + licenseSelection: { + licenseType: 'per-api-calls', + apiCallLimit: 1000000, + }, + additionalNotes: '', + }); + + const user = useSelector(getUser); + const currentOrg = useSelector(getCurrentOrg); + const apiUsage = useSelector(getOrgApiUsage); + const lastMonthApiUsage = useSelector(getOrgLastMonthApiUsage); + const deploymentId = useSelector(getDeploymentId); + + // Pre-fill company name if available + useEffect(() => { + if (currentOrg?.name && open) { + form.setFieldsValue({ + 'companyDetails.companyName': currentOrg.name, + }); + setFormData(prev => ({ + ...prev, + companyDetails: { + ...prev.companyDetails, + companyName: currentOrg.name, + } + })); + } + }, [currentOrg, open, form]); + + const handleCompanyDetailsSubmit = async (values: any) => { + setFormData(prev => ({ + ...prev, + companyDetails: values.companyDetails, + })); + setCurrentStep(ModalStep.LicenseSelection); + }; + + const handleLicenseSelectionSubmit = async (values: any) => { + setFormData(prev => ({ + ...prev, + licenseSelection: values.licenseSelection, + additionalNotes: values.additionalNotes || '', + })); + setCurrentStep(ModalStep.Review); + }; + + const handleSubmit = async () => { + setLoading(true); + try { + // Prepare data object for API + const requestData: LicenseRequestData = { + contactData: { + ...formData.companyDetails, + organizationId: orgId, + deploymentIds: deploymentIds, + }, + licenseType: formData.licenseSelection.licenseType, + licenseData: { + ...formData.licenseSelection, + currentApiUsage: apiUsage, + lastMonthApiUsage: lastMonthApiUsage, + }, + organizationId: orgId, + deploymentIds: deploymentIds, + submittedAt: new Date().toISOString(), + submittedBy: user.username, + }; + + // Submit to flow.lowcoder.cloud + const response = await submitLicenseRequest(requestData); + + if (response.success) { + message.success(response.message || 'License request submitted successfully!'); + setCurrentStep(ModalStep.ThankYou); + } else { + message.error(response.message || 'Failed to submit license request'); + } + } catch (error) { + message.error('Failed to submit license request. Please try again.'); + console.error('License request error:', error); + } finally { + setLoading(false); + } + }; + + const handleBack = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1); + } + }; + + const handleClose = () => { + setCurrentStep(ModalStep.CompanyDetails); + form.resetFields(); + onClose(); + }; + + const renderCompanyDetailsStep = () => ( + + {trans("enterprise.licenseRequest.companyDetails")} + + + + + +