AccessField.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import { IconLock, IconLockOpen2 } from "@tabler/icons-react";
  2. import { Field, useFormikContext } from "formik";
  3. import type { ReactNode } from "react";
  4. import Select, { type ActionMeta, components, type OptionProps } from "react-select";
  5. import type { AccessList } from "src/api/backend";
  6. import { useAccessLists } from "src/hooks";
  7. import { DateTimeFormat, intl, T } from "src/locale";
  8. interface AccessOption {
  9. readonly value: number;
  10. readonly label: string;
  11. readonly subLabel: string;
  12. readonly icon: ReactNode;
  13. }
  14. const Option = (props: OptionProps<AccessOption>) => {
  15. return (
  16. <components.Option {...props}>
  17. <div className="flex-fill">
  18. <div className="font-weight-medium">
  19. {props.data.icon} <strong>{props.data.label}</strong>
  20. </div>
  21. <div className="text-secondary mt-1 ps-3">{props.data.subLabel}</div>
  22. </div>
  23. </components.Option>
  24. );
  25. };
  26. interface Props {
  27. id?: string;
  28. name?: string;
  29. label?: string;
  30. }
  31. export function AccessField({ name = "accessListId", label = "access.title", id = "accessListId" }: Props) {
  32. const { isLoading, isError, error, data } = useAccessLists();
  33. const { setFieldValue } = useFormikContext();
  34. const handleChange = (newValue: any, _actionMeta: ActionMeta<AccessOption>) => {
  35. setFieldValue(name, newValue?.value);
  36. };
  37. const options: AccessOption[] =
  38. data?.map((item: AccessList) => ({
  39. value: item.id || 0,
  40. label: item.name,
  41. subLabel: intl.formatMessage(
  42. { id: "access.subtitle" },
  43. {
  44. users: item?.items?.length,
  45. rules: item?.clients?.length,
  46. date: item?.createdOn ? DateTimeFormat(item?.createdOn) : "N/A",
  47. },
  48. ),
  49. icon: <IconLock size={14} className="text-lime" />,
  50. })) || [];
  51. // Public option
  52. options?.unshift({
  53. value: 0,
  54. label: intl.formatMessage({ id: "access.public" }),
  55. subLabel: "No basic auth required",
  56. icon: <IconLockOpen2 size={14} className="text-red" />,
  57. });
  58. return (
  59. <Field name={name}>
  60. {({ field, form }: any) => (
  61. <div className="mb-3">
  62. <label className="form-label" htmlFor={id}>
  63. <T id={label} />
  64. </label>
  65. {isLoading ? <div className="placeholder placeholder-lg col-12 my-3 placeholder-glow" /> : null}
  66. {isError ? <div className="invalid-feedback">{`${error}`}</div> : null}
  67. {!isLoading && !isError ? (
  68. <Select
  69. className="react-select-container"
  70. classNamePrefix="react-select"
  71. defaultValue={options.find((o) => o.value === field.value) || options[0]}
  72. options={options}
  73. components={{ Option }}
  74. styles={{
  75. option: (base) => ({
  76. ...base,
  77. height: "100%",
  78. }),
  79. }}
  80. onChange={handleChange}
  81. />
  82. ) : null}
  83. {form.errors[field.name] ? (
  84. <div className="invalid-feedback">
  85. {form.errors[field.name] && form.touched[field.name] ? form.errors[field.name] : null}
  86. </div>
  87. ) : null}
  88. </div>
  89. )}
  90. </Field>
  91. );
  92. }