new batch for site -> projects

This commit is contained in:
nick-delirium 2024-09-18 16:49:59 +02:00
parent df20cd5333
commit 81ecbac892
No known key found for this signature in database
GPG key ID: 93ABD695DF5FDBA0
14 changed files with 223 additions and 192 deletions

View file

@ -10,6 +10,8 @@ import NotFoundPage from 'Shared/NotFoundPage';
import { ModalProvider } from 'Components/Modal';
import Layout from 'App/layout/Layout';
import PublicRoutes from 'App/PublicRoutes';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
const components: any = {
SessionPure: lazy(() => import('Components/Session/Session')),
@ -41,8 +43,11 @@ interface Props {
}
function IFrameRoutes(props: Props) {
const { isJwt = false, isLoggedIn = false, loading, onboarding, sites, siteId, jwt } = props;
const siteIdList: any = sites.map(({ id }) => id).toJS();
const { projectsStore } = useStore();
const sites = projectsStore.list;
const siteId = projectsStore.siteId;
const { isJwt = false, isLoggedIn = false, loading, onboarding, jwt } = props;
const siteIdList: any = sites.map(({ id }) => id);
if (isLoggedIn) {
return (
@ -75,11 +80,9 @@ function IFrameRoutes(props: Props) {
export default connect((state: any) => ({
changePassword: state.getIn(['user', 'account', 'changePassword']),
onboarding: state.getIn(['user', 'onboarding']),
sites: state.getIn(['site', 'list']),
siteId: state.getIn(['site', 'siteId']),
jwt: state.getIn(['user', 'jwt']),
tenantId: state.getIn(['user', 'account', 'tenantId']),
isEnterprise:
state.getIn(['user', 'account', 'edition']) === 'ee' ||
state.getIn(['user', 'authDetails', 'edition']) === 'ee'
}))(IFrameRoutes);
}))(observer(IFrameRoutes));

View file

@ -23,11 +23,14 @@ interface Props {
permissionsMap: any;
removeErrors: any;
resetErrors: () => void;
projectsMap: any;
}
function Roles(props: Props) {
const { roleStore } = useStore();
const { roleStore, projectsStore } = useStore();
const projectsMap = projectsStore.list.reduce((acc: any, p: any) => {
acc[p.id] = p.name;
return acc;
}, {})
const roles = roleStore.list;
const loading = roleStore.loading;
const init = roleStore.init;
@ -36,7 +39,7 @@ function Roles(props: Props) {
roleStore.permissions.forEach((p: any) => {
permissionsMap[p.value] = p.text;
});
const { account, projectsMap } = props;
const { account } = props;
const { showModal, hideModal } = useModal();
const isAdmin = account.admin || account.superAdmin;
@ -108,13 +111,8 @@ function Roles(props: Props) {
export default connect(
(state: any) => {
const projects = state.getIn(['site', 'list']);
return {
account: state.getIn(['user', 'account']),
projectsMap: projects.reduce((acc: any, p: any) => {
acc[p.id] = p.name;
return acc;
}, {}),
};
}
)(withPageTitle('Roles & Access - OpenReplay Preferences')(observer(Roles)));

View file

@ -23,16 +23,16 @@ interface Permission {
interface Props {
closeModal: (toastMessage?: string) => void;
projects: any[];
permissionsMap: any;
deleteHandler: (id: any) => Promise<void>;
}
const RoleForm = (props: Props) => {
const { roleStore } = useStore();
const { roleStore, projectsStore } = useStore();
const projects = projectsStore.list;
const role = roleStore.instance;
const saving = roleStore.loading;
const { closeModal, permissionsMap, projects } = props;
const { closeModal, permissionsMap } = props;
const projectOptions = projects
.filter(({ value }) => !role.projects.includes(value))
.map((p: any) => ({
@ -217,12 +217,7 @@ const RoleForm = (props: Props) => {
);
};
export default connect((state: any) => {
const projects = state.getIn(['site', 'list']);
return {
projects,
};
})(observer(RoleForm));
export default observer(RoleForm);
function OptionLabel(nameMap: any, p: any, onChangeOption: (e: any) => void) {
return (

View file

@ -3,22 +3,18 @@ import React, { ChangeEvent, FormEvent, useEffect, useState } from 'react';
import { ConnectedProps, connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { toast } from 'react-toastify';
import { withStore } from 'App/mstore';
import { clearSearch as clearSearchLive } from 'Duck/liveSearch';
import { clearSearch } from 'Duck/search';
import { edit, fetchList, remove, save, update } from 'Duck/site';
import { setSiteId } from 'Duck/site';
import { pushNewSite } from 'Duck/user';
import { Button, Form, Icon, Input, SegmentSelection } from 'UI';
import { confirm } from 'UI';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import styles from './siteForm.module.css';
type OwnProps = {
onClose: (arg: any) => void;
mstore: any;
canDelete: boolean;
};
type PropsFromRedux = ConnectedProps<typeof connector>;
@ -26,36 +22,35 @@ type PropsFromRedux = ConnectedProps<typeof connector>;
type Props = PropsFromRedux & RouteComponentProps & OwnProps;
const NewSiteForm = ({
site,
loading,
save,
remove,
edit,
update,
pushNewSite,
fetchList,
setSiteId,
clearSearch,
clearSearchLive,
location: { pathname },
onClose,
mstore,
activeSiteId,
canDelete,
}: Props) => {
const mstore = useStore();
const { projectsStore } = mstore;
const activeSiteId = projectsStore.active?.id
const site = projectsStore.instance;
const siteList = projectsStore.list;
const loading = projectsStore.loading;
const canDelete = siteList.length > 1;
const setSiteId = projectsStore.setSiteId;
const saveProject = projectsStore.save;
const fetchList = projectsStore.fetchList;
const [existsError, setExistsError] = useState(false);
useEffect(() => {
if (pathname.includes('onboarding')) {
if (pathname.includes('onboarding') && site?.id) {
setSiteId(site.id);
}
if (!site) projectsStore.initProject({});
}, []);
const onSubmit = (e: FormEvent) => {
e.preventDefault();
if (site.exists()) {
update(site, site.id).then((response: any) => {
if (site?.id && site.exists()) {
projectsStore.updateProject( site.id, site.toData()).then((response: any) => {
if (!response || !response.errors || response.errors.size === 0) {
onClose(null);
if (!pathname.includes('onboarding')) {
@ -67,7 +62,7 @@ const NewSiteForm = ({
}
});
} else {
save(site).then((response: any) => {
saveProject(site!).then((response: any) => {
if (!response || !response.errors || response.errors.size === 0) {
onClose(null);
clearSearch();
@ -89,8 +84,9 @@ const NewSiteForm = ({
confirmButton: 'Yes, delete',
cancelButton: 'Cancel',
})
&& site?.id
) {
remove(site.id).then(() => {
projectsStore.removeProject(site.id).then(() => {
onClose(null);
if (site.id === activeSiteId) {
setSiteId(null);
@ -103,9 +99,12 @@ const NewSiteForm = ({
target: { name, value },
}: ChangeEvent<HTMLInputElement>) => {
setExistsError(false);
edit({ [name]: value });
projectsStore.editInstance({ [name]: value });
};
if (!site) {
return null
}
return (
<div
className="bg-white h-screen overflow-y-auto"
@ -116,7 +115,7 @@ const NewSiteForm = ({
</h3>
<Form
className={styles.formWrapper}
onSubmit={site.validate() && onSubmit}
onSubmit={site.validate && onSubmit}
>
<div className={styles.content}>
<Form.Field>
@ -146,7 +145,7 @@ const NewSiteForm = ({
]}
value={site.platform}
onChange={(value) => {
edit({ platform: value });
projectsStore.editInstance({ platform: value });
}}
/>
</div>
@ -157,9 +156,9 @@ const NewSiteForm = ({
type="submit"
className="float-left mr-2"
loading={loading}
disabled={!site.validate()}
disabled={!site.validate}
>
{site.exists() ? 'Update' : 'Add'}
{site?.exists() ? 'Update' : 'Add'}
</Button>
{site.exists() && (
<Button
@ -183,26 +182,11 @@ const NewSiteForm = ({
);
};
const mapStateToProps = (state: any) => ({
activeSiteId: state.getIn(['site', 'active', 'id']),
site: state.getIn(['site', 'instance']),
siteList: state.getIn(['site', 'list']),
loading:
state.getIn(['site', 'save', 'loading']) ||
state.getIn(['site', 'remove', 'loading']),
canDelete: state.getIn(['site', 'list']).size > 1,
});
const mapStateToProps = null;
const connector = connect(mapStateToProps, {
save,
remove,
edit,
update,
pushNewSite,
fetchList,
setSiteId,
clearSearch,
clearSearchLive,
});
export default connector(withRouter(withStore(NewSiteForm)));
export default connector(withRouter(observer(NewSiteForm)));

View file

@ -24,11 +24,12 @@ import AnimatedSVG, { ICONS } from 'Shared/AnimatedSVG/AnimatedSVG';
import DashboardEditModal from '../DashboardEditModal';
function DashboardList({ siteId }: { siteId: string }) {
function DashboardList() {
const { dashboardStore, projectsStore } = useStore();
const siteId = projectsStore.siteId;
const [focusTitle, setFocusedInput] = React.useState(true);
const [showEditModal, setShowEditModal] = React.useState(false);
const { dashboardStore } = useStore();
const list = dashboardStore.filteredList;
const dashboardsSearch = dashboardStore.filter.query;
const history = useHistory();
@ -219,6 +220,4 @@ function DashboardList({ siteId }: { siteId: string }) {
);
}
export default connect((state: any) => ({
siteId: state.getIn(['site', 'siteId']),
}))(observer(DashboardList));
export default observer(DashboardList);

View file

@ -2,7 +2,8 @@ import { Modal } from 'antd';
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import colors from 'tailwindcss/colors';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
import CreateCard from 'Components/Dashboard/components/DashboardList/NewDashModal/CreateCard';
import SelectCard from './SelectCard';
@ -12,7 +13,6 @@ interface NewDashboardModalProps {
open: boolean;
isAddingFromLibrary?: boolean;
isEnterprise?: boolean;
isMobile?: boolean;
}
const NewDashboardModal: React.FC<NewDashboardModalProps> = ({
@ -20,8 +20,9 @@ const NewDashboardModal: React.FC<NewDashboardModalProps> = ({
open,
isAddingFromLibrary = false,
isEnterprise = false,
isMobile = false,
}) => {
const { projectsStore } = useStore();
const isMobile = projectsStore.isMobile;
const [step, setStep] = React.useState<number>(0);
const [selectedCategory, setSelectedCategory] =
React.useState<string>('product-analytics');
@ -75,10 +76,9 @@ const NewDashboardModal: React.FC<NewDashboardModalProps> = ({
};
const mapStateToProps = (state: any) => ({
isMobile: state.getIn(['site', 'instance', 'platform']) === 'ios',
isEnterprise:
state.getIn(['user', 'account', 'edition']) === 'ee' ||
state.getIn(['user', 'account', 'edition']) === 'msaas',
});
export default connect(mapStateToProps)(NewDashboardModal);
export default connect(mapStateToProps)(observer(NewDashboardModal));

View file

@ -1,29 +1,26 @@
import React, { useEffect } from 'react';
import { Button, TagBadge } from 'UI';
import { connect } from 'react-redux';
import CustomFieldForm from '../../../Client/CustomFields/CustomFieldForm';
import { confirm } from 'UI';
import { useModal } from 'App/components/Modal';
import { toast } from 'react-toastify';
import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
interface MetadataListProps {
site: { id: string };
}
const MetadataList: React.FC<MetadataListProps> = (props) => {
const { site } = props;
const { customFieldStore } = useStore();
const MetadataList = () => {
const { customFieldStore, projectsStore } = useStore();
const site = projectsStore.instance;
const fields = customFieldStore.list;
const { showModal, hideModal } = useModal();
useEffect(() => {
customFieldStore.fetchList(site.id);
}, [site.id]);
customFieldStore.fetchList(site?.id);
}, [site?.id]);
const save = (field: any) => {
customFieldStore.save(site.id, field).then((response) => {
if (!site) return;
customFieldStore.save(site.id!, field).then((response) => {
if (!response || !response.errors || response.errors.size === 0) {
hideModal();
toast.success('Metadata added successfully!');
@ -62,11 +59,4 @@ const MetadataList: React.FC<MetadataListProps> = (props) => {
);
};
export default connect(
(state: any) => ({
site: state.getIn(['site', 'instance']),
fields: state.getIn(['customFields', 'list']).sortBy((i: any) => i.index),
field: state.getIn(['customFields', 'instance']),
loading: state.getIn(['customFields', 'fetchRequest', 'loading'])
})
)(MetadataList);
export default observer(MetadataList);

View file

@ -5,7 +5,6 @@ import { useStore } from 'App/mstore';
import { observer } from 'mobx-react-lite';
import { useHistory } from 'react-router-dom';
import { multiview, liveSession, withSiteId } from 'App/routes';
import { connect } from 'react-redux';
import { PlayerContext, ILivePlayerContext } from 'App/components/Session/playerContext';
interface ITab {
@ -44,13 +43,14 @@ const CurrentTab = React.memo(() => (
</Tab>
));
function AssistTabs({ session, siteId }: { session: Record<string, any>; siteId: string }) {
function AssistTabs({ session }: { session: Record<string, any> }) {
const history = useHistory();
const { store } = React.useContext(PlayerContext) as unknown as ILivePlayerContext
const { recordingState, calling, remoteControl } = store.get()
const isDisabled = recordingState !== 0 || calling !== 0 || remoteControl !== 0
const { assistMultiviewStore } = useStore();
const { assistMultiviewStore, projectsStore } = useStore();
const siteId = projectsStore.siteId!;
const placeholder = new Array(4 - assistMultiviewStore.sessions.length).fill(0);
@ -91,6 +91,4 @@ function AssistTabs({ session, siteId }: { session: Record<string, any>; siteId:
);
}
export default connect((state: any) => ({ siteId: state.getIn(['site', 'siteId']) }))(
observer(AssistTabs)
);
export default observer(AssistTabs);

View file

@ -1,8 +1,7 @@
import React, { useEffect } from 'react';
import { convertElementToImage } from 'App/utils';
import { useStore } from 'App/mstore';
import { useObserver } from 'mobx-react-lite';
import { connect } from 'react-redux';
import { observer } from 'mobx-react-lite';
import { fileNameFormat } from 'App/utils';
import { toast } from 'react-toastify';
import { forceVisible } from 'react-lazyload';
@ -15,11 +14,11 @@ interface Props {
export default function withReport<P extends Props>(WrappedComponent: React.ComponentType<P>) {
const ComponentWithReport = (props: P) => {
const [rendering, setRendering] = React.useState(false);
const { site } = props;
const { dashboardStore } = useStore();
const dashboard: any = useObserver(() => dashboardStore.selectedDashboard);
const period = useObserver(() => dashboardStore.period);
const pendingRequests = useObserver(() => dashboardStore.pendingRequests);
const { dashboardStore, projectsStore } = useStore();
const site = projectsStore.instance;
const dashboard: any = dashboardStore.selectedDashboard;
const period = dashboardStore.period;
const pendingRequests = dashboardStore.pendingRequests;
useEffect(() => {
if (rendering && pendingRequests <= 0) {
@ -181,7 +180,5 @@ export default function withReport<P extends Props>(WrappedComponent: React.Comp
);
};
return connect((state: any) => ({
site: state.getIn(['site', 'instance']),
}))(ComponentWithReport);
return observer(ComponentWithReport);
}

View file

@ -17,7 +17,8 @@ import { toggleFavorite } from 'Duck/sessions';
import SessionDateRange from './SessionDateRange';
import RecordingStatus from 'Shared/SessionsTabOverview/components/RecordingStatus';
import { sessionService } from 'App/services';
import { updateProjectRecordingStatus } from 'Duck/site';
import { observer } from 'mobx-react-lite';
import { useStore } from 'App/mstore';
enum NoContentType {
Bookmarked,
@ -50,17 +51,18 @@ interface Props extends RouteComponentProps {
updateCurrentPage: (page: number) => void;
setScrollPosition: (scrollPosition: number) => void;
fetchSessions: (filters: any, force: boolean) => void;
updateProjectRecordingStatus: (siteId: string, status: boolean) => void;
activeTab: any;
isEnterprise?: boolean;
checkForLatestSessions: () => void;
toggleFavorite: (sessionId: string) => Promise<void>;
sites: object[];
isLoggedIn: boolean;
siteId: string;
}
function SessionList(props: Props) {
const { projectsStore } = useStore();
const sites = projectsStore.list;
const siteId = projectsStore.siteId;
const updateProjectRecordingStatus = projectsStore.updateProjectRecordingStatus;
const [noContentType, setNoContentType] = React.useState<NoContentType>(NoContentType.ToDate);
const {
loading,
@ -73,9 +75,7 @@ function SessionList(props: Props) {
metaList,
activeTab,
isEnterprise = false,
sites,
isLoggedIn,
siteId
} = props;
const _filterKeys = filters.map((i: any) => i.key);
const hasUserFilter =
@ -139,7 +139,7 @@ function SessionList(props: Props) {
}
if (statusData.status === 2 && activeSite) { // recording && processed
props.updateProjectRecordingStatus(activeSite.id, true);
updateProjectRecordingStatus(activeSite.id, true);
props.fetchSessions(null, true);
clearInterval(sessionStatusTimeOut);
}
@ -293,8 +293,6 @@ export default connect(
activeTab: state.getIn(['search', 'activeTab']),
pageSize: state.getIn(['search', 'pageSize']),
isEnterprise: state.getIn(['user', 'account', 'edition']) === 'ee',
siteId: state.getIn(['site', 'siteId']),
sites: state.getIn(['site', 'list']),
isLoggedIn: Boolean(state.getIn(['user', 'jwt'])),
}),
{
@ -304,6 +302,5 @@ export default connect(
fetchSessions,
checkForLatestSessions,
toggleFavorite,
updateProjectRecordingStatus
}
)(withRouter(SessionList));
)(withRouter(observer(SessionList)));

View file

@ -1,77 +1,81 @@
import React from "react";
import stl from "./NoSessionPermission.module.css";
import { Icon, Button } from "UI";
import { connect } from "react-redux";
import { observer } from 'mobx-react-lite';
import React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps, withRouter } from 'react-router-dom';
import { useStore } from 'App/mstore';
import {
sessions as sessionsRoute,
assist as assistRoute,
withSiteId,
} from "App/routes";
import { withRouter, RouteComponentProps } from "react-router-dom";
assist as assistRoute,
sessions as sessionsRoute,
withSiteId,
} from 'App/routes';
import { Button, Icon } from 'UI';
import stl from './NoSessionPermission.module.css';
const SESSIONS_ROUTE = sessionsRoute();
const ASSIST_ROUTE = assistRoute();
interface Props extends RouteComponentProps {
session: any;
siteId: string;
history: any;
sessionPath: any;
isAssist: boolean;
session: any;
history: any;
sessionPath: any;
isAssist: boolean;
}
function NoSessionPermission(props: Props) {
const { session, history, siteId, sessionPath, isAssist } = props;
const { projectsStore } = useStore();
const siteId = projectsStore.siteId!;
const { session, history, sessionPath, isAssist } = props;
const backHandler = () => {
if (
sessionPath.pathname === history.location.pathname ||
sessionPath.pathname.includes("/session/") ||
isAssist
) {
history.push(
withSiteId(isAssist ? ASSIST_ROUTE : SESSIONS_ROUTE, siteId)
);
} else {
history.push(
sessionPath
? sessionPath.pathname + sessionPath.search
: withSiteId(SESSIONS_ROUTE, siteId)
);
}
};
const backHandler = () => {
if (
sessionPath.pathname === history.location.pathname ||
sessionPath.pathname.includes('/session/') ||
isAssist
) {
history.push(
withSiteId(isAssist ? ASSIST_ROUTE : SESSIONS_ROUTE, siteId)
);
} else {
history.push(
sessionPath
? sessionPath.pathname + sessionPath.search
: withSiteId(SESSIONS_ROUTE, siteId)
);
}
};
return (
<div className={stl.wrapper}>
<Icon name="shield-lock" size="50" className="py-16" />
<div className={stl.title}>Not allowed</div>
{session.isLive ? (
<span>
This session is still live, and you dont have the necessary
permissions to access this feature. Please check with your
admin.
</span>
) : (
<span>
You dont have the necessary permissions to access this
feature. Please check with your admin.
</span>
)}
{/* <Link to="/"> */}
<Button variant="primary" onClick={backHandler} className="mt-6">
GO BACK
</Button>
{/* </Link> */}
</div>
);
return (
<div className={stl.wrapper}>
<Icon name="shield-lock" size="50" className="py-16" />
<div className={stl.title}>Not allowed</div>
{session.isLive ? (
<span>
This session is still live, and you dont have the necessary
permissions to access this feature. Please check with your admin.
</span>
) : (
<span>
You dont have the necessary permissions to access this feature.
Please check with your admin.
</span>
)}
{/* <Link to="/"> */}
<Button variant="primary" onClick={backHandler} className="mt-6">
GO BACK
</Button>
{/* </Link> */}
</div>
);
}
export default withRouter(
connect((state: any) => {
const isAssist = window.location.pathname.includes("/assist/");
return {
isAssist,
session: state.getIn(["sessions", "current"]),
siteId: state.getIn(["site", "siteId"]),
sessionPath: state.getIn(["sessions", "sessionPath"]),
};
})(NoSessionPermission)
connect((state: any) => {
const isAssist = window.location.pathname.includes('/assist/');
return {
isAssist,
session: state.getIn(['sessions', 'current']),
sessionPath: state.getIn(['sessions', 'sessionPath']),
};
})(observer(NoSessionPermission))
);

View file

@ -10,6 +10,7 @@ export default class ProjectsStore {
siteId: string | null = null;
active: Project | null = null;
sitesLoading = false;
loading = false;
constructor() {
const storedSiteId = localStorage.getItem(SITE_ID_STORAGE_KEY);
@ -17,6 +18,10 @@ export default class ProjectsStore {
makeAutoObservable(this);
}
get isMobile() {
return this.instance ? ['ios', 'android'].includes(this.instance.platform) : false;
}
getSiteId = () => {
return {
siteId: this.siteId,
@ -32,6 +37,10 @@ export default class ProjectsStore {
this.sitesLoading = loading;
}
setLoading = (loading: boolean) => {
this.loading = loading;
}
setSiteId(siteId: string) {
this.siteId = siteId;
localStorage.setItem(SITE_ID_STORAGE_KEY, siteId.toString());
@ -73,7 +82,7 @@ export default class ProjectsStore {
}
}
fetchList = async (siteIdFromPath: string) =>{
fetchList = async (siteIdFromPath?: string) => {
this.setSitesLoading(true);
try {
const response = await projectsService.fetchList();
@ -87,8 +96,8 @@ export default class ProjectsStore {
siteId = siteIdFromPath;
} else if (!siteId || !siteExists) {
siteId = siteIds.includes(this.siteId)
? this.siteId
: response.data[0].projectId;
? this.siteId
: response.data[0].projectId;
}
const hasRecordings = this.list.some(site => site.recorded);
@ -109,6 +118,7 @@ export default class ProjectsStore {
}
save = async (projectData: Partial<Project>) => {
this.setLoading(true);
try {
const response = await projectsService.saveProject(projectData);
runInAction(() => {
@ -124,6 +134,8 @@ export default class ProjectsStore {
});
} catch (error) {
console.error('Failed to save site:', error);
} finally {
this.setLoading(false);
}
}
@ -139,5 +151,39 @@ export default class ProjectsStore {
}
}
}
}
removeProject = async (projectId: string) => {
this.setLoading(true);
try {
await projectsService.removeProject(projectId);
runInAction(() => {
this.list = this.list.filter(site => site.id !== projectId);
if (this.siteId === projectId) {
this.setSiteId(this.list[0].id!);
}
})
} catch (e) {
console.error('Failed to remove project:', e);
} finally {
this.setLoading(false);
}
}
updateProject = async (projectId: string, projectData: Partial<Project>) => {
this.setLoading(true);
try {
const response = await projectsService.updateProject(projectId, projectData);
runInAction(() => {
const updatedSite = new Project(response.data);
const index = this.list.findIndex(site => site.id === updatedSite.id);
if (index !== -1) {
this.list[index] = updatedSite;
}
});
} catch (error) {
console.error('Failed to update site:', error);
} finally {
this.setLoading
}
}
}

View file

@ -25,6 +25,14 @@ export default class Project {
makeAutoObservable(this);
}
exists = () => {
return !!this.id;
}
get validate() {
return this.name.length > 0;
}
edit = (data: Partial<Project>) => {
Object.keys(data).forEach((key) => {
if (key in this) {

View file

@ -24,4 +24,16 @@ export default class ProjectsService extends BaseService {
return await r.json();
}
}
removeProject = async (projectId: string) => {
const r = await this.client.delete(`/projects/${projectId}`)
return await r.json();
}
updateProject = async (projectId: string, projectData: any) => {
const r = await this.client.put(`/projects/${projectId}`, projectData);
return await r.json();
}
}