feat: reorganize admin settings with tabbed interface and documentation

- Reorganized admin settings into tabbed interface (General, Security, Payment Methods)
- Vertical tabs on desktop, horizontal scrollable on mobile
- Moved Payment Methods from separate menu to Settings tab
- Fixed admin profile reuse and dashboard blocking
- Fixed maintenance mode guard to use AppConfig model
- Added admin auto-redirect after login (admins → /admin, users → /)
- Reorganized documentation into docs/ folder structure
- Created comprehensive README and documentation index
- Added PWA and Web Push notifications to to-do list
This commit is contained in:
dwindown
2025-10-13 09:28:12 +07:00
parent 49d60676d0
commit 89f881e7cf
99 changed files with 4884 additions and 392 deletions

View File

@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
const auth_guard_1 = require("../auth/auth.guard");
const admin_guard_1 = require("./guards/admin.guard");
const admin_config_service_1 = require("./admin-config.service");
const skip_maintenance_decorator_1 = require("../common/decorators/skip-maintenance.decorator");
let AdminConfigController = class AdminConfigController {
service;
constructor(service) {
@@ -78,6 +79,7 @@ __decorate([
exports.AdminConfigController = AdminConfigController = __decorate([
(0, common_1.Controller)('admin/config'),
(0, common_1.UseGuards)(auth_guard_1.AuthGuard, admin_guard_1.AdminGuard),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__metadata("design:paramtypes", [admin_config_service_1.AdminConfigService])
], AdminConfigController);
//# sourceMappingURL=admin-config.controller.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"admin-config.controller.js","sourceRoot":"","sources":["../../src/admin/admin-config.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAUwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,iEAA4D;AAUrD,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IACH;IAA7B,YAA6B,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAG5D,OAAO,CAAoB,QAAiB;QAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAGD,aAAa;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;IACtC,CAAC;IAGD,OAAO,CAAe,GAAW;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAGD,MAAM,CACU,GAAW,EACjB,IAAS,EACV,GAAoB;QAE3B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC;IAGD,MAAM,CAAe,GAAW;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;CACF,CAAA;AA/BY,sDAAqB;AAIhC;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,cAAK,EAAC,UAAU,CAAC,CAAA;;;;oDAEzB;AAGD;IADC,IAAA,YAAG,EAAC,aAAa,CAAC;;;;0DAGlB;AAGD;IADC,IAAA,YAAG,EAAC,MAAM,CAAC;IACH,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;oDAEpB;AAGD;IADC,IAAA,aAAI,EAAC,MAAM,CAAC;IAEV,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IACZ,WAAA,IAAA,aAAI,GAAE,CAAA;IACN,WAAA,IAAA,YAAG,GAAE,CAAA;;;;mDAGP;AAGD;IADC,IAAA,eAAM,EAAC,MAAM,CAAC;IACP,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;mDAEnB;gCA9BU,qBAAqB;IAFjC,IAAA,mBAAU,EAAC,cAAc,CAAC;IAC1B,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;qCAEO,yCAAkB;GAD7C,qBAAqB,CA+BjC"}
{"version":3,"file":"admin-config.controller.js","sourceRoot":"","sources":["../../src/admin/admin-config.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAUwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,iEAA4D;AAC5D,gGAAkF;AAW3E,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IACH;IAA7B,YAA6B,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAG5D,OAAO,CAAoB,QAAiB;QAC1C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAGD,aAAa;QACX,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,CAAC;IACtC,CAAC;IAGD,OAAO,CAAe,GAAW;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACnC,CAAC;IAGD,MAAM,CACU,GAAW,EACjB,IAAS,EACV,GAAoB;QAE3B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC;IAGD,MAAM,CAAe,GAAW;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;CACF,CAAA;AA/BY,sDAAqB;AAIhC;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,cAAK,EAAC,UAAU,CAAC,CAAA;;;;oDAEzB;AAGD;IADC,IAAA,YAAG,EAAC,aAAa,CAAC;;;;0DAGlB;AAGD;IADC,IAAA,YAAG,EAAC,MAAM,CAAC;IACH,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;oDAEpB;AAGD;IADC,IAAA,aAAI,EAAC,MAAM,CAAC;IAEV,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;IACZ,WAAA,IAAA,aAAI,GAAE,CAAA;IACN,WAAA,IAAA,YAAG,GAAE,CAAA;;;;mDAGP;AAGD;IADC,IAAA,eAAM,EAAC,MAAM,CAAC;IACP,WAAA,IAAA,cAAK,EAAC,KAAK,CAAC,CAAA;;;;mDAEnB;gCA9BU,qBAAqB;IAHjC,IAAA,mBAAU,EAAC,cAAc,CAAC;IAC1B,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;IAChC,IAAA,4CAAe,GAAE;qCAEsB,yCAAkB;GAD7C,qBAAqB,CA+BjC"}

View File

@@ -1 +1 @@
{"version":3,"file":"admin-config.service.js","sourceRoot":"","sources":["../../src/admin/admin-config.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AAGlD,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IACA;IAA7B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAEtD,KAAK,CAAC,OAAO,CAAC,QAAiB;QAC7B,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC;YACpC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS;YAC1C,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;SAC7B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;YACtC,KAAK,EAAE,EAAE,GAAG,EAAE;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW,EAAE,IAAS,EAAE,SAAiB;QACpD,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YAClC,KAAK,EAAE,EAAE,GAAG,EAAE;YACd,MAAM,EAAE;gBACN,GAAG,IAAI;gBACP,SAAS;gBACT,SAAS,EAAE,IAAI,IAAI,EAAE;aACtB;YACD,MAAM,EAAE;gBACN,GAAG;gBACH,GAAG,IAAI;gBACP,SAAS;aACV;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YAClC,KAAK,EAAE,EAAE,GAAG,EAAE;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;QAGvD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YAC7C,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC1B,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;YAC5B,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClC,OAAO,GAAG,CAAC;QACb,CAAC,EAAE,EAA2B,CAAC,CAAC;QAEhC,OAAO,OAAO,CAAC;IACjB,CAAC;CACF,CAAA;AApDY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,mBAAU,GAAE;qCAE0B,8BAAa;GADvC,kBAAkB,CAoD9B"}
{"version":3,"file":"admin-config.service.js","sourceRoot":"","sources":["../../src/admin/admin-config.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AAGlD,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;IACA;IAA7B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAEtD,KAAK,CAAC,OAAO,CAAC,QAAiB;QAC7B,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC;YACpC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS;YAC1C,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;SAC7B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;YACtC,KAAK,EAAE,EAAE,GAAG,EAAE;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW,EAAE,IAAS,EAAE,SAAiB;QACpD,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YAClC,KAAK,EAAE,EAAE,GAAG,EAAE;YACd,MAAM,EAAE;gBACN,GAAG,IAAI;gBACP,SAAS;gBACT,SAAS,EAAE,IAAI,IAAI,EAAE;aACtB;YACD,MAAM,EAAE;gBACN,GAAG;gBACH,GAAG,IAAI;gBACP,SAAS;aACV;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,GAAW;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YAClC,KAAK,EAAE,EAAE,GAAG,EAAE;SACf,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;QAGvD,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAC5B,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACd,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC1B,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;YAC5B,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClC,OAAO,GAAG,CAAC;QACb,CAAC,EACD,EAA2B,CAC5B,CAAC;QAEF,OAAO,OAAO,CAAC;IACjB,CAAC;CACF,CAAA;AAvDY,gDAAkB;6BAAlB,kBAAkB;IAD9B,IAAA,mBAAU,GAAE;qCAE0B,8BAAa;GADvC,kBAAkB,CAuD9B"}

View File

@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
const auth_guard_1 = require("../auth/auth.guard");
const admin_guard_1 = require("./guards/admin.guard");
const admin_payment_methods_service_1 = require("./admin-payment-methods.service");
const skip_maintenance_decorator_1 = require("../common/decorators/skip-maintenance.decorator");
let AdminPaymentMethodsController = class AdminPaymentMethodsController {
service;
constructor(service) {
@@ -87,6 +88,7 @@ __decorate([
exports.AdminPaymentMethodsController = AdminPaymentMethodsController = __decorate([
(0, common_1.Controller)('admin/payment-methods'),
(0, common_1.UseGuards)(auth_guard_1.AuthGuard, admin_guard_1.AdminGuard),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__metadata("design:paramtypes", [admin_payment_methods_service_1.AdminPaymentMethodsService])
], AdminPaymentMethodsController);
//# sourceMappingURL=admin-payment-methods.controller.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"admin-payment-methods.controller.js","sourceRoot":"","sources":["../../src/admin/admin-payment-methods.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,mFAA6E;AAItE,IAAM,6BAA6B,GAAnC,MAAM,6BAA6B;IACX;IAA7B,YAA6B,OAAmC;QAAnC,YAAO,GAAP,OAAO,CAA4B;IAAG,CAAC;IAGpE,OAAO;QACL,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAChC,CAAC;IAGD,OAAO,CAAc,EAAU;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAGD,MAAM,CAAS,IAAS;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAGD,MAAM,CAAc,EAAU,EAAU,IAAS;QAC/C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAGD,MAAM,CAAc,EAAU;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;IAGD,OAAO,CAAS,IAA6B;QAC3C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9C,CAAC;CACF,CAAA;AAhCY,sEAA6B;AAIxC;IADC,IAAA,YAAG,GAAE;;;;4DAGL;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;4DAEnB;AAGD;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;;;2DAEb;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACH,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,aAAI,GAAE,CAAA;;;;2DAEtC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;2DAElB;AAGD;IADC,IAAA,aAAI,EAAC,SAAS,CAAC;IACP,WAAA,IAAA,aAAI,GAAE,CAAA;;;;4DAEd;wCA/BU,6BAA6B;IAFzC,IAAA,mBAAU,EAAC,uBAAuB,CAAC;IACnC,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;qCAEO,0DAA0B;GADrD,6BAA6B,CAgCzC"}
{"version":3,"file":"admin-payment-methods.controller.js","sourceRoot":"","sources":["../../src/admin/admin-payment-methods.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,mFAA6E;AAC7E,gGAAkF;AAK3E,IAAM,6BAA6B,GAAnC,MAAM,6BAA6B;IACX;IAA7B,YAA6B,OAAmC;QAAnC,YAAO,GAAP,OAAO,CAA4B;IAAG,CAAC;IAGpE,OAAO;QACL,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAChC,CAAC;IAGD,OAAO,CAAc,EAAU;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAGD,MAAM,CAAS,IAAS;QACtB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAGD,MAAM,CAAc,EAAU,EAAU,IAAS;QAC/C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAGD,MAAM,CAAc,EAAU;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;IAGD,OAAO,CAAS,IAA6B;QAC3C,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9C,CAAC;CACF,CAAA;AAhCY,sEAA6B;AAIxC;IADC,IAAA,YAAG,GAAE;;;;4DAGL;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;4DAEnB;AAGD;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;;;2DAEb;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACH,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,aAAI,GAAE,CAAA;;;;2DAEtC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;2DAElB;AAGD;IADC,IAAA,aAAI,EAAC,SAAS,CAAC;IACP,WAAA,IAAA,aAAI,GAAE,CAAA;;;;4DAEd;wCA/BU,6BAA6B;IAHzC,IAAA,mBAAU,EAAC,uBAAuB,CAAC;IACnC,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;IAChC,IAAA,4CAAe,GAAE;qCAEsB,0DAA0B;GADrD,6BAA6B,CAgCzC"}

View File

@@ -16,10 +16,10 @@ export declare class AdminPaymentsController {
subscription: ({
plan: {
id: string;
currency: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
@@ -42,10 +42,10 @@ export declare class AdminPaymentsController {
};
} & {
id: string;
userId: string;
status: string;
createdAt: Date;
updatedAt: Date;
status: string;
userId: string;
planId: string;
startDate: Date;
endDate: Date;
@@ -56,19 +56,21 @@ export declare class AdminPaymentsController {
}) | null;
} & {
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
method: string;
userId: string;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
subscriptionId: string | null;
invoiceNumber: string;
amount: import("@prisma/client/runtime/library").Decimal;
currency: string;
method: string;
tripayReference: string | null;
tripayFee: import("@prisma/client/runtime/library").Decimal | null;
totalAmount: import("@prisma/client/runtime/library").Decimal;
paymentChannel: string | null;
paymentUrl: string | null;
qrUrl: string | null;
status: string;
proofImageUrl: string | null;
transferDate: Date | null;
verifiedBy: string | null;
@@ -79,8 +81,6 @@ export declare class AdminPaymentsController {
notes: string | null;
expiresAt: Date | null;
paidAt: Date | null;
createdAt: Date;
updatedAt: Date;
})[]>;
getPendingCount(): Promise<number>;
getMonthlyRevenue(): Promise<{
@@ -97,10 +97,10 @@ export declare class AdminPaymentsController {
subscription: ({
plan: {
id: string;
currency: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
@@ -123,10 +123,10 @@ export declare class AdminPaymentsController {
};
} & {
id: string;
userId: string;
status: string;
createdAt: Date;
updatedAt: Date;
status: string;
userId: string;
planId: string;
startDate: Date;
endDate: Date;
@@ -137,19 +137,21 @@ export declare class AdminPaymentsController {
}) | null;
} & {
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
method: string;
userId: string;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
subscriptionId: string | null;
invoiceNumber: string;
amount: import("@prisma/client/runtime/library").Decimal;
currency: string;
method: string;
tripayReference: string | null;
tripayFee: import("@prisma/client/runtime/library").Decimal | null;
totalAmount: import("@prisma/client/runtime/library").Decimal;
paymentChannel: string | null;
paymentUrl: string | null;
qrUrl: string | null;
status: string;
proofImageUrl: string | null;
transferDate: Date | null;
verifiedBy: string | null;
@@ -160,24 +162,24 @@ export declare class AdminPaymentsController {
notes: string | null;
expiresAt: Date | null;
paidAt: Date | null;
createdAt: Date;
updatedAt: Date;
}) | null>;
verify(id: string, req: RequestWithUser): Promise<{
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
method: string;
userId: string;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
subscriptionId: string | null;
invoiceNumber: string;
amount: import("@prisma/client/runtime/library").Decimal;
currency: string;
method: string;
tripayReference: string | null;
tripayFee: import("@prisma/client/runtime/library").Decimal | null;
totalAmount: import("@prisma/client/runtime/library").Decimal;
paymentChannel: string | null;
paymentUrl: string | null;
qrUrl: string | null;
status: string;
proofImageUrl: string | null;
transferDate: Date | null;
verifiedBy: string | null;
@@ -188,26 +190,26 @@ export declare class AdminPaymentsController {
notes: string | null;
expiresAt: Date | null;
paidAt: Date | null;
createdAt: Date;
updatedAt: Date;
}>;
reject(id: string, req: RequestWithUser, body: {
reason: string;
}): Promise<{
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
method: string;
userId: string;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
subscriptionId: string | null;
invoiceNumber: string;
amount: import("@prisma/client/runtime/library").Decimal;
currency: string;
method: string;
tripayReference: string | null;
tripayFee: import("@prisma/client/runtime/library").Decimal | null;
totalAmount: import("@prisma/client/runtime/library").Decimal;
paymentChannel: string | null;
paymentUrl: string | null;
qrUrl: string | null;
status: string;
proofImageUrl: string | null;
transferDate: Date | null;
verifiedBy: string | null;
@@ -218,8 +220,6 @@ export declare class AdminPaymentsController {
notes: string | null;
expiresAt: Date | null;
paidAt: Date | null;
createdAt: Date;
updatedAt: Date;
}>;
}
export {};

View File

@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
const auth_guard_1 = require("../auth/auth.guard");
const admin_guard_1 = require("./guards/admin.guard");
const admin_payments_service_1 = require("./admin-payments.service");
const skip_maintenance_decorator_1 = require("../common/decorators/skip-maintenance.decorator");
let AdminPaymentsController = class AdminPaymentsController {
service;
constructor(service) {
@@ -88,6 +89,7 @@ __decorate([
exports.AdminPaymentsController = AdminPaymentsController = __decorate([
(0, common_1.Controller)('admin/payments'),
(0, common_1.UseGuards)(auth_guard_1.AuthGuard, admin_guard_1.AdminGuard),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__metadata("design:paramtypes", [admin_payments_service_1.AdminPaymentsService])
], AdminPaymentsController);
//# sourceMappingURL=admin-payments.controller.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"admin-payments.controller.js","sourceRoot":"","sources":["../../src/admin/admin-payments.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,qEAAgE;AAUzD,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IACL;IAA7B,YAA6B,OAA6B;QAA7B,YAAO,GAAP,OAAO,CAAsB;IAAG,CAAC;IAG9D,OAAO,CAAkB,MAAe;QACtC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAGD,eAAe;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;IACxC,CAAC;IAGD,iBAAiB;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAC1C,CAAC;IAGD,OAAO,CAAc,EAAU;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAGD,MAAM,CAAc,EAAU,EAAS,GAAoB;QACzD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;IAGD,MAAM,CACS,EAAU,EAChB,GAAoB,EACnB,IAAwB;QAEhC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/D,CAAC;CACF,CAAA;AApCY,0DAAuB;AAIlC;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,cAAK,EAAC,QAAQ,CAAC,CAAA;;;;sDAEvB;AAGD;IADC,IAAA,YAAG,EAAC,eAAe,CAAC;;;;8DAGpB;AAGD;IADC,IAAA,YAAG,EAAC,iBAAiB,CAAC;;;;gEAGtB;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;sDAEnB;AAGD;IADC,IAAA,aAAI,EAAC,YAAY,CAAC;IACX,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,YAAG,GAAE,CAAA;;;;qDAErC;AAGD;IADC,IAAA,aAAI,EAAC,YAAY,CAAC;IAEhB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;qDAGR;kCAnCU,uBAAuB;IAFnC,IAAA,mBAAU,EAAC,gBAAgB,CAAC;IAC5B,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;qCAEO,6CAAoB;GAD/C,uBAAuB,CAoCnC"}
{"version":3,"file":"admin-payments.controller.js","sourceRoot":"","sources":["../../src/admin/admin-payments.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,qEAAgE;AAChE,gGAAkF;AAW3E,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IACL;IAA7B,YAA6B,OAA6B;QAA7B,YAAO,GAAP,OAAO,CAAsB;IAAG,CAAC;IAG9D,OAAO,CAAkB,MAAe;QACtC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAGD,eAAe;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;IACxC,CAAC;IAGD,iBAAiB;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAC1C,CAAC;IAGD,OAAO,CAAc,EAAU;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAGD,MAAM,CAAc,EAAU,EAAS,GAAoB;QACzD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;IAGD,MAAM,CACS,EAAU,EAChB,GAAoB,EACnB,IAAwB;QAEhC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/D,CAAC;CACF,CAAA;AApCY,0DAAuB;AAIlC;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,cAAK,EAAC,QAAQ,CAAC,CAAA;;;;sDAEvB;AAGD;IADC,IAAA,YAAG,EAAC,eAAe,CAAC;;;;8DAGpB;AAGD;IADC,IAAA,YAAG,EAAC,iBAAiB,CAAC;;;;gEAGtB;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;sDAEnB;AAGD;IADC,IAAA,aAAI,EAAC,YAAY,CAAC;IACX,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,YAAG,GAAE,CAAA;;;;qDAErC;AAGD;IADC,IAAA,aAAI,EAAC,YAAY,CAAC;IAEhB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;qDAGR;kCAnCU,uBAAuB;IAHnC,IAAA,mBAAU,EAAC,gBAAgB,CAAC;IAC5B,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;IAChC,IAAA,4CAAe,GAAE;qCAEsB,6CAAoB;GAD/C,uBAAuB,CAoCnC"}

View File

@@ -11,10 +11,10 @@ export declare class AdminPaymentsService {
subscription: ({
plan: {
id: string;
currency: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
@@ -37,10 +37,10 @@ export declare class AdminPaymentsService {
};
} & {
id: string;
userId: string;
status: string;
createdAt: Date;
updatedAt: Date;
status: string;
userId: string;
planId: string;
startDate: Date;
endDate: Date;
@@ -51,19 +51,21 @@ export declare class AdminPaymentsService {
}) | null;
} & {
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
method: string;
userId: string;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
subscriptionId: string | null;
invoiceNumber: string;
amount: import("@prisma/client/runtime/library").Decimal;
currency: string;
method: string;
tripayReference: string | null;
tripayFee: import("@prisma/client/runtime/library").Decimal | null;
totalAmount: import("@prisma/client/runtime/library").Decimal;
paymentChannel: string | null;
paymentUrl: string | null;
qrUrl: string | null;
status: string;
proofImageUrl: string | null;
transferDate: Date | null;
verifiedBy: string | null;
@@ -74,8 +76,6 @@ export declare class AdminPaymentsService {
notes: string | null;
expiresAt: Date | null;
paidAt: Date | null;
createdAt: Date;
updatedAt: Date;
})[]>;
findOne(id: string): Promise<({
user: {
@@ -86,10 +86,10 @@ export declare class AdminPaymentsService {
subscription: ({
plan: {
id: string;
currency: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
@@ -112,10 +112,10 @@ export declare class AdminPaymentsService {
};
} & {
id: string;
userId: string;
status: string;
createdAt: Date;
updatedAt: Date;
status: string;
userId: string;
planId: string;
startDate: Date;
endDate: Date;
@@ -126,19 +126,21 @@ export declare class AdminPaymentsService {
}) | null;
} & {
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
method: string;
userId: string;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
subscriptionId: string | null;
invoiceNumber: string;
amount: import("@prisma/client/runtime/library").Decimal;
currency: string;
method: string;
tripayReference: string | null;
tripayFee: import("@prisma/client/runtime/library").Decimal | null;
totalAmount: import("@prisma/client/runtime/library").Decimal;
paymentChannel: string | null;
paymentUrl: string | null;
qrUrl: string | null;
status: string;
proofImageUrl: string | null;
transferDate: Date | null;
verifiedBy: string | null;
@@ -149,24 +151,24 @@ export declare class AdminPaymentsService {
notes: string | null;
expiresAt: Date | null;
paidAt: Date | null;
createdAt: Date;
updatedAt: Date;
}) | null>;
verify(id: string, adminUserId: string): Promise<{
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
method: string;
userId: string;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
subscriptionId: string | null;
invoiceNumber: string;
amount: import("@prisma/client/runtime/library").Decimal;
currency: string;
method: string;
tripayReference: string | null;
tripayFee: import("@prisma/client/runtime/library").Decimal | null;
totalAmount: import("@prisma/client/runtime/library").Decimal;
paymentChannel: string | null;
paymentUrl: string | null;
qrUrl: string | null;
status: string;
proofImageUrl: string | null;
transferDate: Date | null;
verifiedBy: string | null;
@@ -177,24 +179,24 @@ export declare class AdminPaymentsService {
notes: string | null;
expiresAt: Date | null;
paidAt: Date | null;
createdAt: Date;
updatedAt: Date;
}>;
reject(id: string, adminUserId: string, reason: string): Promise<{
id: string;
createdAt: Date;
updatedAt: Date;
status: string;
method: string;
userId: string;
currency: string;
amount: import("@prisma/client/runtime/library").Decimal;
subscriptionId: string | null;
invoiceNumber: string;
amount: import("@prisma/client/runtime/library").Decimal;
currency: string;
method: string;
tripayReference: string | null;
tripayFee: import("@prisma/client/runtime/library").Decimal | null;
totalAmount: import("@prisma/client/runtime/library").Decimal;
paymentChannel: string | null;
paymentUrl: string | null;
qrUrl: string | null;
status: string;
proofImageUrl: string | null;
transferDate: Date | null;
verifiedBy: string | null;
@@ -205,8 +207,6 @@ export declare class AdminPaymentsService {
notes: string | null;
expiresAt: Date | null;
paidAt: Date | null;
createdAt: Date;
updatedAt: Date;
}>;
getPendingCount(): Promise<number>;
getMonthlyRevenue(): Promise<{

View File

@@ -123,7 +123,20 @@ let AdminPaymentsService = class AdminPaymentsService {
},
});
const monthlyData = {};
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
payments.forEach((payment) => {
if (payment.paidAt) {
const date = new Date(payment.paidAt);

View File

@@ -1 +1 @@
{"version":3,"file":"admin-payments.service.js","sourceRoot":"","sources":["../../src/admin/admin-payments.service.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"admin-payments.service.js","sourceRoot":"","sources":["../../src/admin/admin-payments.service.ts"],"names":[],"mappings":"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"}

View File

@@ -8,11 +8,13 @@ export declare class AdminPlansController {
};
} & {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -29,8 +31,6 @@ export declare class AdminPlansController {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
})[]>;
findOne(id: string): Promise<({
_count: {
@@ -38,11 +38,13 @@ export declare class AdminPlansController {
};
} & {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -59,16 +61,16 @@ export declare class AdminPlansController {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
}) | null>;
create(data: any): Promise<{
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -85,16 +87,16 @@ export declare class AdminPlansController {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
}>;
update(id: string, data: any): Promise<{
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -111,8 +113,6 @@ export declare class AdminPlansController {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
}>;
delete(id: string): Promise<{
success: boolean;
@@ -120,11 +120,13 @@ export declare class AdminPlansController {
action: string;
plan: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -141,8 +143,6 @@ export declare class AdminPlansController {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
};
} | {
success: boolean;

View File

@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
const auth_guard_1 = require("../auth/auth.guard");
const admin_guard_1 = require("./guards/admin.guard");
const admin_plans_service_1 = require("./admin-plans.service");
const skip_maintenance_decorator_1 = require("../common/decorators/skip-maintenance.decorator");
let AdminPlansController = class AdminPlansController {
plansService;
constructor(plansService) {
@@ -87,6 +88,7 @@ __decorate([
exports.AdminPlansController = AdminPlansController = __decorate([
(0, common_1.Controller)('admin/plans'),
(0, common_1.UseGuards)(auth_guard_1.AuthGuard, admin_guard_1.AdminGuard),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__metadata("design:paramtypes", [admin_plans_service_1.AdminPlansService])
], AdminPlansController);
//# sourceMappingURL=admin-plans.controller.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"admin-plans.controller.js","sourceRoot":"","sources":["../../src/admin/admin-plans.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,+DAA0D;AAInD,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACF;IAA7B,YAA6B,YAA+B;QAA/B,iBAAY,GAAZ,YAAY,CAAmB;IAAG,CAAC;IAGhE,OAAO;QACL,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;IACrC,CAAC;IAGD,OAAO,CAAc,EAAU;QAC7B,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACvC,CAAC;IAGD,MAAM,CAAS,IAAS;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAGD,MAAM,CAAc,EAAU,EAAU,IAAS;QAC/C,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC;IAGD,MAAM,CAAc,EAAU;QAC5B,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;IAGD,OAAO,CAAS,IAA2B;QACzC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC;CACF,CAAA;AAhCY,oDAAoB;AAI/B;IADC,IAAA,YAAG,GAAE;;;;mDAGL;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;mDAEnB;AAGD;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;;;kDAEb;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACH,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,aAAI,GAAE,CAAA;;;;kDAEtC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;kDAElB;AAGD;IADC,IAAA,aAAI,EAAC,SAAS,CAAC;IACP,WAAA,IAAA,aAAI,GAAE,CAAA;;;;mDAEd;+BA/BU,oBAAoB;IAFhC,IAAA,mBAAU,EAAC,aAAa,CAAC;IACzB,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;qCAEY,uCAAiB;GADjD,oBAAoB,CAgChC"}
{"version":3,"file":"admin-plans.controller.js","sourceRoot":"","sources":["../../src/admin/admin-plans.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,+DAA0D;AAC1D,gGAAkF;AAK3E,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACF;IAA7B,YAA6B,YAA+B;QAA/B,iBAAY,GAAZ,YAAY,CAAmB;IAAG,CAAC;IAGhE,OAAO;QACL,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;IACrC,CAAC;IAGD,OAAO,CAAc,EAAU;QAC7B,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACvC,CAAC;IAGD,MAAM,CAAS,IAAS;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxC,CAAC;IAGD,MAAM,CAAc,EAAU,EAAU,IAAS;QAC/C,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC5C,CAAC;IAGD,MAAM,CAAc,EAAU;QAC5B,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;IAGD,OAAO,CAAS,IAA2B;QACzC,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC;CACF,CAAA;AAhCY,oDAAoB;AAI/B;IADC,IAAA,YAAG,GAAE;;;;mDAGL;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;mDAEnB;AAGD;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;;;kDAEb;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACH,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,aAAI,GAAE,CAAA;;;;kDAEtC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;kDAElB;AAGD;IADC,IAAA,aAAI,EAAC,SAAS,CAAC;IACP,WAAA,IAAA,aAAI,GAAE,CAAA;;;;mDAEd;+BA/BU,oBAAoB;IAHhC,IAAA,mBAAU,EAAC,aAAa,CAAC;IACzB,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;IAChC,IAAA,4CAAe,GAAE;qCAE2B,uCAAiB;GADjD,oBAAoB,CAgChC"}

View File

@@ -8,11 +8,13 @@ export declare class AdminPlansService {
};
} & {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -29,8 +31,6 @@ export declare class AdminPlansService {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
})[]>;
findOne(id: string): Promise<({
_count: {
@@ -38,11 +38,13 @@ export declare class AdminPlansService {
};
} & {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -59,16 +61,16 @@ export declare class AdminPlansService {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
}) | null>;
create(data: any): Promise<{
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -85,16 +87,16 @@ export declare class AdminPlansService {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
}>;
update(id: string, data: any): Promise<{
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -111,8 +113,6 @@ export declare class AdminPlansService {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
}>;
delete(id: string): Promise<{
success: boolean;
@@ -120,11 +120,13 @@ export declare class AdminPlansService {
action: string;
plan: {
id: string;
createdAt: Date;
updatedAt: Date;
name: string;
currency: string;
slug: string;
description: string | null;
price: import("@prisma/client/runtime/library").Decimal;
currency: string;
durationType: string;
durationDays: number | null;
trialDays: number;
@@ -141,8 +143,6 @@ export declare class AdminPlansService {
maxTeamMembers: number | null;
apiEnabled: boolean;
apiRateLimit: number | null;
createdAt: Date;
updatedAt: Date;
};
} | {
success: boolean;

View File

@@ -177,4 +177,32 @@ export declare class AdminUsersController {
cancelledAt: Date | null;
cancellationReason: string | null;
}>;
create(body: {
email: string;
password: string;
name?: string;
role?: string;
}): Promise<{
id: string;
email: string;
createdAt: Date;
emailVerified: boolean;
name: string | null;
role: string;
}>;
update(id: string, body: {
email?: string;
name?: string;
role?: string;
}): Promise<{
id: string;
email: string;
createdAt: Date;
emailVerified: boolean;
name: string | null;
role: string;
}>;
delete(id: string): Promise<{
message: string;
}>;
}

View File

@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
const auth_guard_1 = require("../auth/auth.guard");
const admin_guard_1 = require("./guards/admin.guard");
const admin_users_service_1 = require("./admin-users.service");
const skip_maintenance_decorator_1 = require("../common/decorators/skip-maintenance.decorator");
let AdminUsersController = class AdminUsersController {
service;
constructor(service) {
@@ -43,6 +44,15 @@ let AdminUsersController = class AdminUsersController {
grantProAccess(id, body) {
return this.service.grantProAccess(id, body.planSlug, body.durationDays);
}
create(body) {
return this.service.create(body);
}
update(id, body) {
return this.service.update(id, body);
}
delete(id) {
return this.service.delete(id);
}
};
exports.AdminUsersController = AdminUsersController;
__decorate([
@@ -96,9 +106,32 @@ __decorate([
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", void 0)
], AdminUsersController.prototype, "grantProAccess", null);
__decorate([
(0, common_1.Post)(),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], AdminUsersController.prototype, "create", null);
__decorate([
(0, common_1.Put)(':id'),
__param(0, (0, common_1.Param)('id')),
__param(1, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", void 0)
], AdminUsersController.prototype, "update", null);
__decorate([
(0, common_1.Delete)(':id'),
__param(0, (0, common_1.Param)('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", void 0)
], AdminUsersController.prototype, "delete", null);
exports.AdminUsersController = AdminUsersController = __decorate([
(0, common_1.Controller)('admin/users'),
(0, common_1.UseGuards)(auth_guard_1.AuthGuard, admin_guard_1.AdminGuard),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__metadata("design:paramtypes", [admin_users_service_1.AdminUsersService])
], AdminUsersController);
//# sourceMappingURL=admin-users.controller.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"admin-users.controller.js","sourceRoot":"","sources":["../../src/admin/admin-users.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,mDAA+C;AAC/C,sDAAkD;AAClD,+DAA0D;AAInD,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACF;IAA7B,YAA6B,OAA0B;QAA1B,YAAO,GAAP,OAAO,CAAmB;IAAG,CAAC;IAG3D,OAAO,CAAkB,MAAe;QACtC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAGD,QAAQ;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;IACjC,CAAC;IAGD,OAAO,CAAc,EAAU;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClC,CAAC;IAGD,UAAU,CAAc,EAAU,EAAU,IAAsB;QAChE,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC;IAGD,OAAO,CAAc,EAAU,EAAU,IAAwB;QAC/D,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IAGD,SAAS,CAAc,EAAU;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAGD,cAAc,CACC,EAAU,EACf,IAAgD;QAExD,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAC3E,CAAC;CACF,CAAA;AAxCY,oDAAoB;AAI/B;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,cAAK,EAAC,QAAQ,CAAC,CAAA;;;;mDAEvB;AAGD;IADC,IAAA,YAAG,EAAC,OAAO,CAAC;;;;oDAGZ;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACF,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;mDAEnB;AAGD;IADC,IAAA,YAAG,EAAC,UAAU,CAAC;IACJ,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,aAAI,GAAE,CAAA;;;;sDAE1C;AAGD;IADC,IAAA,aAAI,EAAC,aAAa,CAAC;IACX,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,aAAI,GAAE,CAAA;;;;mDAEvC;AAGD;IADC,IAAA,aAAI,EAAC,eAAe,CAAC;IACX,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;qDAErB;AAGD;IADC,IAAA,aAAI,EAAC,eAAe,CAAC;IAEnB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;;;0DAGR;+BAvCU,oBAAoB;IAFhC,IAAA,mBAAU,EAAC,aAAa,CAAC;IACzB,IAAA,kBAAS,EAAC,sBAAS,EAAE,wBAAU,CAAC;qCAEO,uCAAiB;GAD5C,oBAAoB,CAwChC"}
{"version":3,"file":"admin-users.controller.js","sourceRoot":"","sources":["../../src/admin/admin-users.controller.ts"],"names":[],"mappings":"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"}

View File

@@ -170,4 +170,32 @@ export declare class AdminUsersService {
activeSubscriptions: number;
suspendedUsers: number;
}>;
create(data: {
email: string;
password: string;
name?: string;
role?: string;
}): Promise<{
id: string;
email: string;
createdAt: Date;
emailVerified: boolean;
name: string | null;
role: string;
}>;
update(id: string, data: {
email?: string;
name?: string;
role?: string;
}): Promise<{
id: string;
email: string;
createdAt: Date;
emailVerified: boolean;
name: string | null;
role: string;
}>;
delete(id: string): Promise<{
message: string;
}>;
}

View File

@@ -1,10 +1,43 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
@@ -12,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.AdminUsersService = void 0;
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("../prisma/prisma.service");
const bcrypt = __importStar(require("bcrypt"));
let AdminUsersService = class AdminUsersService {
prisma;
constructor(prisma) {
@@ -139,6 +173,76 @@ let AdminUsersService = class AdminUsersService {
suspendedUsers,
};
}
async create(data) {
const existing = await this.prisma.user.findUnique({
where: { email: data.email },
});
if (existing) {
throw new common_1.ConflictException('User with this email already exists');
}
const hashedPassword = await bcrypt.hash(data.password, 10);
return this.prisma.user.create({
data: {
email: data.email,
passwordHash: hashedPassword,
name: data.name || null,
role: data.role || 'user',
emailVerified: true,
},
select: {
id: true,
email: true,
name: true,
role: true,
emailVerified: true,
createdAt: true,
},
});
}
async update(id, data) {
const user = await this.prisma.user.findUnique({
where: { id },
});
if (!user) {
throw new common_1.NotFoundException('User not found');
}
if (data.email && data.email !== user.email) {
const existing = await this.prisma.user.findUnique({
where: { email: data.email },
});
if (existing) {
throw new common_1.ConflictException('Email already in use');
}
}
return this.prisma.user.update({
where: { id },
data: {
...(data.email && { email: data.email }),
...(data.name !== undefined && { name: data.name }),
...(data.role && { role: data.role }),
},
select: {
id: true,
email: true,
name: true,
role: true,
emailVerified: true,
createdAt: true,
},
});
}
async delete(id) {
const user = await this.prisma.user.findUnique({
where: { id },
});
if (!user) {
throw new common_1.NotFoundException('User not found');
}
await this.prisma.user.delete({
where: { id },
});
return { message: 'User deleted successfully' };
}
};
exports.AdminUsersService = AdminUsersService;
exports.AdminUsersService = AdminUsersService = __decorate([

File diff suppressed because one or more lines are too long

View File

@@ -42,6 +42,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.AppModule = void 0;
const common_1 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const core_1 = require("@nestjs/core");
const path = __importStar(require("path"));
const prisma_module_1 = require("./prisma/prisma.module");
const auth_module_1 = require("./auth/auth.module");
@@ -52,6 +53,7 @@ const transactions_module_1 = require("./transactions/transactions.module");
const categories_module_1 = require("./categories/categories.module");
const otp_module_1 = require("./otp/otp.module");
const admin_module_1 = require("./admin/admin.module");
const maintenance_guard_1 = require("./common/guards/maintenance.guard");
let AppModule = class AppModule {
};
exports.AppModule = AppModule;
@@ -75,7 +77,12 @@ exports.AppModule = AppModule = __decorate([
admin_module_1.AdminModule,
],
controllers: [health_controller_1.HealthController],
providers: [],
providers: [
{
provide: core_1.APP_GUARD,
useClass: maintenance_guard_1.MaintenanceGuard,
},
],
})
], AppModule);
//# sourceMappingURL=app.module.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAwC;AACxC,2CAA8C;AAC9C,2CAA6B;AAC7B,0DAAsD;AACtD,oDAAgD;AAChD,kEAA8D;AAC9D,uDAAmD;AACnD,6DAAyD;AACzD,4EAAwE;AACxE,sEAAkE;AAClE,iDAA6C;AAC7C,uDAAmD;AAuB5C,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IArBrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,qBAAY,CAAC,OAAO,CAAC;gBACnB,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE;oBACX,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC;oBACnC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;iBAC1C;aACF,CAAC;YACF,4BAAY;YACZ,wBAAU;YACV,0BAAW;YACX,8BAAa;YACb,wCAAkB;YAClB,oCAAgB;YAChB,sBAAS;YACT,0BAAW;SACZ;QACD,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,EAAE;KACd,CAAC;GACW,SAAS,CAAG"}
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAwC;AACxC,2CAA8C;AAC9C,uCAAyC;AACzC,2CAA6B;AAC7B,0DAAsD;AACtD,oDAAgD;AAChD,kEAA8D;AAC9D,uDAAmD;AACnD,6DAAyD;AACzD,4EAAwE;AACxE,sEAAkE;AAClE,iDAA6C;AAC7C,uDAAmD;AACnD,yEAAqE;AA4B9D,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IA1BrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,qBAAY,CAAC,OAAO,CAAC;gBACnB,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE;oBACX,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC;oBACnC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;iBAC1C;aACF,CAAC;YACF,4BAAY;YACZ,wBAAU;YACV,0BAAW;YACX,8BAAa;YACb,wCAAkB;YAClB,oCAAgB;YAChB,sBAAS;YACT,0BAAW;SACZ;QACD,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE;YACT;gBACE,OAAO,EAAE,gBAAS;gBAClB,QAAQ,EAAE,oCAAgB;aAC3B;SACF;KACF,CAAC;GACW,SAAS,CAAG"}

View File

@@ -17,6 +17,7 @@ const common_1 = require("@nestjs/common");
const auth_guard_1 = require("./auth.guard");
const passport_1 = require("@nestjs/passport");
const auth_service_1 = require("./auth.service");
const skip_maintenance_decorator_1 = require("../common/decorators/skip-maintenance.decorator");
let AuthController = class AuthController {
authService;
constructor(authService) {
@@ -53,6 +54,7 @@ let AuthController = class AuthController {
exports.AuthController = AuthController;
__decorate([
(0, common_1.Post)('register'),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
@@ -60,6 +62,7 @@ __decorate([
], AuthController.prototype, "register", null);
__decorate([
(0, common_1.Post)('login'),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
@@ -67,6 +70,7 @@ __decorate([
], AuthController.prototype, "login", null);
__decorate([
(0, common_1.Post)('verify-otp'),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__param(0, (0, common_1.Body)()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
@@ -74,6 +78,7 @@ __decorate([
], AuthController.prototype, "verifyOtp", null);
__decorate([
(0, common_1.Get)('google'),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
(0, common_1.UseGuards)((0, passport_1.AuthGuard)('google')),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
@@ -81,6 +86,7 @@ __decorate([
], AuthController.prototype, "googleAuth", null);
__decorate([
(0, common_1.Get)('google/callback'),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
(0, common_1.UseGuards)((0, passport_1.AuthGuard)('google')),
__param(0, (0, common_1.Req)()),
__param(1, (0, common_1.Res)()),

View File

@@ -1 +1 @@
{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAQwB;AACxB,6CAAyD;AACzD,+CAA6C;AAC7C,iDAA6C;AAC7C,gGAAkF;AAW3E,IAAM,cAAc,GAApB,MAAM,cAAc;IACL;IAApB,YAAoB,WAAwB;QAAxB,gBAAW,GAAX,WAAW,CAAa;IAAG,CAAC;IAI1C,AAAN,KAAK,CAAC,QAAQ,CACJ,IAAwD;QAEhE,OAAO,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzE,CAAC;IAIK,AAAN,KAAK,CAAC,KAAK,CAAS,IAAyC;QAC3D,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3D,CAAC;IAIK,AAAN,KAAK,CAAC,SAAS,CAEb,IAIC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC,iBAAiB,CACvC,IAAI,CAAC,SAAS,EACd,IAAI,CAAC,OAAO,EACZ,IAAI,CAAC,MAAM,CACZ,CAAC;IACJ,CAAC;IAKK,AAAN,KAAK,CAAC,UAAU;IAEhB,CAAC;IAKK,AAAN,KAAK,CAAC,kBAAkB,CAAQ,GAAQ,EAAS,GAAa;QAE5D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAG5D,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,uBAAuB,CAAC;QAEvE,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YAEvB,GAAG,CAAC,QAAQ,CACV,GAAG,WAAW,mBAAmB,MAAM,CAAC,SAAS,YAAY,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CACvG,CAAC;QACJ,CAAC;aAAM,CAAC;YAEN,GAAG,CAAC,QAAQ,CAAC,GAAG,WAAW,wBAAwB,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IAIK,AAAN,KAAK,CAAC,UAAU,CAAQ,GAAoB;QAC1C,OAAO,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1D,CAAC;IAIK,AAAN,KAAK,CAAC,cAAc,CACX,GAAoB,EAE3B,IAIC;QAED,OAAO,IAAI,CAAC,WAAW,CAAC,cAAc,CACpC,GAAG,CAAC,IAAI,CAAC,MAAM,EACf,IAAI,CAAC,eAAe,EACpB,IAAI,CAAC,WAAW,EAChB,IAAI,CAAC,iBAAiB,CACvB,CAAC;IACJ,CAAC;CACF,CAAA;AAtFY,wCAAc;AAKnB;IAFL,IAAA,aAAI,EAAC,UAAU,CAAC;IAChB,IAAA,4CAAe,GAAE;IAEf,WAAA,IAAA,aAAI,GAAE,CAAA;;;;8CAGR;AAIK;IAFL,IAAA,aAAI,EAAC,OAAO,CAAC;IACb,IAAA,4CAAe,GAAE;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;2CAElB;AAIK;IAFL,IAAA,aAAI,EAAC,YAAY,CAAC;IAClB,IAAA,4CAAe,GAAE;IAEf,WAAA,IAAA,aAAI,GAAE,CAAA;;;;+CAYR;AAKK;IAHL,IAAA,YAAG,EAAC,QAAQ,CAAC;IACb,IAAA,4CAAe,GAAE;IACjB,IAAA,kBAAS,EAAC,IAAA,oBAAS,EAAC,QAAQ,CAAC,CAAC;;;;gDAG9B;AAKK;IAHL,IAAA,YAAG,EAAC,iBAAiB,CAAC;IACtB,IAAA,4CAAe,GAAE;IACjB,IAAA,kBAAS,EAAC,IAAA,oBAAS,EAAC,QAAQ,CAAC,CAAC;IACL,WAAA,IAAA,YAAG,GAAE,CAAA;IAAY,WAAA,IAAA,YAAG,GAAE,CAAA;;;;wDAgB/C;AAIK;IAFL,IAAA,YAAG,EAAC,IAAI,CAAC;IACT,IAAA,kBAAS,EAAC,sBAAY,CAAC;IACN,WAAA,IAAA,YAAG,GAAE,CAAA;;;;gDAEtB;AAIK;IAFL,IAAA,aAAI,EAAC,iBAAiB,CAAC;IACvB,IAAA,kBAAS,EAAC,sBAAY,CAAC;IAErB,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;oDAaR;yBArFU,cAAc;IAD1B,IAAA,mBAAU,EAAC,MAAM,CAAC;qCAEgB,0BAAW;GADjC,cAAc,CAsF1B"}

View File

@@ -32,7 +32,7 @@ exports.AuthModule = AuthModule = __decorate([
],
controllers: [auth_controller_1.AuthController],
providers: [auth_service_1.AuthService, jwt_strategy_1.JwtStrategy, google_strategy_1.GoogleStrategy],
exports: [auth_service_1.AuthService],
exports: [auth_service_1.AuthService, jwt_1.JwtModule],
})
], AuthModule);
//# sourceMappingURL=auth.module.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAoD;AACpD,qCAAwC;AACxC,+CAAkD;AAClD,uDAAmD;AACnD,iDAA6C;AAC7C,iDAA6C;AAC7C,uDAAmD;AACnD,2DAAuD;AACvD,kDAA8C;AAgBvC,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAdtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,4BAAY;YACZ,yBAAc;YACd,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,sBAAS,CAAC;YAC3B,eAAS,CAAC,QAAQ,CAAC;gBACjB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,iBAAiB;gBACnD,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;aACjC,CAAC;SACH;QACD,WAAW,EAAE,CAAC,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,EAAE,0BAAW,EAAE,gCAAc,CAAC;QACrD,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;GACW,UAAU,CAAG"}
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAoD;AACpD,qCAAwC;AACxC,+CAAkD;AAClD,uDAAmD;AACnD,iDAA6C;AAC7C,iDAA6C;AAC7C,uDAAmD;AACnD,2DAAuD;AACvD,kDAA8C;AAgBvC,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAdtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,4BAAY;YACZ,yBAAc;YACd,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,sBAAS,CAAC;YAC3B,eAAS,CAAC,QAAQ,CAAC;gBACjB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,iBAAiB;gBACnD,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;aACjC,CAAC;SACH;QACD,WAAW,EAAE,CAAC,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,EAAE,0BAAW,EAAE,gCAAc,CAAC;QACrD,OAAO,EAAE,CAAC,0BAAW,EAAE,eAAS,CAAC;KAClC,CAAC;GACW,UAAU,CAAG"}

View File

@@ -25,7 +25,7 @@ let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(pas
return {
userId: payload.sub,
email: payload.email,
role: payload.role || 'user'
role: payload.role || 'user',
};
}
};

View File

@@ -1 +1 @@
{"version":3,"file":"categories.controller.js","sourceRoot":"","sources":["../../src/categories/categories.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA4F;AAC5F,yEAAqE;AACrE,+EAA0E;AAC1E,mDAA+C;AAUxC,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACF;IAA7B,YAA6B,iBAAoC;QAApC,sBAAiB,GAAjB,iBAAiB,CAAmB;IAAG,CAAC;IAGrE,MAAM,CAAQ,GAAoB,EAAU,iBAAoC;QAC9E,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;YACnC,GAAG,iBAAiB;YACpB,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM;SACxB,CAAC,CAAC;IACL,CAAC;IAGD,OAAO,CAAQ,GAAoB;QACjC,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC;IAGD,MAAM,CAAQ,GAAoB,EAAe,EAAU;QACzD,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5D,CAAC;CACF,CAAA;AApBY,oDAAoB;AAI/B;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,YAAG,GAAE,CAAA;IAAwB,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAoB,uCAAiB;;kDAK/E;AAGD;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,YAAG,GAAE,CAAA;;;;mDAEb;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,YAAG,GAAE,CAAA;IAAwB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;kDAE/C;+BAnBU,oBAAoB;IAFhC,IAAA,mBAAU,EAAC,YAAY,CAAC;IACxB,IAAA,kBAAS,EAAC,sBAAS,CAAC;qCAE6B,sCAAiB;GADtD,oBAAoB,CAoBhC"}
{"version":3,"file":"categories.controller.js","sourceRoot":"","sources":["../../src/categories/categories.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CASwB;AACxB,yEAAqE;AACrE,+EAA0E;AAC1E,mDAA+C;AAUxC,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACF;IAA7B,YAA6B,iBAAoC;QAApC,sBAAiB,GAAjB,iBAAiB,CAAmB;IAAG,CAAC;IAGrE,MAAM,CACG,GAAoB,EACnB,iBAAoC;QAE5C,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;YACnC,GAAG,iBAAiB;YACpB,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM;SACxB,CAAC,CAAC;IACL,CAAC;IAGD,OAAO,CAAQ,GAAoB;QACjC,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC;IAGD,MAAM,CAAQ,GAAoB,EAAe,EAAU;QACzD,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5D,CAAC;CACF,CAAA;AAvBY,oDAAoB;AAI/B;IADC,IAAA,aAAI,GAAE;IAEJ,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAoB,uCAAiB;;kDAM7C;AAGD;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,YAAG,GAAE,CAAA;;;;mDAEb;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,YAAG,GAAE,CAAA;IAAwB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;kDAE/C;+BAtBU,oBAAoB;IAFhC,IAAA,mBAAU,EAAC,YAAY,CAAC;IACxB,IAAA,kBAAS,EAAC,sBAAS,CAAC;qCAE6B,sCAAiB;GADtD,oBAAoB,CAuBhC"}

View File

@@ -0,0 +1 @@
export declare const SkipMaintenance: () => import("@nestjs/common").CustomDecorator<string>;

View File

@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SkipMaintenance = void 0;
const common_1 = require("@nestjs/common");
const SkipMaintenance = () => (0, common_1.SetMetadata)('skipMaintenance', true);
exports.SkipMaintenance = SkipMaintenance;
//# sourceMappingURL=skip-maintenance.decorator.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"skip-maintenance.decorator.js","sourceRoot":"","sources":["../../../src/common/decorators/skip-maintenance.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAEtC,MAAM,eAAe,GAAG,GAAG,EAAE,CAAC,IAAA,oBAAW,EAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;AAA7D,QAAA,eAAe,mBAA8C"}

View File

@@ -0,0 +1,11 @@
import { CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PrismaService } from '../../prisma/prisma.service';
import { JwtService } from '@nestjs/jwt';
export declare class MaintenanceGuard implements CanActivate {
private reflector;
private prisma;
private jwtService;
constructor(reflector: Reflector, prisma: PrismaService, jwtService: JwtService);
canActivate(context: ExecutionContext): Promise<boolean>;
}

View File

@@ -0,0 +1,71 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MaintenanceGuard = void 0;
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const prisma_service_1 = require("../../prisma/prisma.service");
const jwt_1 = require("@nestjs/jwt");
let MaintenanceGuard = class MaintenanceGuard {
reflector;
prisma;
jwtService;
constructor(reflector, prisma, jwtService) {
this.reflector = reflector;
this.prisma = prisma;
this.jwtService = jwtService;
}
async canActivate(context) {
const isExempt = this.reflector.get('skipMaintenance', context.getHandler());
const isControllerExempt = this.reflector.get('skipMaintenance', context.getClass());
if (isExempt || isControllerExempt) {
return true;
}
const maintenanceConfig = await this.prisma.appConfig.findUnique({
where: { key: 'maintenance_mode' },
});
const isMaintenanceMode = maintenanceConfig?.value === 'true';
if (!isMaintenanceMode) {
return true;
}
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
try {
const token = authHeader.substring(7);
const payload = this.jwtService.verify(token);
if (payload.role === 'admin') {
return true;
}
}
catch (error) {
}
}
const messageConfig = await this.prisma.appConfig.findUnique({
where: { key: 'maintenance_message' },
});
const message = messageConfig?.value ||
'System is under maintenance. Please try again later.';
throw new common_1.ServiceUnavailableException({
statusCode: 503,
message: message,
maintenanceMode: true,
});
}
};
exports.MaintenanceGuard = MaintenanceGuard;
exports.MaintenanceGuard = MaintenanceGuard = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [core_1.Reflector,
prisma_service_1.PrismaService,
jwt_1.JwtService])
], MaintenanceGuard);
//# sourceMappingURL=maintenance.guard.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"maintenance.guard.js","sourceRoot":"","sources":["../../../src/common/guards/maintenance.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,uCAAyC;AACzC,gEAA4D;AAC5D,qCAAyC;AAGlC,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAEjB;IACA;IACA;IAHV,YACU,SAAoB,EACpB,MAAqB,EACrB,UAAsB;QAFtB,cAAS,GAAT,SAAS,CAAW;QACpB,WAAM,GAAN,MAAM,CAAe;QACrB,eAAU,GAAV,UAAU,CAAY;IAC7B,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,OAAyB;QAEzC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAU,iBAAiB,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QACtF,MAAM,kBAAkB,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAU,iBAAiB,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAE9F,IAAI,QAAQ,IAAI,kBAAkB,EAAE,CAAC;YACnC,OAAO,IAAI,CAAC;QACd,CAAC;QAGD,MAAM,iBAAiB,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;YAC/D,KAAK,EAAE,EAAE,GAAG,EAAE,kBAAkB,EAAE;SACnC,CAAC,CAAC;QAEH,MAAM,iBAAiB,GAAG,iBAAiB,EAAE,KAAK,KAAK,MAAM,CAAC;QAE9D,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAGD,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QACpD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QAEjD,IAAI,UAAU,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;gBACtC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAG9C,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC7B,OAAO,IAAI,CAAC;gBACd,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;YAEjB,CAAC;QACH,CAAC;QAGD,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC;YAC3D,KAAK,EAAE,EAAE,GAAG,EAAE,qBAAqB,EAAE;SACtC,CAAC,CAAC;QAEH,MAAM,OAAO,GACX,aAAa,EAAE,KAAK;YACpB,sDAAsD,CAAC;QAEzD,MAAM,IAAI,oCAA2B,CAAC;YACpC,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,OAAO;YAChB,eAAe,EAAE,IAAI;SACtB,CAAC,CAAC;IACL,CAAC;CACF,CAAA;AA5DY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;qCAGU,gBAAS;QACZ,8BAAa;QACT,gBAAU;GAJrB,gBAAgB,CA4D5B"}

View File

@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.HealthController = void 0;
const common_1 = require("@nestjs/common");
const prisma_service_1 = require("../prisma/prisma.service");
const skip_maintenance_decorator_1 = require("../common/decorators/skip-maintenance.decorator");
let HealthController = class HealthController {
prisma;
constructor(prisma) {
@@ -40,6 +41,7 @@ __decorate([
], HealthController.prototype, "db", null);
exports.HealthController = HealthController = __decorate([
(0, common_1.Controller)('health'),
(0, skip_maintenance_decorator_1.SkipMaintenance)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
], HealthController);
//# sourceMappingURL=health.controller.js.map

View File

@@ -1 +1 @@
{"version":3,"file":"health.controller.js","sourceRoot":"","sources":["../../src/health/health.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,6DAAyD;AAGlD,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IACE;IAA7B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAGtD,EAAE;QACA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC1B,CAAC;IAGK,AAAN,KAAK,CAAC,EAAE;QAEN,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA,UAAU,CAAC;QACtC,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC;IAC7B,CAAC;CACF,CAAA;AAdY,4CAAgB;AAI3B;IADC,IAAA,YAAG,GAAE;;;;0CAGL;AAGK;IADL,IAAA,YAAG,EAAC,IAAI,CAAC;;;;0CAKT;2BAbU,gBAAgB;IAD5B,IAAA,mBAAU,EAAC,QAAQ,CAAC;qCAEkB,8BAAa;GADvC,gBAAgB,CAc5B"}
{"version":3,"file":"health.controller.js","sourceRoot":"","sources":["../../src/health/health.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,6DAAyD;AACzD,gGAAkF;AAI3E,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IACE;IAA7B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAGtD,EAAE;QACA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IAC1B,CAAC;IAGK,AAAN,KAAK,CAAC,EAAE;QAEN,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA,UAAU,CAAC;QACtC,OAAO,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC;IAC7B,CAAC;CACF,CAAA;AAdY,4CAAgB;AAI3B;IADC,IAAA,YAAG,GAAE;;;;0CAGL;AAGK;IADL,IAAA,YAAG,EAAC,IAAI,CAAC;;;;0CAKT;2BAbU,gBAAgB;IAF5B,IAAA,mBAAU,EAAC,QAAQ,CAAC;IACpB,IAAA,4CAAe,GAAE;qCAEqB,8BAAa;GADvC,gBAAgB,CAc5B"}

File diff suppressed because one or more lines are too long

54
apps/api/dist/seed.js vendored
View File

@@ -107,10 +107,22 @@ async function main() {
features: {
wallets: { limit: null, label: 'Unlimited wallets' },
goals: { limit: null, label: 'Unlimited goals' },
team: { enabled: true, maxMembers: 10, label: 'Team feature (10 members)' },
api: { enabled: true, rateLimit: 1000, label: 'API access (1000 req/hr)' },
team: {
enabled: true,
maxMembers: 10,
label: 'Team feature (10 members)',
},
api: {
enabled: true,
rateLimit: 1000,
label: 'API access (1000 req/hr)',
},
support: { level: 'priority', label: 'Priority support' },
export: { enabled: true, formats: ['csv', 'excel', 'pdf'], label: 'All export formats' },
export: {
enabled: true,
formats: ['csv', 'excel', 'pdf'],
label: 'All export formats',
},
},
badge: 'Popular',
badgeColor: 'blue',
@@ -141,10 +153,22 @@ async function main() {
features: {
wallets: { limit: null, label: 'Unlimited wallets' },
goals: { limit: null, label: 'Unlimited goals' },
team: { enabled: true, maxMembers: 10, label: 'Team feature (10 members)' },
api: { enabled: true, rateLimit: 1000, label: 'API access (1000 req/hr)' },
team: {
enabled: true,
maxMembers: 10,
label: 'Team feature (10 members)',
},
api: {
enabled: true,
rateLimit: 1000,
label: 'API access (1000 req/hr)',
},
support: { level: 'priority', label: 'Priority support' },
export: { enabled: true, formats: ['csv', 'excel', 'pdf'], label: 'All export formats' },
export: {
enabled: true,
formats: ['csv', 'excel', 'pdf'],
label: 'All export formats',
},
discount: { value: '17%', label: 'Save 17% (2 months free)' },
},
badge: 'Best Value',
@@ -161,7 +185,11 @@ async function main() {
apiRateLimit: 1000,
},
});
console.log('✅ Plans created:', [freePlan.name, proMonthly.name, proYearly.name]);
console.log('✅ Plans created:', [
freePlan.name,
proMonthly.name,
proYearly.name,
]);
console.log('\n💳 Creating default payment methods...');
const bcaMethod = await prisma.paymentMethod.upsert({
where: { id: 'bca-method' },
@@ -211,7 +239,11 @@ async function main() {
sortOrder: 3,
},
});
console.log('✅ Payment methods created:', [bcaMethod.displayName, mandiriMethod.displayName, gopayMethod.displayName]);
console.log('✅ Payment methods created:', [
bcaMethod.displayName,
mandiriMethod.displayName,
gopayMethod.displayName,
]);
console.log('\n⚙ Creating app config...');
await prisma.appConfig.upsert({
where: { key: 'MAINTENANCE_MODE' },
@@ -253,7 +285,11 @@ async function main() {
console.log(' Admin Email:', ADMIN_EMAIL);
console.log(' Admin Password:', ADMIN_PASSWORD);
console.log(' Plans:', [freePlan.name, proMonthly.name, proYearly.name].join(', '));
console.log(' Payment Methods:', [bcaMethod.displayName, mandiriMethod.displayName, gopayMethod.displayName].join(', '));
console.log(' Payment Methods:', [
bcaMethod.displayName,
mandiriMethod.displayName,
gopayMethod.displayName,
].join(', '));
console.log('\n⚠ IMPORTANT: Change admin password after first login!');
console.log('\n🔗 Login at: http://localhost:5174/auth/login');
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"users.controller.js","sourceRoot":"","sources":["../../src/users/users.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoF;AACpF,mDAA+C;AAC/C,mDAA+C;AAWxC,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,KAAmB;QAAnB,UAAK,GAAL,KAAK,CAAc;IAAG,CAAC;IAGpD,EAAE;QACA,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IACzB,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa,CACV,GAAoB,EACnB,IAAuC;QAE/C,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CAAQ,GAAoB;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjD,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa,CACV,GAAoB,EACnB,IAA0B;QAElC,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClE,CAAC;CACF,CAAA;AA5BY,0CAAe;AAI1B;IADC,IAAA,YAAG,EAAC,IAAI,CAAC;;;;yCAGT;AAGK;IADL,IAAA,YAAG,EAAC,SAAS,CAAC;IAEZ,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;oDAGR;AAGK;IADL,IAAA,YAAG,EAAC,WAAW,CAAC;IACE,WAAA,IAAA,YAAG,GAAE,CAAA;;;;kDAEvB;AAGK;IADL,IAAA,eAAM,EAAC,SAAS,CAAC;IAEf,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;oDAGR;0BA3BU,eAAe;IAF3B,IAAA,mBAAU,EAAC,OAAO,CAAC;IACnB,IAAA,kBAAS,EAAC,sBAAS,CAAC;qCAEiB,4BAAY;GADrC,eAAe,CA4B3B"}
{"version":3,"file":"users.controller.js","sourceRoot":"","sources":["../../src/users/users.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAQwB;AACxB,mDAA+C;AAC/C,mDAA+C;AAWxC,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,KAAmB;QAAnB,UAAK,GAAL,KAAK,CAAc;IAAG,CAAC;IAGpD,EAAE;QACA,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IACzB,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa,CACV,GAAoB,EACnB,IAAuC;QAE/C,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CAAQ,GAAoB;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjD,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa,CACV,GAAoB,EACnB,IAA0B;QAElC,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClE,CAAC;CACF,CAAA;AA5BY,0CAAe;AAI1B;IADC,IAAA,YAAG,EAAC,IAAI,CAAC;;;;yCAGT;AAGK;IADL,IAAA,YAAG,EAAC,SAAS,CAAC;IAEZ,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;oDAGR;AAGK;IADL,IAAA,YAAG,EAAC,WAAW,CAAC;IACE,WAAA,IAAA,YAAG,GAAE,CAAA;;;;kDAEvB;AAGK;IADL,IAAA,eAAM,EAAC,SAAS,CAAC;IAEf,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;oDAGR;0BA3BU,eAAe;IAF3B,IAAA,mBAAU,EAAC,OAAO,CAAC;IACnB,IAAA,kBAAS,EAAC,sBAAS,CAAC;qCAEiB,4BAAY;GADrC,eAAe,CA4B3B"}

View File

@@ -1 +1 @@
{"version":3,"file":"users.service.js","sourceRoot":"","sources":["../../src/users/users.service.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"users.service.js","sourceRoot":"","sources":["../../src/users/users.service.ts"],"names":[],"mappings":"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"}

View File

@@ -11,11 +11,11 @@ export declare class WalletsController {
constructor(wallets: WalletsService, transactions: TransactionsService);
list(req: RequestWithUser): import("@prisma/client").Prisma.PrismaPromise<{
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
@@ -23,15 +23,15 @@ export declare class WalletsController {
deletedAt: Date | null;
}[]>;
getAllTransactions(req: RequestWithUser): Promise<{
category: string | null;
id: string;
userId: string;
createdAt: Date;
walletId: string;
date: Date;
userId: string;
amount: import("@prisma/client/runtime/library").Decimal;
direction: string;
category: string | null;
date: Date;
memo: string | null;
walletId: string;
recurrenceId: string | null;
}[]>;
create(req: RequestWithUser, body: {
@@ -43,11 +43,11 @@ export declare class WalletsController {
pricePerUnit?: number;
}): import("@prisma/client").Prisma.Prisma__WalletClient<{
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
@@ -65,11 +65,11 @@ export declare class WalletsController {
pricePerUnit?: number;
}): import("@prisma/client").Prisma.Prisma__WalletClient<{
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
@@ -86,11 +86,11 @@ export declare class WalletsController {
updated: number;
wallets: {
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
@@ -102,11 +102,11 @@ export declare class WalletsController {
};
delete(req: RequestWithUser, id: string): import("@prisma/client").Prisma.Prisma__WalletClient<{
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;

View File

@@ -4,11 +4,11 @@ export declare class WalletsService {
constructor(prisma: PrismaService);
list(userId: string): import("@prisma/client").Prisma.PrismaPromise<{
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
@@ -24,11 +24,11 @@ export declare class WalletsService {
pricePerUnit?: number;
}): import("@prisma/client").Prisma.Prisma__WalletClient<{
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
@@ -44,11 +44,11 @@ export declare class WalletsService {
pricePerUnit?: number;
}): import("@prisma/client").Prisma.Prisma__WalletClient<{
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
@@ -63,11 +63,11 @@ export declare class WalletsService {
updated: number;
wallets: {
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;
@@ -77,11 +77,11 @@ export declare class WalletsService {
}>;
delete(userId: string, id: string): import("@prisma/client").Prisma.Prisma__WalletClient<{
id: string;
userId: string;
createdAt: Date;
updatedAt: Date;
kind: string;
name: string;
userId: string;
kind: string;
currency: string | null;
unit: string | null;
initialAmount: import("@prisma/client/runtime/library").Decimal | null;

View File

@@ -12,6 +12,7 @@ import {
import { AuthGuard } from '../auth/auth.guard';
import { AdminGuard } from './guards/admin.guard';
import { AdminConfigService } from './admin-config.service';
import { SkipMaintenance } from '../common/decorators/skip-maintenance.decorator';
interface RequestWithUser {
user: {
@@ -21,6 +22,7 @@ interface RequestWithUser {
@Controller('admin/config')
@UseGuards(AuthGuard, AdminGuard)
@SkipMaintenance()
export class AdminConfigController {
constructor(private readonly service: AdminConfigService) {}

View File

@@ -42,15 +42,18 @@ export class AdminConfigService {
async getByCategory() {
const configs = await this.prisma.appConfig.findMany();
// Group by category
const grouped = configs.reduce((acc, config) => {
if (!acc[config.category]) {
acc[config.category] = [];
}
acc[config.category].push(config);
return acc;
}, {} as Record<string, any[]>);
const grouped = configs.reduce(
(acc, config) => {
if (!acc[config.category]) {
acc[config.category] = [];
}
acc[config.category].push(config);
return acc;
},
{} as Record<string, any[]>,
);
return grouped;
}

View File

@@ -11,9 +11,11 @@ import {
import { AuthGuard } from '../auth/auth.guard';
import { AdminGuard } from './guards/admin.guard';
import { AdminPaymentMethodsService } from './admin-payment-methods.service';
import { SkipMaintenance } from '../common/decorators/skip-maintenance.decorator';
@Controller('admin/payment-methods')
@UseGuards(AuthGuard, AdminGuard)
@SkipMaintenance()
export class AdminPaymentMethodsController {
constructor(private readonly service: AdminPaymentMethodsService) {}

View File

@@ -41,9 +41,9 @@ export class AdminPaymentMethodsService {
this.prisma.paymentMethod.update({
where: { id },
data: { sortOrder: index + 1 },
})
}),
);
await this.prisma.$transaction(updates);
return { success: true };
}

View File

@@ -11,6 +11,7 @@ import {
import { AuthGuard } from '../auth/auth.guard';
import { AdminGuard } from './guards/admin.guard';
import { AdminPaymentsService } from './admin-payments.service';
import { SkipMaintenance } from '../common/decorators/skip-maintenance.decorator';
interface RequestWithUser {
user: {
@@ -20,6 +21,7 @@ interface RequestWithUser {
@Controller('admin/payments')
@UseGuards(AuthGuard, AdminGuard)
@SkipMaintenance()
export class AdminPaymentsController {
constructor(private readonly service: AdminPaymentsService) {}

View File

@@ -72,7 +72,7 @@ export class AdminPaymentsService {
const plan = payment.subscription.plan;
const now = new Date();
const endDate = new Date(now);
if (plan.durationDays) {
endDate.setDate(endDate.getDate() + plan.durationDays);
}
@@ -82,7 +82,8 @@ export class AdminPaymentsService {
data: {
status: 'active',
startDate: now,
endDate: plan.durationType === 'lifetime' ? new Date('2099-12-31') : endDate,
endDate:
plan.durationType === 'lifetime' ? new Date('2099-12-31') : endDate,
},
});
}
@@ -127,18 +128,32 @@ export class AdminPaymentsService {
});
// Group by month
const monthlyData: { [key: string]: { revenue: number; count: number } } = {};
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const monthlyData: { [key: string]: { revenue: number; count: number } } =
{};
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
payments.forEach((payment) => {
if (payment.paidAt) {
const date = new Date(payment.paidAt);
const monthKey = `${months[date.getMonth()]} ${date.getFullYear()}`;
if (!monthlyData[monthKey]) {
monthlyData[monthKey] = { revenue: 0, count: 0 };
}
monthlyData[monthKey].revenue += Number(payment.amount);
monthlyData[monthKey].count += 1;
}

View File

@@ -11,9 +11,11 @@ import {
import { AuthGuard } from '../auth/auth.guard';
import { AdminGuard } from './guards/admin.guard';
import { AdminPlansService } from './admin-plans.service';
import { SkipMaintenance } from '../common/decorators/skip-maintenance.decorator';
@Controller('admin/plans')
@UseGuards(AuthGuard, AdminGuard)
@SkipMaintenance()
export class AdminPlansController {
constructor(private readonly plansService: AdminPlansService) {}

View File

@@ -86,9 +86,9 @@ export class AdminPlansService {
this.prisma.plan.update({
where: { id },
data: { sortOrder: index + 1 },
})
}),
);
await this.prisma.$transaction(updates);
return { success: true };
}

View File

@@ -3,6 +3,7 @@ import {
Get,
Post,
Put,
Delete,
Body,
Param,
Query,
@@ -11,9 +12,11 @@ import {
import { AuthGuard } from '../auth/auth.guard';
import { AdminGuard } from './guards/admin.guard';
import { AdminUsersService } from './admin-users.service';
import { SkipMaintenance } from '../common/decorators/skip-maintenance.decorator';
@Controller('admin/users')
@UseGuards(AuthGuard, AdminGuard)
@SkipMaintenance()
export class AdminUsersController {
constructor(private readonly service: AdminUsersService) {}
@@ -54,4 +57,30 @@ export class AdminUsersController {
) {
return this.service.grantProAccess(id, body.planSlug, body.durationDays);
}
@Post()
create(
@Body()
body: {
email: string;
password: string;
name?: string;
role?: string;
},
) {
return this.service.create(body);
}
@Put(':id')
update(
@Param('id') id: string,
@Body() body: { email?: string; name?: string; role?: string },
) {
return this.service.update(id, body);
}
@Delete(':id')
delete(@Param('id') id: string) {
return this.service.delete(id);
}
}

View File

@@ -1,5 +1,10 @@
import { Injectable } from '@nestjs/common';
import {
Injectable,
ConflictException,
NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AdminUsersService {
@@ -140,4 +145,102 @@ export class AdminUsersService {
suspendedUsers,
};
}
async create(data: {
email: string;
password: string;
name?: string;
role?: string;
}) {
// Check if user already exists
const existing = await this.prisma.user.findUnique({
where: { email: data.email },
});
if (existing) {
throw new ConflictException('User with this email already exists');
}
// Hash password
const hashedPassword = await bcrypt.hash(data.password, 10);
// Create user
return this.prisma.user.create({
data: {
email: data.email,
passwordHash: hashedPassword,
name: data.name || null,
role: data.role || 'user',
emailVerified: true, // Admin-created users are auto-verified
},
select: {
id: true,
email: true,
name: true,
role: true,
emailVerified: true,
createdAt: true,
},
});
}
async update(
id: string,
data: { email?: string; name?: string; role?: string },
) {
// Check if user exists
const user = await this.prisma.user.findUnique({
where: { id },
});
if (!user) {
throw new NotFoundException('User not found');
}
// If email is being updated, check if it's already taken
if (data.email && data.email !== user.email) {
const existing = await this.prisma.user.findUnique({
where: { email: data.email },
});
if (existing) {
throw new ConflictException('Email already in use');
}
}
return this.prisma.user.update({
where: { id },
data: {
...(data.email && { email: data.email }),
...(data.name !== undefined && { name: data.name }),
...(data.role && { role: data.role }),
},
select: {
id: true,
email: true,
name: true,
role: true,
emailVerified: true,
createdAt: true,
},
});
}
async delete(id: string) {
// Check if user exists
const user = await this.prisma.user.findUnique({
where: { id },
});
if (!user) {
throw new NotFoundException('User not found');
}
// Delete user (cascade will handle related records)
await this.prisma.user.delete({
where: { id },
});
return { message: 'User deleted successfully' };
}
}

View File

@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import * as path from 'path';
import { PrismaModule } from './prisma/prisma.module';
import { AuthModule } from './auth/auth.module';
@@ -10,6 +11,7 @@ import { TransactionsModule } from './transactions/transactions.module';
import { CategoriesModule } from './categories/categories.module';
import { OtpModule } from './otp/otp.module';
import { AdminModule } from './admin/admin.module';
import { MaintenanceGuard } from './common/guards/maintenance.guard';
@Module({
imports: [
@@ -30,6 +32,11 @@ import { AdminModule } from './admin/admin.module';
AdminModule,
],
controllers: [HealthController],
providers: [],
providers: [
{
provide: APP_GUARD,
useClass: MaintenanceGuard,
},
],
})
export class AppModule {}

View File

@@ -10,6 +10,7 @@ import {
import { AuthGuard as JwtAuthGuard } from './auth.guard';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { SkipMaintenance } from '../common/decorators/skip-maintenance.decorator';
import type { Response } from 'express';
interface RequestWithUser {
@@ -24,6 +25,7 @@ export class AuthController {
constructor(private authService: AuthService) {}
@Post('register')
@SkipMaintenance()
async register(
@Body() body: { email: string; password: string; name?: string },
) {
@@ -31,11 +33,13 @@ export class AuthController {
}
@Post('login')
@SkipMaintenance()
async login(@Body() body: { email: string; password: string }) {
return this.authService.login(body.email, body.password);
}
@Post('verify-otp')
@SkipMaintenance()
async verifyOtp(
@Body()
body: {
@@ -52,12 +56,14 @@ export class AuthController {
}
@Get('google')
@SkipMaintenance()
@UseGuards(AuthGuard('google'))
async googleAuth() {
// Initiates Google OAuth flow
}
@Get('google/callback')
@SkipMaintenance()
@UseGuards(AuthGuard('google'))
async googleAuthCallback(@Req() req: any, @Res() res: Response) {
// Handle Google OAuth callback

View File

@@ -20,6 +20,6 @@ import { OtpModule } from '../otp/otp.module';
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy, GoogleStrategy],
exports: [AuthService],
exports: [AuthService, JwtModule],
})
export class AuthModule {}

View File

@@ -370,7 +370,7 @@ export class AuthService {
where: { id: userId },
select: { role: true },
});
return this.jwtService.sign({
sub: userId,
email,

View File

@@ -21,10 +21,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
}
async validate(payload: JwtPayload) {
return {
userId: payload.sub,
return {
userId: payload.sub,
email: payload.email,
role: payload.role || 'user'
role: payload.role || 'user',
};
}
}

View File

@@ -1,4 +1,13 @@
import { Controller, Get, Post, Body, Param, Delete, Req, UseGuards } from '@nestjs/common';
import {
Controller,
Get,
Post,
Body,
Param,
Delete,
Req,
UseGuards,
} from '@nestjs/common';
import { CategoriesService } from '../categories/categories.service';
import { CreateCategoryDto } from '../categories/dto/create-category.dto';
import { AuthGuard } from '../auth/auth.guard';
@@ -15,7 +24,10 @@ export class CategoriesController {
constructor(private readonly categoriesService: CategoriesService) {}
@Post()
create(@Req() req: RequestWithUser, @Body() createCategoryDto: CreateCategoryDto) {
create(
@Req() req: RequestWithUser,
@Body() createCategoryDto: CreateCategoryDto,
) {
return this.categoriesService.create({
...createCategoryDto,
userId: req.user.userId,
@@ -31,4 +43,4 @@ export class CategoriesController {
remove(@Req() req: RequestWithUser, @Param('id') id: string) {
return this.categoriesService.remove(id, req.user.userId);
}
}
}

View File

@@ -0,0 +1,3 @@
import { SetMetadata } from '@nestjs/common';
export const SkipMaintenance = () => SetMetadata('skipMaintenance', true);

View File

@@ -0,0 +1,72 @@
import {
Injectable,
CanActivate,
ExecutionContext,
ServiceUnavailableException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PrismaService } from '../../prisma/prisma.service';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class MaintenanceGuard implements CanActivate {
constructor(
private reflector: Reflector,
private prisma: PrismaService,
private jwtService: JwtService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// Check if route is exempt from maintenance mode (auth, health, admin routes)
const isExempt = this.reflector.get<boolean>('skipMaintenance', context.getHandler());
const isControllerExempt = this.reflector.get<boolean>('skipMaintenance', context.getClass());
if (isExempt || isControllerExempt) {
return true;
}
// Get maintenance mode status from config
const maintenanceConfig = await this.prisma.appConfig.findUnique({
where: { key: 'maintenance_mode' },
});
const isMaintenanceMode = maintenanceConfig?.value === 'true';
if (!isMaintenanceMode) {
return true;
}
// Try to extract user from JWT token (if exists)
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
try {
const token = authHeader.substring(7);
const payload = this.jwtService.verify(token);
// If user is admin, allow access
if (payload.role === 'admin') {
return true;
}
} catch (error) {
// Invalid token, continue to block
}
}
// Get maintenance message
const messageConfig = await this.prisma.appConfig.findUnique({
where: { key: 'maintenance_message' },
});
const message =
messageConfig?.value ||
'System is under maintenance. Please try again later.';
throw new ServiceUnavailableException({
statusCode: 503,
message: message,
maintenanceMode: true,
});
}
}

View File

@@ -1,7 +1,9 @@
import { Controller, Get } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { SkipMaintenance } from '../common/decorators/skip-maintenance.decorator';
@Controller('health')
@SkipMaintenance()
export class HealthController {
constructor(private readonly prisma: PrismaService) {}

View File

@@ -404,7 +404,9 @@ export class OtpService {
} catch (error: unknown) {
console.error('Failed to check WhatsApp number:', error);
// Return false if webhook fails - safer approach
console.log(`📱 Failed to check WhatsApp number: ${phone} - Webhook error`);
console.log(
`📱 Failed to check WhatsApp number: ${phone} - Webhook error`,
);
return {
success: false,
isRegistered: false,

View File

@@ -16,9 +16,9 @@ async function main() {
// 1. CREATE ADMIN USER
// ============================================
console.log('\n👤 Creating admin user...');
const passwordHash = await bcrypt.hash(ADMIN_PASSWORD, 10);
const admin = await prisma.user.upsert({
where: { email: ADMIN_EMAIL },
update: {
@@ -34,14 +34,14 @@ async function main() {
emailVerified: true,
},
});
console.log('✅ Admin user created:', admin.email);
// ============================================
// 2. CREATE DEFAULT PLANS
// ============================================
console.log('\n💰 Creating default plans...');
const freePlan = await prisma.plan.upsert({
where: { slug: 'free' },
update: {},
@@ -74,7 +74,7 @@ async function main() {
apiRateLimit: null,
},
});
const proMonthly = await prisma.plan.upsert({
where: { slug: 'pro-monthly' },
update: {},
@@ -90,10 +90,22 @@ async function main() {
features: {
wallets: { limit: null, label: 'Unlimited wallets' },
goals: { limit: null, label: 'Unlimited goals' },
team: { enabled: true, maxMembers: 10, label: 'Team feature (10 members)' },
api: { enabled: true, rateLimit: 1000, label: 'API access (1000 req/hr)' },
team: {
enabled: true,
maxMembers: 10,
label: 'Team feature (10 members)',
},
api: {
enabled: true,
rateLimit: 1000,
label: 'API access (1000 req/hr)',
},
support: { level: 'priority', label: 'Priority support' },
export: { enabled: true, formats: ['csv', 'excel', 'pdf'], label: 'All export formats' },
export: {
enabled: true,
formats: ['csv', 'excel', 'pdf'],
label: 'All export formats',
},
},
badge: 'Popular',
badgeColor: 'blue',
@@ -109,7 +121,7 @@ async function main() {
apiRateLimit: 1000,
},
});
const proYearly = await prisma.plan.upsert({
where: { slug: 'pro-yearly' },
update: {},
@@ -125,10 +137,22 @@ async function main() {
features: {
wallets: { limit: null, label: 'Unlimited wallets' },
goals: { limit: null, label: 'Unlimited goals' },
team: { enabled: true, maxMembers: 10, label: 'Team feature (10 members)' },
api: { enabled: true, rateLimit: 1000, label: 'API access (1000 req/hr)' },
team: {
enabled: true,
maxMembers: 10,
label: 'Team feature (10 members)',
},
api: {
enabled: true,
rateLimit: 1000,
label: 'API access (1000 req/hr)',
},
support: { level: 'priority', label: 'Priority support' },
export: { enabled: true, formats: ['csv', 'excel', 'pdf'], label: 'All export formats' },
export: {
enabled: true,
formats: ['csv', 'excel', 'pdf'],
label: 'All export formats',
},
discount: { value: '17%', label: 'Save 17% (2 months free)' },
},
badge: 'Best Value',
@@ -145,14 +169,18 @@ async function main() {
apiRateLimit: 1000,
},
});
console.log('✅ Plans created:', [freePlan.name, proMonthly.name, proYearly.name]);
console.log('✅ Plans created:', [
freePlan.name,
proMonthly.name,
proYearly.name,
]);
// ============================================
// 3. CREATE DEFAULT PAYMENT METHODS
// ============================================
console.log('\n💳 Creating default payment methods...');
const bcaMethod = await prisma.paymentMethod.upsert({
where: { id: 'bca-method' },
update: {},
@@ -164,12 +192,13 @@ async function main() {
accountNumber: '1234567890',
displayName: 'BCA Virtual Account',
logoUrl: '/logos/bca.png',
instructions: 'Transfer to the account above and upload proof of payment.',
instructions:
'Transfer to the account above and upload proof of payment.',
isActive: true,
sortOrder: 1,
},
});
const mandiriMethod = await prisma.paymentMethod.upsert({
where: { id: 'mandiri-method' },
update: {},
@@ -181,12 +210,13 @@ async function main() {
accountNumber: '9876543210',
displayName: 'Mandiri Virtual Account',
logoUrl: '/logos/mandiri.png',
instructions: 'Transfer to the account above and upload proof of payment.',
instructions:
'Transfer to the account above and upload proof of payment.',
isActive: true,
sortOrder: 2,
},
});
const gopayMethod = await prisma.paymentMethod.upsert({
where: { id: 'gopay-method' },
update: {},
@@ -203,14 +233,18 @@ async function main() {
sortOrder: 3,
},
});
console.log('✅ Payment methods created:', [bcaMethod.displayName, mandiriMethod.displayName, gopayMethod.displayName]);
console.log('✅ Payment methods created:', [
bcaMethod.displayName,
mandiriMethod.displayName,
gopayMethod.displayName,
]);
// ============================================
// 4. CREATE APP CONFIG (Optional)
// ============================================
console.log('\n⚙ Creating app config...');
await prisma.appConfig.upsert({
where: { key: 'MAINTENANCE_MODE' },
update: {},
@@ -224,14 +258,14 @@ async function main() {
isSecret: false,
},
});
console.log('✅ App config created');
// ============================================
// 5. CREATE TEMP USER & WALLET (Legacy)
// ============================================
console.log('\n🔧 Creating temp user (legacy)...');
const user = await prisma.user.upsert({
where: { id: TEMP_USER_ID },
update: {},
@@ -252,7 +286,7 @@ async function main() {
},
});
}
console.log('✅ Temp user created:', user.id);
// ============================================
@@ -262,8 +296,18 @@ async function main() {
console.log('\n📋 Summary:');
console.log(' Admin Email:', ADMIN_EMAIL);
console.log(' Admin Password:', ADMIN_PASSWORD);
console.log(' Plans:', [freePlan.name, proMonthly.name, proYearly.name].join(', '));
console.log(' Payment Methods:', [bcaMethod.displayName, mandiriMethod.displayName, gopayMethod.displayName].join(', '));
console.log(
' Plans:',
[freePlan.name, proMonthly.name, proYearly.name].join(', '),
);
console.log(
' Payment Methods:',
[
bcaMethod.displayName,
mandiriMethod.displayName,
gopayMethod.displayName,
].join(', '),
);
console.log('\n⚠ IMPORTANT: Change admin password after first login!');
console.log('\n🔗 Login at: http://localhost:5174/auth/login');
}

View File

@@ -1,4 +1,12 @@
import { Controller, Get, Put, Delete, Body, Req, UseGuards } from '@nestjs/common';
import {
Controller,
Get,
Put,
Delete,
Body,
Req,
UseGuards,
} from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { UsersService } from './users.service';

View File

@@ -1,4 +1,8 @@
import { Injectable, BadRequestException, UnauthorizedException } from '@nestjs/common';
import {
Injectable,
BadRequestException,
UnauthorizedException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { getTempUserId } from '../common/user.util';
import * as bcrypt from 'bcrypt';

View File

@@ -1,4 +1,5 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { useState, useEffect } from 'react'
import { AuthProvider, useAuth } from './contexts/AuthContext'
import { LanguageProvider } from './contexts/LanguageContext'
import { ThemeProvider } from './components/ThemeProvider'
@@ -8,14 +9,16 @@ import { Login } from './components/pages/Login'
import { Register } from './components/pages/Register'
import { OtpVerification } from './components/pages/OtpVerification'
import { AuthCallback } from './components/pages/AuthCallback'
import { MaintenancePage } from './components/pages/MaintenancePage'
import { AdminLayout } from './components/admin/AdminLayout'
import { AdminDashboard } from './components/admin/pages/AdminDashboard'
import { AdminPlans } from './components/admin/pages/AdminPlans'
import { AdminPaymentMethods } from './components/admin/pages/AdminPaymentMethods'
import { AdminPayments } from './components/admin/pages/AdminPayments'
import { AdminUsers } from './components/admin/pages/AdminUsers'
import { AdminSettings } from './components/admin/pages/AdminSettings'
import { AdminSettings } from './components/admin/pages/AdminSettingsNew'
import { Profile } from './components/pages/Profile'
import { Loader2 } from 'lucide-react'
import { setupAxiosInterceptors } from './utils/axiosSetup'
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth()
@@ -50,13 +53,35 @@ function PublicRoute({ children }: { children: React.ReactNode }) {
}
if (user) {
return <Navigate to="/" replace />
// Redirect based on role
const redirectTo = user.role === 'admin' ? '/admin' : '/'
return <Navigate to={redirectTo} replace />
}
return <>{children}</>
}
export default function App() {
const [maintenanceMode, setMaintenanceMode] = useState(false)
const [maintenanceMessage, setMaintenanceMessage] = useState('')
useEffect(() => {
// Setup axios interceptor for maintenance mode
setupAxiosInterceptors((message) => {
setMaintenanceMessage(message)
setMaintenanceMode(true)
})
}, [])
// Show maintenance page if maintenance mode is active
if (maintenanceMode) {
return (
<ThemeProvider defaultTheme="light" storageKey="tabungin-ui-theme">
<MaintenancePage message={maintenanceMessage} />
</ThemeProvider>
)
}
return (
<BrowserRouter>
<ThemeProvider defaultTheme="light" storageKey="tabungin-ui-theme">
@@ -74,9 +99,9 @@ export default function App() {
<Route path="/admin" element={<ProtectedRoute><AdminLayout /></ProtectedRoute>}>
<Route index element={<AdminDashboard />} />
<Route path="plans" element={<AdminPlans />} />
<Route path="payment-methods" element={<AdminPaymentMethods />} />
<Route path="payments" element={<AdminPayments />} />
<Route path="users" element={<AdminUsers />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<AdminSettings />} />
</Route>

View File

@@ -1,5 +1,6 @@
import { useState, useCallback } from "react"
import { Routes, Route, useLocation, useNavigate } from "react-router-dom"
import { Routes, Route, useLocation, useNavigate, Navigate } from "react-router-dom"
import { useAuth } from "@/contexts/AuthContext"
import { DashboardLayout } from "./layout/DashboardLayout"
import { Overview } from "./pages/Overview"
import { Wallets } from "./pages/Wallets"
@@ -7,8 +8,14 @@ import { Transactions } from "./pages/Transactions"
import { Profile } from "./pages/Profile"
export function Dashboard() {
const { user } = useAuth()
const location = useLocation()
const navigate = useNavigate()
// Block admins from accessing member dashboard
if (user?.role === 'admin') {
return <Navigate to="/admin" replace />
}
const [fabWalletDialogOpen, setFabWalletDialogOpen] = useState(false)
const [fabTransactionDialogOpen, setFabTransactionDialogOpen] = useState(false)

View File

@@ -16,16 +16,16 @@ export function AdminLayout() {
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="text-center">
<h1 className="text-2xl font-bold text-foreground mb-2">
Akses Ditolak
Access Denied
</h1>
<p className="text-muted-foreground">
Anda tidak memiliki izin untuk mengakses panel admin.
You don't have permission to access the admin panel.
</p>
<Link
to="/"
className="mt-4 inline-block text-primary hover:text-primary/90"
>
Kembali ke Dashboard
Back to Dashboard
</Link>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { LayoutDashboard, CreditCard, Wallet, Users, Settings, LogOut } from 'lucide-react'
import { LayoutDashboard, CreditCard, Wallet, Users, Settings, LogOut, UserCircle } from 'lucide-react'
import { Logo } from '../Logo'
import {
Sidebar,
@@ -26,21 +26,21 @@ const items = [
url: '/admin/plans',
icon: CreditCard,
},
{
title: 'Payment Methods',
url: '/admin/payment-methods',
icon: Wallet,
},
{
title: 'Payments',
url: '/admin/payments',
icon: CreditCard,
icon: Wallet,
},
{
title: 'Users',
url: '/admin/users',
icon: Users,
},
{
title: 'Profile',
url: '/admin/profile',
icon: UserCircle,
},
{
title: 'Settings',
url: '/admin/settings',

View File

@@ -115,7 +115,7 @@ export function AdminDashboard() {
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">Memuat...</div>
<div className="text-muted-foreground">Loading...</div>
</div>
)
}
@@ -127,7 +127,7 @@ export function AdminDashboard() {
<div>
<h1 className="text-3xl font-bold text-foreground">Dashboard</h1>
<p className="mt-2 text-muted-foreground">
Selamat datang di panel admin - Overview performa aplikasi
Welcome to admin panel - Application performance overview
</p>
</div>
@@ -143,7 +143,7 @@ export function AdminDashboard() {
<p className="text-xs text-muted-foreground flex items-center mt-1">
<ArrowUpRight className="h-3 w-3 text-green-600 mr-1" />
<span className="text-green-600">+{stats?.userGrowth || 0}%</span>
<span className="ml-1">dari bulan lalu</span>
<span className="ml-1">from last month</span>
</p>
</CardContent>
</Card>
@@ -156,7 +156,7 @@ export function AdminDashboard() {
<CardContent>
<div className="text-2xl font-bold">{stats?.activeSubscriptions || 0}</div>
<p className="text-xs text-muted-foreground mt-1">
Langganan aktif saat ini
Currently active subscriptions
</p>
</CardContent>
</Card>
@@ -171,7 +171,7 @@ export function AdminDashboard() {
<p className="text-xs text-muted-foreground flex items-center mt-1">
<ArrowUpRight className="h-3 w-3 text-green-600 mr-1" />
<span className="text-green-600">+{stats?.revenueGrowth || 0}%</span>
<span className="ml-1">dari bulan lalu</span>
<span className="ml-1">from last month</span>
</p>
</CardContent>
</Card>
@@ -184,7 +184,7 @@ export function AdminDashboard() {
<CardContent>
<div className="text-2xl font-bold">{pendingPayments}</div>
<p className="text-xs text-muted-foreground mt-1">
Menunggu verifikasi
Awaiting verification
</p>
</CardContent>
</Card>
@@ -196,7 +196,7 @@ export function AdminDashboard() {
<Card>
<CardHeader>
<CardTitle>Revenue Overview</CardTitle>
<CardDescription>Pendapatan 6 bulan terakhir</CardDescription>
<CardDescription>Revenue for the last 6 months</CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
@@ -228,7 +228,7 @@ export function AdminDashboard() {
<Card>
<CardHeader>
<CardTitle>Subscription Distribution</CardTitle>
<CardDescription>Distribusi pengguna per plan</CardDescription>
<CardDescription>User distribution by plan</CardDescription>
</CardHeader>
<CardContent>
<ResponsiveContainer width="100%" height={300}>
@@ -256,19 +256,19 @@ export function AdminDashboard() {
<Card>
<CardHeader>
<CardTitle>Quick Actions</CardTitle>
<CardDescription>Akses cepat ke fitur utama</CardDescription>
<CardDescription>Quick access to main features</CardDescription>
</CardHeader>
<CardContent className="grid gap-2">
<Button variant="outline" className="justify-start" asChild>
<a href="/admin/plans">
<CreditCard className="h-4 w-4 mr-2" />
Kelola Plans
Manage Plans
</a>
</Button>
<Button variant="outline" className="justify-start" asChild>
<a href="/admin/payments">
<DollarSign className="h-4 w-4 mr-2" />
Verifikasi Pembayaran
Verify Payments
{pendingPayments > 0 && (
<Badge variant="destructive" className="ml-auto">{pendingPayments}</Badge>
)}
@@ -277,13 +277,13 @@ export function AdminDashboard() {
<Button variant="outline" className="justify-start" asChild>
<a href="/admin/users">
<Users className="h-4 w-4 mr-2" />
Kelola Users
Manage Users
</a>
</Button>
<Button variant="outline" className="justify-start" asChild>
<a href="/admin/payment-methods">
<Wallet className="h-4 w-4 mr-2" />
Metode Pembayaran
Payment Methods
</a>
</Button>
</CardContent>
@@ -293,7 +293,7 @@ export function AdminDashboard() {
<Card>
<CardHeader>
<CardTitle>System Status</CardTitle>
<CardDescription>Status sistem dan statistik</CardDescription>
<CardDescription>System status and statistics</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">

View File

@@ -171,7 +171,7 @@ function SortableMethodCard({ method, onEdit, onDelete, onToggleActive, getTypeI
{/* Instructions */}
{method.instructions && (
<div className="mb-6 p-4 rounded-lg bg-muted/50 border border-border/50">
<p className="text-xs text-muted-foreground mb-1">Instruksi:</p>
<p className="text-xs text-muted-foreground mb-1">Instruction:</p>
<p className="text-sm text-foreground line-clamp-3">
{method.instructions}
</p>
@@ -230,7 +230,7 @@ function SortableMethodCard({ method, onEdit, onDelete, onToggleActive, getTypeI
<button
onClick={() => onDelete(method.id)}
className="p-2.5 rounded-lg bg-destructive/10 text-destructive hover:bg-destructive hover:text-destructive-foreground transition-all"
title="Hapus"
title="Delete"
>
<Trash2 className="h-4 w-4" />
</button>
@@ -301,10 +301,10 @@ export function AdminPaymentMethods() {
{ methodIds: newMethods.map((m) => m.id) },
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success('Urutan metode pembayaran berhasil diubah')
toast.success('Payment method order updated successfully')
} catch (error) {
console.error('Failed to reorder methods:', error)
toast.error('Gagal mengubah urutan metode pembayaran')
toast.error('Failed to update payment method order')
fetchMethods() // Revert on error
}
}
@@ -319,11 +319,11 @@ export function AdminPaymentMethods() {
{ ...method, isActive: !method.isActive },
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success(method.isActive ? 'Metode pembayaran dinonaktifkan' : 'Metode pembayaran diaktifkan')
toast.success(method.isActive ? 'Payment method deactivated' : 'Payment method activated')
fetchMethods()
} catch (error) {
console.error('Failed to toggle status:', error)
toast.error('Gagal mengubah status')
toast.error('Failed to change status')
}
}
@@ -337,12 +337,12 @@ export function AdminPaymentMethods() {
await axios.delete(`${API_URL}/api/admin/payment-methods/${deleteDialog.methodId}`, {
headers: { Authorization: `Bearer ${token}` },
})
toast.success('Metode pembayaran berhasil dihapus')
toast.success('Payment method deleted successfully')
fetchMethods()
setDeleteDialog({ open: false, methodId: '' })
} catch (error) {
console.error('Failed to delete payment method:', error)
toast.error('Gagal menghapus metode pembayaran')
toast.error('Failed to delete payment method')
}
}
@@ -391,13 +391,13 @@ export function AdminPaymentMethods() {
headers: { Authorization: `Bearer ${token}` },
})
}
toast.success(editingMethod ? 'Metode pembayaran berhasil diupdate' : 'Metode pembayaran berhasil ditambahkan')
toast.success(editingMethod ? 'Payment method updated successfully' : 'Payment method added successfully')
fetchMethods()
setShowModal(false)
setEditingMethod(null)
} catch (error) {
console.error('Failed to save payment method:', error)
toast.error('Gagal menyimpan metode pembayaran')
toast.error('Failed to save payment method')
}
}
@@ -443,7 +443,7 @@ export function AdminPaymentMethods() {
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">Memuat...</div>
<div className="text-muted-foreground">Loading...</div>
</div>
)
}
@@ -452,16 +452,16 @@ export function AdminPaymentMethods() {
<div>
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-3xl font-bold text-foreground">
Metode Pembayaran
</h1>
<h4 className="text-xl font-bold text-foreground">
Payment Methods
</h4>
<p className="mt-2 text-muted-foreground">
Kelola metode pembayaran yang tersedia
Manage available payment methods
</p>
</div>
<Button onClick={() => handleOpenModal()}>
<Plus className="h-5 w-5 mr-2" />
Tambah Metode
Add Method
</Button>
</div>
@@ -487,7 +487,7 @@ export function AdminPaymentMethods() {
{methods.length === 0 && (
<div className="text-center py-12">
<p className="text-muted-foreground">Belum ada metode pembayaran</p>
<p className="text-muted-foreground">No payment methods yet</p>
</div>
)}
@@ -495,21 +495,21 @@ export function AdminPaymentMethods() {
<Dialog open={showModal} onOpenChange={setShowModal}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingMethod ? 'Edit Metode Pembayaran' : 'Tambah Metode Pembayaran'}</DialogTitle>
<DialogTitle>{editingMethod ? 'Edit Payment Method' : 'Add Payment Method'}</DialogTitle>
<DialogDescription>
{editingMethod ? 'Ubah informasi metode pembayaran' : 'Tambah metode pembayaran baru'}
{editingMethod ? 'Update payment method information' : 'Add a new payment method'}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="displayName">Nama Tampilan</Label>
<Label htmlFor="displayName">Display Name</Label>
<Input
id="displayName"
required
value={formData.displayName}
onChange={(e) => setFormData({ ...formData, displayName: e.target.value })}
placeholder="BCA, GoPay, QRIS, dll"
placeholder="BCA, GoPay, QRIS, etc"
/>
</div>
@@ -520,28 +520,28 @@ export function AdminPaymentMethods() {
required
value={formData.provider}
onChange={(e) => setFormData({ ...formData, provider: e.target.value })}
placeholder="BCA, Gopay, OVO, dll"
placeholder="BCA, Gopay, OVO, etc"
/>
</div>
<div className="space-y-2">
<Label htmlFor="type">Tipe</Label>
<Label htmlFor="type">Type</Label>
<Select value={formData.type} onValueChange={(value) => setFormData({ ...formData, type: value })}>
<SelectTrigger id="type">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="bank_transfer">Transfer Bank</SelectItem>
<SelectItem value="bank_transfer">Bank Transfer</SelectItem>
<SelectItem value="ewallet">E-Wallet</SelectItem>
<SelectItem value="qris">QRIS</SelectItem>
<SelectItem value="other">Lainnya</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="accountNumber">Nomor Rekening / Akun</Label>
<Label htmlFor="accountNumber">Account Number</Label>
<Input
id="accountNumber"
value={formData.accountNumber}
@@ -550,7 +550,7 @@ export function AdminPaymentMethods() {
/>
</div>
<div className="space-y-2">
<Label htmlFor="accountName">Nama Pemilik</Label>
<Label htmlFor="accountName">Account Holder Name</Label>
<Input
id="accountName"
value={formData.accountName}
@@ -561,13 +561,13 @@ export function AdminPaymentMethods() {
</div>
<div className="space-y-2">
<Label htmlFor="instructions">Instruksi Pembayaran</Label>
<Label htmlFor="instructions">Payment Instruction</Label>
<Textarea
id="instructions"
value={formData.instructions}
onChange={(e) => setFormData({ ...formData, instructions: e.target.value })}
rows={4}
placeholder="Petunjuk cara melakukan pembayaran..."
placeholder="Detail payment instruction, step by step to make payment..."
/>
</div>
@@ -591,17 +591,17 @@ export function AdminPaymentMethods() {
/>
<Label htmlFor="isActive" className="cursor-pointer flex justify-between w-full">
<div className="font-semibold text-foreground">Active</div>
<div className="text-xs text-muted-foreground">Metode pembayaran dapat digunakan</div>
<div className="text-xs text-muted-foreground">Payment method can be used</div>
</Label>
</div>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setShowModal(false)}>
Batal
Cancel
</Button>
<Button type="submit">
{editingMethod ? 'Update' : 'Tambah'}
{editingMethod ? 'Update' : 'Add'}
</Button>
</DialogFooter>
</form>
@@ -612,15 +612,15 @@ export function AdminPaymentMethods() {
<AlertDialog open={deleteDialog.open} onOpenChange={(open) => setDeleteDialog({ ...deleteDialog, open })}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Hapus Metode Pembayaran?</AlertDialogTitle>
<AlertDialogTitle>Delete Payment Method?</AlertDialogTitle>
<AlertDialogDescription>
Apakah Anda yakin ingin menghapus metode pembayaran ini? Tindakan ini tidak dapat dibatalkan.
Are you sure you want to delete this payment method? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Batal</AlertDialogCancel>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
Hapus
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@@ -68,7 +68,7 @@ export function AdminPayments() {
}
const handleVerify = async (paymentId: string) => {
const notes = prompt('Catatan verifikasi (opsional):')
const notes = prompt('Verification notes (optional):')
if (notes === null) return
try {
@@ -78,16 +78,16 @@ export function AdminPayments() {
{ notes },
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success('Pembayaran berhasil diverifikasi')
toast.success('Payment verified successfully')
fetchPayments()
} catch (error) {
console.error('Failed to verify payment:', error)
toast.error('Gagal memverifikasi pembayaran')
toast.error('Failed to verify payment')
}
}
const handleReject = async (paymentId: string) => {
const reason = prompt('Alasan penolakan:')
const reason = prompt('Rejection reason:')
if (!reason) return
try {
@@ -97,11 +97,11 @@ export function AdminPayments() {
{ reason },
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success('Pembayaran berhasil ditolak')
toast.success('Payment rejected successfully')
fetchPayments()
} catch (error) {
console.error('Failed to reject payment:', error)
toast.error('Gagal menolak pembayaran')
toast.error('Failed to reject payment')
}
}
@@ -154,7 +154,7 @@ export function AdminPayments() {
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">Memuat...</div>
<div className="text-muted-foreground">Loading...</div>
</div>
)
}
@@ -162,9 +162,9 @@ export function AdminPayments() {
return (
<div>
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground">Verifikasi Pembayaran</h1>
<h1 className="text-3xl font-bold text-foreground">Payment Verification</h1>
<p className="mt-2 text-muted-foreground">
Kelola dan verifikasi bukti pembayaran dari pengguna
Manage and verify payment proofs from users
</p>
</div>
@@ -190,7 +190,7 @@ export function AdminPayments() {
onChange={(e) => setFilter(e.target.value as FilterStatus)}
className="px-4 py-2 border border-input rounded-lg bg-background text-foreground focus:ring-2 focus:ring-ring focus:border-transparent"
>
<option value="all">Semua Status</option>
<option value="all">All Status</option>
<option value="pending">Pending</option>
<option value="verified">Verified</option>
<option value="rejected">Rejected</option>
@@ -211,16 +211,16 @@ export function AdminPayments() {
Plan
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Jumlah
Amount
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Metode
Method
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Status
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Tanggal
Date
</th>
<th className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
Actions
@@ -272,7 +272,7 @@ export function AdminPayments() {
<button
onClick={() => setSelectedPayment(payment)}
className="p-2 rounded-lg text-primary hover:bg-primary/10 transition-colors"
title="Lihat Bukti"
title="View Proof"
>
<Eye className="h-4 w-4" />
</button>
@@ -282,14 +282,14 @@ export function AdminPayments() {
<button
onClick={() => handleVerify(payment.id)}
className="p-2 rounded-lg text-green-600 hover:bg-green-500/10 transition-colors"
title="Verifikasi"
title="Verify"
>
<Check className="h-4 w-4" />
</button>
<button
onClick={() => handleReject(payment.id)}
className="p-2 rounded-lg text-destructive hover:bg-destructive/10 transition-colors"
title="Tolak"
title="Reject"
>
<X className="h-4 w-4" />
</button>
@@ -321,7 +321,7 @@ export function AdminPayments() {
onClick={(e) => e.stopPropagation()}
>
<div className="p-6 border-b border-border">
<h2 className="text-xl font-bold text-foreground">Bukti Pembayaran</h2>
<h2 className="text-xl font-bold text-foreground">Payment Proof</h2>
<p className="text-sm text-muted-foreground mt-1">
{selectedPayment.user.name} - {selectedPayment.plan.name}
</p>
@@ -330,15 +330,15 @@ export function AdminPayments() {
{selectedPayment.proofUrl ? (
<img
src={selectedPayment.proofUrl}
alt="Bukti Pembayaran"
alt="Payment Proof"
className="w-full rounded-lg"
/>
) : (
<p className="text-muted-foreground">Tidak ada bukti pembayaran</p>
<p className="text-muted-foreground">No payment proof</p>
)}
{selectedPayment.notes && (
<div className="mt-4 p-4 bg-muted rounded-lg">
<p className="text-sm font-medium text-foreground">Catatan:</p>
<p className="text-sm font-medium text-foreground">Notes:</p>
<p className="text-sm text-muted-foreground mt-1">
{selectedPayment.notes}
</p>
@@ -361,7 +361,7 @@ export function AdminPayments() {
}}
className="px-4 py-2 rounded-lg bg-green-500 text-white hover:bg-green-600 transition-colors"
>
Verifikasi
Verify
</button>
<button
onClick={() => {
@@ -370,7 +370,7 @@ export function AdminPayments() {
}}
className="px-4 py-2 rounded-lg bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors"
>
Tolak
Reject
</button>
</>
)}

View File

@@ -242,7 +242,7 @@ function SortablePlanCard({ plan, onEdit, onDelete, onToggleVisibility, formatPr
<button
onClick={() => onDelete(plan.id)}
className="p-2.5 rounded-lg bg-destructive/10 text-destructive hover:bg-destructive hover:text-destructive-foreground transition-all"
title="Hapus"
title="Delete"
>
<Trash2 className="h-4 w-4" />
</button>
@@ -317,10 +317,10 @@ export function AdminPlans() {
{ planIds: newPlans.map((p) => p.id) },
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success('Urutan plan berhasil diubah')
toast.success('Plan order updated successfully')
} catch (error) {
console.error('Failed to reorder plans:', error)
toast.error('Gagal mengubah urutan plan')
toast.error('Failed to update plan order')
fetchPlans() // Revert on error
}
}
@@ -341,14 +341,14 @@ export function AdminPlans() {
if (response.data.action === 'deactivated') {
toast.warning(response.data.message)
} else {
toast.success('Plan berhasil dihapus')
toast.success('Plan deleted successfully')
}
fetchPlans()
setDeleteDialog({ open: false, planId: '' })
} catch (error) {
console.error('Failed to delete plan:', error)
toast.error('Gagal menghapus plan')
toast.error('Failed to delete plan')
}
}
@@ -360,11 +360,11 @@ export function AdminPlans() {
{ isVisible: !plan.isVisible },
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success(plan.isVisible ? 'Plan berhasil disembunyikan' : 'Plan berhasil ditampilkan')
toast.success(plan.isVisible ? 'Plan hidden successfully' : 'Plan shown successfully')
fetchPlans()
} catch (error) {
console.error('Failed to update plan:', error)
toast.error('Gagal mengubah visibilitas plan')
toast.error('Failed to change plan visibility')
}
}
@@ -423,13 +423,13 @@ export function AdminPlans() {
headers: { Authorization: `Bearer ${token}` },
})
}
toast.success(editingPlan ? 'Plan berhasil diupdate' : 'Plan berhasil ditambahkan')
toast.success(editingPlan ? 'Plan updated successfully' : 'Plan added successfully')
fetchPlans()
setShowModal(false)
setEditingPlan(null)
} catch (error) {
console.error('Failed to save plan:', error)
toast.error('Gagal menyimpan plan')
toast.error('Failed to save plan')
}
}
@@ -446,7 +446,7 @@ export function AdminPlans() {
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">Memuat...</div>
<div className="text-muted-foreground">Loading...</div>
</div>
)
}
@@ -455,12 +455,12 @@ export function AdminPlans() {
<div>
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-3xl font-bold text-foreground">Kelola Plans</h1>
<p className="mt-2 text-muted-foreground">Kelola paket berlangganan</p>
<h1 className="text-3xl font-bold text-foreground">Manage Plans</h1>
<p className="mt-2 text-muted-foreground">Manage subscription plans</p>
</div>
<Button onClick={() => handleOpenModal()}>
<Plus className="h-5 w-5 mr-2" />
Tambah Plan
Add Plan
</Button>
</div>
@@ -486,16 +486,16 @@ export function AdminPlans() {
<Dialog open={showModal} onOpenChange={setShowModal}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>{editingPlan ? 'Edit Plan' : 'Tambah Plan Baru'}</DialogTitle>
<DialogTitle>{editingPlan ? 'Edit Plan' : 'Add New Plan'}</DialogTitle>
<DialogDescription>
{editingPlan ? 'Ubah informasi plan berlangganan' : 'Buat plan berlangganan baru'}
{editingPlan ? 'Update subscription plan information' : 'Create a new subscription plan'}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="name">Nama Plan</Label>
<Label htmlFor="name">Plan Name</Label>
<Input
id="name"
required
@@ -515,7 +515,7 @@ export function AdminPlans() {
</div>
<div className="space-y-2">
<Label htmlFor="description">Deskripsi</Label>
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
required
@@ -527,7 +527,7 @@ export function AdminPlans() {
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="price">Harga</Label>
<Label htmlFor="price">Price</Label>
<Input
id="price"
type="number"
@@ -552,7 +552,7 @@ export function AdminPlans() {
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="durationType">Tipe Durasi</Label>
<Label htmlFor="durationType">Duration Type</Label>
<Select value={formData.durationType} onValueChange={(value) => setFormData({ ...formData, durationType: value })}>
<SelectTrigger id="durationType">
<SelectValue />
@@ -576,7 +576,7 @@ export function AdminPlans() {
</div>
<div className="space-y-2">
<Label htmlFor="features">Features (satu per baris)</Label>
<Label htmlFor="features">Features (one per line)</Label>
<Textarea
id="features"
value={formData.features.join('\n')}
@@ -636,7 +636,7 @@ export function AdminPlans() {
/>
<Label htmlFor="isVisible" className="cursor-pointer flex justify-between w-full">
<div className="font-semibold text-foreground">Visible</div>
<div className="text-xs text-muted-foreground">Tampil di halaman pricing</div>
<div className="text-xs text-muted-foreground">Show on pricing page</div>
</Label>
</div>
</div>
@@ -644,10 +644,10 @@ export function AdminPlans() {
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setShowModal(false)}>
Batal
Cancel
</Button>
<Button type="submit">
{editingPlan ? 'Update' : 'Tambah'}
{editingPlan ? 'Update' : 'Add'}
</Button>
</DialogFooter>
</form>
@@ -658,15 +658,15 @@ export function AdminPlans() {
<AlertDialog open={deleteDialog.open} onOpenChange={(open) => setDeleteDialog({ ...deleteDialog, open })}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Hapus Plan?</AlertDialogTitle>
<AlertDialogTitle>Delete Plan?</AlertDialogTitle>
<AlertDialogDescription>
Apakah Anda yakin ingin menghapus plan ini? Tindakan ini tidak dapat dibatalkan.
Are you sure you want to delete this plan? This action cannot be undone.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Batal</AlertDialogCancel>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={handleDelete} className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
Hapus
Delete
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@@ -29,7 +29,7 @@ export function AdminSettings() {
enableEmailVerification: true,
enablePaymentVerification: true,
maintenanceMode: false,
maintenanceMessage: 'Sistem sedang dalam pemeliharaan. Mohon coba lagi nanti.',
maintenanceMessage: 'System is under maintenance. Please try again later.',
})
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
@@ -65,7 +65,7 @@ export function AdminSettings() {
enableEmailVerification: configData.features?.find((c) => c.key === 'enable_email_verification')?.value === 'true',
enablePaymentVerification: configData.features?.find((c) => c.key === 'enable_payment_verification')?.value === 'true',
maintenanceMode: configData.system?.find((c) => c.key === 'maintenance_mode')?.value === 'true',
maintenanceMessage: configData.system?.find((c) => c.key === 'maintenance_message')?.value || 'Sistem sedang dalam pemeliharaan. Mohon coba lagi nanti.',
maintenanceMessage: configData.system?.find((c) => c.key === 'maintenance_message')?.value || 'System is under maintenance. Please try again later.',
}
setSettings(settingsObj)
} catch (error) {
@@ -82,14 +82,14 @@ export function AdminSettings() {
// Save each setting individually
const configUpdates = [
{ key: 'app_name', value: settings.appName, category: 'general', label: 'Nama Aplikasi', type: 'text' },
{ key: 'app_url', value: settings.appUrl, category: 'general', label: 'URL Aplikasi', type: 'text' },
{ key: 'support_email', value: settings.supportEmail, category: 'general', label: 'Email Support', type: 'email' },
{ key: 'enable_registration', value: String(settings.enableRegistration), category: 'features', label: 'Registrasi Pengguna Baru', type: 'boolean' },
{ key: 'enable_email_verification', value: String(settings.enableEmailVerification), category: 'features', label: 'Verifikasi Email', type: 'boolean' },
{ key: 'enable_payment_verification', value: String(settings.enablePaymentVerification), category: 'features', label: 'Verifikasi Pembayaran', type: 'boolean' },
{ key: 'maintenance_mode', value: String(settings.maintenanceMode), category: 'system', label: 'Mode Pemeliharaan', type: 'boolean' },
{ key: 'maintenance_message', value: settings.maintenanceMessage, category: 'system', label: 'Pesan Pemeliharaan', type: 'text' },
{ key: 'app_name', value: settings.appName, category: 'general', label: 'Application Name', type: 'text' },
{ key: 'app_url', value: settings.appUrl, category: 'general', label: 'Application URL', type: 'text' },
{ key: 'support_email', value: settings.supportEmail, category: 'general', label: 'Support Email', type: 'email' },
{ key: 'enable_registration', value: String(settings.enableRegistration), category: 'features', label: 'New User Registration', type: 'boolean' },
{ key: 'enable_email_verification', value: String(settings.enableEmailVerification), category: 'features', label: 'Email Verification', type: 'boolean' },
{ key: 'enable_payment_verification', value: String(settings.enablePaymentVerification), category: 'features', label: 'Payment Verification', type: 'boolean' },
{ key: 'maintenance_mode', value: String(settings.maintenanceMode), category: 'system', label: 'Maintenance Mode', type: 'boolean' },
{ key: 'maintenance_message', value: settings.maintenanceMessage, category: 'system', label: 'Maintenance Message', type: 'text' },
]
await Promise.all(
@@ -100,11 +100,11 @@ export function AdminSettings() {
)
)
toast.success('Pengaturan berhasil disimpan')
toast.success('Settings saved successfully')
fetchSettings() // Refresh
} catch (error) {
console.error('Failed to save settings:', error)
toast.error('Gagal menyimpan pengaturan')
toast.error('Failed to save settings')
} finally {
setSaving(false)
}
@@ -117,7 +117,7 @@ export function AdminSettings() {
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">Memuat...</div>
<div className="text-muted-foreground">Loading...</div>
</div>
)
}
@@ -125,9 +125,9 @@ export function AdminSettings() {
return (
<div className="max-w-4xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground">Pengaturan Aplikasi</h1>
<h1 className="text-3xl font-bold text-foreground">Application Settings</h1>
<p className="mt-2 text-muted-foreground">
Kelola konfigurasi dan pengaturan sistem
Manage system configuration and settings
</p>
</div>
@@ -139,15 +139,15 @@ export function AdminSettings() {
<Globe className="h-5 w-5 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">Pengaturan Umum</h2>
<p className="text-sm text-muted-foreground">Informasi dasar aplikasi</p>
<h2 className="text-lg font-semibold text-foreground">General Settings</h2>
<p className="text-sm text-muted-foreground">Basic application information</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Nama Aplikasi
Application Name
</label>
<Input
type="text"
@@ -158,7 +158,7 @@ export function AdminSettings() {
<div>
<label className="block text-sm font-medium text-foreground mb-2">
URL Aplikasi
Application URL
</label>
<Input
type="url"
@@ -187,17 +187,17 @@ export function AdminSettings() {
<Shield className="h-5 w-5 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">Fitur & Keamanan</h2>
<p className="text-sm text-muted-foreground">Aktifkan atau nonaktifkan fitur</p>
<h2 className="text-lg font-semibold text-foreground">Features & Security</h2>
<p className="text-sm text-muted-foreground">Enable or disable features</p>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between p-4 rounded-lg bg-muted/50">
<div>
<p className="font-medium text-foreground">Registrasi Pengguna Baru</p>
<p className="font-medium text-foreground">New User Registration</p>
<p className="text-sm text-muted-foreground">
Izinkan pengguna baru mendaftar
Allow new users to register
</p>
</div>
<Switch
@@ -208,9 +208,9 @@ export function AdminSettings() {
<div className="flex items-center justify-between p-4 rounded-lg bg-muted/50">
<div>
<p className="font-medium text-foreground">Verifikasi Email</p>
<p className="font-medium text-foreground">Email Verification</p>
<p className="text-sm text-muted-foreground">
Wajibkan verifikasi email untuk pengguna baru
Require email verification for new users
</p>
</div>
<Switch
@@ -221,9 +221,9 @@ export function AdminSettings() {
<div className="flex items-center justify-between p-4 rounded-lg bg-muted/50">
<div>
<p className="font-medium text-foreground">Verifikasi Pembayaran Manual</p>
<p className="font-medium text-foreground">Manual Payment Verification</p>
<p className="text-sm text-muted-foreground">
Aktifkan verifikasi manual untuk pembayaran
Enable manual verification for payments
</p>
</div>
<Switch
@@ -241,9 +241,9 @@ export function AdminSettings() {
<Database className="h-5 w-5 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">Mode Pemeliharaan</h2>
<h2 className="text-lg font-semibold text-foreground">Maintenance Mode</h2>
<p className="text-sm text-muted-foreground">
Nonaktifkan akses sementara untuk maintenance
Temporarily disable access for maintenance
</p>
</div>
</div>
@@ -251,9 +251,9 @@ export function AdminSettings() {
<div className="space-y-4">
<div className="flex items-center justify-between p-4 rounded-lg bg-destructive/10 border border-destructive/20">
<div>
<p className="font-medium text-foreground">Mode Pemeliharaan</p>
<p className="font-medium text-foreground">Maintenance Mode</p>
<p className="text-sm text-muted-foreground">
Aktifkan untuk menutup akses sementara
Enable to temporarily close access
</p>
</div>
<Switch
@@ -266,7 +266,7 @@ export function AdminSettings() {
{settings.maintenanceMode && (
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Pesan Pemeliharaan
Maintenance Message
</label>
<Textarea
value={settings.maintenanceMessage}
@@ -286,7 +286,7 @@ export function AdminSettings() {
className="flex items-center gap-2"
>
<Save className="h-5 w-5" />
{saving ? 'Menyimpan...' : 'Simpan Pengaturan'}
{saving ? 'Saving...' : 'Save Settings'}
</Button>
</div>
</div>

View File

@@ -0,0 +1,63 @@
import { useState } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Globe, Shield, Wallet } from 'lucide-react'
import { AdminSettingsGeneral } from './settings/AdminSettingsGeneral'
import { AdminSettingsSecurity } from './settings/AdminSettingsSecurity'
import { AdminSettingsPaymentMethods } from './settings/AdminSettingsPaymentMethods'
export function AdminSettings() {
const [activeTab, setActiveTab] = useState('general')
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold text-foreground">Settings</h1>
<p className="mt-2 text-muted-foreground">
Manage system configuration and settings
</p>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab} className="flex flex-col md:flex-row gap-6">
{/* Vertical Tabs (Horizontal scrollable on mobile) */}
<TabsList className="flex md:flex-col h-auto md:w-64 flex-shrink-0 bg-muted/50 p-1 overflow-x-auto md:overflow-x-visible justify-start md:h-fit">
<TabsTrigger
value="general"
className="w-full justify-start gap-3 px-4 py-3 data-[state=active]:bg-background whitespace-nowrap"
>
<Globe className="h-5 w-5" />
<span className="inline">General</span>
</TabsTrigger>
<TabsTrigger
value="security"
className="w-full justify-start gap-3 px-4 py-3 data-[state=active]:bg-background whitespace-nowrap"
>
<Shield className="h-5 w-5" />
<span className="inline">Security</span>
</TabsTrigger>
<TabsTrigger
value="payment-methods"
className="w-full justify-start gap-3 px-4 py-3 data-[state=active]:bg-background whitespace-nowrap"
>
<Wallet className="h-5 w-5" />
<span className="inline">Payment Methods</span>
</TabsTrigger>
</TabsList>
{/* Tab Content */}
<div className="flex-1 min-w-0">
<TabsContent value="general" className="mt-0">
<AdminSettingsGeneral />
</TabsContent>
<TabsContent value="security" className="mt-0">
<AdminSettingsSecurity />
</TabsContent>
<TabsContent value="payment-methods" className="mt-0">
<AdminSettingsPaymentMethods />
</TabsContent>
</div>
</Tabs>
</div>
)
}

View File

@@ -1,10 +1,17 @@
import { useEffect, useState } from 'react'
import axios from 'axios'
import { Search, UserX, UserCheck, Crown } from 'lucide-react'
import { Search, UserX, UserCheck, Crown, Plus, Edit, Trash2 } from 'lucide-react'
import { toast } from 'sonner'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import {
Dialog,
DialogContent,
@@ -39,6 +46,9 @@ export function AdminUsers() {
const [suspendReason, setSuspendReason] = useState('')
const [grantProDialog, setGrantProDialog] = useState<{ open: boolean; userId: string }>({ open: false, userId: '' })
const [proDays, setProDays] = useState('')
const [userDialog, setUserDialog] = useState<{ open: boolean; mode: 'create' | 'edit'; user?: User }>({ open: false, mode: 'create' })
const [formData, setFormData] = useState({ email: '', password: '', name: '', role: 'user' })
const [deleteDialog, setDeleteDialog] = useState<{ open: boolean; userId: string; userName: string }>({ open: false, userId: '', userName: '' })
useEffect(() => {
fetchUsers()
@@ -76,12 +86,12 @@ export function AdminUsers() {
suspendDialog.suspend ? { reason: suspendReason } : {},
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success(suspendDialog.suspend ? 'User berhasil disuspend' : 'User berhasil diaktifkan kembali')
toast.success(suspendDialog.suspend ? 'User suspended successfully' : 'User unsuspended successfully')
fetchUsers()
setSuspendDialog({ open: false, userId: '', suspend: false })
} catch (error) {
console.error('Failed to update user:', error)
toast.error('Gagal mengupdate user')
toast.error('Failed to update user')
}
}
@@ -103,32 +113,116 @@ export function AdminUsers() {
},
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success('Akses Pro berhasil diberikan!')
toast.success('Pro access granted successfully!')
fetchUsers()
setGrantProDialog({ open: false, userId: '' })
} catch (error) {
console.error('Failed to grant pro access:', error)
toast.error('Gagal memberikan akses Pro')
toast.error('Failed to grant Pro access')
}
}
const openUserDialog = (mode: 'create' | 'edit', user?: User) => {
setUserDialog({ open: true, mode, user })
if (mode === 'edit' && user) {
setFormData({
email: user.email,
password: '',
name: user.name || '',
role: user.role,
})
} else {
setFormData({ email: '', password: '', name: '', role: 'user' })
}
}
const handleSaveUser = async () => {
if (!formData.email) {
toast.error('Email is required')
return
}
if (userDialog.mode === 'create' && !formData.password) {
toast.error('Password is required')
return
}
try {
const token = localStorage.getItem('token')
if (userDialog.mode === 'create') {
await axios.post(
`${API_URL}/api/admin/users`,
formData,
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success('User created successfully!')
} else {
const updateData: any = {
email: formData.email,
name: formData.name || null,
role: formData.role,
}
await axios.put(
`${API_URL}/api/admin/users/${userDialog.user?.id}`,
updateData,
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success('User updated successfully!')
}
fetchUsers()
setUserDialog({ open: false, mode: 'create' })
} catch (error: any) {
console.error('Failed to save user:', error)
const message = error.response?.data?.message || 'Failed to save user'
toast.error(message)
}
}
const openDeleteDialog = (userId: string, userName: string) => {
setDeleteDialog({ open: true, userId, userName })
}
const handleDelete = async () => {
try {
const token = localStorage.getItem('token')
await axios.delete(
`${API_URL}/api/admin/users/${deleteDialog.userId}`,
{ headers: { Authorization: `Bearer ${token}` } }
)
toast.success('User deleted successfully!')
fetchUsers()
setDeleteDialog({ open: false, userId: '', userName: '' })
} catch (error) {
console.error('Failed to delete user:', error)
toast.error('Failed to delete user')
}
}
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">Memuat...</div>
<div className="text-muted-foreground">Loading...</div>
</div>
)
}
return (
<div>
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground">
Kelola Users
</h1>
<p className="mt-2 text-muted-foreground">
Kelola akun dan izin pengguna
</p>
<div className="mb-8 flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-foreground">
Manage Users
</h1>
<p className="mt-2 text-muted-foreground">
Manage user accounts and permissions
</p>
</div>
<Button onClick={() => openUserDialog('create')}>
<Plus className="h-4 w-4 mr-2" />
Add User
</Button>
</div>
{/* Search */}
@@ -217,6 +311,13 @@ export function AdminUsers() {
)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium space-x-2">
<button
onClick={() => openUserDialog('edit', user)}
className="text-primary hover:text-primary/80"
title="Edit User"
>
<Edit className="h-4 w-4 inline" />
</button>
{user.suspendedAt ? (
<button
onClick={() => openSuspendDialog(user.id, false)}
@@ -241,6 +342,13 @@ export function AdminUsers() {
>
<Crown className="h-4 w-4 inline" />
</button>
<button
onClick={() => openDeleteDialog(user.id, user.name || user.email)}
className="text-destructive hover:text-destructive/80"
title="Delete User"
>
<Trash2 className="h-4 w-4 inline" />
</button>
</td>
</tr>
))}
@@ -251,7 +359,7 @@ export function AdminUsers() {
{users.length === 0 && (
<div className="text-center py-12">
<p className="text-muted-foreground">Tidak ada user</p>
<p className="text-muted-foreground">No users found</p>
</div>
)}
@@ -317,6 +425,93 @@ export function AdminUsers() {
</DialogFooter>
</DialogContent>
</Dialog>
{/* Create/Edit User Dialog */}
<Dialog open={userDialog.open} onOpenChange={(open) => setUserDialog({ ...userDialog, open })}>
<DialogContent>
<DialogHeader>
<DialogTitle>{userDialog.mode === 'create' ? 'Create New User' : 'Edit User'}</DialogTitle>
<DialogDescription>
{userDialog.mode === 'create'
? 'Add a new user to the system.'
: 'Update user information.'}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email *</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="user@example.com"
/>
</div>
{userDialog.mode === 'create' && (
<div className="space-y-2">
<Label htmlFor="password">Password *</Label>
<Input
id="password"
type="password"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
placeholder="Enter password"
/>
</div>
)}
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="Full name"
/>
</div>
<div className="space-y-2">
<Label htmlFor="role">Role</Label>
<Select value={formData.role} onValueChange={(value) => setFormData({ ...formData, role: value })}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="user">User</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setUserDialog({ open: false, mode: 'create' })}>
Cancel
</Button>
<Button onClick={handleSaveUser}>
{userDialog.mode === 'create' ? 'Create User' : 'Update User'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Delete Confirmation Dialog */}
<Dialog open={deleteDialog.open} onOpenChange={(open) => setDeleteDialog({ ...deleteDialog, open })}>
<DialogContent>
<DialogHeader>
<DialogTitle>Delete User</DialogTitle>
<DialogDescription>
Are you sure you want to delete <strong>{deleteDialog.userName}</strong>? This action cannot be undone and will delete all associated data including wallets and transactions.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setDeleteDialog({ open: false, userId: '', userName: '' })}>
Cancel
</Button>
<Button variant="destructive" onClick={handleDelete}>
Delete User
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}

View File

@@ -0,0 +1,209 @@
import { useState, useEffect } from 'react'
import axios from 'axios'
import { Globe, Database, Save } from 'lucide-react'
import { toast } from 'sonner'
import { Input } from '@/components/ui/input'
import { Textarea } from '@/components/ui/textarea'
import { Switch } from '@/components/ui/switch'
import { Button } from '@/components/ui/button'
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001'
interface GeneralSettings {
appName: string
appUrl: string
supportEmail: string
maintenanceMode: boolean
maintenanceMessage: string
}
export function AdminSettingsGeneral() {
const [settings, setSettings] = useState<GeneralSettings>({
appName: 'Tabungin',
appUrl: 'https://tabungin.app',
supportEmail: 'support@tabungin.app',
maintenanceMode: false,
maintenanceMessage: 'System is under maintenance. Please try again later.',
})
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
useEffect(() => {
fetchSettings()
}, [])
const fetchSettings = async () => {
try {
setLoading(true)
const token = localStorage.getItem('token')
const response = await axios.get(`${API_URL}/api/admin/config/by-category`, {
headers: { Authorization: `Bearer ${token}` },
})
const configData = response.data
const settingsObj: GeneralSettings = {
appName: configData.general?.find((c: any) => c.key === 'app_name')?.value || 'Tabungin',
appUrl: configData.general?.find((c: any) => c.key === 'app_url')?.value || 'https://tabungin.app',
supportEmail: configData.general?.find((c: any) => c.key === 'support_email')?.value || 'support@tabungin.app',
maintenanceMode: configData.system?.find((c: any) => c.key === 'maintenance_mode')?.value === 'true',
maintenanceMessage: configData.system?.find((c: any) => c.key === 'maintenance_message')?.value || 'System is under maintenance. Please try again later.',
}
setSettings(settingsObj)
} catch (error) {
console.error('Failed to fetch settings:', error)
} finally {
setLoading(false)
}
}
const handleSave = async () => {
try {
setSaving(true)
const token = localStorage.getItem('token')
const configUpdates = [
{ key: 'app_name', value: settings.appName, category: 'general', label: 'Application Name', type: 'text' },
{ key: 'app_url', value: settings.appUrl, category: 'general', label: 'Application URL', type: 'text' },
{ key: 'support_email', value: settings.supportEmail, category: 'general', label: 'Support Email', type: 'email' },
{ key: 'maintenance_mode', value: String(settings.maintenanceMode), category: 'system', label: 'Maintenance Mode', type: 'boolean' },
{ key: 'maintenance_message', value: settings.maintenanceMessage, category: 'system', label: 'Maintenance Message', type: 'text' },
]
await Promise.all(
configUpdates.map((config) =>
axios.post(`${API_URL}/api/admin/config/${config.key}`, config, {
headers: { Authorization: `Bearer ${token}` },
})
)
)
toast.success('Settings saved successfully')
fetchSettings()
} catch (error) {
console.error('Failed to save settings:', error)
toast.error('Failed to save settings')
} finally {
setSaving(false)
}
}
const handleChange = (field: keyof GeneralSettings, value: string | boolean) => {
setSettings((prev) => ({ ...prev, [field]: value }))
}
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">Loading...</div>
</div>
)
}
return (
<div className="space-y-6">
{/* General Settings Card */}
<div className="bg-card rounded-xl border border-border p-6">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 rounded-lg bg-primary/10">
<Globe className="h-5 w-5 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">General Settings</h2>
<p className="text-sm text-muted-foreground">Basic application information</p>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Application Name
</label>
<Input
type="text"
value={settings.appName}
onChange={(e) => handleChange('appName', e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Application URL
</label>
<Input
type="url"
value={settings.appUrl}
onChange={(e) => handleChange('appUrl', e.target.value)}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Support Email
</label>
<Input
type="email"
value={settings.supportEmail}
onChange={(e) => handleChange('supportEmail', e.target.value)}
/>
</div>
</div>
</div>
{/* Maintenance Mode Card */}
<div className="bg-card rounded-xl border border-border p-6">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 rounded-lg bg-primary/10">
<Database className="h-5 w-5 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">Maintenance Mode</h2>
<p className="text-sm text-muted-foreground">
Temporarily disable access for maintenance
</p>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between p-4 rounded-lg bg-destructive/10 border border-destructive/20">
<div>
<p className="font-medium text-foreground">Maintenance Mode</p>
<p className="text-sm text-muted-foreground">
Enable to temporarily close access
</p>
</div>
<Switch
checked={settings.maintenanceMode}
onCheckedChange={(checked) => handleChange('maintenanceMode', checked)}
className="data-[state=checked]:bg-destructive"
/>
</div>
{settings.maintenanceMode && (
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Maintenance Message
</label>
<Textarea
value={settings.maintenanceMessage}
onChange={(e) => handleChange('maintenanceMessage', e.target.value)}
rows={3}
/>
</div>
)}
</div>
</div>
{/* Save Button */}
<div className="flex justify-end">
<Button
onClick={handleSave}
disabled={saving}
className="flex items-center gap-2"
>
<Save className="h-5 w-5" />
{saving ? 'Saving...' : 'Save Settings'}
</Button>
</div>
</div>
)
}

View File

@@ -0,0 +1,8 @@
// This component wraps the existing AdminPaymentMethods component
// to be used as a tab content in the Settings page
import { AdminPaymentMethods } from '../AdminPaymentMethods'
export function AdminSettingsPaymentMethods() {
return <AdminPaymentMethods />
}

View File

@@ -0,0 +1,161 @@
import { useState, useEffect } from 'react'
import axios from 'axios'
import { Shield, Save } from 'lucide-react'
import { toast } from 'sonner'
import { Switch } from '@/components/ui/switch'
import { Button } from '@/components/ui/button'
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001'
interface SecuritySettings {
enableRegistration: boolean
enableEmailVerification: boolean
enablePaymentVerification: boolean
}
export function AdminSettingsSecurity() {
const [settings, setSettings] = useState<SecuritySettings>({
enableRegistration: true,
enableEmailVerification: true,
enablePaymentVerification: true,
})
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
useEffect(() => {
fetchSettings()
}, [])
const fetchSettings = async () => {
try {
setLoading(true)
const token = localStorage.getItem('token')
const response = await axios.get(`${API_URL}/api/admin/config/by-category`, {
headers: { Authorization: `Bearer ${token}` },
})
const configData = response.data
const settingsObj: SecuritySettings = {
enableRegistration: configData.features?.find((c: any) => c.key === 'enable_registration')?.value === 'true',
enableEmailVerification: configData.features?.find((c: any) => c.key === 'enable_email_verification')?.value === 'true',
enablePaymentVerification: configData.features?.find((c: any) => c.key === 'enable_payment_verification')?.value === 'true',
}
setSettings(settingsObj)
} catch (error) {
console.error('Failed to fetch settings:', error)
} finally {
setLoading(false)
}
}
const handleSave = async () => {
try {
setSaving(true)
const token = localStorage.getItem('token')
const configUpdates = [
{ key: 'enable_registration', value: String(settings.enableRegistration), category: 'features', label: 'New User Registration', type: 'boolean' },
{ key: 'enable_email_verification', value: String(settings.enableEmailVerification), category: 'features', label: 'Email Verification', type: 'boolean' },
{ key: 'enable_payment_verification', value: String(settings.enablePaymentVerification), category: 'features', label: 'Payment Verification', type: 'boolean' },
]
await Promise.all(
configUpdates.map((config) =>
axios.post(`${API_URL}/api/admin/config/${config.key}`, config, {
headers: { Authorization: `Bearer ${token}` },
})
)
)
toast.success('Settings saved successfully')
fetchSettings()
} catch (error) {
console.error('Failed to save settings:', error)
toast.error('Failed to save settings')
} finally {
setSaving(false)
}
}
const handleChange = (field: keyof SecuritySettings, value: boolean) => {
setSettings((prev) => ({ ...prev, [field]: value }))
}
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="text-muted-foreground">Loading...</div>
</div>
)
}
return (
<div className="space-y-6">
{/* Feature Toggles Card */}
<div className="bg-card rounded-xl border border-border p-6">
<div className="flex items-center gap-3 mb-6">
<div className="p-2 rounded-lg bg-primary/10">
<Shield className="h-5 w-5 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold text-foreground">Features & Security</h2>
<p className="text-sm text-muted-foreground">Enable or disable features</p>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between p-4 rounded-lg bg-muted/50">
<div>
<p className="font-medium text-foreground">New User Registration</p>
<p className="text-sm text-muted-foreground">
Allow new users to register
</p>
</div>
<Switch
checked={settings.enableRegistration}
onCheckedChange={(checked) => handleChange('enableRegistration', checked)}
/>
</div>
<div className="flex items-center justify-between p-4 rounded-lg bg-muted/50">
<div>
<p className="font-medium text-foreground">Email Verification</p>
<p className="text-sm text-muted-foreground">
Require email verification for new users
</p>
</div>
<Switch
checked={settings.enableEmailVerification}
onCheckedChange={(checked) => handleChange('enableEmailVerification', checked)}
/>
</div>
<div className="flex items-center justify-between p-4 rounded-lg bg-muted/50">
<div>
<p className="font-medium text-foreground">Manual Payment Verification</p>
<p className="text-sm text-muted-foreground">
Enable manual verification for payments
</p>
</div>
<Switch
checked={settings.enablePaymentVerification}
onCheckedChange={(checked) => handleChange('enablePaymentVerification', checked)}
/>
</div>
</div>
</div>
{/* Save Button */}
<div className="flex justify-end">
<Button
onClick={handleSave}
disabled={saving}
className="flex items-center gap-2"
>
<Save className="h-5 w-5" />
{saving ? 'Saving...' : 'Save Settings'}
</Button>
</div>
</div>
)
}

View File

@@ -1,32 +1,64 @@
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useAuth } from '@/contexts/AuthContext'
import { Loader2 } from 'lucide-react'
import axios from 'axios'
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001'
export function AuthCallback() {
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const { updateUser } = useAuth()
const [error, setError] = useState('')
useEffect(() => {
const token = searchParams.get('token')
if (token) {
// Store token and redirect to dashboard
localStorage.setItem('token', token)
// Force reload to trigger auth context
window.location.href = '/'
} else {
// No token, redirect to login
navigate('/auth/login')
const handleCallback = async () => {
const token = searchParams.get('token')
if (!token) {
navigate('/auth/login')
return
}
try {
// Store token
localStorage.setItem('token', token)
// Fetch user to check role
const response = await axios.get(`${API_URL}/api/auth/me`, {
headers: { Authorization: `Bearer ${token}` }
})
// Redirect based on role
if (response.data.role === 'admin') {
window.location.href = '/admin'
} else {
window.location.href = '/'
}
} catch (err) {
console.error('Failed to fetch user:', err)
setError('Failed to complete sign in')
localStorage.removeItem('token')
setTimeout(() => navigate('/auth/login'), 2000)
}
}
}, [searchParams, navigate, updateUser])
handleCallback()
}, [searchParams, navigate])
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="text-center">
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4" />
<p className="text-gray-600">Completing sign in...</p>
{error ? (
<>
<p className="text-red-600 mb-4">{error}</p>
<p className="text-gray-600">Redirecting to login...</p>
</>
) : (
<>
<Loader2 className="h-8 w-8 animate-spin mx-auto mb-4" />
<p className="text-gray-600">Completing sign in...</p>
</>
)}
</div>
</div>
)

View File

@@ -0,0 +1,60 @@
import { AlertTriangle, RefreshCw } from 'lucide-react'
import { Button } from '@/components/ui/button'
interface MaintenancePageProps {
message?: string
}
export function MaintenancePage({ message }: MaintenancePageProps) {
const defaultMessage = 'System is under maintenance. Please try again later.'
const handleRefresh = () => {
window.location.reload()
}
return (
<div className="min-h-screen flex items-center justify-center bg-background p-4">
<div className="max-w-md w-full text-center space-y-6">
{/* Icon */}
<div className="flex justify-center">
<div className="p-6 rounded-full bg-yellow-500/10">
<AlertTriangle className="h-16 w-16 text-yellow-600" />
</div>
</div>
{/* Title */}
<div className="space-y-2">
<h1 className="text-3xl font-bold text-foreground">
Under Maintenance
</h1>
<p className="text-muted-foreground text-lg">
{message || defaultMessage}
</p>
</div>
{/* Description */}
<div className="p-4 rounded-lg bg-muted/50 border border-border">
<p className="text-sm text-muted-foreground">
We're currently performing scheduled maintenance to improve your experience.
We'll be back online shortly.
</p>
</div>
{/* Refresh Button */}
<Button
onClick={handleRefresh}
className="w-full"
size="lg"
>
<RefreshCw className="h-5 w-5 mr-2" />
Refresh Page
</Button>
{/* Footer */}
<p className="text-xs text-muted-foreground">
Thank you for your patience
</p>
</div>
</div>
)
}

View File

@@ -57,9 +57,13 @@ export function OtpVerification() {
setLoading(true)
try {
await verifyOtp(tempToken, code, method)
// Verification successful, redirect to dashboard
navigate('/')
const result = await verifyOtp(tempToken, code, method)
// Verification successful, redirect based on role
if (result.user?.role === 'admin') {
navigate('/admin')
} else {
navigate('/')
}
} catch (err) {
const error = err as { response?: { data?: { message?: string } } }
setError(error.response?.data?.message || 'Invalid OTP code. Please try again.')

View File

@@ -498,14 +498,14 @@ export function Profile() {
return (
<div className="space-y-6">
<div className="max-w-4xl mx-auto">
<div className="mx-auto">
<h1 className="text-3xl font-bold">{t.profile.title}</h1>
<p className="text-muted-foreground">{t.profile.description}</p>
</div>
<div className="max-w-4xl mx-auto">
<div className="mx-auto">
<Tabs defaultValue="profile" className="w-full">
<TabsList className="grid w-[50%] grid-cols-2 h-auto p-1">
<TabsList className="grid md:w-[40%] grid-cols-2 h-auto p-1">
<TabsTrigger value="profile" className="h-11 md:h-9 text-base md:text-sm data-[state=active]:bg-background">
{t.profile.editProfile}
</TabsTrigger>
@@ -516,6 +516,7 @@ export function Profile() {
{/* Edit Profile Tab */}
<TabsContent value="profile" className="w-full space-y-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
<Card>
<CardHeader>
<CardTitle>{t.profile.personalInfo}</CardTitle>
@@ -679,6 +680,7 @@ export function Profile() {
</div>
</CardContent>
</Card>
</div>
</TabsContent>
{/* Security Tab */}
@@ -720,7 +722,7 @@ export function Profile() {
<Input
id="current-password"
type="password"
placeholder="******"
placeholder="••••••"
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
disabled={passwordLoading}
@@ -733,7 +735,7 @@ export function Profile() {
<Input
id="new-password"
type="password"
placeholder="******"
placeholder="••••••"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
disabled={passwordLoading}
@@ -745,7 +747,7 @@ export function Profile() {
<Input
id="confirm-password"
type="password"
placeholder="******"
placeholder="••••••"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={passwordLoading}
@@ -782,6 +784,8 @@ export function Profile() {
</CardHeader>
<CardContent className="space-y-6">
<Separator />
{/* WhatsApp OTP */}
<div className="space-y-4">
<div className="flex items-center justify-between">
@@ -794,7 +798,7 @@ export function Profile() {
</p>
</div>
</div>
<Badge variant={otpStatus.whatsappEnabled ? "default" : "secondary"}>
<Badge variant={otpStatus.whatsappEnabled ? "default" : "secondary"} className="text-nowrap">
{otpStatus.whatsappEnabled ? t.profile.enabled : t.profile.disabled}
</Badge>
</div>
@@ -895,7 +899,7 @@ export function Profile() {
</p>
</div>
</div>
<Badge variant={otpStatus.emailEnabled ? "default" : "secondary"}>
<Badge variant={otpStatus.emailEnabled ? "default" : "secondary"} className="text-nowrap">
{otpStatus.emailEnabled ? t.profile.enabled : t.profile.disabled}
</Badge>
</div>
@@ -974,7 +978,7 @@ export function Profile() {
</p>
</div>
</div>
<Badge variant={otpStatus.totpEnabled ? "default" : "secondary"}>
<Badge variant={otpStatus.totpEnabled ? "default" : "secondary"} className="text-nowrap">
{otpStatus.totpEnabled ? t.profile.enabled : t.profile.disabled}
</Badge>
</div>

View File

@@ -0,0 +1,24 @@
import axios from 'axios'
let maintenanceCallback: ((message: string) => void) | null = null
export function setupAxiosInterceptors(onMaintenance: (message: string) => void) {
maintenanceCallback = onMaintenance
// Response interceptor to handle maintenance mode
axios.interceptors.response.use(
(response) => response,
(error) => {
// Check if it's a maintenance mode error (503)
if (error.response?.status === 503 && error.response?.data?.maintenanceMode) {
const message = error.response.data.message || 'System is under maintenance. Please try again later.'
if (maintenanceCallback) {
maintenanceCallback(message)
}
// Prevent further error handling
return Promise.reject({ maintenanceMode: true, message })
}
return Promise.reject(error)
}
)
}