@apptimate/core-lib 1.0.0 → 1.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.
@@ -1,9 +1,14 @@
1
- export interface IStorageOptions {
2
- name: string;
3
- secretName: string;
4
- encrypted: boolean;
5
- }
6
-
7
- export const cookie = {
8
- access_token: { name: 'access_token', secretName: 'PATKXFGLSIZXZBXEEFK', encrypted: true } as IStorageOptions
9
- };
1
+ export interface IStorageOptions {
2
+ name: string;
3
+ secretName: string;
4
+ encrypted: boolean;
5
+ }
6
+
7
+ export const cookie = {
8
+ access_token: { name: 'access_token', secretName: 'PATKXFGLSIZXZBXEEFK', encrypted: false } as IStorageOptions
9
+ };
10
+
11
+ export const local_storage = {
12
+ user_info: { name: 'user_info', secretName: 'USER_INFO_SECRET', encrypted: false } as IStorageOptions,
13
+ selected_organization: { name: 'selected_organization', secretName: 'SELECTED_ORG', encrypted: false } as IStorageOptions
14
+ };
package/src/index.ts CHANGED
@@ -1,8 +1,3 @@
1
- export * from './utils/commonService';
2
- export * from './utils/cookiesHandler';
3
- export * from './utils/httpClient';
4
- export * from './utils/localStorageHandler';
5
- export * from './utils/bem';
6
- export * from './utils/cn';
7
- export * from './common/interfaces/ICommon';
8
- export * from './constants/storageKeys';
1
+ // Default entry point — client-safe (no 'use server' modules)
2
+ // For server components/actions, import from '@apptimate/core-lib/src/server'
3
+ export * from './client';
package/src/server.ts ADDED
@@ -0,0 +1,12 @@
1
+ // Server-safe exports — includes everything including 'use server' modules
2
+ export * from './utils/commonService';
3
+ export * from './utils/cookiesHandler';
4
+ export * from './utils/httpClient';
5
+ export * from './utils/localStorageHandler';
6
+ export * from './utils/bem';
7
+ export * from './utils/cn';
8
+ export * from './common/interfaces/ICommon';
9
+ export * from './constants/storageKeys';
10
+ export * from './constants/menus';
11
+ export * from './constants/iconRegistry';
12
+ export * from './utils/bootstrapConfig';
package/src/utils/bem.ts CHANGED
@@ -1,13 +1,13 @@
1
- export function block(prefix: string, name: string): string {
2
- return `${prefix}-${name}`;
3
- }
4
-
5
- export function element(prefix: string, blockName: string, elementName: string): string {
6
- return `${prefix}-${blockName}__${elementName}`;
7
- }
8
-
9
- export function modifier(prefix: string, name: string, modifier: string | undefined | null | boolean): string {
10
- if (!modifier) return "";
11
- if (typeof modifier === "boolean") return modifier ? `${prefix}-${name}--active` : "";
12
- return `${prefix}-${name}--${modifier}`;
13
- }
1
+ export function block(prefix: string, name: string): string {
2
+ return `${prefix}-${name}`;
3
+ }
4
+
5
+ export function element(prefix: string, blockName: string, elementName: string): string {
6
+ return `${prefix}-${blockName}__${elementName}`;
7
+ }
8
+
9
+ export function modifier(prefix: string, name: string, modifier: string | undefined | null | boolean): string {
10
+ if (!modifier) return "";
11
+ if (typeof modifier === "boolean") return modifier ? `${prefix}-${name}--active` : "";
12
+ return `${prefix}-${name}--${modifier}`;
13
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared bootstrap configuration loader for module layouts.
3
+ * Fetches the public app_config from the API during SSR.
4
+ *
5
+ * This centralizes the config fetch logic so all module layouts
6
+ * share the same implementation instead of duplicating it.
7
+ */
8
+ export async function fetchAppBootstrapConfig(): Promise<any | null> {
9
+ try {
10
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL;
11
+ if (!apiUrl) return null;
12
+
13
+ const response = await fetch(`${apiUrl}/api/core/config/app_config`, { cache: 'no-store' });
14
+ if (response.ok) {
15
+ const data = await response.json();
16
+ if (data.is_success) {
17
+ return data.result;
18
+ }
19
+ }
20
+ } catch (error) {
21
+ console.error("Failed to fetch app_config:", error);
22
+ }
23
+ return null;
24
+ }
25
+
26
+ /**
27
+ * Server-side menu config loader.
28
+ *
29
+ * Reads the auth token and selected organization from cookies,
30
+ * then fetches the active menu configuration from the API.
31
+ * Returns the raw config object or null if not available.
32
+ *
33
+ * Usage in server layouts:
34
+ * ```ts
35
+ * import { fetchMenuConfig } from '@apptimate/core-lib/src/utils/bootstrapConfig';
36
+ * const menuConfig = await fetchMenuConfig();
37
+ * <DashboardLayoutClient initialMenuConfig={menuConfig} />
38
+ * ```
39
+ */
40
+ export async function fetchMenuConfig(): Promise<any | null> {
41
+ try {
42
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL;
43
+ if (!apiUrl) return null;
44
+
45
+ // Dynamic import to avoid bundling next/headers in client code
46
+ const { cookies } = await import('next/headers');
47
+ const cookieStore = await cookies();
48
+
49
+ // Read auth token — cookie name matches storageKeys.ts (encrypted: false → use .name)
50
+ const token = cookieStore.get('access_token')?.value;
51
+ if (!token) return null;
52
+
53
+ // Read selected organization from cookie (set by the client layout)
54
+ const orgCookie = cookieStore.get('selected_organization_id')?.value;
55
+
56
+ const headers: Record<string, string> = {
57
+ 'Accept': 'application/json',
58
+ 'Authorization': `Bearer ${token}`,
59
+ };
60
+
61
+ if (orgCookie) {
62
+ headers['X-Organization-Id'] = orgCookie;
63
+ }
64
+
65
+ const response = await fetch(`${apiUrl}/api/menu-config/active`, {
66
+ cache: 'no-store',
67
+ headers,
68
+ });
69
+
70
+ if (response.ok) {
71
+ const data = await response.json();
72
+ if (data.is_success && data.result?.config) {
73
+ return data.result.config;
74
+ }
75
+ }
76
+ } catch (error) {
77
+ console.error("Failed to fetch menu config:", error);
78
+ }
79
+ return null;
80
+ }
package/src/utils/cn.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { type ClassValue, clsx } from "clsx";
2
- import { twMerge } from "tailwind-merge";
3
-
4
- /**
5
- * Utility function to merge tailwind classes safely.
6
- */
7
- export function cn(...inputs: ClassValue[]) {
8
- return twMerge(clsx(inputs));
9
- }
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ /**
5
+ * Utility function to merge tailwind classes safely.
6
+ */
7
+ export function cn(...inputs: ClassValue[]) {
8
+ return twMerge(clsx(inputs));
9
+ }
@@ -1,26 +1,46 @@
1
- export function replacePlaceholders(template: string, replacements: Record<string, string | number>): string {
2
- return template.replace(/\${(\w+)}/g, (_, key) => {
3
- return (replacements[key] ?? `\${${key}}`).toString();
4
- });
5
- }
6
-
7
- // For now, these are simple passthrough since a specific encryption library/secret wasn't provided.
8
- // The skill requires them, so we implement them so code works, even if weak until secret is present.
9
- export function encrypt(value: any): string {
10
- const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
11
- // In production, use process.env.CRYPT_SECRET_KEY and an encryption lib (e.g. crypto-js or node-crypto)
12
- return btoa(unescape(encodeURIComponent(stringValue)));
13
- }
14
-
15
- export function decrypt(encryptedValue: string): any {
16
- try {
17
- const decrypted = decodeURIComponent(escape(atob(encryptedValue)));
18
- try {
19
- return JSON.parse(decrypted);
20
- } catch {
21
- return decrypted;
22
- }
23
- } catch {
24
- return null;
25
- }
26
- }
1
+ export function replacePlaceholders(template: string, replacements: Record<string, string | number>): string {
2
+ return template.replace(/\${(\w+)}/g, (_, key) => {
3
+ return (replacements[key] ?? `\${${key}}`).toString();
4
+ });
5
+ }
6
+
7
+ /**
8
+ * Base64 encode a value.
9
+ *
10
+ * WARNING: This is NOT encryption. It is a reversible encoding meant only for
11
+ * transport convenience. Do NOT rely on this for security.
12
+ * Auth tokens should be stored in HttpOnly, Secure, SameSite cookies managed
13
+ * by the server in a production deployment.
14
+ */
15
+ export function encode(value: any): string {
16
+ const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
17
+ return btoa(unescape(encodeURIComponent(stringValue)));
18
+ }
19
+
20
+ /**
21
+ * Base64 decode a value.
22
+ *
23
+ * WARNING: This is NOT decryption. See the note on `encode()`.
24
+ */
25
+ export function decode(encodedValue: string): any {
26
+ try {
27
+ const decoded = decodeURIComponent(escape(atob(encodedValue)));
28
+ try {
29
+ return JSON.parse(decoded);
30
+ } catch {
31
+ return decoded;
32
+ }
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * @deprecated Use `encode()` instead. This function name is misleading — it performs base64 encoding, not encryption.
40
+ */
41
+ export const encrypt = encode;
42
+
43
+ /**
44
+ * @deprecated Use `decode()` instead. This function name is misleading — it performs base64 decoding, not decryption.
45
+ */
46
+ export const decrypt = decode;
@@ -1,64 +1,64 @@
1
- 'use server';
2
-
3
- import { cookies } from 'next/headers';
4
- import { IStorageOptions } from '../constants/storageKeys';
5
- import { decrypt, encrypt, replacePlaceholders } from './commonService';
6
-
7
- export async function getCookies<T = any>(storageKey: IStorageOptions, options?: { replacements?: Record<string, string | number> }): Promise<T | null> {
8
- const cookieStore = await cookies();
9
- const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
10
-
11
- const value = cookieStore.get(name)?.value;
12
- if (!value) return null;
13
-
14
- if (storageKey.encrypted) {
15
- return decrypt(value) as T;
16
- }
17
-
18
- try {
19
- return JSON.parse(value) as T;
20
- } catch {
21
- return value as unknown as T;
22
- }
23
- }
24
-
25
- export async function setCookies(storageKey: IStorageOptions, value: any, options?: {
26
- replacements?: Record<string, string | number>;
27
- expires?: number | Date;
28
- maxAge?: number;
29
- domain?: string;
30
- path?: string;
31
- secure?: boolean;
32
- httpOnly?: boolean;
33
- sameSite?: 'lax' | 'strict' | 'none';
34
- }): Promise<void> {
35
- const cookieStore = await cookies();
36
- const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
37
-
38
- let finalValue = typeof value === 'string' ? value : JSON.stringify(value);
39
- if (storageKey.encrypted) {
40
- finalValue = encrypt(value);
41
- }
42
-
43
- cookieStore.set(name, finalValue, {
44
- expires: options?.expires,
45
- maxAge: options?.maxAge,
46
- domain: options?.domain,
47
- path: options?.path || '/',
48
- secure: options?.secure ?? process.env.NODE_ENV === 'production',
49
- httpOnly: options?.httpOnly ?? true,
50
- sameSite: options?.sameSite || 'lax',
51
- });
52
- }
53
-
54
- export async function clearCookie(storageKey: IStorageOptions, options?: { replacements?: Record<string, string | number> }): Promise<void> {
55
- const cookieStore = await cookies();
56
- const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
57
- cookieStore.delete(name);
58
- }
59
-
60
- export async function clearAllCookies(): Promise<void> {
61
- const cookieStore = await cookies();
62
- const all = cookieStore.getAll();
63
- all.forEach(c => cookieStore.delete(c.name));
64
- }
1
+ 'use server';
2
+
3
+ import { cookies } from 'next/headers';
4
+ import { IStorageOptions } from '../constants/storageKeys';
5
+ import { decrypt, encrypt, replacePlaceholders } from './commonService';
6
+
7
+ export async function getCookies<T = any>(storageKey: IStorageOptions, options?: { replacements?: Record<string, string | number> }): Promise<T | null> {
8
+ const cookieStore = await cookies();
9
+ const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
10
+
11
+ const value = cookieStore.get(name)?.value;
12
+ if (!value) return null;
13
+
14
+ if (storageKey.encrypted) {
15
+ return decrypt(value) as T;
16
+ }
17
+
18
+ try {
19
+ return JSON.parse(value) as T;
20
+ } catch {
21
+ return value as unknown as T;
22
+ }
23
+ }
24
+
25
+ export async function setCookies(storageKey: IStorageOptions, value: any, options?: {
26
+ replacements?: Record<string, string | number>;
27
+ expires?: number | Date;
28
+ maxAge?: number;
29
+ domain?: string;
30
+ path?: string;
31
+ secure?: boolean;
32
+ httpOnly?: boolean;
33
+ sameSite?: 'lax' | 'strict' | 'none';
34
+ }): Promise<void> {
35
+ const cookieStore = await cookies();
36
+ const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
37
+
38
+ let finalValue = typeof value === 'string' ? value : JSON.stringify(value);
39
+ if (storageKey.encrypted) {
40
+ finalValue = encrypt(value);
41
+ }
42
+
43
+ cookieStore.set(name, finalValue, {
44
+ expires: options?.expires,
45
+ maxAge: options?.maxAge,
46
+ domain: options?.domain,
47
+ path: options?.path || '/',
48
+ secure: options?.secure ?? process.env.NODE_ENV === 'production',
49
+ httpOnly: options?.httpOnly ?? true,
50
+ sameSite: options?.sameSite || 'lax',
51
+ });
52
+ }
53
+
54
+ export async function clearCookie(storageKey: IStorageOptions, options?: { replacements?: Record<string, string | number> }): Promise<void> {
55
+ const cookieStore = await cookies();
56
+ const name = replacePlaceholders(storageKey.encrypted ? storageKey.secretName : storageKey.name, options?.replacements || {});
57
+ cookieStore.delete(name);
58
+ }
59
+
60
+ export async function clearAllCookies(): Promise<void> {
61
+ const cookieStore = await cookies();
62
+ const all = cookieStore.getAll();
63
+ all.forEach(c => cookieStore.delete(c.name));
64
+ }