teableio/teable

View on GitHub
apps/nextjs-app/src/features/app/blocks/space-setting/general/GeneralPage.tsx

Summary

Maintainability
A
1 hr
Test Coverage
/* eslint-disable jsx-a11y/no-autofocus */
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { hasPermission } from '@teable/core';
import { Edit } from '@teable/icons';
import { deleteSpace, getSpaceById, updateSpace } from '@teable/openapi';
import { ReactQueryKeys } from '@teable/sdk/config';
import { ConfirmDialog } from '@teable/ui-lib/base';
import { Button, Input } from '@teable/ui-lib/shadcn';
import { useRouter } from 'next/router';
import { Trans, useTranslation } from 'next-i18next';
import { useState } from 'react';
import { CopyButton } from '@/features/app/components/CopyButton';
import { spaceConfig } from '@/features/i18n/space.config';

export const GeneralPage = () => {
  const router = useRouter();
  const queryClient = useQueryClient();
  const { t } = useTranslation(spaceConfig.i18nNamespaces);
  const spaceId = router.query.spaceId as string;
  const [isEditing, setIsEditing] = useState(false);
  const [deleteConfirm, setDeleteConfirm] = useState(false);

  const { data: space } = useQuery({
    queryKey: ReactQueryKeys.space(spaceId),
    queryFn: ({ queryKey }) => getSpaceById(queryKey[1]).then((res) => res.data),
  });

  const { mutateAsync: updateSpaceMutator } = useMutation({
    mutationFn: updateSpace,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ReactQueryKeys.space(spaceId) });
    },
  });

  const { mutate: deleteSpaceMutator } = useMutation({
    mutationFn: deleteSpace,
    onSuccess: () => {
      router.push('/space');
    },
  });

  const onBlur = async (e: React.FocusEvent<HTMLInputElement, Element>) => {
    const value = e.target.value;
    if (!value || value === space?.name) {
      return setIsEditing(false);
    }
    await updateSpaceMutator({
      spaceId,
      updateSpaceRo: { name: value },
    });
    setIsEditing(false);
  };

  const onKeydown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      const value = e.currentTarget.value;
      if (!value || value === space?.name) {
        return setIsEditing(false);
      }
      await updateSpaceMutator({
        spaceId,
        updateSpaceRo: { name: value },
      });
      setIsEditing(false);
    }
  };

  return (
    <>
      <div className="h-screen w-full overflow-y-auto overflow-x-hidden">
        <div className="w-full px-8 py-6">
          <div className="border-b pb-4">
            <h1 className="text-3xl font-semibold">{t('space:spaceSetting.general')}</h1>
            {space && hasPermission(space.role, 'space|delete') && (
              <div className="mt-3 text-sm text-slate-500">
                {t('space:spaceSetting.generalDescription')}
              </div>
            )}
          </div>

          {!!space && (
            <div className="flex flex-col gap-y-2 py-4">
              <div className="flex justify-between">
                <h2 className="mb-2 text-xl font-semibold">{t('common:noun.space')}</h2>
                <Button variant="destructive" size="sm" onClick={() => setDeleteConfirm(true)}>
                  {t('actions.delete')}
                </Button>
              </div>
              <div className="flex h-8 items-center gap-x-1 text-sm">
                <span className="w-24 text-gray-500">{t('space:spaceSetting.spaceName')}</span>
                {isEditing ? (
                  <Input
                    defaultValue={space.name}
                    onBlur={onBlur}
                    onKeyDown={onKeydown}
                    autoFocus
                    className="h-8"
                  />
                ) : (
                  <>
                    <span>{space.name}</span>
                    {hasPermission(space.role, 'space|update') && (
                      <Button variant="ghost" size="xs" onClick={() => setIsEditing(true)}>
                        <Edit className="size-4 cursor-pointer text-gray-500" />
                      </Button>
                    )}
                  </>
                )}
              </div>
              <div className="flex h-8 items-center gap-x-1 text-sm">
                <span className="w-24 text-gray-500">{t('space:spaceSetting.spaceId')}</span>
                <span>{spaceId}</span>
                <CopyButton
                  variant="ghost"
                  text={spaceId}
                  size="xs"
                  iconClassName="size-4 text-gray-500"
                />
              </div>
            </div>
          )}
        </div>
      </div>
      <ConfirmDialog
        open={deleteConfirm}
        onOpenChange={setDeleteConfirm}
        title={
          <Trans ns="space" i18nKey={'tip.delete'}>
            {space?.name}
          </Trans>
        }
        cancelText={t('actions.cancel')}
        confirmText={t('actions.confirm')}
        onCancel={() => setDeleteConfirm(false)}
        onConfirm={() => space && deleteSpaceMutator(space.id)}
      />
    </>
  );
};