components/CreateCredentialProp/index.tsx

import { FC, useState, useEffect, ChangeEvent } from "react"

import {
	Typography,
	Accordion,
	AccordionDetails,
	AccordionSummary,
	TextField,
	Grid,
} from "@material-ui/core"

import Autocomplete from "@material-ui/lab/Autocomplete"

import useStyles from "./styles"

import ExpandMoreIcon from "@material-ui/icons/ExpandMore"

import { useSelector } from "react-redux"
import { RootState } from "../../redux/store"

import { translate } from "../../lang"

import { AccessCredentialPropT, CompanyT } from "../../misc/types"

import EditCodes from "../EditCodes"

type LayoutOptions = "text field" | "select option" | "multiline" | "multiple codes" | "sqa"

type ExportEdits = {
	mainText: string
	secondText: string
	accessCredentialProp: AccessCredentialPropT
	editingOption: "question" | "answer" | ""
}

type Props = {
	layout: LayoutOptions
	label: string
	accessCredentialProp: AccessCredentialPropT
	editCredentialProp: (edits: ExportEdits) => void
	isMandatory?: boolean
	defaultExpanded?: boolean
	companies?: CompanyT[]
	maxChar?: number
	isCrypto?: boolean
}

/**
 * @alias CreateCredentialProp
 *
 * @description This component will be the card that shows the suer the inputs they must / should complete to register their credential, and will update the global state, where the credential is.
 *
 *@property {"text field" | "select option" | "multiline" | "multiple codes" | "sqa"} layoutOptions All the possible inputs for the credential props
 *
 * @property {string} label The input's label
 *
 * @property {AccessCredntialPropT} accessCredentialProp The name of the property the user will be editing
 * 
 * @property {function} editCredentialProp This function will dispatch an event to update the state of the credencial when the user changes anything
 *
 * @property {boolean} [isMandatory] If the user MUST complete this input
 *
 * @property {boolean} [defaultExpanded] If the card is open before the user does anything
 *
 * @property {CompanyT[]} [companies] An array of all the available companies for the user to select
 *
 * @property {number} maxChar The max amount of characters per input
 *
 * @property {boolean} isCrypto If the input is for crypto currency access words
 *
 * @example
 * 
 * <CreateCredentialProp
		label="your security question & answer"
		layout="sqa"
		maxChar={191}
		accessCredentialProp="security_question"
		editCredentialProp={dispatchEditCredential}
	/>

	@example
	<CreateCredentialProp
		layout="select option"
		companies={["", ""]}
		label="select one or write your own"
		isMandatory
		defaultExpanded
		maxChar={50}
		accessCredentialProp="company_name"
		editCredentialProp={dispatchEditCredential}
	/>
 */

const CreateCredentialProp: FC<Props> = (props) => {
	const { lng } = useSelector((state: RootState) => state.lng)

	const {
		layout,
		label,
		isMandatory,
		defaultExpanded,
		companies,
		maxChar,
		isCrypto,
		accessCredentialProp,
		editCredentialProp,
	} = props

	const [mainCharCount, setMainCharCount] = useState(0)

	const [secondCharCount, setSecondCharCount] = useState(0)

	const [mainText, setMainText] = useState("")

	const [secondText, setSecondText] = useState("")

	const [editingOption, setEditingOption] = useState<"question" | "answer" | "">("")

	const classes = useStyles()

	const handleChange = (event: ChangeEvent<{ value: unknown }>) => {
		const target = event.target as HTMLInputElement

		const inputType = target.getAttribute("type")

		const variant = target.getAttribute("variant")

		switch (inputType) {
			case "text field":
				setMainText(target.value)

				setMainCharCount(target.value.length)
				break

			case "select option":
				setMainText(target.value)

				setMainCharCount(target.value.length)
				break

			case "multiline":
				setMainText(target.value)

				setMainCharCount(target.value.length)
				break

			case "sqa":
				if (variant && variant === "security question") {
					setMainText(target.value)

					setMainCharCount(target.value.length)

					setEditingOption("question")
				}

				if (variant && variant === "security answer") {
					setSecondText(target.value)

					setSecondCharCount(target.value.length)

					setEditingOption("answer")
				}

				break

			default:
				break
		}
	}

	useEffect(() => {
		const edits: ExportEdits = {
			mainText,
			secondText,
			accessCredentialProp,
			editingOption,
		}

		editCredentialProp(edits)
	}, [mainText, secondText])

	const renderBody = (option: LayoutOptions) => {
		switch (option) {
			case "text field":
				return (
					<>
						<TextField
							label={label}
							variant="outlined"
							fullWidth
							className={classes.textColor}
							InputProps={{
								classes: {
									input: classes.textColor,
								},
							}}
							inputProps={{
								type: layout,
								"data-testid": "test_text_field",
							}}
							value={mainText}
							onChange={handleChange}
						/>
						{maxChar && (
							<Typography variant="body1" data-testid="test_max_char">
								{mainCharCount} / {maxChar}
							</Typography>
						)}
					</>
				)

			case "select option":
				let options: CompanyT[]

				if (companies) {
					options = companies.sort(
						(a, b) => -b.name.charAt(0).localeCompare(a.name.charAt(0))
					)
				} else {
					options = [
						{
							id: 0,
							name: "",
							url_logo: "",
						},
					]
				}

				return (
					<>
						<Autocomplete
							freeSolo
							data-testid="test_select_option"
							fullWidth
							options={options}
							groupBy={(option) => option.name.charAt(0)}
							getOptionLabel={(option) => option.name}
							id="autocomplete-input"
							renderInput={(params) => (
								<TextField
									{...params}
									label={label}
									variant="outlined"
									value={mainText}
									onChange={handleChange}
									InputProps={{
										...params.InputProps,
										type: layout,
									}}
								/>
							)}
						/>
						{maxChar && (
							<Typography variant="body1">
								{mainCharCount} / {maxChar}
							</Typography>
						)}
					</>
				)

			case "multiline":
				return (
					<>
						<TextField
							label={label}
							variant="outlined"
							fullWidth
							multiline
							className={classes.textColor}
							InputProps={{
								classes: {
									input: classes.textColor,
								},
							}}
							inputProps={{
								type: layout,
								"data-testid": "test_multiline",
							}}
							value={mainText}
							onChange={handleChange}
						/>
						{maxChar && (
							<Typography variant="body1">
								{mainCharCount} / {maxChar}
							</Typography>
						)}
					</>
				)

			case "multiple codes":
				return <EditCodes codes={[""]} option={2} isCrypto={isCrypto ? isCrypto : false} />

			case "sqa":
				return (
					<>
						<Grid item xs={12}>
							<TextField
								label={translate("encryption_examples", lng, 5)}
								variant="outlined"
								fullWidth
								className={classes.textColor}
								InputProps={{
									classes: {
										input: classes.textColor,
									},
								}}
								inputProps={{
									type: layout,
									variant: "security question",
									"data-testid": "test_sqa_question",
								}}
								value={mainText}
								onChange={handleChange}
							/>
							{maxChar && (
								<Typography variant="body1">
									{mainCharCount} / {maxChar}
								</Typography>
							)}
						</Grid>
						<Grid item xs={12}>
							<TextField
								label={translate("encryption_examples", lng, 6)}
								variant="outlined"
								fullWidth
								className={classes.textColor}
								InputProps={{
									classes: {
										input: classes.textColor,
									},
								}}
								inputProps={{
									type: layout,
									variant: "security answer",
									"data-testid": "test_sqa_answer",
								}}
								value={secondText}
								onChange={handleChange}
							/>
							{maxChar && (
								<Typography variant="body1">
									{secondCharCount} / {maxChar}
								</Typography>
							)}
						</Grid>
					</>
				)

			default:
				return null
		}
	}

	//don't know why, but the class "borderRadius" doesn't apply the border radius, but the style does...
	return (
		<>
			<Accordion
				defaultExpanded={defaultExpanded}
				data-testid="test_accordion"
				style={{ borderRadius: 8 }}
			>
				<AccordionSummary expandIcon={<ExpandMoreIcon />}>
					<Typography className={classes.heading}>{label}</Typography>
					<Typography className={classes.secondaryHeading} data-testid="test_mandatory">
						{translate("auth_form_texts", lng, isMandatory ? 16 : 15)}
					</Typography>
				</AccordionSummary>
				<AccordionDetails>
					<Grid container spacing={4}>
						{layout === "sqa" ? (
							renderBody(layout)
						) : (
							<Grid item xs={12}>
								{renderBody(layout)}
							</Grid>
						)}
					</Grid>
				</AccordionDetails>
			</Accordion>
		</>
	)
}

export default CreateCredentialProp