diff --git a/web/lib/client/hooks/use-unsaved-changes.tsx b/web/lib/client/hooks/use-unsaved-changes.tsx index 78723f7e7..33a16c359 100644 --- a/web/lib/client/hooks/use-unsaved-changes.tsx +++ b/web/lib/client/hooks/use-unsaved-changes.tsx @@ -6,6 +6,7 @@ import { useRevalidator, } from '@remix-run/react'; import { + ReactNode, createContext, useCallback, useContext, @@ -13,7 +14,6 @@ import { useMemo, useState, } from 'react'; -import { ChildrenProps } from '~/components/types'; import Popup from '~/components/molecule/popup'; import { useReload } from '../helpers/reloader'; @@ -41,7 +41,13 @@ const UnsavedChanges = createContext<{ loading: false, }); -export const UnsavedChangesProvider = ({ children }: ChildrenProps) => { +export const UnsavedChangesProvider = ({ + children, + onProceed, +}: { + children?: ReactNode; + onProceed?: (props: { setPerformAction?: (action: string) => void }) => void; +}) => { const [hasChanges, setHasChanges] = useState(false); const [reload, setReload] = useState(false); const [ignorePaths, setIgnorePaths] = useState([]); @@ -139,13 +145,23 @@ export const UnsavedChangesProvider = ({ children }: ChildrenProps) => { proceed?.()} + onClick={() => { + proceed?.(); + onProceed?.({ setPerformAction }); + }} /> ); }; + +export const DISCARD_ACTIONS = { + DISCARD_CHANGES: 'discard-changes', + VIEW_CHANGES: 'view-changes', + INIT: 'init', +}; + export const useUnsavedChanges = () => { return useContext(UnsavedChanges); }; diff --git a/web/src/apps/console/hooks/use-is-owner.tsx b/web/src/apps/console/hooks/use-is-owner.tsx new file mode 100644 index 000000000..936cd2c9d --- /dev/null +++ b/web/src/apps/console/hooks/use-is-owner.tsx @@ -0,0 +1,35 @@ +import { useCallback } from 'react'; +import useCustomSwr from '~/root/lib/client/hooks/use-custom-swr'; +import { useConsoleApi } from '../server/gql/api-provider'; + +export const useIsOwner = ({ accountName }: { accountName: string }) => { + const api = useConsoleApi(); + const { data: teamMembers, isLoading: teamMembersLoading } = useCustomSwr( + `${accountName}-owners`, + async () => { + return api.listMembershipsForAccount({ + accountName, + }); + } + ); + + const { data: currentUser, isLoading: currentUserLoading } = useCustomSwr( + 'current-user', + async () => { + return api.whoAmI(); + } + ); + + const isOwner = useCallback(() => { + if (!teamMembers || !currentUser) return false; + + const owner = teamMembers.find((member) => member.role === 'account_owner'); + + return owner?.user?.email === currentUser?.email; + }, [teamMembers, currentUser]); + + return { + isOwner: isOwner(), + isLoading: teamMembersLoading || currentUserLoading, + }; +}; diff --git a/web/src/apps/console/page-components/app/compute.tsx b/web/src/apps/console/page-components/app/compute.tsx index 0682a7365..5df9b3a23 100644 --- a/web/src/apps/console/page-components/app/compute.tsx +++ b/web/src/apps/console/page-components/app/compute.tsx @@ -7,7 +7,10 @@ import ExtendedFilledTab from '~/console/components/extended-filled-tab'; import { useAppState } from '~/console/page-components/app-states'; import { FadeIn, parseValue } from '~/console/page-components/util'; import useForm, { dummyEvent } from '~/root/lib/client/hooks/use-form'; -import { useUnsavedChanges } from '~/root/lib/client/hooks/use-unsaved-changes'; +import { + DISCARD_ACTIONS, + useUnsavedChanges, +} from '~/root/lib/client/hooks/use-unsaved-changes'; import Yup from '~/root/lib/server/helpers/yup'; import appInitialFormValues, { mapFormValuesToApp } from './app-utils'; import { plans } from './datas'; @@ -79,7 +82,7 @@ const AppCompute = ({ mode = 'new' }: { mode: 'edit' | 'new' }) => { mapFormValuesToApp({ appIn: val, oldAppIn: s, - }) + }), ); }, }); @@ -97,7 +100,7 @@ const AppCompute = ({ mode = 'new' }: { mode: 'edit' | 'new' }) => { }, [values, mode]); useEffect(() => { - if (performAction === 'discard-changes') { + if (performAction === DISCARD_ACTIONS.DISCARD_CHANGES) { resetValues(); } }, [performAction]); @@ -167,7 +170,7 @@ const AppCompute = ({ mode = 'new' }: { mode: 'edit' | 'new' }) => { handleChange('selectedPlan')(dummyEvent(v.value)); handleChange('memPerCpu')(dummyEvent(v.memoryPerCpu)); handleChange('cpuMode')( - dummyEvent(v.isShared ? 'shared' : 'dedicated') + dummyEvent(v.isShared ? 'shared' : 'dedicated'), ); }} /> diff --git a/web/src/apps/console/page-components/app/general.tsx b/web/src/apps/console/page-components/app/general.tsx index e01f491ac..6a18aaf23 100644 --- a/web/src/apps/console/page-components/app/general.tsx +++ b/web/src/apps/console/page-components/app/general.tsx @@ -16,7 +16,10 @@ import { ensureAccountClientSide } from '~/console/server/utils/auth-utils'; import { constants } from '~/console/server/utils/constants'; import useDebounce from '~/root/lib/client/hooks/use-debounce'; import useForm, { dummyEvent } from '~/root/lib/client/hooks/use-form'; -import { useUnsavedChanges } from '~/root/lib/client/hooks/use-unsaved-changes'; +import { + DISCARD_ACTIONS, + useUnsavedChanges, +} from '~/root/lib/client/hooks/use-unsaved-changes'; import Yup from '~/root/lib/server/helpers/yup'; import { handleError } from '~/root/lib/utils/common'; @@ -119,7 +122,7 @@ const AppGeneral = ({ mode = 'new' }: { mode: 'edit' | 'new' }) => { setImageLoaded(false); } }, - [] + [], ); useEffect(() => { @@ -133,7 +136,7 @@ const AppGeneral = ({ mode = 'new' }: { mode: 'edit' | 'new' }) => { } }, 300, - [imageSearchText] + [imageSearchText], ); const { @@ -176,7 +179,7 @@ const AppGeneral = ({ mode = 'new' }: { mode: 'edit' | 'new' }) => { displayName: Yup.string().required(), imageUrl: Yup.string().matches( constants.dockerImageFormatRegex, - 'Invalid image format' + 'Invalid image format', ), manualRepo: Yup.string().when( ['imageUrl', 'imageMode'], @@ -189,7 +192,7 @@ const AppGeneral = ({ mode = 'new' }: { mode: 'edit' | 'new' }) => { return schema.required().matches(regex, 'Invalid image format'); } return schema; - } + }, ), imageMode: Yup.string().required(), source: Yup.object() @@ -242,7 +245,7 @@ const AppGeneral = ({ mode = 'new' }: { mode: 'edit' | 'new' }) => { }, [values, mode]); useEffect(() => { - if (performAction === 'discard-changes') { + if (performAction === DISCARD_ACTIONS.DISCARD_CHANGES) { // if (app.ciBuildId) { // setIsEdited(false); // } diff --git a/web/src/apps/console/routes/_main+/$account+/env+/$environment+/app+/$app+/settings+/_layout.tsx b/web/src/apps/console/routes/_main+/$account+/env+/$environment+/app+/$app+/settings+/_layout.tsx index 3abb38b30..579cfcf8e 100644 --- a/web/src/apps/console/routes/_main+/$account+/env+/$environment+/app+/$app+/settings+/_layout.tsx +++ b/web/src/apps/console/routes/_main+/$account+/env+/$environment+/app+/$app+/settings+/_layout.tsx @@ -18,6 +18,7 @@ import { constants } from '~/console/server/utils/constants'; import { useReload } from '~/lib/client/helpers/reloader'; import useForm from '~/lib/client/hooks/use-form'; import { + DISCARD_ACTIONS, UnsavedChangesProvider, useUnsavedChanges, } from '~/lib/client/hooks/use-unsaved-changes'; @@ -177,7 +178,7 @@ const Layout = () => { } toast.success('App updated successfully'); // @ts-ignore - setPerformAction('init'); + setPerformAction(DISCARD_ACTIONS.INIT); if (!gitMode) { // @ts-ignore setBuildData(null); @@ -221,7 +222,7 @@ const Layout = () => { }, [rootContext.app]); useEffect(() => { - if (performAction === 'discard-changes') { + if (performAction === DISCARD_ACTIONS.DISCARD_CHANGES) { setApp(rootContext.app); setReadOnlyApp(rootContext.app); // @ts-ignore @@ -237,7 +238,7 @@ const Layout = () => { 'min-w-[1000px]': showDiff, 'min-w-[500px]': !showDiff, })} - show={performAction === 'view-changes'} + show={performAction === DISCARD_ACTIONS.VIEW_CHANGES} onOpenChange={(v) => setPerformAction(v)} > Commit Changes @@ -302,7 +303,11 @@ const Settings = () => { const rootContext = useOutletContext(); return ( - + { + setPerformAction?.(DISCARD_ACTIONS.DISCARD_CHANGES); + }} + > diff --git a/web/src/apps/console/routes/_main+/$account+/env+/$environment+/new-app/app-environment-variables.tsx b/web/src/apps/console/routes/_main+/$account+/env+/$environment+/new-app/app-environment-variables.tsx index f1dcbd587..05ae53218 100644 --- a/web/src/apps/console/routes/_main+/$account+/env+/$environment+/new-app/app-environment-variables.tsx +++ b/web/src/apps/console/routes/_main+/$account+/env+/$environment+/new-app/app-environment-variables.tsx @@ -21,6 +21,10 @@ import NoResultsFound from '~/console/components/no-results-found'; import { IShowDialog } from '~/console/components/types.d'; import { useAppState } from '~/console/page-components/app-states'; import useForm from '~/root/lib/client/hooks/use-form'; +import { + DISCARD_ACTIONS, + useUnsavedChanges, +} from '~/root/lib/client/hooks/use-unsaved-changes'; import Yup from '~/root/lib/server/helpers/yup'; import { NonNullableString } from '~/root/lib/types/common'; import AppDialog from './app-dialogs'; @@ -205,9 +209,11 @@ const EnvironmentVariablesList = ({ }; export const EnvironmentVariables = () => { - const { setContainer, getContainer } = useAppState(); + const { setContainer, getContainer, getReadOnlyContainer, readOnlyApp } = + useAppState(); const [showCSDialog, setShowCSDialog] = useState(null); + const { performAction } = useUnsavedChanges(); const entry = Yup.object({ type: Yup.string().oneOf(['config', 'secret']).notRequired(), @@ -231,10 +237,16 @@ export const EnvironmentVariables = () => { .notRequired(), }); - const { values, setValues, submit } = useForm({ - initialValues: getContainer().env, + const { + values, + setValues, + submit, + resetValues: reset, + } = useForm({ + initialValues: getReadOnlyContainer().env || null, validationSchema: Yup.array(entry), onSubmit: (val) => { + // @ts-ignore setContainer((c) => ({ ...c, env: val, @@ -261,6 +273,7 @@ export const EnvironmentVariables = () => { }; const removeEntry = (val: IEnvVariable) => { + // @ts-ignore setValues((v) => { const nv = v?.filter((v) => v.key !== val.key); return nv; @@ -334,6 +347,25 @@ export const EnvironmentVariables = () => { }, }); + useEffect(() => { + if (performAction === DISCARD_ACTIONS.DISCARD_CHANGES) { + // if (app.ciBuildId) { + // setIsEdited(false); + // } + reset(); + // @ts-ignore + // setBuildData(readOnlyApp?.build); + } + + // else if (performAction === 'init') { + // setIsEdited(false); + // } + }, [performAction]); + + useEffect(() => { + reset(); + }, [readOnlyApp]); + return ( <>
{ setIgnorePaths( navItems.map( (ni) => - `/${account}/deviceblueprint/${deviceBlueprintName}/app/${appName}/settings/${ni.value}` - ) + `/${account}/deviceblueprint/${deviceBlueprintName}/app/${appName}/settings/${ni.value}`, + ), ); }, []); @@ -110,7 +111,7 @@ const Layout = () => { }, [rootContext.app]); useEffect(() => { - if (performAction === 'discard-changes') { + if (performAction === DISCARD_ACTIONS.DISCARD_CHANGES) { setApp(rootContext.app); setPerformAction(''); } @@ -120,7 +121,7 @@ const Layout = () => { setPerformAction(v)} > Review Changes