diff --git a/package-lock.json b/package-lock.json index 125748ecc..c0beb5d39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "react-dom": "^18.2.0", "react-icons": "^4.10.1", "react-toastify": "^9.1.3", + "secure-random-password": "^0.2.3", "shiki": "^0.14.1", "socket.io-client": "^4.6.2", "tailwind-scrollbar": "^3.0.4", @@ -44,6 +45,7 @@ "devDependencies": { "@types/crypto-js": "^4.1.1", "@types/eslint": "^8.21.1", + "@types/secure-random-password": "^0.2.1", "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", "astro-eslint-parser": "^0.11.0", @@ -1626,6 +1628,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==" }, + "node_modules/@types/secure-random-password": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@types/secure-random-password/-/secure-random-password-0.2.1.tgz", + "integrity": "sha512-tpG5oVF+NpIS9UJ9ttXAokafyhE/MCZBg65D345qu3gOM4YoJ/mFNVzUDUNBfb1hIi598bNOzvY04BbfS7VKwA==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -11048,6 +11056,19 @@ "node": ">=4" } }, + "node_modules/secure-random": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/secure-random/-/secure-random-1.1.2.tgz", + "integrity": "sha512-H2bdSKERKdBV1SwoqYm6C0y+9EA94v6SUBOWO8kDndc4NoUih7Dv6Tsgma7zO1lv27wIvjlD0ZpMQk7um5dheQ==" + }, + "node_modules/secure-random-password": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/secure-random-password/-/secure-random-password-0.2.3.tgz", + "integrity": "sha512-2zpmr6pK3CZGIS9fgApFw00/tKEBVbJTqe4AZLrLNgahCK6ueIR5uMzvbERNibr8hkWneMcOqDcm4wpHWUxrYw==", + "dependencies": { + "secure-random": "^1.1.2" + } + }, "node_modules/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz", diff --git a/src/api/organization.ts b/src/api/organization.ts index 4a2dc41c8..968cf89f6 100644 --- a/src/api/organization.ts +++ b/src/api/organization.ts @@ -130,7 +130,7 @@ export const getOrgDashboard = async (orgId: string) => { } try { - return axiosGet(axiosPayload); + return await axiosGet(axiosPayload); } catch (error) { const err = error as Error @@ -235,7 +235,7 @@ export const getOrganizationUsers = async () => { } try { - return axiosGet(axiosPayload); + return await axiosGet(axiosPayload); } catch (error) { const err = error as Error @@ -270,3 +270,36 @@ export const editOrganizationUserRole = async (userId: number, roles: number[]) } } +//Create Connection + + +export const createConnection = async (orgName: string) => { + + const url = apiRoutes.connection.create + + const orgId = await getFromLocalStorage(storageKeys.ORG_ID) + + const data = { + label: orgName, + multiUseInvitation: true, + autoAcceptConnection: true, + orgId: Number(orgId) + } + const payload = data + + const axiosPayload = { + url, + payload, + config: await getHeaderConfigs() + } + + try { + return await axiosPost(axiosPayload); + } + catch (error) { + const err = error as Error + return err?.message + } +} + + diff --git a/src/app/NavBarSidebar.astro b/src/app/NavBarSidebar.astro index 8ecd26918..5323665aa 100644 --- a/src/app/NavBarSidebar.astro +++ b/src/app/NavBarSidebar.astro @@ -289,14 +289,14 @@ import SignOutButton from '../components/SignOutButton/index' --> -   -   + +
diff --git a/src/components/EmptyListComponent/index.tsx b/src/components/EmptyListComponent/index.tsx new file mode 100644 index 000000000..77aa4e16a --- /dev/null +++ b/src/components/EmptyListComponent/index.tsx @@ -0,0 +1,29 @@ +import { ReactElement } from "react"; + +export const EmptyListMessage = ({ message, description, buttonContent, svgComponent, onClick } + : { + message: string, + description: string, + buttonContent?: string, + svgComponent?: ReactElement, + + onClick?: () => void, + }) => { + return ( +
+

{message}

+

{description}

+ { + buttonContent + && + } + +
+ ) +}; + diff --git a/src/components/InputCopy/index.tsx b/src/components/InputCopy/index.tsx index 755159df9..813f22d88 100644 --- a/src/components/InputCopy/index.tsx +++ b/src/components/InputCopy/index.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from 'react'; +import { ChangeEventHandler, MouseEvent, MouseEventHandler, useRef, useState } from 'react'; interface InputProps { field: { @@ -14,7 +14,8 @@ const InputCopy = ({ field, ...props }: InputProps) => { const inputRef = useRef(null); const [isCopied, setIsCopied] = useState(false); - function copyTextVal() { + function copyTextVal(event: React.MouseEvent) { + event.preventDefault() const copyText = inputRef?.current; const copiedText: string = copyText?.value as string diff --git a/src/components/Resources/Schema/Schemas.tsx b/src/components/Resources/Schema/Schemas.tsx index 93d91f112..b547659dc 100644 --- a/src/components/Resources/Schema/Schemas.tsx +++ b/src/components/Resources/Schema/Schemas.tsx @@ -12,6 +12,7 @@ import SearchInput from '../../SearchInput'; import { getAllSchemas } from '../../../api/Schema'; import { getFromLocalStorage } from '../../../api/Auth'; import { pathRoutes } from '../../../config/pathRoutes'; +import { EmptyListMessage } from '../../EmptyListComponent'; const SchemaList = () => { const [schemaList, setSchemaList] = useState([]) @@ -57,7 +58,9 @@ const SchemaList = () => { } }; - + const createSchema = () => { + window.location.href = `${pathRoutes.organizations.createSchema}?OrgId=${orgId}` + } useEffect(() => { getSchemaList(schemaListAPIParameter) @@ -102,9 +105,7 @@ const SchemaList = () => {
@@ -153,6 +151,7 @@ const OrganizationsList = () => { setOpenModal={ props.setOpenModal } /> + { setError(null) }} /> + {loading ?
- : organizationsList && organizationsList?.length > 0 &&
+ : organizationsList && organizationsList?.length > 0 ? (
{ organizationsList.map((org) => ( redirectOrgDashboard(org.id)} className='transform transition duration-500 hover:scale-105 hover:bg-gray-50 cursor-pointer'> @@ -209,8 +209,15 @@ const OrganizationsList = () => { )) } - -
+
) + : organizationsList && ( + + } />) }
diff --git a/src/components/organization/SharedIllustrate.tsx b/src/components/organization/SharedIllustrate.tsx index d92a08b00..81d55c926 100644 --- a/src/components/organization/SharedIllustrate.tsx +++ b/src/components/organization/SharedIllustrate.tsx @@ -1,11 +1,35 @@ import SharedIcon from '../../assets/shared.svg'; - const SharedIllustrate = () => { return ( -
+
+
dedicated +
+
+

+ Your Hassle-Free Multi-Tenant Solution +

+
    +
  • +

    + Our Shared Agent is here to simplify your operations! Managed by CREDEBL on your organization's behalf, this multi-tenant solution allows multiple organizations to share the same agent. You'll benefit from shared resources and reduced operational burdens. +

    +
  • +
  • +

    + Leave the agent maintenance, updates, and technical aspects to CREDEBL, so your team can focus on core tasks without worrying about the backend. Enjoy improved productivity and efficiency while we handle the nitty-gritty. +

    +
  • +
  • +

    + Experience a seamless and hassle-free future with the Shared Agent. Let CREDEBL take care of everything, while you concentrate on achieving your goals. +

    +
  • +
+ +
) diff --git a/src/components/organization/WalletSpinup.tsx b/src/components/organization/WalletSpinup.tsx index 20872fa50..dd14bd75b 100644 --- a/src/components/organization/WalletSpinup.tsx +++ b/src/components/organization/WalletSpinup.tsx @@ -12,11 +12,11 @@ import { spinupDedicatedAgent, spinupSharedAgent } from '../../api/organization' import { useEffect, useState } from 'react'; import type { AxiosResponse } from 'axios'; -import SOCKET from '../../config/SocketConfig'; -import { nanoid } from 'nanoid' -import InputCopy from '../InputCopy'; import DedicatedIllustrate from './DedicatedIllustrate'; +import InputCopy from '../InputCopy'; +import SOCKET from '../../config/SocketConfig'; import SharedIllustrate from './SharedIllustrate'; +import { nanoid } from 'nanoid' interface Values { seed: string; @@ -96,6 +96,7 @@ const WalletSpinup = ( const submitSharedWallet = async (values: ValuesShared) => { + setLoading(true) const orgId = await getFromLocalStorage(storageKeys.ORG_ID) const payload = { @@ -103,7 +104,6 @@ const WalletSpinup = ( seed: seeds, orgId: Number(orgId) } - setLoading(true) const spinupRes = await spinupSharedAgent(payload) const { data } = spinupRes as AxiosResponse @@ -253,7 +253,7 @@ const WalletSpinup = ( @@ -323,7 +323,7 @@ const WalletSpinup = ( @@ -366,8 +366,7 @@ const WalletSpinup = (
{ - !agentSpinupCall - &&
+ !agentSpinupCall && !loading &&
  • diff --git a/src/components/organization/invitations/Invitations.tsx b/src/components/organization/invitations/Invitations.tsx index df7996e80..28ac55bb1 100644 --- a/src/components/organization/invitations/Invitations.tsx +++ b/src/components/organization/invitations/Invitations.tsx @@ -13,6 +13,7 @@ import { TextTittlecase } from '../../../utils/TextTransform'; import { apiStatusCodes } from '../../../config/CommonConstant'; import { getOrganizationInvitations } from '../../../api/invitations'; import { getOrganizations } from '../../../api/organization'; +import { EmptyListMessage } from '../../EmptyListComponent'; const initialPageState = { pageNumber: 1, @@ -47,25 +48,24 @@ const Invitations = () => { const response = await getOrganizationInvitations(currentPage.pageNumber, currentPage.pageSize, searchText); const { data } = response as AxiosResponse + setLoading(false) + if (data?.statusCode === apiStatusCodes.API_STATUS_SUCCESS) { const totalPages = data?.data?.totalPages; const invitationList = data?.data?.invitations - if (invitationList.length === 0) { - setError('No Data Found') - } setInvitationsList(invitationList) setCurrentPage({ ...currentPage, total: totalPages }) - } else { - setError(response as string) } + else { + setError(response as string) - setLoading(false) + } } //This useEffect is called when the searchText changes @@ -96,151 +96,161 @@ const Invitations = () => { } - return ( -
    -
    -
    - - -
    - - setMessage(data)} - setOpenModal={ - props.setOpenModal - } /> - - { - setMessage(null) - setError(null) - }} + return ( +
    +
    +
    + - {loading - ?
    - -
    - : invitationsList && invitationsList?.length > 0 &&
    -
    -
      - { - invitationsList.map((invitation) => ( - -
    • -
      -
      -
      - - - -
      -
      -

      - {invitation.email} -

      - -
      -
        -
      • -
        -
        - Roles: - {invitation.orgRoles && - invitation.orgRoles.length > 0 && - invitation.orgRoles.map((role: OrgRole, index: number) => { - return ( - - {role.name.charAt(0).toUpperCase() + role.name.slice(1)} - - ); - })} -
        + +
        + setMessage(data)} + setOpenModal={ + props.setOpenModal + } /> + + { + setMessage(null) + setError(null) + }} + /> + + {loading + ?
        + +
        + : invitationsList && invitationsList?.length > 0 ? (
        +
        +
          + { + invitationsList.map((invitation) => ( + +
        • +
          +
          +
          + + + +
          +
          +

          + {invitation.email} +

          + +
          +
            +
          • +
            +
            + Roles: + {invitation.orgRoles && + invitation.orgRoles.length > 0 && + invitation.orgRoles.map((role: OrgRole, index: number) => { + return ( + + {role.name.charAt(0).toUpperCase() + role.name.slice(1)} + + ); + })}
            -
          • -
          -
          + +
          +
        • +
        +
      - - { - invitation.status === 'pending' + + { + invitation.status === 'pending' + ? + {TextTittlecase(invitation.status)} + + : invitation.status === 'accepted' ? + {TextTittlecase(invitation.status)} + + : + {TextTittlecase(invitation.status)} - : invitation.status === 'accepted' - ? - {TextTittlecase(invitation.status)} - - : - - {TextTittlecase(invitation.status)} - - - } - - - -

      - Invited On: {invitation.createDateTime.split('T')[0]} -

      - -
      -
    • - )) - } -
    -
    -
    - } -
    + } + - -
    + +

    + Invited On: {invitation.createDateTime.split('T')[0]} +

    + +
    +
  • + )) + } +
+
+
) + : invitationsList && ( + + } + />) + } + +
+ +
- +
+ ) } diff --git a/src/components/organization/invitations/ReceivedInvitations.tsx b/src/components/organization/invitations/ReceivedInvitations.tsx index c920e09db..4fd2799cc 100644 --- a/src/components/organization/invitations/ReceivedInvitations.tsx +++ b/src/components/organization/invitations/ReceivedInvitations.tsx @@ -15,6 +15,7 @@ import { TextTittlecase } from '../../../utils/TextTransform'; import { apiStatusCodes } from '../../../config/CommonConstant'; import { getOrganizations } from '../../../api/organization'; import { pathRoutes } from '../../../config/pathRoutes'; +import { EmptyListMessage } from '../../EmptyListComponent'; const initialPageState = { pageNumber: 1, @@ -54,15 +55,14 @@ const ReceivedInvitations = () => { const totalPages = data?.data?.totalPages; const invitationList = data?.data?.invitations - if (invitationList.length === 0) { - setError('No Data Found') - } + setInvitationsList(invitationList) setCurrentPage({ ...currentPage, total: totalPages }) - } else { + } + else { setError(response as string) } @@ -99,7 +99,7 @@ const ReceivedInvitations = () => { if (data?.statusCode === apiStatusCodes.API_STATUS_SUCCESS) { setMessage(data?.message) setLoading(false) - window.location.href=pathRoutes.organizations.root + window.location.href = pathRoutes.organizations.root } else { setError(response as string) setLoading(false) @@ -121,9 +121,12 @@ const ReceivedInvitations = () => { className="p-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800" >
- + {invitationsList && invitationsList?.length > 0 ? + + : + null}
{ setError(null) }} /> + + {loading ?
- : invitationsList && invitationsList?.length > 0 &&
0 ? (
-
-
    - { - invitationsList.map((invitation) => ( - -
  • -
    -
    -
    - - - - - - - - - - - - - - -
    -
    -

    - {invitation.organisation.name} -

    - -
    -
      -
    • -
      -
      - Roles: - {invitation.orgRoles && - invitation.orgRoles.length > 0 && - invitation.orgRoles.map((role: OrgRole, index: number) => { - return ( - - {role.name.charAt(0).toUpperCase() + role.name.slice(1)} - - ); - })} -
      + > +
      +
        + { + invitationsList.map((invitation) => ( + +
      • +
        +
        +
        + + + + + + + + + + + + + + +
        +
        +

        + {invitation.organisation.name} +

        + +
        +
          +
        • +
          +
          + Roles: + {invitation.orgRoles && + invitation.orgRoles.length > 0 && + invitation.orgRoles.map((role: OrgRole, index: number) => { + return ( + + {role.name.charAt(0).toUpperCase() + role.name.slice(1)} + + ); + })} +
          -
          -
        • -
        +
        +
      • +
      -
      - {/*

      + {/*

      Received On: {invitation.createDateTime.split('T')[0]}

      */} -
      -
    +
    -
    -
    -
    - - +
    -
    +
    +
    + + +
    -
    -
  • - )) - } -
-
- -
+
+ + + )) + } + +
+ + ) + : invitationsList && () }
diff --git a/src/config/apiRoutes.ts b/src/config/apiRoutes.ts index f83850bac..0e8affcaa 100644 --- a/src/config/apiRoutes.ts +++ b/src/config/apiRoutes.ts @@ -23,6 +23,9 @@ export const apiRoutes = { orgRoles: '/organization/roles', editUserROle: '/organization/user-roles' }, + connection: { + create: '/connections', + }, schema: { create: '/schemas', getAll: '/schemas', diff --git a/src/pages/index.astro b/src/pages/index.astro index 045f6d57f..e646a3a46 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -27,9 +27,14 @@ import { pathRoutes } from '../config/pathRoutes.js'; >
+ + + Sign in
See our repository