All files / src/components/views/Schemas/CreateSchema AttributesStep.tsx

0% Statements 0/32
0% Branches 0/21
0% Functions 0/6
0% Lines 0/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126                                                                                                                                                                                                                                                           
import * as React from "react";
import { Box, Button, Flash, Form } from "rimble-ui";
import { colors } from "../../../../themes";
import { WorkingSchema, newSchemaAttribute, defaultSchemaProperties } from "../types";
import { SchemaAttribute } from "./SchemaAttribute";
 
export interface AttributesStepProps {
  schema: WorkingSchema;
  updateSchema(updates: Partial<WorkingSchema>): void;
  onComplete(): void;
}
 
export const AttributesStep: React.FunctionComponent<AttributesStepProps> = (props) => {
  const { schema, updateSchema } = props;
  const [doValidation, setDoValidation] = React.useState(false);
  const [error, setError] = React.useState("");
 
  function addAttribute(e: Event) {
    if (schema.properties?.credentialSubject?.properties?.[""]) {
      // We have a property with no name - have them update that before creating a no one. We don't have to do anything, since the un-preventDefaulted event will trigger browser UI on empty required title field
      return;
    }
 
    e.preventDefault();
 
    updateSchema({
      ...schema,
      properties: {
        ...schema.properties,
        credentialSubject: {
          ...schema.properties?.credentialSubject,
          properties: {
            ...schema.properties?.credentialSubject?.properties,
            "": { ...newSchemaAttribute },
          },
        },
      },
    });
  }
 
  function goNext(e: Event) {
    e.preventDefault();
    setError("");
 
    const propertyIds = new Set();
    let missingField = false;
    let duplicateId = false;
    let invalidId = false;
    // @TODO/tobek These checks should be recursive. Empty `title`'s are already handled by native browser form checks, but `$linkedData.term` collisions (which would have to be unique only among sibling properties) are not handled.
    Object.entries(schema.properties?.credentialSubject?.properties || {}).forEach(([key, prop]) => {
      if (key === "") {
        missingField = true;
      }
 
      if (prop.$linkedData?.term) {
        if (prop.$linkedData?.term === "type") {
          setError(
            `An attribute name result in ID "${prop.$linkedData.term}" - this is a reserved keyword in the JSON-LD specification that VCs use. Please use a different name.`,
          );
          invalidId = true;
        } else if (propertyIds.has(prop.$linkedData.term)) {
          setError(
            `Two attribute names result in ID "${prop.$linkedData.term}" - all attributes must have unique IDs.`,
          );
          duplicateId = true;
        } else {
          propertyIds.add(prop.$linkedData.term);
        }
      }
    });
 
    if (missingField || duplicateId || invalidId) {
      setDoValidation(true);
    } else {
      setDoValidation(false);
      props.onComplete();
    }
  }
 
  return (
    <Form validated={doValidation} onSubmit={goNext}>
      {defaultSchemaProperties.map((prop, i) => (
        <SchemaAttribute key={i + "required"} attr={prop} readOnly={true} parentRequired={["issuanceDate", "issuer"]} />
      ))}
 
      {schema.properties?.credentialSubject && (
        <SchemaAttribute
          key={"credentialSubject"}
          attr={schema.properties.credentialSubject}
          isCredSubject
          updateAttribute={(attr) => {
            updateSchema({
              ...schema,
              properties: {
                ...schema.properties,
                credentialSubject: attr,
              },
            });
          }}
        />
      )}
 
      <Box mt={3}>
        <Button.Outline
          mb={3}
          mx="auto"
          type="submit"
          width="100%"
          onClick={addAttribute}
          style={{ borderColor: colors.primary.base }}
        >
          Add Another Attribute
        </Button.Outline>
        {error && (
          <Flash mb={3} variant="danger">
            Error: {error}
          </Flash>
        )}
        <Button type="submit" width="100%">
          Review
        </Button>
      </Box>
    </Form>
  );
};