l-min-components 1.7.1314 → 1.7.1315

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.
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "l-min-components",
3
- "version": "1.7.1314",
3
+ "version": "1.7.1315",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src/assets",
7
7
  "src/components",
8
- "src/hooks"
8
+ "src/hooks",
9
+ "src/utils"
9
10
  ],
10
11
  "scripts": {
11
12
  "dev": "vite",
@@ -0,0 +1,3 @@
1
+ const authRoutes = ["/"];
2
+
3
+ export default authRoutes;
@@ -0,0 +1,131 @@
1
+ import Axios from "axios";
2
+ import { toast } from "react-toastify";
3
+
4
+ const pendingRequests = new Map();
5
+
6
+ const axios = Axios.create({
7
+ baseURL:
8
+ window.location.hostname.includes("staging") ||
9
+ window.location.hostname.includes("localhost")
10
+ ? "https://dev-117782726-api.learngual.com"
11
+ : "https://api.learngual.com",
12
+ });
13
+
14
+ axios.interceptors.request.use(
15
+ async (config) => {
16
+ // preventing duplicate requests >> starts
17
+ const requestKey = config.url;
18
+
19
+ if (pendingRequests.has(requestKey)) {
20
+ console.log("preventing::", requestKey);
21
+ return Promise.reject(new Axios.Cancel("Duplicate request"));
22
+ }
23
+
24
+ const source = Axios.CancelToken.source();
25
+ pendingRequests.set(requestKey, source);
26
+ config.cancelToken = source.token;
27
+ // preventing duplicate requests ends >>
28
+
29
+ const cookies = document.cookie;
30
+ const cookieArray = cookies.split(";");
31
+
32
+ let token;
33
+ let defaultAccountID;
34
+
35
+ for (let i = 0; i < cookieArray.length; i++) {
36
+ const cookie = cookieArray[i].trim();
37
+
38
+ if (cookie.startsWith("access=")) {
39
+ token = cookie.substring("access=".length, cookie.length);
40
+ break;
41
+ }
42
+ }
43
+
44
+ for (let i = 0; i < cookieArray.length; i++) {
45
+ const cookie = cookieArray[i].trim();
46
+ if (cookie.startsWith("defaultAccountID=")) {
47
+ defaultAccountID = cookie.substring(
48
+ "defaultAccountID=".length,
49
+ cookie.length
50
+ );
51
+ break;
52
+ }
53
+ }
54
+
55
+ // Add additional logic to check if the current request path matches any authRoutes
56
+ // const currentPath = config.url;
57
+ // const matchedRoute = authRoutes.find((route) =>
58
+ // currentPath.includes(route)
59
+ // );
60
+ if (token) {
61
+ config.headers.Authorization = `Bearer ${token}`;
62
+ const date = new Date();
63
+ const params = {
64
+ // ...config.params,
65
+ // // _tz: date.getTimezoneOffset() / 60,
66
+ // _account: defaultAccountID,
67
+ // //
68
+
69
+ _account: defaultAccountID,
70
+ ...config.params,
71
+ };
72
+ config.params = params;
73
+ }
74
+
75
+ return config;
76
+ },
77
+ (error) => {
78
+ return Promise.reject(error);
79
+ }
80
+ );
81
+
82
+ // axios.interceptors.response.use(
83
+ // function (response) {
84
+ // // Any status code that lie within the range of 2xx cause this function to trigger
85
+ // // Do something with response data
86
+ // if (response.status === 403) {
87
+ // // Handle unauthorized access
88
+ // toast.error("Unauthorized");
89
+ // //console.log(response.status, "+++++++REPONSE ====");
90
+ // }
91
+ // return response;
92
+ // },
93
+ // function (error) {
94
+ // // Any status codes that fall outside the range of 2xx cause this function to trigger
95
+ // // Do something with response error
96
+ // return Promise.reject(error);
97
+ // }
98
+ // );
99
+
100
+ axios.interceptors.response.use(
101
+ (response) => {
102
+ pendingRequests.delete(response.config.url);
103
+ console.log("deleting", response.config.url);
104
+ if (
105
+ response.status === 207 &&
106
+ !window.location.host.includes("staging") &&
107
+ !window.location.host.includes("local")
108
+ ) {
109
+ window.location.href = "/auth/account-type";
110
+ }
111
+ return response;
112
+ },
113
+ (err) => {
114
+ if (err) {
115
+ if (err.config) pendingRequests.delete(err.config.url);
116
+
117
+ // If it's not a cancellation error, handle it as usual
118
+ if (!Axios.isCancel(err)) {
119
+ if (err.response?.status === 403) {
120
+ toast.error(err?.response.data?.detail);
121
+ }
122
+ return err;
123
+ } else {
124
+ return {};
125
+ }
126
+ // return err;
127
+ }
128
+ }
129
+ );
130
+
131
+ export default axios;
@@ -0,0 +1,13 @@
1
+ import Dexie from "dexie";
2
+ import locale from "iso-639-1";
3
+ export const db = new Dexie("translations");
4
+
5
+ const languages = locale.getAllCodes();
6
+ db.version(2).stores({
7
+ kwarg: "&key, value, screenId",
8
+ ...languages.reduce(
9
+ (x, y) => ({ ...x, [y]: "&key, value, screenId, updatedAt" }),
10
+ {}
11
+ ),
12
+ words: "&text, screenId, updatedAt",
13
+ });
@@ -0,0 +1,59 @@
1
+ import Cookies from "js-cookie";
2
+
3
+ /**
4
+ * Delete a single cookie for specific domain and path configuration
5
+ * @param {string} name - Cookie name to delete
6
+ * @param {Object} options - Cookie options (domain, expires, path)
7
+ */
8
+ const deleteSingleCookie = (name, options) => {
9
+ Cookies.remove(name, options);
10
+ };
11
+
12
+ /**
13
+ * Delete cookies on domain and all subdomains
14
+ * @param {string|string[]} names - Cookie name or array of cookie names to delete
15
+ */
16
+ const deleteCookies = (names) => {
17
+ // Convert single name to array for consistent handling
18
+ const cookieNames = Array.isArray(names) ? names : [names];
19
+
20
+ const date = new Date();
21
+ date.setHours(date.getHours() - 1);
22
+ const { host } = window.location;
23
+ const env = host.includes("localhost")
24
+ ? "local"
25
+ : host.includes("test") || host.includes("staging")
26
+ ? "staging"
27
+ : "production";
28
+
29
+ // Process each cookie name
30
+ cookieNames.forEach(name => {
31
+ if (env === "local") {
32
+ // For local environment
33
+ const localDomains = [".localhost", "localhost"];
34
+ localDomains.forEach(domain => {
35
+ deleteSingleCookie(name, {
36
+ domain,
37
+ expires: date,
38
+ path: "/",
39
+ });
40
+ });
41
+ // Also delete without domain specification for local testing
42
+ deleteSingleCookie(name, {
43
+ expires: date,
44
+ path: "/",
45
+ });
46
+ } else {
47
+ // For staging and production environments
48
+ const domain = ".learngual.com";
49
+ // Delete for main domain and all potential subdomains
50
+ deleteSingleCookie(name, {
51
+ domain,
52
+ expires: date,
53
+ path: "/",
54
+ });
55
+ }
56
+ });
57
+ };
58
+
59
+ export default deleteCookies;
@@ -0,0 +1,2 @@
1
+ const sheetUrl =
2
+ "https://docs.google.com/spreadsheets/d/1VoV2Etjg9Ft4bhML_7IAfNRQ9AxmpU-JvKgal0QGhdI/edit?usp=sharing";
@@ -0,0 +1,68 @@
1
+ import { db } from "./db";
2
+ import axios from "./axiosConfig";
3
+
4
+ async function loadTranslations(language = "ko") {
5
+ await new Promise((resolve) => {
6
+ //check if loading else resolve
7
+ if (document.readyState === "complete") {
8
+ return resolve();
9
+ }
10
+ window.addEventListener("load", resolve);
11
+ });
12
+
13
+ const screenId = btoa(window.location.href);
14
+
15
+ /**
16
+ * @typedef {{text: string; screenId: string; updatedAt: string}} WORDS
17
+ * @type {WORDS[]}
18
+ */
19
+ const words = await db.words.where({ screenId }).toArray();
20
+ const texts = words.map((x) => x.text); // list of texts
21
+
22
+ /**
23
+ * @typedef {{ key: string; value: string; screenId: string }} Kwarg
24
+ * @type {Kwarg[]} kwargList
25
+ */
26
+ const kwargList = await db.kwarg.where({ screenId }).toArray();
27
+ const kwargs = kwargList.reduce((x, t) => ({ ...x, [t.key]: t.value }), {});
28
+ /**
29
+ * @type {{ data: {has_translated: boolean; result: string[]} }}
30
+ */
31
+ const res = await axios({
32
+ url: "/iam/v1/utils/translate/",
33
+ method: "POST",
34
+ data: {
35
+ language,
36
+ sentences: texts,
37
+ kwargs,
38
+ },
39
+ params: {
40
+ _account: "",
41
+ },
42
+ authRequired: false,
43
+ });
44
+ // build an object using texts as key and results from request as value
45
+ if (!res.data) return {}; // somehow data was not found
46
+ const translations = texts.reduce(
47
+ (x, y, z) => ({ ...x, [y]: res.data.result[z] }),
48
+ {}
49
+ );
50
+ db[language].bulkPut(
51
+ Object.keys(translations).map((x, i) => ({
52
+ key: texts[i],
53
+ value: translations[x],
54
+ screenId,
55
+ updatedAt: new Date().toDateString(),
56
+ }))
57
+ );
58
+
59
+ return translations;
60
+ }
61
+
62
+ export function extractBracedText(text) {
63
+ const pattern = /\{([^}]+)\}/g; // Match all occurrences of text within {}
64
+ const matches = text.match(pattern);
65
+ return matches ? matches.map((match) => match.slice(1, -1)) : []; // Remove braces
66
+ }
67
+
68
+ export default loadTranslations;
@@ -0,0 +1,81 @@
1
+ function writeString(view, offset, string) {
2
+ for (let i = 0; i < string.length; i++) {
3
+ view.setUint8(offset + i, string.charCodeAt(i));
4
+ }
5
+ }
6
+
7
+ function audioBufferToWav(audioBuffer) {
8
+ const numChannels = audioBuffer.numberOfChannels;
9
+ const sampleRate = audioBuffer.sampleRate;
10
+ const format = 1;
11
+ const bitDepth = 16;
12
+
13
+ const bytesPerSample = bitDepth / 8;
14
+ const blockAlign = numChannels * bytesPerSample;
15
+ const byteRate = sampleRate * blockAlign;
16
+
17
+ const pcmChannels = [];
18
+ for (let i = 0; i < numChannels; i++) {
19
+ pcmChannels.push(audioBuffer.getChannelData(i));
20
+ }
21
+
22
+ const pcmDataLength = pcmChannels[0].length;
23
+ const dataSize = pcmDataLength * numChannels * bytesPerSample;
24
+
25
+ const interleavedPcm = new Int16Array(pcmDataLength * numChannels);
26
+ let outputOffset = 0;
27
+ for (let i = 0; i < pcmDataLength; i++) {
28
+ for (let channel = 0; channel < numChannels; channel++) {
29
+ let sample = pcmChannels[channel][i];
30
+ sample = Math.max(-1, Math.min(1, sample));
31
+ interleavedPcm[outputOffset++] =
32
+ sample < 0 ? sample * 0x8000 : sample * 0x7fff;
33
+ }
34
+ }
35
+
36
+ const buffer = new ArrayBuffer(44 + dataSize);
37
+ const view = new DataView(buffer);
38
+
39
+ writeString(view, 0, "RIFF");
40
+ view.setUint32(4, 36 + dataSize, true);
41
+ writeString(view, 8, "WAVE");
42
+
43
+ writeString(view, 12, "fmt ");
44
+ view.setUint32(16, 16, true);
45
+ view.setUint16(20, format, true);
46
+ view.setUint16(22, numChannels, true);
47
+ view.setUint32(24, sampleRate, true);
48
+ view.setUint32(28, byteRate, true);
49
+ view.setUint16(32, blockAlign, true);
50
+ view.setUint16(34, bitDepth, true);
51
+
52
+ writeString(view, 36, "data");
53
+ view.setUint32(40, dataSize, true);
54
+
55
+ new Int16Array(buffer, 44).set(interleavedPcm);
56
+
57
+ return new Blob([view], { type: "audio/wav" });
58
+ }
59
+
60
+ export default async function convertBlobToWav(blob) {
61
+ if (!blob || blob.size === 0) {
62
+ throw new Error("Input blob is empty or null. Cannot convert to WAV.");
63
+ }
64
+
65
+ let audioContext;
66
+ try {
67
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
68
+ const arrayBuffer = await blob.arrayBuffer();
69
+ const audioBuffer = await audioContext.decodeAudioData(arrayBuffer);
70
+ const wavBlob = audioBufferToWav(audioBuffer);
71
+
72
+ return wavBlob;
73
+ } catch (error) {
74
+ console.error("Error during blob to WAV conversion:", error);
75
+ throw new Error(`Failed to convert blob to WAV: ${error.message}`);
76
+ } finally {
77
+ if (audioContext && audioContext.state !== "closed") {
78
+ await audioContext.close();
79
+ }
80
+ }
81
+ }