📦 EqualifyEverything / equalify

📄 AuditRemoteCsvInput.tsx · 128 lines
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
127
128import { useEffect, useState } from "react";
import { useGlobalStore } from "../utils";
import style from "./AuditRemoteCsvInput.module.scss";
import { Card } from "./Card";
import { StyledLabeledInput } from "./StyledLabeledInput";
import { useDebouncedCallback } from "use-debounce";

import * as API from "aws-amplify/api";

interface Page {
  url: string;
  type: "html" | "pdf";
  id?: string;
}
interface ChildProps {
  csvUrl: string
  setCsvUrl: (value: React.SetStateAction<string>) => void
}

// test file: https://equalify.app/wp-content/uploads/2026/02/url-import-template.csv

export const AuditRemoteCsvInput: React.FC<ChildProps> = ({ csvUrl, setCsvUrl
}) => {
  const { setAnnounceMessage } = useGlobalStore();
  const [error, setError] = useState<string | null>("");

  /**
   * Normalize a URL by removing trailing slashes from the path
   * This ensures URLs like "https://example.com/" and "https://example.com" are treated as the same
   */
  const normalizeUrl = (url: string): string => {
    // Remove trailing slash unless it's just the root path
    return url.replace(/\/+$/, '') || url;
  };

  const validateAndFormatUrl = (input: string): string | null => {
    // Trim whitespace
    let url = input.trim();
    if (!url) return null;

    // Add https:// if no protocol is specified
    if (!url.match(/^https?:\/\//i)) {
      url = "https://" + url;
    }

    // Validate URL format
    try {
      const urlObj = new URL(url);
      // Check if it's http or https
      if (!["http:", "https:"].includes(urlObj.protocol)) {
        setError("Only HTTP and HTTPS URLs are supported");
        return null;
      }
      setError(null);
      // Normalize the URL to remove trailing slashes
      return normalizeUrl(urlObj.href);
    } catch {
      setError(
        "Invalid URL format. Please enter a valid URL (e.g., example.com or https://example.com)"
      );
      return null;
    }
  };

  /**
   * Parse a line that may contain a URL and optional type separated by comma
   * Format: url,type (e.g., "https://example.com,html" or "https://example.com/doc.pdf,pdf")
   * If no type specified, defaults to "html"
   */
  const parseUrlWithType = (line: string): { url: string; type: "html" | "pdf" } => {
    const trimmedLine = line.trim();

    // Check if the line ends with ,html or ,pdf (case-insensitive)
    const htmlMatch = trimmedLine.match(/^(.+),\s*html\s*$/i);
    const pdfMatch = trimmedLine.match(/^(.+),\s*pdf\s*$/i);

    if (pdfMatch) {
      return { url: pdfMatch[1].trim(), type: "pdf" };
    }
    if (htmlMatch) {
      return { url: htmlMatch[1].trim(), type: "html" };
    }

    // No type specified, default to html
    return { url: trimmedLine, type: "html" };
  };

  const handleInput = useDebouncedCallback(
    // function
    (value) => {
      if (value.length >= 3) {
        setCsvUrl(value);
      };
      fetchAndValidateCsv();
    },
    750
  );

  const fetchAndValidateCsv = async () => {
    const params: Record<string, string> = {
        url: csvUrl,
    };
    const response = await API.get({
      apiName: "auth",
      path: "/fetchAndValidateRemoteCsv",
      options: { queryParams: params },
    }).response;
    const resp = (await response.body.json()) as any;
    console.log(resp);
  }

  return (
    <div className={style.AuditRemoteCsvInput}>
      <Card variant="inset-light">
        <h3>Use a Remote CSV Integration</h3>
        <p className="font-small">
          Or, provide a link to a hosted CSV to automatically sync your URL list before every scan.
        </p>
        <StyledLabeledInput>
          <label htmlFor="remote-csv-url">URL of Remote CSV</label>
          <input id="remote-csv-url" type="url" value={csvUrl} onChange={(e) => { handleInput(e.target.value) }} />
        </StyledLabeledInput>
        { error ? error : null }
      </Card>
    </div>
  );
};