@crowdin/app-project-module 3.0.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ import { Response } from 'express';
2
+ export default function handle(): (req: import("express").Request | import("../../..").CrowdinClientRequest, res: Response, next: Function) => void;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.default = handle;
13
+ const storage_1 = require("../../../storage");
14
+ const util_1 = require("../../../util");
15
+ const logger_1 = require("../../../util/logger");
16
+ const INVALID_DATA = JSON.stringify({
17
+ appData: { Warning: 'Invalid response data. Please contact the Crowdin support manager.' },
18
+ });
19
+ function handle() {
20
+ return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
21
+ const id = Number.parseInt(`${req.params.id}`, 10);
22
+ if (!Number.isInteger(id)) {
23
+ return res.status(400).json({
24
+ error: {
25
+ message: 'Invalid error log id',
26
+ },
27
+ });
28
+ }
29
+ const userError = yield (0, storage_1.getStorage)().getUserError({
30
+ id,
31
+ crowdinId: req.crowdinContext.crowdinId,
32
+ integrationId: req.crowdinContext.clientId,
33
+ });
34
+ if (!userError) {
35
+ req.logInfo(`Error log ${id} not found`);
36
+ return res.status(404).json({
37
+ error: {
38
+ message: `Error log with ID ${id} not found`,
39
+ },
40
+ });
41
+ }
42
+ if (!userError.data || !(0, util_1.isJson)(userError.data)) {
43
+ (0, logger_1.logError)(userError);
44
+ return res.send({ data: INVALID_DATA });
45
+ }
46
+ res.send({ data: userError.data });
47
+ }));
48
+ }
@@ -1,2 +1,8 @@
1
1
  import { Response } from 'express';
2
+ export declare function parseUserErrorsQuery(query?: any): {
3
+ limit: number;
4
+ offset: number;
5
+ orderDirection: "asc" | "desc";
6
+ search: any;
7
+ };
2
8
  export default function handle(): (req: import("express").Request | import("../../..").CrowdinClientRequest, res: Response, next: Function) => void;
@@ -9,27 +9,49 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.parseUserErrorsQuery = parseUserErrorsQuery;
12
13
  exports.default = handle;
13
14
  const storage_1 = require("../../../storage");
14
15
  const util_1 = require("../../../util");
15
- const logger_1 = require("../../../util/logger");
16
+ const DEFAULT_LIMIT = 10;
17
+ const MAX_LIMIT = 100;
18
+ const MAX_SEARCH_LENGTH = 255;
19
+ function parseInteger(value, fallback) {
20
+ const parsed = Number.parseInt(`${value}`, 10);
21
+ return Number.isFinite(parsed) ? parsed : fallback;
22
+ }
23
+ function parseUserErrorsQuery(query = {}) {
24
+ const search = typeof query.search === 'string' ? query.search.trim().slice(0, MAX_SEARCH_LENGTH) : '';
25
+ return {
26
+ limit: Math.min(Math.max(parseInteger(query.limit, DEFAULT_LIMIT), 1), MAX_LIMIT),
27
+ offset: Math.max(parseInteger(query.offset, 0), 0),
28
+ orderDirection: (query.order === 'asc' ? 'asc' : 'desc'),
29
+ search: search || undefined,
30
+ };
31
+ }
16
32
  function handle() {
17
33
  return (0, util_1.runAsyncWrapper)((req, res) => __awaiter(this, void 0, void 0, function* () {
18
- var _a;
19
- const userTimezone = (yield req.crowdinApiClient.usersApi.getAuthenticatedUser()).data.timezone;
20
- req.logInfo(`Loading user errors for crowdinId ${req.crowdinContext.crowdinId} and integrationId ${req.crowdinContext.clientId}`);
21
- const userErrors = (_a = (yield (0, storage_1.getStorage)().getAllUserErrors(req.crowdinContext.crowdinId, req.crowdinContext.clientId))) === null || _a === void 0 ? void 0 : _a.map((userError) => {
22
- if (!(0, util_1.isJson)(userError.data)) {
23
- (0, logger_1.logError)(userError);
24
- userError.data = JSON.stringify({
25
- appData: { Warning: 'Invalid response data. Please contact the Crowdin support manager.' },
26
- });
27
- }
28
- const date = new Date(+userError.createdAt);
29
- const formattedDate = (0, util_1.getFormattedDate)({ date, userTimezone });
30
- return Object.assign(Object.assign({}, userError), { formattedDate });
31
- });
32
- req.logInfo(`Returning ${userErrors === null || userErrors === void 0 ? void 0 : userErrors.length} user errors`);
33
- res.send(userErrors);
34
+ const { limit, offset, orderDirection, search } = parseUserErrorsQuery(req.query);
35
+ const filter = {
36
+ crowdinId: req.crowdinContext.crowdinId,
37
+ integrationId: req.crowdinContext.clientId,
38
+ search,
39
+ };
40
+ req.logInfo(`Loading user errors for crowdinId ${req.crowdinContext.crowdinId} and integrationId ${req.crowdinContext.clientId} (limit ${limit}, offset ${offset})`);
41
+ const [userTimezone, userErrors, total] = yield Promise.all([
42
+ req.crowdinApiClient.usersApi
43
+ .getAuthenticatedUser()
44
+ .then(({ data }) => data.timezone)
45
+ .catch((e) => {
46
+ const errorMessage = e instanceof Error ? e.message : String(e);
47
+ req.logError(`Can't load user timezone, falling back to server timezone: ${errorMessage}`);
48
+ return undefined;
49
+ }),
50
+ (0, storage_1.getStorage)().getUserErrors(Object.assign(Object.assign({}, filter), { limit, offset, orderDirection })),
51
+ (0, storage_1.getStorage)().countUserErrors(filter),
52
+ ]);
53
+ const data = userErrors.map((userError) => (Object.assign(Object.assign({}, userError), { formattedDate: (0, util_1.getFormattedDate)({ date: new Date(+userError.createdAt), userTimezone }) })));
54
+ req.logInfo(`Returning ${data.length} of ${total} user errors`);
55
+ res.send({ data, total });
34
56
  }));
35
57
  }
@@ -43,6 +43,7 @@ const settings_save_1 = __importDefault(require("./handlers/settings-save"));
43
43
  const subscription_info_1 = __importDefault(require("./handlers/subscription-info"));
44
44
  const sync_settings_1 = __importDefault(require("./handlers/sync-settings"));
45
45
  const sync_settings_save_1 = __importDefault(require("./handlers/sync-settings-save"));
46
+ const user_error_detail_1 = __importDefault(require("./handlers/user-error-detail"));
46
47
  const user_errors_1 = __importDefault(require("./handlers/user-errors"));
47
48
  const users_1 = __importDefault(require("./handlers/users"));
48
49
  const invite_users_1 = __importDefault(require("./handlers/invite-users"));
@@ -240,6 +241,13 @@ function register({ config, app }) {
240
241
  moduleKey: integrationLogic.key,
241
242
  isIntegration: true,
242
243
  }), (0, integration_credentials_1.default)({ config, integration: integrationLogic }), (0, user_errors_1.default)());
244
+ app.get('/api/user-errors/:id', json_response_1.handle, (0, crowdin_client_1.handle)({
245
+ config,
246
+ optional: false,
247
+ checkSubscriptionExpiration: true,
248
+ moduleKey: integrationLogic.key,
249
+ isIntegration: true,
250
+ }), (0, integration_credentials_1.default)({ config, integration: integrationLogic }), (0, user_error_detail_1.default)());
243
251
  app.get('/api/users', json_response_1.handle, (0, crowdin_client_1.handle)({
244
252
  config,
245
253
  optional: false,
@@ -36821,54 +36821,21 @@ Defaulting to \`null\`.`;
36821
36821
  }
36822
36822
 
36823
36823
  const rowsPerPageOptions = [10, 25, 50];
36824
- function getTimestamp(value) {
36825
- const numericValue = Number(value);
36826
- return Number.isNaN(numericValue) ? new Date(value).getTime() : numericValue;
36827
- }
36828
- const UserErrorsTable = ({ errors, isLoading, onSelect }) => {
36829
- const [search, setSearch] = reactExports.useState('');
36830
- const [isAscending, setIsAscending] = reactExports.useState(false);
36831
- const [page, setPage] = reactExports.useState(1);
36832
- const [rowsPerPage, setRowsPerPage] = reactExports.useState(rowsPerPageOptions[0]);
36833
- const filteredErrors = reactExports.useMemo(() => {
36834
- const normalizedSearch = search.trim().toLocaleLowerCase();
36835
- const matchingErrors = normalizedSearch
36836
- ? errors.filter((error) =>
36837
- // Typed as string, but the payload is unvalidated API data - a row
36838
- // with a missing field must not take the whole table down.
36839
- [error.formattedDate, error.action, error.message].some((value) => String(value !== null && value !== void 0 ? value : '')
36840
- .toLocaleLowerCase()
36841
- .includes(normalizedSearch)))
36842
- : errors;
36843
- return [...matchingErrors].sort((first, second) => {
36844
- const difference = getTimestamp(first.createdAt) - getTimestamp(second.createdAt);
36845
- return isAscending ? difference : -difference;
36846
- });
36847
- }, [errors, isAscending, search]);
36848
- const pageCount = Math.max(1, Math.ceil(filteredErrors.length / rowsPerPage));
36849
- reactExports.useEffect(() => {
36850
- setPage((currentPage) => Math.min(currentPage, pageCount));
36851
- }, [pageCount]);
36852
- const visibleErrors = filteredErrors.slice((page - 1) * rowsPerPage, page * rowsPerPage);
36853
- const firstVisibleRow = filteredErrors.length === 0 ? 0 : (page - 1) * rowsPerPage + 1;
36854
- const lastVisibleRow = Math.min(page * rowsPerPage, filteredErrors.length);
36824
+ const UserErrorsTable = ({ errors, isLoading, total, currentPage, rowsPerPage, searchText, isSortedAsc, onSelect, onOptionsChange, }) => {
36825
+ const pageCount = Math.max(1, Math.ceil(total / rowsPerPage));
36826
+ const firstVisibleRow = total === 0 ? 0 : (currentPage - 1) * rowsPerPage + 1;
36827
+ const lastVisibleRow = Math.min(currentPage * rowsPerPage, total);
36855
36828
  const handleRowKeyDown = (event, error) => {
36856
36829
  if (event.key === 'Enter' || event.key === ' ') {
36857
36830
  event.preventDefault();
36858
36831
  onSelect(error);
36859
36832
  }
36860
36833
  };
36861
- return (jsxRuntimeExports.jsxs("div", { "data-slot": "user-errors-table", className: "overflow-hidden rounded-lg border bg-card text-card-foreground", children: [jsxRuntimeExports.jsx("div", { "data-slot": "table-toolbar", className: "border-b p-4", children: jsxRuntimeExports.jsxs("div", { className: "relative max-w-md", children: [jsxRuntimeExports.jsx(Search, { className: "pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" }), jsxRuntimeExports.jsx(Input, { value: search, onChange: (event) => {
36862
- setSearch(event.target.value);
36863
- setPage(1);
36864
- }, className: "h-9 pl-9", placeholder: "Search error logs", "aria-label": "Search error logs" })] }) }), jsxRuntimeExports.jsxs(Table, { children: [jsxRuntimeExports.jsx(TableHeader, { children: jsxRuntimeExports.jsxs(TableRow, { className: "hover:bg-transparent", children: [jsxRuntimeExports.jsx(TableHead, { className: "w-1/3 px-4", "aria-sort": isAscending ? 'ascending' : 'descending', children: jsxRuntimeExports.jsxs(Button, { type: "button", variant: "ghost", className: "-ml-2", onClick: () => setIsAscending((currentValue) => !currentValue), "aria-label": `Sort by date ${isAscending ? 'descending' : 'ascending'}`, children: ["Date", isAscending ? (jsxRuntimeExports.jsx(ArrowUp, { "aria-hidden": "true" })) : (jsxRuntimeExports.jsx(ArrowDown, { "aria-hidden": "true" }))] }) }), jsxRuntimeExports.jsx(TableHead, { className: "w-1/3 px-4", children: "Action" }), jsxRuntimeExports.jsx(TableHead, { className: "w-1/3 px-4", children: "Message" })] }) }), jsxRuntimeExports.jsx(TableBody, { "aria-busy": isLoading, children: isLoading ? (Array.from({ length: 3 }).map((_, index) => (jsxRuntimeExports.jsx(TableRow, { children: [0, 1, 2].map((cell) => (jsxRuntimeExports.jsxs(TableCell, { className: "h-12 px-4", children: [index === 0 && cell === 0 && (jsxRuntimeExports.jsx("span", { className: "sr-only", role: "status", children: "Loading error logs" })), jsxRuntimeExports.jsx("div", { className: "h-4 max-w-48 animate-pulse rounded bg-muted" })] }, cell))) }, index)))) : visibleErrors.length > 0 ? (visibleErrors.map((error) => (jsxRuntimeExports.jsxs(TableRow, { tabIndex: 0, className: "cursor-pointer focus-visible:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", onClick: () => onSelect(error), onKeyDown: (event) => handleRowKeyDown(event, error), "aria-label": `Open error details for ${error.action} on ${error.formattedDate}`, children: [jsxRuntimeExports.jsx(TableCell, { className: "h-12 px-4 font-medium whitespace-normal", children: error.formattedDate }), jsxRuntimeExports.jsx(TableCell, { className: "h-12 px-4 whitespace-normal", children: error.action }), jsxRuntimeExports.jsx(TableCell, { className: "h-12 max-w-0 px-4 whitespace-normal", children: jsxRuntimeExports.jsx("span", { className: "line-clamp-2 break-words", children: error.message }) })] }, error.id)))) : (jsxRuntimeExports.jsx(TableRow, { className: "hover:bg-transparent", children: jsxRuntimeExports.jsx(TableCell, { colSpan: 3, className: "h-32 text-center text-muted-foreground", children: search ? 'No error logs match your search.' : 'No error logs found.' }) })) })] }), jsxRuntimeExports.jsxs("div", { "data-slot": "table-pagination", className: "flex flex-col gap-3 border-t px-4 py-2 sm:flex-row sm:items-center sm:justify-end", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-sm", children: [jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap text-muted-foreground", children: "Rows per page" }), jsxRuntimeExports.jsxs(Select, { value: String(rowsPerPage), onValueChange: (value) => {
36865
- setRowsPerPage(Number(value));
36866
- setPage(1);
36867
- }, children: [jsxRuntimeExports.jsx(SelectTrigger, { className: "w-18", "aria-label": "Rows per page", children: jsxRuntimeExports.jsx(SelectValue, {}) }), jsxRuntimeExports.jsx(SelectContent, { align: "end", children: rowsPerPageOptions.map((option) => (jsxRuntimeExports.jsx(SelectItem, { value: String(option), children: option }, option))) })] })] }), jsxRuntimeExports.jsxs("p", { className: "m-0 min-w-24 text-sm text-muted-foreground", "aria-live": "polite", children: [firstVisibleRow, "-", lastVisibleRow, " of ", filteredErrors.length] }), jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntimeExports.jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => setPage(1), disabled: page === 1, "aria-label": "Go to first page", children: jsxRuntimeExports.jsx(ChevronsLeft, {}) }), jsxRuntimeExports.jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => setPage((currentPage) => Math.max(1, currentPage - 1)), disabled: page === 1, "aria-label": "Go to previous page", children: jsxRuntimeExports.jsx(ChevronLeft, {}) }), jsxRuntimeExports.jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => setPage((currentPage) => Math.min(pageCount, currentPage + 1)), disabled: page === pageCount, "aria-label": "Go to next page", children: jsxRuntimeExports.jsx(ChevronRight, {}) }), jsxRuntimeExports.jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => setPage(pageCount), disabled: page === pageCount, "aria-label": "Go to last page", children: jsxRuntimeExports.jsx(ChevronsRight, {}) })] })] })] }));
36834
+ return (jsxRuntimeExports.jsxs("div", { "data-slot": "user-errors-table", className: "overflow-hidden rounded-lg border bg-card text-card-foreground", children: [jsxRuntimeExports.jsx("div", { "data-slot": "table-toolbar", className: "border-b p-4", children: jsxRuntimeExports.jsxs("div", { className: "relative max-w-md", children: [jsxRuntimeExports.jsx(Search, { className: "pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" }), jsxRuntimeExports.jsx(Input, { value: searchText, onChange: (event) => onOptionsChange({ searchText: event.target.value, currentPage: 1 }), className: "h-9 pl-9", placeholder: "Search by action or message", "aria-label": "Search error logs by action or message" })] }) }), jsxRuntimeExports.jsxs(Table, { children: [jsxRuntimeExports.jsx(TableHeader, { children: jsxRuntimeExports.jsxs(TableRow, { className: "hover:bg-transparent", children: [jsxRuntimeExports.jsx(TableHead, { className: "w-1/3 px-4", "aria-sort": isSortedAsc ? 'ascending' : 'descending', children: jsxRuntimeExports.jsxs(Button, { type: "button", variant: "ghost", className: "-ml-2", onClick: () => onOptionsChange({ isSortedAsc: !isSortedAsc, currentPage: 1 }), "aria-label": `Sort by date ${isSortedAsc ? 'descending' : 'ascending'}`, children: ["Date", isSortedAsc ? (jsxRuntimeExports.jsx(ArrowUp, { "aria-hidden": "true" })) : (jsxRuntimeExports.jsx(ArrowDown, { "aria-hidden": "true" }))] }) }), jsxRuntimeExports.jsx(TableHead, { className: "w-1/3 px-4", children: "Action" }), jsxRuntimeExports.jsx(TableHead, { className: "w-1/3 px-4", children: "Message" })] }) }), jsxRuntimeExports.jsx(TableBody, { "aria-busy": isLoading, children: isLoading ? (Array.from({ length: 3 }).map((_, index) => (jsxRuntimeExports.jsx(TableRow, { children: [0, 1, 2].map((cell) => (jsxRuntimeExports.jsxs(TableCell, { className: "h-12 px-4", children: [index === 0 && cell === 0 && (jsxRuntimeExports.jsx("span", { className: "sr-only", role: "status", children: "Loading error logs" })), jsxRuntimeExports.jsx("div", { className: "h-4 max-w-48 animate-pulse rounded bg-muted" })] }, cell))) }, index)))) : errors.length > 0 ? (errors.map((error) => (jsxRuntimeExports.jsxs(TableRow, { tabIndex: 0, className: "cursor-pointer focus-visible:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset", onClick: () => onSelect(error), onKeyDown: (event) => handleRowKeyDown(event, error), "aria-label": `Open error details for ${error.action} on ${error.formattedDate}`, children: [jsxRuntimeExports.jsx(TableCell, { className: "h-12 px-4 font-medium whitespace-normal", children: error.formattedDate }), jsxRuntimeExports.jsx(TableCell, { className: "h-12 px-4 whitespace-normal", children: error.action }), jsxRuntimeExports.jsx(TableCell, { className: "h-12 max-w-0 px-4 whitespace-normal", children: jsxRuntimeExports.jsx("span", { className: "line-clamp-2 break-words", children: error.message }) })] }, error.id)))) : (jsxRuntimeExports.jsx(TableRow, { className: "hover:bg-transparent", children: jsxRuntimeExports.jsx(TableCell, { colSpan: 3, className: "h-32 text-center text-muted-foreground", children: searchText ? 'No error logs match your search.' : 'No error logs found.' }) })) })] }), jsxRuntimeExports.jsxs("div", { "data-slot": "table-pagination", className: "flex flex-col gap-3 border-t px-4 py-2 sm:flex-row sm:items-center sm:justify-end", children: [jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-sm", children: [jsxRuntimeExports.jsx("span", { className: "whitespace-nowrap text-muted-foreground", children: "Rows per page" }), jsxRuntimeExports.jsxs(Select, { value: String(rowsPerPage), onValueChange: (value) => onOptionsChange({ rowsPerPage: Number(value), currentPage: 1 }), children: [jsxRuntimeExports.jsx(SelectTrigger, { className: "w-18", "aria-label": "Rows per page", children: jsxRuntimeExports.jsx(SelectValue, {}) }), jsxRuntimeExports.jsx(SelectContent, { align: "end", children: rowsPerPageOptions.map((option) => (jsxRuntimeExports.jsx(SelectItem, { value: String(option), children: option }, option))) })] })] }), jsxRuntimeExports.jsxs("p", { className: "m-0 min-w-24 text-sm text-muted-foreground", "aria-live": "polite", children: [firstVisibleRow, "-", lastVisibleRow, " of ", total] }), jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntimeExports.jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => onOptionsChange({ currentPage: 1 }), disabled: currentPage === 1, "aria-label": "Go to first page", children: jsxRuntimeExports.jsx(ChevronsLeft, {}) }), jsxRuntimeExports.jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => onOptionsChange({ currentPage: Math.max(1, currentPage - 1) }), disabled: currentPage === 1, "aria-label": "Go to previous page", children: jsxRuntimeExports.jsx(ChevronLeft, {}) }), jsxRuntimeExports.jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => onOptionsChange({ currentPage: Math.min(pageCount, currentPage + 1) }), disabled: currentPage === pageCount, "aria-label": "Go to next page", children: jsxRuntimeExports.jsx(ChevronRight, {}) }), jsxRuntimeExports.jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => onOptionsChange({ currentPage: pageCount }), disabled: currentPage === pageCount, "aria-label": "Go to last page", children: jsxRuntimeExports.jsx(ChevronsRight, {}) })] })] })] }));
36868
36835
  };
36869
36836
 
36870
- const IntegrationView = ({ appComponentRef, userErrors, isUserErrorsLoading, onSelectUserError, isErrorLogsVisible, ...integrationProps }) => {
36871
- return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { style: { display: isErrorLogsVisible ? 'none' : undefined }, children: jsxRuntimeExports.jsx(SimpleIntegration, { ref: appComponentRef, ...integrationProps }) }), jsxRuntimeExports.jsxs("div", { id: "user-errors", style: { display: isErrorLogsVisible ? undefined : 'none' }, children: [jsxRuntimeExports.jsx(UserErrorsTable, { errors: userErrors, isLoading: isUserErrorsLoading, onSelect: onSelectUserError }), jsxRuntimeExports.jsx(AppAlert, { className: "mt-4", children: "This table displays the most recent error logs from the past month. Logs older than one month will be automatically deleted." })] })] }));
36837
+ const IntegrationView = ({ appComponentRef, userErrors, isUserErrorsLoading, userErrorsTotal, userErrorsCurrentPage, userErrorsRowsPerPage, userErrorsSearchText, userErrorsIsSortedAsc, onSelectUserError, onUserErrorsOptionsChange, isErrorLogsVisible, ...integrationProps }) => {
36838
+ return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx("div", { style: { display: isErrorLogsVisible ? 'none' : undefined }, children: jsxRuntimeExports.jsx(SimpleIntegration, { ref: appComponentRef, ...integrationProps }) }), jsxRuntimeExports.jsxs("div", { id: "user-errors", style: { display: isErrorLogsVisible ? undefined : 'none' }, children: [jsxRuntimeExports.jsx(UserErrorsTable, { errors: userErrors, isLoading: isUserErrorsLoading, total: userErrorsTotal, currentPage: userErrorsCurrentPage, rowsPerPage: userErrorsRowsPerPage, searchText: userErrorsSearchText, isSortedAsc: userErrorsIsSortedAsc, onSelect: onSelectUserError, onOptionsChange: onUserErrorsOptionsChange }), jsxRuntimeExports.jsx(AppAlert, { className: "mt-4", children: "This table displays the most recent error logs from the past month. Logs older than one month will be automatically deleted." })] })] }));
36872
36839
  };
36873
36840
 
36874
36841
  function Progress({ className, value, ...props }) {
@@ -49165,7 +49132,7 @@ Defaulting to \`null\`.`;
49165
49132
  return reactExports.useRef(null);
49166
49133
  }
49167
49134
 
49168
- const AppDialog = reactExports.forwardRef(({ id, title, width = '70', showCloseButton = true, children, footer, className }, ref) => {
49135
+ const AppDialog = reactExports.forwardRef(({ id, title, width = '70', showCloseButton = true, children, footer, className, onClose }, ref) => {
49169
49136
  const [isOpen, setIsOpen] = reactExports.useState(false);
49170
49137
  const [portalContainer, setPortalContainer] = reactExports.useState(null);
49171
49138
  const contentRef = reactExports.useRef(null);
@@ -49180,7 +49147,12 @@ Defaulting to \`null\`.`;
49180
49147
  : width === '65'
49181
49148
  ? 'sm:max-w-4xl'
49182
49149
  : 'sm:max-w-5xl';
49183
- return (jsxRuntimeExports.jsx(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: jsxRuntimeExports.jsx(DialogContent, { ref: contentRef, id: id, showCloseButton: showCloseButton, onOpenAutoFocus: (event) => {
49150
+ return (jsxRuntimeExports.jsx(Dialog, { open: isOpen, onOpenChange: (open) => {
49151
+ setIsOpen(open);
49152
+ if (!open) {
49153
+ onClose === null || onClose === void 0 ? void 0 : onClose();
49154
+ }
49155
+ }, children: jsxRuntimeExports.jsx(DialogContent, { ref: contentRef, id: id, showCloseButton: showCloseButton, onOpenAutoFocus: (event) => {
49184
49156
  var _a;
49185
49157
  event.preventDefault();
49186
49158
  (_a = contentRef.current) === null || _a === void 0 ? void 0 : _a.focus();
@@ -49504,7 +49476,8 @@ Defaulting to \`null\`.`;
49504
49476
  }
49505
49477
  }
49506
49478
  function getUserErrorDetail(error) {
49507
- const data = JSON.parse(error.data);
49479
+ // `data` is loaded separately, right after the modal opens
49480
+ const data = error.data && isJSON(error.data) ? JSON.parse(error.data) : null;
49508
49481
  const preClassName = 'm-0 max-w-full overflow-x-auto whitespace-pre-wrap break-words rounded-lg bg-muted p-3 text-xs leading-relaxed text-foreground ring-1 ring-border dark:bg-background';
49509
49482
  const rows = [
49510
49483
  { label: 'Action', value: error.action },
@@ -49514,6 +49487,10 @@ Defaulting to \`null\`.`;
49514
49487
  },
49515
49488
  { label: 'Date/time', value: error.formattedDate },
49516
49489
  ];
49490
+ if (!data) {
49491
+ rows.push({ label: 'Details', value: jsxRuntimeExports.jsx(Skeleton, { className: "h-8 w-full" }) });
49492
+ return renderUserErrorDetailRows(rows);
49493
+ }
49517
49494
  if (data.requestParams) {
49518
49495
  rows.push({ label: 'Method', value: data.requestParams.method });
49519
49496
  }
@@ -49542,12 +49519,17 @@ Defaulting to \`null\`.`;
49542
49519
  rows.push({ label: 'App Data', value: data.appData });
49543
49520
  }
49544
49521
  }
49522
+ return renderUserErrorDetailRows(rows);
49523
+ }
49524
+ function renderUserErrorDetailRows(rows) {
49545
49525
  return (jsxRuntimeExports.jsx("div", { className: "min-w-0", children: jsxRuntimeExports.jsx("dl", { className: "grid gap-4", children: rows.map((row) => (jsxRuntimeExports.jsxs("div", { className: "grid gap-1 sm:grid-cols-[10rem_minmax(0,1fr)] sm:gap-6", children: [jsxRuntimeExports.jsx("dt", { className: "text-sm font-medium text-muted-foreground", children: row.label }), jsxRuntimeExports.jsx("dd", { className: "m-0 min-w-0 break-words text-sm text-foreground", children: row.value })] }, row.label))) }) }));
49546
49526
  }
49547
- const UserErrorDetailModal = ({ modalRef, selectedUserError }) => {
49548
- return (jsxRuntimeExports.jsx(AppDialog, { ref: modalRef, id: "user-error-detail", width: "60", title: "Details", children: selectedUserError && getUserErrorDetail(selectedUserError) }));
49527
+ const UserErrorDetailModal = ({ modalRef, selectedUserError, onClose }) => {
49528
+ return (jsxRuntimeExports.jsx(AppDialog, { ref: modalRef, id: "user-error-detail", width: "60", title: "Details", onClose: onClose, children: selectedUserError && getUserErrorDetail(selectedUserError) }));
49549
49529
  };
49550
49530
 
49531
+ const USER_ERRORS_PER_PAGE = 10;
49532
+ const USER_ERRORS_SEARCH_DEBOUNCE = 300;
49551
49533
  const MainPage = (props) => {
49552
49534
  const { name, userId, notice, checkSubscription, isManager, isOwner, infoModal, configurationFields, config: configString, syncNewElements, withCronSync, webhooks, integrationOneLevelFetching, integrationSearchListener, excludedTargetLanguages, filtering, hasOrganization, zenModeUrl, asyncProgress = { checkInterval: 3000 }, reloadOnConfigSave, progressiveCrowdinFilesLoading, integrationButtonMenuItems, crowdinButtonMenuItems, } = props;
49553
49535
  const config = reactExports.useRef(configString ? JSON.parse(configString.replace(/\\n/g, '\\\\n')) : {});
@@ -49559,6 +49541,9 @@ Defaulting to \`null\`.`;
49559
49541
  const scheduleModalRef = reactExports.useRef(null);
49560
49542
  const permissionsModalRef = reactExports.useRef(null);
49561
49543
  const userErrorDetailModalRef = reactExports.useRef(null);
49544
+ const userErrorsRequestId = reactExports.useRef(0);
49545
+ const userErrorDetailRequestId = reactExports.useRef(0);
49546
+ const userErrorsSearchTimer = reactExports.useRef(undefined);
49562
49547
  const subscriptionLink = reactExports.useRef('');
49563
49548
  const refreshDependencies = useFieldDependencies(configurationFields);
49564
49549
  const [isNoticeVisible, setIsNoticeVisible] = reactExports.useState(false);
@@ -49566,6 +49551,11 @@ Defaulting to \`null\`.`;
49566
49551
  const [isErrorLogsVisible, setIsErrorLogsVisible] = reactExports.useState(false);
49567
49552
  const [userErrors, setUserErrors] = reactExports.useState([]);
49568
49553
  const [isUserErrorsLoading, setIsUserErrorsLoading] = reactExports.useState(false);
49554
+ const [userErrorsTotal, setUserErrorsTotal] = reactExports.useState(0);
49555
+ const [userErrorsCurrentPage, setUserErrorsCurrentPage] = reactExports.useState(1);
49556
+ const [userErrorsRowsPerPage, setUserErrorsRowsPerPage] = reactExports.useState(USER_ERRORS_PER_PAGE);
49557
+ const [userErrorsSearchText, setUserErrorsSearchText] = reactExports.useState('');
49558
+ const [userErrorsIsSortedAsc, setUserErrorsIsSortedAsc] = reactExports.useState(false);
49569
49559
  const [selectedUserError, setSelectedUserError] = reactExports.useState(null);
49570
49560
  const syncOps = useSyncOperations({
49571
49561
  appComponent,
@@ -49618,7 +49608,12 @@ Defaulting to \`null\`.`;
49618
49608
  }
49619
49609
  };
49620
49610
  document.body.addEventListener('keydown', handleKeydown);
49621
- return () => document.body.removeEventListener('keydown', handleKeydown);
49611
+ return () => {
49612
+ document.body.removeEventListener('keydown', handleKeydown);
49613
+ if (userErrorsSearchTimer.current) {
49614
+ clearTimeout(userErrorsSearchTimer.current);
49615
+ }
49616
+ };
49622
49617
  }, []);
49623
49618
  // A 402 from any API call means the subscription lapsed: show the subscribe modal.
49624
49619
  reactExports.useEffect(() => {
@@ -49659,24 +49654,90 @@ Defaulting to \`null\`.`;
49659
49654
  setIsNoticeVisible(false);
49660
49655
  localStorage.setItem(`revised_${suffix !== null && suffix !== void 0 ? suffix : name}`, '1');
49661
49656
  }
49662
- async function showErrorLogs() {
49663
- setIsErrorLogsVisible(true);
49657
+ async function loadUserErrors({ currentPage = 1, rowsPerPage = USER_ERRORS_PER_PAGE, searchText = '', isSortedAsc = false, } = {}) {
49658
+ var _a, _b;
49659
+ const requestId = ++userErrorsRequestId.current;
49664
49660
  setIsUserErrorsLoading(true);
49665
49661
  try {
49666
49662
  const restParams = await checkOrigin();
49667
- const res = await checkResponse(await fetch('api/user-errors' + restParams));
49668
- setUserErrors(res !== null && res !== void 0 ? res : []);
49663
+ const query = `&limit=${rowsPerPage}&offset=${(currentPage - 1) * rowsPerPage}` +
49664
+ `&order=${isSortedAsc ? 'asc' : 'desc'}` +
49665
+ (searchText ? `&search=${encodeURIComponent(searchText)}` : '');
49666
+ const res = await checkResponse(await fetch('api/user-errors' + restParams + query));
49667
+ // a slow response must not overwrite a page requested later
49668
+ if (requestId !== userErrorsRequestId.current) {
49669
+ return;
49670
+ }
49671
+ setUserErrors((_a = res === null || res === void 0 ? void 0 : res.data) !== null && _a !== void 0 ? _a : []);
49672
+ setUserErrorsTotal((_b = res === null || res === void 0 ? void 0 : res.total) !== null && _b !== void 0 ? _b : 0);
49673
+ setUserErrorsCurrentPage(currentPage);
49674
+ setUserErrorsRowsPerPage(rowsPerPage);
49675
+ setUserErrorsIsSortedAsc(isSortedAsc);
49669
49676
  }
49670
49677
  catch (e) {
49671
- catchRejection(e, "Can't fetch error logs");
49678
+ if (requestId === userErrorsRequestId.current) {
49679
+ catchRejection(e, "Can't fetch error logs");
49680
+ }
49672
49681
  }
49673
49682
  finally {
49674
- setIsUserErrorsLoading(false);
49683
+ if (requestId === userErrorsRequestId.current) {
49684
+ setIsUserErrorsLoading(false);
49685
+ }
49675
49686
  }
49676
49687
  }
49677
- function showUserErrorDetails(error) {
49678
- setSelectedUserError(error);
49688
+ function clearUserErrorsSearchTimer() {
49689
+ if (userErrorsSearchTimer.current) {
49690
+ clearTimeout(userErrorsSearchTimer.current);
49691
+ userErrorsSearchTimer.current = undefined;
49692
+ }
49693
+ }
49694
+ // Called by the table on paging, rows-per-page, sorting and search input.
49695
+ // The search field has no debounce of its own, so typing is debounced here.
49696
+ function onUserErrorsOptionsChange(options) {
49697
+ var _a, _b, _c, _d;
49698
+ const currentPage = (_a = options.currentPage) !== null && _a !== void 0 ? _a : userErrorsCurrentPage;
49699
+ const rowsPerPage = (_b = options.rowsPerPage) !== null && _b !== void 0 ? _b : userErrorsRowsPerPage;
49700
+ const isSortedAsc = (_c = options.isSortedAsc) !== null && _c !== void 0 ? _c : userErrorsIsSortedAsc;
49701
+ const searchText = (_d = options.searchText) !== null && _d !== void 0 ? _d : userErrorsSearchText;
49702
+ const isSearchChange = options.searchText !== undefined && options.searchText !== userErrorsSearchText;
49703
+ clearUserErrorsSearchTimer();
49704
+ if (isSearchChange) {
49705
+ // reflect the keystroke immediately, but debounce the actual request
49706
+ setUserErrorsSearchText(searchText);
49707
+ userErrorsSearchTimer.current = setTimeout(() => loadUserErrors({ currentPage, rowsPerPage, searchText, isSortedAsc }), USER_ERRORS_SEARCH_DEBOUNCE);
49708
+ return;
49709
+ }
49710
+ loadUserErrors({ currentPage, rowsPerPage, searchText, isSortedAsc });
49711
+ }
49712
+ async function openUserErrorDetail(item) {
49713
+ // the list response carries no `data` - the heavy payload is loaded for this record only
49714
+ const requestId = ++userErrorDetailRequestId.current;
49715
+ setSelectedUserError({ ...item, data: null });
49679
49716
  openModal(userErrorDetailModalRef.current);
49717
+ try {
49718
+ const restParams = await checkOrigin();
49719
+ const res = await checkResponse(await fetch(`api/user-errors/${item.id}` + restParams));
49720
+ setSelectedUserError((current) => ((current === null || current === void 0 ? void 0 : current.id) === item.id ? { ...current, data: res.data } : current));
49721
+ }
49722
+ catch (e) {
49723
+ // dialog was closed or reopened
49724
+ if (requestId !== userErrorDetailRequestId.current) {
49725
+ return;
49726
+ }
49727
+ catchRejection(e, "Can't fetch error log details");
49728
+ closeModal(userErrorDetailModalRef.current);
49729
+ setSelectedUserError(null);
49730
+ }
49731
+ }
49732
+ function onUserErrorDetailClose() {
49733
+ userErrorDetailRequestId.current++;
49734
+ }
49735
+ async function showErrorLogs() {
49736
+ setIsErrorLogsVisible(true);
49737
+ // the table keeps its state while the view is hidden - start over on re-entry
49738
+ clearUserErrorsSearchTimer();
49739
+ setUserErrorsSearchText('');
49740
+ await loadUserErrors();
49680
49741
  }
49681
49742
  function showIntegration() {
49682
49743
  setIsErrorLogsVisible(false);
@@ -49684,7 +49745,7 @@ Defaulting to \`null\`.`;
49684
49745
  return (jsxRuntimeExports.jsxs("div", { children: [jsxRuntimeExports.jsxs("div", { className: "mx-auto max-w-[1420px] px-6 py-4", children: [jsxRuntimeExports.jsx(TopBar, { notice: notice, isNoticeVisible: isNoticeVisible, checkSubscription: checkSubscription, subscriptionInfo: subscriptionInfo, isErrorLogsVisible: isErrorLogsVisible, isManager: isManager, infoModal: infoModal, hasSettings: !!configurationFields, isOwner: isOwner, onCloseNotice: closeAlert, onShowIntegration: showIntegration, onShowErrorLogs: showErrorLogs, onShowPermissions: permissions.showPermissionsDialog, onOpenInfoModal: () => openModal(infoModalRef.current), onOpenSettings: () => {
49685
49746
  openModal(settingsModalRef.current);
49686
49747
  setTimeout(settings.fillSettingsForm, 0);
49687
- }, onLogout: integrationLogout }), jsxRuntimeExports.jsx(IntegrationView, { appComponentRef: appComponent, userErrors: userErrors, isUserErrorsLoading: isUserErrorsLoading, onSelectUserError: showUserErrorDetails, name: name, isErrorLogsVisible: isErrorLogsVisible, syncNewElements: syncNewElements, withCronSync: withCronSync, webhooks: webhooks, integrationOneLevelFetching: integrationOneLevelFetching, integrationSearchListener: integrationSearchListener, filtering: filtering, integrationButtonMenuItems: integrationButtonMenuItems, crowdinButtonMenuItems: crowdinButtonMenuItems, onRefreshFilesList: syncOps.handleRefreshFilesList, onFolderToggle: syncOps.handleFolderToggle, onUploadFilesToCrowdin: (detail) => syncOps.uploadFilesToCrowdin({ detail }), onUploadFilesToIntegration: (detail) => syncOps.uploadFilesToIntegration({ detail }), onLoadMore: syncOps.handleLoadMore, onIntegrationFilterChange: syncOps.handleIntegrationFilterChange, onIntegrationScheduleSync: (detail) => schedule.setIntegrationScheduleSync({ detail }), onCrowdinScheduleSync: (detail) => schedule.setCrowdinScheduleSync({ detail }), onIntegrationDisableSync: (detail) => schedule.disableIntegrationSync({ detail }), onCrowdinDisableSync: (detail) => schedule.disableCrowdinSync({ detail }) })] }), jsxRuntimeExports.jsx(Toaster, {}), jsxRuntimeExports.jsx(AsyncProgress, { ref: asyncProgressRef, onCancelJob: syncOps.cancelJob }), jsxRuntimeExports.jsx(SubscriptionModal, { modalRef: subscriptionModal, subscriptionLink: subscriptionLink, name: name, onLogout: integrationLogout }), jsxRuntimeExports.jsx(PermissionsModal, { modalRef: permissionsModalRef, isLoading: permissions.isLoading, permissionUserOptions: permissions.permissionUserOptions, selectedUsers: permissions.permissionsSelectedUsers, isConfirmVisible: permissions.isPermissionsConfirmVisible, permissionsSummary: permissions.permissionsSummary, hasOrganization: hasOrganization, zenModeUrl: zenModeUrl, saveDisabled: !permissions.isPermissionsLoaded, onUsersChange: permissions.checkUsers, onSave: permissions.inviteUsers }), excludedTargetLanguages && (jsxRuntimeExports.jsx(ExcludedLanguagesModal, { modalRef: syncOps.languageModalRef, languageSelectRef: syncOps.languageSelectRef, languageModalState: syncOps.languageModalState, onUpdateTargetLanguages: syncOps.updateCrowdinFilesTargetLanguages, onSyncToCrowdin: syncOps.uploadFilesToCrowdinWithExcludedLanguages })), infoModal && jsxRuntimeExports.jsx(InfoModal, { modalRef: infoModalRef, infoModal: infoModal }), configurationFields && (jsxRuntimeExports.jsx(SettingsModal, { modalRef: settingsModalRef, isLoading: settings.isLoading, configurationFields: configurationFields, isSaveDisabled: settings.isSettingsSaveDisabled, settingsFieldChange: settings.settingsFieldChange, registerSettingsField: settings.registerSettingsField, fieldErrors: settings.fieldErrors, onSave: settings.saveSettings })), ((syncNewElements === null || syncNewElements === void 0 ? void 0 : syncNewElements.crowdin) || (syncNewElements === null || syncNewElements === void 0 ? void 0 : syncNewElements.integration)) && (jsxRuntimeExports.jsx(ScheduleModal, { modalRef: scheduleModalRef, selectedFilesCheckboxRef: schedule.selectedFilesCheckboxRef, allFolderFilesCheckboxRef: schedule.allFolderFilesCheckboxRef, newFilesCheckboxRef: schedule.newFilesCheckboxRef, scheduleModalState: schedule.scheduleModalState, isSaveDisabled: schedule.isScheduleSaveDisabled, onChangeOptions: schedule.onChangeAutoSynchronizationOptions, onSave: schedule.saveScheduleSync })), jsxRuntimeExports.jsx(UserErrorDetailModal, { modalRef: userErrorDetailModalRef, selectedUserError: selectedUserError })] }));
49748
+ }, onLogout: integrationLogout }), jsxRuntimeExports.jsx(IntegrationView, { appComponentRef: appComponent, userErrors: userErrors, isUserErrorsLoading: isUserErrorsLoading, userErrorsTotal: userErrorsTotal, userErrorsCurrentPage: userErrorsCurrentPage, userErrorsRowsPerPage: userErrorsRowsPerPage, userErrorsSearchText: userErrorsSearchText, userErrorsIsSortedAsc: userErrorsIsSortedAsc, onSelectUserError: openUserErrorDetail, onUserErrorsOptionsChange: onUserErrorsOptionsChange, name: name, isErrorLogsVisible: isErrorLogsVisible, syncNewElements: syncNewElements, withCronSync: withCronSync, webhooks: webhooks, integrationOneLevelFetching: integrationOneLevelFetching, integrationSearchListener: integrationSearchListener, filtering: filtering, integrationButtonMenuItems: integrationButtonMenuItems, crowdinButtonMenuItems: crowdinButtonMenuItems, onRefreshFilesList: syncOps.handleRefreshFilesList, onFolderToggle: syncOps.handleFolderToggle, onUploadFilesToCrowdin: (detail) => syncOps.uploadFilesToCrowdin({ detail }), onUploadFilesToIntegration: (detail) => syncOps.uploadFilesToIntegration({ detail }), onLoadMore: syncOps.handleLoadMore, onIntegrationFilterChange: syncOps.handleIntegrationFilterChange, onIntegrationScheduleSync: (detail) => schedule.setIntegrationScheduleSync({ detail }), onCrowdinScheduleSync: (detail) => schedule.setCrowdinScheduleSync({ detail }), onIntegrationDisableSync: (detail) => schedule.disableIntegrationSync({ detail }), onCrowdinDisableSync: (detail) => schedule.disableCrowdinSync({ detail }) })] }), jsxRuntimeExports.jsx(Toaster, {}), jsxRuntimeExports.jsx(AsyncProgress, { ref: asyncProgressRef, onCancelJob: syncOps.cancelJob }), jsxRuntimeExports.jsx(SubscriptionModal, { modalRef: subscriptionModal, subscriptionLink: subscriptionLink, name: name, onLogout: integrationLogout }), jsxRuntimeExports.jsx(PermissionsModal, { modalRef: permissionsModalRef, isLoading: permissions.isLoading, permissionUserOptions: permissions.permissionUserOptions, selectedUsers: permissions.permissionsSelectedUsers, isConfirmVisible: permissions.isPermissionsConfirmVisible, permissionsSummary: permissions.permissionsSummary, hasOrganization: hasOrganization, zenModeUrl: zenModeUrl, saveDisabled: !permissions.isPermissionsLoaded, onUsersChange: permissions.checkUsers, onSave: permissions.inviteUsers }), excludedTargetLanguages && (jsxRuntimeExports.jsx(ExcludedLanguagesModal, { modalRef: syncOps.languageModalRef, languageSelectRef: syncOps.languageSelectRef, languageModalState: syncOps.languageModalState, onUpdateTargetLanguages: syncOps.updateCrowdinFilesTargetLanguages, onSyncToCrowdin: syncOps.uploadFilesToCrowdinWithExcludedLanguages })), infoModal && jsxRuntimeExports.jsx(InfoModal, { modalRef: infoModalRef, infoModal: infoModal }), configurationFields && (jsxRuntimeExports.jsx(SettingsModal, { modalRef: settingsModalRef, isLoading: settings.isLoading, configurationFields: configurationFields, isSaveDisabled: settings.isSettingsSaveDisabled, settingsFieldChange: settings.settingsFieldChange, registerSettingsField: settings.registerSettingsField, fieldErrors: settings.fieldErrors, onSave: settings.saveSettings })), ((syncNewElements === null || syncNewElements === void 0 ? void 0 : syncNewElements.crowdin) || (syncNewElements === null || syncNewElements === void 0 ? void 0 : syncNewElements.integration)) && (jsxRuntimeExports.jsx(ScheduleModal, { modalRef: scheduleModalRef, selectedFilesCheckboxRef: schedule.selectedFilesCheckboxRef, allFolderFilesCheckboxRef: schedule.allFolderFilesCheckboxRef, newFilesCheckboxRef: schedule.newFilesCheckboxRef, scheduleModalState: schedule.scheduleModalState, isSaveDisabled: schedule.isScheduleSaveDisabled, onChangeOptions: schedule.onChangeAutoSynchronizationOptions, onSave: schedule.saveScheduleSync })), jsxRuntimeExports.jsx(UserErrorDetailModal, { modalRef: userErrorDetailModalRef, selectedUserError: selectedUserError, onClose: onUserErrorDetailClose })] }));
49688
49749
  };
49689
49750
 
49690
49751
  initializeCrowdinIframeContext();