components/Sections/ShowCredential/index.tsx

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

import { Grid, Typography } from "@material-ui/core"

import useStyles from "./styles"

import { useSelector, useDispatch } from "react-redux"
import { RootState } from "../../../redux/store"
import {
	initializeCredential,
	setDecryptedCredential,
} from "../../../redux/actions/credentialActions"
import { showError } from "../../../redux/actions/errorHandlingActions"
import { setErrorLoading, toggleLoading } from "../../../redux/actions/loadingActions"

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

import { findCredential, putCredential } from "../../../misc/indexedDB"
import { calcMaxChar, getUserAgent } from "../../../misc/staticData"
import { AccessCredentialPropT, ApiCallI, CharSizesT, ReduxCredentialT } from "../../../misc/types"
import { callApi } from "../../../misc/ajaxManager"

import UnlockData from "../../UnlockData"
import DisplayData from "../../DisplayData"
import GoBackBtn from "../../GoBackBtn"
import DeleteCredential from "../../DeleteCredential"
import Snackbar from "../../Snackbar"
import VisualizeCredentialProp from "../../VisualizeCredentialProp"

type Props = {
	getDecryptedCredential: (decrypted: true, agent: string) => Promise<boolean | undefined>
}

/**
 * @alias Section_ShowCredential
 *
 * @description This is the section that will show all the properties that a credential has, one each, using the {@link VisualizeCredentialProp} component
 *
 * @property {Function} getDecryptedCredential This function receives 2 params: 1- decrypted: true alweys, 2- agent: string (the user agent which is obtained inside of this component)
 */

const ShowCredential: FC<Props> = ({ getDecryptedCredential }) => {
	const { lng } = useSelector((state: RootState) => state.lng)
	const { credential } = useSelector((state: RootState) => state.credential)
	const { token } = useSelector((state: RootState) => state.token)

	const { REACT_APP_ENV_LOCAL } = process.env

	const [locked, setLocked] = useState(true)

	const [visible, setVisible] = useState(false)

	const [isAuthenticated, setIsAuthenticated] = useState(false)

	const [showSnackbar, setShowSnackbar] = useState(false)

	const [snackbarMessage, setSnackbarMessage] = useState("")

	const classes = useStyles()

	const dispatch = useDispatch()

	useEffect(() => {
		setIsAuthenticated(!locked)

		if (showSnackbar) {
			const timer = setTimeout(() => {
				setShowSnackbar(false)
			}, 12500)

			return () => {
				clearTimeout(timer)
			}
		}
	}, [locked, showSnackbar])

	const obtainCredential = async (id: number) => {
		const data = await findCredential(id)

		if (data === undefined) {
			return undefined
		}

		return data
	}

	const toggleVisibility = () => {
		if (!token) return

		if (!locked && visible) {
			setLocked(true)
		}

		if (!visible) {
			dispatch(toggleLoading(true))

			const request: ApiCallI = {
				lng,
				token,
				method: "POST",
				endpoint: "/auth/grant-access",
				body: {
					accessTo: "credential-data",
					credentialId: credential.id,
					accessingPlatform: "web",
					accessingDevice: getUserAgent(),
				},
			}

			callApi(request).then((response) => {
				if (response.status !== 200) {
					setShowSnackbar(true)

					setSnackbarMessage(translate("access_denied_message", lng))

					dispatch(setErrorLoading(response.message))
				} else {
					dispatch(toggleLoading(false))

					setVisible(true)

					dispatch(setDecryptedCredential(response.data.decrypted_credential))
				}
			})
		} else {
			const id = credential.id ? credential.id : 0

			id !== 0 &&
				obtainCredential(id).then((credentialDB: any) => {
					if (credentialDB) {
						setVisible(false)

						dispatch(initializeCredential(credentialDB))
					} else {
						setSnackbarMessage(translate("error_messages", lng, 3))

						setShowSnackbar(true)
					}
				})
		}
	}

	const toggleLock = () => {
		if (!token) return

		if (locked) {
			dispatch(toggleLoading(true))

			const request: ApiCallI = {
				lng,
				token,
				method: "POST",
				endpoint: "/auth/grant-access",
				body: {
					accessTo: "credential-data",
					credentialId: credential.id,
					accessingPlatform: "web",
					accessingDevice: getUserAgent(),
				},
			}

			callApi(request).then((response) => {
				if (response.status !== 200) {
					setShowSnackbar(true)

					setSnackbarMessage(translate("access_denied_message", lng))

					dispatch(setErrorLoading(response.message))
				} else {
					dispatch(toggleLoading(false))

					if (!visible && locked) {
						setVisible(true)
					}

					setLocked(false)

					setIsAuthenticated(true)

					dispatch(setDecryptedCredential(response.data.decrypted_credential))
				}
			})
		} else {
			updateCredential(credential)
		}
	}

	const updateCredential = (credential: ReduxCredentialT) => {
		if (!token) return

		const request: ApiCallI = {
			lng,
			method: "PUT",
			endpoint: "/credential/update/" + credential.id,
			body: {
				...credential,
				accessing_device: getUserAgent(),
				accessing_platform: "web",
			},
			token,
		}

		dispatch(toggleLoading(true))

		callApi(request).then(async (response) => {
			if (response.status !== 200) {
				dispatch(setErrorLoading(response.message))
				return
			}

			const updatedCredential = await putCredential(response.data.credential)

			if (updatedCredential === undefined) {
				dispatch(showError(translate("error_messages", lng, 0)))

				return
			}

			dispatch(initializeCredential(response.data.credential))

			dispatch(toggleLoading(false))

			setLocked(true)

			setVisible(false)
		})
	}

	const renderCredentialProp = (
		propName: AccessCredentialPropT,
		label: string,
		maxChar?: CharSizesT,
		isCrypto?: boolean
	) => {
		if (credential[propName]) {
			return (
				<VisualizeCredentialProp
					label={label}
					locked={locked}
					visible={visible}
					propName={propName}
					maxChar={calcMaxChar(maxChar ? maxChar : "sm")}
					isCrypto={isCrypto}
				/>
			)
		} else if (!locked && !credential[propName]) {
			return (
				<VisualizeCredentialProp
					label={label}
					locked={locked}
					visible={visible}
					propName={propName}
					maxChar={calcMaxChar(maxChar ? maxChar : "sm")}
					isCrypto={isCrypto}
				/>
			)
		}
	}

	return (
		<>
			<Grid item xs={12} md={3}>
				<Grid container spacing={4}>
					<Grid item xs={12}>
						<Grid container justify="space-between" spacing={3}>
							<Grid item className={classes.title}>
								<Typography variant="h6">{credential.company_name}</Typography>
							</Grid>
							<Grid item className={classes.lockIcon}>
								<GoBackBtn
									altText={!locked ? translate("go_back", lng, 1) : undefined}
									altColor={!locked ? "primary" : undefined}
								/>
							</Grid>
						</Grid>
					</Grid>
					{renderCredentialProp("description", translate("description", lng), "xl")}
					<Grid item xs={12}>
						<Grid container justify="space-between" spacing={2}>
							<Grid item>
								<Typography variant="body2">
									{translate("credential_info", lng, 0)}:
								</Typography>
							</Grid>
							<Grid item>
								<Typography color="secondary" variant="body2">
									{credential.created_at}
								</Typography>
							</Grid>
						</Grid>
					</Grid>
					<Grid item xs={12}>
						<Grid container justify="space-between" spacing={2}>
							<Grid item>
								<Typography variant="body2">
									{translate("credential_info", lng, 1)}:
								</Typography>
							</Grid>
							<Grid item>
								<Typography color="secondary" variant="body2">
									{credential.updated_at}
								</Typography>
							</Grid>
						</Grid>
					</Grid>
					<Grid item xs={12}>
						<Grid container justify="space-between" spacing={2}>
							<Grid item>
								<Typography variant="body2">
									{translate("credential_info", lng, 2)}:
								</Typography>
							</Grid>
							<Grid item>
								<Typography color="secondary" variant="body2">
									{credential.last_seen}
								</Typography>
							</Grid>
						</Grid>
					</Grid>

					<Grid item xs={12}>
						<Grid container justify="space-around">
							<Grid item>
								<DisplayData toggleDisplay={toggleVisibility} visible={visible} />
							</Grid>
							{isAuthenticated && credential.id && (
								<Grid item>
									<DeleteCredential credentialId={credential.id} />
								</Grid>
							)}
							<Grid item className={classes.lockIcon}>
								<UnlockData
									toggleLock={toggleLock}
									testing={REACT_APP_ENV_LOCAL ? true : false}
									locked={locked}
									lockedTitle={translate("access_management", lng, 3)}
									unlockedTitle={translate("access_management", lng, 2)}
								/>
							</Grid>
						</Grid>
					</Grid>
				</Grid>
			</Grid>

			<Grid item xs={12} md={9}>
				<Grid container spacing={4}>
					{renderCredentialProp("user_name", translate("auth_form_texts", lng, 14))}
					{renderCredentialProp("email", translate("auth_form_texts", lng, 0))}
					{renderCredentialProp("username", translate("auth_form_texts", lng, 12))}
					{renderCredentialProp("password", translate("auth_form_texts", lng, 11))}
					{renderCredentialProp("phone_number", translate("auth_form_texts", lng, 7))}
					{renderCredentialProp(
						"security_question",
						translate("encryption_examples", lng, 10)
					)}
					{renderCredentialProp(
						"unique_code",
						translate("auth_form_texts", lng, 13),
						"xs"
					)}
					{renderCredentialProp(
						"multiple_codes",
						translate("encryption_examples", lng, 8),
						"xs"
					)}
					{renderCredentialProp(
						"crypto_codes",
						translate("encryption_examples", lng, 9),
						"xs",
						true
					)}
				</Grid>
			</Grid>

			{showSnackbar && (
				<Snackbar open={showSnackbar} message={snackbarMessage} duration={12000} />
			)}
		</>
	)
}
export default ShowCredential