@econneq/utility-functions 1.0.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.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # @econneq/gql-auth
2
+ ![NPM Version](https://img.shields.io/npm/v/@econneq/utilility-functions)
3
+
4
+ A high-performance, multi-tenant GraphQL abstraction layer designed for **Next.js (App Router)** and **Apollo Client**.
5
+
6
+ This package standardizes how frontend projects interact with a Django GraphQL backend, handling tenant detection, SSR cookie forwarding, and multipart file uploads.
7
+
8
+ ---
9
+
10
+ ## ✨ Features
11
+
12
+ * 🌍 **Automatic Tenant Injection**: Detects subdomains and injects `X-Tenant-ID`, `X-Process-ID`, and `X-Widget-ID` headers.
13
+ * 🔐 **SSR Ready**: Built-in support for `next/headers` to forward authentication cookies during Server-Side Rendering.
14
+ * 📁 **Smart File Uploads**: Simplifies `multipart/form-data` logic for GraphQL mutations.
15
+ * 🚀 **Lightweight**: Optimized to use peer dependencies, keeping your bundle size minimal.
16
+
17
+ ---
18
+
19
+ ## 📦 Installation
20
+
21
+ ```bash
22
+ npm install @econneq/gql-auth
@@ -0,0 +1,30 @@
1
+ type SubmitOptions = {
2
+ newData: any;
3
+ editData?: any;
4
+ mutationName: string;
5
+ modelName: string;
6
+ successField?: string;
7
+ query: any;
8
+ params: any;
9
+ router: any;
10
+ token?: string;
11
+ config: {
12
+ protocol: string;
13
+ RootApi: string;
14
+ Subdomains: any[];
15
+ };
16
+ onAlert: (options: {
17
+ title: string;
18
+ status: boolean;
19
+ duration: number;
20
+ }) => void;
21
+ redirect?: boolean;
22
+ redirectPath?: string;
23
+ returnResponseField?: boolean;
24
+ returnResponseObject?: boolean;
25
+ reload?: boolean;
26
+ getFileMap?: (item: any) => Record<string, File>;
27
+ };
28
+ export declare const ApiFactory: (options: SubmitOptions) => Promise<any>;
29
+ export {};
30
+ //# sourceMappingURL=api-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-factory.d.ts","sourceRoot":"","sources":["../src/api-factory.ts"],"names":[],"mappings":"AAIA,KAAK,aAAa,GAAG;IACnB,OAAO,EAAE,GAAG,CAAC;IACb,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,GAAG,CAAC;IACX,MAAM,EAAE,GAAG,CAAC;IACZ,MAAM,EAAE,GAAG,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,GAAG,EAAE,CAAC;KACnB,CAAC;IACF,OAAO,EAAE,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,SAAS,aAAa,iBA0DtD,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { uploadGraphQLMutation } from "./upload-gql";
2
+ import { removeEmptyFields } from "./utilsGeneral";
3
+ export const ApiFactory = async (options) => {
4
+ const { newData, editData, mutationName, modelName, successField = "title", query, router, params, reload = true, redirect, redirectPath, returnResponseField = false, returnResponseObject = false, getFileMap, token, config, onAlert } = options;
5
+ const items = Array.isArray(newData || editData) ? (newData || editData) : [(newData || editData)];
6
+ const successMessages = [];
7
+ const errorMessages = [];
8
+ let responseFieldData = null;
9
+ for (const res of items) {
10
+ try {
11
+ console.log("launching e-conneq/auth-gql ......");
12
+ const response = await uploadGraphQLMutation({
13
+ query: query.loc?.source.body || "",
14
+ variables: removeEmptyFields(res),
15
+ fileMap: getFileMap ? getFileMap(res) : {},
16
+ params, // Pass domain params for multi-tenancy
17
+ token,
18
+ config, // Pass global URLs and Subdomain list
19
+ });
20
+ console.log("response e-conneq/auth-gql ......");
21
+ const result = response?.data?.[mutationName]?.[modelName];
22
+ if (response?.data?.[mutationName]) {
23
+ successMessages.push(result?.[successField] || "operation successful ✅");
24
+ if (returnResponseObject)
25
+ responseFieldData = response.data[mutationName];
26
+ if (result?.id && returnResponseField)
27
+ responseFieldData = result[successField];
28
+ if (result?.id || result?.token || result === null) {
29
+ }
30
+ }
31
+ // Error Handling
32
+ if (response?.errors) {
33
+ handleGraphQLErrors(response.errors, errorMessages);
34
+ }
35
+ }
36
+ catch (err) {
37
+ errorMessages.push(`Error: ${err.message}`);
38
+ }
39
+ }
40
+ // Handle Response/UI Logic
41
+ if (successMessages.length > 0 && errorMessages.length === 0) {
42
+ if (returnResponseObject || returnResponseField)
43
+ return responseFieldData;
44
+ onAlert({ title: "successfully submitted ✅", status: true, duration: 3000 });
45
+ if (redirect && redirectPath)
46
+ router.push(redirectPath);
47
+ else if (reload)
48
+ window.location.reload();
49
+ }
50
+ else if (errorMessages.length > 0) {
51
+ onAlert({
52
+ title: `❌ Errors:\n${errorMessages.join("\n")}`,
53
+ status: false,
54
+ duration: 5000
55
+ });
56
+ }
57
+ };
58
+ // Internal Helper for Error Parsing
59
+ const handleGraphQLErrors = (errors, errorList) => {
60
+ errors.forEach(error => {
61
+ if (error.message.includes("duplicate"))
62
+ errorList.push("Record Already Exists ❌");
63
+ else if (error.message.includes("Authentication"))
64
+ errorList.push("Login Required ❌");
65
+ else
66
+ errorList.push(error.message);
67
+ });
68
+ };
@@ -0,0 +1,15 @@
1
+ import { ApolloClient } from '@apollo/client';
2
+ export default function getApolloClient(options?: {
3
+ csrfToken?: string;
4
+ cookie?: string;
5
+ }, domain?: string | null, config?: {
6
+ protocol: string;
7
+ RootApi: string;
8
+ Subdomains: {
9
+ subdomain: string;
10
+ processId?: string;
11
+ widgetID?: string;
12
+ }[];
13
+ apiKey: string;
14
+ }): Promise<ApolloClient | null>;
15
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAA2B,MAAM,gBAAgB,CAAC;AAGvE,wBAA8B,eAAe,CAC3C,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,EACjD,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,EACtB,MAAM,CAAC,EAAE;IACP,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC3E,MAAM,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAgD9B"}
package/dist/client.js ADDED
@@ -0,0 +1,44 @@
1
+ // src/client.ts
2
+ import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
3
+ import Cookies from "js-cookie";
4
+ export default async function getApolloClient(options, domain, config) {
5
+ if (!config)
6
+ throw new Error("Configuration is required for getApolloClient");
7
+ const isServer = typeof window === "undefined";
8
+ const { protocol, RootApi, Subdomains, apiKey } = config;
9
+ // 1. Find Tenant Data
10
+ const tenant = domain
11
+ ? Subdomains.find((s) => s.subdomain.toLowerCase() === domain.toLowerCase())
12
+ : null;
13
+ const baseUrl = `${protocol + RootApi}`;
14
+ const uri = `${baseUrl}/graphql/`;
15
+ const csrfToken = options?.csrfToken || (!isServer ? Cookies.get("csrftoken") : "");
16
+ const dynamicHost = domain
17
+ ? `${domain}.${RootApi}`
18
+ : RootApi;
19
+ const headers = {
20
+ 'Content-Type': 'application/json',
21
+ 'X-API-KEY': apiKey,
22
+ ...(options?.cookie && { 'Cookie': options.cookie }),
23
+ ...(csrfToken && { 'X-CSRFToken': csrfToken }),
24
+ ...(tenant && {
25
+ 'X-Tenant-ID': tenant.subdomain,
26
+ 'X-Process-ID': tenant?.processId || "",
27
+ 'X-Widget-ID': tenant?.widgetID || "",
28
+ }),
29
+ 'Referer': baseUrl,
30
+ 'Host': isServer ? dynamicHost : window.location.host,
31
+ };
32
+ return new ApolloClient({
33
+ link: new HttpLink({
34
+ uri,
35
+ fetch, // Next.js polyfills fetch on server automatically
36
+ headers,
37
+ }),
38
+ cache: new InMemoryCache(),
39
+ defaultOptions: {
40
+ mutate: { fetchPolicy: 'no-cache' },
41
+ query: { fetchPolicy: 'no-cache' },
42
+ },
43
+ });
44
+ }
@@ -0,0 +1,67 @@
1
+ import { ReactNode } from "react";
2
+ export type ApolloOptions = {
3
+ csrfToken?: string;
4
+ cookie?: string;
5
+ };
6
+ export type SelectOptions = string | {
7
+ label: string;
8
+ value: string | number;
9
+ };
10
+ export interface InterLoginData {
11
+ username: string;
12
+ password: string;
13
+ parent?: boolean;
14
+ }
15
+ export interface JwtPayload {
16
+ iss?: string;
17
+ sub?: string;
18
+ aud?: string[] | string;
19
+ exp: number;
20
+ nbf?: number;
21
+ iat?: number;
22
+ jti?: string;
23
+ user_id?: number;
24
+ username?: string;
25
+ matricle?: string;
26
+ photo?: string;
27
+ is_superuser?: boolean;
28
+ is_staff?: boolean;
29
+ is_active?: boolean;
30
+ is_hod?: boolean;
31
+ role?: string;
32
+ dept?: string[] | any;
33
+ page?: string[] | any;
34
+ school?: number[] | any;
35
+ domain?: number[] | any;
36
+ language?: string[];
37
+ last_login?: any;
38
+ }
39
+ export interface InterField {
40
+ name: string;
41
+ label: string;
42
+ inputType: 'text' | "textarea" | 'number' | 'float' | 'search-select' | 'select' | 'file' | 'date' | 'password';
43
+ type: 'text' | 'textarea' | 'number' | 'float' | 'email' | 'date' | 'file' | 'password';
44
+ required?: boolean;
45
+ min?: number;
46
+ max?: number;
47
+ message?: string;
48
+ options?: {
49
+ value: string | number;
50
+ label: string;
51
+ }[];
52
+ show?: boolean;
53
+ sort?: boolean;
54
+ placeholder?: string;
55
+ acceptedFileType?: string;
56
+ icon?: ReactNode;
57
+ labelIcon?: ReactNode;
58
+ }
59
+ export interface InterFieldList {
60
+ rowId: number | string;
61
+ desktopCols: 1 | 2 | 3 | 4;
62
+ mobileCols: 1 | 2;
63
+ fields: InterField[];
64
+ }
65
+ export { capitalizeEachWord, errorLog, getToken, getUser, logout, getStoredFont, getTimeAgo, getLanguage, getTemplate, decodeUrlID, removeEmptyFields, Alert, validateFormFields, lastYears, formatText, } from './utilsGeneral.js';
66
+ export { getProfile, getClass, getClassName, getLevel, getAcademicYear, getAcademicYearStudent } from './utilsSchool.js';
67
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { capitalizeEachWord, errorLog, getToken, getUser, logout, getStoredFont, getTimeAgo, getLanguage, getTemplate, decodeUrlID, removeEmptyFields, Alert, validateFormFields, lastYears, formatText, } from './utilsGeneral.js';
2
+ export { getProfile, getClass, getClassName, getLevel, getAcademicYear, getAcademicYearStudent } from './utilsSchool.js';
@@ -0,0 +1,14 @@
1
+ type ServerQueryArgs = {
2
+ query: any;
3
+ variables?: Record<string, any>;
4
+ domain?: string | null;
5
+ config: {
6
+ protocol: string;
7
+ RootApi: string;
8
+ Subdomains: any[];
9
+ apiKey: string;
10
+ };
11
+ };
12
+ export declare function queryServerGraphQL<T = any>({ query, variables, domain, config, }: ServerQueryArgs): Promise<T | null>;
13
+ export {};
14
+ //# sourceMappingURL=query-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-server.d.ts","sourceRoot":"","sources":["../src/query-server.ts"],"names":[],"mappings":"AAKA,KAAK,eAAe,GAAG;IACrB,KAAK,EAAE,GAAG,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,GAAG,EAAE,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,CAAC,GAAG,GAAG,EAAE,EAChD,KAAK,EACL,SAAc,EACd,MAAM,EACN,MAAM,GACP,EAAE,eAAe,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CA8BrC"}
@@ -0,0 +1,30 @@
1
+ // src/server-query.ts
2
+ 'use server';
3
+ import { cookies } from 'next/headers';
4
+ import getApolloClient from './client';
5
+ export async function queryServerGraphQL({ query, variables = {}, domain, config, }) {
6
+ try {
7
+ // 1. Get server-side cookies
8
+ const cookieStore = await cookies();
9
+ const cookieString = cookieStore.toString();
10
+ // 2. Call getApolloClient with the correct 3 arguments:
11
+ // Arg 1: options (cookies)
12
+ // Arg 2: domain (subdomain string)
13
+ // Arg 3: config (URLs and Subdomain array)
14
+ const client = await getApolloClient({ cookie: cookieString }, domain, config);
15
+ if (!client)
16
+ return null;
17
+ const result = await client.query({
18
+ query,
19
+ variables,
20
+ fetchPolicy: 'no-cache',
21
+ });
22
+ console.log("getting @e-conneq/gql-auth ........");
23
+ return result?.data;
24
+ }
25
+ catch (err) {
26
+ // On the server, we console.error instead of using Swal
27
+ console.error("GraphQL Server Error:", err);
28
+ return null;
29
+ }
30
+ }
@@ -0,0 +1,21 @@
1
+ type UploadGraphQLArgs = {
2
+ query: string;
3
+ variables: Record<string, any>;
4
+ fileMap: Record<string, File>;
5
+ token?: string;
6
+ params: any;
7
+ config: {
8
+ protocol: string;
9
+ RootApi: string;
10
+ Subdomains: {
11
+ subdomain: string;
12
+ id: string | number;
13
+ processId: string;
14
+ widgetID: string;
15
+ }[];
16
+ apiKey?: string;
17
+ };
18
+ };
19
+ export declare function uploadGraphQLMutation({ query, variables, fileMap, token, params, config }: UploadGraphQLArgs): Promise<any>;
20
+ export {};
21
+ //# sourceMappingURL=upload-gql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload-gql.d.ts","sourceRoot":"","sources":["../src/upload-gql.ts"],"names":[],"mappings":"AAAA,KAAK,iBAAiB,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,GAAG,CAAC;IACZ,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAC9F,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF,wBAAsB,qBAAqB,CAAC,EAC1C,KAAK,EACL,SAAS,EACT,OAAO,EACP,KAAK,EACL,MAAM,EACN,MAAM,EACP,EAAE,iBAAiB,gBA0DnB"}
@@ -0,0 +1,45 @@
1
+ export async function uploadGraphQLMutation({ query, variables, fileMap, token, params, config }) {
2
+ const { protocol, RootApi, Subdomains, apiKey } = config;
3
+ const formData = new FormData();
4
+ const updatedVariables = { ...variables };
5
+ // 1. Prepare GraphQL Multipart Request (Spec compliant)
6
+ Object.keys(fileMap).forEach((key) => {
7
+ updatedVariables[key] = null;
8
+ });
9
+ const operations = { query, variables: updatedVariables };
10
+ const map = {};
11
+ const fileKeys = Object.keys(fileMap);
12
+ fileKeys.forEach((key, index) => {
13
+ map[`${index}`] = [`variables.${key}`];
14
+ });
15
+ formData.append("operations", JSON.stringify(operations));
16
+ formData.append("map", JSON.stringify(map));
17
+ fileKeys.forEach((key, index) => {
18
+ formData.append(`${index}`, fileMap[key]);
19
+ });
20
+ // 2. Multi-tenant URL and Data Logic
21
+ const requestedDomain = params?.domain || "";
22
+ const tenantData = Subdomains.find((d) => d.subdomain.toLowerCase() === requestedDomain.toLowerCase());
23
+ // Determine URL: Use subdomain api if tenant exists, otherwise use root API
24
+ const API_LINK = `${protocol + RootApi}/graphql/`;
25
+ // 3. Construct Headers
26
+ const headers = {
27
+ ...(token ? { "Authorization": `Bearer ${token}` } : {}),
28
+ ...(apiKey ? { "X-API-KEY": apiKey } : {}),
29
+ };
30
+ // Inject specific tenant metadata into headers for Django to read
31
+ if (tenantData) {
32
+ headers["X-Tenant-ID"] = String(tenantData.subdomain);
33
+ headers["X-Process-ID"] = tenantData.processId || "";
34
+ headers["X-Widget-ID"] = tenantData.widgetID || "";
35
+ }
36
+ // 4. Execute Fetch
37
+ const res = await fetch(API_LINK, {
38
+ method: "POST",
39
+ headers,
40
+ body: formData,
41
+ // credentials: "include" is important if you use cookies alongside headers
42
+ credentials: "include",
43
+ });
44
+ return await res.json();
45
+ }
@@ -0,0 +1,24 @@
1
+ import { InterFieldList, JwtPayload } from ".";
2
+ export declare const capitalizeEachWord: (str: string) => string;
3
+ export declare const errorLog: (err: any, show?: boolean) => string;
4
+ export declare const getToken: () => string | null;
5
+ export declare const getUser: () => JwtPayload | null;
6
+ export declare const getStoredFont: (FONTS: string[]) => string;
7
+ export declare const getTimeAgo: (dateStr: string, type: "past" | "future") => string;
8
+ export declare const getLanguage: () => "en" | "fr";
9
+ export declare const getTemplate: () => number;
10
+ export declare const decodeUrlID: (urlID: string) => string;
11
+ export declare function getAcademicYear(): string;
12
+ export declare const removeEmptyFields: (obj: any) => any;
13
+ export declare const Alert: ({ title, duration, status }: {
14
+ title: string;
15
+ duration: 1000 | 1500 | 2000 | 3000 | 4000 | 5000;
16
+ status?: boolean;
17
+ }) => void;
18
+ export declare const validateFormFields: (formData: any, currentStepFields: InterFieldList[]) => Record<string, string>;
19
+ export declare const lastYears: (number: number) => {
20
+ value: string;
21
+ label: string;
22
+ }[];
23
+ export declare const formatText: (text: string) => string;
24
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC;AAI/C,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,WAM7C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,KAAK,GAAG,EAAE,OAAO,OAAO,WAyBhD,CAAC;AAKF,eAAO,MAAM,QAAQ,qBAKpB,CAAC;AAEF,eAAO,MAAM,OAAO,yBAKnB,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,EAAE,WAO5C,CAAC;AAEF,eAAO,MAAM,UAAU,GACnB,SAAS,MAAM,EACf,MAAM,MAAM,GAAG,QAAQ,KACxB,MA0CF,CAAC;AAGF,eAAO,MAAM,WAAW,mBAOvB,CAAC;AAEF,eAAO,MAAM,WAAW,cAMvB,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,WAIxC,CAAA;AAED,wBAAgB,eAAe,IAAI,MAAM,CAUxC;AAED,eAAO,MAAM,iBAAiB,GAAI,KAAK,GAAG,QAUzC,CAAC;AAGF,eAAO,MAAM,KAAK,GACd,6BACI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,SAU7F,CAAC;AAIF,eAAO,MAAM,kBAAkB,GAAI,UAAU,GAAG,EAAE,mBAAmB,cAAc,EAAE,2BAkDpF,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM;;;GAKvC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,WAiDtC,CAAC"}
package/dist/utils.js ADDED
@@ -0,0 +1,257 @@
1
+ import Swal from "sweetalert2";
2
+ import Cookies from "js-cookie";
3
+ import { jwtDecode } from "jwt-decode";
4
+ export const capitalizeEachWord = (str) => {
5
+ if (!str)
6
+ return ''; // Handle empty or null strings
7
+ return str
8
+ .split(' ')
9
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
10
+ .join(' ');
11
+ };
12
+ export const errorLog = (err, show) => {
13
+ let mes = "An unknown error occurred";
14
+ if (typeof err === "string")
15
+ mes = err;
16
+ else if (err?.graphQLErrors?.length > 0)
17
+ mes = err.graphQLErrors.map((e) => e.message).join('\n');
18
+ else if (err?.networkError) {
19
+ const netErr = err.networkError;
20
+ if ("result" in netErr && netErr.result?.errors?.length > 0)
21
+ mes = netErr.result.errors.map((e) => e.message).join('\n');
22
+ else if (netErr.message)
23
+ mes = netErr.message;
24
+ }
25
+ else if (err?.extraInfo)
26
+ mes = String(err.extraInfo);
27
+ else if (err?.message)
28
+ mes = err.message;
29
+ if (show) {
30
+ Swal.fire({
31
+ title: mes,
32
+ icon: 'error',
33
+ timer: 3000,
34
+ timerProgressBar: true,
35
+ showConfirmButton: false,
36
+ });
37
+ }
38
+ return mes;
39
+ };
40
+ const currentYear = new Date().getFullYear();
41
+ export const getToken = () => {
42
+ if (typeof window === "undefined") {
43
+ return null;
44
+ }
45
+ return Cookies.get("token") || localStorage.getItem("token");
46
+ };
47
+ export const getUser = () => {
48
+ // const token = typeof window !== 'undefined' ? localStorage.getItem("token") : null;
49
+ const token = getToken();
50
+ const user = token ? jwtDecode(token) : null;
51
+ return user;
52
+ };
53
+ export const getStoredFont = (FONTS) => {
54
+ if (typeof window === "undefined")
55
+ return FONTS[0]; // Default for SSR
56
+ const up = localStorage.getItem("user-pref");
57
+ if (!up)
58
+ return FONTS[5]; // Default to Ubuntu (6th index)
59
+ const fontIndex = parseInt(up[1]) - 1;
60
+ return FONTS[fontIndex] || FONTS[5];
61
+ };
62
+ export const getTimeAgo = (dateStr, type) => {
63
+ if (!dateStr)
64
+ return "";
65
+ const now = new Date();
66
+ const item = new Date(dateStr);
67
+ let diffMs = 0;
68
+ if (type === "past")
69
+ diffMs = now.getTime() - item.getTime();
70
+ if (type === "future")
71
+ diffMs = item.getTime() - now.getTime();
72
+ // Handle invalid date ranges
73
+ if (diffMs < 0) {
74
+ return type === "past" ? "just now" : "Closed";
75
+ }
76
+ const seconds = Math.floor(diffMs / 1000);
77
+ const minutes = Math.floor(seconds / 60);
78
+ const hours = Math.floor(minutes / 60);
79
+ const days = Math.floor(hours / 24);
80
+ const weeks = Math.floor(days / 7);
81
+ const months = Math.floor(days / 30.44); // Use average month length
82
+ const years = Math.floor(days / 365.25); // Account for leap years
83
+ const suffix = type === "past" ? "ago" : "left";
84
+ // 1. Logic for Past Dates (e.g., Posted 2 days ago)
85
+ if (type === "past") {
86
+ if (days === 0)
87
+ return "Today";
88
+ if (days === 1)
89
+ return "Yesterday";
90
+ if (days < 7)
91
+ return `${days} days ago`;
92
+ if (weeks < 5)
93
+ return `${weeks} week${weeks > 1 ? "s" : ""} ago`;
94
+ if (months < 12)
95
+ return `${months} month${months > 1 ? "s" : ""} ago`;
96
+ return `${years} year${years > 1 ? "s" : ""} ago`;
97
+ }
98
+ // 2. Logic for Future Dates (e.g., 5 days left)
99
+ if (days === 0)
100
+ return "Ends today";
101
+ if (days === 1)
102
+ return "1 day left";
103
+ if (days < 7)
104
+ return `${days} days left`;
105
+ if (weeks < 5)
106
+ return `${weeks} week${weeks > 1 ? "s" : ""} left`;
107
+ if (months < 12)
108
+ return `${months} month${months > 1 ? "s" : ""} left`;
109
+ return `${years} year${years > 1 ? "s" : ""} left`;
110
+ };
111
+ export const getLanguage = () => {
112
+ if (typeof window === "undefined")
113
+ return "en";
114
+ const up = localStorage.getItem("user-pref");
115
+ if (!up)
116
+ return "en";
117
+ const langNumber = parseInt(up[0]);
118
+ return langNumber === 1 ? "en" : "fr";
119
+ };
120
+ export const getTemplate = () => {
121
+ if (typeof window === "undefined")
122
+ return 1;
123
+ const up = localStorage.getItem("user-pref");
124
+ if (!up)
125
+ return 1;
126
+ return parseInt(up[2]);
127
+ };
128
+ export const decodeUrlID = (urlID) => {
129
+ const base64DecodedString = decodeURIComponent(urlID); // Decodes %3D%3D to ==
130
+ const id = Buffer.from(base64DecodedString, 'base64').toString('utf-8'); // Decoding from base64
131
+ return id.split(":")[1];
132
+ };
133
+ export function getAcademicYear() {
134
+ const today = new Date();
135
+ const year = today.getFullYear();
136
+ const month = today.getMonth(); // 0 = January, 7 = August
137
+ if (month < 7) {
138
+ return `${year - 1}/${year}`;
139
+ }
140
+ else {
141
+ return `${year}/${year + 1}`;
142
+ }
143
+ }
144
+ export const removeEmptyFields = (obj) => {
145
+ const newObj = {};
146
+ for (const key in obj) {
147
+ // Keep File objects and non-empty values
148
+ if (obj[key] instanceof File ||
149
+ (obj[key] !== null && obj[key] !== undefined && obj[key] !== '')) {
150
+ newObj[key] = obj[key];
151
+ }
152
+ }
153
+ return newObj;
154
+ };
155
+ export const Alert = ({ title, duration, status = true }) => {
156
+ Swal.fire({
157
+ title: capitalizeEachWord(title),
158
+ timer: duration,
159
+ timerProgressBar: true,
160
+ showConfirmButton: false,
161
+ icon: status ? 'success' : 'error',
162
+ });
163
+ };
164
+ export const validateFormFields = (formData, currentStepFields) => {
165
+ const newErrors = {};
166
+ // 1. Flatten the fields from all rows in the step
167
+ const fieldsToValidate = currentStepFields.flatMap(row => row.fields);
168
+ fieldsToValidate.forEach((field) => {
169
+ // Skip validation if the field is hidden
170
+ if (field.show === false)
171
+ return;
172
+ // 2. Get the value from formData
173
+ const value = formData[field.name];
174
+ // 3. Check Required
175
+ if (field.required && (!value || value.toString().trim() === "")) {
176
+ newErrors[field.name] = `${capitalizeEachWord(field.label)} is required`;
177
+ // return;
178
+ }
179
+ // 4. Check Type (Email)
180
+ if (field.type === 'email' && value) {
181
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
182
+ if (!emailRegex.test(value)) {
183
+ newErrors[field.name] = "Invalid email format";
184
+ }
185
+ }
186
+ // 5. Check Numbers (Min/Max)
187
+ if (field.type === 'number' && value) {
188
+ const numValue = Number(value);
189
+ if (field.min !== undefined && numValue < field.min) {
190
+ newErrors[field.name] = `Minimum value is ${field.min}`;
191
+ }
192
+ if (field.max !== undefined && numValue > field.max) {
193
+ newErrors[field.name] = `Maximum value is ${field.max}`;
194
+ }
195
+ }
196
+ // 6. Check Length (Strings)
197
+ if (field.type === 'text' && value) {
198
+ if (field.min !== undefined && value.length < field.min) {
199
+ newErrors[field.name] = `Too short (min ${field.min} chars)`;
200
+ }
201
+ if (field.max !== undefined && value.length > field.max) {
202
+ newErrors[field.name] = `Too long (min ${field.max} chars)`;
203
+ }
204
+ }
205
+ });
206
+ return newErrors;
207
+ };
208
+ export const lastYears = (number) => {
209
+ return Array.from({ length: number }, (_, i) => ({
210
+ value: (currentYear - i).toString(),
211
+ label: (currentYear - i).toString()
212
+ })).sort((a, b) => Number(b.value) - Number(a.value));
213
+ };
214
+ export const formatText = (text) => {
215
+ return text;
216
+ if (!text || typeof text !== "string")
217
+ return "";
218
+ return text
219
+ // Ensure space after "." if it's not part of abbreviation/number
220
+ .replace(/\.(?!\s|$|[a-zA-Z0-9])/g, ". ")
221
+ // Split sentences by ". " but preserve delimiter
222
+ .split(/(\. )/g)
223
+ .map((segment) => {
224
+ if (segment === ". ")
225
+ return segment; // keep delimiter
226
+ return segment
227
+ .split(" ")
228
+ .map((word, i) => {
229
+ if (word === "")
230
+ return word;
231
+ // Preserve existing ALL CAPS words
232
+ if (word.length > 1 && /^[A-Z]+$/.test(word)) {
233
+ return word;
234
+ }
235
+ // Abbreviations like g.a, n.l.n
236
+ if (/^([a-zA-Z]\.)+[a-zA-Z]?$/.test(word)) {
237
+ return word.toUpperCase(); // → G.A, N.L.N
238
+ }
239
+ // Version numbers like 3.0.1
240
+ if (/^\d+(\.\d+)+$/.test(word)) {
241
+ return word;
242
+ }
243
+ // Mixed abbrev/numbers (like p.m2.5 or covid-19 v2.0)
244
+ if (/[a-zA-Z]\.\d/.test(word) || /\d+\.\d+[a-zA-Z]?/.test(word)) {
245
+ return word; // leave as-is
246
+ }
247
+ // First word of sentence → capitalize first letter
248
+ if (i === 0) {
249
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
250
+ }
251
+ // Other words → lowercase
252
+ return word.toLowerCase();
253
+ })
254
+ .join(" ");
255
+ })
256
+ .join("");
257
+ };
@@ -0,0 +1,24 @@
1
+ import { InterFieldList, JwtPayload } from ".";
2
+ export declare const capitalizeEachWord: (str: string) => string;
3
+ export declare const errorLog: (err: any, show?: boolean) => string;
4
+ export declare const logout: () => void;
5
+ export declare const getToken: () => string | null;
6
+ export declare const getUser: () => JwtPayload | null;
7
+ export declare const getStoredFont: (FONTS: string[]) => string;
8
+ export declare const getTimeAgo: (dateStr: string, type: "past" | "future") => string;
9
+ export declare const getLanguage: () => "en" | "fr";
10
+ export declare const getTemplate: () => number;
11
+ export declare const decodeUrlID: (urlID: string) => string;
12
+ export declare const removeEmptyFields: (obj: any) => any;
13
+ export declare const Alert: ({ title, duration, status }: {
14
+ title: string;
15
+ duration: 1000 | 1500 | 2000 | 3000 | 4000 | 5000;
16
+ status?: boolean;
17
+ }) => void;
18
+ export declare const validateFormFields: (formData: any, currentStepFields: InterFieldList[]) => Record<string, string>;
19
+ export declare const lastYears: (number: number) => {
20
+ value: string;
21
+ label: string;
22
+ }[];
23
+ export declare const formatText: (text: string) => string;
24
+ //# sourceMappingURL=utilsGeneral.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilsGeneral.d.ts","sourceRoot":"","sources":["../src/utilsGeneral.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC;AAM/C,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,WAM7C,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,KAAK,GAAG,EAAE,OAAO,OAAO,WAyBhD,CAAC;AAEF,eAAO,MAAM,MAAM,YAclB,CAAC;AAEF,eAAO,MAAM,QAAQ,qBAKpB,CAAC;AAEF,eAAO,MAAM,OAAO,yBAKnB,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,OAAO,MAAM,EAAE,WAO5C,CAAC;AAEF,eAAO,MAAM,UAAU,GACnB,SAAS,MAAM,EACf,MAAM,MAAM,GAAG,QAAQ,KACxB,MA0CF,CAAC;AAEF,eAAO,MAAM,WAAW,mBAOvB,CAAC;AAEF,eAAO,MAAM,WAAW,cAMvB,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,WAIxC,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,KAAK,GAAG,QAUzC,CAAC;AAEF,eAAO,MAAM,KAAK,GACd,6BACI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,SAU7F,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,UAAU,GAAG,EAAE,mBAAmB,cAAc,EAAE,2BAkDpF,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM;;;GAKvC,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,WAiDtC,CAAC"}
@@ -0,0 +1,260 @@
1
+ import Swal from "sweetalert2";
2
+ import Cookies from "js-cookie";
3
+ import { jwtDecode } from "jwt-decode";
4
+ const currentYear = new Date().getFullYear();
5
+ export const capitalizeEachWord = (str) => {
6
+ if (!str)
7
+ return ''; // Handle empty or null strings
8
+ return str
9
+ .split(' ')
10
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
11
+ .join(' ');
12
+ };
13
+ export const errorLog = (err, show) => {
14
+ let mes = "An unknown error occurred";
15
+ if (typeof err === "string")
16
+ mes = err;
17
+ else if (err?.graphQLErrors?.length > 0)
18
+ mes = err.graphQLErrors.map((e) => e.message).join('\n');
19
+ else if (err?.networkError) {
20
+ const netErr = err.networkError;
21
+ if ("result" in netErr && netErr.result?.errors?.length > 0)
22
+ mes = netErr.result.errors.map((e) => e.message).join('\n');
23
+ else if (netErr.message)
24
+ mes = netErr.message;
25
+ }
26
+ else if (err?.extraInfo)
27
+ mes = String(err.extraInfo);
28
+ else if (err?.message)
29
+ mes = err.message;
30
+ if (show) {
31
+ Swal.fire({
32
+ title: mes,
33
+ icon: 'error',
34
+ timer: 3000,
35
+ timerProgressBar: true,
36
+ showConfirmButton: false,
37
+ });
38
+ }
39
+ return mes;
40
+ };
41
+ export const logout = () => {
42
+ // 1. Clear Local Storage
43
+ localStorage.removeItem("token");
44
+ const tokenDeleteString = "token=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/;";
45
+ document.cookie = tokenDeleteString;
46
+ // 3. Fallback: Clear ALL cookies (your original loop)
47
+ const cookies = document.cookie.split(";");
48
+ for (let i = 0; i < cookies.length; i++) {
49
+ const cookie = cookies[i];
50
+ const eqPos = cookie.indexOf("=");
51
+ const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
52
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/;`;
53
+ }
54
+ };
55
+ export const getToken = () => {
56
+ if (typeof window === "undefined") {
57
+ return null;
58
+ }
59
+ return Cookies.get("token") || localStorage.getItem("token");
60
+ };
61
+ export const getUser = () => {
62
+ // const token = typeof window !== 'undefined' ? localStorage.getItem("token") : null;
63
+ const token = getToken();
64
+ const user = token ? jwtDecode(token) : null;
65
+ return user;
66
+ };
67
+ export const getStoredFont = (FONTS) => {
68
+ if (typeof window === "undefined")
69
+ return FONTS[0]; // Default for SSR
70
+ const up = localStorage.getItem("user-pref");
71
+ if (!up)
72
+ return FONTS[5]; // Default to Ubuntu (6th index)
73
+ const fontIndex = parseInt(up[1]) - 1;
74
+ return FONTS[fontIndex] || FONTS[5];
75
+ };
76
+ export const getTimeAgo = (dateStr, type) => {
77
+ if (!dateStr)
78
+ return "";
79
+ const now = new Date();
80
+ const item = new Date(dateStr);
81
+ let diffMs = 0;
82
+ if (type === "past")
83
+ diffMs = now.getTime() - item.getTime();
84
+ if (type === "future")
85
+ diffMs = item.getTime() - now.getTime();
86
+ // Handle invalid date ranges
87
+ if (diffMs < 0) {
88
+ return type === "past" ? "just now" : "Closed";
89
+ }
90
+ const seconds = Math.floor(diffMs / 1000);
91
+ const minutes = Math.floor(seconds / 60);
92
+ const hours = Math.floor(minutes / 60);
93
+ const days = Math.floor(hours / 24);
94
+ const weeks = Math.floor(days / 7);
95
+ const months = Math.floor(days / 30.44); // Use average month length
96
+ const years = Math.floor(days / 365.25); // Account for leap years
97
+ const suffix = type === "past" ? "ago" : "left";
98
+ // 1. Logic for Past Dates (e.g., Posted 2 days ago)
99
+ if (type === "past") {
100
+ if (days === 0)
101
+ return "Today";
102
+ if (days === 1)
103
+ return "Yesterday";
104
+ if (days < 7)
105
+ return `${days} days ago`;
106
+ if (weeks < 5)
107
+ return `${weeks} week${weeks > 1 ? "s" : ""} ago`;
108
+ if (months < 12)
109
+ return `${months} month${months > 1 ? "s" : ""} ago`;
110
+ return `${years} year${years > 1 ? "s" : ""} ago`;
111
+ }
112
+ // 2. Logic for Future Dates (e.g., 5 days left)
113
+ if (days === 0)
114
+ return "Ends today";
115
+ if (days === 1)
116
+ return "1 day left";
117
+ if (days < 7)
118
+ return `${days} days left`;
119
+ if (weeks < 5)
120
+ return `${weeks} week${weeks > 1 ? "s" : ""} left`;
121
+ if (months < 12)
122
+ return `${months} month${months > 1 ? "s" : ""} left`;
123
+ return `${years} year${years > 1 ? "s" : ""} left`;
124
+ };
125
+ export const getLanguage = () => {
126
+ if (typeof window === "undefined")
127
+ return "en";
128
+ const up = localStorage.getItem("user-pref");
129
+ if (!up)
130
+ return "en";
131
+ const langNumber = parseInt(up[0]);
132
+ return langNumber === 1 ? "en" : "fr";
133
+ };
134
+ export const getTemplate = () => {
135
+ if (typeof window === "undefined")
136
+ return 1;
137
+ const up = localStorage.getItem("user-pref");
138
+ if (!up)
139
+ return 1;
140
+ return parseInt(up[2]);
141
+ };
142
+ export const decodeUrlID = (urlID) => {
143
+ const base64DecodedString = decodeURIComponent(urlID); // Decodes %3D%3D to ==
144
+ const id = Buffer.from(base64DecodedString, 'base64').toString('utf-8'); // Decoding from base64
145
+ return id.split(":")[1];
146
+ };
147
+ export const removeEmptyFields = (obj) => {
148
+ const newObj = {};
149
+ for (const key in obj) {
150
+ // Keep File objects and non-empty values
151
+ if (obj[key] instanceof File ||
152
+ (obj[key] !== null && obj[key] !== undefined && obj[key] !== '')) {
153
+ newObj[key] = obj[key];
154
+ }
155
+ }
156
+ return newObj;
157
+ };
158
+ export const Alert = ({ title, duration, status = true }) => {
159
+ Swal.fire({
160
+ title: capitalizeEachWord(title),
161
+ timer: duration,
162
+ timerProgressBar: true,
163
+ showConfirmButton: false,
164
+ icon: status ? 'success' : 'error',
165
+ });
166
+ };
167
+ export const validateFormFields = (formData, currentStepFields) => {
168
+ const newErrors = {};
169
+ // 1. Flatten the fields from all rows in the step
170
+ const fieldsToValidate = currentStepFields.flatMap(row => row.fields);
171
+ fieldsToValidate.forEach((field) => {
172
+ // Skip validation if the field is hidden
173
+ if (field.show === false)
174
+ return;
175
+ // 2. Get the value from formData
176
+ const value = formData[field.name];
177
+ // 3. Check Required
178
+ if (field.required && (!value || value.toString().trim() === "")) {
179
+ newErrors[field.name] = `${capitalizeEachWord(field.label)} is required`;
180
+ // return;
181
+ }
182
+ // 4. Check Type (Email)
183
+ if (field.type === 'email' && value) {
184
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
185
+ if (!emailRegex.test(value)) {
186
+ newErrors[field.name] = "Invalid email format";
187
+ }
188
+ }
189
+ // 5. Check Numbers (Min/Max)
190
+ if (field.type === 'number' && value) {
191
+ const numValue = Number(value);
192
+ if (field.min !== undefined && numValue < field.min) {
193
+ newErrors[field.name] = `Minimum value is ${field.min}`;
194
+ }
195
+ if (field.max !== undefined && numValue > field.max) {
196
+ newErrors[field.name] = `Maximum value is ${field.max}`;
197
+ }
198
+ }
199
+ // 6. Check Length (Strings)
200
+ if (field.type === 'text' && value) {
201
+ if (field.min !== undefined && value.length < field.min) {
202
+ newErrors[field.name] = `Too short (min ${field.min} chars)`;
203
+ }
204
+ if (field.max !== undefined && value.length > field.max) {
205
+ newErrors[field.name] = `Too long (min ${field.max} chars)`;
206
+ }
207
+ }
208
+ });
209
+ return newErrors;
210
+ };
211
+ export const lastYears = (number) => {
212
+ return Array.from({ length: number }, (_, i) => ({
213
+ value: (currentYear - i).toString(),
214
+ label: (currentYear - i).toString()
215
+ })).sort((a, b) => Number(b.value) - Number(a.value));
216
+ };
217
+ export const formatText = (text) => {
218
+ return text;
219
+ if (!text || typeof text !== "string")
220
+ return "";
221
+ return text
222
+ // Ensure space after "." if it's not part of abbreviation/number
223
+ .replace(/\.(?!\s|$|[a-zA-Z0-9])/g, ". ")
224
+ // Split sentences by ". " but preserve delimiter
225
+ .split(/(\. )/g)
226
+ .map((segment) => {
227
+ if (segment === ". ")
228
+ return segment; // keep delimiter
229
+ return segment
230
+ .split(" ")
231
+ .map((word, i) => {
232
+ if (word === "")
233
+ return word;
234
+ // Preserve existing ALL CAPS words
235
+ if (word.length > 1 && /^[A-Z]+$/.test(word)) {
236
+ return word;
237
+ }
238
+ // Abbreviations like g.a, n.l.n
239
+ if (/^([a-zA-Z]\.)+[a-zA-Z]?$/.test(word)) {
240
+ return word.toUpperCase(); // → G.A, N.L.N
241
+ }
242
+ // Version numbers like 3.0.1
243
+ if (/^\d+(\.\d+)+$/.test(word)) {
244
+ return word;
245
+ }
246
+ // Mixed abbrev/numbers (like p.m2.5 or covid-19 v2.0)
247
+ if (/[a-zA-Z]\.\d/.test(word) || /\d+\.\d+[a-zA-Z]?/.test(word)) {
248
+ return word; // leave as-is
249
+ }
250
+ // First word of sentence → capitalize first letter
251
+ if (i === 0) {
252
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
253
+ }
254
+ // Other words → lowercase
255
+ return word.toLowerCase();
256
+ })
257
+ .join(" ");
258
+ })
259
+ .join("");
260
+ };
@@ -0,0 +1,7 @@
1
+ export declare const getProfile: (item: any) => any;
2
+ export declare const getClass: (item: any) => any;
3
+ export declare const getClassName: (item: any) => any;
4
+ export declare const getLevel: (item: any) => any;
5
+ export declare const getAcademicYearStudent: (item: any) => any;
6
+ export declare function getAcademicYear(): string;
7
+ //# sourceMappingURL=utilsSchool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utilsSchool.d.ts","sourceRoot":"","sources":["../src/utilsSchool.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,UAAU,GAAI,MAAM,GAAG,QAAuE,CAAC;AAC5G,eAAO,MAAM,QAAQ,GAAI,MAAM,GAAG,QAA+G,CAAC;AAClJ,eAAO,MAAM,YAAY,GAAI,MAAM,GAAG,QAA2J,CAAC;AAClM,eAAO,MAAM,QAAQ,GAAI,MAAM,GAAG,QAA6M,CAAC;AAChP,eAAO,MAAM,sBAAsB,GAAI,MAAM,GAAG,QAAyJ,CAAC;AAC1M,wBAAgB,eAAe,IAAI,MAAM,CAUxC"}
@@ -0,0 +1,16 @@
1
+ export const getProfile = (item) => item?.userprofile || item?.userprofilesec || item?.userprofileprim;
2
+ export const getClass = (item) => item?.userprofile?.specialty || item?.userprofilesec?.classroomsec || item?.userprofileprim?.classroomprim;
3
+ export const getClassName = (item) => item?.userprofile?.specialty?.mainSpecialty?.specialtyName || item?.userprofilesec?.classroomsec?.level || item?.userprofileprim?.classroomprim?.level;
4
+ export const getLevel = (item) => item?.userprofile?.specialty?.level?.level || (item?.userprofilesec?.classroomsec?.series?.name || item?.userprofilesec?.classroomsec?.series?.classType) || item?.userprofileprim?.classroomprim?.level;
5
+ export const getAcademicYearStudent = (item) => item?.userprofile?.specialty?.academicYear || item?.userprofilesec?.classroomsec?.academicYear || item?.userprofileprim?.classroomprim?.academicYear;
6
+ export function getAcademicYear() {
7
+ const today = new Date();
8
+ const year = today.getFullYear();
9
+ const month = today.getMonth(); // 0 = January, 7 = August
10
+ if (month < 7) {
11
+ return `${year - 1}/${year}`;
12
+ }
13
+ else {
14
+ return `${year}/${year + 1}`;
15
+ }
16
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@econneq/utility-functions",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.js"
20
+ },
21
+ "./query-server": {
22
+ "types": "./dist/query-server.d.ts",
23
+ "import": "./dist/query-server.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "peerDependencies": {
30
+ "@apollo/client": "^4.0.0",
31
+ "graphql": "^16.0.0",
32
+ "next": "^16.0.0",
33
+ "react": "^19.0.0",
34
+ "react-dom": "^18.0.0 || ^19.0.0",
35
+ "sweetalert2": "^11.0.0"
36
+ },
37
+ "dependencies": {
38
+ "js-cookie": "^3.0.0",
39
+ "jwt-decode": "^4.0.0"
40
+ }
41
+ }