Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 5 additions & 130 deletions web/sdk/react/components/organization/general/index.tsx
Original file line number Diff line number Diff line change
@@ -1,135 +1,10 @@
'use client';

import { useMemo } from 'react';
import {
Button,
Tooltip,
Separator,
Skeleton,
Text,
Flex
} from '@raystack/apsara';
import { Outlet, useNavigate } from '@tanstack/react-router';
import { useFrontier } from '~/react/contexts/FrontierContext';
import { usePermissions } from '~/react/hooks/usePermissions';
import { PERMISSIONS, shouldShowComponent } from '~/utils';
import { GeneralOrganization } from './general.workspace';
import { AuthTooltipMessage } from '~/react/utils';
import { useTerminology } from '~/react/hooks/useTerminology';
import { PageHeader } from '~/react/components/common/page-header';
import sharedStyles from '../styles.module.css';
import { GeneralPage } from '~/react/views/general';

export default function GeneralSetting() {
const t = useTerminology();
const { activeOrganization: organization, isActiveOrganizationLoading } =
useFrontier();

const resource = `app/organization:${organization?.id}`;

const listOfPermissionsToCheck = useMemo(() => {
return [
{
permission: PERMISSIONS.UpdatePermission,
resource: resource
},
{
permission: PERMISSIONS.DeletePermission,
resource: resource
}
];
}, [resource]);

const { permissions, isFetching: isPermissionsFetching } = usePermissions(
listOfPermissionsToCheck,
!!organization?.id
);

const { canUpdateWorkspace, canDeleteWorkspace } = useMemo(() => {
return {
canUpdateWorkspace: shouldShowComponent(
permissions,
`${PERMISSIONS.UpdatePermission}::${resource}`
),
canDeleteWorkspace: shouldShowComponent(
permissions,
`${PERMISSIONS.DeletePermission}::${resource}`
)
};
}, [permissions, resource]);

const isLoading = isActiveOrganizationLoading || isPermissionsFetching;

return (
<Flex direction="column" style={{ width: '100%' }}>
<Flex direction="column" className={sharedStyles.container}>
<Flex
direction="row"
justify="between"
align="center"
className={sharedStyles.header}
>
<PageHeader
title="General"
description={`Basic configuration for the ${t.organization({
case: 'lower'
})}.`}
/>
</Flex>
<Flex direction="column" gap={9}>
<GeneralOrganization
organization={organization}
canUpdateWorkspace={canUpdateWorkspace}
isLoading={isLoading}
/>
<Separator />
<GeneralDeleteOrganization
isLoading={isLoading}
canDelete={canDeleteWorkspace}
/>
</Flex>
</Flex>
</Flex>
);
return <GeneralPage onDeleteSuccess={() => {
// @ts-ignore
window.location = window.location.origin;
}}/>;
}

export const GeneralDeleteOrganization = ({
isLoading,
canDelete
}: {
isLoading?: boolean;
canDelete: boolean;
}) => {
const t = useTerminology();
const navigate = useNavigate({ from: '/' });
return (
<>
<Flex direction="column" gap={5}>
{isLoading ? (
<Skeleton height={'16px'} width={'50%'} />
) : (
<Text size={3} variant="secondary">
If you want to permanently delete this{' '}
{t.organization({ case: 'lower' })} and all of its data.
</Text>
)}
{isLoading ? (
<Skeleton height={'32px'} width={'64px'} />
) : (
<Tooltip disabled={canDelete} message={AuthTooltipMessage}>
<Button
variant="solid"
color="danger"
type="submit"
onClick={() => navigate({ to: '/delete' })}
disabled={!canDelete}
data-test-id="frontier-sdk-delete-organization-btn"
>
Delete {t.organization({ case: 'lower' })}
</Button>
</Tooltip>
)}
<Outlet />
</Flex>
</>
);
};
9 changes: 1 addition & 8 deletions web/sdk/react/components/organization/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import Domain from './domain';
import { AddDomain } from './domain/add-domain';
import { VerifyDomain } from './domain/verify-domain';
import GeneralSetting from './general';
import { DeleteOrganization } from './general/delete';
import WorkspaceMembers from './members';
import { InviteMember } from './members/invite';
import UserPreferences from './preferences';
Expand Down Expand Up @@ -148,12 +147,6 @@ const indexRoute = createRoute({
component: GeneralSetting
});

const deleteOrgRoute = createRoute({
getParentRoute: () => indexRoute,
path: '/delete',
component: DeleteOrganization
});

const securityRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/security',
Expand Down Expand Up @@ -367,7 +360,7 @@ interface getRootTreeOptions {

export function getRootTree({ customScreens = [] }: getRootTreeOptions) {
return rootRoute.addChildren([
indexRoute.addChildren([deleteOrgRoute]),
indexRoute,
securityRoute,
sessionsRoute.addChildren([revokeSessionRoute]),
membersRoute.addChildren([inviteMemberRoute, removeMemberRoute]),
Expand Down
7 changes: 7 additions & 0 deletions web/sdk/react/components/organization/shared/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type OnNavigate = (to: string, params?: Record<string, unknown>) => void;

export interface BasePageProps {
organizationId: string;
onNavigate?: OnNavigate;
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from '@raystack/apsara';

import { yupResolver } from '@hookform/resolvers/yup';
import { useNavigate } from '@tanstack/react-router';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { useFrontier } from '~/react/contexts/FrontierContext';
Expand All @@ -30,7 +29,17 @@ const orgSchema = yup
})
.required();

export const DeleteOrganization = () => {
export interface DeleteOrganizationDialogProps {
open: boolean;
onOpenChange: (value: boolean) => void;
onDeleteSuccess?: () => void;
}

export const DeleteOrganizationDialog = ({
open,
onOpenChange,
onDeleteSuccess
}: DeleteOrganizationDialogProps) => {
const {
watch,
handleSubmit,
Expand All @@ -40,7 +49,6 @@ export const DeleteOrganization = () => {
} = useForm({
resolver: yupResolver(orgSchema)
});
const navigate = useNavigate({ from: '/delete' });
const t = useTerminology();
const { activeOrganization: organization } = useFrontier();
const { mutateAsync: deleteOrganization } = useMutation(
Expand All @@ -62,8 +70,7 @@ export const DeleteOrganization = () => {
await deleteOrganization(req);
toast.success(`${t.organization({ case: 'capital' })} deleted`);

// @ts-ignore
window.location = window.location.origin;
onDeleteSuccess?.();
} catch (error: any) {
toast.error('Something went wrong', {
description:
Expand All @@ -75,14 +82,13 @@ export const DeleteOrganization = () => {

const title = watch('title', '');
return (
<Dialog open={true}>
<Dialog open={open} onOpenChange={onOpenChange}>
<Dialog.Content overlayClassName={styles.overlay} width={600}>
<Dialog.Header>
<Dialog.Title>
Verify {t.organization({ case: 'lower' })} deletion
</Dialog.Title>
<Dialog.CloseButton
onClick={() => navigate({ to: '/' })}
data-test-id="frontier-sdk-delete-organization-close-btn"
/>
</Dialog.Header>
Expand Down Expand Up @@ -139,3 +145,4 @@ export const DeleteOrganization = () => {
</Dialog>
);
};

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
} from '@raystack/proton/frontier';
import { create } from '@bufbuild/protobuf';
import { AuthTooltipMessage } from '~/react/utils';
import { AvatarUpload } from '../../avatar-upload';
import { AvatarUpload } from '~/react/components/avatar-upload';
import { getInitials } from '~/utils';
import { useTerminology } from '~/react/hooks/useTerminology';
import styles from './general.module.css';
Expand All @@ -41,7 +41,7 @@ const generalSchema = yup

type FormData = yup.InferType<typeof generalSchema>;

interface GeneralOrganizationProps {
export interface GeneralOrganizationProps {
organization?: Organization;
isLoading?: boolean;
canUpdateWorkspace?: boolean;
Expand Down Expand Up @@ -201,3 +201,4 @@ export const GeneralOrganization = ({
</form>
);
};

127 changes: 127 additions & 0 deletions web/sdk/react/views/general/general-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
'use client';

import { useMemo, useState } from 'react';
import {
Button,
Tooltip,
Separator,
Skeleton,
Text,
Flex
} from '@raystack/apsara';
import { useFrontier } from '~/react/contexts/FrontierContext';
import { usePermissions } from '~/react/hooks/usePermissions';
import { PERMISSIONS, shouldShowComponent } from '~/utils';
import { GeneralOrganization } from './general-organization';
import { AuthTooltipMessage } from '~/react/utils';
import { useTerminology } from '~/react/hooks/useTerminology';
import { PageHeader } from '~/react/components/common/page-header';
import { DeleteOrganizationDialog } from './delete-organization-dialog';
import sharedStyles from '../../components/organization/styles.module.css';

export interface GeneralPageProps {
onDeleteSuccess?: () => void;
}

export function GeneralPage({ onDeleteSuccess }: GeneralPageProps = {}) {
const t = useTerminology();
const { activeOrganization: organization, isActiveOrganizationLoading } =
useFrontier();

const resource = `app/organization:${organization?.id}`;

const listOfPermissionsToCheck = useMemo(() => {
return [
{
permission: PERMISSIONS.UpdatePermission,
resource: resource
},
{
permission: PERMISSIONS.DeletePermission,
resource: resource
}
];
}, [resource]);

const { permissions, isFetching: isPermissionsFetching } = usePermissions(
listOfPermissionsToCheck,
!!organization?.id
);

const { canUpdateWorkspace, canDeleteWorkspace } = useMemo(() => {
return {
canUpdateWorkspace: shouldShowComponent(
permissions,
`${PERMISSIONS.UpdatePermission}::${resource}`
),
canDeleteWorkspace: shouldShowComponent(
permissions,
`${PERMISSIONS.DeletePermission}::${resource}`
)
};
}, [permissions, resource]);

const isLoading = isActiveOrganizationLoading || isPermissionsFetching;

const [showDeleteDialog, setShowDeleteDialog] = useState(false);

return (
<Flex direction="column" style={{ width: '100%' }}>
<Flex direction="column" className={sharedStyles.container}>
<Flex
direction="row"
justify="between"
align="center"
className={sharedStyles.header}
>
<PageHeader
title="General"
description={`Basic configuration for the ${t.organization({
case: 'lower'
})}.`}
/>
</Flex>
<Flex direction="column" gap={9}>
<GeneralOrganization
organization={organization}
canUpdateWorkspace={canUpdateWorkspace}
isLoading={isLoading}
/>
<Separator />
<Flex direction="column" gap={5}>
{isLoading ? (
<Skeleton height={'16px'} width={'50%'} />
) : (
<Text size={3} variant="secondary">
If you want to permanently delete this{' '}
{t.organization({ case: 'lower' })} and all of its data.
</Text>
)}
{isLoading ? (
<Skeleton height={'32px'} width={'64px'} />
) : (
<Tooltip disabled={canDeleteWorkspace} message={AuthTooltipMessage}>
<Button
variant="solid"
color="danger"
type="submit"
onClick={() => setShowDeleteDialog(true)}
disabled={!canDeleteWorkspace}
data-test-id="frontier-sdk-delete-organization-btn"
>
Delete {t.organization({ case: 'lower' })}
</Button>
Comment on lines +104 to +113
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Delete button should use type="button", not type="submit".

This button is not inside a <form> — it opens a dialog via onClick. Using type="submit" can cause unintended form submission if this component is ever rendered inside an ancestor <form> element.

Proposed fix
                <Button
                  variant="solid"
                  color="danger"
-                  type="submit"
+                  type="button"
                  onClick={() => setShowDeleteDialog(true)}
                  disabled={!canDeleteWorkspace}
                  data-test-id="frontier-sdk-delete-organization-btn"
                >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button
variant="solid"
color="danger"
type="submit"
onClick={() => setShowDeleteDialog(true)}
disabled={!canDeleteWorkspace}
data-test-id="frontier-sdk-delete-organization-btn"
>
Delete {t.organization({ case: 'lower' })}
</Button>
<Button
variant="solid"
color="danger"
type="button"
onClick={() => setShowDeleteDialog(true)}
disabled={!canDeleteWorkspace}
data-test-id="frontier-sdk-delete-organization-btn"
>
Delete {t.organization({ case: 'lower' })}
</Button>

</Tooltip>
)}
<DeleteOrganizationDialog
open={showDeleteDialog}
onOpenChange={setShowDeleteDialog}
onDeleteSuccess={onDeleteSuccess}
/>
</Flex>
</Flex>
</Flex>
</Flex>
);
}

Loading
Loading