Compare commits
57 Commits
develop/tr
...
develop/mo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b80939c78d | ||
![]() |
779984f65c | ||
![]() |
8bb6c9e992 | ||
![]() |
60ecca48b4 | ||
![]() |
7a6014a115 | ||
![]() |
37ace98e60 | ||
![]() |
44091e0432 | ||
![]() |
279496ea38 | ||
![]() |
a450747670 | ||
![]() |
215476cfaa | ||
![]() |
c93b31b2f6 | ||
![]() |
4cf93f431e | ||
![]() |
2b09261f0a | ||
![]() |
1325a361dc | ||
![]() |
a4d0d8b746 | ||
![]() |
f8919b8c84 | ||
![]() |
583e31fd4d | ||
![]() |
ae7f005236 | ||
![]() |
834f9360ba | ||
![]() |
1cb018d4d5 | ||
![]() |
448825545e | ||
![]() |
7c7a532381 | ||
![]() |
eb515dbe68 | ||
![]() |
7a793be610 | ||
![]() |
1d89134ea2 | ||
![]() |
44d324b40e | ||
![]() |
ee2a377bf6 | ||
![]() |
0941ab6dd1 | ||
![]() |
e93c5dcf62 | ||
![]() |
17aaf97722 | ||
![]() |
47660be0cd | ||
![]() |
cf5cfb8d63 | ||
![]() |
a686dd55dd | ||
![]() |
8d52be9b96 | ||
![]() |
db16b2d5dd | ||
![]() |
7370316ea0 | ||
![]() |
77f7211317 | ||
![]() |
dfc9873815 | ||
![]() |
53b112e488 | ||
![]() |
a9dd265658 | ||
![]() |
ae39b7ca67 | ||
![]() |
4c2a06585d | ||
![]() |
816d88c2c1 | ||
![]() |
843e459527 | ||
![]() |
cd0ae5ba62 | ||
![]() |
ecdbc45c4a | ||
![]() |
4b64778b59 | ||
![]() |
a88de2f17f | ||
![]() |
d987b0fe36 | ||
![]() |
0142c9ba24 | ||
![]() |
48e1f821ad | ||
![]() |
9943283eff | ||
![]() |
9ac13787aa | ||
![]() |
c1b71fca64 | ||
![]() |
08642a2bf6 | ||
![]() |
043d45862c | ||
![]() |
8444b947a4 |
@@ -1,10 +1,10 @@
|
||||
---
|
||||
tags: frontend, product, update
|
||||
tags: frontend, party-event
|
||||
---
|
||||
|
||||
# REQ0185 frontend product update
|
||||
# REQ0185 frontend party-event
|
||||
|
||||
frontend page to update product
|
||||
frontend page to handle party-event (CRUD)
|
||||
|
||||
edit page T.B.A.
|
||||
|
||||
@@ -14,4 +14,5 @@ T.B.A.
|
||||
|
||||
## branch
|
||||
|
||||
develop/frontend/party-event/trunk
|
||||
develop/requirements/REQ0185
|
||||
|
17
01_Requirements/REQ0186/index.md
Normal file
17
01_Requirements/REQ0186/index.md
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
tags: frontend, product, update
|
||||
---
|
||||
|
||||
# REQ0185 frontend product update
|
||||
|
||||
frontend page to update product
|
||||
|
||||
edit page T.B.A.
|
||||
|
||||
## sources
|
||||
|
||||
T.B.A.
|
||||
|
||||
## branch
|
||||
|
||||
develop/requirements/REQ0185
|
23
01_Requirements/REQ0187/index.md
Normal file
23
01_Requirements/REQ0187/index.md
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
tags: frontend, party-order
|
||||
---
|
||||
|
||||
# REQ0185 frontend party-order
|
||||
|
||||
frontend page to handle party-order (CRUD)
|
||||
|
||||
edit page T.B.A.
|
||||
|
||||
## TODO
|
||||
|
||||
- remove detail in left nav bar
|
||||
- implement `changeStatus`
|
||||
|
||||
## sources
|
||||
|
||||
T.B.A.
|
||||
|
||||
## branch
|
||||
|
||||
develop/frontend/party-order/trunk
|
||||
develop/requirements/REQ0187
|
21
01_Requirements/REQ0188/index.md
Normal file
21
01_Requirements/REQ0188/index.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
tags: frontend, party-user
|
||||
---
|
||||
|
||||
# REQ0188 frontend party-user
|
||||
|
||||
frontend page to handle party-user (CRUD)
|
||||
|
||||
edit page T.B.A.
|
||||
|
||||
## TODO
|
||||
|
||||
## sources
|
||||
|
||||
T.B.A.
|
||||
|
||||
## branch
|
||||
|
||||
develop/requirements/REQ0188
|
||||
develop/frontend/party-user/trunk
|
||||
develop/frontend/party-user-auth/trunk
|
20
01_Requirements/REQ0189/index.md
Normal file
20
01_Requirements/REQ0189/index.md
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
tags: mobile, payment
|
||||
---
|
||||
|
||||
# REQ0189 party payment flow
|
||||
|
||||
frontend page to handle party-user pay join event
|
||||
|
||||
edit page T.B.A.
|
||||
|
||||
## TODO
|
||||
|
||||
## sources
|
||||
|
||||
T.B.A.
|
||||
|
||||
## branch
|
||||
|
||||
develop/requirements/REQ0189
|
||||
develop/mobile/DummyPayPage/trunk
|
@@ -6,6 +6,7 @@ RUN npm install -g pnpm
|
||||
|
||||
RUN apt-get update -y
|
||||
RUN apt-get install -y openssl
|
||||
RUN apt-get install -qqy psmisc
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
@@ -1,3 +1,5 @@
|
||||
// src/cms_backend/eslint.config.mjs
|
||||
//
|
||||
import globals from 'globals';
|
||||
import eslintJs from '@eslint/js';
|
||||
import eslintTs from 'typescript-eslint';
|
||||
@@ -69,10 +71,7 @@ const importRules = () => ({
|
||||
*/
|
||||
const unusedImportsRules = () => ({
|
||||
'unused-imports/no-unused-imports': 1,
|
||||
'unused-imports/no-unused-vars': [
|
||||
0,
|
||||
{ vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
|
||||
],
|
||||
'unused-imports/no-unused-vars': [0, { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }],
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -93,15 +92,17 @@ const sortImportsRules = () => {
|
||||
|
||||
return {
|
||||
'perfectionist/sort-named-imports': [1, { type: 'line-length', order: 'asc' }],
|
||||
'perfectionist/sort-named-exports': [1, { type: 'line-length', order: 'asc' }],
|
||||
'perfectionist/sort-exports': [
|
||||
1,
|
||||
{
|
||||
order: 'asc',
|
||||
type: 'line-length',
|
||||
groupKind: 'values-first',
|
||||
},
|
||||
],
|
||||
|
||||
// disable sorting of export, i manage the export ordering
|
||||
// 'perfectionist/sort-named-exports': [1, { type: 'line-length', order: 'asc' }],
|
||||
// 'perfectionist/sort-exports': [
|
||||
// 1,
|
||||
// {
|
||||
// order: 'asc',
|
||||
// type: 'line-length',
|
||||
// groupKind: 'values-first',
|
||||
// },
|
||||
// ],
|
||||
'perfectionist/sort-imports': [
|
||||
2,
|
||||
{
|
||||
|
@@ -5,6 +5,7 @@
|
||||
"description": "Mock server & assets",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev:check": "yarn tsc:w",
|
||||
"dev": "next dev -p 7272 -H 0.0.0.0",
|
||||
"start": "next start -p 7272 -H 0.0.0.0",
|
||||
"build": "next build",
|
||||
@@ -20,7 +21,7 @@
|
||||
"re:build-npm": "npm run clean && npm install && npm run build",
|
||||
"tsc:dev": "yarn dev & yarn tsc:watch",
|
||||
"tsc:print": "npx tsc --showConfig",
|
||||
"tsc:w": "npx nodemon --delay 3 --ext ts,tsx --exec \"yarn tsc\"",
|
||||
"tsc:w": "npx nodemon --delay 1 --ext ts,tsx --exec \"yarn tsc\"",
|
||||
"tsc:watch": "tsc --noEmit --watch",
|
||||
"tsc": "tsc --noEmit",
|
||||
"migrate": "npx prisma migrate dev --skip-seed",
|
||||
@@ -31,7 +32,9 @@
|
||||
"db:push": "prisma db push --force-reset",
|
||||
"db:push:w": "npx nodemon --delay 1 --watch prisma --ext \"ts,tsx,prisma\" --exec \"yarn db:push && yarn seed\"",
|
||||
"db:studio": "prisma studio",
|
||||
"db:studio:w": "npx nodemon --delay 1 --watch prisma --ext \"prisma\" --exec \"yarn db:studio\""
|
||||
"db:studio:w": "npx nodemon --delay 1 --watch prisma --ext \"prisma\" --exec \"yarn db:studio\"",
|
||||
"db:dev": "yarn db:push && yarn seed && yarn dev",
|
||||
"db:dev:w": "npx nodemon --delay 3 --ext \"ts,tsx,prisma\" --exec \"yarn db:dev\""
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -68,6 +71,7 @@
|
||||
"@types/react": "^18.3.20",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@typescript-eslint/parser": "^8.28.0",
|
||||
"concurrently": "^9.1.2",
|
||||
"eslint": "^9.23.0",
|
||||
"eslint-import-resolver-typescript": "^4.2.2",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
|
@@ -16,11 +16,11 @@ model Helloworld {
|
||||
}
|
||||
|
||||
model Account {
|
||||
id String @id @default(cuid())
|
||||
userId String @map("user_id")
|
||||
id String @id @default(cuid())
|
||||
userId String @map("user_id")
|
||||
type String
|
||||
provider String
|
||||
providerAccountId String @map("provider_account_id")
|
||||
providerAccountId String @map("provider_account_id")
|
||||
refresh_token String?
|
||||
access_token String?
|
||||
expires_at Int?
|
||||
@@ -30,18 +30,21 @@ model Account {
|
||||
session_state String?
|
||||
oauth_token_secret String?
|
||||
oauth_token String?
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
PartyUser PartyUser? @relation(fields: [partyUserId], references: [id])
|
||||
partyUserId String?
|
||||
|
||||
@@unique([provider, providerAccountId])
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @default(cuid())
|
||||
sessionToken String @unique @map("session_token")
|
||||
userId String @map("user_id")
|
||||
id String @id @default(cuid())
|
||||
sessionToken String @unique @map("session_token")
|
||||
userId String @map("user_id")
|
||||
expires DateTime
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
PartyUser PartyUser? @relation(fields: [partyUserId], references: [id])
|
||||
partyUserId String?
|
||||
}
|
||||
|
||||
model User {
|
||||
@@ -1146,48 +1149,60 @@ model EventReview {
|
||||
|
||||
// NOTE: need to consider with Event
|
||||
// mapped to IEventItem
|
||||
// a.k.a. PartyEvent party-event
|
||||
model EventItem {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
//
|
||||
sku String
|
||||
name String
|
||||
code String
|
||||
price Float
|
||||
taxes Float
|
||||
tags String[]
|
||||
sizes String[]
|
||||
publish String
|
||||
gender String[]
|
||||
coverUrl String
|
||||
images String[]
|
||||
colors String[]
|
||||
quantity Int
|
||||
category String
|
||||
available Int
|
||||
totalSold Int
|
||||
description String
|
||||
totalRatings Float
|
||||
totalReviews Int
|
||||
inventoryType String
|
||||
subDescription String
|
||||
priceSale Float?
|
||||
newLabel Json
|
||||
saleLabel Json
|
||||
ratings Json[]
|
||||
available Int @default(99)
|
||||
category String
|
||||
code String @default("")
|
||||
colors String[]
|
||||
coverUrl String
|
||||
description String
|
||||
gender String[]
|
||||
images String[]
|
||||
inventoryType String @default("")
|
||||
name String @default("")
|
||||
newLabel Json @default("{}")
|
||||
price Float @default(999.9)
|
||||
priceSale Float? @default(111.1)
|
||||
publish String @default("")
|
||||
quantity Int @default(99)
|
||||
ratings Json[]
|
||||
saleLabel Json @default("{}")
|
||||
sizes String[] @default([""])
|
||||
sku String @default("")
|
||||
subDescription String @default("")
|
||||
tags String[] @default([""])
|
||||
taxes Float @default(5.0)
|
||||
totalRatings Float @default(5.0)
|
||||
totalReviews Int @default(10)
|
||||
totalSold Int @default(10)
|
||||
//
|
||||
eventDate DateTime @default(now())
|
||||
joinMembers Json[]
|
||||
title String
|
||||
currency String
|
||||
duration_m Float
|
||||
ageBottom Float
|
||||
ageTop Float
|
||||
location String
|
||||
avatar String[]
|
||||
ageBottom Float @default(-1)
|
||||
ageTop Float @default(-1)
|
||||
avatar String[] @default([""])
|
||||
currency String @default("HKD")
|
||||
capacity Int @default(10)
|
||||
duration_m Float @default(180)
|
||||
endDate String? @default("")
|
||||
eventDate DateTime @default(now())
|
||||
isFeatured Boolean @default(false)
|
||||
joinMembers Json[] @default([])
|
||||
location String @default("HK")
|
||||
organizer String @default("")
|
||||
registrationDeadline String @default("")
|
||||
requirements String @default("")
|
||||
schedule String @default("")
|
||||
speakers String[] @default([])
|
||||
sponsors String[] @default([])
|
||||
startDate String? @default("")
|
||||
status String? @default("")
|
||||
title String @default("")
|
||||
//
|
||||
reviews EventReview[]
|
||||
reviews EventReview[]
|
||||
}
|
||||
|
||||
model AppLog {
|
||||
@@ -1218,3 +1233,63 @@ model AccessLog {
|
||||
@@index([timestamp])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model PartyOrderItem {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
//
|
||||
taxes Float
|
||||
status String
|
||||
shipping Float
|
||||
discount Float
|
||||
subtotal Float
|
||||
orderNumber String
|
||||
totalAmount Float
|
||||
totalQuantity Float
|
||||
history Json
|
||||
payment Json
|
||||
customer Json
|
||||
delivery Json
|
||||
items Json[]
|
||||
shippingAddress Json
|
||||
// OrderProductItem OrderProductItem[]
|
||||
// OrderHistory OrderHistory[]
|
||||
// OrderDelivery OrderDelivery[]
|
||||
// OrderCustomer OrderCustomer[]
|
||||
// OrderPayment OrderPayment[]
|
||||
// OrderShippingAddress OrderShippingAddress[]
|
||||
}
|
||||
|
||||
model PartyUser {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
//
|
||||
username String? @unique
|
||||
password String?
|
||||
//
|
||||
name String?
|
||||
email String @unique
|
||||
emailVerified DateTime?
|
||||
avatarUrl String?
|
||||
bucketImage String?
|
||||
admin Boolean @default(false)
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
info Json?
|
||||
phoneNumber String @default("")
|
||||
company String @default("")
|
||||
status String @default("pending")
|
||||
role String @default("")
|
||||
isVerified Boolean @default(false)
|
||||
//
|
||||
country String @default("")
|
||||
state String @default("")
|
||||
city String @default("")
|
||||
address String @default("")
|
||||
zipCode String @default("")
|
||||
//
|
||||
rank String @default("user")
|
||||
sex String @default("")
|
||||
}
|
||||
|
@@ -31,6 +31,9 @@ import { EventReviewSeed } from './seeds/eventReview';
|
||||
import { appLogSeed } from './seeds/AppLog';
|
||||
import { accessLogSeed } from './seeds/AccessLog';
|
||||
import { userMetaSeed } from './seeds/userMeta';
|
||||
//
|
||||
import { partyOrderItemSeed } from './seeds/partyOrderItem';
|
||||
import { partyUserSeed } from './seeds/partyUser';
|
||||
|
||||
//
|
||||
// import { Blog } from './seeds/blog';
|
||||
@@ -59,6 +62,9 @@ import { userMetaSeed } from './seeds/userMeta';
|
||||
//
|
||||
await appLogSeed;
|
||||
await accessLogSeed;
|
||||
//
|
||||
await partyOrderItemSeed;
|
||||
await partyUserSeed;
|
||||
|
||||
// await Blog;
|
||||
// await Mail;
|
||||
|
@@ -125,7 +125,7 @@ const generateRatings = () =>
|
||||
const generateImages = () => Array.from({ length: 8 }, (_, index) => _mock.image.event(index));
|
||||
|
||||
const _events = () =>
|
||||
Array.from({ length: 2 }, (_, index) => {
|
||||
Array.from({ length: 5 }, (_, index) => {
|
||||
const reviews = generateReviews();
|
||||
const images = generateImages();
|
||||
const ratings = generateRatings();
|
||||
|
94
03_source/cms_backend/prisma/seeds/partyOrderItem.ts
Normal file
94
03_source/cms_backend/prisma/seeds/partyOrderItem.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { _mock } from './_mock';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const ITEMS = Array.from({ length: 3 }, (_, index) => ({
|
||||
id: _mock.id(index),
|
||||
sku: `16H9UR${index}`,
|
||||
quantity: index + 1,
|
||||
name: _mock.productName(index),
|
||||
coverUrl: _mock.image.product(index),
|
||||
price: _mock.number.price(index),
|
||||
}));
|
||||
|
||||
async function partyOrderItem() {
|
||||
await prisma.partyOrderItem.deleteMany({});
|
||||
|
||||
for (let index = 1; index < 20 + 1; index++) {
|
||||
const shipping = 10;
|
||||
const discount = 10;
|
||||
const taxes = 10;
|
||||
const items = (index % 2 && ITEMS.slice(0, 1)) || (index % 3 && ITEMS.slice(1, 3)) || ITEMS;
|
||||
const totalQuantity = items.reduce((accumulator, item) => accumulator + item.quantity, 0);
|
||||
const subtotal = items.reduce((accumulator, item) => accumulator + item.price * item.quantity, 0);
|
||||
const totalAmount = subtotal - shipping - discount + taxes;
|
||||
|
||||
const customer = {
|
||||
id: _mock.id(index),
|
||||
name: _mock.fullName(index),
|
||||
email: _mock.email(index),
|
||||
avatarUrl: _mock.image.avatar(index),
|
||||
ipAddress: '192.158.1.38',
|
||||
};
|
||||
|
||||
const delivery = { shipBy: 'DHL', speedy: 'Standard', trackingNumber: 'SPX037739199373' };
|
||||
|
||||
const history = {
|
||||
orderTime: _mock.time(1),
|
||||
paymentTime: _mock.time(2),
|
||||
deliveryTime: _mock.time(3),
|
||||
completionTime: _mock.time(4),
|
||||
timeline: [
|
||||
{ title: 'Delivery successful', time: _mock.time(1) },
|
||||
{ title: 'Transporting to [2]', time: _mock.time(2) },
|
||||
{ title: 'Transporting to [1]', time: _mock.time(3) },
|
||||
{ title: 'The shipping unit has picked up the goods', time: _mock.time(4) },
|
||||
{ title: 'Order has been created', time: _mock.time(5) },
|
||||
],
|
||||
};
|
||||
|
||||
const temp = await prisma.partyOrderItem.upsert({
|
||||
where: { id: index.toString() },
|
||||
update: {},
|
||||
create: {
|
||||
id: _mock.id(index),
|
||||
orderNumber: `#601${index}`,
|
||||
taxes,
|
||||
items,
|
||||
history,
|
||||
subtotal: items.reduce((accumulator, item) => accumulator + item.price * item.quantity, 0),
|
||||
shipping,
|
||||
discount,
|
||||
customer,
|
||||
delivery,
|
||||
totalAmount,
|
||||
totalQuantity,
|
||||
shippingAddress: {
|
||||
fullAddress: '19034 Verna Unions Apt. 164 - Honolulu, RI / 87535',
|
||||
phoneNumber: '365-374-4961',
|
||||
},
|
||||
payment: {
|
||||
//
|
||||
cardType: 'mastercard',
|
||||
cardNumber: '4111 1111 1111 1111',
|
||||
},
|
||||
status: (index % 2 && 'completed') || (index % 3 && 'pending') || (index % 4 && 'cancelled') || 'refunded',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('seed partyOrderItemSeed done');
|
||||
}
|
||||
|
||||
const partyOrderItemSeed = partyOrderItem()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export { partyOrderItemSeed };
|
135
03_source/cms_backend/prisma/seeds/partyUser.ts
Normal file
135
03_source/cms_backend/prisma/seeds/partyUser.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Party User seed data generator
|
||||
* Creates initial user accounts for development and testing
|
||||
* Includes:
|
||||
* - Fixed demo accounts (alice, demo)
|
||||
* - Randomly generated test accounts with CJK locale data
|
||||
*/
|
||||
import { faker as enFaker } from '@faker-js/faker/locale/en_US';
|
||||
import { faker as zhFaker } from '@faker-js/faker/locale/zh_CN';
|
||||
import { faker as jaFaker } from '@faker-js/faker/locale/ja';
|
||||
import { faker as koFaker } from '@faker-js/faker/locale/ko';
|
||||
import { faker as twFaker } from '@faker-js/faker/locale/zh_TW';
|
||||
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const ROLE = [
|
||||
`CEO`,
|
||||
`CTO`,
|
||||
`Project Coordinator`,
|
||||
`Team Leader`,
|
||||
`Software Developer`,
|
||||
`Marketing Strategist`,
|
||||
`Data Analyst`,
|
||||
`Product Owner`,
|
||||
`Graphic Designer`,
|
||||
`Operations Manager`,
|
||||
`Customer Support Specialist`,
|
||||
`Sales Manager`,
|
||||
`HR Recruiter`,
|
||||
`Business Consultant`,
|
||||
`Financial Planner`,
|
||||
`Network Engineer`,
|
||||
`Content Creator`,
|
||||
`Quality Assurance Tester`,
|
||||
`Public Relations Officer`,
|
||||
`IT Administrator`,
|
||||
`Compliance Officer`,
|
||||
`Event Planner`,
|
||||
`Legal Counsel`,
|
||||
`Training Coordinator`,
|
||||
];
|
||||
|
||||
const STATUS = ['active', 'pending', 'banned'];
|
||||
|
||||
async function partyUser() {
|
||||
const alice = await prisma.partyUser.upsert({
|
||||
where: { email: 'alice@prisma.io' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'alice@prisma.io',
|
||||
name: 'Alice',
|
||||
username: 'pualice',
|
||||
password: 'Aa12345678',
|
||||
emailVerified: new Date(),
|
||||
phoneNumber: '+85291234567',
|
||||
company: 'helloworld company',
|
||||
status: STATUS[0],
|
||||
role: ROLE[0],
|
||||
isVerified: true,
|
||||
sex: 'F',
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.partyUser.upsert({
|
||||
where: { email: 'demo@minimals.cc' },
|
||||
update: {},
|
||||
create: {
|
||||
email: 'demo@minimals.cc',
|
||||
password: '@2Minimal',
|
||||
//
|
||||
username: 'pudemo',
|
||||
name: 'Demo',
|
||||
emailVerified: new Date(),
|
||||
phoneNumber: '+85291234568',
|
||||
company: 'helloworld company',
|
||||
status: STATUS[1],
|
||||
role: ROLE[1],
|
||||
isVerified: true,
|
||||
avatarUrl: 'https://images.unsplash.com/photo-1619970096024-c7b438a3b82a',
|
||||
rank: 'user',
|
||||
sex: 'M',
|
||||
},
|
||||
});
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const CJK_LOCALES = {
|
||||
en: enFaker,
|
||||
zh: zhFaker,
|
||||
ja: jaFaker,
|
||||
ko: koFaker,
|
||||
tw: twFaker,
|
||||
};
|
||||
|
||||
function getRandomCJKFaker() {
|
||||
const locales = Object.keys(CJK_LOCALES);
|
||||
const randomKey = locales[Math.floor(Math.random() * locales.length)] as keyof typeof CJK_LOCALES;
|
||||
return CJK_LOCALES[randomKey];
|
||||
}
|
||||
|
||||
const randomFaker = getRandomCJKFaker();
|
||||
|
||||
await prisma.partyUser.upsert({
|
||||
where: { email: `party_user${i}@prisma.io` },
|
||||
update: {},
|
||||
create: {
|
||||
email: `party_user${i}@prisma.io`,
|
||||
name: `Party Dummy ${i}`,
|
||||
username: `pu${i.toString()}`,
|
||||
password: 'Aa12345678',
|
||||
emailVerified: new Date(),
|
||||
phoneNumber: `+8529123456${i.toString()}`,
|
||||
company: randomFaker.company.name(),
|
||||
role: ROLE[Math.floor(Math.random() * ROLE.length)],
|
||||
status: STATUS[Math.floor(Math.random() * STATUS.length)],
|
||||
isVerified: true,
|
||||
sex: i % 2 ? 'F' : 'M',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('seed partyUser done');
|
||||
}
|
||||
|
||||
const partyUserSeed = partyUser()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export { partyUserSeed };
|
@@ -5,13 +5,14 @@ yarn --dev
|
||||
clear
|
||||
|
||||
while true; do
|
||||
yarn db:push
|
||||
|
||||
yarn seed
|
||||
|
||||
yarn db:studio &
|
||||
|
||||
yarn dev
|
||||
npx nodemon --ext ts,tsx,prisma --exec "yarn dev"
|
||||
# npx nodemon --ext ts,tsx,prisma --exec "yarn db:push && yarn seed && yarn dev"
|
||||
# yarn dev
|
||||
|
||||
killall node
|
||||
killall yarn
|
||||
|
||||
echo "restarting..."
|
||||
sleep 1
|
||||
|
9
03_source/cms_backend/scripts/01_db_push.sh
Executable file
9
03_source/cms_backend/scripts/01_db_push.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
yarn --dev
|
||||
|
||||
clear
|
||||
|
||||
yarn db:push && yarn seed
|
||||
|
||||
echo "done"
|
@@ -2,6 +2,8 @@
|
||||
|
||||
set -x
|
||||
|
||||
killall node
|
||||
killall yarn
|
||||
rm -rf ./**/*Zone.Identifier
|
||||
|
||||
# yarn db:push
|
||||
|
14
03_source/cms_backend/src/app/api/_PROMPTS/clone_srevice.md
Normal file
14
03_source/cms_backend/src/app/api/_PROMPTS/clone_srevice.md
Normal file
@@ -0,0 +1,14 @@
|
||||
Hi,
|
||||
|
||||
i copied from
|
||||
`03_source/cms_backend/src/app/api/party-event`
|
||||
to
|
||||
`03_source/cms_backend/src/app/api/party-order`
|
||||
|
||||
with knowledge in `schema.prisma` file, and take a look into the sibling files in the same directory.
|
||||
|
||||
i want you to update `03_source/cms_backend/src/app/api/party-order` content to handle `party-order` (the purchase order of the party)
|
||||
`party-order.service.ts` is already prepared and you can use it
|
||||
please maintain same format and level of detail when you edit.
|
||||
|
||||
thanks.
|
@@ -1,5 +1,7 @@
|
||||
###
|
||||
|
||||
# username and password ok
|
||||
|
||||
POST http://localhost:7272/api/auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
@@ -9,7 +11,9 @@ content-type: application/json
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# There is no user corresponding to the email address.
|
||||
|
||||
POST http://localhost:7272/api/auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
@@ -19,7 +23,9 @@ content-type: application/json
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# Wrong password
|
||||
|
||||
POST http://localhost:7272/api/auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
|
@@ -0,0 +1,58 @@
|
||||
// src/app/api/product/createEvent/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// create product to db
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
//
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { getEventItemById } from 'src/app/services/eventItem.service';
|
||||
import { getPartyUserByEmail } from 'src/app/services/party-user.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* POST - Events
|
||||
***************************************
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
// logger('[Event] list', events.length);
|
||||
const { data } = await req.json();
|
||||
const { eventItemId, email } = data;
|
||||
|
||||
try {
|
||||
const eventItem = await getEventItemById(eventItemId);
|
||||
const partyUser = await getPartyUserByEmail(email);
|
||||
|
||||
if (partyUser) {
|
||||
if (eventItem && eventItem?.joinMembers) {
|
||||
const foundJoined = _.find(eventItem.joinMembers, { email });
|
||||
|
||||
if (foundJoined) {
|
||||
console.log('user joined event already, skipping');
|
||||
} else {
|
||||
const { sex } = partyUser;
|
||||
eventItem.joinMembers.push({ email, sex });
|
||||
|
||||
await prisma?.eventItem.update({
|
||||
where: { id: eventItem.id },
|
||||
data: { joinMembers: JSON.parse(JSON.stringify(eventItem.joinMembers)) },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response({ result: 'joined' }, STATUS.OK);
|
||||
} catch (error) {
|
||||
console.log({ hello: 'world', data });
|
||||
return handleError('Event - Create', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
###
|
||||
|
||||
# username and password ok
|
||||
|
||||
POST http://localhost:7272/api/event/partyUserJoinEvent
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"data": {
|
||||
"eventItemId": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01",
|
||||
"email": "alice@prisma.io"
|
||||
}
|
||||
}
|
35
03_source/cms_backend/src/app/api/party-event/_GUIDELINES.md
Normal file
35
03_source/cms_backend/src/app/api/party-event/_GUIDELINES.md
Normal file
@@ -0,0 +1,35 @@
|
||||
<!-- AI: please maintain same format and level of detail when edit this file -->
|
||||
|
||||
# GUIDELINE
|
||||
|
||||
- Party Event API endpoint for managing party events
|
||||
- Handles CRUD operations for party events
|
||||
- Follows single file for single db table/collection pattern
|
||||
|
||||
## `route.ts`
|
||||
|
||||
Handles HTTP methods:
|
||||
|
||||
- `GET` - Retrieve party events
|
||||
- `POST` - Create new party event
|
||||
- `PUT` - Update existing party event
|
||||
- `DELETE` - Remove party event
|
||||
|
||||
## `test.http`
|
||||
|
||||
Contains test requests for:
|
||||
|
||||
- Listing all party events
|
||||
- Creating new party event
|
||||
- Updating existing party event
|
||||
- Deleting party event
|
||||
|
||||
## `../../services/party-event.service.ts`
|
||||
|
||||
Party Event CRUD operations:
|
||||
|
||||
`listPartyEvents` - List all party events with optional filters
|
||||
`getPartyEvent` - Get single party event by ID
|
||||
`createNewPartyEvent` - Create new party event record
|
||||
`updatePartyEvent` - Update existing party event by ID
|
||||
`deletePartyEvent` - Delete party event by ID
|
@@ -0,0 +1,40 @@
|
||||
// src/app/api/party-event/create/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// Create new party event in db
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { isDev } from 'src/constants';
|
||||
import { createEvent } from 'src/app/services/party-event.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* POST - Create PartyEvent
|
||||
*************************************** */
|
||||
export async function POST(req: NextRequest) {
|
||||
const { partyEventData } = await req.json();
|
||||
|
||||
try {
|
||||
if (isDev) {
|
||||
console.log({ partyEventData });
|
||||
}
|
||||
|
||||
const created = await createEvent(partyEventData);
|
||||
|
||||
if (isDev) {
|
||||
console.log('Event created successfully');
|
||||
}
|
||||
|
||||
return response(created, STATUS.OK);
|
||||
} catch (error) {
|
||||
console.error('Error creating event:', { partyEventData });
|
||||
return handleError('PartyEvent - Create', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
###
|
||||
|
||||
POST http://localhost:7272/api/party-event/create
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyEventData": {
|
||||
"title": "Summer Music Festival",
|
||||
"description": "Annual summer music festival featuring local bands and artists",
|
||||
"startDate": "2024-07-15T18:00:00Z",
|
||||
"endDate": "2024-07-15T23:00:00Z",
|
||||
"location": "Central Park, Hong Kong",
|
||||
"coverUrl": "",
|
||||
"images": [
|
||||
"data:image/png;base64,C",
|
||||
"data:image/png;base64,C"
|
||||
],
|
||||
"tags": [
|
||||
"Music",
|
||||
"Festival"
|
||||
],
|
||||
"status": "upcoming",
|
||||
"capacity": 500,
|
||||
"price": 150.00,
|
||||
"organizer": "HK Music Society",
|
||||
"category": "Music",
|
||||
"isFeatured": true,
|
||||
"registrationDeadline": "2024-07-10T00:00:00Z",
|
||||
"requirements": "Age 18+",
|
||||
"schedule": "18:00 Doors open\n19:00 First performance\n21:00 Main act",
|
||||
"speakers": ["DJ Lee", "Band XYZ"],
|
||||
"sponsors": ["HK Radio", "Music Magazine"]
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { deleteEvent } from 'src/app/services/party-event.service';
|
||||
|
||||
/** **************************************
|
||||
* PATCH - Delete PartyEvent
|
||||
*************************************** */
|
||||
export async function PATCH(req: NextRequest) {
|
||||
try {
|
||||
const { partyEventId } = await req.json();
|
||||
|
||||
if (!partyEventId) throw new Error('partyEventId cannot be null');
|
||||
|
||||
await deleteEvent(partyEventId);
|
||||
|
||||
return response({ partyEventId }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyEvent - Delete', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
###
|
||||
|
||||
PATCH http://localhost:7272/api/party-event/delete
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyEventId": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01"
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
// src/app/api/party-event/details/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// Get party event from db by id
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { L_INFO, L_ERROR } from 'src/constants';
|
||||
import { getEvent } from 'src/app/services/party-event.service';
|
||||
import { createAppLog } from 'src/app/services/app-log.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
**************************************
|
||||
* GET PartyEvent detail
|
||||
***************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
|
||||
// RULES: eventId must exist
|
||||
const partyEventId = searchParams.get('partyEventId');
|
||||
if (!partyEventId) {
|
||||
return response({ message: 'PartyEvent ID is required!' }, STATUS.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// NOTE: eventId confirmed exist, run below
|
||||
const partyEvent = await getEvent(partyEventId);
|
||||
|
||||
if (!partyEvent) {
|
||||
return response({ message: 'PartyEvent not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
logger('[PartyEvent] details', partyEvent.id);
|
||||
|
||||
createAppLog(L_INFO, 'Get event detail OK', debug);
|
||||
|
||||
return response({ partyEvent }, STATUS.OK);
|
||||
} catch (error) {
|
||||
createAppLog(L_ERROR, 'event detail error', debug);
|
||||
|
||||
return handleError('PartyEvent - Get details', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
###
|
||||
|
||||
# Get details for a specific party event
|
||||
GET http://localhost:7272/api/party-event/details?partyEventId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01
|
||||
|
||||
###
|
||||
|
||||
# Alternative format with different ID
|
||||
GET http://localhost:7272/api/party-event/details?eventId=evt_987654321
|
@@ -0,0 +1,16 @@
|
||||
import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* GET - helloworld
|
||||
***************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
return response({ helloworld: 'party-event' }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Helloworld - Get all', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
###
|
||||
GET /api/party-event/helloworld HTTP/1.1
|
||||
Host: localhost:7272
|
||||
|
38
03_source/cms_backend/src/app/api/party-event/list/route.ts
Normal file
38
03_source/cms_backend/src/app/api/party-event/list/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// src/app/api/party-event/list/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// List all party events from db
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { L_INFO, L_ERROR } from 'src/constants';
|
||||
import { createAppLog } from 'src/app/services/app-log.service';
|
||||
import { listEvents } from 'src/app/services/party-event.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - PartyEvents list
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const partyEvents = await listEvents();
|
||||
|
||||
createAppLog(L_INFO, 'party-event list ok', {});
|
||||
|
||||
return response({ partyEvents }, STATUS.OK);
|
||||
} catch (error) {
|
||||
createAppLog(L_ERROR, 'party-event list error', debug);
|
||||
|
||||
return handleError('PartyEvent - Get list', error);
|
||||
}
|
||||
}
|
14
03_source/cms_backend/src/app/api/party-event/list/test.http
Normal file
14
03_source/cms_backend/src/app/api/party-event/list/test.http
Normal file
@@ -0,0 +1,14 @@
|
||||
###
|
||||
|
||||
# Basic list all party events
|
||||
GET http://localhost:7272/api/party-event/list
|
||||
|
||||
###
|
||||
|
||||
# List upcoming party events
|
||||
GET http://localhost:7272/api/party-event/list?status=upcoming
|
||||
|
||||
###
|
||||
|
||||
# List featured party events
|
||||
GET http://localhost:7272/api/party-event/list?isFeatured=true
|
75
03_source/cms_backend/src/app/api/party-event/route.ts
Normal file
75
03_source/cms_backend/src/app/api/party-event/route.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { listEvents, deleteEvent, updateEvent, createEvent } from 'src/app/services/party-event.service';
|
||||
|
||||
/**
|
||||
**************************************
|
||||
* GET - PartyEvent
|
||||
***************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
const events = await listEvents();
|
||||
return response(events, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyEvent - Get list', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* POST - Create PartyEvent
|
||||
***************************************
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const OPERATION = 'PartyEvent - Create';
|
||||
const { data } = await req.json();
|
||||
|
||||
try {
|
||||
const event = await createEvent(data);
|
||||
return response(OPERATION, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError(OPERATION, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* PUT - Update PartyEvent
|
||||
***************************************
|
||||
*/
|
||||
export async function PUT(req: NextRequest) {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const eventId = searchParams.get('eventId');
|
||||
const { data } = await req.json();
|
||||
|
||||
try {
|
||||
if (!eventId) throw new Error('eventId cannot be null');
|
||||
|
||||
const result = await updateEvent(eventId, data);
|
||||
return response(result, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyEvent - Update', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* DELETE - Delete PartyEvent
|
||||
***************************************
|
||||
*/
|
||||
export async function DELETE(req: NextRequest) {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const eventId = searchParams.get('eventId');
|
||||
|
||||
try {
|
||||
if (!eventId) throw new Error('eventId cannot be null');
|
||||
|
||||
await deleteEvent(eventId);
|
||||
return response({ success: true }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyEvent - Delete', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
import { IEventItem } from '../update/route';
|
||||
|
||||
// import { searchEvents } from 'src/app/services/party-event.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Search PartyEvents
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const query = searchParams.get('query')?.trim().toLowerCase();
|
||||
|
||||
if (!query) {
|
||||
return response({ results: [] }, STATUS.OK);
|
||||
}
|
||||
|
||||
const results: IEventItem[] = [];
|
||||
// TODO: search party event not implemented
|
||||
console.log('search party event not implemented');
|
||||
// const results = await searchEvents(query);
|
||||
|
||||
logger('[PartyEvent] search-results', results?.length);
|
||||
|
||||
return response({ results }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyEvent - Get search', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
###
|
||||
|
||||
# Search party events by title
|
||||
GET http://localhost:7272/api/party-event/search?query=Music
|
||||
|
||||
###
|
||||
|
||||
# Search party events by location
|
||||
GET http://localhost:7272/api/party-event/search?query=Central+Park
|
||||
|
||||
###
|
||||
|
||||
# Search party events by tag
|
||||
GET http://localhost:7272/api/party-event/search?query=Festival
|
||||
|
||||
###
|
||||
|
||||
# Combined search with multiple parameters
|
||||
GET http://localhost:7272/api/party-event/search?query=Summer&location=Hong+Kong&category=Music
|
||||
|
||||
###
|
||||
|
||||
# No results expected
|
||||
GET http://localhost:7272/api/party-event/search?query=zzzzzz
|
@@ -0,0 +1,57 @@
|
||||
// src/app/api/party-event/update/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// Update party event in db by id
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { updateEvent } from 'src/app/services/party-event.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* PUT - Update PartyEvent
|
||||
*************************************** */
|
||||
export async function PUT(req: NextRequest) {
|
||||
const { partyEventData } = await req.json();
|
||||
const { id } = partyEventData;
|
||||
|
||||
if (!id) return response({ message: 'id not found' }, STATUS.ERROR);
|
||||
|
||||
try {
|
||||
const result = await updateEvent(id, partyEventData);
|
||||
|
||||
return response({ result }, STATUS.OK);
|
||||
} catch (error) {
|
||||
console.error('Error updating event:', { partyEventData });
|
||||
return handleError('PartyEvent - Update', error);
|
||||
}
|
||||
}
|
||||
|
||||
export type IEventItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
location: string;
|
||||
coverUrl: string;
|
||||
images: string[];
|
||||
tags: string[];
|
||||
status: string;
|
||||
capacity: number;
|
||||
price: number;
|
||||
organizer: string;
|
||||
category: string;
|
||||
isFeatured: boolean;
|
||||
registrationDeadline: Date;
|
||||
requirements: string[];
|
||||
schedule: string;
|
||||
speakers: string[];
|
||||
sponsors: string[];
|
||||
};
|
@@ -0,0 +1,31 @@
|
||||
###
|
||||
|
||||
PUT http://localhost:7272/api/party-event/update
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyEventData": {
|
||||
"id":"e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01",
|
||||
"title": "Summer Music Festival 111",
|
||||
"name": "Summer Music Festival 111",
|
||||
"description": "Annual summer music festival featuring local bands and artists",
|
||||
"startDate": "2024-07-15T18:00:00Z",
|
||||
"endDate": "2024-07-15T23:00:00Z",
|
||||
"location": "Central Park, Hong Kong",
|
||||
"coverUrl": "",
|
||||
"images": [ "data:image/png;base64,C", "data:image/png;base64,C" ],
|
||||
"tags": [ "Music", "Festival" ],
|
||||
"status": "upcoming",
|
||||
"capacity": 500,
|
||||
"price": 150.00,
|
||||
"organizer": "HK Music Society",
|
||||
"category": "Music",
|
||||
"isFeatured": true,
|
||||
"registrationDeadline": "2024-07-10T00:00:00Z",
|
||||
"requirements": "Age 18+",
|
||||
"schedule": "18:00 Doors open\n19:00 First performance\n21:00 Main act",
|
||||
"speakers": ["DJ Lee", "Band XYZ"],
|
||||
"sponsors": ["HK Radio", "Music Magazine"],
|
||||
"reviews":[]
|
||||
}
|
||||
}
|
35
03_source/cms_backend/src/app/api/party-order/_GUIDELINES.md
Normal file
35
03_source/cms_backend/src/app/api/party-order/_GUIDELINES.md
Normal file
@@ -0,0 +1,35 @@
|
||||
<!-- AI: please maintain same format and level of detail when edit this file -->
|
||||
|
||||
# GUIDELINE
|
||||
|
||||
- Party Order API endpoint for managing party orders
|
||||
- Handles CRUD operations for party orders
|
||||
- Follows single file for single db table/collection pattern
|
||||
|
||||
## `route.ts`
|
||||
|
||||
Handles HTTP methods:
|
||||
|
||||
- `GET` - Retrieve party orders
|
||||
- `POST` - Create new party order
|
||||
- `PUT` - Update existing party order
|
||||
- `DELETE` - Remove party order
|
||||
|
||||
## `test.http`
|
||||
|
||||
Contains test requests for:
|
||||
|
||||
- Listing all party orders
|
||||
- Creating new party order
|
||||
- Updating existing party order
|
||||
- Deleting party order
|
||||
|
||||
## `../../services/party-order.service.ts`
|
||||
|
||||
Party Order CRUD operations:
|
||||
|
||||
`listPartyOrders` - List all party orders with optional filters
|
||||
`getPartyOrder` - Get single party order by ID
|
||||
`createNewPartyOrder` - Create new party order record
|
||||
`updatePartyOrder` - Update existing party order by ID
|
||||
`deletePartyOrder` - Delete party order by ID
|
@@ -0,0 +1,40 @@
|
||||
// src/app/api/party-order/create/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// Create new party order in db
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { isDev } from 'src/constants';
|
||||
import { createOrder } from 'src/app/services/party-order.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* POST - Create PartyOrder
|
||||
*************************************** */
|
||||
export async function POST(req: NextRequest) {
|
||||
const { partyOrderData } = await req.json();
|
||||
|
||||
try {
|
||||
if (isDev) {
|
||||
console.log({ partyOrderData });
|
||||
}
|
||||
|
||||
const created = await createOrder(partyOrderData);
|
||||
|
||||
if (isDev) {
|
||||
console.log('Order created successfully');
|
||||
}
|
||||
|
||||
return response(created, STATUS.OK);
|
||||
} catch (error) {
|
||||
console.error('Error creating order:', { partyOrderData });
|
||||
return handleError('PartyOrder - Create', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
###
|
||||
|
||||
POST http://localhost:7272/api/party-order/create
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyOrderData": {
|
||||
"taxes": 15.00,
|
||||
"status": "pending",
|
||||
"shipping": 10.00,
|
||||
"discount": 20.00,
|
||||
"subtotal": 290.00,
|
||||
"orderNumber": "ORD_20231115001",
|
||||
"totalAmount": 300.00,
|
||||
"totalQuantity": 2.0,
|
||||
"history": "{\"actions\":[{\"timestamp\":\"2023-11-15T10:00:00Z\",\"action\":\"order_created\"}]}",
|
||||
"payment": "{\"method\":\"credit_card\",\"status\":\"unpaid\",\"transaction_id\":\"txn_123456\"}",
|
||||
"customer": "{\"name\":\"John Doe\",\"email\":\"john.doe@example.com\",\"phone\":\"+1234567890\"}",
|
||||
"delivery": "{\"method\":\"courier\",\"estimated_delivery\":\"2023-11-18\"}",
|
||||
"items": [
|
||||
{
|
||||
"id": "ticket_001",
|
||||
"name": "General Admission",
|
||||
"quantity": 2,
|
||||
"price": 150.00
|
||||
}
|
||||
],
|
||||
"shippingAddress": "{\"street\":\"123 Main St\",\"city\":\"New York\",\"state\":\"NY\",\"zip\":\"10001\",\"country\":\"USA\"}"
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { deleteOrder } from 'src/app/services/party-order.service';
|
||||
|
||||
/** **************************************
|
||||
* PATCH - Delete PartyOrder
|
||||
*************************************** */
|
||||
export async function PATCH(req: NextRequest) {
|
||||
try {
|
||||
const { partyOrderId } = await req.json();
|
||||
|
||||
if (!partyOrderId) throw new Error('orderId cannot be null');
|
||||
|
||||
await deleteOrder(partyOrderId);
|
||||
|
||||
return response({ partyOrderId }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyOrder - Delete', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
###
|
||||
|
||||
PATCH http://localhost:7272/api/party-order/delete
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyOrderId": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b02"
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
// src/app/api/party-order/details/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// Get party order from db by id
|
||||
//
|
||||
// RULES:
|
||||
// Do not modify the format and level of details thanks.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { L_INFO, L_ERROR } from 'src/constants';
|
||||
import { getOrder } from 'src/app/services/party-order.service';
|
||||
import { createAppLog } from 'src/app/services/app-log.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
**************************************
|
||||
* GET PartyOrder detail
|
||||
***************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
|
||||
// RULES: partyOrderId must exist
|
||||
const partyOrderId = searchParams.get('partyOrderId');
|
||||
if (!partyOrderId) {
|
||||
return response({ message: 'Order ID is required!' }, STATUS.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// NOTE: partyOrderId confirmed exist, run below
|
||||
const partyOrder = await getOrder(partyOrderId);
|
||||
|
||||
if (!partyOrder) {
|
||||
return response({ message: 'Order not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
logger('[PartyOrder] details', partyOrder.id);
|
||||
|
||||
createAppLog(L_INFO, 'Get order detail OK', debug);
|
||||
|
||||
return response({ partyOrder }, STATUS.OK);
|
||||
} catch (error) {
|
||||
createAppLog(L_ERROR, 'order detail error', debug);
|
||||
|
||||
return handleError('PartyOrder - Get details', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
###
|
||||
|
||||
# Get details for a specific party order
|
||||
GET http://localhost:7272/api/party-order/details?partyOrderId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b13
|
||||
|
||||
###
|
||||
|
||||
# Alternative format with different ID
|
||||
GET http://localhost:7272/api/party-order/details?id=ord_987654321
|
@@ -0,0 +1,16 @@
|
||||
import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* GET - helloworld
|
||||
***************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
return response({ helloworld: 'party-order' }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Helloworld - Get all', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
###
|
||||
GET http://localhost:7272/api/party-order/helloworld
|
38
03_source/cms_backend/src/app/api/party-order/list/route.ts
Normal file
38
03_source/cms_backend/src/app/api/party-order/list/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// src/app/api/party-order/list/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// List all party orders from db
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { L_INFO, L_ERROR } from 'src/constants';
|
||||
import { createAppLog } from 'src/app/services/app-log.service';
|
||||
import { listPartyOrders } from 'src/app/services/party-order.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - PartyOrders list
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const partyOrders = await listPartyOrders();
|
||||
|
||||
createAppLog(L_INFO, 'party-order list ok', {});
|
||||
|
||||
return response({ partyOrders }, STATUS.OK);
|
||||
} catch (error) {
|
||||
createAppLog(L_ERROR, 'party-order list error', debug);
|
||||
|
||||
return handleError('PartyOrder - Get list', error);
|
||||
}
|
||||
}
|
19
03_source/cms_backend/src/app/api/party-order/list/test.http
Normal file
19
03_source/cms_backend/src/app/api/party-order/list/test.http
Normal file
@@ -0,0 +1,19 @@
|
||||
###
|
||||
|
||||
# Basic list all orders
|
||||
GET http://localhost:7272/api/party-order/list
|
||||
|
||||
###
|
||||
|
||||
# List orders by status
|
||||
GET http://localhost:7272/api/party-order/list?status=completed
|
||||
|
||||
###
|
||||
|
||||
# List orders by user
|
||||
GET http://localhost:7272/api/party-order/list?userId=usr_987654321
|
||||
|
||||
###
|
||||
|
||||
# List orders by payment status
|
||||
GET http://localhost:7272/api/party-order/list?paymentStatus=paid
|
75
03_source/cms_backend/src/app/api/party-order/route.ts
Normal file
75
03_source/cms_backend/src/app/api/party-order/route.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { listPartyOrders, deleteOrder, updateOrder, createOrder } from 'src/app/services/party-order.service';
|
||||
|
||||
/**
|
||||
**************************************
|
||||
* GET - PartyOrder
|
||||
***************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
const orders = await listPartyOrders();
|
||||
return response(orders, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyOrder - Get list', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* POST - Create PartyOrder
|
||||
***************************************
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const OPERATION = 'PartyOrder - Create';
|
||||
const { data } = await req.json();
|
||||
|
||||
try {
|
||||
const order = await createOrder(data);
|
||||
return response(OPERATION, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError(OPERATION, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* PUT - Update PartyOrder
|
||||
***************************************
|
||||
*/
|
||||
export async function PUT(req: NextRequest) {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const orderId = searchParams.get('orderId');
|
||||
const { data } = await req.json();
|
||||
|
||||
try {
|
||||
if (!orderId) throw new Error('orderId cannot be null');
|
||||
|
||||
const result = await updateOrder(orderId, data);
|
||||
return response(result, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyOrder - Update', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* DELETE - Delete PartyOrder
|
||||
***************************************
|
||||
*/
|
||||
export async function DELETE(req: NextRequest) {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const orderId = searchParams.get('orderId');
|
||||
|
||||
try {
|
||||
if (!orderId) throw new Error('orderId cannot be null');
|
||||
|
||||
await deleteOrder(orderId);
|
||||
return response({ success: true }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyOrder - Delete', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import type { IOrderItem } from '../update/route';
|
||||
|
||||
// import { searchEvents } from 'src/app/services/party-event.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Search PartyEvents
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const query = searchParams.get('query')?.trim().toLowerCase();
|
||||
|
||||
if (!query) {
|
||||
return response({ results: [] }, STATUS.OK);
|
||||
}
|
||||
|
||||
const results: IOrderItem[] = [];
|
||||
// TODO: search party event not implemented
|
||||
console.log('search party event not implemented');
|
||||
// const results = await searchEvents(query);
|
||||
|
||||
logger('[PartyEvent] search-results', results?.length);
|
||||
|
||||
return response({ results }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyEvent - Get search', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
###
|
||||
|
||||
# Search party events by title
|
||||
GET http://localhost:7272/api/party-event/search?query=Music
|
||||
|
||||
###
|
||||
|
||||
# Search party events by location
|
||||
GET http://localhost:7272/api/party-event/search?query=Central+Park
|
||||
|
||||
###
|
||||
|
||||
# Search party events by tag
|
||||
GET http://localhost:7272/api/party-event/search?query=Festival
|
||||
|
||||
###
|
||||
|
||||
# Combined search with multiple parameters
|
||||
GET http://localhost:7272/api/party-event/search?query=Summer&location=Hong+Kong&category=Music
|
||||
|
||||
###
|
||||
|
||||
# No results expected
|
||||
GET http://localhost:7272/api/party-event/search?query=zzzzzz
|
@@ -0,0 +1,51 @@
|
||||
// src/app/api/party-order/update/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// Update party order in db by id
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { updateOrder } from 'src/app/services/party-order.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* PUT - Update PartyOrder
|
||||
*************************************** */
|
||||
export async function PUT(req: NextRequest) {
|
||||
const { orderData } = await req.json();
|
||||
const { id } = orderData;
|
||||
|
||||
if (!id) return response({ message: 'id not found' }, STATUS.ERROR);
|
||||
|
||||
try {
|
||||
const result = await updateOrder(id, orderData);
|
||||
|
||||
return response({ result }, STATUS.OK);
|
||||
} catch (error) {
|
||||
console.error('Error updating order:', { orderData });
|
||||
return handleError('PartyOrder - Update', error);
|
||||
}
|
||||
}
|
||||
|
||||
export type IOrderItem = {
|
||||
id: string;
|
||||
eventId: string;
|
||||
userId: string;
|
||||
status: string;
|
||||
paymentStatus: string;
|
||||
totalAmount: number;
|
||||
items: {
|
||||
id: string;
|
||||
name: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
}[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
@@ -0,0 +1,51 @@
|
||||
###
|
||||
|
||||
PUT http://localhost:7272/api/party-order/update
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"orderData": {
|
||||
"id":"e99f09a7-dd88-49d5-b1c8-1daf80c2d7b02",
|
||||
"taxes": 10.50,
|
||||
"status": "completed",
|
||||
"shipping": 5.00,
|
||||
"discount": 20.00,
|
||||
"subtotal": 290.00,
|
||||
"orderNumber": "ORD-2023-001",
|
||||
"totalAmount": 300.00,
|
||||
"totalQuantity": 2,
|
||||
"history": {
|
||||
"payment": "2023-01-01",
|
||||
"status_changes": [
|
||||
"pending",
|
||||
"paid"
|
||||
]
|
||||
},
|
||||
"payment": {
|
||||
"method": "credit_card",
|
||||
"card_last4": "4321"
|
||||
},
|
||||
"customer": {
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com"
|
||||
},
|
||||
"delivery": {
|
||||
"method": "express",
|
||||
"tracking_number": "TRK123456"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": "ticket_001",
|
||||
"name": "General Admission",
|
||||
"quantity": 2,
|
||||
"price": 150.00,
|
||||
"status": "used"
|
||||
}
|
||||
],
|
||||
"shippingAddress": {
|
||||
"street": "123 Main St",
|
||||
"city": "New York",
|
||||
"zip": "10001"
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,76 @@
|
||||
// src/app/api/party-user-auth/me/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Handle authentication for party users via JWT
|
||||
// - Verify and decode JWT tokens
|
||||
// - Return current authenticated party user details
|
||||
// - Log all access attempts (success/failure)
|
||||
// - Validate token structure and user existence
|
||||
//
|
||||
import type { NextRequest } from 'next/server';
|
||||
import type { PartyUser } from '@prisma/client';
|
||||
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
import { verify } from 'src/utils/jwt';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { JWT_SECRET } from 'src/_mock/_auth';
|
||||
import { createAccessLog } from 'src/app/services/access-log.service';
|
||||
import { getPartyUserById } from 'src/app/services/party-user.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
* You should use a real database
|
||||
* You should hash the password before saving to database
|
||||
* You should not save the password in the database
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
const ERR_USER_TOKEN_CHECK_FAILED = 'user token check failed';
|
||||
const ERR_INVALID_AUTH_TOKEN = 'Invalid authorization token';
|
||||
const ERR_USER_ID_NOT_FOUND = 'userId not found';
|
||||
const ERR_AUTHORIZATION_TOKEN_MISSING_OR_INVALID = 'Authorization token missing or invalid';
|
||||
|
||||
const USER_TOKEN_OK = 'user token check ok';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const headersList = headers();
|
||||
const authorization = headersList.get('authorization');
|
||||
|
||||
if (!authorization || !authorization.startsWith('Bearer ')) {
|
||||
return response({ message: ERR_AUTHORIZATION_TOKEN_MISSING_OR_INVALID }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const accessToken = `${authorization}`.split(' ')[1];
|
||||
const data = await verify(accessToken, JWT_SECRET);
|
||||
|
||||
if (data.userId) {
|
||||
// TODO: remove me
|
||||
// const currentUser = _users.find((user) => user.id === data.userId);
|
||||
// const currentUser: User | null = await getUserById(data.userId);
|
||||
const currentUser: PartyUser | null = await getPartyUserById(data.userId);
|
||||
|
||||
if (!currentUser) {
|
||||
createAccessLog('', ERR_USER_TOKEN_CHECK_FAILED, debug);
|
||||
|
||||
return response({ message: ERR_INVALID_AUTH_TOKEN }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
createAccessLog(currentUser.id, USER_TOKEN_OK, debug);
|
||||
|
||||
return response({ user: currentUser }, STATUS.OK);
|
||||
} else {
|
||||
return response({ message: ERR_USER_ID_NOT_FOUND }, STATUS.ERROR);
|
||||
}
|
||||
} catch (error) {
|
||||
return handleError('[Auth] - Me', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
// Test cases for Party User Authentication endpoints
|
||||
// Tests both successful and error scenarios
|
||||
// Environment: http://localhost:7272
|
||||
// Expected responses:
|
||||
// - 200 OK with user data for valid tokens
|
||||
// - 401 Unauthorized for invalid/missing tokens
|
||||
// - 400 Bad Request for invalid credentials
|
||||
|
||||
###
|
||||
|
||||
# username and password ok
|
||||
|
||||
GET http://localhost:7272/api/party-user-auth/me
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWMwdWo4azIwMDBxM2Y1eTZlNXJzejRxIiwiaWF0IjoxNzUwMjEzOTkwLCJleHAiOjE3NTE0MjM1OTB9.MoKv3Nmrp_blE0jQ1rG1WyQ_TrJeF7kSe5xfHrF8b64
|
||||
|
||||
###
|
||||
|
||||
# There is no user corresponding to the email address.
|
||||
|
||||
POST http://localhost:7272/api/party-user-auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals.cc",
|
||||
"password": "@2Minimal"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# Wrong password
|
||||
|
||||
POST http://localhost:7272/api/party-user-auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals.cc",
|
||||
"password": "@2Min111imal"
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
/**
|
||||
* Flattens a Next.js request object into a plain object of headers
|
||||
* @param {NextRequest} req - The Next.js request object
|
||||
* @returns {Record<string, string>} An object containing all request headers
|
||||
*/
|
||||
export function flattenNextjsRequest(req: NextRequest) {
|
||||
return Object.fromEntries(req.headers.entries());
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
// src/app/api/party-user-auth/sign-in/route1.ts
|
||||
//
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { sign } from 'src/utils/jwt';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth';
|
||||
import { createAccessLog } from 'src/app/services/access-log.service';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
import { flattenNextjsRequest } from './flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
* You should use a real database
|
||||
* You should hash the password before saving to database
|
||||
* You should not save the password in the database
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
const ERR_USER_NOT_FOUND = 'There is no user corresponding to the email address.';
|
||||
const ERR_WRONG_PASSWORD = 'Wrong password';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const { email, password } = await req.json();
|
||||
|
||||
const currentUser = await prisma.partyUser.findFirst({ where: { email } });
|
||||
if (!currentUser) {
|
||||
await createAccessLog('', `user tried login with email ${email}`, { debug });
|
||||
return response({ message: ERR_USER_NOT_FOUND }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (currentUser?.password !== password) {
|
||||
await createAccessLog(currentUser.id, 'user logged with wrong password', { debug });
|
||||
return response({ message: ERR_WRONG_PASSWORD }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const accessToken = await sign({ userId: currentUser?.id }, JWT_SECRET, {
|
||||
expiresIn: JWT_EXPIRES_IN,
|
||||
});
|
||||
|
||||
await createAccessLog(currentUser.id, 'access granted', { debug });
|
||||
|
||||
return response({ user: currentUser, accessToken }, STATUS.OK);
|
||||
} catch (error) {
|
||||
await createAccessLog('', 'attempted login but failed', { debug, error });
|
||||
|
||||
return handleError('Auth - Sign in', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
# REQ0188 frontend party-user
|
||||
|
||||
###
|
||||
|
||||
# username and password ok
|
||||
|
||||
POST http://localhost:7272/api/party-user-auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals.cc",
|
||||
"password": "@2Minimal"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# There is no user corresponding to the email address.
|
||||
|
||||
POST http://localhost:7272/api/party-user-auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals1.cc",
|
||||
"password": "@2Minimal"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# Wrong password
|
||||
|
||||
POST http://localhost:7272/api/party-user-auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals.cc",
|
||||
"password": "@2Min111imal"
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { sign } from 'src/utils/jwt';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _users, JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
* You should use a real database
|
||||
* You should hash the password before saving to database
|
||||
* You should not save the password in the database
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { email, password, firstName, lastName } = await req.json();
|
||||
|
||||
const userExists = _users.find((user) => user.email === email);
|
||||
|
||||
if (userExists) {
|
||||
return response({ message: 'There already exists an account with the given email address.' }, STATUS.CONFLICT);
|
||||
}
|
||||
|
||||
const newUser = {
|
||||
id: _users[0].id,
|
||||
displayName: `${firstName} ${lastName}`,
|
||||
email,
|
||||
password,
|
||||
photoURL: '',
|
||||
phoneNumber: '',
|
||||
country: '',
|
||||
address: '',
|
||||
state: '',
|
||||
city: '',
|
||||
zipCode: '',
|
||||
about: '',
|
||||
role: 'user',
|
||||
isPublic: true,
|
||||
};
|
||||
|
||||
const accessToken = await sign({ userId: newUser.id }, JWT_SECRET, {
|
||||
expiresIn: JWT_EXPIRES_IN,
|
||||
});
|
||||
|
||||
// Push new user to database
|
||||
_users.push(newUser);
|
||||
|
||||
return response({ user: newUser, accessToken }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Auth - Sign up', error);
|
||||
}
|
||||
}
|
60
03_source/cms_backend/src/app/api/party-user/_GUIDELINES.md
Normal file
60
03_source/cms_backend/src/app/api/party-user/_GUIDELINES.md
Normal file
@@ -0,0 +1,60 @@
|
||||
<!-- NOTES to AI: please maintain same format and level of detail when edit this file -->
|
||||
|
||||
# GUIDELINE
|
||||
|
||||
- Party-user Management API endpoint for managing party-user accounts
|
||||
- Handles party-user CRUD operations and profile updates
|
||||
- Follows Next.js API route conventions
|
||||
|
||||
## Endpoints
|
||||
|
||||
### `create/route.ts`
|
||||
|
||||
Creates new party-user account with required details
|
||||
|
||||
### `delete/route.ts`
|
||||
|
||||
Deletes party-user account by ID
|
||||
|
||||
### `details/route.ts`
|
||||
|
||||
Gets detailed party-user information
|
||||
|
||||
### `helloworld/route.ts`
|
||||
|
||||
Simple test endpoint returning "Hello World"
|
||||
|
||||
### `list/route.ts`
|
||||
|
||||
Lists all party-users with pagination support
|
||||
|
||||
### `search/route.ts`
|
||||
|
||||
Searches party-users by name or email
|
||||
|
||||
### `update/route.ts`
|
||||
|
||||
Updates party-user profile information
|
||||
|
||||
## Testing
|
||||
|
||||
Test files are available per endpoint in their respective directories:
|
||||
|
||||
- `create/test.http`
|
||||
- `delete/test.http`
|
||||
- `details/test.http`
|
||||
- `helloworld/test.http`
|
||||
- `list/test.http`
|
||||
- `update/test.http`
|
||||
|
||||
## Related Services
|
||||
|
||||
`../../services/party-user.service.ts` (assumed) would handle:
|
||||
|
||||
`createUser` - Register new party-user account
|
||||
`updateUser` - Update party-user profile
|
||||
`deleteUser` - Remove party-user account
|
||||
`listUsers` - Get paginated party-user list
|
||||
`searchUsers` - Search party-users by criteria
|
||||
`changeUserRole` - Modify party-user permissions
|
||||
`uploadUserImage` - Handle profile picture uploads
|
34
03_source/cms_backend/src/app/api/party-user/create/route.ts
Normal file
34
03_source/cms_backend/src/app/api/party-user/create/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// src/app/api/party-user/create/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// create new party user in db
|
||||
//
|
||||
// RULES:
|
||||
// 1. Validates input data shape
|
||||
// 2. Returns created party user data
|
||||
//
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { createPartyUser } from 'src/app/services/party-user.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* POST - create PartyUser
|
||||
***************************************
|
||||
*/
|
||||
export async function POST(req: NextRequest) {
|
||||
const { partyUserData } = await req.json();
|
||||
|
||||
try {
|
||||
const partyUser = await createPartyUser(partyUserData);
|
||||
|
||||
return response({ partyUser }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyUser - Create', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
###
|
||||
|
||||
POST http://localhost:7272/api/party-user/create
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyUserData": {
|
||||
"name": "Alice 123321",
|
||||
"username": null,
|
||||
"email": "alice@123111321.io",
|
||||
"emailVerified": "2025-06-15T17:47:23.919Z",
|
||||
"password": "Aa12345678",
|
||||
"bucketImage": null,
|
||||
"admin": false,
|
||||
"info": null,
|
||||
"phoneNumber": "+85291234567",
|
||||
"avatarUrl": ""
|
||||
}
|
||||
}
|
35
03_source/cms_backend/src/app/api/party-user/delete/route.ts
Normal file
35
03_source/cms_backend/src/app/api/party-user/delete/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
// src/app/api/party-user/delete/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// delete party user from db by id
|
||||
//
|
||||
// RULES:
|
||||
// 1. Requires valid party user ID
|
||||
// 2. Returns deleted party user ID
|
||||
// 3. Handles errors appropriately
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { deletePartyUser } from 'src/app/services/party-user.service';
|
||||
|
||||
/**
|
||||
**************************************
|
||||
* PATCH - Delete party user
|
||||
***************************************
|
||||
*/
|
||||
|
||||
export async function PATCH(req: NextRequest) {
|
||||
try {
|
||||
const { partyUserId } = await req.json();
|
||||
|
||||
if (!partyUserId) throw new Error('partyUserId cannot be null');
|
||||
|
||||
await deletePartyUser(partyUserId);
|
||||
|
||||
return response({ partyUserId }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyUser - Delete', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
###
|
||||
|
||||
PATCH http://localhost:7272/api/party-user/delete
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyUserId": "cmbxz8t2b000oiigxib3o4jla"
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
// src/app/api/party-user/details/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// get party user details from db by id
|
||||
//
|
||||
// RULES:
|
||||
// 1. Requires valid partyUserId parameter
|
||||
// 2. Returns party user details if found
|
||||
// 3. Handles not found case appropriately
|
||||
// 4. Logs the operation
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { getPartyUser } from 'src/app/services/party-user.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET PartyUser detail
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
|
||||
// RULES: userId must exist
|
||||
const partyUserId = searchParams.get('partyUserId');
|
||||
if (!partyUserId) return response({ message: 'partyUserId is required!' }, STATUS.BAD_REQUEST);
|
||||
|
||||
// NOTE: userId confirmed exist, run below
|
||||
const partyUser = await getPartyUser(partyUserId);
|
||||
|
||||
if (!partyUser) return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
|
||||
|
||||
logger('[User] details', partyUser.id);
|
||||
|
||||
return response({ partyUser }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyUser - Get details', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/party-user/details?partyUserId=cmbxziat6000b715hmhrnwlx2
|
||||
|
@@ -0,0 +1,11 @@
|
||||
import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
return response({ helloworld: 'party-user' }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('GET - helloworld', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/party-user/helloworld
|
33
03_source/cms_backend/src/app/api/party-user/list/route.ts
Normal file
33
03_source/cms_backend/src/app/api/party-user/list/route.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
// src/app/api/party-user/update/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// update existing party user in db
|
||||
//
|
||||
// RULES:
|
||||
// 1. Requires valid party user ID
|
||||
// 2. Validates input data shape
|
||||
//
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { listPartyUsers } from 'src/app/services/party-user.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* GET - Products
|
||||
***************************************
|
||||
*/
|
||||
export async function GET() {
|
||||
try {
|
||||
const partyUsers = await listPartyUsers();
|
||||
|
||||
logger('[User] list', partyUsers.length);
|
||||
|
||||
return response({ partyUsers }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Product - Get list', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
###
|
||||
GET http://localhost:7272/api/party-user/list
|
35
03_source/cms_backend/src/app/api/party-user/search/route.ts
Normal file
35
03_source/cms_backend/src/app/api/party-user/search/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _products } from 'src/_mock/_product';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
/** **************************************
|
||||
* GET - Search products
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const query = searchParams.get('query')?.trim().toLowerCase();
|
||||
|
||||
if (!query) {
|
||||
return response({ results: [] }, STATUS.OK);
|
||||
}
|
||||
|
||||
const products = _products();
|
||||
|
||||
// Accept search by name or sku
|
||||
const results = products.filter(({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query));
|
||||
|
||||
logger('[Product] search-results', results.length);
|
||||
|
||||
return response({ results }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Product - Get search', error);
|
||||
}
|
||||
}
|
88
03_source/cms_backend/src/app/api/party-user/update/route.ts
Normal file
88
03_source/cms_backend/src/app/api/party-user/update/route.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
// src/app/api/party-user/update/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// update existing party user in db
|
||||
//
|
||||
// RULES:
|
||||
// 1. Requires valid party user ID
|
||||
// 2. Validates input data shape
|
||||
// 3. Returns updated party user data
|
||||
// 4. Handles errors appropriately
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { updatePartyUser } from 'src/app/services/party-user.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* PUT - Update PartyUser
|
||||
*************************************** */
|
||||
export async function PUT(req: NextRequest) {
|
||||
// logger('[Product] list', products.length);
|
||||
const { partyUserData } = await req.json();
|
||||
|
||||
try {
|
||||
await updatePartyUser(partyUserData.id, partyUserData);
|
||||
return response({ partyUserData }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('PartyUser - Update', error);
|
||||
}
|
||||
}
|
||||
|
||||
export type IProductItem = {
|
||||
id: string;
|
||||
sku: string;
|
||||
name: string;
|
||||
code: string;
|
||||
price: number;
|
||||
taxes: number;
|
||||
tags: string[];
|
||||
sizes: string[];
|
||||
publish: string;
|
||||
gender: string[];
|
||||
coverUrl: string;
|
||||
images: string[];
|
||||
colors: string[];
|
||||
quantity: number;
|
||||
category: string;
|
||||
available: number;
|
||||
totalSold: number;
|
||||
description: string;
|
||||
totalRatings: number;
|
||||
totalReviews: number;
|
||||
// createdAt: IDateValue;
|
||||
inventoryType: string;
|
||||
subDescription: string;
|
||||
priceSale: number | null;
|
||||
// reviews: IProductReview[];
|
||||
newLabel: {
|
||||
content: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
saleLabel: {
|
||||
content: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
ratings: {
|
||||
name: string;
|
||||
starCount: number;
|
||||
reviewCount: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type IDateValue = string | number | null;
|
||||
|
||||
export type IProductReview = {
|
||||
id: string;
|
||||
name: string;
|
||||
rating: number;
|
||||
comment: string;
|
||||
helpful: number;
|
||||
avatarUrl: string;
|
||||
postedAt: IDateValue;
|
||||
isPurchased: boolean;
|
||||
attachments?: string[];
|
||||
};
|
@@ -0,0 +1,22 @@
|
||||
###
|
||||
|
||||
PUT http://localhost:7272/api/party-user/update
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"partyUserData": {
|
||||
"id": "cmc0cedkx000boln3viy77598",
|
||||
"createdAt": "2025-06-15T17:47:24.547Z",
|
||||
"updatedAt": "2025-06-15T17:47:24.547Z",
|
||||
"name": "Alice 123321",
|
||||
"username": null,
|
||||
"email": "alice@prisma.io",
|
||||
"emailVerified": "2025-06-15T17:47:23.919Z",
|
||||
"password": "Aa12345678",
|
||||
"image": null,
|
||||
"bucketImage": null,
|
||||
"admin": false,
|
||||
"info": null,
|
||||
"phoneNumber": "+85291234567"
|
||||
}
|
||||
}
|
@@ -13,10 +13,7 @@ import type { NextRequest } from 'next/server';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { isDev } from 'src/constants';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
import { createProduct } from 'src/app/services/product.service';
|
||||
// import { createProduct } from 'src/app/services/product.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
|
@@ -0,0 +1,16 @@
|
||||
import type { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
/**
|
||||
***************************************
|
||||
* GET - helloworld
|
||||
***************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest, res: NextResponse) {
|
||||
try {
|
||||
return response({ helloworld: 'product' }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Helloworld - Get all', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
###
|
||||
GET /api/product/helloworld HTTP/1.1
|
||||
Host: localhost:7272
|
||||
|
@@ -1,30 +0,0 @@
|
||||
// src/app/api/product/image/upload/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// handle upload product image
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
// import prisma from '../../../lib/prisma';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Products
|
||||
*************************************** */
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const { data } = await req.json();
|
||||
console.log('helloworld');
|
||||
|
||||
return response({ hello: 'world' }, STATUS.OK);
|
||||
} catch (error) {
|
||||
console.log({ hello: 'world' });
|
||||
return handleError('Product - store product image', error);
|
||||
}
|
||||
}
|
81
03_source/cms_backend/src/app/api/user/_GUIDELINES.md
Normal file
81
03_source/cms_backend/src/app/api/user/_GUIDELINES.md
Normal file
@@ -0,0 +1,81 @@
|
||||
<!-- AI: please maintain same format and level of detail when edit this file -->
|
||||
|
||||
# GUIDELINE
|
||||
|
||||
- User Management API endpoint for managing user accounts
|
||||
- Handles user CRUD operations, role management and profile updates
|
||||
- Follows Next.js API route conventions
|
||||
|
||||
## `route.ts`
|
||||
|
||||
Main user route handler with HTTP methods:
|
||||
|
||||
- `GET` - Get current user details
|
||||
- `POST` - Create new user account
|
||||
- `PUT` - Update user profile
|
||||
- `DELETE` - Delete user account
|
||||
|
||||
## Sub-routes
|
||||
|
||||
### `changeToAdmin/route.ts`
|
||||
|
||||
Changes user role to admin
|
||||
|
||||
### `changeToUser/route.ts`
|
||||
|
||||
Changes user role to regular user
|
||||
|
||||
### `checkAdmin/route.ts`
|
||||
|
||||
Verifies if user has admin privileges
|
||||
|
||||
### `createUser/route.ts`
|
||||
|
||||
Creates new user account with required details
|
||||
|
||||
### `deleteUser/route.ts`
|
||||
|
||||
Deletes user account by ID
|
||||
|
||||
### `details/route.ts`
|
||||
|
||||
Gets detailed user information
|
||||
|
||||
### `list/route.ts`
|
||||
|
||||
Lists all users with pagination support
|
||||
|
||||
### `saveUser/route.ts`
|
||||
|
||||
Saves user profile changes
|
||||
|
||||
### `search/route.ts`
|
||||
|
||||
Searches users by name or email
|
||||
|
||||
### `image/upload/route.ts`
|
||||
|
||||
Handles user profile picture uploads
|
||||
|
||||
## `test.http`
|
||||
|
||||
Contains test requests for:
|
||||
|
||||
- User authentication
|
||||
- Role changes (admin/user)
|
||||
- Profile updates
|
||||
- Account creation/deletion
|
||||
- User listing/searching
|
||||
- Image uploads
|
||||
|
||||
## Related Services
|
||||
|
||||
`../../services/user.service.ts` (assumed) would handle:
|
||||
|
||||
`createUser` - Register new user account
|
||||
`updateUser` - Update user profile
|
||||
`deleteUser` - Remove user account
|
||||
`listUsers` - Get paginated user list
|
||||
`searchUsers` - Search users by criteria
|
||||
`changeUserRole` - Modify user permissions
|
||||
`uploadUserImage` - Handle profile picture uploads
|
@@ -1,7 +1,13 @@
|
||||
with knowledge in schema.prisma file,
|
||||
please refer the below helloworld example `helloworld.service.ts`
|
||||
and create `user.service.ts` to cover user record
|
||||
Hi,
|
||||
|
||||
thanks
|
||||
i copied from
|
||||
`03_source/cms_backend/src/app/services/user.service.ts`
|
||||
to
|
||||
`03_source/cms_backend/src/app/services/party-user.service.ts`
|
||||
|
||||
`/home/logic/_wsl_workspace/001_github_ws/HKSingleParty-ws/HKSingleParty/03_source/cms_backend/src/app/services/helloworld.service.ts`
|
||||
with knowledge in `schema.prisma` file, and reference to the sibling files in same folder
|
||||
|
||||
i want you to update `party-user.service.ts` content to handle `party user` (the user joining party)
|
||||
please use the model `PartyUser` to handle it.
|
||||
|
||||
thanks.
|
||||
|
@@ -47,6 +47,10 @@ async function getEvent(eventId: string): Promise<EventItem | null> {
|
||||
return prisma.eventItem.findFirst({ where: { id: eventId } });
|
||||
}
|
||||
|
||||
async function getEventItemById(eventId: string): Promise<EventItem | null> {
|
||||
return prisma.eventItem.findFirst({ where: { id: eventId } });
|
||||
}
|
||||
|
||||
// async function createNewEvent(createForm: CreateEvent) {
|
||||
// return prisma.event.create({ data: createForm });
|
||||
// }
|
||||
@@ -68,4 +72,5 @@ export {
|
||||
// updateEvent,
|
||||
// deleteEvent,
|
||||
// createNewEvent,
|
||||
getEventItemById,
|
||||
};
|
||||
|
139
03_source/cms_backend/src/app/services/party-event.service.ts
Normal file
139
03_source/cms_backend/src/app/services/party-event.service.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
// src/app/services/party-event.service.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Service for handling EventItem (PartyEvent) Record
|
||||
//
|
||||
|
||||
import type { EventItem } from '@prisma/client';
|
||||
|
||||
import prisma from '../lib/prisma';
|
||||
|
||||
type CreateEvent = {
|
||||
name: string;
|
||||
title: string;
|
||||
eventDate: Date;
|
||||
location: string;
|
||||
duration_m: number;
|
||||
ageBottom?: number;
|
||||
ageTop?: number;
|
||||
currency?: string;
|
||||
price?: number;
|
||||
priceSale?: number;
|
||||
coverUrl?: string;
|
||||
images?: string[];
|
||||
description?: string;
|
||||
subDescription?: string;
|
||||
publish?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
joinMembers?: any[];
|
||||
};
|
||||
|
||||
type UpdateEvent = {
|
||||
name?: string;
|
||||
title?: string;
|
||||
eventDate?: Date;
|
||||
location?: string;
|
||||
duration_m?: number;
|
||||
ageBottom?: number;
|
||||
ageTop?: number;
|
||||
currency?: string;
|
||||
price?: number;
|
||||
priceSale?: number;
|
||||
coverUrl?: string;
|
||||
images?: string[];
|
||||
description?: string;
|
||||
subDescription?: string;
|
||||
publish?: string;
|
||||
category?: string;
|
||||
tags?: string[];
|
||||
joinMembers?: any[];
|
||||
};
|
||||
|
||||
async function listEvents(): Promise<EventItem[]> {
|
||||
return prisma.eventItem.findMany({
|
||||
include: { reviews: true },
|
||||
});
|
||||
}
|
||||
|
||||
async function getEvent(eventId: string): Promise<EventItem | null> {
|
||||
return prisma.eventItem.findUnique({
|
||||
where: { id: eventId },
|
||||
include: { reviews: true },
|
||||
});
|
||||
}
|
||||
|
||||
async function getEventByNameOrTitle(searchText: string): Promise<EventItem[] | null> {
|
||||
return prisma.eventItem.findMany({
|
||||
where: {
|
||||
OR: [{ name: { contains: searchText, mode: 'insensitive' } }, { title: { contains: searchText, mode: 'insensitive' } }],
|
||||
},
|
||||
include: { reviews: true },
|
||||
});
|
||||
}
|
||||
|
||||
async function createEvent(eventData: any) {
|
||||
return await prisma.eventItem.create({ data: eventData });
|
||||
}
|
||||
|
||||
async function updateEvent(eventId: string, updateForm: any) {
|
||||
return prisma.eventItem.update({
|
||||
where: { id: eventId },
|
||||
data: {
|
||||
available: updateForm.available,
|
||||
category: updateForm.category,
|
||||
code: updateForm.code,
|
||||
colors: updateForm.colors,
|
||||
coverUrl: updateForm.coverUrl,
|
||||
description: updateForm.description,
|
||||
gender: updateForm.gender,
|
||||
images: updateForm.images,
|
||||
inventoryType: updateForm.inventoryType,
|
||||
name: updateForm.name,
|
||||
newLabel: updateForm.newLabel,
|
||||
price: updateForm.price,
|
||||
priceSale: updateForm.priceSale,
|
||||
publish: updateForm.publish,
|
||||
quantity: updateForm.quantity,
|
||||
ratings: updateForm.ratings,
|
||||
saleLabel: updateForm.saleLabel,
|
||||
sizes: updateForm.sizes,
|
||||
sku: updateForm.sku,
|
||||
subDescription: updateForm.subDescription,
|
||||
tags: updateForm.tags,
|
||||
taxes: updateForm.taxes,
|
||||
totalRatings: updateForm.totalRatings,
|
||||
totalReviews: updateForm.totalReviews,
|
||||
totalSold: updateForm.totalSold,
|
||||
//
|
||||
ageBottom: updateForm.ageBottom,
|
||||
ageTop: updateForm.ageTop,
|
||||
avatar: updateForm.avatar,
|
||||
currency: updateForm.currency,
|
||||
capacity: updateForm.capacity,
|
||||
duration_m: updateForm.duration_m,
|
||||
endDate: updateForm.endDate,
|
||||
eventDate: updateForm.eventDate,
|
||||
isFeatured: updateForm.isFeatured,
|
||||
joinMembers: updateForm.joinMembers,
|
||||
location: updateForm.location,
|
||||
organizer: updateForm.organizer,
|
||||
registrationDeadline: updateForm.registrationDeadline,
|
||||
requirements: updateForm.requirements,
|
||||
schedule: updateForm.schedule,
|
||||
speakers: updateForm.speakers,
|
||||
sponsors: updateForm.sponsors,
|
||||
startDate: updateForm.startDate,
|
||||
status: updateForm.status,
|
||||
title: updateForm.title,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteEvent(eventId: string) {
|
||||
return prisma.eventItem.delete({
|
||||
where: { id: eventId },
|
||||
});
|
||||
}
|
||||
|
||||
export { getEvent, listEvents, createEvent, updateEvent, deleteEvent, getEventByNameOrTitle, type CreateEvent, type UpdateEvent };
|
@@ -0,0 +1,66 @@
|
||||
// src/app/services/party-order.service.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Service for handling PartyOrderItem Record
|
||||
//
|
||||
|
||||
import type { PartyOrderItem } from '@prisma/client';
|
||||
|
||||
import prisma from '../lib/prisma';
|
||||
|
||||
type CreateOrder = {
|
||||
status: string;
|
||||
taxes: number;
|
||||
shipping: number;
|
||||
discount: number;
|
||||
items: any[];
|
||||
customer: any;
|
||||
payment: any;
|
||||
delivery: any;
|
||||
shippingAddress: any;
|
||||
billingAddress: any;
|
||||
};
|
||||
|
||||
type UpdateOrder = {
|
||||
status?: string;
|
||||
taxes?: number;
|
||||
shipping?: number;
|
||||
discount?: number;
|
||||
items?: any[];
|
||||
customer?: any;
|
||||
payment?: any;
|
||||
delivery?: any;
|
||||
shippingAddress?: any;
|
||||
billingAddress?: any;
|
||||
};
|
||||
|
||||
async function listPartyOrders(): Promise<PartyOrderItem[]> {
|
||||
return prisma.partyOrderItem.findMany();
|
||||
}
|
||||
|
||||
async function getPartyOrder(partyOrderId: string): Promise<PartyOrderItem | null> {
|
||||
return prisma.partyOrderItem.findUnique({
|
||||
where: { id: partyOrderId },
|
||||
});
|
||||
}
|
||||
|
||||
async function createOrder(orderData: any) {
|
||||
return prisma.partyOrderItem.create({
|
||||
data: orderData,
|
||||
});
|
||||
}
|
||||
|
||||
async function updateOrder(orderId: string, updateData: UpdateOrder) {
|
||||
return prisma.partyOrderItem.update({
|
||||
where: { id: orderId },
|
||||
data: updateData,
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteOrder(orderId: string) {
|
||||
return prisma.partyOrderItem.delete({
|
||||
where: { id: orderId },
|
||||
});
|
||||
}
|
||||
|
||||
export { getPartyOrder as getOrder, createOrder, updateOrder, deleteOrder, listPartyOrders, type CreateOrder, type UpdateOrder };
|
88
03_source/cms_backend/src/app/services/party-user.service.ts
Normal file
88
03_source/cms_backend/src/app/services/party-user.service.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
// src/app/services/party-user.service.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Handle Party User Record CRUD operations
|
||||
// - Manage party member data and permissions
|
||||
// - Interface between controllers and database
|
||||
//
|
||||
// RULES:
|
||||
// - Follow Prisma best practices for database operations
|
||||
// - Validate all party user data before processing
|
||||
// - Enforce party-specific business rules
|
||||
// - Maintain audit trail for sensitive operations
|
||||
//
|
||||
|
||||
import type { PartyUser } from '@prisma/client';
|
||||
|
||||
import prisma from '../lib/prisma';
|
||||
|
||||
type CreateUser = {
|
||||
email: string;
|
||||
// name?: string;
|
||||
// password: string;
|
||||
// role?: Role;
|
||||
// isEmailVerified?: boolean;
|
||||
// admin?: boolean;
|
||||
};
|
||||
|
||||
type UpdateUser = {
|
||||
email?: string;
|
||||
// name?: string;
|
||||
// password?: string;
|
||||
// role?: Role;
|
||||
// isEmailVerified?: boolean;
|
||||
isAdmin?: boolean;
|
||||
};
|
||||
|
||||
async function listPartyUsers(): Promise<PartyUser[]> {
|
||||
return prisma.partyUser.findMany({});
|
||||
}
|
||||
|
||||
async function getPartyUser(partyUserId: string): Promise<PartyUser | null> {
|
||||
return prisma.partyUser.findUnique({
|
||||
where: { id: partyUserId },
|
||||
// include: { reviews: true },
|
||||
});
|
||||
}
|
||||
|
||||
async function getPartyUserByEmail(email: string): Promise<PartyUser | null> {
|
||||
return prisma.partyUser.findUnique({
|
||||
where: { email },
|
||||
});
|
||||
}
|
||||
|
||||
async function getPartyUserById(id: string): Promise<PartyUser | null> {
|
||||
return prisma.partyUser.findFirst({ where: { id } });
|
||||
}
|
||||
|
||||
async function createPartyUser(partyUserData: any): Promise<PartyUser> {
|
||||
return prisma.partyUser.create({ data: partyUserData });
|
||||
}
|
||||
|
||||
async function updatePartyUser(partyUserId: string, partyUserData: any): Promise<PartyUser | null> {
|
||||
return prisma.partyUser.update({
|
||||
where: { id: partyUserId },
|
||||
data: partyUserData,
|
||||
});
|
||||
}
|
||||
|
||||
async function deletePartyUser(partyUserId: string): Promise<PartyUser | null> {
|
||||
return prisma.partyUser.delete({
|
||||
where: { id: partyUserId },
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
getPartyUserById,
|
||||
getPartyUser,
|
||||
listPartyUsers,
|
||||
createPartyUser,
|
||||
updatePartyUser,
|
||||
deletePartyUser,
|
||||
//
|
||||
getPartyUserByEmail,
|
||||
//
|
||||
type CreateUser,
|
||||
type UpdateUser,
|
||||
//
|
||||
};
|
@@ -1021,7 +1021,7 @@ ansi-regex@^5.0.1:
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-styles@^4.1.0:
|
||||
ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
|
||||
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
|
||||
@@ -1239,7 +1239,7 @@ caniuse-lite@^1.0.30001579:
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001707.tgz"
|
||||
integrity sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw==
|
||||
|
||||
chalk@^4.0.0:
|
||||
chalk@^4.0.0, chalk@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@@ -1257,6 +1257,15 @@ client-only@0.0.1:
|
||||
resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
|
||||
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
|
||||
|
||||
cliui@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
|
||||
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
strip-ansi "^6.0.1"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
clsx@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
|
||||
@@ -1284,6 +1293,19 @@ concat-map@0.0.1:
|
||||
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
concurrently@^9.1.2:
|
||||
version "9.1.2"
|
||||
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-9.1.2.tgz#22d9109296961eaee773e12bfb1ce9a66bc9836c"
|
||||
integrity sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==
|
||||
dependencies:
|
||||
chalk "^4.1.2"
|
||||
lodash "^4.17.21"
|
||||
rxjs "^7.8.1"
|
||||
shell-quote "^1.8.1"
|
||||
supports-color "^8.1.1"
|
||||
tree-kill "^1.2.2"
|
||||
yargs "^17.7.2"
|
||||
|
||||
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
|
||||
@@ -1614,6 +1636,11 @@ esbuild@~0.25.0:
|
||||
"@esbuild/win32-ia32" "0.25.5"
|
||||
"@esbuild/win32-x64" "0.25.5"
|
||||
|
||||
escalade@^3.1.1:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
|
||||
integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
|
||||
|
||||
escape-string-regexp@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
|
||||
@@ -1945,6 +1972,11 @@ gauge@^3.0.0:
|
||||
strip-ansi "^6.0.1"
|
||||
wide-align "^1.1.2"
|
||||
|
||||
get-caller-file@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
|
||||
get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
|
||||
@@ -2985,6 +3017,11 @@ regexp.prototype.flags@^1.5.3:
|
||||
gopd "^1.2.0"
|
||||
set-function-name "^2.0.2"
|
||||
|
||||
require-directory@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
|
||||
@@ -3049,6 +3086,13 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
rxjs@^7.8.1:
|
||||
version "7.8.2"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b"
|
||||
integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-array-concat@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz"
|
||||
@@ -3152,6 +3196,11 @@ shebang-regex@^3.0.0:
|
||||
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
|
||||
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
|
||||
|
||||
shell-quote@^1.8.1:
|
||||
version "1.8.3"
|
||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b"
|
||||
integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==
|
||||
|
||||
side-channel-list@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz"
|
||||
@@ -3222,7 +3271,7 @@ streamsearch@^1.1.0:
|
||||
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
|
||||
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3:
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
@@ -3297,7 +3346,7 @@ string_decoder@^1.1.1:
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
strip-ansi@^6.0.1:
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
@@ -3333,6 +3382,13 @@ supports-color@^7.1.0:
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^8.1.1:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-preserve-symlinks-flag@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
|
||||
@@ -3370,6 +3426,11 @@ tr46@~0.0.3:
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
tree-kill@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||
|
||||
ts-api-utils@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz"
|
||||
@@ -3404,7 +3465,7 @@ tsconfig-paths@^3.15.0:
|
||||
minimist "^1.2.6"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
tslib@^2.4.0:
|
||||
tslib@^2.1.0, tslib@^2.4.0:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
@@ -3612,6 +3673,15 @@ word-wrap@^1.2.5:
|
||||
resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz"
|
||||
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
@@ -3622,6 +3692,11 @@ xtend@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
@@ -3632,6 +3707,24 @@ yaml@^1.10.0:
|
||||
resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
|
||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||
|
||||
yargs-parser@^21.1.1:
|
||||
version "21.1.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
||||
yargs@^17.7.2:
|
||||
version "17.7.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
||||
integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==
|
||||
dependencies:
|
||||
cliui "^8.0.1"
|
||||
escalade "^3.1.1"
|
||||
get-caller-file "^2.0.5"
|
||||
require-directory "^2.1.1"
|
||||
string-width "^4.2.3"
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
yn@3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz"
|
||||
|
@@ -6,6 +6,7 @@ set -ex
|
||||
DOCKER_COMPOSE_FILES=" -f docker-compose.yml -f docker-compose.dev.yml"
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES build
|
||||
# docker compose $DOCKER_COMPOSE_FILES push
|
||||
docker compose $DOCKER_COMPOSE_FILES up -d
|
||||
|
||||
# cd ../api_server
|
||||
|
40
03_source/frontend/.env.example
Normal file
40
03_source/frontend/.env.example
Normal file
@@ -0,0 +1,40 @@
|
||||
# Server url
|
||||
# Local server configuration at: https://docs.minimals.cc/mock-server/
|
||||
VITE_SERVER_URL=http://localhost:7272
|
||||
# VITE_SERVER_URL=https://api-dev-minimal-v630.pages.dev
|
||||
|
||||
# Public resource directory
|
||||
VITE_ASSETS_DIR=
|
||||
|
||||
# Mapbox
|
||||
VITE_MAPBOX_API_KEY=
|
||||
|
||||
# Firebase
|
||||
VITE_FIREBASE_API_KEY=
|
||||
VITE_FIREBASE_AUTH_DOMAIN=
|
||||
VITE_FIREBASE_PROJECT_ID=
|
||||
VITE_FIREBASE_STORAGE_BUCKET=
|
||||
VITE_FIREBASE_MESSAGING_SENDER_ID=
|
||||
VITE_FIREBASE_APPID=
|
||||
|
||||
# Aws
|
||||
VITE_AWS_AMPLIFY_USER_POOL_ID=
|
||||
VITE_AWS_AMPLIFY_USER_POOL_WEB_CLIENT_ID=
|
||||
VITE_AWS_AMPLIFY_REGION=
|
||||
|
||||
# Auth0
|
||||
VITE_AUTH0_DOMAIN=
|
||||
VITE_AUTH0_CLIENT_ID=
|
||||
VITE_AUTH0_CALLBACK_URL=
|
||||
|
||||
# Supabase
|
||||
VITE_SUPABASE_URL=https://prhsilyjzxbkufchywxt.supabase.co
|
||||
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InByaHNpbHlqenhia3VmY2h5d3h0Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MzY2NjM5MTEsImV4cCI6MjA1MjIzOTkxMX0.UBvxEhKEMtsRuDWBSDTglfnupKf9fyPI9IvQBxS5F6U
|
||||
|
||||
# Google Calendar
|
||||
GOOGLE_CLIENT_ID=463956183839-i7j5nt5rkpbm4npukg21vfnhav5vvgeh.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=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
|
||||
GOOGLE_CALENDAR_API_KEY=AIzaSyDh4-SOuKTopXe45oDM9nQA7R4cNJ1So0c
|
||||
|
||||
VITE_GOOGLE_CLIENT_ID=463956183839-i7j5nt5rkpbm4npukg21vfnhav5vvgeh.apps.googleusercontent.com
|
||||
VITE_GOOGLE_CALENDAR_API_KEY=AIzaSyDh4-SOuKTopXe45oDM9nQA7R4cNJ1So0c
|
@@ -3,6 +3,8 @@ FROM node:20-slim
|
||||
|
||||
# Install pnpm globally
|
||||
RUN npm install -g pnpm
|
||||
RUN apt-get update
|
||||
RUN apt-get install -qqy psmisc
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
@@ -6,6 +6,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:check": "yarn tsc:w",
|
||||
"dev": "vite",
|
||||
"start": "vite preview",
|
||||
"build": "tsc && vite build",
|
||||
@@ -22,7 +23,7 @@
|
||||
"re:build-npm": "npm run clean && npm install && npm run build",
|
||||
"tsc:dev": "yarn dev & yarn tsc:watch",
|
||||
"tsc:print": "npx tsc --showConfig",
|
||||
"tsc:w": "npx nodemon --delay 3 --ext ts,tsx --exec \"yarn tsc\"",
|
||||
"tsc:w": "npx nodemon --delay 1 --ext ts,tsx --exec \"yarn tsc\"",
|
||||
"tsc:watch": "tsc --noEmit --watch",
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
|
@@ -10,6 +10,9 @@ while true; do
|
||||
|
||||
yarn dev --force --clearScreen
|
||||
|
||||
killall node
|
||||
killall yarn
|
||||
|
||||
echo "restarting..."
|
||||
sleep 1
|
||||
done
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
set -x
|
||||
|
||||
killall node
|
||||
killall yarn
|
||||
rm -rf ./**/*Zone.Identifier
|
||||
|
||||
set -ex
|
||||
|
70
03_source/frontend/src/_mock/_party-event.ts
Normal file
70
03_source/frontend/src/_mock/_party-event.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
export const PARTY_EVENT_GENDER_OPTIONS = [
|
||||
{ label: 'Men', value: 'Men' },
|
||||
{ label: 'Women', value: 'Women' },
|
||||
{ label: 'Kids', value: 'Kids' },
|
||||
];
|
||||
|
||||
export const PARTY_EVENT_CATEGORY_OPTIONS = ['Shose', 'Apparel', 'Accessories'];
|
||||
|
||||
export const PARTY_EVENT_RATING_OPTIONS = ['up4Star', 'up3Star', 'up2Star', 'up1Star'];
|
||||
|
||||
export const PARTY_EVENT_COLOR_OPTIONS = [
|
||||
'#FF4842',
|
||||
'#1890FF',
|
||||
'#FFC0CB',
|
||||
'#00AB55',
|
||||
'#FFC107',
|
||||
'#7F00FF',
|
||||
'#000000',
|
||||
'#FFFFFF',
|
||||
];
|
||||
|
||||
export const PARTY_EVENT_COLOR_NAME_OPTIONS = [
|
||||
{ value: '#FF4842', label: 'Red' },
|
||||
{ value: '#1890FF', label: 'Blue' },
|
||||
{ value: '#FFC0CB', label: 'Pink' },
|
||||
{ value: '#00AB55', label: 'Green' },
|
||||
{ value: '#FFC107', label: 'Yellow' },
|
||||
{ value: '#7F00FF', label: 'Violet' },
|
||||
{ value: '#000000', label: 'Black' },
|
||||
{ value: '#FFFFFF', label: 'White' },
|
||||
];
|
||||
|
||||
export const PARTY_EVENT_SIZE_OPTIONS = [
|
||||
{ value: '7', label: '7' },
|
||||
{ value: '8', label: '8' },
|
||||
{ value: '8.5', label: '8.5' },
|
||||
{ value: '9', label: '9' },
|
||||
{ value: '9.5', label: '9.5' },
|
||||
{ value: '10', label: '10' },
|
||||
{ value: '10.5', label: '10.5' },
|
||||
{ value: '11', label: '11' },
|
||||
{ value: '11.5', label: '11.5' },
|
||||
{ value: '12', label: '12' },
|
||||
{ value: '13', label: '13' },
|
||||
];
|
||||
|
||||
export const PARTY_EVENT_STOCK_OPTIONS = [
|
||||
{ value: 'in stock', label: 'In stock' },
|
||||
{ value: 'low stock', label: 'Low stock' },
|
||||
{ value: 'out of stock', label: 'Out of stock' },
|
||||
];
|
||||
|
||||
// not used due to i18n
|
||||
export const PARTY_EVENT_PUBLISH_OPTIONS = [
|
||||
{ value: 'published', label: 'Published' },
|
||||
{ value: 'draft', label: 'Draft' },
|
||||
];
|
||||
|
||||
export const PARTY_EVENT_SORT_OPTIONS = [
|
||||
{ value: 'featured', label: 'Featured' },
|
||||
{ value: 'newest', label: 'Newest' },
|
||||
{ value: 'priceDesc', label: 'Price: High - Low' },
|
||||
{ value: 'priceAsc', label: 'Price: Low - High' },
|
||||
];
|
||||
|
||||
export const PARTY_EVENT_CATEGORY_GROUP_OPTIONS = [
|
||||
{ group: 'Clothing', classify: ['Shirts', 'T-shirts', 'Jeans', 'Leather', 'Accessories'] },
|
||||
{ group: 'Tailored', classify: ['Suits', 'Blazers', 'Trousers', 'Waistcoats', 'Apparel'] },
|
||||
{ group: 'Accessories', classify: ['Shoes', 'Backpacks and bags', 'Bracelets', 'Face masks'] },
|
||||
];
|
89
03_source/frontend/src/_mock/_party-order.ts
Normal file
89
03_source/frontend/src/_mock/_party-order.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { _mock } from './_mock';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const PARTY_ORDER_STATUS_OPTIONS = [
|
||||
{ value: 'pending', label: 'Pending' },
|
||||
{ value: 'completed', label: 'Completed' },
|
||||
{ value: 'cancelled', label: 'Cancelled' },
|
||||
{ value: 'refunded', label: 'Refunded' },
|
||||
];
|
||||
|
||||
const ITEMS = Array.from({ length: 3 }, (_, index) => ({
|
||||
id: _mock.id(index),
|
||||
sku: `16H9UR${index}`,
|
||||
quantity: index + 1,
|
||||
name: _mock.productName(index),
|
||||
coverUrl: _mock.image.product(index),
|
||||
price: _mock.number.price(index),
|
||||
}));
|
||||
|
||||
export const _party_orders = Array.from({ length: 20 }, (_, index) => {
|
||||
const shipping = 10;
|
||||
|
||||
const discount = 10;
|
||||
|
||||
const taxes = 10;
|
||||
|
||||
const items = (index % 2 && ITEMS.slice(0, 1)) || (index % 3 && ITEMS.slice(1, 3)) || ITEMS;
|
||||
|
||||
const totalQuantity = items.reduce((accumulator, item) => accumulator + item.quantity, 0);
|
||||
|
||||
const subtotal = items.reduce((accumulator, item) => accumulator + item.price * item.quantity, 0);
|
||||
|
||||
const totalAmount = subtotal - shipping - discount + taxes;
|
||||
|
||||
const customer = {
|
||||
id: _mock.id(index),
|
||||
name: _mock.fullName(index),
|
||||
email: _mock.email(index),
|
||||
avatarUrl: _mock.image.avatar(index),
|
||||
ipAddress: '192.158.1.38',
|
||||
};
|
||||
|
||||
const delivery = { shipBy: 'DHL', speedy: 'Standard', trackingNumber: 'SPX037739199373' };
|
||||
|
||||
const history = {
|
||||
orderTime: _mock.time(1),
|
||||
paymentTime: _mock.time(2),
|
||||
deliveryTime: _mock.time(3),
|
||||
completionTime: _mock.time(4),
|
||||
timeline: [
|
||||
{ title: 'Delivery successful', time: _mock.time(1) },
|
||||
{ title: 'Transporting to [2]', time: _mock.time(2) },
|
||||
{ title: 'Transporting to [1]', time: _mock.time(3) },
|
||||
{ title: 'The shipping unit has picked up the goods', time: _mock.time(4) },
|
||||
{ title: 'Order has been created', time: _mock.time(5) },
|
||||
],
|
||||
};
|
||||
|
||||
return {
|
||||
id: _mock.id(index),
|
||||
orderNumber: `#601${index}`,
|
||||
createdAt: _mock.time(index),
|
||||
taxes,
|
||||
items,
|
||||
history,
|
||||
subtotal,
|
||||
shipping,
|
||||
discount,
|
||||
customer,
|
||||
delivery,
|
||||
totalAmount,
|
||||
totalQuantity,
|
||||
shippingAddress: {
|
||||
fullAddress: '19034 Verna Unions Apt. 164 - Honolulu, RI / 87535',
|
||||
phoneNumber: '365-374-4961',
|
||||
},
|
||||
payment: {
|
||||
//
|
||||
cardType: 'mastercard',
|
||||
cardNumber: '**** **** **** 5678',
|
||||
},
|
||||
status:
|
||||
(index % 2 && 'completed') ||
|
||||
(index % 3 && 'pending') ||
|
||||
(index % 4 && 'cancelled') ||
|
||||
'refunded',
|
||||
};
|
||||
});
|
@@ -23,3 +23,7 @@ export * from './_product';
|
||||
export * from './_overview';
|
||||
|
||||
export * from './_calendar';
|
||||
|
||||
export * from './_party-event';
|
||||
|
||||
export * from './_party-order';
|
||||
|
177
03_source/frontend/src/actions/party-event.ts
Normal file
177
03_source/frontend/src/actions/party-event.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
// src/actions/party-event.ts
|
||||
//
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import type { IPartyEventItem } from 'src/types/party-event';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const swrOptions: SWRConfiguration = {
|
||||
revalidateIfStale: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type PartyEventsData = {
|
||||
partyEvents: IPartyEventItem[];
|
||||
};
|
||||
|
||||
export function useGetPartyEvents() {
|
||||
const url = endpoints.partyEvent.list;
|
||||
|
||||
const { data, isLoading, error, isValidating } = useSWR<PartyEventsData>(
|
||||
url,
|
||||
fetcher,
|
||||
swrOptions
|
||||
);
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
partyEvents: data?.partyEvents || [],
|
||||
partyEventsLoading: isLoading,
|
||||
partyEventsError: error,
|
||||
partyEventsValidating: isValidating,
|
||||
partyEventsEmpty: !isLoading && !isValidating && !data?.partyEvents.length,
|
||||
}),
|
||||
[data?.partyEvents, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type PartyEventData = {
|
||||
partyEvent: IPartyEventItem;
|
||||
};
|
||||
|
||||
export function useGetPartyEvent(partyEventId: string) {
|
||||
const url = partyEventId ? [endpoints.partyEvent.details, { params: { partyEventId } }] : '';
|
||||
|
||||
const { data, isLoading, error, isValidating } = useSWR<PartyEventData>(url, fetcher, swrOptions);
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
partyEvent: data?.partyEvent,
|
||||
partyEventLoading: isLoading,
|
||||
partyEventError: error,
|
||||
partyEventValidating: isValidating,
|
||||
mutate,
|
||||
}),
|
||||
[data?.partyEvent, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type SearchResultsData = {
|
||||
results: IPartyEventItem[];
|
||||
};
|
||||
|
||||
// TODO: update useSearchPartyEvents
|
||||
export function useSearchProducts(query: string) {
|
||||
const url = query ? [endpoints.product.search, { params: { query } }] : '';
|
||||
|
||||
const { data, isLoading, error, isValidating } = useSWR<SearchResultsData>(url, fetcher, {
|
||||
...swrOptions,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
searchResults: data?.results || [],
|
||||
searchLoading: isLoading,
|
||||
searchError: error,
|
||||
searchValidating: isValidating,
|
||||
searchEmpty: !isLoading && !isValidating && !data?.results.length,
|
||||
}),
|
||||
[data?.results, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export async function createPartyEvent(partyEventData: IPartyEventItem) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyEventData };
|
||||
const {
|
||||
data: { id },
|
||||
} = await axiosInstance.post(endpoints.partyEvent.create, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
mutate(
|
||||
endpoints.partyEvent.list,
|
||||
(currentData: any) => {
|
||||
const currentPartyEvents: IPartyEventItem[] = currentData?.partyEvents;
|
||||
|
||||
const partyEvents = [...currentPartyEvents, { ...partyEventData, id }];
|
||||
|
||||
return { ...currentData, partyEvents };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export async function updatePartyEvent(partyEventData: Partial<IPartyEventItem>) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyEventData };
|
||||
await axiosInstance.put(endpoints.partyEvent.update, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
mutate(
|
||||
endpoints.partyEvent.list,
|
||||
(currentData: any) => {
|
||||
const currentPartyEvents: IPartyEventItem[] = currentData?.partyEvents;
|
||||
|
||||
const partyEvents = currentPartyEvents.map((partyEvent) =>
|
||||
partyEvent.id === partyEventData.id ? { ...partyEvent, ...partyEventData } : partyEvent
|
||||
);
|
||||
|
||||
return { ...currentData, partyEvents };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export async function deletePartyEvent(partyEventId: string) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyEventId };
|
||||
await axiosInstance.patch(endpoints.partyEvent.delete, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
mutate(
|
||||
endpoints.partyEvent.list,
|
||||
(currentData: any) => {
|
||||
const currentProducts: IPartyEventItem[] = currentData?.partyEvents;
|
||||
|
||||
const partyEvents = currentProducts.filter((partyEvent) => partyEvent.id !== partyEventId);
|
||||
|
||||
return { ...currentData, partyEvents };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
208
03_source/frontend/src/actions/party-order.ts
Normal file
208
03_source/frontend/src/actions/party-order.ts
Normal file
@@ -0,0 +1,208 @@
|
||||
// src/actions/party-order.ts
|
||||
//
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import type { IPartyOrderItem } from 'src/types/party-order';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const swrOptions: SWRConfiguration = {
|
||||
revalidateIfStale: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type PartyOrdersData = {
|
||||
partyOrders: IPartyOrderItem[];
|
||||
};
|
||||
|
||||
export function useGetPartyOrders() {
|
||||
const url = endpoints.partyOrder.list;
|
||||
|
||||
const { data, isLoading, error, isValidating } = useSWR<PartyOrdersData>(
|
||||
url,
|
||||
fetcher,
|
||||
swrOptions
|
||||
);
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
partyOrders: data?.partyOrders || [],
|
||||
partyOrdersLoading: isLoading,
|
||||
partyOrdersError: error,
|
||||
partyOrdersValidating: isValidating,
|
||||
partyOrdersEmpty: !isLoading && !isValidating && !data?.partyOrders.length,
|
||||
}),
|
||||
[data?.partyOrders, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type PartyOrderData = {
|
||||
partyOrder: IPartyOrderItem;
|
||||
};
|
||||
|
||||
export function useGetPartyOrder(partyOrderId: string) {
|
||||
const url = partyOrderId ? [endpoints.partyOrder.details, { params: { partyOrderId } }] : '';
|
||||
|
||||
const { data, isLoading, error, isValidating } = useSWR<PartyOrderData>(url, fetcher, swrOptions);
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
partyOrder: data?.partyOrder,
|
||||
partyOrderLoading: isLoading,
|
||||
partyOrderError: error,
|
||||
partyOrderValidating: isValidating,
|
||||
}),
|
||||
[data?.partyOrder, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type SearchResultsData = {
|
||||
results: IPartyOrderItem[];
|
||||
};
|
||||
|
||||
export function useSearchProducts(query: string) {
|
||||
const url = query ? [endpoints.product.search, { params: { query } }] : '';
|
||||
|
||||
const { data, isLoading, error, isValidating } = useSWR<SearchResultsData>(url, fetcher, {
|
||||
...swrOptions,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
searchResults: data?.results || [],
|
||||
searchLoading: isLoading,
|
||||
searchError: error,
|
||||
searchValidating: isValidating,
|
||||
searchEmpty: !isLoading && !isValidating && !data?.results.length,
|
||||
}),
|
||||
[data?.results, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export async function createPartyOrder(partyOrderData: IPartyOrderItem) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyOrderData };
|
||||
const {
|
||||
data: { id },
|
||||
} = await axiosInstance.post(endpoints.partyOrder.create, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
mutate(
|
||||
endpoints.partyOrder.list,
|
||||
(currentData: any) => {
|
||||
const currentPartyOrders: IPartyOrderItem[] = currentData?.partyOrders;
|
||||
|
||||
const partyOrders = [...currentPartyOrders, { ...partyOrderData, id }];
|
||||
|
||||
return { ...currentData, partyOrders };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export async function updatePartyOrder(partyOrderData: Partial<IPartyOrderItem>) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyOrderData };
|
||||
await axiosInstance.put(endpoints.partyOrder.update, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
|
||||
mutate(
|
||||
endpoints.partyOrder.list,
|
||||
(currentData: any) => {
|
||||
const currentPartyOrders: IPartyOrderItem[] = currentData?.partyOrders;
|
||||
|
||||
const partyOrders = currentPartyOrders.map((partyOrder) =>
|
||||
partyOrder.id === partyOrderData.id ? { ...partyOrder, ...partyOrderData } : partyOrder
|
||||
);
|
||||
|
||||
return { ...currentData, partyOrders };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export async function deletePartyOrder(partyOrderId: string) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyOrderId };
|
||||
await axiosInstance.patch(endpoints.partyOrder.delete, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
|
||||
mutate(
|
||||
endpoints.partyOrder.list,
|
||||
(currentData: any) => {
|
||||
const currentProducts: IPartyOrderItem[] = currentData?.partyOrders;
|
||||
|
||||
const partyOrders = currentProducts.filter((partyOrder) => partyOrder.id !== partyOrderId);
|
||||
|
||||
return { ...currentData, partyOrders };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// TODO: implement partyOrder changeStatus with url below
|
||||
// const url = endpoints.order.changeStatus(orderId);
|
||||
export async function changeStatus(partyOrderData: any, dummy: any) {
|
||||
return true;
|
||||
// /**
|
||||
// * Work on server
|
||||
// */
|
||||
// const data = { partyOrderData };
|
||||
// await axiosInstance.put(endpoints.partyOrder.update, data);
|
||||
|
||||
// /**
|
||||
// * Work in local
|
||||
// */
|
||||
|
||||
// mutate(
|
||||
// endpoints.partyOrder.list,
|
||||
// (currentData: any) => {
|
||||
// const currentPartyOrders: IPartyOrderItem[] = currentData?.partyOrders;
|
||||
|
||||
// const partyOrders = currentPartyOrders.map((partyOrder) =>
|
||||
// partyOrder.id === partyOrderData.id ? { ...partyOrder, ...partyOrderData } : partyOrder
|
||||
// );
|
||||
|
||||
// return { ...currentData, partyOrders };
|
||||
// },
|
||||
// false
|
||||
// );
|
||||
}
|
289
03_source/frontend/src/actions/party-user.ts
Normal file
289
03_source/frontend/src/actions/party-user.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
// src/actions/party-user1.ts
|
||||
//
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import type { IPartyUserItem } from 'src/types/party-user';
|
||||
import type { IProductItem } from 'src/types/product';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const swrOptions: SWRConfiguration = {
|
||||
revalidateIfStale: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type PartyUsersData = {
|
||||
partyUsers: IPartyUserItem[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches list of party users with SWR caching
|
||||
* @returns {Object} Contains:
|
||||
* - partyUsers: Array of party user items
|
||||
* - partyUsersLoading: Loading state
|
||||
* - partyUsersError: Error object if any
|
||||
* - partyUsersValidating: Validation state
|
||||
* - partyUsersEmpty: Boolean if no users found
|
||||
*/
|
||||
export function useGetPartyUsers() {
|
||||
const url = endpoints.partyUser.list;
|
||||
|
||||
const { data, isLoading, error, isValidating } = useSWR<PartyUsersData>(url, fetcher, swrOptions);
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
partyUsers: data?.partyUsers || [],
|
||||
partyUsersLoading: isLoading,
|
||||
partyUsersError: error,
|
||||
partyUsersValidating: isValidating,
|
||||
partyUsersEmpty: !isLoading && !isValidating && !data?.partyUsers.length,
|
||||
}),
|
||||
[data?.partyUsers, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type PartyUserData = {
|
||||
partyUser: IPartyUserItem;
|
||||
};
|
||||
|
||||
export function useGetPartyUser(partyUserId: string) {
|
||||
const { data, isLoading, error, isValidating } = useSWR<PartyUserData>(
|
||||
endpoints.partyUser.detailsByPartyUserId(partyUserId),
|
||||
fetcher,
|
||||
swrOptions
|
||||
);
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
partyUser: data?.partyUser,
|
||||
partyUserLoading: isLoading,
|
||||
partyUserError: error,
|
||||
partyUserValidating: isValidating,
|
||||
}),
|
||||
[data?.partyUser, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type SearchResultsData = {
|
||||
results: IProductItem[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Searches products by query with SWR caching
|
||||
* @param {string} query - Search term
|
||||
* @returns {Object} Contains:
|
||||
* - searchResults: Array of matching products
|
||||
* - searchLoading: Loading state
|
||||
* - searchError: Error object if any
|
||||
* - searchValidating: Validation state
|
||||
* - searchEmpty: Boolean if no results found
|
||||
*/
|
||||
export function useSearchProducts(query: string) {
|
||||
const url = query ? [endpoints.product.search, { params: { query } }] : '';
|
||||
|
||||
const { data, isLoading, error, isValidating } = useSWR<SearchResultsData>(url, fetcher, {
|
||||
...swrOptions,
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => ({
|
||||
searchResults: data?.results || [],
|
||||
searchLoading: isLoading,
|
||||
searchError: error,
|
||||
searchValidating: isValidating,
|
||||
searchEmpty: !isLoading && !isValidating && !data?.results.length,
|
||||
}),
|
||||
[data?.results, error, isLoading, isValidating]
|
||||
);
|
||||
|
||||
return memoizedValue;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type SaveUserData = {
|
||||
name: string;
|
||||
city: string;
|
||||
role: string;
|
||||
email: string;
|
||||
state: string;
|
||||
status: string;
|
||||
address: string;
|
||||
country: string;
|
||||
zipCode: string;
|
||||
company: string;
|
||||
avatarUrl: string;
|
||||
phoneNumber: string;
|
||||
isVerified: boolean;
|
||||
//
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new party user with optimistic UI updates
|
||||
* @param {CreateUserData} partyUserData - Data for the new party user
|
||||
* @returns {Promise<void>}
|
||||
* @sideeffects
|
||||
* - Makes POST request to create user on server
|
||||
* - Updates local SWR cache optimistically
|
||||
* - Triggers revalidation of party user list
|
||||
*/
|
||||
export async function createPartyUser(partyUserData: CreateUserData) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyUserData };
|
||||
const {
|
||||
data: { id },
|
||||
} = await axiosInstance.post(endpoints.partyUser.create, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
mutate(
|
||||
endpoints.partyUser.list,
|
||||
(currentData: any) => {
|
||||
const currentPartyUsers: IPartyUserItem[] = currentData?.partyUsers;
|
||||
|
||||
const partyUsers = [...currentPartyUsers, { ...partyUserData, id }];
|
||||
|
||||
return { ...currentData, partyUsers };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Updates party user data with optimistic UI updates
|
||||
* @param {Partial<IPartyUserItem>} partyUserData - Partial user data containing at least the ID
|
||||
* @returns {Promise<void>}
|
||||
* @sideeffects
|
||||
* - Makes PUT request to update user on server
|
||||
* - Updates both list and detail views in local SWR cache
|
||||
* - Preserves unchanged fields while updating modified ones
|
||||
*/
|
||||
export async function updatePartyUser(partyUserData: Partial<IPartyUserItem>) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyUserData };
|
||||
await axiosInstance.put(endpoints.partyUser.update, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
mutate(
|
||||
endpoints.partyUser.list,
|
||||
(currentData: any) => {
|
||||
const currentPartyUsers: IPartyUserItem[] = currentData?.partyUsers;
|
||||
|
||||
const partyUsers = currentPartyUsers.map((partyUser) =>
|
||||
partyUser.id === partyUserData.id ? { ...partyUser, ...partyUserData } : partyUser
|
||||
);
|
||||
|
||||
return { ...currentData, partyUsers };
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
const partyUserId: string = partyUserData.id || '';
|
||||
mutate(
|
||||
endpoints.partyUser.detailsByPartyUserId(partyUserId),
|
||||
(currentData: any) => {
|
||||
const currentPartyUser: IPartyUserItem = currentData?.partyUser;
|
||||
|
||||
console.log({ currentPartyUser });
|
||||
const partyUser = partyUserData;
|
||||
|
||||
return { ...currentData, partyUser };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests connection to product API endpoint
|
||||
* @param {SaveUserData} saveUserData - User data object (currently unused)
|
||||
* @returns {Promise<AxiosResponse>} Response from test endpoint
|
||||
* @deprecated This function should be renamed to better reflect its purpose
|
||||
* TODO: Rename to testProductApiConnection() since it tests product API connection
|
||||
* TODO: Or implement actual image upload functionality if needed
|
||||
*/
|
||||
export async function uploadUserImage(saveUserData: SaveUserData) {
|
||||
console.log('uploadUserImage ?');
|
||||
// const url = userId ? [endpoints.user.details, { params: { userId } }] : '';
|
||||
|
||||
const res = await axiosInstance.get('http://localhost:7272/api/product/helloworld');
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type CreateUserData = {
|
||||
name: string;
|
||||
city: string;
|
||||
role: string;
|
||||
email: string;
|
||||
state: string;
|
||||
status: string;
|
||||
address: string;
|
||||
country: string;
|
||||
zipCode: string;
|
||||
company: string;
|
||||
avatarUrl: string;
|
||||
phoneNumber: string;
|
||||
isVerified: boolean;
|
||||
//
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a party user with optimistic UI updates
|
||||
* @param {string} partyUserId - ID of user to delete
|
||||
* @returns {Promise<void>}
|
||||
* @sideeffects
|
||||
* - Makes PATCH request to mark user as deleted on server
|
||||
* - Removes user from local SWR cache
|
||||
* - Triggers revalidation of party user list
|
||||
*/
|
||||
export async function deletePartyUser(partyUserId: string) {
|
||||
/**
|
||||
* Work on server
|
||||
*/
|
||||
const data = { partyUserId };
|
||||
await axiosInstance.patch(endpoints.partyUser.delete, data);
|
||||
|
||||
/**
|
||||
* Work in local
|
||||
*/
|
||||
mutate(
|
||||
endpoints.partyUser.list,
|
||||
(currentData: any) => {
|
||||
const currentPartyUsers: IPartyUserItem[] = currentData?.partyUsers;
|
||||
|
||||
const partyUsers = currentPartyUsers.filter((partyUser) => partyUser.id !== partyUserId);
|
||||
|
||||
return { ...currentData, partyUsers };
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
@@ -91,6 +91,39 @@ export const navData: NavSectionProps['data'] = [
|
||||
{ title: 'Account', path: paths.dashboard.user.account },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'party-event',
|
||||
path: paths.dashboard.partyEvent.root,
|
||||
icon: ICONS.product,
|
||||
children: [
|
||||
{ title: 'List', path: paths.dashboard.partyEvent.root },
|
||||
{ title: 'Details', path: paths.dashboard.partyEvent.demo.details },
|
||||
{ title: 'Create', path: paths.dashboard.partyEvent.new },
|
||||
{ title: 'Edit', path: paths.dashboard.partyEvent.demo.edit },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'party-order',
|
||||
path: paths.dashboard.partyOrder.root,
|
||||
icon: ICONS.order,
|
||||
children: [
|
||||
{ title: 'List', path: paths.dashboard.partyOrder.root },
|
||||
{ title: 'Details', path: paths.dashboard.partyOrder.demo.details },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'party-user',
|
||||
path: paths.dashboard.partyUser.root,
|
||||
icon: ICONS.user,
|
||||
children: [
|
||||
{ title: 'Profile', path: paths.dashboard.partyUser.root },
|
||||
{ title: 'Cards', path: paths.dashboard.partyUser.cards },
|
||||
{ title: 'List', path: paths.dashboard.partyUser.list },
|
||||
{ title: 'Create', path: paths.dashboard.partyUser.new },
|
||||
{ title: 'Edit', path: paths.dashboard.partyUser.demo.edit },
|
||||
{ title: 'Account', path: paths.dashboard.partyUser.account },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Product',
|
||||
path: paths.dashboard.product.root,
|
||||
|
@@ -84,4 +84,37 @@ export const endpoints = {
|
||||
changeStatus: (invoiceId: string) => `/api/invoice/changeStatus?invoiceId=${invoiceId}`,
|
||||
search: '/api/invoice/search',
|
||||
},
|
||||
//
|
||||
//
|
||||
//
|
||||
partyEvent: {
|
||||
list: '/api/party-event/list',
|
||||
details: '/api/party-event/details',
|
||||
search: '/api/party-event/search',
|
||||
create: '/api/party-event/create',
|
||||
update: '/api/party-event/update',
|
||||
delete: '/api/party-event/delete',
|
||||
},
|
||||
partyOrder: {
|
||||
create: '/api/party-order/create',
|
||||
delete: '/api/party-order/delete',
|
||||
list: '/api/party-order/list',
|
||||
profile: '/api/party-order/profile',
|
||||
update: '/api/party-order/update',
|
||||
settings: '/api/party-order/settings',
|
||||
details: '/api/party-order/details',
|
||||
changeStatus: (partyOrderId: string) =>
|
||||
`/api/party-order/changeStatus?partyOrderId=${partyOrderId}`,
|
||||
},
|
||||
partyUser: {
|
||||
list: '/api/party-user/list',
|
||||
details: '/api/party-user/details',
|
||||
search: '/api/party-user/search',
|
||||
create: '/api/party-user/create',
|
||||
update: '/api/party-user/update',
|
||||
delete: '/api/party-user/delete',
|
||||
//
|
||||
detailsByPartyUserId: (partyUserId: string) =>
|
||||
`/api/party-user/details?partyUserId=${partyUserId}`,
|
||||
},
|
||||
};
|
||||
|
@@ -22,7 +22,7 @@ i18next
|
||||
.init({
|
||||
...i18nOptions(lng),
|
||||
detection: { caches: ['localStorage'] },
|
||||
debug: isDev,
|
||||
debug: false,
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
@@ -0,0 +1,28 @@
|
||||
// src/pages/dashboard/party-event/details.tsx
|
||||
|
||||
import { useGetPartyEvent } from 'src/actions/party-event';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
import { useParams } from 'src/routes/hooks';
|
||||
import { PartyEventDetailsView } from 'src/sections/party-event/view';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `PartyEvent details | Dashboard - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
const { id = '' } = useParams();
|
||||
|
||||
const { partyEvent, partyEventLoading, partyEventError } = useGetPartyEvent(id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<PartyEventDetailsView
|
||||
partyEvent={partyEvent}
|
||||
loading={partyEventLoading}
|
||||
error={partyEventError}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
22
03_source/frontend/src/pages/dashboard/party-event/edit.tsx
Normal file
22
03_source/frontend/src/pages/dashboard/party-event/edit.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { useGetPartyEvent } from 'src/actions/party-event';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
import { useParams } from 'src/routes/hooks';
|
||||
import { PartyEventEditView } from 'src/sections/party-event/view';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `PartyEvent edit | Dashboard - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
const { id = '' } = useParams();
|
||||
|
||||
const { partyEvent } = useGetPartyEvent(id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<PartyEventEditView partyEvent={partyEvent} />
|
||||
</>
|
||||
);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user