diff --git a/.eslintrc.json b/.eslintrc.json index 0869eef..3722418 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,9 +1,3 @@ { "extends": ["next/core-web-vitals", "next/typescript"] - // , - // "rules": { - // "@typescript-eslint/no-explicit-any": "off", - // "@typescript-eslint/no-unused-vars": "warn", - // "@typescript-eslint/ban-ts-comment": "off" - // } } diff --git a/components/Charts.tsx b/components/Charts.tsx index 275f77d..2c482a4 100644 --- a/components/Charts.tsx +++ b/components/Charts.tsx @@ -173,7 +173,26 @@ export function LanguagePieChart({ languages }: LanguagePieChartProps) { topLanguages.push(["Other", otherCount]); } - const labels = topLanguages.map(([lang]) => lang); + // Use Intl.DisplayNames to get localized language names from ISO codes + const languageDisplayNames = new Intl.DisplayNames(["en"], { + type: "language", + }); + + // Store original ISO codes for tooltip + const isoCodes = topLanguages.map(([lang]) => lang); + + const labels = topLanguages.map(([lang], index) => { + // Check if this is a valid ISO 639-1 language code + if (lang && lang !== "Other" && /^[a-z]{2}$/.test(lang)) { + try { + return languageDisplayNames.of(lang); + } catch (e) { + return lang; // Fallback to code if display name can't be resolved + } + } + return lang; // Return original string for "Other" or invalid codes + }); + const data = topLanguages.map(([, count]) => count); const chart = new Chart(ctx, { @@ -205,6 +224,27 @@ export function LanguagePieChart({ languages }: LanguagePieChartProps) { padding: 20, }, }, + tooltip: { + callbacks: { + label: function (context) { + const label = context.label || ""; + const value = context.formattedValue || ""; + const index = context.dataIndex; + const isoCode = isoCodes[index]; + + // Only show ISO code if it's not "Other" and it's a valid 2-letter code + if ( + isoCode && + isoCode !== "Other" && + /^[a-z]{2}$/.test(isoCode) + ) { + return `${label} (${isoCode.toUpperCase()}): ${value}`; + } + + return `${label}: ${value}`; + }, + }, + }, }, }, }); diff --git a/components/CountryDisplay.tsx b/components/CountryDisplay.tsx new file mode 100644 index 0000000..cfecb23 --- /dev/null +++ b/components/CountryDisplay.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { getLocalizedCountryName } from "../lib/localization"; + +interface CountryDisplayProps { + countryCode: string | null | undefined; + className?: string; +} + +/** + * Component to display a country name from its ISO 3166-1 alpha-2 code + * Uses Intl.DisplayNames API when available, falls back to the code + */ +export default function CountryDisplay({ + countryCode, + className, +}: CountryDisplayProps) { + const [countryName, setCountryName] = useState( + countryCode || "Unknown", + ); + + useEffect(() => { + // Only run in the browser and if we have a valid code + if (typeof window !== "undefined" && countryCode) { + setCountryName(getLocalizedCountryName(countryCode)); + } + }, [countryCode]); + + return {countryName}; +} diff --git a/components/LanguageDisplay.tsx b/components/LanguageDisplay.tsx new file mode 100644 index 0000000..2cc230f --- /dev/null +++ b/components/LanguageDisplay.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { getLocalizedLanguageName } from "../lib/localization"; + +interface LanguageDisplayProps { + languageCode: string | null | undefined; + className?: string; +} + +/** + * Component to display a language name from its ISO 639-1 code + * Uses Intl.DisplayNames API when available, falls back to the code + */ +export default function LanguageDisplay({ + languageCode, + className, +}: LanguageDisplayProps) { + const [languageName, setLanguageName] = useState( + languageCode || "Unknown", + ); + + useEffect(() => { + // Only run in the browser and if we have a valid code + if (typeof window !== "undefined" && languageCode) { + setLanguageName(getLocalizedLanguageName(languageCode)); + } + }, [languageCode]); + + return {languageName}; +} diff --git a/components/SessionDetails.tsx b/components/SessionDetails.tsx new file mode 100644 index 0000000..a7cee93 --- /dev/null +++ b/components/SessionDetails.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { ChatSession } from "../lib/types"; +import LanguageDisplay from "./LanguageDisplay"; +import CountryDisplay from "./CountryDisplay"; + +interface SessionDetailsProps { + session: ChatSession; +} + +/** + * Component to display session details with formatted country and language names + */ +export default function SessionDetails({ session }: SessionDetailsProps) { + return ( +
+

Session Details

+ +
+
+ Session ID: + {session.sessionId} +
+ +
+ Start Time: + + {new Date(session.startTime).toLocaleString()} + +
+ + {session.endTime && ( +
+ End Time: + + {new Date(session.endTime).toLocaleString()} + +
+ )} + + {session.category && ( +
+ Category: + {session.category} +
+ )} + + {session.language && ( +
+ Language: + + + + ({session.language.toUpperCase()}) + + +
+ )} + + {session.country && ( +
+ Country: + + + + ({session.country}) + + +
+ )} + + {session.sentiment !== null && session.sentiment !== undefined && ( +
+ Sentiment: + 0.3 + ? "text-green-500" + : session.sentiment < -0.3 + ? "text-red-500" + : "text-orange-500" + }`} + > + {session.sentiment > 0.3 + ? "Positive" + : session.sentiment < -0.3 + ? "Negative" + : "Neutral"}{" "} + ({session.sentiment.toFixed(2)}) + +
+ )} + +
+ Messages Sent: + {session.messagesSent || 0} +
+ + {typeof session.tokens === "number" && ( +
+ Tokens: + {session.tokens} +
+ )} + + {typeof session.tokensEur === "number" && ( +
+ Cost: + €{session.tokensEur.toFixed(4)} +
+ )} + + {session.avgResponseTime !== null && + session.avgResponseTime !== undefined && ( +
+ Avg Response Time: + + {session.avgResponseTime.toFixed(2)}s + +
+ )} + + {session.escalated !== null && session.escalated !== undefined && ( +
+ Escalated: + + {session.escalated ? "Yes" : "No"} + +
+ )} + + {session.forwardedHr !== null && session.forwardedHr !== undefined && ( +
+ Forwarded to HR: + + {session.forwardedHr ? "Yes" : "No"} + +
+ )} + + {session.fullTranscriptUrl && ( +
+ Transcript: + + View Full Transcript + +
+ )} +
+
+ ); +} diff --git a/lib/csvFetcher.ts b/lib/csvFetcher.ts index bafc3d6..9a19fea 100644 --- a/lib/csvFetcher.ts +++ b/lib/csvFetcher.ts @@ -1,6 +1,11 @@ // Fetches, parses, and returns chat session data for a company from a CSV URL import fetch from "node-fetch"; import { parse } from "csv-parse/sync"; +import ISO6391 from "iso-639-1"; +import countries from "i18n-iso-countries"; + +// Register locales for i18n-iso-countries +countries.registerLocale(require("i18n-iso-countries/langs/en.json")); // This type is used internally for parsing the CSV records interface CSVRecord { @@ -29,8 +34,8 @@ interface SessionData { startTime: Date; endTime: Date | null; ipAddress?: string; - country?: string; - language?: string | null; + country?: string | null; // Will store ISO 3166-1 alpha-2 country code or null/undefined + language?: string | null; // Will store ISO 639-1 language code or null/undefined messagesSent: number; sentiment: number | null; escalated: boolean; @@ -44,51 +49,136 @@ interface SessionData { } /** - * Normalizes language values to a standard set - * @param languageStr The raw language string from CSV - * @returns A normalized language string + * Converts country names to ISO 3166-1 alpha-2 codes + * @param countryStr Raw country string from CSV + * @returns ISO 3166-1 alpha-2 country code or null if not found */ -function normalizeLanguage(languageStr?: string): string | null { - if (!languageStr) return null; +function getCountryCode(countryStr?: string): string | null | undefined { + if (countryStr === undefined) return undefined; + if (countryStr === null || countryStr === "") return null; - const normalized = languageStr.toLowerCase().trim(); + // Clean the input + const normalized = countryStr.trim(); + if (!normalized) return null; - // Map of language variations to standard names - const languageMap: Record = { - // English variations - english: "English", - en: "English", - eng: "English", + // Direct ISO code check (if already a 2-letter code) + if (normalized.length === 2 && normalized === normalized.toUpperCase()) { + return countries.isValid(normalized) ? normalized : null; + } - // Dutch variations - dutch: "Dutch", - nederlands: "Dutch", - nl: "Dutch", - nederland: "Dutch", - netherland: "Dutch", - netherlands: "Dutch", - hollands: "Dutch", - niederländisch: "Dutch", - nizozemski: "Dutch", - - // Other languages that might appear - bosnian: "Bosnian", - bs: "Bosnian", - turkish: "Turkish", - tr: "Turkish", - turks: "Turkish", - german: "German", - de: "German", - duits: "German", - french: "French", - fr: "French", - frans: "French", - spanish: "Spanish", - es: "Spanish", - spaans: "Spanish", + // Special case for country codes used in the dataset + const countryMapping: Record = { + BA: "BA", // Bosnia and Herzegovina + NL: "NL", // Netherlands + USA: "US", // United States + UK: "GB", // United Kingdom + GB: "GB", // Great Britain + Nederland: "NL", + Netherlands: "NL", + Netherland: "NL", + Holland: "NL", + Germany: "DE", + Deutschland: "DE", + Belgium: "BE", + België: "BE", + Belgique: "BE", + France: "FR", + Frankreich: "FR", + "United States": "US", + "United States of America": "US", + Bosnia: "BA", + "Bosnia and Herzegovina": "BA", + "Bosnia & Herzegovina": "BA", }; - return languageMap[normalized] || "Other"; + // Check mapping + if (normalized in countryMapping) { + return countryMapping[normalized]; + } + + // Try to get the code from the country name (in English) + try { + const code = countries.getAlpha2Code(normalized, "en"); + if (code) return code; + } catch (error) { + console.error( + `Error converting country name to code: ${normalized}`, + error, + ); + } + + // If all else fails, return null + return null; +} + +/** + * Converts language names to ISO 639-1 codes + * @param languageStr Raw language string from CSV + * @returns ISO 639-1 language code or null if not found + */ +function getLanguageCode(languageStr?: string): string | null | undefined { + if (languageStr === undefined) return undefined; + if (languageStr === null || languageStr === "") return null; + + // Clean the input + const normalized = languageStr.trim(); + if (!normalized) return null; + + // Direct ISO code check (if already a 2-letter code) + if (normalized.length === 2 && normalized === normalized.toLowerCase()) { + return ISO6391.validate(normalized) ? normalized : null; + } + + // Special case mappings + const languageMapping: Record = { + english: "en", + English: "en", + dutch: "nl", + Dutch: "nl", + nederlands: "nl", + Nederlands: "nl", + nl: "nl", + bosnian: "bs", + Bosnian: "bs", + turkish: "tr", + Turkish: "tr", + german: "de", + German: "de", + deutsch: "de", + Deutsch: "de", + french: "fr", + French: "fr", + français: "fr", + Français: "fr", + spanish: "es", + Spanish: "es", + español: "es", + Español: "es", + italian: "it", + Italian: "it", + italiano: "it", + Italiano: "it", + nizozemski: "nl", // "Dutch" in some Slavic languages + }; + + // Check mapping + if (normalized in languageMapping) { + return languageMapping[normalized]; + } + + // Try to get code using the ISO6391 library + try { + const code = ISO6391.getCode(normalized); + if (code) return code; + } catch (error) { + console.error( + `Error converting language name to code: ${normalized}`, + error, + ); + } + + // If all else fails, return null + return null; } /** @@ -103,7 +193,7 @@ function normalizeCategory(categoryStr?: string): string | null { // Define category groups using keywords const categoryMapping: Record = { - "Onboarding": [ + Onboarding: [ "onboarding", "start", "begin", @@ -131,7 +221,7 @@ function normalizeCategory(categoryStr?: string): string | null { "gesprek", "talk", ], - "Greeting": [ + Greeting: [ "greeting", "greet", "hello", @@ -199,7 +289,7 @@ function normalizeCategory(categoryStr?: string): string | null { "software", "hardware", ], - "Offboarding": [ + Offboarding: [ "offboarding", "leave", "exit", @@ -343,8 +433,8 @@ export async function fetchAndParseCsv( startTime: safeParseDate(r.start_time) || new Date(), // Fallback to current date if invalid endTime: safeParseDate(r.end_time), ipAddress: r.ip_address, - country: r.country, - language: normalizeLanguage(r.language), + country: getCountryCode(r.country), + language: getLanguageCode(r.language), messagesSent: Number(r.messages_sent) || 0, sentiment: mapSentimentToScore(r.sentiment), escalated: isTruthyValue(r.escalated), diff --git a/lib/localization.ts b/lib/localization.ts new file mode 100644 index 0000000..7c1358d --- /dev/null +++ b/lib/localization.ts @@ -0,0 +1,105 @@ +import ISO6391 from "iso-639-1"; +import countries from "i18n-iso-countries"; + +// Register locales for i18n-iso-countries +countries.registerLocale(require("i18n-iso-countries/langs/en.json")); + +/** + * Get a human-readable language name from ISO 639-1 code + * @param code The ISO 639-1 language code + * @returns The language name or the original code if not found + */ +export function getLanguageName(code: string | null | undefined): string { + if (!code) return "Unknown"; + + // Handle invalid codes + if (code.length !== 2) return code; + + // Try using ISO6391 library + try { + const name = ISO6391.getName(code); + if (name) return name; + } catch (e) { + console.error(`Error getting language name for code: ${code}`, e); + } + + return code; // Return original code as fallback +} + +/** + * Get a human-readable country name from ISO 3166-1 alpha-2 code + * @param code The ISO 3166-1 alpha-2 country code + * @returns The country name or the original code if not found + */ +export function getCountryName(code: string | null | undefined): string { + if (!code) return "Unknown"; + + // Handle invalid codes + if (code.length !== 2) return code; + + // Try using i18n-iso-countries library + try { + const name = countries.getName(code, "en"); + if (name) return name; + } catch (e) { + console.error(`Error getting country name for code: ${code}`, e); + } + + return code; // Return original code as fallback +} + +/** + * Client-side function to get localized language name using Intl.DisplayNames + * @param code The ISO 639-1 language code + * @param locale The locale to use (defaults to browser's locale) + * @returns The localized language name + */ +export function getLocalizedLanguageName( + code: string | null | undefined, + locale?: string, +): string { + if (typeof window === "undefined" || !code) return getLanguageName(code); + + try { + // Check if Intl.DisplayNames is supported + if (typeof Intl !== "undefined" && "DisplayNames" in Intl) { + const userLocale = locale || navigator.language || "en"; + const displayNames = new Intl.DisplayNames([userLocale], { + type: "language", + }); + return displayNames.of(code) || getLanguageName(code); + } + } catch (e) { + console.error(`Error getting localized language name for code: ${code}`, e); + } + + return getLanguageName(code); +} + +/** + * Client-side function to get localized country name using Intl.DisplayNames + * @param code The ISO 3166-1 alpha-2 country code + * @param locale The locale to use (defaults to browser's locale) + * @returns The localized country name + */ +export function getLocalizedCountryName( + code: string | null | undefined, + locale?: string, +): string { + if (typeof window === "undefined" || !code) return getCountryName(code); + + try { + // Check if Intl.DisplayNames is supported + if (typeof Intl !== "undefined" && "DisplayNames" in Intl) { + const userLocale = locale || navigator.language || "en"; + const displayNames = new Intl.DisplayNames([userLocale], { + type: "region", + }); + return displayNames.of(code) || getCountryName(code); + } + } catch (e) { + console.error(`Error getting localized country name for code: ${code}`, e); + } + + return getCountryName(code); +} diff --git a/lib/types.ts b/lib/types.ts index c8c70c0..fb8060e 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -42,7 +42,10 @@ export interface ChatSession { userId?: string | null; category?: string | null; language?: string | null; + country?: string | null; + ipAddress?: string | null; sentiment?: number | null; + messagesSent?: number; startTime: Date; endTime?: Date | null; createdAt: Date; @@ -55,6 +58,7 @@ export interface ChatSession { tokens?: number; tokensEur?: number; initialMsg?: string; + fullTranscriptUrl?: string | null; } export interface DayMetrics { diff --git a/package-lock.json b/package-lock.json index 590d82e..21684d7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "0.1.0", "dependencies": { "@prisma/client": "^6.8.2", - "@types/node-fetch": "^3.0.2", + "@types/node-fetch": "^2.6.12", "bcryptjs": "^3.0.2", "chart.js": "^4.0.0", "csv-parse": "^5.5.0", + "i18n-iso-countries": "^7.14.0", + "iso-639-1": "^3.1.5", "next": "^15.3.2", "next-auth": "^4.24.11", "node-cron": "^4.0.6", @@ -35,7 +37,6 @@ "postcss": "^8.4.0", "prettier": "^3.5.3", "prisma": "^6.8.2", - "ts-node": "^10.9.2", "typescript": "^5.0.0" } }, @@ -75,30 +76,6 @@ "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/@emnapi/core": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", @@ -1425,34 +1402,6 @@ "tailwindcss": "4.1.7" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -1496,7 +1445,6 @@ "version": "22.15.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", "integrity": "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1510,12 +1458,13 @@ "license": "MIT" }, "node_modules/@types/node-fetch": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-3.0.2.tgz", - "integrity": "sha512-3q5FyT6iuekUxXeL2qjcyIhtMJdfMF7RGhYXWKkYpdcW9k36A/+txXrjG0l+NMVkiC30jKNrcOqVlqBl7BcCHA==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", "license": "MIT", "dependencies": { - "node-fetch": "*" + "@types/node": "*", + "form-data": "^4.0.0" } }, "node_modules/@types/react": { @@ -2031,19 +1980,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2077,13 +2013,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2276,6 +2205,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -2457,7 +2392,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2604,6 +2538,18 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2620,13 +2566,6 @@ "node": ">= 0.6" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2786,6 +2725,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2796,21 +2744,16 @@ "node": ">=8" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } + "node_modules/diacritics": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz", + "integrity": "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==", + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2919,7 +2862,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2929,7 +2871,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2967,7 +2908,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2980,7 +2920,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3688,6 +3627,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -3718,7 +3672,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3759,7 +3712,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3784,7 +3736,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3872,7 +3823,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3951,7 +3901,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3964,7 +3913,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -3980,7 +3928,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3989,6 +3936,18 @@ "node": ">= 0.4" } }, + "node_modules/i18n-iso-countries": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.14.0.tgz", + "integrity": "sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==", + "license": "MIT", + "dependencies": { + "diacritics": "1.3.0" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4450,6 +4409,15 @@ "dev": true, "license": "ISC" }, + "node_modules/iso-639-1": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz", + "integrity": "sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -4898,18 +4866,10 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4939,6 +4899,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -6441,50 +6422,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -6632,7 +6569,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -6718,13 +6654,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -6855,16 +6784,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index fe7bfd7..2d00812 100644 --- a/package.json +++ b/package.json @@ -15,10 +15,12 @@ }, "dependencies": { "@prisma/client": "^6.8.2", - "@types/node-fetch": "^3.0.2", + "@types/node-fetch": "^2.6.12", "bcryptjs": "^3.0.2", "chart.js": "^4.0.0", "csv-parse": "^5.5.0", + "i18n-iso-countries": "^7.14.0", + "iso-639-1": "^3.1.5", "next": "^15.3.2", "next-auth": "^4.24.11", "node-cron": "^4.0.6",