Merge branch 'main' into test/rest-playground

This commit is contained in:
Nishchit14
2023-08-04 12:53:00 +05:30
56 changed files with 2211 additions and 836 deletions

View File

@@ -1,17 +1,20 @@
{ {
"name": "firecamp", "name": "firecamp",
"version": "3.0.0", "version": "3.2.1",
"private": true, "private": true,
"description": "Universal API testing and collaboration platform", "description": "Universal API testing and collaboration platform",
"main": "packages/firecamp-desktop-app/dist/services/Main", "main": "packages/firecamp-desktop-app/dist/services/Main",
"homepage": "./dev", "homepage": "./dev",
"scripts": { "scripts": {
"build:workspace": "pnpm --filter=@firecamp/scripts --filter=@firecamp/rest-executor --filter=@firecamp/ws-executor --filter=@firecamp/socket.io-executor build",
"boot": "pnpm install --shamefully-hoist", "boot": "pnpm install --shamefully-hoist",
"bootstrap": "pnpm install --shamefully-hoist", "bootstrap": "pnpm install --shamefully-hoist",
"start": "npx webpack serve --config ./webpack.dev.js", "dev": "run-p build:workspace webpack:dev",
"dev": "APP_VERSION=$npm_package_version AppFormat=webapp && node scripts/build && pnpm build:workspace && pnpm start", "build": "run-s validate:release build:workspace webpack:prod",
"release:web": "AppFormat=webapp && pnpm build:workspace && node scripts/release", "build:workspace": "pnpm --filter=@firecamp/scripts --filter=@firecamp/rest-executor --filter=@firecamp/ws-executor --filter=@firecamp/socket.io-executor build",
"webpack:dev": "webpack serve --config ./webpack.dev.js",
"webpack:prod": "webpack --config ./webpack.prod.js",
"validate:release": "node scripts/release",
"release:web": "pnpm build",
"lint": "eslint packages/firecamp-rest/src/**/*.{ts|tsx} packages/*.js packages-clients/*.js scripts webpack/*.js", "lint": "eslint packages/firecamp-rest/src/**/*.{ts|tsx} packages/*.js packages-clients/*.js scripts webpack/*.js",
"test": "jest", "test": "jest",
"prettify": "prettier --write \"platform/firecamp-platform/src/**/*.(ts|tsx)\" \"packages/firecamp-rest/src/**/*.(ts|tsx)\" \"packages/firecamp-graphql/src/**/*.(ts|tsx)\"", "prettify": "prettier --write \"platform/firecamp-platform/src/**/*.(ts|tsx)\" \"packages/firecamp-rest/src/**/*.(ts|tsx)\" \"packages/firecamp-graphql/src/**/*.(ts|tsx)\"",
@@ -76,6 +79,7 @@
"jest-css-modules-transform": "^4.4.2", "jest-css-modules-transform": "^4.4.2",
"lint-staged": "^13.1.2", "lint-staged": "^13.1.2",
"node-polyfill-webpack-plugin": "^2.0.0", "node-polyfill-webpack-plugin": "^2.0.0",
"npm-run-all": "^4.1.5",
"postcss-loader": "^7.0.2", "postcss-loader": "^7.0.2",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"react-addons-test-utils": "^15.0.2", "react-addons-test-utils": "^15.0.2",
@@ -85,6 +89,7 @@
"semver": "^7.3.5", "semver": "^7.3.5",
"shelljs": "^0.8.5", "shelljs": "^0.8.5",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.9",
"ts-loader": "^9.2.8", "ts-loader": "^9.2.8",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "^5.0.2", "typescript": "^5.0.2",
@@ -95,6 +100,7 @@
"webpack-hot-middleware": "^2.25.1", "webpack-hot-middleware": "^2.25.1",
"webpack-html-plugin": "^0.1.1", "webpack-html-plugin": "^0.1.1",
"webpack-httpolyglot-server": "^0.3.0", "webpack-httpolyglot-server": "^0.3.0",
"webpack-merge": "^5.9.0",
"worker-loader": "^3.0.8" "worker-loader": "^3.0.8"
}, },
"dependencies": { "dependencies": {

View File

@@ -50,7 +50,7 @@
}, },
"dependencies": { "dependencies": {
"@firecamp/agent-manager": "workspace:*", "@firecamp/agent-manager": "workspace:*",
"@firecamp/cloud-apis": "^0.2.8", "@firecamp/cloud-apis": "0.2.10",
"@firecamp/cookie-manager": "^0.0.0", "@firecamp/cookie-manager": "^0.0.0",
"@firecamp/graphql": "workspace:*", "@firecamp/graphql": "workspace:*",
"@firecamp/rest": "workspace:*", "@firecamp/rest": "workspace:*",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -26,6 +26,7 @@ import { useTabStore } from '../../../store/tab';
import { ETabEntityTypes } from '../../tabs/types'; import { ETabEntityTypes } from '../../tabs/types';
import platformContext from '../../../services/platform-context'; import platformContext from '../../../services/platform-context';
import { useExplorerStore } from '../../../store/explorer'; import { useExplorerStore } from '../../../store/explorer';
import useExplorerFacade from './useExplorerFacade';
const Explorer: FC<any> = () => { const Explorer: FC<any> = () => {
const explorerTreeRef = useRef(); const explorerTreeRef = useRef();
@@ -43,29 +44,14 @@ const Explorer: FC<any> = () => {
fetchExplorer, fetchExplorer,
updateCollection, updateCollection,
updateFolder, updateFolder,
moveRequest, // moveRequest,
moveFolder, // moveFolder,
changeCollectionChildrenPosition, changeCollectionChildrenPosition,
changeFolderChildrenPosition, changeFolderChildrenPosition,
deleteCollection, // deleteCollection,
deleteFolder, // deleteFolder,
deleteRequest, // deleteRequest,
} = useExplorerStore( } = useExplorerFacade();
(s) => ({
explorer: s.explorer,
fetchExplorer: s.fetchExplorer,
updateCollection: s.updateCollection,
updateFolder: s.updateFolder,
moveRequest: s.moveRequest,
moveFolder: s.moveFolder,
changeCollectionChildrenPosition: s.changeCollectionChildrenPosition,
changeFolderChildrenPosition: s.changeFolderChildrenPosition,
deleteCollection: s.deleteCollection,
deleteFolder: s.deleteFolder,
deleteRequest: s.deleteRequest,
}),
shallow
);
const { isProgressing, collections, folders, requests } = explorer; const { isProgressing, collections, folders, requests } = explorer;
@@ -457,31 +443,6 @@ const Explorer: FC<any> = () => {
</Container> </Container>
</div> </div>
); );
return (
<div className="w-full h-full flex flex-row">
<Container>
<Container.Body>
<div className="flex flex-col mt-8 p-4 items-center justify-center mt-2">
<div className="fc-sidebar-noproject-icon text-5xl opacity-20 mb-2"></div>
{!!searchString ? (
<div className="text-sm text-app-foreground-inactive text-center mb-1">
<span className="text-base block mb-2">No search found...</span>
Your search is not found within this workspace.
</div>
) : (
<div className="text-sm text-app-foreground-inactive text-center mb-1">
<span className="text-app-foreground text-base block mb-2">
Create your first API collection!
</span>
You don't have any API collection in this workspace.
</div>
)}
</div>
</Container.Body>
</Container>
</div>
);
}; };
export default Explorer; export default Explorer;

View File

@@ -0,0 +1,23 @@
import shallow from 'zustand/shallow';
import { useExplorerStore } from '../../../store/explorer';
const useExplorerFacade = () => {
return useExplorerStore(
(s) => ({
explorer: s.explorer,
fetchExplorer: s.fetchExplorer,
updateCollection: s.updateCollection,
updateFolder: s.updateFolder,
// moveRequest: s.moveRequest,
// moveFolder: s.moveFolder,
changeCollectionChildrenPosition: s.changeCollectionChildrenPosition,
changeFolderChildrenPosition: s.changeFolderChildrenPosition,
// deleteCollection: s.deleteCollection,
// deleteFolder: s.deleteFolder,
// deleteRequest: s.deleteRequest,
}),
shallow
);
};
export default useExplorerFacade;

View File

@@ -1,15 +1,15 @@
import { useState } from 'react';
import classnames from 'classnames';
import { RiBracesLine } from '@react-icons/all-files/ri/RiBracesLine';
import { VscArrowDown } from '@react-icons/all-files/vsc/VscArrowDown';
import { VscFolder } from '@react-icons/all-files/vsc/VscFolder';
import { VscOrganization } from '@react-icons/all-files/vsc/VscOrganization'; import { VscOrganization } from '@react-icons/all-files/vsc/VscOrganization';
import { AiOutlineUserAdd } from '@react-icons/all-files/ai/AiOutlineUserAdd';
import { AiOutlineUserSwitch } from '@react-icons/all-files/ai/AiOutlineUserSwitch'; import { AiOutlineUserSwitch } from '@react-icons/all-files/ai/AiOutlineUserSwitch';
import { VscMultipleWindows } from '@react-icons/all-files/vsc/VscMultipleWindows'; import { VscMultipleWindows } from '@react-icons/all-files/vsc/VscMultipleWindows';
import { VscWindow } from '@react-icons/all-files/vsc/VscWindow'; import {
import { VscTriangleDown } from '@react-icons/all-files/vsc/VscTriangleDown'; Triangle,
MailOpen,
FolderClosed,
Braces,
ArrowDown,
AppWindow,
UserPlus2,
} from 'lucide-react';
import { Button, DropdownMenu, FcIconGetSquare } from '@firecamp/ui'; import { Button, DropdownMenu, FcIconGetSquare } from '@firecamp/ui';
import platformContext from '../../services/platform-context'; import platformContext from '../../services/platform-context';
@@ -27,61 +27,83 @@ enum EMenuOptions {
InviteMembers = 'invite-members', InviteMembers = 'invite-members',
SwitchOrg = 'switch-org', SwitchOrg = 'switch-org',
SwitchWorkspace = 'switch-workspace', SwitchWorkspace = 'switch-workspace',
AllInvitation = 'all-invitation',
} }
const options = [ const options = [
{ {
id: EMenuOptions.Request, id: EMenuOptions.Request,
name: 'New request', name: 'New request',
prefix: () => <FcIconGetSquare size={16} className='text-app-foreground-active' />, prefix: () => (
<FcIconGetSquare size={16} className="text-app-foreground-active" />
),
}, },
{ {
id: EMenuOptions.Collection, id: EMenuOptions.Collection,
name: 'New collection', name: 'New collection',
prefix: () => <VscFolder size={16} className='text-app-foreground-active' />, prefix: () => (
<FolderClosed size={16} className="text-app-foreground-active" />
),
}, },
{ {
id: EMenuOptions.Environment, id: EMenuOptions.Environment,
name: 'New environment', name: 'New environment',
prefix: () => <RiBracesLine size={16} className='text-app-foreground-active' />, prefix: () => <Braces size={16} className="text-app-foreground-active" />,
}, },
{ {
id: EMenuOptions.ImportCollection, id: EMenuOptions.ImportCollection,
name: 'Import collection', name: 'Import collection',
showSeparator: true, showSeparator: true,
prefix: () => <VscArrowDown size={16} className='text-app-foreground-active' />, prefix: () => (
<ArrowDown size={16} className="text-app-foreground-active" />
),
}, },
{ {
id: EMenuOptions.Workspace, id: EMenuOptions.Workspace,
name: 'New workspace', name: 'New workspace',
prefix: () => <VscWindow size={16} className='text-app-foreground-active' />, prefix: () => (
<AppWindow size={16} className="text-app-foreground-active" />
),
}, },
{ {
id: EMenuOptions.Organization, id: EMenuOptions.Organization,
name: 'New organization', name: 'New organization',
prefix: () => <VscOrganization size={16} className='text-app-foreground-active' />, prefix: () => (
<VscOrganization size={16} className="text-app-foreground-active" />
),
}, },
{ {
id: EMenuOptions.InviteMembers, id: EMenuOptions.InviteMembers,
name: 'Invite members', name: 'Invite members',
showSeparator: true, showSeparator: true,
prefix: () => <AiOutlineUserAdd size={16} className='text-app-foreground-active' />, prefix: () => (
<UserPlus2 size={16} className="text-app-foreground-active" />
),
}, },
{ {
id: EMenuOptions.SwitchOrg, id: EMenuOptions.SwitchOrg,
name: 'Switch organization', name: 'Switch organization',
prefix: () => <AiOutlineUserSwitch size={16} className='text-app-foreground-active' />, prefix: () => (
<AiOutlineUserSwitch size={16} className="text-app-foreground-active" />
),
}, },
{ {
id: EMenuOptions.SwitchWorkspace, id: EMenuOptions.SwitchWorkspace,
name: 'Switch workspace', name: 'Switch workspace',
prefix: () => <VscMultipleWindows size={16} className='text-app-foreground-active' />, prefix: () => (
<VscMultipleWindows size={16} className="text-app-foreground-active" />
),
showSeparator: true,
},
{
id: EMenuOptions.AllInvitation,
name: 'View invitation',
prefix: () => <MailOpen size={16} className="text-app-foreground-active" />,
}, },
]; ];
const GlobalCreateDD = ({}) => { const GlobalCreateDD = ({}) => {
const [isOpen, toggleOpen] = useState(false);
const { open } = useTabStore.getState(); const { open } = useTabStore.getState();
const onSelect = (option) => { const onSelect = (option) => {
switch (option.id) { switch (option.id) {
@@ -113,17 +135,19 @@ const GlobalCreateDD = ({}) => {
case EMenuOptions.SwitchWorkspace: case EMenuOptions.SwitchWorkspace:
platformContext.app.modals.openSwitchWorkspace(); platformContext.app.modals.openSwitchWorkspace();
break; break;
case EMenuOptions.AllInvitation:
platformContext.app.modals.openAllInvitation();
break;
} }
}; };
return ( return (
<div className="border-l border-b border-tab-border flex items-center pl-1"> <div className="border-l border-b border-tab-border flex items-center pl-1">
<DropdownMenu <DropdownMenu
onOpenChange={(v) => toggleOpen(v)}
handler={() => ( handler={() => (
<Button <Button
text={'Create'} leftIcon={<Triangle size={20} />}
rightIcon={<VscTriangleDown size={12} className={classnames({'transform rotate-180': isOpen})}/>} animate={false}
transparent transparent
primary primary
compact compact
@@ -131,9 +155,10 @@ const GlobalCreateDD = ({}) => {
/> />
)} )}
options={options} options={options}
footer={<div className="mt-1">v{process.env.APP_VERSION}</div>}
onSelect={onSelect} onSelect={onSelect}
classNames={{ classNames={{
dropdown: '-ml-[2px]', dropdown: '-ml-[2px] pb-0',
}} }}
/> />
</div> </div>

View File

@@ -5,12 +5,6 @@ import { Modal } from '@firecamp/ui';
import './ErrorPopup.sass'; import './ErrorPopup.sass';
const ErrorPopup: FC<FallbackProps> = ({ error }) => { const ErrorPopup: FC<FallbackProps> = ({ error }) => {
const bg = {
modal: {
background: '#c84a1782',
color: '#e3dfdf',
},
};
let [isOpen, toggleOpen] = useState(true); let [isOpen, toggleOpen] = useState(true);
@@ -24,7 +18,6 @@ const ErrorPopup: FC<FallbackProps> = ({ error }) => {
return ( return (
<Modal <Modal
opened={isOpen} opened={isOpen}
styles={{ content: bg.modal }}
onClose={_onClose} onClose={_onClose}
className="fc-error-popup" className="fc-error-popup"
> >

View File

@@ -240,8 +240,7 @@ const SidebarContainer: FC<any> = () => {
const _setActiveItem = async (selected) => { const _setActiveItem = async (selected) => {
if (!selected?.id) return; if (!selected?.id) return;
else if (selected.item == EActivityBarItems.User) { else if (selected.item == EActivityBarItems.User) {
window.open('https://app.firecamp.dev/profile/info'); platformContext.app.modals.openUserProfile();
platformContext.app.modals.openSaveRequest(); // openUserProfile();
} else if (selected.item == EActivityBarItems.Settings) } else if (selected.item == EActivityBarItems.Settings)
platformContext.app.modals.openWorkspaceManagement(); platformContext.app.modals.openWorkspaceManagement();
else if (selected.item == EActivityBarItems.SslNProxy) else if (selected.item == EActivityBarItems.SslNProxy)

View File

@@ -18,6 +18,8 @@ import WorkspaceManagement from './workspace/WorkspaceManagement';
import SwitchWorkspace from './workspace/SwitchWorkspace'; import SwitchWorkspace from './workspace/SwitchWorkspace';
import CloneEnvironment from './environment/CloneEnvironment'; import CloneEnvironment from './environment/CloneEnvironment';
import EditRequest from './request/edit-request/EditRequest'; import EditRequest from './request/edit-request/EditRequest';
import ProfileManagement from './profile/ProfileManagement';
import AllInvitation from './invitation/AllInvitation';
export const ModalContainer = () => { export const ModalContainer = () => {
const { currentOpenModal, isOpen, close } = useModalStore( const { currentOpenModal, isOpen, close } = useModalStore(
@@ -58,7 +60,8 @@ export const ModalContainer = () => {
return <CloneEnvironment opened={isOpen} onClose={close} />; return <CloneEnvironment opened={isOpen} onClose={close} />;
// User // User
// case EPlatformModalTypes.UserProfile: return <UserProfile isOpen={isOpen} onClose={close} />; case EPlatformModalTypes.UserProfile:
return <ProfileManagement opened={isOpen} onClose={close} />;
// Auth // Auth
case EPlatformModalTypes.SignIn: case EPlatformModalTypes.SignIn:
@@ -73,6 +76,8 @@ export const ModalContainer = () => {
return <ResetPassword opened={isOpen} onClose={close} />; return <ResetPassword opened={isOpen} onClose={close} />;
case EPlatformModalTypes.RefreshToken: case EPlatformModalTypes.RefreshToken:
return <RefreshToken opened={isOpen} onClose={close} />; return <RefreshToken opened={isOpen} onClose={close} />;
case EPlatformModalTypes.AllInvitation:
return <AllInvitation opened={isOpen} onClose={close} />;
default: default:
return <></>; return <></>;
} }

View File

@@ -51,7 +51,7 @@ const ForgotPassword: FC<IModal> = ({ opened = false, onClose = () => {} }) => {
const _onKeyDown = (e: any) => e.key === 'Enter' && handleSubmit(_onSubmit); const _onKeyDown = (e: any) => e.key === 'Enter' && handleSubmit(_onSubmit);
return ( return (
<Drawer opened={opened} onClose={onClose} size={440}> <Drawer opened={opened} onClose={onClose} size={440} classNames={{body: 'mt-[10vh]'}}>
<Mail <Mail
size="48" size="48"
className="mb-6 mx-auto text-activityBar-foreground-inactive" className="mb-6 mx-auto text-activityBar-foreground-inactive"

View File

@@ -11,7 +11,7 @@ import platformContext from '../../../services/platform-context';
*/ */
const SignIn: FC<IModal> = ({ opened, onClose }) => { const SignIn: FC<IModal> = ({ opened, onClose }) => {
return ( return (
<Drawer opened={opened} onClose={onClose} size={440}> <Drawer opened={opened} onClose={onClose} size={440} classNames={{body: 'mt-[10vh]'}}>
{/* <img className="mx-auto w-12 mb-6" src={'img/firecamp-logo.svg'} /> */} {/* <img className="mx-auto w-12 mb-6" src={'img/firecamp-logo.svg'} /> */}
<div className="mb-4"> <div className="mb-4">
<FcLogo className="mx-auto w-14" size={80} /> <FcLogo className="mx-auto w-14" size={80} />

View File

@@ -62,7 +62,7 @@ const SignInWithEmail: FC<IModal> = ({ opened, onClose }) => {
}; };
return ( return (
<Drawer opened={opened} onClose={onClose} size={440}> <Drawer opened={opened} onClose={onClose} size={440} classNames={{body: 'mt-[10vh]'}}>
<div className="mb-2"> <div className="mb-2">
<FcLogo className="mx-auto w-14" size={80} /> <FcLogo className="mx-auto w-14" size={80} />
</div> </div>

View File

@@ -68,7 +68,7 @@ const SignUp: FC<IModal> = ({ opened, onClose }) => {
const _onKeyDown = (e) => e.key === 'Enter' && handleSubmit(_onSignUp); const _onKeyDown = (e) => e.key === 'Enter' && handleSubmit(_onSignUp);
return ( return (
<Drawer opened={opened} onClose={onClose} size={440}> <Drawer opened={opened} onClose={onClose} size={440} classNames={{body: 'mt-[10vh]'}}>
{/* <img className="mx-auto w-12 mb-6" src={'img/firecamp-logo.svg'} /> */} {/* <img className="mx-auto w-12 mb-6" src={'img/firecamp-logo.svg'} /> */}
<div className="-mt-4"> <div className="-mt-4">
<FcLogo className="mx-auto w-14" size={80} /> <FcLogo className="mx-auto w-14" size={80} />
@@ -101,7 +101,10 @@ const SignUp: FC<IModal> = ({ opened, onClose }) => {
required: true, required: true,
maxLength: 50, maxLength: 50,
minLength: 1, minLength: 1,
pattern: /^[0-9a-zA-Z ]+$/, pattern: {
value: /^[0-9a-zA-Z ]+$/,
message: "The username should not have any special characters"
},
}} }}
useformRef={form} useformRef={form}
onKeyDown={_onKeyDown} onKeyDown={_onKeyDown}
@@ -110,7 +113,7 @@ const SignUp: FC<IModal> = ({ opened, onClose }) => {
? errors?.username?.message || 'Please enter username' ? errors?.username?.message || 'Please enter username'
: '' : ''
} }
wrapperClassName="!mb-2" wrapperClassName="!mb-4"
/> />
<Input <Input
placeholder="Enter your email" placeholder="Enter your email"
@@ -132,7 +135,7 @@ const SignUp: FC<IModal> = ({ opened, onClose }) => {
'Please enter valid username or password' 'Please enter valid username or password'
: '' : ''
} }
wrapperClassName="!mb-2" wrapperClassName="!mb-4"
/> />
<Input <Input
placeholder="Enter password" placeholder="Enter password"
@@ -163,7 +166,7 @@ const SignUp: FC<IModal> = ({ opened, onClose }) => {
? errors?.password?.message || 'Please enter valid password' ? errors?.password?.message || 'Please enter valid password'
: '' : ''
} }
wrapperClassName="!mb-3" wrapperClassName="!mb-4"
/> />
<Button <Button

View File

@@ -118,9 +118,6 @@ const CloneEnvironment: FC<IModal> = ({ opened, onClose = () => {} }) => {
opened={opened} opened={opened}
onClose={onClose} onClose={onClose}
size={500} size={500}
classNames={{
content: 'h-[750px]'
}}
title={ title={
!isFetching ? ( !isFetching ? (
<> <>

View File

@@ -0,0 +1,134 @@
import { FC, useEffect, useState } from 'react';
import { _array } from '@firecamp/utils';
import { Container, Drawer, IModal, ProgressBar } from '@firecamp/ui';
import InvitationCard from './InvitationCard';
import platformContext from '../../../services/platform-context';
import { IInvite } from './InvitationCard.interface';
import { Rest } from '@firecamp/cloud-apis';
const AllInvitation: FC<IModal> = ({ opened, onClose }) => {
const [list, updateList] = useState<Array<IInvite> | []>([]);
const [inviteId, updateInviteId] = useState('');
const [isRequesting, setIsRequesting] = useState(false);
const [isFetching, setIsFetching] = useState(false);
useEffect(() => {
setIsFetching(true);
Rest.invitation
.getMyPendingInvitations()
.then((res) => res.data)
.then((res) => {
const { error } = res;
if (!error) {
updateList(res);
}
})
.finally(() => setIsFetching(false));
}, []);
const switchToWrs = async (wrs: any, org: any) => {
await platformContext.app.switchWorkspace(wrs, org);
platformContext.app.modals.close();
};
const _handleInvitation = async (invite: IInvite) => {
if (isRequesting) return;
setIsRequesting(true);
updateInviteId(invite.token);
Rest.invitation
.accept(invite.token)
.then((res) => res.data)
.then(({ flag, data, message }) => {
if (flag) {
const { org, workspace } = data;
platformContext.app.notify.success(
'You have successfully joined the invitation'
);
platformContext.window.confirm({
message:
'Congratulations on joining the invitation! Are you interested in switching workspaces and start collaboration?',
labels: { confirm: 'Yes, switch workspace.' },
onConfirm: () => switchToWrs(workspace, org),
onCancel: () => {
setIsRequesting(false);
updateInviteId('');
},
});
let Index = list.findIndex((i) => i.token === invite.token);
updateList((list) => [
...list.slice(0, Index),
...list.slice(Index + 1),
]);
} else {
platformContext.app.notify.alert(message);
}
})
.catch((e) => {
platformContext.app.notify.alert(e.response?.data.message || e.message);
})
.finally(() => {
setIsRequesting(false);
updateInviteId('');
});
};
return (
<Drawer
opened={opened}
onClose={onClose}
size={600}
title={
<div className="text-lg leading-5 px-3 flex items-center font-medium">
All Invitation
</div>
}
>
<Container className="py-4">
<ProgressBar active={isFetching} />
{!isFetching ? (
<Container.Body>
{!_array.isEmpty(list) ? (
list.map((invite, index) => (
<InvitationCard
key={index}
inviterName={invite.inviterName}
orgName={invite.orgName}
workspaceName={invite.workspaceName}
role={invite.role}
isRequesting={isRequesting}
disabled={inviteId.length > 0 && inviteId === invite.token}
onAccept={() => _handleInvitation(invite)}
/>
))
) : (
<NoInvitationFound />
)}
</Container.Body>
) : (
<></>
)}
</Container>
</Drawer>
);
};
export default AllInvitation;
const NoInvitationFound = () => {
return (
<div className="p-8 flex flex-col justify-center items-center">
<div className="text-sm max-w-xs mx-auto text-center px-10">
<label className="font-semibold text-app-foreground uppercase">
No Invitation Found
</label>
<span className="block font-normal text-app-foreground-inactive">
You do not have any new invitations at this time.
</span>
</div>
</div>
);
};

View File

@@ -0,0 +1,13 @@
export interface IInvite {
inviterName: string;
orgName: string;
workspaceName: string;
role: number;
token?: string;
}
export type IInvitationCard = IInvite & {
disabled: boolean;
onAccept: () => void;
isRequesting: boolean;
};

View File

@@ -0,0 +1,66 @@
import { FC } from 'react';
import { Button, TabHeader } from '@firecamp/ui';
import { EUserRolesWorkspace } from '../../../types';
import { IInvitationCard } from './InvitationCard.interface';
const RoleOptions = [
{
id: EUserRolesWorkspace.Owner,
name: 'Owner',
},
{
id: EUserRolesWorkspace.Admin,
name: 'Admin',
},
{
id: EUserRolesWorkspace.Collaborator,
name: 'Collaborator',
},
{
id: EUserRolesWorkspace.Viewer,
name: 'Viewer',
},
];
const InvitationCard: FC<IInvitationCard> = ({
inviterName,
workspaceName,
orgName,
role,
disabled = false,
isRequesting = false,
onAccept,
}) => {
const userRole = RoleOptions.find((r) => r.id == role);
// console.log(`user.role`, userRole, role);
return (
<div className="bg-app-background p-4 shadow-sm rounded border mb-4">
{/* <label className="text-sm font-semibold leading-3 block text-app-foreground-inactive uppercase w-full relative py-4">
NEW INVITATION
</label> */}
<div className="my-4">
<span className="font-semibold">{inviterName}</span>
<span> has invited you to collaborate on the </span>
<span className="font-semibold">{orgName}</span>
<span>/</span>
<span className="font-semibold">{workspaceName}</span>
<span> as </span>
<span className="font-semibold">{userRole?.name}</span>
</div>
<TabHeader className="!px-0">
<TabHeader.Right>
<Button
text={isRequesting ? 'Accepting...': 'Accept Invitation'}
disabled={disabled}
onClick={() => onAccept()}
primary
xs
/>
</TabHeader.Right>
</TabHeader>
</div>
);
};
export default InvitationCard;

View File

@@ -1,36 +1,175 @@
import { FC, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { import { Rest } from '@firecamp/cloud-apis';
Input,
TextArea,
TabHeader,
Button,
Modal,
IModal,
SecondaryTab,
} from '@firecamp/ui';
import { _misc } from '@firecamp/utils'; import { _misc } from '@firecamp/utils';
import { VscEdit } from '@react-icons/all-files/vsc/VscEdit'; import { IModal, SecondaryTab, Drawer, ProgressBar } from '@firecamp/ui';
import EditOrganization from './tabs/EditOrganization';
import Members from './tabs/Members';
import BillingTab from './tabs/Billing';
import Workspaces from './tabs/Workspaces';
import { usePlatformStore } from '../../../store/platform';
import platformContext from '../../../services/platform-context';
import { Regex } from '../../../constants';
const OrgManagement: FC<IModal> = ({ opened = false, onClose = () => {} }) => { enum ETabTypes {
// let { create, checkNameAvailability } = useWorkspaceStore((s: IWorkspaceStore)=>({ Overview = 'overview',
// create: s.create, Workspaces = 'workspaces',
// checkNameAvailability: s.checkNameAvailability Members = 'members',
// })) Billing = 'billing',
}
const tabs = [
{ name: 'Overview', id: ETabTypes.Overview },
{ name: 'Workspaces', id: ETabTypes.Workspaces },
{ name: 'Members', id: ETabTypes.Members },
{ name: 'Billing', id: ETabTypes.Billing },
];
const tabs = [ // to convert date into readable date
{ name: 'Edit', id: 'edit' }, export const getFormalDate = (convertDate: string) => {
{ name: 'Members', id: 'members' }, let date = new Date(convertDate);
{ name: 'Billing', id: 'billing' }, if (date.toString() === 'Invalid Date') return 'N/A';
];
let [activeTab, setActiveTab] = useState<string>(tabs[0].id); let dateString = date.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
return dateString;
};
const OrgManagement: FC<IModal> = ({ opened = false, onClose = () => { } }) => {
const { organization, setOrg } = usePlatformStore((s) => ({
organization: s.organization,
setOrg: s.setOrg,
}));
const [activeTab, setActiveTab] = useState<string>(tabs[0].id);
const [isFetching, setIsFetching] = useState(false);
const [workspaces, updateWorkspaces] = useState([]);
const [members, updateMembers] = useState([]);
const [org, updOrg] = useState(organization);
const [error, setError] = useState({ name: '' });
const [isRequesting, setIsRequesting] = useState(false);
// getting organization's workspaces / members listing once
useEffect(() => {
if (activeTab === ETabTypes.Workspaces && workspaces.length === 0) {
setIsFetching(true);
Rest.organization
.getMyWorkspacesOfOrg(organization.__ref.id)
.then((res) => res.data)
.then((list) => {
updateWorkspaces(list);
})
.finally(() => setIsFetching(false));
} else if (activeTab === ETabTypes.Members && members.length === 0) {
setIsFetching(true);
Rest.organization
.getMembers(organization.__ref.id)
.then((res) => res.data)
.then((list) => {
updateMembers(list);
})
.finally(() => setIsFetching(false));
}
}, [activeTab]);
const onChange = (e, reset) => {
if (reset) {
updOrg(organization);
if (error.name) setError({ name: '' });
} else {
const { name, value } = e.target;
if (error.name) setError({ name: '' });
updOrg((o) => ({ ...o, [name]: value }));
}
};
const onUpdate = () => {
if (isRequesting) return;
const name = org.name.trim();
const description = org.description?.trim();
if (!name || name.length < 4) {
setError({
name: 'The organization name must have minimum 4 characters',
});
return;
}
const isValid = Regex.OrgName.test(name);
if (!isValid) {
setError({
name: 'The org name must not contain any spaces or special characters.',
});
return;
}
if (
organization.name === org.name &&
organization.description === org.description
)
return;
const _org: { name?: string; description?: string } = {};
if (organization.name !== name) {
_org.name = name;
}
if (organization.description !== description) {
_org.description = description;
}
setIsRequesting(true);
Rest.organization
.update(organization.__ref.id, _org)
.then((res) => res.data)
.then(({ error, message }) => {
if (!error) {
platformContext.app.notify.success(
"The organization's detail has been updated successfully."
);
setOrg({ ...organization, ..._org });
} else {
platformContext.app.notify.alert(message);
}
})
.catch((e) => {
platformContext.app.notify.alert(e.response?.data.message || e.message);
})
.finally(() => {
setIsRequesting(false);
});
};
const renderTab = (tabId: string) => { const renderTab = (tabId: string) => {
switch (tabId) { switch (tabId) {
case 'edit': case ETabTypes.Overview:
return <EditInfoTab />; return (
case 'members': <EditOrganization
return <MembersTab />; organization={org}
case 'billing': error={error}
isRequesting={isRequesting}
onSubmit={onUpdate}
onChange={onChange}
enableReset={
organization.name !== org.name ||
organization.description !== org.description
}
// disabled={true} // TODO: only allow owner
/>
);
case ETabTypes.Workspaces:
return <Workspaces workspaces={workspaces} isFetching={isFetching} />;
case ETabTypes.Members:
return (
<Members
members={members}
updateMembers={updateMembers}
isFetching={isFetching}
organizationId={organization.__ref.id}
/>
);
case ETabTypes.Billing:
return <BillingTab />; return <BillingTab />;
default: default:
return <></>; return <></>;
@@ -38,128 +177,25 @@ const OrgManagement: FC<IModal> = ({ opened = false, onClose = () => {} }) => {
}; };
return ( return (
<Modal <Drawer
opened={opened} opened={opened}
onClose={onClose} onClose={onClose}
size={600} size={600}
classNames={{
body: 'h-[80vh]',
}}
title={ title={
<div className="text-lg leading-5 px-6 flex items-center font-medium"> <div className="text-lg leading-5 px-3 flex items-center font-medium">
Organization Management Organization Management
</div> </div>
} }
> >
<> <ProgressBar active={isFetching} />
<SecondaryTab <SecondaryTab
className="flex items-center p-4 pb-0" className="pt-4"
list={tabs} list={tabs}
activeTab={'edit'} activeTab={activeTab}
onSelect={setActiveTab} onSelect={setActiveTab}
/> />
{renderTab(activeTab)} {renderTab(activeTab)}
</> </Drawer>
<div className="!py-3 border-t border-app-border ">
<TabHeader>
<TabHeader.Right>
<Button text="Cancel" onClick={(e) => onClose(e)} ghost xs />
<Button
text={false ? 'Creating...' : 'Create'}
onClick={() => {}}
disabled={false}
primary
xs
/>
</TabHeader.Right>
</TabHeader>
</div>
</Modal>
); );
}; };
export default OrgManagement; export default OrgManagement;
const EditInfoTab: FC<any> = () => {
let [org, setOrg] = useState({
name: '',
description: '',
});
return (
<div className="px-6 py-3">
<label className="text-sm font-semibold leading-3 block text-app-foreground-inactive uppercase w-full relative mb-2">
ADD NEW WORKSPACE INFO
</label>
<div className="mt-4">
<Input
autoFocus={true}
label="Name"
placeholder="Organization name"
name={'name'}
value={org.name || ''}
onChange={() => {}}
onKeyDown={() => {}}
onBlur={() => {}}
disabled={true}
iconPosition="right"
icon={<VscEdit />}
// error={error.name}
/>
{/* {
flag_wrsNameCheckInProgress === false && flag_isWrsNameAvailable === undefined
? <Alert withBorder text="please type the workspace name to check its availability" info/>: <></>
}
{
flag_wrsNameCheckInProgress === true? <Alert withBorder text={`checking workspace name availability - ${workspace?.name}`} warning/>: <></>
}
{
flag_wrsNameCheckInProgress === false && flag_isWrsNameAvailable === true
? <Alert withBorder text={`the workspace name is available - ${workspace?.name}`} success/>: <></>
}
{
flag_wrsNameCheckInProgress === false && flag_isWrsNameAvailable === false
? <Alert withBorder text={`the workspace name is not available - ${workspace?.name}`} error/>: <></>
}
{
error.name?.length
? <Alert withBorder text={error.name} error/>: <></>
} */}
</div>
<TextArea
type="text"
minHeight="200px"
label="Description (optional)"
labelClassName="fc-input-label"
placeholder="Description"
note="Markdown supported in description"
name={'description'}
value={org.description || ''}
onChange={() => {}}
disabled={true}
iconPosition="right"
icon={<VscEdit />}
/>
{/* {error.global?.length ? (
<TabHeader.Left>
<div
style={{
fontSize: '12px',
color: 'red' //'green'
}}
>
{error.global}
</div>
</TabHeader.Left>
) : <></>} */}
</div>
);
};
const MembersTab = () => {
return <div className="p-6"> Members Tab</div>;
};
const BillingTab = () => {
return <div className="p-6"> Billing Tab</div>;
};

View File

@@ -0,0 +1,9 @@
const BillingTab = () => {
return <div className="p-4 text-activityBar-foreground-inactive">
<span className="my-4">
Coming soon...
</span>
</div>;
};
export default BillingTab;

View File

@@ -0,0 +1,65 @@
import { FC } from 'react';
import { Button, Container, Input, TabHeader, TextArea } from '@firecamp/ui';
const EditOrganization: FC<any> = ({
organization,
error,
isRequesting,
onSubmit,
onChange,
enableReset = true,
disabled = false
}) => {
return (
<Container className="py-6 px-3 flex-1 flex flex-col h-full">
<Container.Body>
<label className="text-sm font-semibold leading-3 block text-app-foreground-inactive uppercase w-full relative mb-4">
UPDATE ORGANIZATION INFO
</label>
<div>
<Input
autoFocus={true}
label="Name"
placeholder="Organization name"
name={'name'}
defaultValue={organization.name || ''}
onChange={onChange}
onKeyDown={() => {}}
onBlur={() => {}}
error={error.name}
wrapperClassName="!mb-3"
/>
</div>
<TextArea
type="text"
minHeight="240px"
label="Description (optional)"
labelClassName="fc-input-label"
placeholder="Description"
note="Markdown supported in description"
name={'description'}
defaultValue={organization.description || ''}
onChange={onChange}
/>
</Container.Body>
<Container.Footer>
<TabHeader className="!px-0">
<TabHeader.Left></TabHeader.Left>
<TabHeader.Right>
{/* {enableReset ? <Button text="Undo" onClick={(e) => onChange(e, true)} ghost xs /> : <></>} */}
<Button
text={isRequesting ? 'Updating...' : 'Update'}
onClick={onSubmit}
disabled={isRequesting || disabled || !enableReset}
primary
xs
/>
</TabHeader.Right>
</TabHeader>
</Container.Footer>
</Container>
);
};
export default EditOrganization;

View File

@@ -0,0 +1,196 @@
import { FC, useEffect, useRef, useState } from 'react';
import { ChevronDown } from 'lucide-react';
import cx from 'classnames';
import {
Button,
Container,
DropdownMenu,
PrimitiveTable,
TTableApi,
} from '@firecamp/ui';
import { _array } from '@firecamp/utils';
import { Rest } from '@firecamp/cloud-apis';
import platformContext from '../../../../services/platform-context';
import { EUserRolesWorkspace } from '../../../../types';
import { getFormalDate } from '../OrgManagement';
const columns = [
{ id: 'index', name: 'No.', key: 'index', width: '35px', fixedWidth: true },
{
id: 'name',
name: 'Name',
key: 'name',
width: '100px',
resizeWithContainer: true,
},
{
id: 'role',
name: 'Role',
key: 'role',
width: '100px',
fixedWidth: true,
},
// {
// id: 'joinedAt',
// name: 'Joined Date',
// key: 'joinedAt',
// width: '130px',
// fixedWidth: true,
// },
];
const RoleOptions = [
{
id: EUserRolesWorkspace.Owner,
name: 'Owner',
},
{
id: EUserRolesWorkspace.Admin,
name: 'Admin',
},
{
id: EUserRolesWorkspace.Collaborator,
name: 'Collaborator',
},
];
const Members = ({
organizationId = '',
members = [],
updateMembers,
isFetching = false,
}) => {
const tableApi = useRef<TTableApi>(null);
useEffect(() => {
if (!_array.isEmpty(members)) {
const memberList = members.map((m, i) => {
return {
id: m.id,
name: m.username,
role: m.role,
joinedAt: getFormalDate(m.__ref.joinedAt),
};
});
tableApi.current.initialize(memberList);
}
}, [members]);
const onChangeRole = (row) => {
platformContext.window.confirm({
message: `Please confirm, You're assigning ${row.role.name} role to ${row.name}, right?`,
labels: {
cancel: 'Cancel',
confirm: 'Yes, change the role.',
},
onConfirm: () => {
Rest.organization
.changeMemberRole(organizationId, row.id, row.role.id)
.then((res) => res.data)
.then(({ error, message }) => {
if (!error) {
tableApi.current.setRow({ ...row, role: row.role.id });
// update the member listing after update
let Index = members.findIndex((m) => m.id == row.id);
updateMembers([
...members.slice(0, Index),
{ ...members[Index], role: row.role.id },
...members.slice(Index + 1),
]);
platformContext.app.notify.success(
"The member's role has been changed successfully."
);
} else {
platformContext.app.notify.alert(message);
}
})
.catch((e) => {
platformContext.app.notify.alert(
e.response?.data.message || e.message
);
});
},
});
};
const renderCell = (column, cellValue, rowIndex, row, tableApi, onChange) => {
switch (column.id) {
case 'index':
return <div className="px-2 text-base"> {rowIndex + 1} </div>;
break;
case 'name':
case 'joinedAt':
return <div className="p-1 text-base">{cellValue}</div>;
break;
case 'role':
return (
<div className="p-1 text-center">
<RoleDD
role={row.role}
onSelect={(role) => onChangeRole({ ...row, role })}
/>
</div>
);
break;
default:
return <></>;
}
};
return (
<Container className="gap-2 pt-2 !h-[80vh]">
<Container.Body>
<PrimitiveTable
classes={{
container: 'h-full',
}}
columns={columns}
rows={[]}
showDefaultEmptyRows={false}
renderColumn={(c) => c.name}
renderCell={renderCell}
onChange={console.log}
onMount={(api) => (tableApi.current = api)}
/>
</Container.Body>
</Container>
);
};
export default Members;
const RoleDD: FC<{
role: number;
onSelect: (role: { name: string; id: number }) => void;
}> = ({ role, onSelect }) => {
const [isOpen, toggleOpen] = useState(false);
const _role = RoleOptions.find((r) => r.id == role);
if (!_role) return <></>;
return (
<DropdownMenu
onOpenChange={(v) => toggleOpen(v)}
handler={() => (
<Button
text={_role.name}
rightIcon={
<ChevronDown
size={12}
className={cx({ 'transform rotate-180': isOpen })}
/>
}
compact
ghost
sm
/>
)}
options={RoleOptions}
onSelect={onSelect}
disabled={role === EUserRolesWorkspace.Owner}
width={115}
sm
/>
);
};

View File

@@ -0,0 +1,86 @@
import { FC, useEffect, useRef } from 'react';
import {
Container,
PrimitiveTable,
TTableApi,
} from '@firecamp/ui';
import { _array } from '@firecamp/utils';
import { getFormalDate } from '../OrgManagement';
const columns = [
{ id: 'index', name: 'No.', key: 'index', width: '35px', fixedWidth: true },
{
id: 'name',
name: 'Name',
key: 'name',
width: '100px',
resizeWithContainer: true,
},
{
id: 'created',
name: 'Created Date',
key: 'created',
width: '130px',
fixedWidth: true,
},
{
id: 'members',
name: 'Members',
key: 'members',
width: '100px',
fixedWidth: true,
},
];
const Workspaces: FC<{workspaces: Array<any>, isFetching: boolean}> = ({ workspaces = [], isFetching = false }) => {
const tableApi = useRef<TTableApi>(null);
useEffect(() => {
if (!_array.isEmpty(workspaces)) {
const workspaceList = workspaces.map((m, i) => {
return {
id: m.__ref?.id,
name: m.name,
created: getFormalDate(m.__ref.createdAt),
members: m.members.length,
};
});
tableApi.current.initialize(workspaceList);
}
}, [workspaces]);
const renderCell = (column, cellValue, rowIndex, row, tableApi, onChange) => {
switch (column.id) {
case 'index':
return <div className="px-2"> {rowIndex + 1} </div>;
break;
case 'name':
case 'members':
case 'created':
return <div className="p-1 text-base">{cellValue}</div>;
break;
default:
return <></>;
}
};
return (
<Container className="gap-2 pt-2 !h-[80vh]">
<Container.Body>
<PrimitiveTable
classes={{
container: 'h-full',
}}
columns={columns}
rows={[]}
showDefaultEmptyRows={false}
renderColumn={(c) => c.name}
renderCell={renderCell}
onChange={console.log}
onMount={(api) => (tableApi.current = api)}
/>
</Container.Body>
</Container>
);
};
export default Workspaces;

View File

@@ -0,0 +1,178 @@
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { Button, Input } from '@firecamp/ui';
import { VscEye } from '@react-icons/all-files/vsc/VscEye';
import { Rest } from '@firecamp/cloud-apis';
import platformContext from '../../../services/platform-context';
/**
* Change Password component
*/
const ChangePassword = () => {
const [isRequesting, setFlagIsRequesting] = useState(false);
const [oldPassword, toggleOldPassword] = useState(false);
const [showPassword, toggleShowPassword] = useState(false);
const [confirmPassword, toggleConfirmPassword] = useState(false);
const form = useForm();
const { handleSubmit, errors, getValues, reset } = form;
const _onSubmit = async (payload: {
currentPassword: string;
newPassword: string;
}) => {
if (isRequesting) return;
setFlagIsRequesting(true);
await Rest.user
.changePassword({
currentPassword: payload.currentPassword,
newPassword: payload.newPassword,
})
.then((res) => res.data)
.then(({ error, message }) => {
if (!error) {
reset({ currentPassword: '', newPassword: '', confirmPassword: '' });
platformContext.app.notify.success(message);
} else {
platformContext.app.notify.alert(
message ?? `Failed to change password!`
);
}
})
.catch((e) => {
platformContext.app.notify.alert(
e?.response?.data?.message || e.message,
{
labels: { alert: 'error!' },
}
);
})
.finally(() => {
setFlagIsRequesting(false);
});
};
const _onKeyDown = (e: any) => e.key === 'Enter' && handleSubmit(_onSubmit);
return (
<>
<form onSubmit={handleSubmit(_onSubmit)} className="mx-2 mt-4">
<Input
placeholder="Enter old password"
key={'currentPassword'}
name={'currentPassword'}
type={oldPassword ? 'text' : 'password'}
label="Old Password"
iconPosition="right"
icon={
<VscEye
title="password"
size={16}
onClick={() => {
toggleOldPassword(!oldPassword);
}}
/>
}
registerMeta={{
required: 'Please enter your current password',
minLength: {
value: 8,
message: 'Password should be at least 8 character',
},
maxLength: {
value: 50,
message: 'Password should not exceed 50 character',
},
}}
useformRef={form}
onKeyDown={_onKeyDown}
error={
errors?.currentPassword
? errors?.currentPassword?.message || 'Invalid password'
: ''
}
/>
<Input
placeholder="Enter new password"
key={'newPassword'}
name={'newPassword'}
type={showPassword ? 'text' : 'password'}
label="New Password"
iconPosition="right"
icon={
<VscEye
title="password"
size={16}
onClick={() => {
toggleShowPassword(!showPassword);
}}
/>
}
registerMeta={{
required: 'Please enter your new password',
minLength: {
value: 8,
message: 'Password should be at least 8 character',
},
maxLength: {
value: 50,
message: 'Password should not exceed 50 character',
},
}}
useformRef={form}
onKeyDown={_onKeyDown}
error={
errors?.newPassword
? errors?.newPassword?.message || 'Invalid password'
: ''
}
/>
<Input
placeholder="Enter password again"
key={'confirmPassword'}
name={'confirmPassword'}
type={confirmPassword ? 'text' : 'password'}
label="Confirm Password"
iconPosition="right"
icon={
<VscEye
title="password"
size={16}
onClick={() => {
toggleConfirmPassword(!confirmPassword);
}}
/>
}
registerMeta={{
required: 'Please enter password again',
validate: (value) => {
const { newPassword } = getValues();
return newPassword === value || 'Passwords should match';
},
}}
useformRef={form}
onKeyDown={_onKeyDown}
error={
errors?.confirmPassword
? errors?.confirmPassword?.message || 'Invalid password'
: ''
}
/>
<Button
type="submit"
text={isRequesting ? 'Updating Password...' : 'Update Password'}
onClick={handleSubmit(_onSubmit)}
fullWidth
primary
sm
/>
</form>
</>
);
};
export default ChangePassword;

View File

@@ -0,0 +1,46 @@
import { FC, useState } from 'react';
import { Drawer, IModal, SecondaryTab } from '@firecamp/ui';
import ChangePassword from './ChangePassword';
import UpdateProfile from './UpdateProfile';
const tabs = [
{ name: 'Profile', id: 'profile' },
{ name: 'Change Password', id: 'password' },
];
const ProfileManagement: FC<IModal> = ({ opened, onClose }) => {
let [activeTab, setActiveTab] = useState<string>(tabs[0].id);
const renderTab = (tabId: string) => {
switch (tabId) {
case 'profile':
return <UpdateProfile />;
case 'password':
return <ChangePassword />;
default:
return <></>;
}
};
return (
<Drawer
opened={opened}
onClose={onClose}
size={600}
// classNames={{
// body: 'h-[80vh]',
// }}
title={
<div className="text-lg leading-5 px-6 flex items-center font-medium">
Profile Management
</div>
}
>
<SecondaryTab
className="py-4"
list={tabs}
activeTab={activeTab}
onSelect={setActiveTab}
/>
{renderTab(activeTab)}
</Drawer>
);
};
export default ProfileManagement;

View File

@@ -0,0 +1,113 @@
import { FC, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { Button, Input } from '@firecamp/ui';
import { Rest } from '@firecamp/cloud-apis';
import platformContext from '../../../services/platform-context';
import { useUserStore } from '../../../store/user';
/**
* Update Profile component
*/
const UpdateProfile = () => {
const [isRequesting, setFlagIsRequesting] = useState(false);
const form = useForm();
let { handleSubmit, errors, setValue } = form;
const { user, setUser } = useUserStore((s) => ({
user: s.user,
setUser: s.setUser
}));
// set the initial value for the form
useEffect(() => {
setValue("name", user.name);
},[user]);
const _onSubmit = async (payload: { name: string }) => {
let { name } = payload;
if (isRequesting || user.name === name) return;
setFlagIsRequesting(true);
await Rest.user
.updateProfile({ name })
.then((res) => res.data)
.then(({ error, message }) => {
if (!error) {
platformContext.app.notify.success(
`Your profile details are updated`
);
// update the user store
setUser({...user, name})
} else {
platformContext.app.notify.alert(message);
}
})
.catch((e) => {
platformContext.app.notify.alert(
e?.response?.data?.message || e.message,
{
labels: { alert: 'error!' },
}
);
})
.finally(() => {
setFlagIsRequesting(false);
});
};
const _onKeyDown = (e: any) => e.key === 'Enter' && handleSubmit(_onSubmit);
return (
<>
<form onSubmit={handleSubmit(_onSubmit)} className="mx-2 mt-4">
<Input
placeholder="Enter name"
key={'name'}
name={'name'}
type={'text'}
label="Name"
registerMeta={{
required: true,
}}
useformRef={form}
onKeyDown={_onKeyDown}
error={
errors?.name ? errors?.name?.message || 'Please enter name' : ''
}
/>
<Input
placeholder="Enter Username"
key={'username'}
name={'username'}
label="Username"
value={user.username}
disabled
/>
<Input
placeholder="Enter email"
key={'email'}
name={'email'}
label="Email"
value={user.email}
disabled
/>
<Button
type="submit"
text={isRequesting ? 'Updating...' : 'Update Name'}
onClick={handleSubmit(_onSubmit)}
fullWidth
primary
sm
/>
</form>
</>
);
};
export default UpdateProfile;

View File

@@ -92,7 +92,6 @@ const EditRequest: FC<IModal> = ({
</div> </div>
} }
classNames={{ classNames={{
content: 'h-[600px]',
body: 'p-0', body: 'p-0',
}} }}
> >

View File

@@ -2,7 +2,6 @@ import { FC, useEffect, useState } from 'react';
import { import {
Container, Container,
Drawer, Drawer,
Modal,
IModal, IModal,
SecondaryTab, SecondaryTab,
ProgressBar, ProgressBar,
@@ -12,23 +11,29 @@ import { Rest } from '@firecamp/cloud-apis';
import { useWorkspaceStore, IWorkspaceStore } from '../../../store/workspace'; import { useWorkspaceStore, IWorkspaceStore } from '../../../store/workspace';
import EditInfoTab from './tabs/EditInfoTab'; import EditInfoTab from './tabs/EditInfoTab';
import MembersTab from './tabs/MembersTab'; import MembersTab from './tabs/MembersTab';
import platformContext from '../../../services/platform-context';
import './workspace.scss'; import './workspace.scss';
import { Regex } from '../../../constants';
import PendingInviteMembersTab from './tabs/PendingInviteMembersTab';
enum ETabTypes { enum ETabTypes {
Edit = 'edit', Edit = 'edit',
Members = 'members', Members = 'members',
PendingInvitation = 'pending_invitation',
} }
const WorkspaceManagement: FC<IModal> = ({ const WorkspaceManagement: FC<IModal> = ({
opened = false, opened = false,
onClose = () => {}, onClose = () => {},
}) => { }) => {
let { workspace } = useWorkspaceStore((s: IWorkspaceStore) => ({ let { workspace, setWorkspace } = useWorkspaceStore((s: IWorkspaceStore) => ({
workspace: s.workspace, workspace: s.workspace,
setWorkspace: s.setWorkspace,
})); }));
const [wrs, setWrs] = useState(workspace); const [wrs, setWrs] = useState(workspace);
const [isRequesting, setIsRequesting] = useState(false); const [isRequesting, setIsRequesting] = useState(false);
const [wrsMembers, setWrsMembers] = useState([]); const [wrsMembers, setWrsMembers] = useState([]);
const [wrsInviteMembers, setWrsInviteMembers] = useState([]);
const [isFetchingMembers, setIsFetchingMembers] = useState(false); const [isFetchingMembers, setIsFetchingMembers] = useState(false);
const [error, setError] = useState({ name: '' }); const [error, setError] = useState({ name: '' });
const [activeTab, setActiveTab] = useState<ETabTypes>(ETabTypes.Edit); const [activeTab, setActiveTab] = useState<ETabTypes>(ETabTypes.Edit);
@@ -36,16 +41,22 @@ const WorkspaceManagement: FC<IModal> = ({
const tabs = [ const tabs = [
{ name: 'Edit', id: ETabTypes.Edit }, { name: 'Edit', id: ETabTypes.Edit },
{ name: 'Members', id: ETabTypes.Members }, { name: 'Members', id: ETabTypes.Members },
{ name: 'Pending Invitation', id: ETabTypes.PendingInvitation },
]; ];
/** fetch wrs members to be shown on second tab activated, only fetch once */ /** fetch wrs members to be shown on second tab activated, only fetch once */
useEffect(() => { useEffect(() => {
if (activeTab === ETabTypes.Members && wrsMembers.length === 0) { if (
[ETabTypes.Members, ETabTypes.PendingInvitation].includes(activeTab) &&
activeTab === ETabTypes.Members
? wrsMembers.length === 0
: wrsInviteMembers.length === 0
) {
setIsFetchingMembers(true); setIsFetchingMembers(true);
Rest.workspace Rest.workspace
.getMembers(workspace.__ref.id) .getMembers(workspace.__ref.id)
.then((res) => res.data) .then((res) => res.data)
.then(({ members = [], invited }) => { .then(({ members = [], invited = [] }) => {
const memberList = members.map((m, i) => { const memberList = members.map((m, i) => {
return { return {
id: m.__ref?.id ?? i, id: m.__ref?.id ?? i,
@@ -54,51 +65,117 @@ const WorkspaceManagement: FC<IModal> = ({
role: m.role, role: m.role,
}; };
}); });
const invitedMemberList = invited.map((m, i) => {
return {
id: m.__ref?.id ?? i,
name: m.name || m.username,
email: m.email,
role: m.role,
};
});
setWrsMembers(memberList); setWrsMembers(memberList);
setWrsInviteMembers(invitedMemberList);
}) })
.finally(() => setIsFetchingMembers(false)); .finally(() => setIsFetchingMembers(false));
} }
}, [activeTab]); }, [activeTab]);
const onChange = (e) => { const onChange = (e, reset) => {
const { name, value } = e.target; if (reset) {
if (error.name) setError({ name: '' }); if (error.name) setError({ name: '' });
setWrs((w) => ({ ...w, [name]: value })); setWrs(workspace);
} else {
const { name, value } = e.target;
if (error.name) setError({ name: '' });
setWrs((w) => ({ ...w, [name]: value }));
}
}; };
const onUpdate = () => { const onUpdate = () => {
if (isRequesting) return; if (isRequesting) return;
const name = wrs.name.trim(); const name = wrs.name.trim();
const description = wrs.description?.trim();
if (!name || name.length < 6) { if (!name || name.length < 6) {
setError({ name: 'The workspace name must have minimum 6 characters' }); setError({ name: 'The workspace name must have minimum 6 characters' });
return; return;
} }
const _wrs = { name, description: wrs?.description?.trim() };
// TODO: workspace update API call const isValid = Regex.WorkspaceName.test(name);
if (!isValid) {
setError({
name: 'The workspace name must not contain any spaces or special characters.',
});
return;
}
if (
workspace.name === wrs.name &&
workspace.description === wrs.description
)
return;
const _wrs: { name?: string; description?: string } = {};
if (workspace.name !== name) {
_wrs.name = name;
}
if (workspace.description !== description) {
_wrs.description = description;
}
setIsRequesting(true);
Rest.workspace
.update(workspace.__ref.id, _wrs)
.then(({ error, message }) => {
if (!error) {
platformContext.app.notify.success(
"The workspace's detail has been changed successfully."
);
setWorkspace({ ...workspace, ..._wrs });
} else {
platformContext.app.notify.alert(message);
}
})
.catch((e) => {
platformContext.app.notify.alert(e.response?.data.message || e.message);
})
.finally(() => {
setIsRequesting(false);
});
}; };
const renderTab = (tabId: string) => { const renderTab = (tabId: string) => {
// console.log(wrs, "wrs....") // console.log(wrs, "wrs....")
switch (tabId) { switch (tabId) {
case 'edit': case ETabTypes.Edit:
return ( return (
<EditInfoTab <EditInfoTab
workspace={wrs} workspace={wrs}
error={error} error={error}
isRequesting={isRequesting} isRequesting={isRequesting}
onChange={onChange} onChange={onChange}
close={onClose}
onSubmit={onUpdate} onSubmit={onUpdate}
enableReset={
workspace.name !== wrs.name ||
workspace.description !== wrs.description
}
// disabled={true} // TODO: only allowed for owner & admin
/> />
); );
case 'members': case ETabTypes.Members:
return ( return (
<MembersTab <MembersTab
members={wrsMembers} members={wrsMembers}
isFetchingMembers={isFetchingMembers} isFetchingMembers={isFetchingMembers}
/> />
); );
case ETabTypes.PendingInvitation:
return (
<PendingInviteMembersTab
members={wrsInviteMembers}
isFetchingMembers={isFetchingMembers}
/>
);
default: default:
return <></>; return <></>;
} }
@@ -115,7 +192,7 @@ const WorkspaceManagement: FC<IModal> = ({
} }
size={550} size={550}
classNames={{ classNames={{
body: '!p-4 h-[450px]' body: '!p-4 h-[90vh]',
}} }}
> >
<> <>

View File

@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { Drawer, IModal, SecondaryTab } from '@firecamp/ui'; import { Container, Drawer, IModal, Notes, SecondaryTab } from '@firecamp/ui';
import { _array, _misc } from '@firecamp/utils'; import { _array, _misc } from '@firecamp/utils';
import { Rest } from '@firecamp/cloud-apis'; import { Rest } from '@firecamp/cloud-apis';
import InviteNonOrgMembers from './tabs/InviteNonOrgMembers'; import InviteNonOrgMembers from './tabs/InviteNonOrgMembers';
@@ -13,6 +13,11 @@ enum EInviteMemberTabs {
} }
const InviteMembers: FC<IModal> = ({ opened = false, onClose = () => {} }) => { const InviteMembers: FC<IModal> = ({ opened = false, onClose = () => {} }) => {
const { scope, organization } = usePlatformStore(s => ({
scope: s.scope,
organization: s.organization
}));
const [isFetchingMembers, setIsFetchingMembers] = useState(false); const [isFetchingMembers, setIsFetchingMembers] = useState(false);
const [orgMembers, setOrgMembers] = useState([]); const [orgMembers, setOrgMembers] = useState([]);
@@ -24,7 +29,11 @@ const InviteMembers: FC<IModal> = ({ opened = false, onClose = () => {} }) => {
}>; }>;
}>({ }>({
role: EUserRolesWorkspace.Collaborator, role: EUserRolesWorkspace.Collaborator,
usersList: [{ name: '', email: '' }], usersList: [
{ name: '', email: '' },
{ name: '', email: '' },
{ name: '', email: '' },
],
}); });
const [orgTabState, setOrgTabState] = useState<{ const [orgTabState, setOrgTabState] = useState<{
id: string; id: string;
@@ -57,7 +66,7 @@ const InviteMembers: FC<IModal> = ({ opened = false, onClose = () => {} }) => {
/** fetch org members to be invited on second tab activated, only fetch once */ /** fetch org members to be invited on second tab activated, only fetch once */
useEffect(() => { useEffect(() => {
if (activeTab === EInviteMemberTabs.OrgMembers && orgMembers.length === 0) { if (activeTab === EInviteMemberTabs.OrgMembers && orgMembers.length === 0) {
const { scope, organization } = usePlatformStore.getState();
if (scope == EPlatformScope.Person) return; if (scope == EPlatformScope.Person) return;
setIsFetchingMembers(true); setIsFetchingMembers(true);
Rest.organization Rest.organization
@@ -90,38 +99,62 @@ const InviteMembers: FC<IModal> = ({ opened = false, onClose = () => {} }) => {
opened={opened} opened={opened}
onClose={onClose} onClose={onClose}
size={576} size={576}
classNames={{
content: 'h-[700px]',
body: 'h-[480px]'
}}
title={ title={
<div className="text-lg leading-5 px-1 flex items-center font-medium"> <div className="text-lg leading-5 px-1 flex items-center font-medium">
Invite Members To Join The Workspace Invite Members To Join The Workspace
</div> </div>
} }
classNames={{
body: 'pb-4 h-[90vh]',
}}
> >
<div className="!pt-4 h-fit flex flex-col"> <Container>
<SecondaryTab {(scope == EPlatformScope.Person) ? (
className="flex items-center pb-6 -ml-2" <Container.Body className="mt-8">
list={tabs} <InviteNotAllowed />
activeTab={activeTab} </Container.Body>
onSelect={(tabId: EInviteMemberTabs) => setActiveTab(tabId)} ) : (
/> <>
{activeTab == EInviteMemberTabs.NewMembers ? ( <Container.Header className="!pt-4">
<InviteNonOrgMembers <SecondaryTab
state={nonOrgTabState} className="flex items-center pb-6 -ml-2"
onChange={changeNonOrgTabState} list={tabs}
/> activeTab={activeTab}
) : ( onSelect={(tabId: EInviteMemberTabs) => setActiveTab(tabId)}
<InviteOrgMembers />
state={orgTabState} </Container.Header>
members={orgMembers} <Container.Body>
isFetchingMembers={isFetchingMembers} {activeTab == EInviteMemberTabs.NewMembers ? (
onChange={changeOrgTabState} <InviteNonOrgMembers
/> state={nonOrgTabState}
)} onChange={changeNonOrgTabState}
</div> />
) : (
<InviteOrgMembers
state={orgTabState}
members={orgMembers}
isFetchingMembers={isFetchingMembers}
onChange={changeOrgTabState}
/>
)}
</Container.Body>
</>
)}
</Container>
</Drawer> </Drawer>
); );
}; };
export default InviteMembers; export default InviteMembers;
const InviteNotAllowed = () => {
return (
<Notes
title={
'Inviting users to your personal workspace is currently unavailable.'
}
description={`But don't worry! <br/>
You can still collaborate effectively by taking the first step to create an organization. 🤝 <br/>
Start working together seamlessly and efficiently with your team in no time!`}
/>
);
};

View File

@@ -2,6 +2,7 @@ import cx from 'classnames';
import { VscAdd } from '@react-icons/all-files/vsc/VscAdd'; import { VscAdd } from '@react-icons/all-files/vsc/VscAdd';
import { VscClose } from '@react-icons/all-files/vsc/VscClose'; import { VscClose } from '@react-icons/all-files/vsc/VscClose';
import { FormField, Input } from '@firecamp/ui'; import { FormField, Input } from '@firecamp/ui';
import { _array } from '@firecamp/utils';
const InviteUsersForm = ({ usersList, onChange, error }) => { const InviteUsersForm = ({ usersList, onChange, error }) => {
const _handleNameChange = (e, position) => { const _handleNameChange = (e, position) => {
@@ -73,14 +74,8 @@ const InviteUsersForm = ({ usersList, onChange, error }) => {
<VscClose size={20} className="text-error" /> <VscClose size={20} className="text-error" />
)} )}
</span> </span>
{error[index]?.message.length > 0 ? ( {!_array.isEmpty(error) ? (
<div <Error error={error} index={index} />
className={cx(
'text-sm font-light text-error absolute left-0 bottom-0'
)}
>
{error[index].message}
</div>
) : ( ) : (
<></> <></>
)} )}
@@ -91,3 +86,17 @@ const InviteUsersForm = ({ usersList, onChange, error }) => {
}; };
export default InviteUsersForm; export default InviteUsersForm;
const Error = ({ error, index }) => {
let errorObject = error.find((m) => m.index === index);
if (errorObject?.message.length > 0)
return (
<div
className={cx('text-sm font-light text-error absolute left-0 bottom-0')}
>
{errorObject.message}
</div>
);
return <></>;
};

View File

@@ -47,6 +47,7 @@ const InviteNonOrgMembers = ({ state, onChange }) => {
const { usersList, role } = state; const { usersList, role } = state;
const inviteMembers = useCallback(() => { const inviteMembers = useCallback(() => {
setInvitingFlag(true);
const { success, error } = validateMembersDetail(usersList); const { success, error } = validateMembersDetail(usersList);
if (error?.length) { if (error?.length) {
setError(error); setError(error);
@@ -118,17 +119,17 @@ const InviteNonOrgMembers = ({ state, onChange }) => {
</ScrollBar> </ScrollBar>
</Container.Body> </Container.Body>
<Container.Footer className="flex items-center"> <Container.Footer className="flex items-center">
<a <Button
className="!text-link hover:!text-link hover:underline cursor-pointer text-sm px-2 pl-0" onClick={() => {
target="_blank"
href="#"
onClick={(e) => {
e.preventDefault();
platformContext.app.modals.openWorkspaceManagement(); platformContext.app.modals.openWorkspaceManagement();
}} }}
> text='Open Workspace Management'
Open Workspace Management // classNames={{
</a> // root: '!text-link hover:!text-link hover:underline'
// }}
ghost
xs
/>
<Button <Button
text={isInvitingMembers ? 'Sending invitation...' : 'Send Invitation'} text={isInvitingMembers ? 'Sending invitation...' : 'Send Invitation'}
classNames={{ classNames={{

View File

@@ -86,7 +86,7 @@ const InviteOrgMembers: FC<IProps> = ({
onSelect={(m) => onChange({ ...member, ...m })} onSelect={(m) => onChange({ ...member, ...m })}
classNames={{ classNames={{
trigger: 'block', trigger: 'block',
dropdown: '-mt-2 overflow-y-scroll invisible-scrollbar h-[200px]', dropdown: '-mt-2 overflow-y-scroll invisible-scrollbar max-h-[200px]',
item: '!px-4', item: '!px-4',
}} }}
width={512} width={512}
@@ -127,17 +127,19 @@ const InviteOrgMembers: FC<IProps> = ({
<RolesCallout role={_role.id} /> <RolesCallout role={_role.id} />
</Container.Body> </Container.Body>
<Container.Footer className="flex items-center"> <Container.Footer className="flex items-center">
<a <Button
className="!text-link hover:!text-link hover:underline cursor-pointer text-sm px-2 pl-0" onClick={() => {
target="_blank"
href="#"
onClick={(e) => {
e.preventDefault();
platformContext.app.modals.openWorkspaceManagement(); platformContext.app.modals.openWorkspaceManagement();
}} }}
> // classNames={{
Open Workspace Management // root: '!text-link hover:!text-link hover:underline'
</a> // }}
text='Open Workspace Management'
ghost
xs
/>
<Button <Button
text={'Send Invitation'} text={'Send Invitation'}
disabled={!member.name || !member.role || isInvitingMembers} disabled={!member.name || !member.role || isInvitingMembers}

View File

@@ -1,5 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { Button, Input, TabHeader, TextArea } from '@firecamp/ui'; import { Button, Container, Input, TabHeader, TextArea } from '@firecamp/ui';
import platformContext from '../../../../services/platform-context'; import platformContext from '../../../../services/platform-context';
const EditInfoTab: FC<any> = ({ const EditInfoTab: FC<any> = ({
@@ -8,76 +8,74 @@ const EditInfoTab: FC<any> = ({
isRequesting, isRequesting,
onSubmit, onSubmit,
onChange, onChange,
close, disabled = false,
enableReset = false,
}) => { }) => {
return ( return (
<div className="p-3 flex-1 flex flex-col"> <Container className="pt-3 px-3 flex-1 flex flex-col h-full">
<label className="text-sm font-semibold leading-3 block text-app-foreground-inactive uppercase w-full relative mb-2"> <Container.Body>
UPDATE WORKSPACE INFO <label className="text-sm font-semibold leading-3 block text-app-foreground-inactive uppercase w-full relative mb-2">
</label> UPDATE WORKSPACE INFO
<div> </label>
<Input <div>
autoFocus={true} <Input
label="Name" autoFocus={true}
placeholder="Workspace name" label="Name"
name={'name'} placeholder="Workspace name"
defaultValue={workspace.name || ''} name={'name'}
defaultValue={workspace.name || ''}
onChange={onChange}
onKeyDown={() => {}}
onBlur={() => {}}
error={error.name}
// error={error.name}
// iconPosition="right"
// icon={<VscEdit />}
wrapperClassName="!mb-3"
/>
</div>
<TextArea
type="text"
minHeight="240px"
label="Description (optional)"
labelClassName="fc-input-label"
placeholder="Description"
note="Markdown supported in description"
name={'description'}
defaultValue={workspace.description || ''}
onChange={onChange} onChange={onChange}
onKeyDown={() => {}} // disabled={true}
onBlur={() => {}}
error={error.name}
// error={error.name}
// iconPosition="right" // iconPosition="right"
// icon={<VscEdit />} // icon={<VscEdit />}
wrapperClassName='!mb-3'
/> />
</div> </Container.Body>
<Container.Footer>
<TextArea <TabHeader className="!px-0">
type="text" <TabHeader.Left>
minHeight="180px" <Button
label="Description (optional)" onClick={() => {
labelClassName="fc-input-label" platformContext.app.modals.openInviteMembers();
placeholder="Description" }}
note="Markdown supported in description" text="Invite New Members"
name={'description'} ghost
defaultValue={workspace.description || ''} xs
onChange={onChange} />
// disabled={true} </TabHeader.Left>
// iconPosition="right" <TabHeader.Right>
// icon={<VscEdit />} {/* TODO: update details */}
/> {/* {enableReset ? <Button text="Undo" onClick={(e) => onChange(e, true)} ghost xs /> : <></>} */}
<TabHeader className="!px-0"> <Button
<TabHeader.Left> text={isRequesting ? 'Updating...' : 'Update'}
<a onClick={onSubmit}
className="!text-link hover:!text-link hover:underline cursor-pointer text-sm px-2 pl-0" disabled={isRequesting || disabled || !enableReset}
target="_blank" primary
href="#" xs
onClick={(e) => { />
e.preventDefault(); </TabHeader.Right>
platformContext.app.modals.openInviteMembers(); </TabHeader>
}} </Container.Footer>
> </Container>
Invite New Members
</a>
</TabHeader.Left>
<TabHeader.Right>
<Button
text="Cancel"
onClick={(e) => close(e)}
ghost
xs
/>
<Button
text={isRequesting ? 'Updating...' : 'Update'}
onClick={onSubmit}
disabled={isRequesting}
primary
xs
/>
</TabHeader.Right>
</TabHeader>
</div>
); );
}; };
export default EditInfoTab; export default EditInfoTab;

View File

@@ -84,7 +84,7 @@ const MembersTab = ({ members = [], isFetchingMembers = false }) => {
e.response?.data.message || e.message e.response?.data.message || e.message
); );
}); });
} },
}); });
}; };
@@ -151,8 +151,8 @@ const MembersTab = ({ members = [], isFetchingMembers = false }) => {
}; };
return ( return (
<Container className="gap-2"> <Container className="gap-2 pt-2">
<Container.Body className="pt-2 visible-scrollbar"> <Container.Body className="visible-scrollbar">
<ProgressBar active={isFetchingMembers} className={'top-auto'} /> <ProgressBar active={isFetchingMembers} className={'top-auto'} />
<PrimitiveTable <PrimitiveTable
classes={{ classes={{
@@ -167,18 +167,15 @@ const MembersTab = ({ members = [], isFetchingMembers = false }) => {
onMount={(api) => (tableApi.current = api)} onMount={(api) => (tableApi.current = api)}
/> />
</Container.Body> </Container.Body>
<Container.Footer className="flex items-center"> <Container.Footer className="px-3 h-[34px] flex items-center">
<a <Button
className="!text-link hover:!text-link hover:underline cursor-pointer text-sm px-2 pl-0" onClick={() => {
target="_blank"
href="#"
onClick={(e) => {
e.preventDefault();
platformContext.app.modals.openInviteMembers(); platformContext.app.modals.openInviteMembers();
}} }}
> text="Invite New Members"
Invite New Members ghost
</a> xs
/>
</Container.Footer> </Container.Footer>
</Container> </Container>
); );

View File

@@ -0,0 +1,219 @@
import { FC, useEffect, useRef, useState } from 'react';
import { VscTrash } from '@react-icons/all-files/vsc/VscTrash';
import { VscTriangleDown } from '@react-icons/all-files/vsc/VscTriangleDown';
import cx from 'classnames';
import {
Button,
Container,
DropdownMenu,
PrimitiveTable,
ProgressBar,
TTableApi,
} from '@firecamp/ui';
import { _array } from '@firecamp/utils';
import { Rest } from '@firecamp/cloud-apis';
import platformContext from '../../../../services/platform-context';
import { useWorkspaceStore } from '../../../../store/workspace';
import { EUserRolesWorkspace } from '../../../../types';
const columns = [
{ id: 'index', name: 'No.', key: 'index', width: '35px', fixedWidth: true },
{ id: 'name', name: 'Name', key: 'name', width: '100px' },
{
id: 'email',
name: 'Email',
key: 'email',
resizeWithContainer: true,
width: '180px',
},
{ id: 'role', name: 'Role', key: 'role', width: '115px', fixedWidth: true },
// { id: 'action', name: '', key: '', width: '35px', fixedWidth: true },
];
const RoleOptions = [
{
id: EUserRolesWorkspace.Owner,
name: 'Owner',
},
{
id: EUserRolesWorkspace.Admin,
name: 'Admin',
},
{
id: EUserRolesWorkspace.Collaborator,
name: 'Collaborator',
},
];
const PendingInviteMembersTab = ({ members = [], isFetchingMembers = false }) => {
const workspace = useWorkspaceStore.getState().workspace;
const tableApi = useRef<TTableApi>(null);
useEffect(() => {
if (!_array.isEmpty(members)) {
const memberList = members.map((m, i) => {
return {
id: m.id,
name: m.name || m.username,
email: m.email,
role: m.role,
};
});
tableApi.current.initialize(memberList);
}
}, [members]);
const onRemoveMember = (row) => {
platformContext.window.confirm({
message: `You're sure to remove ${row.name} from the workspace?`,
labels: {
cancel: 'Cancel',
confirm: 'Yes, remove the member.',
},
onConfirm: () => {
Rest.workspace
.removeMember(workspace.__ref.id, row.id)
.then(() => {
tableApi.current.removeRow(row.id);
platformContext.app.notify.success(
'The member has been removed successfully.'
);
})
.catch((e) => {
platformContext.app.notify.alert(
e.response?.data.message || e.message
);
});
},
});
};
const onChangeRole = (row) => {
platformContext.window.confirm({
message: `Please confirm, You're assigning ${row.role.name} role to ${row.name}, right?`,
labels: {
cancel: 'Cancel',
confirm: 'Yes, change the role.',
},
onConfirm: () => {
Rest.workspace
.changeMemberRole(workspace.__ref.id, row.id, row.role.id)
.then(() => {
tableApi.current.setRow({ ...row, role: row.role.id });
platformContext.app.notify.success(
"The member's role has been changed successfully."
);
})
.catch((e) => {
platformContext.app.notify.alert(
e.response?.data.message || e.message
);
});
},
});
};
const renderCell = (column, cellValue, rowIndex, row, tableApi, onChange) => {
switch (column.id) {
case 'index':
return <div className="px-2"> {rowIndex + 1} </div>;
break;
case 'name':
return <div className="p-1">{cellValue}</div>;
break;
case 'email':
return <div className="p-1">{cellValue}</div>;
break;
case 'role':
return (
<div className="p-1 text-center">
<RoleDD
role={row.role}
onSelect={(role) => onChangeRole({ ...row, role })}
/>
</div>
);
break;
// case 'action':
// return (
// <div className="px-2">
// <VscTrash
// size={14}
// className="text-error cursor-pointer"
// onClick={() => onRemoveMember(row)}
// />
// </div>
// );
// break;
default:
return column.key;
}
};
return (
<Container className="gap-2 pt-2">
<Container.Body className="visible-scrollbar">
<ProgressBar active={isFetchingMembers} className={'top-auto'} />
<PrimitiveTable
classes={{
container: 'h-full',
}}
columns={columns}
rows={[]}
showDefaultEmptyRows={false}
renderColumn={(c) => c.name}
renderCell={renderCell}
onChange={console.log}
onMount={(api) => (tableApi.current = api)}
/>
</Container.Body>
<Container.Footer className="px-3 h-[34px] flex items-center">
<Button
onClick={() => {
platformContext.app.modals.openInviteMembers();
}}
text="Invite New Members"
ghost
xs
/>
</Container.Footer>
</Container>
);
};
export default PendingInviteMembersTab;
const RoleDD: FC<{
role: number;
onSelect: (role: { name: string; id: number }) => void;
}> = ({ role, onSelect }) => {
const [isOpen, toggleOpen] = useState(false);
const _role = RoleOptions.find((r) => r.id == role);
if (!_role) return <></>;
return (
<DropdownMenu
onOpenChange={(v) => toggleOpen(v)}
handler={() => (
<Button
text={_role.name}
rightIcon={
<VscTriangleDown
size={12}
className={cx({ 'transform rotate-180': isOpen })}
/>
}
disabled
ghost
xs
/>
)}
options={RoleOptions}
onSelect={onSelect}
disabled={true}
width={115}
sm
/>
);
};

View File

@@ -65,22 +65,6 @@ const UserDDMenus: FC<{ title: string; isGuest: boolean }> = ({
isGuest, isGuest,
}) => { }) => {
const guestOptions = [ const guestOptions = [
{
name: title,
isLabel: true,
postfix: () => (
<>
<br />
<div
className={
'text-sm font-light leading-3 text-app-foreground-inactive'
}
>
User
</div>
</>
),
},
{ {
name: 'Sign in', name: 'Sign in',
postfix: () => ( postfix: () => (
@@ -102,23 +86,6 @@ const UserDDMenus: FC<{ title: string; isGuest: boolean }> = ({
]; ];
const userOptions = [ const userOptions = [
{
name: title,
isLabel: true,
postfix: () => (
<>
<br />
<div
className={
'text-sm font-light leading-3 text-app-foreground-inactive'
}
>
User
</div>
</>
),
},
// TODO: add the onClick action
{ {
name: 'User Profile', name: 'User Profile',
postfix: () => ( postfix: () => (
@@ -126,6 +93,9 @@ const UserDDMenus: FC<{ title: string; isGuest: boolean }> = ({
<VscAccount size={14} /> <VscAccount size={14} />
</div> </div>
), ),
onClick: () => {
platformContext.app.modals.openUserProfile();
},
}, },
{ {
name: 'Create new organization', name: 'Create new organization',
@@ -175,15 +145,24 @@ const UserDDMenus: FC<{ title: string; isGuest: boolean }> = ({
return ( return (
<DropdownMenu <DropdownMenu
handler={() => ( handler={() => <span className="pl-1 cursor-pointer">{title}</span>}
<span className="pl-1 cursor-pointer">{title}</span>
)}
options={isGuest ? guestOptions : userOptions} options={isGuest ? guestOptions : userOptions}
header={
<div className="!capitalize !pt-[0.2rem] !pb-2 !px-3 !block !bg-focus2 ">
{title}
<br />
<div
className={
'text-sm font-light leading-3 text-app-foreground-inactive'
}
>
User
</div>
</div>
}
onSelect={(v) => v.onClick()} onSelect={(v) => v.onClick()}
classNames={{ classNames={{
dropdown: '!pt-0 mt-2 min-w-fit', dropdown: '!pt-0 mt-2 min-w-fit',
label:
'!capitalize flex items-center text-app-foreground !pt-[0.2rem] !pb-2 !px-3 !block !text-base leading-6 !bg-focus2 ',
item: '!py-1 !px-3', item: '!py-1 !px-3',
}} }}
width={150} width={150}
@@ -197,22 +176,6 @@ const WorkspaceDDMenus: FC<{ title: string; disabled?: boolean }> = ({
disabled = false, disabled = false,
}) => { }) => {
const options = [ const options = [
{
name: title,
isLabel: true,
postfix: () => (
<>
<br />
<div
className={
'text-sm font-light leading-3 text-app-foreground-inactive'
}
>
Workspace
</div>
</>
),
},
{ {
name: 'Workspace Management', name: 'Workspace Management',
disabled, disabled,
@@ -265,15 +228,24 @@ const WorkspaceDDMenus: FC<{ title: string; disabled?: boolean }> = ({
return ( return (
<DropdownMenu <DropdownMenu
handler={() => ( handler={() => <span className="pl-1 cursor-pointer">{title}</span>}
<span className="pl-1 cursor-pointer">{title}</span>
)}
options={options} options={options}
header={
<div className="!capitalize !pt-[0.2rem] !pb-2 !px-3 !block !bg-focus2 ">
{title}
<br />
<div
className={
'text-sm font-light leading-3 text-app-foreground-inactive'
}
>
Workspace
</div>
</div>
}
onSelect={(v) => v.onClick()} onSelect={(v) => v.onClick()}
classNames={{ classNames={{
dropdown: '!pt-0 mt-2 min-w-fit', dropdown: '!pt-0 mt-2 min-w-fit',
label:
'!capitalize flex items-center text-app-foreground !pt-[0.2rem] !pb-2 !px-3 !block !text-base leading-6 !bg-focus2 ',
item: '!py-1 !px-3', item: '!py-1 !px-3',
}} }}
width={150} width={150}
@@ -287,22 +259,6 @@ const OrgDDMenus: FC<{ title: string; disabled?: boolean }> = ({
disabled = false, disabled = false,
}) => { }) => {
const options = [ const options = [
{
name: title,
isLabel: true,
postfix: () => (
<>
<br />
<div
className={
'text-sm font-light leading-3 text-app-foreground-inactive'
}
>
Organization
</div>
</>
),
},
{ {
name: 'Org Management', name: 'Org Management',
disabled, disabled,
@@ -343,15 +299,24 @@ const OrgDDMenus: FC<{ title: string; disabled?: boolean }> = ({
return ( return (
<DropdownMenu <DropdownMenu
handler={() => ( handler={() => <span className="pl-1 cursor-pointer">{title}</span>}
<span className="pl-1 cursor-pointer">{title}</span>
)}
options={options} options={options}
header={
<div className="!capitalize !pt-[0.2rem] !pb-2 !px-3 !block !bg-focus2 ">
{title}
<br />
<div
className={
'text-sm font-light leading-3 text-app-foreground-inactive'
}
>
Organization
</div>
</div>
}
onSelect={(v) => v.onClick()} onSelect={(v) => v.onClick()}
classNames={{ classNames={{
dropdown: '!pt-0 mt-2 min-w-fit', dropdown: '!pt-0 mt-2 min-w-fit',
label:
'!capitalize flex items-center text-app-foreground !pt-[0.2rem] !pb-2 !px-3 !block !text-base leading-6 !bg-focus2 ',
item: '!py-1 !px-3', item: '!py-1 !px-3',
}} }}
width={140} width={140}

View File

@@ -5,18 +5,19 @@ export const Regex = {
), ),
/** /**
* allow alphanumeric and underscore * allow alphanumeric and single hyphen
* don't allow spaces and special characters * don't allow spaces and special characters
* characters range between 6 to 20 * characters range between 6 to 20
* @ref: https://github.com/shinnn/github-username-regex/blob/master/index.js
*/ */
Username: /^[a-zA-Z0-9\_]{6,20}$/, Username: /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){5,19}$/i,
WorkspaceName: /^[a-zA-Z0-9\_]{6,20}$/, WorkspaceName: /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){5,19}$/i,
OrgName: /^[a-zA-Z0-9\_]{4,20}$/, OrgName: /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){3,19}$/i,
/** /**
* don't allow any special character * don't allow any special character
* @ref: https://stackoverflow.com/a/23127284 * @ref: https://stackoverflow.com/a/23127284
* allows: colName, colName_, _colNmae, col_name * allows: colName, colName_, _colName, col_name
* not allow" colName. , colName?, colName/@ or any special character in the name * not allow" colName. , colName?, colName/@ or any special character in the name
* TODO: add character range * TODO: add character range
*/ */

View File

@@ -116,7 +116,8 @@ const initApp = async () => {
} catch (e) { } catch (e) {
console.log(e, 'error while connecting the socket'); console.log(e, 'error while connecting the socket');
} }
}); })
.catch(console.log);
}; };
const initUser = (user: any) => { const initUser = (user: any) => {
const { setUser } = useUserStore.getState(); const { setUser } = useUserStore.getState();

View File

@@ -78,6 +78,12 @@ const modalService = {
open(EPlatformModalTypes.SwitchOrg); open(EPlatformModalTypes.SwitchOrg);
}, },
openAllInvitation: () => {
const { isGuest } = useUserStore.getState();
if (isGuest) return modalService.openSignIn();
open(EPlatformModalTypes.AllInvitation);
},
// Cookie, Ssl, Proxy // Cookie, Ssl, Proxy
openCookieManager: () => { openCookieManager: () => {
const { isGuest } = useUserStore.getState(); const { isGuest } = useUserStore.getState();

View File

@@ -36,7 +36,7 @@ const promptInput: TOpenPromptInput = (props) => {
size: 400, size: 400,
classNames: { classNames: {
header: 'border-0 pb-0', header: 'border-0 pb-0',
body: 'px-6', body: 'px-6 relative',
content: 'min-h-0', content: 'min-h-0',
} }
}); });
@@ -68,6 +68,7 @@ const promptSaveItem: TOpenPromptSaveItem = (props) => {
size: 400, size: 400,
classNames: { classNames: {
header: 'border-0 px-6 pt-6 pb-0', header: 'border-0 px-6 pt-6 pb-0',
body: 'relative'
} }
}); });
}); });
@@ -101,7 +102,7 @@ const confirm: TConfirmApi = (props) => {
size: 400, size: 400,
classNames: { classNames: {
header: 'border-0', header: 'border-0',
body: 'px-6', body: 'px-6 relative',
content: 'min-h-0', content: 'min-h-0',
}, },
}); });

View File

@@ -48,6 +48,7 @@ export enum EPlatformModalTypes {
// user // user
UserProfile = 'userProfile', UserProfile = 'userProfile',
AllInvitation = 'allInvitation',
// cookie // cookie
CookieManager = 'cookieManager', CookieManager = 'cookieManager',

View File

@@ -1,6 +1,5 @@
import { FC } from 'react'; import { FC } from 'react';
import { Drawer as MantineDrawer, DrawerProps, ScrollArea, createStyles } from '@mantine/core'; import { Drawer as MantineDrawer, DrawerProps, ScrollArea, createStyles } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
export interface IDrawer extends DrawerProps { } export interface IDrawer extends DrawerProps { }

View File

@@ -1,6 +1,15 @@
import type { MenuProps } from '@mantine/core'; import type { MenuProps } from '@mantine/core';
import { ReactNode } from 'react';
export interface IDropdownMenu { export interface IDropdownMenu {
/**
* Add the custom header for options
*/
header?: ReactNode;
/**
* Add the custom footer for options
*/
footer?: ReactNode;
/** /**
* Add id to the dropdown wrapper * Add id to the dropdown wrapper
*/ */

View File

@@ -98,9 +98,22 @@ Example.args = {
prefix: () => <VscMultipleWindows size={18} />, prefix: () => <VscMultipleWindows size={18} />,
}, },
], ],
header:
<div className="!capitalize !pt-[0.2rem] !pb-2 !px-3 !block !bg-focus2 ">
Header title
<br />
<div
className={
'text-sm font-light leading-3 text-app-foreground-inactive'
}
>
User
</div>
</div>,
footer: <div className='text-center'>3.0.0</div>,
handler: () => <Button text={'Create'} primary ghost xs />, handler: () => <Button text={'Create'} primary ghost xs />,
classNames: { classNames: {
dropdown: '-ml-[2px]', dropdown: '-ml-[2px] pt-0',
}, },
onSelect: (value: any) => console.log(`selected item :`, value), onSelect: (value: any) => console.log(`selected item :`, value),
}; };

View File

@@ -12,12 +12,16 @@ enum EDefaultStyles {
divider = 'bg-app-border border-app-border', divider = 'bg-app-border border-app-border',
disabled = 'opacity-50 cursor-default', disabled = 'opacity-50 cursor-default',
disabledItem = '!text-activityBar-foreground-inactive !cursor-default', disabledItem = '!text-activityBar-foreground-inactive !cursor-default',
header = 'text-app-foreground p-0 text-base leading-6',
footer = 'text-activityBar-foreground-inactive px-5 py-2 text-sm leading-3',
} }
const DropdownMenu: FC<IDropdownMenu> = ({ const DropdownMenu: FC<IDropdownMenu> = ({
id = '', id = '',
selected = '', selected = '',
width = 200, width = 200,
header,
footer,
options = [], options = [],
handler, handler,
classNames = {}, classNames = {},
@@ -25,7 +29,7 @@ const DropdownMenu: FC<IDropdownMenu> = ({
onOpenChange = () => {}, onOpenChange = () => {},
disabled = false, disabled = false,
menuProps = {}, menuProps = {},
sm = false sm = false,
}) => { }) => {
return ( return (
<Menu <Menu
@@ -33,7 +37,9 @@ const DropdownMenu: FC<IDropdownMenu> = ({
shadow="md" shadow="md"
width={width} width={width}
classNames={classNames} classNames={classNames}
onChange={(v) => disabled || options.length === 0 ? {} : onOpenChange(v)} onChange={(v) =>
disabled || options.length === 0 ? {} : onOpenChange(v)
}
disabled={disabled} disabled={disabled}
{...menuProps} {...menuProps}
> >
@@ -51,13 +57,21 @@ const DropdownMenu: FC<IDropdownMenu> = ({
</Menu.Target> </Menu.Target>
<Menu.Dropdown <Menu.Dropdown
className={cx(EDefaultStyles.dropdown, className={cx(
EDefaultStyles.dropdown,
{ 'py-2.5': sm }, { 'py-2.5': sm },
{ 'py-[15px]': !sm }, { 'py-[15px]': !sm },
{ {
'hidden border-0': options.length === 0, 'hidden border-0': options.length === 0,
})} }
)}
> >
{!!header ? (
<Menu.Label className={EDefaultStyles.header}>{header}</Menu.Label>
) : (
<></>
)}
{options.map((item, i) => { {options.map((item, i) => {
return ( return (
<Fragment key={`menu-item-${i}`}> <Fragment key={`menu-item-${i}`}>
@@ -70,10 +84,10 @@ const DropdownMenu: FC<IDropdownMenu> = ({
<Menu.Item <Menu.Item
className={cx( className={cx(
{ {
[EDefaultStyles.item]: !sm [EDefaultStyles.item]: !sm,
}, },
{ {
[EDefaultStyles.itemSmall]: sm [EDefaultStyles.itemSmall]: sm,
}, },
{ {
'font-bold': selected === item.name, 'font-bold': selected === item.name,
@@ -114,6 +128,11 @@ const DropdownMenu: FC<IDropdownMenu> = ({
</Fragment> </Fragment>
); );
})} })}
{!!footer ? (
<Menu.Label className={EDefaultStyles.footer}>{footer}</Menu.Label>
) : (
<></>
)}
</Menu.Dropdown> </Menu.Dropdown>
</Menu> </Menu>
); );

View File

@@ -547,6 +547,10 @@
.\!mb-3{ .\!mb-3{
margin-bottom: 0.75rem !important; margin-bottom: 0.75rem !important;
}
.\!mb-4{
margin-bottom: 1rem !important;
} }
.\!ml-auto{ .\!ml-auto{
margin-left: auto !important; margin-left: auto !important;
@@ -751,6 +755,10 @@
.mt-8{ .mt-8{
margin-top: 2rem; margin-top: 2rem;
}
.mt-\[10vh\]{
margin-top: 10vh;
} }
.mt-\[50\%\]{ .mt-\[50\%\]{
margin-top: 50%; margin-top: 50%;
@@ -831,6 +839,10 @@
.\!h-80{ .\!h-80{
height: 20rem !important; height: 20rem !important;
}
.\!h-\[80vh\]{
height: 80vh !important;
} }
.\!h-fit{ .\!h-fit{
height: -moz-fit-content !important; height: -moz-fit-content !important;
@@ -913,37 +925,25 @@
height: 340px; height: 340px;
} }
.h-\[450px\]{ .h-\[34px\]{
height: 450px; height: 34px;
}
.h-\[480px\]{
height: 480px;
} }
.h-\[50vh\]{ .h-\[50vh\]{
height: 50vh; height: 50vh;
}
.h-\[600px\]{
height: 600px;
}
.h-\[700px\]{
height: 700px;
} }
.h-\[70vh\]{ .h-\[70vh\]{
height: 70vh; height: 70vh;
}
.h-\[750px\]{
height: 750px;
} }
.h-\[80vh\]{ .h-\[80vh\]{
height: 80vh; height: 80vh;
}
.h-\[90vh\]{
height: 90vh;
} }
.h-fit{ .h-fit{
height: -moz-fit-content; height: -moz-fit-content;
@@ -973,6 +973,10 @@
.max-h-56{ .max-h-56{
max-height: 14rem; max-height: 14rem;
}
.max-h-\[200px\]{
max-height: 200px;
} }
.min-h-0{ .min-h-0{
min-height: 0px; min-height: 0px;
@@ -2423,6 +2427,12 @@
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.shadow-sm{
--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
} }
.\!shadow-popover-shadow{ .\!shadow-popover-shadow{
--tw-shadow-color: var(--popover-shadow) !important; --tw-shadow-color: var(--popover-shadow) !important;

View File

@@ -106,8 +106,6 @@ const BodyTab: FC<any> = () => {
return <BinaryBody body={body || {}} onChange={changeBodyValue} />; return <BinaryBody body={body || {}} onChange={changeBodyValue} />;
case ERestBodyTypes.GraphQL: case ERestBodyTypes.GraphQL:
return <GraphQLBody body={body || {}} onChange={changeBodyValue} />; return <GraphQLBody body={body || {}} onChange={changeBodyValue} />;
case ERestBodyTypes.None:
return <NoBodyTab selectBodyType={_selectBodyType} />;
default: default:
return <></>; return <></>;
} }

View File

@@ -112,6 +112,7 @@ const SIOVersionDropDown: FC<any> = ({
className={cx({ 'transform rotate-180': isDropDownOpen })} className={cx({ 'transform rotate-180': isDropDownOpen })}
/> />
} }
animate={false}
secondary secondary
xs xs
/> />

165
pnpm-lock.yaml generated
View File

@@ -135,6 +135,9 @@ importers:
node-polyfill-webpack-plugin: node-polyfill-webpack-plugin:
specifier: ^2.0.0 specifier: ^2.0.0
version: 2.0.1(webpack@5.75.0) version: 2.0.1(webpack@5.75.0)
npm-run-all:
specifier: ^4.1.5
version: 4.1.5
postcss-loader: postcss-loader:
specifier: ^7.0.2 specifier: ^7.0.2
version: 7.0.2(postcss@8.4.27)(webpack@5.75.0) version: 7.0.2(postcss@8.4.27)(webpack@5.75.0)
@@ -162,6 +165,9 @@ importers:
style-loader: style-loader:
specifier: ^3.3.1 specifier: ^3.3.1
version: 3.3.1(webpack@5.75.0) version: 3.3.1(webpack@5.75.0)
terser-webpack-plugin:
specifier: ^5.3.9
version: 5.3.9(webpack@5.75.0)
ts-loader: ts-loader:
specifier: ^9.2.8 specifier: ^9.2.8
version: 9.4.2(typescript@5.0.2)(webpack@5.75.0) version: 9.4.2(typescript@5.0.2)(webpack@5.75.0)
@@ -192,6 +198,9 @@ importers:
webpack-httpolyglot-server: webpack-httpolyglot-server:
specifier: ^0.3.0 specifier: ^0.3.0
version: 0.3.0(webpack@5.75.0) version: 0.3.0(webpack@5.75.0)
webpack-merge:
specifier: ^5.9.0
version: 5.9.0
worker-loader: worker-loader:
specifier: ^3.0.8 specifier: ^3.0.8
version: 3.0.8(webpack@5.75.0) version: 3.0.8(webpack@5.75.0)
@@ -478,8 +487,8 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../packages/firecamp-agent-manager version: link:../../packages/firecamp-agent-manager
'@firecamp/cloud-apis': '@firecamp/cloud-apis':
specifier: ^0.2.8 specifier: 0.2.10
version: 0.2.8 version: 0.2.10
'@firecamp/cookie-manager': '@firecamp/cookie-manager':
specifier: ^0.0.0 specifier: ^0.0.0
version: link:../../packages/firecamp-cookie-manager version: link:../../packages/firecamp-cookie-manager
@@ -5011,8 +5020,8 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@firecamp/cloud-apis@0.2.8: /@firecamp/cloud-apis@0.2.10:
resolution: {integrity: sha512-RYgD/d39YyN91chDESwdGDUdK6Ph9oX14y1HXnMfACnEnviOtCQkMkjBLu0RoSPfqHiLJe8PopgASfv/XOAT7g==} resolution: {integrity: sha512-aWTxsmfUygfa0Tv9TaRUhbNPTP9I6CVjJhrY9kLJwdtzRcbg5jW7oqnD1HEl0662pwPyYJ1weC3XOo2/cJJ+JQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
axios: 0.21.4 axios: 0.21.4
@@ -10220,6 +10229,7 @@ packages:
/async-each@1.0.3: /async-each@1.0.3:
resolution: {integrity: sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==} resolution: {integrity: sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -10831,6 +10841,7 @@ packages:
/big-integer@1.6.51: /big-integer@1.6.51:
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -10845,6 +10856,7 @@ packages:
/binary-extensions@1.13.1: /binary-extensions@1.13.1:
resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==} resolution: {integrity: sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -10945,6 +10957,7 @@ packages:
/bplist-parser@0.1.1: /bplist-parser@0.1.1:
resolution: {integrity: sha512-2AEM0FXy8ZxVLBuqX0hqt1gDwcnz2zygEkQ6zaD5Wko/sB9paUNwlpawrFtKeHUAQUOzjVy9AO4oeonqIHKA9Q==} resolution: {integrity: sha512-2AEM0FXy8ZxVLBuqX0hqt1gDwcnz2zygEkQ6zaD5Wko/sB9paUNwlpawrFtKeHUAQUOzjVy9AO4oeonqIHKA9Q==}
requiresBuild: true
dependencies: dependencies:
big-integer: 1.6.51 big-integer: 1.6.51
dev: true dev: true
@@ -11289,6 +11302,7 @@ packages:
/camelcase-keys@2.1.0: /camelcase-keys@2.1.0:
resolution: {integrity: sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==} resolution: {integrity: sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
camelcase: 2.1.1 camelcase: 2.1.1
map-obj: 1.0.1 map-obj: 1.0.1
@@ -11312,6 +11326,7 @@ packages:
/camelcase@2.1.1: /camelcase@2.1.1:
resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==} resolution: {integrity: sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -11476,6 +11491,7 @@ packages:
/chokidar@2.1.8: /chokidar@2.1.8:
resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==} resolution: {integrity: sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==}
deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies deprecated: Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies
requiresBuild: true
dependencies: dependencies:
anymatch: 2.0.0 anymatch: 2.0.0
async-each: 1.0.3 async-each: 1.0.3
@@ -11927,7 +11943,7 @@ packages:
- supports-color - supports-color
/concat-map@0.0.1: /concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
/concat-stream@1.4.11: /concat-stream@1.4.11:
resolution: {integrity: sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==} resolution: {integrity: sha512-X3JMh8+4je3U1cQpG87+f9lXHDrqcb2MVLg9L7o8b1UZ0DzhRrUpdn65ttzu10PpJPPI3MQNkis+oha6TSA9Mw==}
@@ -14564,6 +14580,7 @@ packages:
/find-up@1.1.2: /find-up@1.1.2:
resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==} resolution: {integrity: sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
path-exists: 2.1.0 path-exists: 2.1.0
pinkie-promise: 2.0.1 pinkie-promise: 2.0.1
@@ -15030,6 +15047,7 @@ packages:
/get-stdin@4.0.1: /get-stdin@4.0.1:
resolution: {integrity: sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==} resolution: {integrity: sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -16071,6 +16089,7 @@ packages:
/indent-string@2.1.0: /indent-string@2.1.0:
resolution: {integrity: sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==} resolution: {integrity: sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
repeating: 2.0.1 repeating: 2.0.1
dev: true dev: true
@@ -16220,6 +16239,7 @@ packages:
/is-binary-path@1.0.1: /is-binary-path@1.0.1:
resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==} resolution: {integrity: sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
binary-extensions: 1.13.1 binary-extensions: 1.13.1
dev: true dev: true
@@ -16353,6 +16373,7 @@ packages:
/is-finite@1.1.0: /is-finite@1.1.0:
resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==} resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -16584,6 +16605,7 @@ packages:
/is-utf8@0.2.1: /is-utf8@0.2.1:
resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -18469,6 +18491,7 @@ packages:
/load-json-file@1.1.0: /load-json-file@1.1.0:
resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==} resolution: {integrity: sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
parse-json: 2.2.0 parse-json: 2.2.0
@@ -18980,6 +19003,7 @@ packages:
/meow@3.7.0: /meow@3.7.0:
resolution: {integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==} resolution: {integrity: sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
camelcase-keys: 2.1.0 camelcase-keys: 2.1.0
decamelize: 1.2.0 decamelize: 1.2.0
@@ -20172,6 +20196,7 @@ packages:
/parse-json@2.2.0: /parse-json@2.2.0:
resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==} resolution: {integrity: sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
error-ex: 1.3.2 error-ex: 1.3.2
dev: true dev: true
@@ -20256,11 +20281,13 @@ packages:
/path-dirname@1.0.2: /path-dirname@1.0.2:
resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==} resolution: {integrity: sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==}
requiresBuild: true
dev: true dev: true
/path-exists@2.1.0: /path-exists@2.1.0:
resolution: {integrity: sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==} resolution: {integrity: sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
pinkie-promise: 2.0.1 pinkie-promise: 2.0.1
dev: true dev: true
@@ -20310,6 +20337,7 @@ packages:
/path-type@1.1.0: /path-type@1.1.0:
resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==} resolution: {integrity: sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
pify: 2.3.0 pify: 2.3.0
@@ -20402,6 +20430,7 @@ packages:
/pinkie-promise@2.0.1: /pinkie-promise@2.0.1:
resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==} resolution: {integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
pinkie: 2.0.4 pinkie: 2.0.4
dev: true dev: true
@@ -20415,6 +20444,7 @@ packages:
/pinkie@2.0.4: /pinkie@2.0.4:
resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -21583,6 +21613,7 @@ packages:
/read-pkg-up@1.0.1: /read-pkg-up@1.0.1:
resolution: {integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==} resolution: {integrity: sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
find-up: 1.1.2 find-up: 1.1.2
read-pkg: 1.1.0 read-pkg: 1.1.0
@@ -21609,6 +21640,7 @@ packages:
/read-pkg@1.1.0: /read-pkg@1.1.0:
resolution: {integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==} resolution: {integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
load-json-file: 1.1.0 load-json-file: 1.1.0
normalize-package-data: 2.5.0 normalize-package-data: 2.5.0
@@ -21689,6 +21721,7 @@ packages:
/readdirp@2.2.1: /readdirp@2.2.1:
resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==}
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
requiresBuild: true
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
micromatch: 3.1.10 micromatch: 3.1.10
@@ -21720,6 +21753,7 @@ packages:
/redent@1.0.0: /redent@1.0.0:
resolution: {integrity: sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==} resolution: {integrity: sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
indent-string: 2.1.0 indent-string: 2.1.0
strip-indent: 1.0.1 strip-indent: 1.0.1
@@ -21931,6 +21965,7 @@ packages:
/repeating@2.0.1: /repeating@2.0.1:
resolution: {integrity: sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==} resolution: {integrity: sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
is-finite: 1.1.0 is-finite: 1.1.0
dev: true dev: true
@@ -23255,6 +23290,7 @@ packages:
/strip-bom@2.0.0: /strip-bom@2.0.0:
resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
is-utf8: 0.2.1 is-utf8: 0.2.1
dev: true dev: true
@@ -23288,6 +23324,7 @@ packages:
resolution: {integrity: sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==} resolution: {integrity: sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
hasBin: true hasBin: true
requiresBuild: true
dependencies: dependencies:
get-stdin: 4.0.1 get-stdin: 4.0.1
dev: true dev: true
@@ -23659,6 +23696,53 @@ packages:
serialize-javascript: 6.0.1 serialize-javascript: 6.0.1
terser: 5.16.1 terser: 5.16.1
webpack: 5.75.0(webpack-cli@5.0.1) webpack: 5.75.0(webpack-cli@5.0.1)
dev: true
/terser-webpack-plugin@5.3.9(webpack@5.75.0):
resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==}
engines: {node: '>= 10.13.0'}
peerDependencies:
'@swc/core': '*'
esbuild: '*'
uglify-js: '*'
webpack: ^5.1.0
peerDependenciesMeta:
'@swc/core':
optional: true
esbuild:
optional: true
uglify-js:
optional: true
dependencies:
'@jridgewell/trace-mapping': 0.3.17
jest-worker: 27.5.1
schema-utils: 3.1.1
serialize-javascript: 6.0.1
terser: 5.19.2
webpack: 5.75.0(webpack-cli@5.0.1)
/terser-webpack-plugin@5.3.9(webpack@5.88.2):
resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==}
engines: {node: '>= 10.13.0'}
peerDependencies:
'@swc/core': '*'
esbuild: '*'
uglify-js: '*'
webpack: ^5.1.0
peerDependenciesMeta:
'@swc/core':
optional: true
esbuild:
optional: true
uglify-js:
optional: true
dependencies:
'@jridgewell/trace-mapping': 0.3.18
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.1
terser: 5.19.2
webpack: 5.88.2(webpack-cli@5.0.1)
/terser-webpack-plugin@5.3.9(webpack@5.88.2): /terser-webpack-plugin@5.3.9(webpack@5.88.2):
resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==} resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==}
@@ -23703,6 +23787,27 @@ packages:
acorn: 8.8.2 acorn: 8.8.2
commander: 2.20.3 commander: 2.20.3
source-map-support: 0.5.21 source-map-support: 0.5.21
dev: true
/terser@5.19.2:
resolution: {integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==}
engines: {node: '>=10'}
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.5
acorn: 8.8.2
commander: 2.20.3
source-map-support: 0.5.21
/terser@5.19.2:
resolution: {integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==}
engines: {node: '>=10'}
hasBin: true
dependencies:
'@jridgewell/source-map': 0.3.5
acorn: 8.10.0
commander: 2.20.3
source-map-support: 0.5.21
/terser@5.19.2: /terser@5.19.2:
resolution: {integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==} resolution: {integrity: sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==}
@@ -23897,6 +24002,7 @@ packages:
/trim-newlines@1.0.0: /trim-newlines@1.0.0:
resolution: {integrity: sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==} resolution: {integrity: sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dev: true dev: true
optional: true optional: true
@@ -24593,6 +24699,7 @@ packages:
/untildify@2.1.0: /untildify@2.1.0:
resolution: {integrity: sha512-sJjbDp2GodvkB0FZZcn7k6afVisqX5BZD7Yq3xp4nN2O15BBK0cLm3Vwn2vQaF7UDS0UUsrQMkkplmDI5fskig==} resolution: {integrity: sha512-sJjbDp2GodvkB0FZZcn7k6afVisqX5BZD7Yq3xp4nN2O15BBK0cLm3Vwn2vQaF7UDS0UUsrQMkkplmDI5fskig==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
requiresBuild: true
dependencies: dependencies:
os-homedir: 1.0.2 os-homedir: 1.0.2
dev: true dev: true
@@ -25059,7 +25166,7 @@ packages:
webpack: 5.75.0(webpack-cli@5.0.1) webpack: 5.75.0(webpack-cli@5.0.1)
webpack-bundle-analyzer: 4.7.0 webpack-bundle-analyzer: 4.7.0
webpack-dev-server: 4.11.1(webpack-cli@5.0.1)(webpack@5.75.0) webpack-dev-server: 4.11.1(webpack-cli@5.0.1)(webpack@5.75.0)
webpack-merge: 5.8.0 webpack-merge: 5.9.0
/webpack-dev-middleware@2.0.6(webpack@5.75.0): /webpack-dev-middleware@2.0.6(webpack@5.75.0):
resolution: {integrity: sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==} resolution: {integrity: sha512-tj5LLD9r4tDuRIDa5Mu9lnY2qBBehAITv6A9irqXhw/HQquZgTx3BCd57zYbU2gMDnncA49ufK2qVQSbaKJwOw==}
@@ -25226,8 +25333,8 @@ packages:
uuid: 3.4.0 uuid: 3.4.0
dev: true dev: true
/webpack-merge@5.8.0: /webpack-merge@5.9.0:
resolution: {integrity: sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q==} resolution: {integrity: sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
dependencies: dependencies:
clone-deep: 4.0.1 clone-deep: 4.0.1
@@ -25328,7 +25435,47 @@ packages:
neo-async: 2.6.2 neo-async: 2.6.2
schema-utils: 3.1.1 schema-utils: 3.1.1
tapable: 2.2.1 tapable: 2.2.1
terser-webpack-plugin: 5.3.6(webpack@5.75.0) terser-webpack-plugin: 5.3.9(webpack@5.75.0)
watchpack: 2.4.0
webpack-cli: 5.0.1(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.11.1)(webpack@5.75.0)
webpack-sources: 3.2.3
transitivePeerDependencies:
- '@swc/core'
- esbuild
- uglify-js
/webpack@5.88.2(webpack-cli@5.0.1):
resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==}
engines: {node: '>=10.13.0'}
hasBin: true
peerDependencies:
webpack-cli: '*'
peerDependenciesMeta:
webpack-cli:
optional: true
dependencies:
'@types/eslint-scope': 3.7.4
'@types/estree': 1.0.1
'@webassemblyjs/ast': 1.11.6
'@webassemblyjs/wasm-edit': 1.11.6
'@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.10.0
acorn-import-assertions: 1.9.0(acorn@8.10.0)
browserslist: 4.21.9
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.3.0
eslint-scope: 5.1.1
events: 3.3.0
glob-to-regexp: 0.4.1
graceful-fs: 4.2.11
json-parse-even-better-errors: 2.3.1
loader-runner: 4.3.0
mime-types: 2.1.35
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
terser-webpack-plugin: 5.3.9(webpack@5.88.2)
watchpack: 2.4.0 watchpack: 2.4.0
webpack-cli: 5.0.1(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.11.1)(webpack@5.75.0) webpack-cli: 5.0.1(webpack-bundle-analyzer@4.7.0)(webpack-dev-server@4.11.1)(webpack@5.75.0)
webpack-sources: 3.2.3 webpack-sources: 3.2.3

View File

@@ -1,45 +0,0 @@
/* eslint-disable no-console */
require('dotenv').config();
const fs = require('fs');
const path = require('path');
require('shelljs/global');
const { Environment, AppFormat } = require('./constants');
const build = require('../webpack.prod');
const env = process.env.NODE_ENV;
module.exports = async () => {
try {
// hold the build path as per the environment mode
const buildPath = path.join(`${__dirname}/../build/${env}`);
// copy project assets and generate config.
const directoryPaths = [path.join(`${__dirname}/../build`), buildPath];
// Remove build before start bundle
rm('-rf', buildPath);
// Create build directories
directoryPaths.forEach((directoryPath) => {
if (!fs.existsSync(directoryPath)) mkdir(directoryPath);
});
// Copy react app assets
cp(
'-R',
path.join(`${__dirname}/../platform/firecamp-platform/public/assets/*`),
buildPath
);
// generate package.json and manifest based on app environment
// exec(`node ${buildPath}/build-scripts/init-package.js`);
if (env === Environment.Production || env === Environment.Staging) {
await build();
}
return Promise.resolve();
} catch (error) {
return Promise.reject(error);
}
};
if (env === Environment.Development) module.exports();

View File

@@ -1,21 +1,12 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
require('dotenv-vault-core').config(); require('dotenv-vault-core').config();
const { red, yellow } = require('colors'); const { red, yellow } = require('colors');
const semver = require('semver'); // const semver = require('semver');
require('shelljs/global'); require('shelljs/global');
const build = require('./build');
const { version } = require('../package.json'); const { version } = require('../package.json');
const { Environment, AppFormat } = require('./constants'); const { Environment } = require('./constants');
const env = process.env.NODE_ENV; const env = process.env.NODE_ENV;
const helper = {
buildWebApp: async () => {
process.env.NODE_OPTIONS = '--max-old-space-size=4096';
await build();
},
};
// set app version in the environment
process.env.APP_VERSION = version; process.env.APP_VERSION = version;
// check FIRECAMP_API_HOST env. variable value does not contains invalid value // check FIRECAMP_API_HOST env. variable value does not contains invalid value
@@ -33,20 +24,5 @@ if (
process.env.FIRECAMP_API_HOST process.env.FIRECAMP_API_HOST
)})` )})`
); );
process.exit(); process.exit(1);
}
const preBuildCliCommands = async () => {
// pre conditions can be validated here
return Promise.resolve();
};
if ([Environment.Production, Environment.Staging].includes(env)) {
try {
preBuildCliCommands().then(async () => {
await helper.buildWebApp();
});
} catch (error) {
console.error(error);
}
} }

View File

@@ -4,6 +4,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Firecamp, API campsite for developers.</title> <title>Firecamp, API campsite for developers.</title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta NAME="robots" CONTENT="noindex,nofollow">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -2,11 +2,47 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Firecamp, API campsite for developers.</title> <!-- HTML Meta Tags -->
<title>Firecamp, The API Campsite for Developers.</title>
<meta
name="description"
content="Build APIs 3x faster with Firecamp's features like team collaboration, API documentation, API collection etc. Boost your confidence to build APIs for millions of users with Firecamp's simple, powerful, and open-source platform."
/>
<!-- Facebook Meta Tags -->
<meta property="og:url" content="https://firecamp.dev/" />
<meta property="og:type" content="website" />
<meta
property="og:title"
content="Firecamp, The API Campsite for Developers."
/>
<meta
property="og:description"
content="Build APIs 3x faster with Firecamp's features like team collaboration, API documentation, API collection etc. Boost your confidence to build APIs for millions of users with Firecamp's simple, powerful, and open-source platform."
/>
<meta property="og:image" content="https://firecamp.io/og.png" />
<!-- Twitter Meta Tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta property="twitter:domain" content="firecamp.dev" />
<meta property="twitter:url" content="https://firecamp.dev/" />
<meta
name="twitter:title"
content="Firecamp, The API Campsite for Developers."
/>
<meta
name="twitter:description"
content="Build APIs 3x faster with Firecamp's features like team collaboration, API documentation, API collection etc. Boost your confidence to build APIs for millions of users with Firecamp's simple, powerful, and open-source platform."
/>
<meta name="twitter:image" content="https://firecamp.io/og.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" /> <meta
http-equiv="Content-Security-Policy"
content="upgrade-insecure-requests"
/>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
</body> </body>
</html> </html>

View File

@@ -2,132 +2,26 @@
require('dotenv').config(); require('dotenv').config();
/* eslint-disable no-console */ /* eslint-disable no-console */
require('dotenv-vault-core').config(); require('dotenv-vault-core').config();
console.log(process.env.FIRECAMP_API_HOST, 'FIRECAMP_API_HOST'); // for debugging purposes. remove when ready.
const webpack = require('webpack'); const webpack = require('webpack');
const path = require('path'); const path = require('path');
// eslint-disable-next-line import/no-extraneous-dependencies // eslint-disable-next-line import/no-extraneous-dependencies
// const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); // const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const { Environment } = require('./scripts/constants'); const CopyPlugin = require('copy-webpack-plugin');
const env = process.env.NODE_ENV;
const metadata = require('./package.json'); const metadata = require('./package.json');
exports.common = { const NodeEnv = process.env.NODE_ENV;
entry: { console.log(process.env.FIRECAMP_API_HOST, 'FIRECAMP_API_HOST'); // for debugging purposes. remove when ready.
index: path.join(
__dirname,
'./platform/firecamp-platform/src/containers/index.tsx'
),
identity: path.join(
__dirname,
'./platform/firecamp-platform/src/containers/identity.tsx'
),
},
optimization: {
nodeEnv: process.env.NODE_ENV,
minimize: true,
runtimeChunk: 'single',
splitChunks: {
// name: 'vendor',
// chunks(chunk) {
// // To prevent generate separate chunk for background script
// // Because all node_modules not needed in background script
// return !chunk?.name?.includes('background');
// },
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
return 'vender';
// get the name. E.g. node_modules/packageName/not/this/part.js const plugins = [
// or node_modules/packageName
// const packageName = module.context.match(
// /[\\/]node_modules[\\/](.*?)([\\/]|$)/
// )[1];
// // npm package names are URL-safe, but some servers don't like @ symbols
// return `npm.${packageName.replace('@', '')}`;
},
},
},
},
},
resolve: {
extensions: ['*', '.mjs', '.js', '.json', '.jsx', '.ts', '.tsx', '.svg'],
alias: {
// faker: path.resolve('./node_modules/faker'),
react: path.resolve('./node_modules/react'),
'react-dom': path.resolve('./node_modules/react-dom'),
lodash: path.resolve('./node_modules/lodash'),
nanoid: path.resolve('./node_modules/nanoid'),
'awesome-notifications': path.resolve(
'./node_modules/awesome-notifications'
),
'@babel/runtime': path.resolve('./node_modules/@babel/runtime'),
'monaco-editor': path.resolve('./node_modules/monaco-editor'),
},
fallback: {
fs: false,
},
},
};
const outputPath = `${__dirname}/build/${env}`;
const publicPath = '';
exports.output = {
globalObject: 'this',
filename: '[name].bundle.js',
chunkFilename: '[name].bundle.js',
path: outputPath,
publicPath,
};
// exports.output.path = path.join(__dirname, `./build/${env}`);
if (env === Environment.Development) exports.output.clean = true;
exports.env = {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
FIRECAMP_API_HOST: JSON.stringify(process.env.FIRECAMP_API_HOST),
FIRECAMP_CLOUD_AGENT: JSON.stringify(process.env.FIRECAMP_CLOUD_AGENT),
FIRECAMP_EXTENSION_AGENT_ID: JSON.stringify(
process.env.FIRECAMP_EXTENSION_AGENT_ID
),
APP_VERSION: JSON.stringify(metadata.version),
AppFormat: JSON.stringify(process.env.AppFormat),
SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN),
CRISP_WEBSITE_ID: JSON.stringify(process.env.CRISP_WEBSITE_ID),
CRISP_FIRECAMP_DEV: JSON.stringify(process.env.CRISP_FIRECAMP_DEV),
GOOGLE_OAUTH2_CLIENT_ID: JSON.stringify(process.env.GOOGLE_OAUTH2_CLIENT_ID),
GOOGLE_OAUTH2_REDIRECT_URI: JSON.stringify(
process.env.GOOGLE_OAUTH2_REDIRECT_URI
),
GITHUB_OAUTH2_CLIENT_ID: JSON.stringify(process.env.GITHUB_OAUTH2_CLIENT_ID),
GITHUB_OAUTH2_REDIRECT_URI: JSON.stringify(
process.env.GITHUB_OAUTH2_REDIRECT_URI
),
GOOGLE_ANALYTICS_CHROME_ID: JSON.stringify(
process.env.GOOGLE_ANALYTICS_CHROME_ID
),
GOOGLE_ANALYTICS_ELECTRON_ID: JSON.stringify(
process.env.GOOGLE_ANALYTICS_ELECTRON_ID
),
};
exports.plugins = [
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: true, inject: true,
chunks: ['index'], chunks: ['index'],
filename: 'index.html', filename: 'index.html',
template: 'templates/index.html', template: 'templates/index.html',
favicon: 'templates/favicon.png', favicon: 'templates/favicon.png',
hash: true,
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: true, inject: true,
@@ -156,10 +50,79 @@ exports.plugins = [
// publicPath: '/js', // publicPath: '/js',
// filename: '[name].worker.bundle.js', // filename: '[name].worker.bundle.js',
// languages: ['javascript', 'html', 'typescript', 'json'], // languages: ['javascript', 'html', 'typescript', 'json'],
// }), // }),11
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
FIRECAMP_API_HOST: JSON.stringify(process.env.FIRECAMP_API_HOST),
FIRECAMP_CLOUD_AGENT: JSON.stringify(process.env.FIRECAMP_CLOUD_AGENT),
FIRECAMP_EXTENSION_AGENT_ID: JSON.stringify(
process.env.FIRECAMP_EXTENSION_AGENT_ID
),
APP_VERSION: JSON.stringify(metadata.version),
AppFormat: JSON.stringify(process.env.AppFormat),
SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN),
CRISP_WEBSITE_ID: JSON.stringify(process.env.CRISP_WEBSITE_ID),
CRISP_FIRECAMP_DEV: JSON.stringify(process.env.CRISP_FIRECAMP_DEV),
GOOGLE_OAUTH2_CLIENT_ID: JSON.stringify(
process.env.GOOGLE_OAUTH2_CLIENT_ID
),
GOOGLE_OAUTH2_REDIRECT_URI: JSON.stringify(
process.env.GOOGLE_OAUTH2_REDIRECT_URI
),
GITHUB_OAUTH2_CLIENT_ID: JSON.stringify(
process.env.GITHUB_OAUTH2_CLIENT_ID
),
GITHUB_OAUTH2_REDIRECT_URI: JSON.stringify(
process.env.GITHUB_OAUTH2_REDIRECT_URI
),
GOOGLE_ANALYTICS_CHROME_ID: JSON.stringify(
process.env.GOOGLE_ANALYTICS_CHROME_ID
),
GOOGLE_ANALYTICS_ELECTRON_ID: JSON.stringify(
process.env.GOOGLE_ANALYTICS_ELECTRON_ID
),
},
}),
new CopyPlugin({
patterns: [
{
from: `${__dirname}/platform/firecamp-platform/public/assets`,
to: `${__dirname}/build/${NodeEnv}`,
},
],
}),
]; ];
exports.rules = [ const rules = [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: [
'@babel/preset-env',
[
'@babel/preset-react',
{
runtime: 'automatic',
},
],
'@babel/preset-typescript',
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
regenerator: true,
},
],
['@babel/plugin-proposal-export-default-from'],
'add-module-exports',
],
},
},
{ test: /\.flow$/, loader: 'ignore-loader' }, { test: /\.flow$/, loader: 'ignore-loader' },
{ {
test: /\.css$/, test: /\.css$/,
@@ -195,3 +158,68 @@ exports.rules = [
}, },
}, },
]; ];
module.exports = {
entry: {
index: path.join(
__dirname,
'./platform/firecamp-platform/src/containers/index.tsx'
),
identity: path.join(
__dirname,
'./platform/firecamp-platform/src/containers/identity.tsx'
),
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
// name: 'vendor',
// chunks(chunk) {
// // To prevent generate separate chunk for background script
// // Because all node_modules not needed in background script
// return !chunk?.name?.includes('background');
// },
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
// eslint-disable-next-line no-unused-vars
name(module) {
return 'vender';
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
// const packageName = module.context.match(
// /[\\/]node_modules[\\/](.*?)([\\/]|$)/
// )[1];
// // npm package names are URL-safe, but some servers don't like @ symbols
// return `npm.${packageName.replace('@', '')}`;
},
},
},
},
},
resolve: {
extensions: ['*', '.mjs', '.js', '.json', '.jsx', '.ts', '.tsx', '.svg'],
alias: {
// faker: path.resolve('./node_modules/faker'),
react: path.resolve('./node_modules/react'),
'react-dom': path.resolve('./node_modules/react-dom'),
lodash: path.resolve('./node_modules/lodash'),
nanoid: path.resolve('./node_modules/nanoid'),
'awesome-notifications': path.resolve(
'./node_modules/awesome-notifications'
),
'@babel/runtime': path.resolve('./node_modules/@babel/runtime'),
'monaco-editor': path.resolve('./node_modules/monaco-editor'),
},
fallback: {
fs: false,
},
},
plugins,
module: { rules },
};

View File

@@ -1,18 +1,36 @@
// imports environment
require('dotenv').config();
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const BundleAnalyzerPlugin = const { merge } = require('webpack-merge');
require('webpack-bundle-analyzer').BundleAnalyzerPlugin; // const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const { common, env, plugins, rules, output } = require('./webpack.config'); const TerserPlugin = require('terser-webpack-plugin');
const base = require('./webpack.common');
const withReport = process.env.npm_config_withReport; // const withReport = process.env.npm_config_withReport;
const nodeEnv = process.env.NODE_ENV;
module.exports = { module.exports = merge(base, {
...common,
mode: 'development', mode: 'development',
devtool: 'eval-cheap-module-source-map', devtool: 'cheap-module-source-map',
output: {
clean: true,
globalObject: 'this',
filename: '[name].dev.js',
chunkFilename: '[name].dev.js',
path: `${__dirname}/build/${nodeEnv}`,
publicPath: '',
},
optimization: {
nodeEnv: 'development',
minimizer: [
new TerserPlugin({
parallel: 4,
minify: TerserPlugin.esbuildMinify,
// terserOptions: {
// sourceMap: 'external',
// },
}),
],
},
devServer: { devServer: {
//server: 'https', //server: 'https',
static: path.join(__dirname, './build/development'), static: path.join(__dirname, './build/development'),
@@ -28,51 +46,9 @@ module.exports = {
'X-Requested-With, content-type, Authorization', 'X-Requested-With, content-type, Authorization',
}, },
}, },
output,
plugins: [ plugins: [
...plugins,
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.IgnorePlugin({ resourceRegExp: /[^/]+\/[\S]+.dev$/ }), new webpack.IgnorePlugin({ resourceRegExp: /[^/]+\/[\S]+.dev$/ }),
new webpack.DefinePlugin({
'process.env': {
...env,
FIRECAMP_EXTENSION_AGENT_ID: JSON.stringify(
process.env.FIRECAMP_EXTENSION_AGENT_ID
),
},
}),
// new BundleAnalyzerPlugin(), // new BundleAnalyzerPlugin(),
], ],
module: { });
rules: [
...rules,
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
[
'@babel/preset-react',
{
runtime: 'automatic',
},
],
'@babel/preset-typescript',
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
regenerator: true,
},
],
['@babel/plugin-proposal-export-default-from'],
'add-module-exports',
],
},
},
],
},
};

View File

@@ -1,94 +1,37 @@
/* eslint-disable no-console */
const path = require('path'); const path = require('path');
const webpack = require('webpack'); const webpack = require('webpack');
const CompressionPlugin = require('compression-webpack-plugin'); const { merge } = require('webpack-merge');
const { common, env, plugins, rules } = require('./webpack.config'); const TerserPlugin = require('terser-webpack-plugin');
// const CompressionPlugin = require('compression-webpack-plugin');
const base = require('./webpack.common');
const nodeEnv = process.env.NODE_ENV; const nodeEnv = process.env.NODE_ENV;
const config = {
...common, module.exports = merge(base, {
mode: 'production', mode: 'production',
output: { output: {
clean: true,
globalObject: 'this', globalObject: 'this',
filename: '[name].bundle.js', filename: '[name].min.js',
chunkFilename: '[name].bundle.js', chunkFilename: '[name].min.js',
path: path.join(__dirname, `./build/${nodeEnv}`), path: path.join(__dirname, `./build/${nodeEnv}`),
}, },
plugins: [ optimization: {
...plugins, nodeEnv: 'production',
new webpack.ProvidePlugin({ // minimize: true,
React: 'react', minimizer: [
}), new TerserPlugin({
new webpack.IgnorePlugin({ resourceRegExp: /[^/]+\/[\S]+.prod$/ }), parallel: 4,
new webpack.DefinePlugin({ minify: TerserPlugin.esbuildMinify,
'process.env': env, // terserOptions: {
}), // sourceMap: 'external',
new CompressionPlugin(), // },
], }),
module: {
rules: [
...rules,
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
[
'@babel/preset-react',
{
runtime: 'automatic',
},
],
'@babel/preset-typescript',
],
plugins: [
[
'@babel/plugin-transform-runtime',
{
regenerator: true,
},
],
['@babel/plugin-proposal-export-default-from'],
'add-module-exports',
['transform-remove-console', { exclude: ['info'] }],
],
},
},
], ],
}, },
}; plugins: [
new webpack.ProvidePlugin({ React: 'react' }),
module.exports = () => new webpack.IgnorePlugin({ resourceRegExp: /[^/]+\/[\S]+.prod$/ }),
new Promise((resolve, reject) => { // new CompressionPlugin(),
console.log('[Webpack Build]'); ],
console.log('-'.repeat(80)); });
const compiler = webpack(config);
compiler.run((err, stats) => {
if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
reject(err.stack || err.details || err);
}
const info = stats.toJson();
if (stats.hasErrors()) {
console.error(info.errors);
reject(info.errors);
}
if (stats.hasWarnings()) {
console.warn(info.warnings);
}
resolve();
});
});