Update import paths for DataTable component in InvoiceTable and SubscriptionsList to improve module structure and maintainability.

This commit is contained in:
barsa 2025-09-25 15:12:20 +09:00
parent 4447278b2c
commit be3af76e01
6 changed files with 158 additions and 5 deletions

View File

@ -0,0 +1,153 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import type { WhmcsApiConfig } from "../types/connection.types";
/**
* Service for managing WHMCS API configuration
* Handles environment-based configuration loading with dev/prod separation
*/
@Injectable()
export class WhmcsConfigService {
private readonly config: WhmcsApiConfig;
private readonly accessKey?: string;
constructor(private readonly configService: ConfigService) {
this.config = this.loadConfiguration();
this.accessKey = this.loadAccessKey();
}
/**
* Get the complete WHMCS API configuration
*/
getConfig(): WhmcsApiConfig {
return { ...this.config };
}
/**
* Get the API access key if available
*/
getAccessKey(): string | undefined {
return this.accessKey;
}
/**
* Check if admin authentication is available
*/
hasAdminAuth(): boolean {
return Boolean(this.config.adminUsername && this.config.adminPasswordHash);
}
/**
* Get admin authentication credentials
*/
getAdminAuth(): { username: string; passwordHash: string } | null {
if (!this.hasAdminAuth()) {
return null;
}
return {
username: this.config.adminUsername!,
passwordHash: this.config.adminPasswordHash!,
};
}
/**
* Validate that required configuration is present
*/
validateConfig(): void {
const required = ['baseUrl', 'identifier', 'secret'];
const missing = required.filter(key => !this.config[key as keyof WhmcsApiConfig]);
if (missing.length > 0) {
throw new Error(`Missing required WHMCS configuration: ${missing.join(', ')}`);
}
if (!this.config.baseUrl.startsWith('http')) {
throw new Error('WHMCS baseUrl must start with http:// or https://');
}
}
/**
* Load configuration from environment variables
*/
private loadConfiguration(): WhmcsApiConfig {
const nodeEnv = this.configService.get<string>("NODE_ENV", "development");
const isDev = nodeEnv !== "production";
// Resolve and normalize base URL (trim trailing slashes)
const rawBaseUrl = this.getFirst([
isDev ? "WHMCS_DEV_BASE_URL" : undefined,
"WHMCS_BASE_URL"
]) || "";
const baseUrl = rawBaseUrl.replace(/\/+$/, "");
const identifier = this.getFirst([
isDev ? "WHMCS_DEV_API_IDENTIFIER" : undefined,
"WHMCS_API_IDENTIFIER"
]) || "";
const secret = this.getFirst([
isDev ? "WHMCS_DEV_API_SECRET" : undefined,
"WHMCS_API_SECRET"
]) || "";
const adminUsername = this.getFirst([
isDev ? "WHMCS_DEV_ADMIN_USERNAME" : undefined,
"WHMCS_ADMIN_USERNAME",
]);
const adminPasswordHash = this.getFirst([
isDev ? "WHMCS_DEV_ADMIN_PASSWORD_MD5" : undefined,
"WHMCS_ADMIN_PASSWORD_MD5",
"WHMCS_ADMIN_PASSWORD_HASH",
]);
return {
baseUrl,
identifier,
secret,
timeout: this.getNumberConfig("WHMCS_API_TIMEOUT", 30000),
retryAttempts: this.getNumberConfig("WHMCS_API_RETRY_ATTEMPTS", 3),
retryDelay: this.getNumberConfig("WHMCS_API_RETRY_DELAY", 1000),
adminUsername,
adminPasswordHash,
};
}
/**
* Load API access key
*/
private loadAccessKey(): string | undefined {
const nodeEnv = this.configService.get<string>("NODE_ENV", "development");
const isDev = nodeEnv !== "production";
return this.getFirst([
isDev ? "WHMCS_DEV_API_ACCESS_KEY" : undefined,
"WHMCS_API_ACCESS_KEY",
]);
}
/**
* Helper: read the first defined value across a list of keys
*/
private getFirst(keys: Array<string | undefined>): string | undefined {
for (const key of keys) {
if (!key) continue;
const v = this.configService.get<string | undefined>(key);
if (v && `${v}`.length > 0) return v;
const raw = process.env[key];
if (raw && `${raw}`.length > 0) return raw;
}
return undefined;
}
/**
* Get numeric configuration value with fallback
*/
private getNumberConfig(key: string, defaultValue: number): number {
const value = this.configService.get<string>(key);
if (!value) return defaultValue;
const parsed = parseInt(value, 10);
return isNaN(parsed) ? defaultValue : parsed;
}
}

View File

@ -7,7 +7,7 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { Input } from "@/components/atoms"; import { Input } from "@/components/atoms";
import { FormField } from "@/components/molecules/FormField"; import { FormField } from "@/components/molecules/FormField/FormField";
import type { SignupFormData } from "@customer-portal/domain"; import type { SignupFormData } from "@customer-portal/domain";
import type { FormErrors, FormTouched, UseZodFormReturn } from "@customer-portal/validation"; import type { FormErrors, FormTouched, UseZodFormReturn } from "@customer-portal/validation";

View File

@ -6,7 +6,7 @@
"use client"; "use client";
import { Input, Checkbox } from "@/components/atoms"; import { Input, Checkbox } from "@/components/atoms";
import { FormField } from "@/components/molecules/FormField"; import { FormField } from "@/components/molecules/FormField/FormField";
import { type SignupFormData } from "@customer-portal/domain"; import { type SignupFormData } from "@customer-portal/domain";
import type { UseZodFormReturn } from "@customer-portal/validation"; import type { UseZodFormReturn } from "@customer-portal/validation";

View File

@ -6,7 +6,7 @@
"use client"; "use client";
import { Input } from "@/components/atoms"; import { Input } from "@/components/atoms";
import { FormField } from "@/components/molecules/FormField"; import { FormField } from "@/components/molecules/FormField/FormField";
import { type SignupFormData } from "@customer-portal/domain"; import { type SignupFormData } from "@customer-portal/domain";
import type { FormErrors, FormTouched, UseZodFormReturn } from "@customer-portal/validation"; import type { FormErrors, FormTouched, UseZodFormReturn } from "@customer-portal/validation";

View File

@ -11,7 +11,7 @@ import {
ExclamationTriangleIcon, ExclamationTriangleIcon,
ClockIcon, ClockIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { DataTable } from "@/components/molecules/DataTable"; import { DataTable } from "@/components/molecules/DataTable/DataTable";
import { BillingStatusBadge } from "../BillingStatusBadge"; import { BillingStatusBadge } from "../BillingStatusBadge";
import type { Invoice } from "@customer-portal/domain"; import type { Invoice } from "@customer-portal/domain";
import { formatCurrency, getCurrencyLocale } from "@customer-portal/domain"; import { formatCurrency, getCurrencyLocale } from "@customer-portal/domain";

View File

@ -6,7 +6,7 @@ import Link from "next/link";
import { Button } from "@/components/atoms/button"; import { Button } from "@/components/atoms/button";
import { ErrorBoundary } from "@/components/molecules"; import { ErrorBoundary } from "@/components/molecules";
import { PageLayout } from "@/components/templates/PageLayout"; import { PageLayout } from "@/components/templates/PageLayout";
import { DataTable } from "@/components/molecules/DataTable"; import { DataTable } from "@/components/molecules/DataTable/DataTable";
import { StatusPill } from "@/components/atoms/status-pill"; import { StatusPill } from "@/components/atoms/status-pill";
import { SubCard } from "@/components/molecules/SubCard"; import { SubCard } from "@/components/molecules/SubCard";
import { SearchFilterBar } from "@/components/molecules/SearchFilterBar"; import { SearchFilterBar } from "@/components/molecules/SearchFilterBar";