imaginerio/narratives

View on GitHub
src/pages/edit/[project].js

Summary

Maintainability
A
1 hr
Test Coverage
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useQuery, useMutation, gql } from '@apollo/client';
import axios from 'axios';
import ErrorPage from 'next/error';
import { Container, Grid, Dimmer, Loader } from 'semantic-ui-react';

import withApollo from '../../providers/withApollo';

import Head from '../../components/Head';
import Slides from '../../components/Slides';
import Editor from '../../components/Editor';
import EditorHeader from '../../components/Editor/EditorHeader';
import { DrawProvider } from '../../providers/DrawProvider';
import useProjectAuth from '../../providers/useProjectAuth';
import useLocale from '../../hooks/useLocale';

const GET_SLIDES = gql`
  query GetSlides($project: ID!) {
    Project(where: { id: $project }) {
      title
      slides(sortBy: order_ASC) {
        id
        title
        order
      }
    }
  }
`;

const ADD_SLIDE = gql`
  mutation AddSlide($project: ProjectRelateToOneInput, $order: Int) {
    createSlide(data: { project: $project, order: $order }) {
      id
      title
    }
  }
`;

const DELETE_SLIDE = gql`
  mutation deleteSlide($id: ID!) {
    deleteSlide(id: $id) {
      id
    }
  }
`;

const EDIT_SLIDE_ORDER = gql`
  mutation setOrder($data: [SlidesUpdateInput]) {
    updateSlides(data: $data) {
      id
      title
      order
    }
  }
`;

const EditPage = ({ project, statusCode }) => {
  if (statusCode) return <ErrorPage statusCode={statusCode} />;

  const [activeSlide, setActiveSlide] = useState(null);
  const [apiLoading, setApiLoading] = useState(false);

  const [addSlide] = useMutation(ADD_SLIDE);
  const [deleteSlide] = useMutation(DELETE_SLIDE);
  const [editSlideOrder] = useMutation(EDIT_SLIDE_ORDER);

  const { loadingText } = useLocale();

  const { loading, error, data, refetch } = useQuery(GET_SLIDES, {
    variables: { project },
    onCompleted: res => {
      if (res.Project.slides.length) {
        setActiveSlide(res.Project.slides[0].id);
      } else {
        // eslint-disable-next-line no-use-before-define
        newSlide();
      }
    },
  });

  const newSlide = slideId => {
    setApiLoading(true);
    let order;
    try {
      ({ order } = data.Project.slides.find(s => s.id === slideId));
    } catch {
      order = 1;
    }
    return addSlide({
      variables: {
        project: {
          connect: { id: project },
        },
        order,
      },
    }).then(async ({ data: { createSlide } }) => {
      await refetch();
      setActiveSlide(createSlide.id);
      setApiLoading(false);
    });
  };

  const duplicate = slideId => {
    setApiLoading(true);
    axios.get(`/duplicate/${slideId}`).then(async ({ data: { id } }) => {
      await refetch();
      setActiveSlide(id);
      setApiLoading(false);
    });
  };

  const removeSlide = id =>
    deleteSlide({
      variables: {
        id,
      },
    }).then(async () => {
      await refetch();
      setActiveSlide(data.Project.slides[0].id);
    });

  const updateSlideOrder = newData =>
    editSlideOrder({
      variables: {
        data: newData,
      },
      optimisticResponse: {
        __typename: 'Mutation',
        updateSlides: newData.map(d => ({
          __typename: 'Slide',
          id: d.id,
          title: d.data.title,
          order: d.data.order,
        })),
      },
      refetchQueries: [{ query: GET_SLIDES, variables: { project } }],
    });

  if (loading || !project)
    return (
      <Dimmer active>
        <Loader size="huge">{loadingText}</Loader>
      </Dimmer>
    );
  if (error) return <p>Error :(</p>;

  return (
    <Container fluid>
      <Head title={data.Project.title} />
      <Grid>
        <Grid.Row style={{ paddingBottom: 0, zIndex: 2 }}>
          <Grid.Column>
            <EditorHeader title={data.Project.title} project={project} />
          </Grid.Column>
        </Grid.Row>
        <Grid.Row style={{ paddingTop: 0, paddingBottom: 0 }}>
          <Dimmer active={apiLoading}>
            <Loader size="huge">{loadingText}</Loader>
          </Dimmer>
          <Grid.Column width={3}>
            <Slides
              slides={data.Project.slides}
              active={activeSlide}
              handler={setActiveSlide}
              onUpdate={updateSlideOrder}
              duplicate={duplicate}
              newSlide={newSlide}
              removeSlide={removeSlide}
            />
          </Grid.Column>
          <Grid.Column width={13} style={{ padding: 0 }}>
            <DrawProvider>{activeSlide && <Editor slide={activeSlide} />}</DrawProvider>
          </Grid.Column>
        </Grid.Row>
      </Grid>
    </Container>
  );
};

export default withApollo(EditPage);

EditPage.propTypes = {
  project: PropTypes.string.isRequired,
  statusCode: PropTypes.number,
};

EditPage.defaultProps = {
  statusCode: null,
};

export async function getServerSideProps({ req, params: { project } }) {
  const statusCode = await useProjectAuth({ req, project });
  return { props: { project, statusCode } };
}