Compare commits

..

6 Commits

Author SHA1 Message Date
louiscklaw
8b73b583cd "feat: rename 'createdDate' to 'createDate' across frontend, backend, and mobile codebases" 2025-05-30 20:15:27 +08:00
louiscklaw
fd20a3531b "feat: enhance invoice management with schema updates, seed data, and new APIs" 2025-05-30 16:48:54 +08:00
louiscklaw
5a707427c6 "feat: enhance order management with new APIs and schema changes" 2025-05-30 11:40:25 +08:00
louiscklaw
834f58bde1 update, 2025-05-30 01:14:10 +08:00
louiscklaw
98bc3fe3ce update, 2025-05-30 01:13:54 +08:00
louiscklaw
9f5367e35c init user edit, 2025-05-28 23:17:04 +08:00
158 changed files with 5093 additions and 2199 deletions

View File

@@ -27,7 +27,7 @@
"unseed": "tsx ./prisma/unseed.ts", "unseed": "tsx ./prisma/unseed.ts",
"db:generate": "prisma generate", "db:generate": "prisma generate",
"db:push": "prisma db push --force-reset", "db:push": "prisma db push --force-reset",
"db:push:w": "npx nodemon --delay 5 --ext \"ts,tsx,prisma\" --exec \"yarn db:push && yarn seed && yarn db:studio\"", "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": "prisma studio"
}, },
"engines": { "engines": {
@@ -37,6 +37,7 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@faker-js/faker": "^9.8.0",
"@mui/material": "^6.4.8", "@mui/material": "^6.4.8",
"@next-auth/prisma-adapter": "^1.0.7", "@next-auth/prisma-adapter": "^1.0.7",
"@prisma/adapter-pg": "^6.8.2", "@prisma/adapter-pg": "^6.8.2",
@@ -75,4 +76,4 @@
"typescript": "^5.8.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.28.0" "typescript-eslint": "^8.28.0"
} }
} }

View File

@@ -266,85 +266,85 @@ model Mail {
// attachments MailAttachment[] // attachments MailAttachment[]
} }
model OrderHistory { // model OrderHistory {
id Int @id @default(autoincrement()) // id Int @id @default(autoincrement())
createdAt DateTime @default(now()) // createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // updatedAt DateTime @updatedAt
// // //
orderTime DateTime // orderTime DateTime @default(now())
paymentTime DateTime // paymentTime DateTime
deliveryTime DateTime // deliveryTime DateTime
completionTime DateTime // completionTime DateTime
timeline Json[] // timeline Json[]
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id]) // OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
orderItemId Int? // orderItemId Int?
} // }
model OrderShippingAddress { // model OrderShippingAddress {
id Int @id @default(autoincrement()) // id Int @id @default(autoincrement())
createdAt DateTime @default(now()) // createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // updatedAt DateTime @updatedAt
// // //
fullAddress String // fullAddress String
phoneNumber String // phoneNumber String
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id]) // OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
orderItemId Int? // orderItemId Int?
} // }
model OrderPayment { // model OrderPayment {
id Int @id @default(autoincrement()) // id Int @id @default(autoincrement())
createdAt DateTime @default(now()) // createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // updatedAt DateTime @updatedAt
// // //
cardType String // cardType String
cardNumber String // cardNumber String
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id]) // OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
orderItemId Int? // orderItemId Int?
} // }
model OrderDelivery { // model OrderDelivery {
id Int @id @default(autoincrement()) // id Int @id @default(autoincrement())
createdAt DateTime @default(now()) // createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // updatedAt DateTime @updatedAt
// // //
shipBy String // shipBy String
speedy String // speedy String
trackingNumber String // trackingNumber String
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id]) // OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
orderItemId Int? // orderItemId Int?
} // }
model OrderCustomer { // model OrderCustomer {
id Int @id @default(autoincrement()) // id Int @id @default(autoincrement())
createdAt DateTime @default(now()) // createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // updatedAt DateTime @updatedAt
// // //
name String // name String
email String // email String
avatarUrl String // avatarUrl String
ipAddress String // ipAddress String
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id]) // OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
orderItemId Int? // orderItemId Int?
} // }
model OrderProductItem { // model OrderProductItem {
id Int @id @default(autoincrement()) // id Int @id @default(autoincrement())
createdAt DateTime @default(now()) // createdAt DateTime @default(now())
updatedAt DateTime @updatedAt // updatedAt DateTime @updatedAt
// // //
sku String // sku String
name String // name String
price Float // price Float
coverUrl String // coverUrl String
quantity Float // quantity Float
OrderItem OrderItem? @relation(fields: [orderItemId], references: [id]) // OrderItem OrderItem? @relation(fields: [orderItemId], references: [id])
orderItemId Int? // orderItemId Int?
} // }
model OrderItem { model OrderItem {
id Int @id @default(autoincrement()) id String @id @default(uuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
// //
taxes Float taxes Float
status String status String
@@ -354,12 +354,18 @@ model OrderItem {
orderNumber String orderNumber String
totalAmount Float totalAmount Float
totalQuantity Float totalQuantity Float
history OrderHistory[] history Json
payment OrderPayment[] payment Json
customer OrderCustomer[] customer Json
delivery OrderDelivery[] delivery Json
items OrderProductItem[] items Json[]
shippingAddress OrderShippingAddress[] shippingAddress Json
// OrderProductItem OrderProductItem[]
// OrderHistory OrderHistory[]
// OrderDelivery OrderDelivery[]
// OrderCustomer OrderCustomer[]
// OrderPayment OrderPayment[]
// OrderShippingAddress OrderShippingAddress[]
} }
// src/types/tour.ts // src/types/tour.ts
@@ -511,7 +517,7 @@ model UserCard {
} }
model UserItem { model UserItem {
id Int @id @default(autoincrement()) id String @id @default(uuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
// //
@@ -528,6 +534,9 @@ model UserItem {
avatarUrl String avatarUrl String
phoneNumber String phoneNumber String
isVerified Boolean isVerified Boolean
//
username String
password String
} }
model UserAccountBillingHistory { model UserAccountBillingHistory {
@@ -777,8 +786,8 @@ model AddressItem {
addressType String? addressType String?
CheckoutState CheckoutState[] CheckoutState CheckoutState[]
checkoutStateId Int? checkoutStateId Int?
InvoiceTo Invoice[] @relation("invoice_to") // InvoiceTo Invoice[] @relation("invoice_to")
InvoiceFrom Invoice[] @relation("invoice_from") // InvoiceFrom Invoice[] @relation("invoice_from")
} }
model SocialLink { model SocialLink {
@@ -878,40 +887,45 @@ model InvoiceTableFilters {
} }
model InvoiceItem { model InvoiceItem {
id Int @id @default(autoincrement()) id String @id @default(uuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
// //
title String
price Float
total Float
service String
quantity Int
description String
Invoice Invoice? @relation(fields: [invoiceId], references: [id])
invoiceId Int?
}
model Invoice {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
//
sent Int
taxes Float taxes Float
status String status String
subtotal Float
discount Float discount Float
shipping Float shipping Float
subtotal Float
totalAmount Float totalAmount Float
dueDate DateTime items Json[]
invoiceNumber String invoiceNumber String
items InvoiceItem[] invoiceFrom Json
createDate DateTime invoiceTo Json
invoiceTo AddressItem[] @relation("invoice_to") sent Float
invoiceFrom AddressItem[] @relation("invoice_from") dueDate DateTime @default(now())
createDate DateTime @default(now())
} }
// model Invoice {
// id Int @id @default(autoincrement())
// createdAt DateTime @default(now())
// updatedAt DateTime @updatedAt
// //
// sent Int
// taxes Float
// status String
// subtotal Float
// discount Float
// shipping Float
// totalAmount Float
// dueDate DateTime
// invoiceNumber String
// items InvoiceItem[]
// createDate DateTime
// invoiceTo AddressItem[] @relation("invoice_to")
// invoiceFrom AddressItem[] @relation("invoice_from")
// }
// job.ts // job.ts
model JobFilters { model JobFilters {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())

View File

@@ -22,6 +22,10 @@ import { userSeed } from './seeds/user';
import { ProductReview } from './seeds/productReview'; import { ProductReview } from './seeds/productReview';
import { ProductItem } from './seeds/productItem'; import { ProductItem } from './seeds/productItem';
import { FileStore } from './seeds/fileStore'; import { FileStore } from './seeds/fileStore';
import { userItemSeed } from './seeds/userItem';
import { orderItemSeed } from './seeds/orderItem';
import { invoiceItemSeed } from './seeds/invoiceItem';
// //
// import { Blog } from './seeds/blog'; // import { Blog } from './seeds/blog';
// import { Mail } from './seeds/mail'; // import { Mail } from './seeds/mail';
@@ -37,6 +41,9 @@ import { FileStore } from './seeds/fileStore';
await ProductReview; await ProductReview;
await FileStore; await FileStore;
await ProductItem; await ProductItem;
await userItemSeed;
await orderItemSeed;
await invoiceItemSeed;
// await Blog; // await Blog;
// await Mail; // await Mail;
// await File; // await File;

View File

@@ -10,8 +10,8 @@ async function order() {
title: 'Single Party with Dating', title: 'Single Party with Dating',
order_time: new Date(), order_time: new Date(),
last_payment_date: new Date(), last_payment_date: new Date(),
status: 'Pending' status: 'Pending',
} },
}); });
} }

View File

@@ -0,0 +1,187 @@
import { _mock } from './_mock';
// ----------------------------------------------------------------------
export const _carouselsMembers = Array.from({ length: 6 }, (_, index) => ({
id: _mock.id(index),
name: _mock.fullName(index),
role: _mock.role(index),
avatarUrl: _mock.image.portrait(index),
}));
// ----------------------------------------------------------------------
export const _faqs = Array.from({ length: 8 }, (_, index) => ({
id: _mock.id(index),
value: `panel${index + 1}`,
heading: `Questions ${index + 1}`,
detail: _mock.description(index),
}));
// ----------------------------------------------------------------------
export const _addressBooks = Array.from({ length: 24 }, (_, index) => ({
id: _mock.id(index),
primary: index === 0,
name: _mock.fullName(index),
email: _mock.email(index + 1),
fullAddress: _mock.fullAddress(index),
phoneNumber: _mock.phoneNumber(index),
company: _mock.companyNames(index + 1),
addressType: index === 0 ? 'Home' : 'Office',
}));
// ----------------------------------------------------------------------
export const _contacts = Array.from({ length: 20 }, (_, index) => {
const status = (index % 2 && 'online') || (index % 3 && 'offline') || (index % 4 && 'always') || 'busy';
return {
id: _mock.id(index),
status,
role: _mock.role(index),
email: _mock.email(index),
name: _mock.fullName(index),
phoneNumber: _mock.phoneNumber(index),
lastActivity: _mock.time(index),
avatarUrl: _mock.image.avatar(index),
address: _mock.fullAddress(index),
};
});
// ----------------------------------------------------------------------
export const _notifications = Array.from({ length: 9 }, (_, index) => ({
id: _mock.id(index),
avatarUrl: [_mock.image.avatar(1), _mock.image.avatar(2), _mock.image.avatar(3), _mock.image.avatar(4), _mock.image.avatar(5), null, null, null, null, null][
index
],
type: ['friend', 'project', 'file', 'tags', 'payment', 'order', 'delivery', 'chat', 'mail'][index],
category: ['Communication', 'Project UI', 'File manager', 'File manager', 'File manager', 'Order', 'Order', 'Communication', 'Communication'][index],
isUnRead: _mock.boolean(index),
createdAt: _mock.time(index),
title:
(index === 0 && `<p><strong>Deja Brady</strong> sent you a friend request</p>`) ||
(index === 1 && `<p><strong>Jayvon Hull</strong> mentioned you in <strong><a href='#'>Minimal UI</a></strong></p>`) ||
(index === 2 && `<p><strong>Lainey Davidson</strong> added file to <strong><a href='#'>File manager</a></strong></p>`) ||
(index === 3 && `<p><strong>Angelique Morse</strong> added new tags to <strong><a href='#'>File manager<a/></strong></p>`) ||
(index === 4 && `<p><strong>Giana Brandt</strong> request a payment of <strong>$200</strong></p>`) ||
(index === 5 && `<p>Your order is placed waiting for shipping</p>`) ||
(index === 6 && `<p>Delivery processing your order is being shipped</p>`) ||
(index === 7 && `<p>You have new message 5 unread messages</p>`) ||
(index === 8 && `<p>You have new mail`) ||
'',
}));
// ----------------------------------------------------------------------
export const _mapContact = [
{ latlng: [33, 65], address: _mock.fullAddress(1), phoneNumber: _mock.phoneNumber(1) },
{ latlng: [-12.5, 18.5], address: _mock.fullAddress(2), phoneNumber: _mock.phoneNumber(2) },
];
// ----------------------------------------------------------------------
export const _socials = [
{
value: 'facebook',
label: 'Facebook',
path: 'https://www.facebook.com/caitlyn.kerluke',
},
{
value: 'instagram',
label: 'Instagram',
path: 'https://www.instagram.com/caitlyn.kerluke',
},
{
value: 'linkedin',
label: 'Linkedin',
path: 'https://www.linkedin.com/caitlyn.kerluke',
},
{
value: 'twitter',
label: 'Twitter',
path: 'https://www.twitter.com/caitlyn.kerluke',
},
];
// ----------------------------------------------------------------------
export const _pricingPlans = [
{
subscription: 'basic',
price: 0,
caption: 'Forever',
lists: ['3 prototypes', '3 boards', 'Up to 5 team members'],
labelAction: 'Current plan',
},
{
subscription: 'starter',
price: 4.99,
caption: 'Saving $24 a year',
lists: ['3 prototypes', '3 boards', 'Up to 5 team members', 'Advanced security', 'Issue escalation'],
labelAction: 'Choose starter',
},
{
subscription: 'premium',
price: 9.99,
caption: 'Saving $124 a year',
lists: [
'3 prototypes',
'3 boards',
'Up to 5 team members',
'Advanced security',
'Issue escalation',
'Issue development license',
'Permissions & workflows',
],
labelAction: 'Choose premium',
},
];
// ----------------------------------------------------------------------
export const _testimonials = [
{
name: _mock.fullName(1),
postedDate: _mock.time(1),
ratingNumber: _mock.number.rating(1),
avatarUrl: _mock.image.avatar(1),
content: `Excellent Work! Thanks a lot!`,
},
{
name: _mock.fullName(2),
postedDate: _mock.time(2),
ratingNumber: _mock.number.rating(2),
avatarUrl: _mock.image.avatar(2),
content: `It's a very good dashboard and we are really liking the product . We've done some things, like migrate to TS and implementing a react useContext api, to fit our job methodology but the product is one of the best in terms of design and application architecture. The team did a really good job.`,
},
{
name: _mock.fullName(3),
postedDate: _mock.time(3),
ratingNumber: _mock.number.rating(3),
avatarUrl: _mock.image.avatar(3),
content: `Customer support is realy fast and helpful the desgin of this theme is looks amazing also the code is very clean and readble realy good job !`,
},
{
name: _mock.fullName(4),
postedDate: _mock.time(4),
ratingNumber: _mock.number.rating(4),
avatarUrl: _mock.image.avatar(4),
content: `Amazing, really good code quality and gives you a lot of examples for implementations.`,
},
{
name: _mock.fullName(5),
postedDate: _mock.time(5),
ratingNumber: _mock.number.rating(5),
avatarUrl: _mock.image.avatar(5),
content: `Got a few questions after purchasing the product. The owner responded very fast and very helpfull. Overall the code is excellent and works very good. 5/5 stars!`,
},
{
name: _mock.fullName(6),
postedDate: _mock.time(6),
ratingNumber: _mock.number.rating(6),
avatarUrl: _mock.image.avatar(6),
content: `CEO of Codealy.io here. Weve built a developer assessment platform that makes sense - tasks are based on git repositories and run in virtual machines. We automate the pain points - storing candidates code, running it and sharing test results with the whole team, remotely. Bought this template as we need to provide an awesome dashboard for our early customers. I am super happy with purchase. The code is just as good as the design. Thanks!`,
},
];

View File

@@ -0,0 +1,76 @@
import { PrismaClient } from '@prisma/client';
import { _mock } from './_mock';
import { _tags } from './assets';
import { _addressBooks } from './_others';
import { fSub, fAdd } from './utils/format-time';
const prisma = new PrismaClient();
export const INVOICE_SERVICE_OPTIONS = Array.from({ length: 8 }, (_, index) => ({
id: _mock.id(index),
name: _tags[index],
price: _mock.number.price(index),
}));
const ITEMS = Array.from({ length: 3 }, (__, index) => {
const total = INVOICE_SERVICE_OPTIONS[index].price * _mock.number.nativeS(index);
return {
id: _mock.id(index),
total,
title: _mock.productName(index),
description: _mock.sentence(index),
price: INVOICE_SERVICE_OPTIONS[index].price,
service: INVOICE_SERVICE_OPTIONS[index].name,
quantity: _mock.number.nativeS(index),
};
});
async function invoiceItem() {
await prisma.orderItem.deleteMany({});
for (let index = 1; index < 3 + 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 subtotal = items.reduce((accumulator, item) => accumulator + item.price * item.quantity, 0);
const totalAmount = subtotal - shipping - discount + taxes;
const temp = await prisma.invoiceItem.upsert({
where: { id: index.toString() },
update: {},
create: {
id: index.toString(),
taxes,
status: (index % 2 && 'paid') || (index % 3 && 'pending') || (index % 4 && 'overdue') || 'draft',
discount,
shipping,
subtotal: items.reduce((accumulator, item) => accumulator + item.price * item.quantity, 0),
totalAmount,
items,
invoiceNumber: `INV-199${index}`,
invoiceFrom: _addressBooks[index],
invoiceTo: _addressBooks[index + 1],
sent: _mock.number.nativeS(index),
dueDate: new Date(fAdd({ days: index + 15, hours: index })),
createDate: new Date(fAdd({ days: index + 15, hours: index })),
},
});
}
console.log('seed invoiceItem done');
}
const invoiceItemSeed = invoiceItem()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { invoiceItemSeed };

View 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 orderItem() {
await prisma.orderItem.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.orderItem.upsert({
where: { id: index.toString() },
update: {},
create: {
id: index.toString(),
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 orderItem done');
}
const orderItemSeed = orderItem()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { orderItemSeed };

View File

@@ -8,8 +8,8 @@ async function user() {
create: { create: {
email: 'alice@prisma.io', email: 'alice@prisma.io',
name: 'Alice', name: 'Alice',
password: 'Aa12345678' password: 'Aa12345678',
} },
}); });
const bob = await prisma.user.upsert({ const bob = await prisma.user.upsert({
@@ -18,8 +18,8 @@ async function user() {
create: { create: {
email: 'bob@prisma.io', email: 'bob@prisma.io',
name: 'Bob', name: 'Bob',
password: 'Aa12345678' password: 'Aa12345678',
} },
}); });
console.log('seed user done'); console.log('seed user done');
} }

View File

@@ -0,0 +1,126 @@
import { PrismaClient } from '@prisma/client';
import { generateHash } from 'src/utils/hash';
import { Config, names, uniqueNamesGenerator } from 'unique-names-generator';
import { faker } from '@faker-js/faker';
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';
const SEED_EMAIL_DOMAIN = 'seed.com';
const prisma = new PrismaClient();
async function userItem() {
const config: Config = { dictionaries: [names] };
const firstName = uniqueNamesGenerator(config);
const lastName = uniqueNamesGenerator(config);
const username = `${firstName.toLowerCase()}-${lastName.toLowerCase()}`;
const alice = await prisma.userItem.upsert({
where: { id: 'admin_uuid' },
update: {},
create: {
name: `admin test`,
city: '',
role: '',
email: `admin@123.com`,
state: '',
status: '',
address: '',
country: '',
zipCode: '',
company: '',
avatarUrl: '',
phoneNumber: '',
isVerified: true,
//
username: 'admin@123.com',
password: await generateHash('Aa1234567'),
},
});
for (let i = 1; i < 20; 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.userItem.upsert({
where: { id: i.toString() },
update: {},
create: {
name: randomFaker.person.fullName(),
city: randomFaker.location.city(),
role: ROLE[Math.floor(Math.random() * ROLE.length)],
email: randomFaker.internet.email(),
state: randomFaker.location.state(),
status: STATUS[Math.floor(Math.random() * STATUS.length)],
address: randomFaker.location.streetAddress(),
country: randomFaker.location.country(),
zipCode: randomFaker.location.zipCode(),
company: randomFaker.company.name(),
avatarUrl: randomFaker.image.avatar(),
phoneNumber: randomFaker.phone.number(),
isVerified: true,
//
username: randomFaker.internet.username(),
password: await generateHash('Abc1234!'),
},
});
}
console.log('seed user done');
}
const userItemSeed = userItem()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { userItemSeed };
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'];

View File

@@ -0,0 +1,257 @@
import type { Dayjs, OpUnitType } from 'dayjs';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
// ----------------------------------------------------------------------
/**
* @Docs
* https://day.js.org/docs/en/display/format
*/
/**
* Default timezones
* https://day.js.org/docs/en/timezone/set-default-timezone#docsNav
*
*/
/**
* UTC
* https://day.js.org/docs/en/plugin/utc
* @install
* import utc from 'dayjs/plugin/utc';
* dayjs.extend(utc);
* @usage
* dayjs().utc().format()
*
*/
dayjs.extend(duration);
dayjs.extend(relativeTime);
// ----------------------------------------------------------------------
export type DatePickerFormat = Dayjs | Date | string | number | null | undefined;
export const formatPatterns = {
dateTime: 'DD MMM YYYY h:mm a', // 17 Apr 2022 12:00 am
date: 'DD MMM YYYY', // 17 Apr 2022
time: 'h:mm a', // 12:00 am
split: {
dateTime: 'DD/MM/YYYY h:mm a', // 17/04/2022 12:00 am
date: 'DD/MM/YYYY', // 17/04/2022
},
paramCase: {
dateTime: 'DD-MM-YYYY h:mm a', // 17-04-2022 12:00 am
date: 'DD-MM-YYYY', // 17-04-2022
},
};
const isValidDate = (date: DatePickerFormat) => date !== null && date !== undefined && dayjs(date).isValid();
// ----------------------------------------------------------------------
export function today(template?: string): string {
return dayjs(new Date()).startOf('day').format(template);
}
// ----------------------------------------------------------------------
/**
* @output 17 Apr 2022 12:00 am
*/
export function fDateTime(date: DatePickerFormat, template?: string): string {
if (!isValidDate(date)) {
return 'Invalid date';
}
return dayjs(date).format(template ?? formatPatterns.dateTime);
}
// ----------------------------------------------------------------------
/**
* @output 17 Apr 2022
*/
export function fDate(date: DatePickerFormat, template?: string): string {
if (!isValidDate(date)) {
return 'Invalid date';
}
return dayjs(date).format(template ?? formatPatterns.date);
}
// ----------------------------------------------------------------------
/**
* @output 12:00 am
*/
export function fTime(date: DatePickerFormat, template?: string): string {
if (!isValidDate(date)) {
return 'Invalid date';
}
return dayjs(date).format(template ?? formatPatterns.time);
}
// ----------------------------------------------------------------------
/**
* @output 1713250100
*/
export function fTimestamp(date: DatePickerFormat): number | 'Invalid date' {
if (!isValidDate(date)) {
return 'Invalid date';
}
return dayjs(date).valueOf();
}
// ----------------------------------------------------------------------
/**
* @output a few seconds, 2 years
*/
export function fToNow(date: DatePickerFormat): string {
if (!isValidDate(date)) {
return 'Invalid date';
}
return dayjs(date).toNow(true);
}
// ----------------------------------------------------------------------
/**
* @output boolean
*/
export function fIsBetween(inputDate: DatePickerFormat, startDate: DatePickerFormat, endDate: DatePickerFormat): boolean {
if (!isValidDate(inputDate) || !isValidDate(startDate) || !isValidDate(endDate)) {
return false;
}
const formattedInputDate = fTimestamp(inputDate);
const formattedStartDate = fTimestamp(startDate);
const formattedEndDate = fTimestamp(endDate);
if (formattedInputDate === 'Invalid date' || formattedStartDate === 'Invalid date' || formattedEndDate === 'Invalid date') {
return false;
}
return formattedInputDate >= formattedStartDate && formattedInputDate <= formattedEndDate;
}
// ----------------------------------------------------------------------
/**
* @output boolean
*/
export function fIsAfter(startDate: DatePickerFormat, endDate: DatePickerFormat): boolean {
if (!isValidDate(startDate) || !isValidDate(endDate)) {
return false;
}
return dayjs(startDate).isAfter(endDate);
}
// ----------------------------------------------------------------------
/**
* @output boolean
*/
export function fIsSame(startDate: DatePickerFormat, endDate: DatePickerFormat, unitToCompare?: OpUnitType): boolean {
if (!isValidDate(startDate) || !isValidDate(endDate)) {
return false;
}
return dayjs(startDate).isSame(endDate, unitToCompare ?? 'year');
}
/**
* @output
* Same day: 26 Apr 2024
* Same month: 25 - 26 Apr 2024
* Same month: 25 - 26 Apr 2024
* Same year: 25 Apr - 26 May 2024
*/
export function fDateRangeShortLabel(startDate: DatePickerFormat, endDate: DatePickerFormat, initial?: boolean): string {
if (!isValidDate(startDate) || !isValidDate(endDate) || fIsAfter(startDate, endDate)) {
return 'Invalid date';
}
let label = `${fDate(startDate)} - ${fDate(endDate)}`;
if (initial) {
return label;
}
const isSameYear = fIsSame(startDate, endDate, 'year');
const isSameMonth = fIsSame(startDate, endDate, 'month');
const isSameDay = fIsSame(startDate, endDate, 'day');
if (isSameYear && !isSameMonth) {
label = `${fDate(startDate, 'DD MMM')} - ${fDate(endDate)}`;
} else if (isSameYear && isSameMonth && !isSameDay) {
label = `${fDate(startDate, 'DD')} - ${fDate(endDate)}`;
} else if (isSameYear && isSameMonth && isSameDay) {
label = `${fDate(endDate)}`;
}
return label;
}
// ----------------------------------------------------------------------
/**
* @output 2024-05-28T05:55:31+00:00
*/
export type DurationProps = {
years?: number;
months?: number;
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
milliseconds?: number;
};
export function fAdd({ years = 0, months = 0, days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0 }: DurationProps) {
const result = dayjs()
.add(
dayjs.duration({
years,
months,
days,
hours,
minutes,
seconds,
milliseconds,
})
)
.format();
return result;
}
/**
* @output 2024-05-28T05:55:31+00:00
*/
export function fSub({ years = 0, months = 0, days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0 }: DurationProps) {
const result = dayjs()
.subtract(
dayjs.duration({
years,
months,
days,
hours,
minutes,
seconds,
milliseconds,
})
)
.format();
return result;
}

View File

@@ -2,14 +2,9 @@ import type { NextRequest, NextResponse } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response'; import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../lib/prisma';
export async function GET(req: NextRequest, res: NextResponse) { export async function GET(req: NextRequest, res: NextResponse) {
try { try {
const users = await prisma.user.findMany(); return response({ hello: 'world' }, STATUS.OK);
console.log({ users });
return response({ users }, STATUS.OK);
} catch (error) { } catch (error) {
return handleError('Post - Get latest', error); return handleError('Post - Get latest', error);
} }

View File

@@ -0,0 +1,4 @@
###
GET http://localhost:7272/api/helloworld

View File

@@ -0,0 +1,98 @@
// src/app/api/product/saveProduct/route.ts
//
// PURPOSE:
// save product to db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* PUT - change order status
*************************************** */
export async function PUT(req: NextRequest) {
// logger('[Invoice] list', products.length);
const { searchParams } = req.nextUrl;
const invoiceId = searchParams.get('invoiceId');
// RULES: invoiceId must exist
if (!invoiceId) {
return response({ message: 'Invoice ID is required!' }, STATUS.BAD_REQUEST);
}
const { data } = await req.json();
try {
const order = await prisma.invoiceItem.updateMany({
where: { id: invoiceId },
data: { status: data.status },
});
return response({ order }, STATUS.OK);
} catch (error) {
console.log({ data });
return handleError('Invoice - Get list', 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[];
};

View File

@@ -0,0 +1,9 @@
###
PUT http://localhost:7272/api/invoice/changeStatus?orderId=1
content-type: application/json
{
"data":{"status": "helloworld"}
}

View File

@@ -0,0 +1,53 @@
// src/app/api/user/createUser/route.ts
//
// PURPOSE:
// create user to db
//
// RULES:
// T.B.A.
//
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/**
***************************************
* POST - create User
***************************************
*/
export async function POST(req: NextRequest) {
// logger('[User] list', users.length);
const { data } = await req.json();
const createForm: CreateUserData = data as unknown as CreateUserData;
try {
const user = await prisma.userItem.create({ data: createForm });
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('User - Create', error);
}
}
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;
};

View File

@@ -0,0 +1,4 @@
###
POST http://localhost:7272/api/user/createUser

View File

@@ -0,0 +1,47 @@
// src/app/api/product/deleteUser/route.ts
//
// PURPOSE:
// delete product 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 prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* handle Delete Users
*************************************** */
export async function DELETE(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
// RULES: userId must exist
const userId = searchParams.get('userId');
if (!userId) {
return response({ message: 'User ID is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: userId confirmed exist, run below
const user = await prisma.userItem.delete({
//
where: { id: userId },
});
if (!user) {
return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
}
logger('[User] details', user.id);
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('User - Get details', error);
}
}

View File

@@ -0,0 +1,3 @@
###
DELETE http://localhost:7272/api/user/deleteUser?userId=3f431e6f-ad05-4d60-9c25-6a7e92a954ad

View File

@@ -0,0 +1,47 @@
// src/app/api/invoice/details/route.ts
//
// PURPOSE:
// read invoice 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 prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET Invoice detail
*************************************** */
export async function GET(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
// RULES: invoiceId must exist
const invoiceId = searchParams.get('invoiceId');
if (!invoiceId) {
return response({ message: 'invoiceId is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: invoiceId confirmed exist, run below
const invoice = await prisma.invoiceItem.findFirst({
// include: { reviews: true },
where: { id: invoiceId.toString() },
});
if (!invoice) {
return response({ message: 'Invoice not found!' }, STATUS.NOT_FOUND);
}
logger('[Invoice] details', invoice.id);
return response({ invoice }, STATUS.OK);
} catch (error) {
return handleError('Product - Get details', error);
}
}

View File

@@ -0,0 +1,4 @@
###
GET http://localhost:7272/api/invoice/details?invoiceId=1

View File

@@ -0,0 +1,30 @@
// 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);
}
}

View File

@@ -0,0 +1,22 @@
// src/app/api/invoice/list/route.ts
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET - InvoiceItem
*************************************** */
export async function GET() {
try {
const invoices = await prisma.invoiceItem.findMany();
logger('[Invoice] list', invoices.length);
return response({ invoices }, STATUS.OK);
} catch (error) {
return handleError('InvoiceItem - Get list', error);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/invoice/list

View File

@@ -0,0 +1,98 @@
// src/app/api/invoice/saveInvoice/route.ts
//
// PURPOSE:
// save invoice to db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* PUT - Update invoice
*************************************** */
export async function PUT(req: NextRequest) {
// logger('[Invoice] list', invoices.length);
const { searchParams } = req.nextUrl;
const invoiceId = searchParams.get('invoiceId');
// RULES: invoiceId must exist
if (!invoiceId) {
return response({ message: 'Invoice ID is required!' }, STATUS.BAD_REQUEST);
}
const { data } = await req.json();
try {
const invoice = await prisma.invoiceItem.updateMany({
where: { id: invoiceId },
data,
});
return response({ invoice }, STATUS.OK);
} catch (error) {
console.log({ data });
return handleError('Invoice - Update invoice', error);
}
}
export type IInvoiceItem = {
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: IInvoiceReview[];
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 IInvoiceReview = {
id: string;
name: string;
rating: number;
comment: string;
helpful: number;
avatarUrl: string;
postedAt: IDateValue;
isPurchased: boolean;
attachments?: string[];
};

View File

@@ -0,0 +1,58 @@
###
PUT http://localhost:7272/api/invoice/saveInvoice?invoiceId=1
content-type: application/json
{
"data": {
"id": "1",
"taxes": 10,
"status": "paid",
"discount": 10,
"shipping": 10,
"subtotal": 921.14,
"totalAmount": 993.254,
"items": [
{
"title": "Urban Explorer Sneakers 1111",
"service": "Technology",
"quantity": 11,
"price": 83.74,
"total": 921.14,
"description": "The sun slowly set over the horizon, painting the sky in vibrant hues of orange and pink."
},
{
"price": 83.74,
"title": "Urban Explorer Sneakers 22222",
"total": 921.14,
"service": "Technology",
"quantity": 11,
"description": "The sun slowly set over the horizon, painting the sky in vibrant hues of orange and pink."
}
],
"invoiceNumber": "INV-1991",
"invoiceFrom": {
"id": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b02",
"name": "Lucian Obrien",
"email": "milo.farrell@hotmail.com",
"company": "Nikolaus - Leuschke",
"primary": false,
"addressType": "Office",
"fullAddress": "1147 Rohan Drive Suite 819 - Burlington, VT / 82021",
"phoneNumber": "+1 416-555-0198"
},
"invoiceTo": {
"id": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b03",
"name": "Deja Brady",
"email": "violet.ratke86@yahoo.com",
"company": "Hegmann, Kreiger and Bayer",
"primary": false,
"addressType": "Office",
"fullAddress": "18605 Thompson Circle Apt. 086 - Idaho Falls, WV / 50337",
"phoneNumber": "+44 20 7946 0958"
},
"sent": 10,
"createDate": "2025-06-15T17:07:24+08:00",
"dueDate": "2025-06-15T17:07:24+08:00"
}
}

View File

@@ -0,0 +1,37 @@
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);
}
}

View File

@@ -0,0 +1,98 @@
// src/app/api/product/saveProduct/route.ts
//
// PURPOSE:
// save product to db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* PUT - change order status
*************************************** */
export async function PUT(req: NextRequest) {
// logger('[Order] list', products.length);
const { searchParams } = req.nextUrl;
const orderId = searchParams.get('orderId');
// RULES: orderId must exist
if (!orderId) {
return response({ message: 'Order ID is required!' }, STATUS.BAD_REQUEST);
}
const { data } = await req.json();
try {
const order = await prisma.orderItem.updateMany({
where: { id: orderId },
data: { status: data.status },
});
return response({ order }, STATUS.OK);
} catch (error) {
console.log({ data });
return handleError('Order - Get list', 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[];
};

View File

@@ -0,0 +1,9 @@
###
PUT http://localhost:7272/api/order/changeStatus?orderId=1
content-type: application/json
{
"data":{"status": "helloworld"}
}

View File

@@ -0,0 +1,53 @@
// src/app/api/user/createUser/route.ts
//
// PURPOSE:
// create user to db
//
// RULES:
// T.B.A.
//
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/**
***************************************
* POST - create User
***************************************
*/
export async function POST(req: NextRequest) {
// logger('[User] list', users.length);
const { data } = await req.json();
const createForm: CreateUserData = data as unknown as CreateUserData;
try {
const user = await prisma.userItem.create({ data: createForm });
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('User - Create', error);
}
}
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;
};

View File

@@ -0,0 +1,4 @@
###
POST http://localhost:7272/api/user/createUser

View File

@@ -0,0 +1,47 @@
// src/app/api/product/deleteUser/route.ts
//
// PURPOSE:
// delete product 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 prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* handle Delete Users
*************************************** */
export async function DELETE(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
// RULES: userId must exist
const userId = searchParams.get('userId');
if (!userId) {
return response({ message: 'User ID is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: userId confirmed exist, run below
const user = await prisma.userItem.delete({
//
where: { id: userId },
});
if (!user) {
return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
}
logger('[User] details', user.id);
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('User - Get details', error);
}
}

View File

@@ -0,0 +1,3 @@
###
DELETE http://localhost:7272/api/user/deleteUser?userId=3f431e6f-ad05-4d60-9c25-6a7e92a954ad

View File

@@ -0,0 +1,47 @@
// src/app/api/order/details/route.ts
//
// PURPOSE:
// read order 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 prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET Order detail
*************************************** */
export async function GET(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
// RULES: orderId must exist
const orderId = searchParams.get('orderId');
if (!orderId) {
return response({ message: 'orderId is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: orderId confirmed exist, run below
const order = await prisma.orderItem.findFirst({
// include: { reviews: true },
where: { id: orderId.toString() },
});
if (!order) {
return response({ message: 'Order not found!' }, STATUS.NOT_FOUND);
}
logger('[Order] details', order.id);
return response({ order }, STATUS.OK);
} catch (error) {
return handleError('Product - Get details', error);
}
}

View File

@@ -0,0 +1,4 @@
###
GET http://localhost:7272/api/order/details?orderId=1

View File

@@ -0,0 +1,30 @@
// 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);
}
}

View File

@@ -0,0 +1,22 @@
// src/app/api/order/list/route.ts
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET - OrderItem
*************************************** */
export async function GET() {
try {
const orders = await prisma.orderItem.findMany();
logger('[Order] list', orders.length);
return response({ orders }, STATUS.OK);
} catch (error) {
return handleError('OrderItem - Get list', error);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/order/list

View File

@@ -0,0 +1,115 @@
// src/app/api/product/saveProduct/route.ts
//
// PURPOSE:
// save product to db by id
//
// 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) {
// logger('[Product] list', products.length);
const { searchParams } = req.nextUrl;
const userId = searchParams.get('userId');
// RULES: userId must exist
if (!userId) {
return response({ message: 'Product ID is required!' }, STATUS.BAD_REQUEST);
}
const { data } = await req.json();
try {
const user = await prisma.userItem.updateMany({
where: { id: userId },
data: {
status: data.status,
avatarUrl: data.avatarUrl,
isVerified: data.isVerified,
name: data.name,
email: data.email,
phoneNumber: data.phoneNumber,
country: data.country,
state: data.state,
city: data.city,
address: data.address,
zipCode: data.zipCode,
company: data.company,
role: data.role,
//
username: data.username,
password: data.password,
},
});
return response({ user }, STATUS.OK);
} catch (error) {
console.log({ hello: 'world', data });
return handleError('Product - Get list', 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[];
};

View File

@@ -0,0 +1,3 @@
###
POST http://localhost:7272/api/user/list

View File

@@ -0,0 +1,37 @@
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);
}
}

View File

@@ -66,9 +66,9 @@ export async function POST(req: NextRequest) {
where: { id: data.id }, where: { id: data.id },
}); });
return response({ hello: 'world', data }, STATUS.OK); return response({ data }, STATUS.OK);
} catch (error) { } catch (error) {
console.log({ hello: 'world', data }); console.log({ data });
return handleError('Product - Get list', error); return handleError('Product - Get list', error);
} }
} }

View File

@@ -24,9 +24,7 @@ export async function GET(req: NextRequest) {
const products = _products(); const products = _products();
// Accept search by name or sku // Accept search by name or sku
const results = products.filter( const results = products.filter(({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query));
({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query)
);
logger('[Product] search-results', results.length); logger('[Product] search-results', results.length);

View File

@@ -0,0 +1,53 @@
// src/app/api/user/createUser/route.ts
//
// PURPOSE:
// create user to db
//
// RULES:
// T.B.A.
//
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/**
***************************************
* POST - create User
***************************************
*/
export async function POST(req: NextRequest) {
// logger('[User] list', users.length);
const { data } = await req.json();
const createForm: CreateUserData = data as unknown as CreateUserData;
try {
const user = await prisma.userItem.create({ data: createForm });
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('User - Create', error);
}
}
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;
};

View File

@@ -0,0 +1,4 @@
###
POST http://localhost:7272/api/user/createUser

View File

@@ -0,0 +1,47 @@
// src/app/api/product/deleteUser/route.ts
//
// PURPOSE:
// delete product 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 prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* handle Delete Users
*************************************** */
export async function DELETE(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
// RULES: userId must exist
const userId = searchParams.get('userId');
if (!userId) {
return response({ message: 'User ID is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: userId confirmed exist, run below
const user = await prisma.userItem.delete({
//
where: { id: userId },
});
if (!user) {
return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
}
logger('[User] details', user.id);
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('User - Get details', error);
}
}

View File

@@ -0,0 +1,3 @@
###
DELETE http://localhost:7272/api/user/deleteUser?userId=3f431e6f-ad05-4d60-9c25-6a7e92a954ad

View File

@@ -0,0 +1,47 @@
// src/app/api/product/details/route.ts
//
// PURPOSE:
// read user 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 prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET User detail
*************************************** */
export async function GET(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
// RULES: userId must exist
const userId = searchParams.get('userId');
if (!userId) {
return response({ message: 'userId is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: userId confirmed exist, run below
const user = await prisma.userItem.findFirst({
// include: { reviews: true },
where: { id: userId },
});
if (!user) {
return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
}
logger('[User] details', user.id);
return response({ user }, STATUS.OK);
} catch (error) {
return handleError('Product - Get details', error);
}
}

View File

@@ -0,0 +1,4 @@
###
GET http://localhost:7272/api/user/details?userId=1165ce3a-29b8-4e1a-9148-1ae08d7e8e01

View File

@@ -0,0 +1,30 @@
// 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);
}
}

View File

@@ -0,0 +1,22 @@
// src/app/api/product/list/route.ts
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import prisma from '../../../lib/prisma';
// ----------------------------------------------------------------------
/** **************************************
* GET - Products
*************************************** */
export async function GET() {
try {
const users = await prisma.userItem.findMany();
logger('[User] list', users.length);
return response({ users }, STATUS.OK);
} catch (error) {
return handleError('Product - Get list', error);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/user/list

View File

@@ -0,0 +1,115 @@
// src/app/api/product/saveProduct/route.ts
//
// PURPOSE:
// save product to db by id
//
// 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) {
// logger('[Product] list', products.length);
const { searchParams } = req.nextUrl;
const userId = searchParams.get('userId');
// RULES: userId must exist
if (!userId) {
return response({ message: 'Product ID is required!' }, STATUS.BAD_REQUEST);
}
const { data } = await req.json();
try {
const user = await prisma.userItem.updateMany({
where: { id: userId },
data: {
status: data.status,
avatarUrl: data.avatarUrl,
isVerified: data.isVerified,
name: data.name,
email: data.email,
phoneNumber: data.phoneNumber,
country: data.country,
state: data.state,
city: data.city,
address: data.address,
zipCode: data.zipCode,
company: data.company,
role: data.role,
//
username: data.username,
password: data.password,
},
});
return response({ user }, STATUS.OK);
} catch (error) {
console.log({ hello: 'world', data });
return handleError('Product - Get list', 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[];
};

View File

@@ -0,0 +1,3 @@
###
POST http://localhost:7272/api/user/list

View File

@@ -0,0 +1,37 @@
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);
}
}

View File

@@ -411,6 +411,11 @@
"@eslint/core" "^0.12.0" "@eslint/core" "^0.12.0"
levn "^0.4.1" levn "^0.4.1"
"@faker-js/faker@^9.8.0":
version "9.8.0"
resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-9.8.0.tgz#3344284028d1c9dc98dee2479f82939310370d88"
integrity sha512-U9wpuSrJC93jZBxx/Qq2wPjCuYISBueyVUGK7qqdmj7r/nxaxwW8AQDCLeRO7wZnjj94sh3p246cAYjUKuqgfg==
"@humanfs/core@^0.19.1": "@humanfs/core@^0.19.1":
version "0.19.1" version "0.19.1"
resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz" resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz"

View File

@@ -3,7 +3,7 @@
set -ex set -ex
# -f docker-compose.db.yml # -f docker-compose.db.yml
DOCKER_COMPOSE_FILES=" -f docker-compose.yml " DOCKER_COMPOSE_FILES=" -f docker-compose.yml -f docker-compose.dev.yml"
# docker compose $DOCKER_COMPOSE_FILES build # docker compose $DOCKER_COMPOSE_FILES build
docker compose $DOCKER_COMPOSE_FILES up -d docker compose $DOCKER_COMPOSE_FILES up -d

15
03_source/docker/03_mobile.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -ex
# -f docker-compose.db.yml
DOCKER_COMPOSE_FILES=" -f docker-compose.yml "
docker compose $DOCKER_COMPOSE_FILES exec -it mobile bash
# cd ../api_server
# yarn docker:dev
# cd ..
# docker compose $DOCKER_COMPOSE_FILES logs -f

View File

@@ -0,0 +1,9 @@
services:
frontend:
command: "sleep infinity"
mobile:
command: "sleep infinity"
cms_backend:
command: "sleep infinity"

View File

@@ -12,8 +12,7 @@ services:
volumes: volumes:
- ../frontend:/app - ../frontend:/app
working_dir: "/app" working_dir: "/app"
# command: "yarn dev" command: "yarn dev"
command: "sleep infinity"
mobile: mobile:
image: 192.168.10.61:5000/hksingleparty_mobile image: 192.168.10.61:5000/hksingleparty_mobile
@@ -38,8 +37,7 @@ services:
volumes: volumes:
- ../cms_backend:/app - ../cms_backend:/app
working_dir: "/app" working_dir: "/app"
# command: "yarn dev" command: "yarn dev"
command: "sleep infinity"
postgres: postgres:
container_name: postgres container_name: postgres

View File

@@ -10,7 +10,10 @@ const config = {
printWidth: 100, printWidth: 100,
singleQuote: true, singleQuote: true,
trailingComma: 'es5', trailingComma: 'es5',
plugins: ['@ianvs/prettier-plugin-sort-imports'], plugins: [
//
// '@ianvs/prettier-plugin-sort-imports',
],
}; };
export default config; export default config;

View File

@@ -75,7 +75,11 @@ export const _orders = Array.from({ length: 20 }, (_, index) => {
fullAddress: '19034 Verna Unions Apt. 164 - Honolulu, RI / 87535', fullAddress: '19034 Verna Unions Apt. 164 - Honolulu, RI / 87535',
phoneNumber: '365-374-4961', phoneNumber: '365-374-4961',
}, },
payment: { cardType: 'mastercard', cardNumber: '**** **** **** 5678' }, payment: {
//
cardType: 'mastercard',
cardNumber: '**** **** **** 5678',
},
status: status:
(index % 2 && 'completed') || (index % 2 && 'completed') ||
(index % 3 && 'pending') || (index % 3 && 'pending') ||

View File

@@ -0,0 +1,221 @@
// src/actions/invoice.ts
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import type { IInvoiceItem } from 'src/types/invoice';
import type { SWRConfiguration } from 'swr';
import useSWR from 'swr';
// ----------------------------------------------------------------------
const swrOptions: SWRConfiguration = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
// ----------------------------------------------------------------------
type InvoicesData = {
invoices: IInvoiceItem[];
};
export function useGetInvoices() {
const url = endpoints.invoice.list;
const { data, isLoading, error, isValidating, mutate } = useSWR<InvoicesData>(
url,
fetcher,
swrOptions
);
const memoizedValue = useMemo(
() => ({
invoices: data?.invoices || [],
invoicesLoading: isLoading,
invoicesError: error,
invoicesValidating: isValidating,
invoicesEmpty: !isLoading && !isValidating && !data?.invoices.length,
mutate,
}),
[data?.invoices, error, isLoading, isValidating, mutate]
);
return memoizedValue;
}
// ----------------------------------------------------------------------
type InvoiceData = {
invoice: IInvoiceItem;
};
export function useGetInvoice(invoiceId: string) {
const url = invoiceId ? [endpoints.invoice.details, { params: { invoiceId } }] : '';
const { data, isLoading, error, isValidating } = useSWR<InvoiceData>(url, fetcher, swrOptions);
const memoizedValue = useMemo(
() => ({
currentInvoice: data?.invoice,
invoiceLoading: isLoading,
invoiceError: error,
invoiceValidating: isValidating,
}),
[data?.invoice, error, isLoading, isValidating]
);
return memoizedValue;
}
// ----------------------------------------------------------------------
type SearchResultsData = {
results: IInvoiceItem[];
};
export function useSearchInvoices(query: string) {
const url = query ? [endpoints.invoice.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 SaveInvoiceData = IInvoiceItem;
export async function saveInvoice(invoiceId: string, saveInvoiceData: SaveInvoiceData) {
const url = endpoints.invoice.saveInvoice(invoiceId);
const res = await axiosInstance.put(url, { data: saveInvoiceData });
return res;
}
export async function uploadInvoiceImage(saveInvoiceData: SaveInvoiceData) {
console.log('save invoice ?');
// const url = invoiceId ? [endpoints.invoice.details, { params: { invoiceId } }] : '';
const res = await axiosInstance.get('http://localhost:7272/api/invoice/helloworld');
return res;
}
// ----------------------------------------------------------------------
type CreateInvoiceData = {
// id: string;
sku: string;
name: string;
code: string;
price: number | null;
taxes: number | null;
tags: string[];
sizes: string[];
publish: string;
gender: string[];
coverUrl: string;
images: (string | File)[];
colors: string[];
quantity: number | null;
category: string;
available: number;
totalSold: number;
description: string;
totalRatings: number;
totalReviews: number;
inventoryType: string;
subDescription: string;
priceSale: number | null;
newLabel: {
content: string;
enabled: boolean;
};
saleLabel: {
content: string;
enabled: boolean;
};
// ratings: {
// name: string;
// starCount: number;
// reviewCount: number;
// }[];
};
export async function createInvoice(createInvoiceData: CreateInvoiceData) {
console.log('create invoice ?');
// const url = invoiceId ? [endpoints.invoice.details, { params: { invoiceId } }] : '';
const res = await axiosInstance.post('http://localhost:7272/api/invoice/createInvoice', {
data: createInvoiceData,
});
return res;
}
// ----------------------------------------------------------------------
type DeleteInvoiceResponse = {
success: boolean;
message?: string;
};
export async function deleteInvoice(invoiceId: string): Promise<DeleteInvoiceResponse> {
const url = `http://localhost:7272/api/invoice/deleteInvoice?invoiceId=${invoiceId}`;
try {
const res = await axiosInstance.delete(url);
console.log({ res });
return {
success: true,
message: 'Invoice deleted successfully',
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to delete invoice',
};
}
}
// ----------------------------------------------------------------------
type ChangeStatusResponse = {
success: boolean;
message?: string;
};
export async function changeStatus(
invoiceId: string,
newStatus: string
): Promise<ChangeStatusResponse> {
const url = endpoints.invoice.changeStatus(invoiceId);
try {
const res = await axiosInstance.put(url, { data: { status: newStatus } });
return {
success: true,
message: 'status updated successfully',
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to delete product',
};
}
}

View File

@@ -0,0 +1,226 @@
// src/actions/order.ts
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import type { IProductItem } from 'src/types/product';
import type { IOrderItem } from 'src/types/order';
import type { SWRConfiguration } from 'swr';
import useSWR from 'swr';
// ----------------------------------------------------------------------
const swrOptions: SWRConfiguration = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
// ----------------------------------------------------------------------
type OrdersData = {
orders: IOrderItem[];
};
export function useGetOrders() {
const url = endpoints.order.list;
const { data, isLoading, error, isValidating, mutate } = useSWR<OrdersData>(
url,
fetcher,
swrOptions
);
const memoizedValue = useMemo(
() => ({
orders: data?.orders || [],
ordersLoading: isLoading,
ordersError: error,
ordersValidating: isValidating,
ordersEmpty: !isLoading && !isValidating && !data?.orders.length,
mutate,
}),
[data?.orders, error, isLoading, isValidating, mutate]
);
return memoizedValue;
}
// ----------------------------------------------------------------------
type OrderData = {
order: IOrderItem;
};
export function useGetOrder(orderId: string) {
const url = orderId ? [endpoints.order.details, { params: { orderId } }] : '';
const { data, isLoading, error, isValidating } = useSWR<OrderData>(url, fetcher, swrOptions);
const memoizedValue = useMemo(
() => ({
order: data?.order,
orderLoading: isLoading,
orderError: error,
orderValidating: isValidating,
}),
[data?.order, error, isLoading, isValidating]
);
return memoizedValue;
}
// ----------------------------------------------------------------------
type SearchResultsData = {
results: IProductItem[];
};
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 SaveOrderData = {
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;
//
ordername: string;
password: string;
};
export async function saveOrder(orderId: string, saveOrderData: SaveOrderData) {
// const url = orderId ? [endpoints.order.details, { params: { orderId } }] : '';
const res = await axiosInstance.post(
//
`http://localhost:7272/api/order/saveOrder?orderId=${orderId}`,
{
data: saveOrderData,
}
);
return res;
}
export async function uploadOrderImage(saveOrderData: SaveOrderData) {
console.log('uploadOrderImage ?');
// const url = orderId ? [endpoints.order.details, { params: { orderId } }] : '';
const res = await axiosInstance.get('http://localhost:7272/api/product/helloworld');
return res;
}
// ----------------------------------------------------------------------
type CreateOrderData = {
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;
//
ordername: string;
password: string;
};
export async function createOrder(createOrderData: CreateOrderData) {
console.log('create product ?');
// const url = productId ? [endpoints.product.details, { params: { productId } }] : '';
const res = await axiosInstance.post('http://localhost:7272/api/order/createOrder', {
data: createOrderData,
});
return res;
}
// ----------------------------------------------------------------------
type DeleteOrderResponse = {
success: boolean;
message?: string;
};
export async function deleteOrder(orderId: string): Promise<DeleteOrderResponse> {
const url = `http://localhost:7272/api/order/deleteOrder?orderId=${orderId}`;
try {
const res = await axiosInstance.delete(url);
return {
success: true,
message: 'Order deleted successfully',
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to delete product',
};
}
}
// ----------------------------------------------------------------------
type ChangeStatusResponse = {
success: boolean;
message?: string;
};
export async function changeStatus(
orderId: string,
newOrderStatus: string
): Promise<ChangeStatusResponse> {
const url = endpoints.order.changeStatus(orderId);
try {
const res = await axiosInstance.put(url, { data: { status: newOrderStatus } });
return {
success: true,
message: 'status updated successfully',
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to delete product',
};
}
}

View File

@@ -1,3 +1,4 @@
// src/actions/product.ts
import { useMemo } from 'react'; import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios'; import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import type { IProductItem } from 'src/types/product'; import type { IProductItem } from 'src/types/product';
@@ -55,7 +56,7 @@ export function useGetProduct(productId: string) {
const memoizedValue = useMemo( const memoizedValue = useMemo(
() => ({ () => ({
product: data?.product, currentProduct: data?.product,
productLoading: isLoading, productLoading: isLoading,
productError: error, productError: error,
productValidating: isValidating, productValidating: isValidating,

View File

@@ -0,0 +1,197 @@
import { useMemo } from 'react';
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
import type { IProductItem } from 'src/types/product';
import { IUserItem } from 'src/types/user';
import type { SWRConfiguration } from 'swr';
import useSWR from 'swr';
// ----------------------------------------------------------------------
const swrOptions: SWRConfiguration = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
// ----------------------------------------------------------------------
type UsersData = {
users: IUserItem[];
};
export function useGetUsers() {
const url = `http://localhost:7272/api/user/list`;
const { data, isLoading, error, isValidating, mutate } = useSWR<UsersData>(
url,
fetcher,
swrOptions
);
const memoizedValue = useMemo(
() => ({
users: data?.users || [],
usersLoading: isLoading,
usersError: error,
usersValidating: isValidating,
usersEmpty: !isLoading && !isValidating && !data?.users.length,
mutate,
}),
[data?.users, error, isLoading, isValidating, mutate]
);
return memoizedValue;
}
// ----------------------------------------------------------------------
type UserData = {
user: IUserItem;
};
export function useGetUser(userId: string) {
const url = userId ? [endpoints.user.details, { params: { userId } }] : '';
const { data, isLoading, error, isValidating } = useSWR<UserData>(url, fetcher, swrOptions);
const memoizedValue = useMemo(
() => ({
user: data?.user,
userLoading: isLoading,
userError: error,
userValidating: isValidating,
}),
[data?.user, error, isLoading, isValidating]
);
return memoizedValue;
}
// ----------------------------------------------------------------------
type SearchResultsData = {
results: IProductItem[];
};
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;
};
export async function saveUser(userId: string, saveUserData: SaveUserData) {
// const url = userId ? [endpoints.user.details, { params: { userId } }] : '';
const res = await axiosInstance.post(
//
`http://localhost:7272/api/user/saveUser?userId=${userId}`,
{
data: saveUserData,
}
);
return res;
}
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;
};
export async function createUser(createUserData: CreateUserData) {
console.log('create product ?');
// const url = productId ? [endpoints.product.details, { params: { productId } }] : '';
const res = await axiosInstance.post('http://localhost:7272/api/user/createUser', {
data: createUserData,
});
return res;
}
// ----------------------------------------------------------------------
type DeleteUserResponse = {
success: boolean;
message?: string;
};
export async function deleteUser(userId: string): Promise<DeleteUserResponse> {
const url = `http://localhost:7272/api/user/deleteUser?userId=${userId}`;
try {
const res = await axiosInstance.delete(url);
return {
success: true,
message: 'User deleted successfully',
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : 'Failed to delete product',
};
}
}

View File

@@ -5,17 +5,13 @@ import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent'; import DialogContent from '@mui/material/DialogContent';
import type { ConfirmDialogProps } from './types'; import type { ConfirmDialogProps } from './types';
import { useTranslation } from 'react-i18next';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export function ConfirmDialog({ export function ConfirmDialog({ open, title, action, content, onClose, ...other }: ConfirmDialogProps) {
open, const { t } = useTranslation();
title,
action,
content,
onClose,
...other
}: ConfirmDialogProps) {
return ( return (
<Dialog fullWidth maxWidth="xs" open={open} onClose={onClose} {...other}> <Dialog fullWidth maxWidth="xs" open={open} onClose={onClose} {...other}>
<DialogTitle sx={{ pb: 2 }}>{title}</DialogTitle> <DialogTitle sx={{ pb: 2 }}>{title}</DialogTitle>
@@ -26,7 +22,7 @@ export function ConfirmDialog({
{action} {action}
<Button variant="outlined" color="inherit" onClick={onClose}> <Button variant="outlined" color="inherit" onClick={onClose}>
Cancel {t('Cancel')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@@ -11,24 +11,20 @@ import { megaMenuClasses } from '../styles';
import { NavUl, NavLi } from './nav-elements'; import { NavUl, NavLi } from './nav-elements';
import type { NavSubItemProps, NavSubListProps } from '../types'; import type { NavSubItemProps, NavSubListProps } from '../types';
import { useTranslation } from 'react-i18next';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export function NavSubList({ data, slotProps, ...other }: NavSubListProps) { export function NavSubList({ data, slotProps, ...other }: NavSubListProps) {
const pathname = usePathname(); const pathname = usePathname();
const { t } = useTranslation();
return ( return (
<> <>
{data?.map((list) => ( {data?.map((list) => (
<NavLi key={list?.subheader ?? list.items[0].title} {...other}> <NavLi key={list?.subheader ?? list.items[0].title} {...other}>
{list?.subheader && ( {list?.subheader && (
<Typography <Typography noWrap component="div" variant="subtitle2" className={megaMenuClasses.subheader} sx={{ mb: 1, ...slotProps?.subheader }}>
noWrap
component="div"
variant="subtitle2"
className={megaMenuClasses.subheader}
sx={{ mb: 1, ...slotProps?.subheader }}
>
{list.subheader} {list.subheader}
</Typography> </Typography>
)} )}

View File

@@ -13,16 +13,8 @@ import { Iconify, iconifyClasses } from '../../iconify';
export type NavSubheaderProps = ListSubheaderProps & { open?: boolean }; export type NavSubheaderProps = ListSubheaderProps & { open?: boolean };
export const NavSubheader = styled(({ open, children, className, ...other }: NavSubheaderProps) => ( export const NavSubheader = styled(({ open, children, className, ...other }: NavSubheaderProps) => (
<ListSubheader <ListSubheader disableSticky component="div" {...other} className={mergeClasses([navSectionClasses.subheader, className])}>
disableSticky <Iconify width={16} icon={open ? 'eva:arrow-ios-downward-fill' : 'eva:arrow-ios-forward-fill'} />
component="div"
{...other}
className={mergeClasses([navSectionClasses.subheader, className])}
>
<Iconify
width={16}
icon={open ? 'eva:arrow-ios-downward-fill' : 'eva:arrow-ios-forward-fill'}
/>
{children} {children}
</ListSubheader> </ListSubheader>
))(({ theme }) => ({ ))(({ theme }) => ({

View File

@@ -27,10 +27,7 @@ export function NavSectionHorizontal({
const cssVars = { ...navSectionCssVars.horizontal(theme), ...overridesVars }; const cssVars = { ...navSectionCssVars.horizontal(theme), ...overridesVars };
return ( return (
<Scrollbar <Scrollbar sx={{ height: 1 }} slotProps={{ contentSx: { height: 1, display: 'flex', alignItems: 'center' } }}>
sx={{ height: 1 }}
slotProps={{ contentSx: { height: 1, display: 'flex', alignItems: 'center' } }}
>
<Nav <Nav
className={mergeClasses([navSectionClasses.horizontal, className])} className={mergeClasses([navSectionClasses.horizontal, className])}
sx={[ sx={[
@@ -66,14 +63,7 @@ export function NavSectionHorizontal({
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function Group({ function Group({ items, render, cssVars, slotProps, checkPermissions, enabledRootRedirect }: NavGroupProps) {
items,
render,
cssVars,
slotProps,
checkPermissions,
enabledRootRedirect,
}: NavGroupProps) {
return ( return (
<NavLi> <NavLi>
<NavUl sx={{ flexDirection: 'row', gap: 'var(--nav-item-gap)' }}> <NavUl sx={{ flexDirection: 'row', gap: 'var(--nav-item-gap)' }}>

View File

@@ -26,11 +26,7 @@ export function NavSectionMini({
const cssVars = { ...navSectionCssVars.mini(theme), ...overridesVars }; const cssVars = { ...navSectionCssVars.mini(theme), ...overridesVars };
return ( return (
<Nav <Nav className={mergeClasses([navSectionClasses.mini, className])} sx={[{ ...cssVars }, ...(Array.isArray(sx) ? sx : [sx])]} {...other}>
className={mergeClasses([navSectionClasses.mini, className])}
sx={[{ ...cssVars }, ...(Array.isArray(sx) ? sx : [sx])]}
{...other}
>
<NavUl sx={{ flex: '1 1 auto', gap: 'var(--nav-item-gap)' }}> <NavUl sx={{ flex: '1 1 auto', gap: 'var(--nav-item-gap)' }}>
{data.map((group) => ( {data.map((group) => (
<Group <Group
@@ -50,14 +46,7 @@ export function NavSectionMini({
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function Group({ function Group({ items, render, cssVars, slotProps, checkPermissions, enabledRootRedirect }: NavGroupProps) {
items,
render,
cssVars,
slotProps,
checkPermissions,
enabledRootRedirect,
}: NavGroupProps) {
return ( return (
<NavLi> <NavLi>
<NavUl sx={{ gap: 'var(--nav-item-gap)' }}> <NavUl sx={{ gap: 'var(--nav-item-gap)' }}>

View File

@@ -6,6 +6,7 @@ import { Nav, NavLi, NavSubheader, NavUl } from '../components';
import { navSectionClasses, navSectionCssVars } from '../styles'; import { navSectionClasses, navSectionCssVars } from '../styles';
import type { NavGroupProps, NavSectionProps } from '../types'; import type { NavGroupProps, NavSectionProps } from '../types';
import { NavList } from './nav-list'; import { NavList } from './nav-list';
import { useTranslation } from 'react-i18next';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -25,11 +26,7 @@ export function NavSectionVertical({
const cssVars = { ...navSectionCssVars.vertical(theme), ...overridesVars }; const cssVars = { ...navSectionCssVars.vertical(theme), ...overridesVars };
return ( return (
<Nav <Nav className={mergeClasses([navSectionClasses.vertical, className])} sx={[{ ...cssVars }, ...(Array.isArray(sx) ? sx : [sx])]} {...other}>
className={mergeClasses([navSectionClasses.vertical, className])}
sx={[{ ...cssVars }, ...(Array.isArray(sx) ? sx : [sx])]}
{...other}
>
<NavUl sx={{ flex: '1 1 auto', gap: 'var(--nav-item-gap)' }}> <NavUl sx={{ flex: '1 1 auto', gap: 'var(--nav-item-gap)' }}>
{data.map((group) => ( {data.map((group) => (
<Group <Group
@@ -49,15 +46,9 @@ export function NavSectionVertical({
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
function Group({ function Group({ items, render, subheader, slotProps, checkPermissions, enabledRootRedirect }: NavGroupProps) {
items,
render,
subheader,
slotProps,
checkPermissions,
enabledRootRedirect,
}: NavGroupProps) {
const groupOpen = useBoolean(true); const groupOpen = useBoolean(true);
const { t } = useTranslation();
const renderContent = () => ( const renderContent = () => (
<NavUl sx={{ gap: 'var(--nav-item-gap)' }}> <NavUl sx={{ gap: 'var(--nav-item-gap)' }}>
@@ -79,13 +70,8 @@ function Group({
<NavLi> <NavLi>
{subheader ? ( {subheader ? (
<> <>
<NavSubheader <NavSubheader data-title={subheader} open={groupOpen.value} onClick={groupOpen.onToggle} sx={slotProps?.subheader}>
data-title={subheader} {t(subheader)}
open={groupOpen.value}
onClick={groupOpen.onToggle}
sx={slotProps?.subheader}
>
{subheader}
</NavSubheader> </NavSubheader>
<Collapse in={groupOpen.value}>{renderContent()}</Collapse> <Collapse in={groupOpen.value}>{renderContent()}</Collapse>
</> </>

View File

@@ -11,18 +11,12 @@ import { uploadClasses } from './classes';
import { RejectionFiles } from './components/rejection-files'; import { RejectionFiles } from './components/rejection-files';
import type { UploadProps } from './types'; import type { UploadProps } from './types';
import { useTranslation } from 'react-i18next';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export function UploadAvatar({ export function UploadAvatar({ sx, error, value, disabled, helperText, className, ...other }: UploadProps) {
sx, const { t } = useTranslation();
error,
value,
disabled,
helperText,
className,
...other
}: UploadProps) {
const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({ const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
multiple: false, multiple: false,
disabled, disabled,
@@ -44,10 +38,7 @@ export function UploadAvatar({
} }
}, [value]); }, [value]);
const renderPreview = () => const renderPreview = () => hasFile && <Image alt="Avatar" src={preview} sx={{ width: 1, height: 1, borderRadius: '50%' }} />;
hasFile && (
<Image alt="Avatar" src={preview} sx={{ width: 1, height: 1, borderRadius: '50%' }} />
);
const renderPlaceholder = () => ( const renderPlaceholder = () => (
<Box <Box
@@ -85,7 +76,7 @@ export function UploadAvatar({
> >
<Iconify icon="solar:camera-add-bold" width={32} /> <Iconify icon="solar:camera-add-bold" width={32} />
<Typography variant="caption">{hasFile ? 'Update photo' : 'Upload photo'}</Typography> <Typography variant="caption">{hasFile ? t('Update photo') : t('Update photo')}</Typography>
</Box> </Box>
); );

View File

@@ -35,9 +35,7 @@ const flattenNavItems = (navItems: NavItem[], parentGroup?: string): OutputItem[
}; };
export function flattenNavSections(navSections: NavSectionProps['data']): OutputItem[] { export function flattenNavSections(navSections: NavSectionProps['data']): OutputItem[] {
return navSections.flatMap((navSection) => return navSections.flatMap((navSection) => flattenNavItems(navSection.items, navSection.subheader));
flattenNavItems(navSection.items, navSection.subheader)
);
} }
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -50,7 +48,5 @@ type ApplyFilterProps = {
export function applyFilter({ inputData, query }: ApplyFilterProps): OutputItem[] { export function applyFilter({ inputData, query }: ApplyFilterProps): OutputItem[] {
if (!query) return inputData; if (!query) return inputData;
return inputData.filter(({ title, path, group }) => return inputData.filter(({ title, path, group }) => [title, path, group].some((field) => field?.toLowerCase().includes(query.toLowerCase())));
[title, path, group].some((field) => field?.toLowerCase().includes(query.toLowerCase()))
);
} }

View File

@@ -50,13 +50,7 @@ export type DashboardLayoutProps = LayoutBaseProps & {
}; };
}; };
export function DashboardLayout({ export function DashboardLayout({ sx, cssVars, children, slotProps, layoutQuery = 'lg' }: DashboardLayoutProps) {
sx,
cssVars,
children,
slotProps,
layoutQuery = 'lg',
}: DashboardLayoutProps) {
const theme = useTheme(); const theme = useTheme();
const { user } = useMockedUser(); const { user } = useMockedUser();
@@ -73,8 +67,7 @@ export function DashboardLayout({
const isNavHorizontal = settings.state.navLayout === 'horizontal'; const isNavHorizontal = settings.state.navLayout === 'horizontal';
const isNavVertical = isNavMini || settings.state.navLayout === 'vertical'; const isNavVertical = isNavMini || settings.state.navLayout === 'vertical';
const canDisplayItemByRole = (allowedRoles: NavItemProps['allowedRoles']): boolean => const canDisplayItemByRole = (allowedRoles: NavItemProps['allowedRoles']): boolean => !allowedRoles?.includes(user?.role);
!allowedRoles?.includes(user?.role);
const renderHeader = () => { const renderHeader = () => {
const headerSlotProps: HeaderSectionProps['slotProps'] = { const headerSlotProps: HeaderSectionProps['slotProps'] = {
@@ -98,27 +91,13 @@ export function DashboardLayout({
</Alert> </Alert>
), ),
bottomArea: isNavHorizontal ? ( bottomArea: isNavHorizontal ? (
<NavHorizontal <NavHorizontal data={navData} layoutQuery={layoutQuery} cssVars={navVars.section} checkPermissions={canDisplayItemByRole} />
data={navData}
layoutQuery={layoutQuery}
cssVars={navVars.section}
checkPermissions={canDisplayItemByRole}
/>
) : null, ) : null,
leftArea: ( leftArea: (
<> <>
{/** @slot Nav mobile */} {/** @slot Nav mobile */}
<MenuButton <MenuButton onClick={onOpen} sx={{ mr: 1, ml: -1, [theme.breakpoints.up(layoutQuery)]: { display: 'none' } }} />
onClick={onOpen} <NavMobile data={navData} open={open} onClose={onClose} cssVars={navVars.section} checkPermissions={canDisplayItemByRole} />
sx={{ mr: 1, ml: -1, [theme.breakpoints.up(layoutQuery)]: { display: 'none' } }}
/>
<NavMobile
data={navData}
open={open}
onClose={onClose}
cssVars={navVars.section}
checkPermissions={canDisplayItemByRole}
/>
{/** @slot Logo */} {/** @slot Logo */}
{isNavHorizontal && ( {isNavHorizontal && (
@@ -131,15 +110,10 @@ export function DashboardLayout({
)} )}
{/** @slot Divider */} {/** @slot Divider */}
{isNavHorizontal && ( {isNavHorizontal && <VerticalDivider sx={{ [theme.breakpoints.up(layoutQuery)]: { display: 'flex' } }} />}
<VerticalDivider sx={{ [theme.breakpoints.up(layoutQuery)]: { display: 'flex' } }} />
)}
{/** @slot Workspace popover */} {/** @slot Workspace popover */}
<WorkspacesPopover <WorkspacesPopover data={_workspaces} sx={{ ...(isNavHorizontal && { color: 'var(--layout-nav-text-primary-color)' }) }} />
data={_workspaces}
sx={{ ...(isNavHorizontal && { color: 'var(--layout-nav-text-primary-color)' }) }}
/>
</> </>
), ),
rightArea: ( rightArea: (
@@ -184,12 +158,7 @@ export function DashboardLayout({
layoutQuery={layoutQuery} layoutQuery={layoutQuery}
cssVars={navVars.section} cssVars={navVars.section}
checkPermissions={canDisplayItemByRole} checkPermissions={canDisplayItemByRole}
onToggleNav={() => onToggleNav={() => settings.setField('navLayout', settings.state.navLayout === 'vertical' ? 'mini' : 'vertical')}
settings.setField(
'navLayout',
settings.state.navLayout === 'vertical' ? 'mini' : 'vertical'
)
}
/> />
); );

View File

@@ -23,18 +23,7 @@ export type NavVerticalProps = React.ComponentProps<'div'> &
}; };
}; };
export function NavVertical({ export function NavVertical({ sx, data, slots, cssVars, className, isNavMini, onToggleNav, checkPermissions, layoutQuery = 'md', ...other }: NavVerticalProps) {
sx,
data,
slots,
cssVars,
className,
isNavMini,
onToggleNav,
checkPermissions,
layoutQuery = 'md',
...other
}: NavVerticalProps) {
const renderNavVertical = () => ( const renderNavVertical = () => (
<> <>
{slots?.topArea ?? ( {slots?.topArea ?? (
@@ -44,12 +33,7 @@ export function NavVertical({
)} )}
<Scrollbar fillContent> <Scrollbar fillContent>
<NavSectionVertical <NavSectionVertical data={data} cssVars={cssVars} checkPermissions={checkPermissions} sx={{ px: 2, flex: '1 1 auto' }} />
data={data}
cssVars={cssVars}
checkPermissions={checkPermissions}
sx={{ px: 2, flex: '1 1 auto' }}
/>
{slots?.bottomArea ?? <NavUpgrade />} {slots?.bottomArea ?? <NavUpgrade />}
</Scrollbar> </Scrollbar>
@@ -110,22 +94,20 @@ export function NavVertical({
const NavRoot = styled('div', { const NavRoot = styled('div', {
shouldForwardProp: (prop: string) => !['isNavMini', 'layoutQuery', 'sx'].includes(prop), shouldForwardProp: (prop: string) => !['isNavMini', 'layoutQuery', 'sx'].includes(prop),
})<Pick<NavVerticalProps, 'isNavMini' | 'layoutQuery'>>( })<Pick<NavVerticalProps, 'isNavMini' | 'layoutQuery'>>(({ isNavMini, layoutQuery = 'md', theme }) => ({
({ isNavMini, layoutQuery = 'md', theme }) => ({ top: 0,
top: 0, left: 0,
left: 0, height: '100%',
height: '100%', display: 'none',
display: 'none', position: 'fixed',
position: 'fixed', flexDirection: 'column',
flexDirection: 'column', zIndex: 'var(--layout-nav-zIndex)',
zIndex: 'var(--layout-nav-zIndex)', backgroundColor: 'var(--layout-nav-bg)',
backgroundColor: 'var(--layout-nav-bg)', width: isNavMini ? 'var(--layout-nav-mini-width)' : 'var(--layout-nav-vertical-width)',
width: isNavMini ? 'var(--layout-nav-mini-width)' : 'var(--layout-nav-vertical-width)', borderRight: `1px solid var(--layout-nav-border-color, ${varAlpha(theme.vars.palette.grey['500Channel'], 0.12)})`,
borderRight: `1px solid var(--layout-nav-border-color, ${varAlpha(theme.vars.palette.grey['500Channel'], 0.12)})`, transition: theme.transitions.create(['width'], {
transition: theme.transitions.create(['width'], { easing: 'var(--layout-transition-easing)',
easing: 'var(--layout-transition-easing)', duration: 'var(--layout-transition-duration)',
duration: 'var(--layout-transition-duration)', }),
}), [theme.breakpoints.up(layoutQuery)]: { display: 'flex' },
[theme.breakpoints.up(layoutQuery)]: { display: 'flex' }, }));
})
);

View File

@@ -1,13 +1,9 @@
import type { CSSObject } from '@mui/material/styles';
import { varAlpha, mergeClasses } from 'minimal-shared/utils';
import { styled } from '@mui/material/styles';
import ButtonBase from '@mui/material/ButtonBase'; import ButtonBase from '@mui/material/ButtonBase';
import type { CSSObject } from '@mui/material/styles';
import { styled } from '@mui/material/styles';
import { mergeClasses, varAlpha } from 'minimal-shared/utils';
import { Iconify } from 'src/components/iconify'; import { Iconify } from 'src/components/iconify';
import { createNavItem, navItemStyles, navSectionClasses } from 'src/components/nav-section'; import { createNavItem, navItemStyles, navSectionClasses } from 'src/components/nav-section';
import type { NavItemProps } from '../types'; import type { NavItemProps } from '../types';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -59,11 +55,7 @@ const shouldForwardProp = (prop: string) => !['open', 'active', 'variant', 'sx']
/** /**
* @slot root * @slot root
*/ */
const ItemRoot = styled(ButtonBase, { shouldForwardProp })<StyledState>(({ const ItemRoot = styled(ButtonBase, { shouldForwardProp })<StyledState>(({ active, open, theme }) => {
active,
open,
theme,
}) => {
const dotTransitions: Record<'in' | 'out', CSSObject> = { const dotTransitions: Record<'in' | 'out', CSSObject> = {
in: { opacity: 0, scale: 0 }, in: { opacity: 0, scale: 0 },
out: { opacity: 1, scale: 1 }, out: { opacity: 1, scale: 1 },

View File

@@ -1,14 +1,11 @@
import { useBoolean } from 'minimal-shared/hooks'; import { useBoolean } from 'minimal-shared/hooks';
import { useRef, useEffect, useCallback } from 'react'; import { isActiveLink, isEqualPath, isExternalLink } from 'minimal-shared/utils';
import { isEqualPath, isActiveLink, isExternalLink } from 'minimal-shared/utils'; import { useCallback, useEffect, useRef } from 'react';
import { usePathname } from 'src/routes/hooks'; import { usePathname } from 'src/routes/hooks';
import { Nav, NavDropdown, NavLi, NavUl } from '../components';
import { NavItem } from './nav-desktop-item';
import { Nav, NavLi, NavUl, NavDropdown } from '../components';
import { NavItemDashboard } from './nav-desktop-item-dashboard';
import type { NavListProps, NavSubListProps } from '../types'; import type { NavListProps, NavSubListProps } from '../types';
import { NavItem } from './nav-desktop-item';
import { NavItemDashboard } from './nav-desktop-item-dashboard';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -109,12 +106,7 @@ function NavSubList({ data, subheader, sx, ...other }: NavSubListProps) {
</NavLi> </NavLi>
) : ( ) : (
<NavLi key={item.title} sx={{ mt: 0.75 }}> <NavLi key={item.title} sx={{ mt: 0.75 }}>
<NavItem <NavItem subItem title={item.title} path={item.path} active={isEqualPath(item.path, pathname)} />
subItem
title={item.title}
path={item.path}
active={isEqualPath(item.path, pathname)}
/>
</NavLi> </NavLi>
) )
)} )}

View File

@@ -1,7 +1,6 @@
import { Nav, NavUl } from '../components'; import { Nav, NavUl } from '../components';
import { NavList } from './nav-desktop-list';
import type { NavMainProps } from '../types'; import type { NavMainProps } from '../types';
import { NavList } from './nav-desktop-list';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,13 +1,9 @@
import type { CSSObject } from '@mui/material/styles';
import { varAlpha, mergeClasses } from 'minimal-shared/utils';
import { styled } from '@mui/material/styles';
import ButtonBase from '@mui/material/ButtonBase'; import ButtonBase from '@mui/material/ButtonBase';
import type { CSSObject } from '@mui/material/styles';
import { styled } from '@mui/material/styles';
import { mergeClasses, varAlpha } from 'minimal-shared/utils';
import { Iconify } from 'src/components/iconify'; import { Iconify } from 'src/components/iconify';
import { createNavItem, navItemStyles, navSectionClasses } from 'src/components/nav-section'; import { createNavItem, navItemStyles, navSectionClasses } from 'src/components/nav-section';
import type { NavItemProps } from '../types'; import type { NavItemProps } from '../types';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,20 +1,14 @@
import { useRef, useCallback } from 'react';
import { useBoolean } from 'minimal-shared/hooks';
import { varAlpha, isActiveLink, isExternalLink } from 'minimal-shared/utils';
import Collapse from '@mui/material/Collapse'; import Collapse from '@mui/material/Collapse';
import { useBoolean } from 'minimal-shared/hooks';
import { paths } from 'src/routes/paths'; import { isActiveLink, isExternalLink, varAlpha } from 'minimal-shared/utils';
import { usePathname } from 'src/routes/hooks'; import { useCallback, useRef } from 'react';
import { CONFIG } from 'src/global-config';
import { navSectionClasses, NavSectionVertical } from 'src/components/nav-section'; import { navSectionClasses, NavSectionVertical } from 'src/components/nav-section';
import { CONFIG } from 'src/global-config';
import { usePathname } from 'src/routes/hooks';
import { paths } from 'src/routes/paths';
import { NavLi } from '../components'; import { NavLi } from '../components';
import { NavItem } from './nav-mobile-item';
import type { NavListProps } from '../types'; import type { NavListProps } from '../types';
import { NavItem } from './nav-mobile-item';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,20 +1,15 @@
import { useEffect } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import Drawer from '@mui/material/Drawer'; import Drawer from '@mui/material/Drawer';
import { useEffect } from 'react';
import { paths } from 'src/routes/paths';
import { usePathname } from 'src/routes/hooks';
import { Logo } from 'src/components/logo'; import { Logo } from 'src/components/logo';
import { Scrollbar } from 'src/components/scrollbar'; import { Scrollbar } from 'src/components/scrollbar';
import { usePathname } from 'src/routes/hooks';
import { Nav, NavUl } from '../components'; import { paths } from 'src/routes/paths';
import { NavList } from './nav-mobile-list';
import { SignInButton } from '../../../components/sign-in-button'; import { SignInButton } from '../../../components/sign-in-button';
import { Nav, NavUl } from '../components';
import type { NavMainProps } from '../types'; import type { NavMainProps } from '../types';
import { NavList } from './nav-mobile-list';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,5 +1,5 @@
import type { Theme, SxProps } from '@mui/material/styles';
import type { ButtonBaseProps } from '@mui/material/ButtonBase'; import type { ButtonBaseProps } from '@mui/material/ButtonBase';
import type { SxProps, Theme } from '@mui/material/styles';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,5 +1,4 @@
import { Iconify } from 'src/components/iconify'; import { Iconify } from 'src/components/iconify';
import type { AccountDrawerProps } from './components/account-drawer'; import type { AccountDrawerProps } from './components/account-drawer';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -2,6 +2,7 @@
import { import {
arSA as arSACore, arSA as arSACore,
frFR as frFRCore, frFR as frFRCore,
jaJP as jaJPCore,
viVN as viVNCore, viVN as viVNCore,
zhCN as zhCNCore, zhCN as zhCNCore,
zhHK as zhHKCore, zhHK as zhHKCore,
@@ -11,6 +12,7 @@ import {
arSD as arSDDataGrid, arSD as arSDDataGrid,
enUS as enUSDataGrid, enUS as enUSDataGrid,
frFR as frFRDataGrid, frFR as frFRDataGrid,
jaJP as jaJPDataGrid,
viVN as viVNDataGrid, viVN as viVNDataGrid,
zhCN as zhCNDataGrid, zhCN as zhCNDataGrid,
zhHK as zhHKDataGrid, zhHK as zhHKDataGrid,
@@ -19,6 +21,7 @@ import {
import { import {
enUS as enUSDate, enUS as enUSDate,
frFR as frFRDate, frFR as frFRDate,
jaJP as jaJPDate,
viVN as viVNDate, viVN as viVNDate,
zhCN as zhCNDate, zhCN as zhCNDate,
zhHK as zhHKDate, zhHK as zhHKDate,
@@ -94,6 +97,16 @@ export const allLangs = [
}, },
}, },
}, },
{
value: 'jp',
label: 'Japanese',
countryCode: 'JP',
adapterLocale: 'ja',
numberFormat: { code: 'ja-JP', currency: 'JPY' },
systemValue: {
components: { ...jaJPCore.components, ...jaJPDate.components, ...jaJPDataGrid.components },
},
},
]; ];
/** /**

View File

@@ -58,5 +58,74 @@
"draft": "草稿", "draft": "草稿",
"Dashboard": "儀表板", "Dashboard": "儀表板",
"Apply": "應用", "Apply": "應用",
"Name": "名稱",
"Phone number": "電話",
"Company": "公司",
"Role": "角色",
"Status": "狀態",
"New user": "新用戶",
"All": "全部",
"Active": "活躍",
"Pending": "等待",
"Banned": "已封鎖",
"Rejected": "已反對",
"Quick update": "快速更新",
"Address": "地址",
"Followers": "追隨者",
"Follower": "追隨者",
"Following": "正在追隨",
"Friends": "朋友圈",
"Gallery": "相集",
"About": "關於用戶",
"Social": "社交媒體",
"Post": "發帖",
"Image/Video": "相/視頻",
"Streaming": "直播",
"Delete": "清除",
"Cancel": "取消",
"Overview": "概覽",
"Management": "管理",
"Quick Edit": "簡易編輯",
"Choose a country": "選擇一個城市",
"Create user": "新用戶",
"State/region": "State/region",
"Misc": "雜項",
"Email verified": "電郵核實",
"Update photo": "上傳相片",
"Create a new user": "創建新用戶",
"Allowed": "Allowed",
"max size of": "max size of",
"Disabling this will automatically send the user a verification email": "Disabling this will automatically send the user a verification email",
"Full name": "Full name",
"Email address": "Email address",
"Country": "Country",
"City": "City",
"Zip/code": "Zip/code",
"Update success": "更新完成",
"Create success": "創建完成",
"Save changes": "儲存變更",
"Product List": "產品列表",
"View": "詳細資料",
"Completed": "已完成",
"Cancelled": "已取消",
"Refunded": "已退款",
"Date": "日期",
"Customer": "客戶",
"Items": "項目",
"Start date": "開始日期",
"End date": "結束日期",
"Search customer or order number...": "搜尋客戶或訂單號碼...",
"Search customer or invoice number...": "搜尋客戶或訂單號碼...",
"Service": "項目",
"Print": "列印",
"Import": "匯入",
"Due": "過期日",
"Amount": "數目",
"Sent": "發出次數",
"Overdue": "己過期",
"Paid": "已符款",
"Export": "匯出",
"Product not found!": "產品未找到!",
"Back to list": "返回列表",
"hello": "world" "hello": "world"
} }

View File

@@ -0,0 +1,74 @@
{
"demo": {
"lang": "日本語",
"description": "あなたの次のプロジェクトの起点はMUIに基づいています。簡単なカスタマイズで、より速く、より良いアプリケーションを構築することができます。"
},
"new-product": "新製品",
"back": "戻る",
"App": "アプリケーション",
"Ecommerce": "電子商取引",
"Analytics": "分析",
"Banking": "銀行",
"Booking": "予約",
"File": "ファイル",
"Course": "コース",
"User": "ユーザー",
"Product": "製品",
"Order": "注文",
"Invoice": "請求書",
"Blog": "ブログ",
"Job": "仕事",
"Tour": "ツアー",
"manager": "マネージャー",
"Mail": "メール",
"Chat": "チャット",
"Calendar": "カレンダー",
"Kanban": "かんばん",
"Permission": "権限",
"Level": "レベル",
"Disabled": "無効",
"Label": "ラベル",
"Caption": "キャプション",
"Params": "パラメーター",
"link": "リンク",
"Blank": "空白",
"File-manager": "ファイルマネージャー",
"External-link": "外部リンク",
"Profile": "プロフィール",
"Cards": "カード",
"List": "リスト",
"Create": "作成",
"Edit": "編集",
"Account": "アカウント",
"Details": "詳細",
"Create-at": "作成日",
"Category": "カテゴリー",
"Stock": "在庫",
"Price": "価格",
"Publish": "公開",
"in stock": "在庫あり",
"low stock": "在庫少",
"out of stock": "在庫なし",
"In stock": "在庫あり",
"Low stock": "在庫少",
"Out of stock": "在庫なし",
"Published": "公開済み",
"Draft": "下書き",
"published": "公開済み",
"draft": "下書き",
"Dashboard": "ダッシュボード",
"Apply": "適用",
"Name": "名前",
"Phone number": "電話番号",
"Company": "会社",
"Role": "役割",
"Status": "ステータス",
"New user": "新規ユーザー",
"All": "すべて",
"Active": "アクティブ",
"Pending": "保留中",
"Banned": "禁止",
"Rejected": "却下",
"Quick update": "クイックアップデート",
"hello": "world"
}

View File

@@ -0,0 +1,12 @@
{
"app": "アプリケーション",
"job": "仕事",
"user": "ユーザー",
"travel": "旅行",
"invoice": "請求書",
"blog": {
"title": "ブログ",
"caption": "カスタムキーボードショートカットを設定します。"
},
"subheader": "サブヘッダー"
}

View File

@@ -1,7 +1,7 @@
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export const fallbackLng = 'en'; export const fallbackLng = 'en';
export const languages = ['en', 'fr', 'vi', 'cn', 'ar', 'hk']; export const languages = ['en', 'fr', 'vi', 'cn', 'ar', 'hk', 'jp'];
export const defaultNS = 'common'; export const defaultNS = 'common';
export type LanguageValue = (typeof languages)[number]; export type LanguageValue = (typeof languages)[number];

View File

@@ -1,9 +1,11 @@
// src/pages/dashboard/invoice/details.tsx
import { useParams } from 'src/routes/hooks'; import { useParams } from 'src/routes/hooks';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { _invoices } from 'src/_mock/_invoice'; import { _invoices } from 'src/_mock/_invoice';
import { InvoiceDetailsView } from 'src/sections/invoice/view'; import { InvoiceDetailsView } from 'src/sections/invoice/view';
import { useGetInvoice } from 'src/actions/invoice';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -12,13 +14,14 @@ const metadata = { title: `Invoice details | Dashboard - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const currentInvoice = _invoices.find((invoice) => invoice.id === id); // const currentInvoice = _invoices.find((invoice) => invoice.id === id);
const { currentInvoice, invoiceLoading, invoiceError } = useGetInvoice(id);
return ( return (
<> <>
<title>{metadata.title}</title> <title>{metadata.title}</title>
<InvoiceDetailsView invoice={currentInvoice} /> <InvoiceDetailsView invoice={currentInvoice} loading={invoiceLoading} error={invoiceError} />
</> </>
); );
} }

View File

@@ -1,7 +1,7 @@
import { useParams } from 'src/routes/hooks'; import { useParams } from 'src/routes/hooks';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { _invoices } from 'src/_mock/_invoice'; import { useGetInvoice } from 'src/actions/invoice';
import { InvoiceEditView } from 'src/sections/invoice/view'; import { InvoiceEditView } from 'src/sections/invoice/view';
@@ -12,7 +12,7 @@ const metadata = { title: `Invoice edit | Dashboard - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const currentInvoice = _invoices.find((invoice) => invoice.id === id); const { currentInvoice } = useGetInvoice(id);
return ( return (
<> <>

View File

@@ -1,3 +1,5 @@
// src/pages/dashboard/invoice/list.tsx
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { InvoiceListView } from 'src/sections/invoice/view'; import { InvoiceListView } from 'src/sections/invoice/view';

View File

@@ -1,3 +1,5 @@
// src/pages/dashboard/kanban/index.tsx
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { KanbanView } from 'src/sections/kanban/view'; import { KanbanView } from 'src/sections/kanban/view';

View File

@@ -1,7 +1,9 @@
// src/pages/dashboard/order/details.tsx
import { useParams } from 'src/routes/hooks'; import { useParams } from 'src/routes/hooks';
import { _orders } from 'src/_mock/_order'; import { _orders } from 'src/_mock/_order';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { useGetOrder } from 'src/actions/order';
import { OrderDetailsView } from 'src/sections/order/view'; import { OrderDetailsView } from 'src/sections/order/view';
@@ -12,13 +14,18 @@ const metadata = { title: `Order details | Dashboard - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const currentOrder = _orders.find((order) => order.id === id); // const currentOrder = _orders.find((order) => order.id === id);
// TODO: error handling
const { order, orderLoading, orderError } = useGetOrder(id);
if (!order) return <>loading</>;
if (orderLoading) return <>loading</>;
return ( return (
<> <>
<title>{metadata.title}</title> <title>{metadata.title}</title>
<OrderDetailsView order={currentOrder} /> <OrderDetailsView order={order} />
</> </>
); );
} }

View File

@@ -1,3 +1,5 @@
// src/pages/dashboard/product/details.tsx
import { useParams } from 'src/routes/hooks'; import { useParams } from 'src/routes/hooks';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
@@ -12,7 +14,7 @@ const metadata = { title: `Product details | Dashboard - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const { product, productLoading, productError } = useGetProduct(id); const { currentProduct: product, productLoading, productError } = useGetProduct(id);
return ( return (
<> <>

View File

@@ -12,14 +12,13 @@ const metadata = { title: `Product edit | Dashboard - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const { product } = useGetProduct(id); const { currentProduct } = useGetProduct(id);
console.log({ id });
return ( return (
<> <>
<title>{metadata.title}</title> <title>{metadata.title}</title>
<ProductEditView product={product} /> <ProductEditView product={currentProduct} />
</> </>
); );
} }

View File

@@ -1,3 +1,5 @@
// src/pages/dashboard/product/list.tsx
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { ProductListView } from 'src/sections/product/view'; import { ProductListView } from 'src/sections/product/view';

View File

@@ -1,9 +1,8 @@
import { useParams } from 'src/routes/hooks'; // import { _userList } from 'src/_mock/_user';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { _userList } from 'src/_mock/_user'; import { useParams } from 'src/routes/hooks';
import { UserEditView } from 'src/sections/user/view'; import { UserEditView } from 'src/sections/user/view';
import { useGetUser } from 'src/actions/user';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -12,13 +11,15 @@ const metadata = { title: `User edit | Dashboard - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const currentUser = _userList.find((user) => user.id === id); // TODO: remove me
// const currentUser = _userList.find((user) => user.id === id);
const { user } = useGetUser(id);
return ( return (
<> <>
<title>{metadata.title}</title> <title>{metadata.title}</title>
<UserEditView user={currentUser} /> <UserEditView user={user} />
</> </>
); );
} }

View File

@@ -1,5 +1,4 @@
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { UserListView } from 'src/sections/user/view'; import { UserListView } from 'src/sections/user/view';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -1,5 +1,4 @@
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
import { UserProfileView } from 'src/sections/user/view'; import { UserProfileView } from 'src/sections/user/view';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------

View File

@@ -12,7 +12,7 @@ const metadata = { title: `Product details - ${CONFIG.appName}` };
export default function Page() { export default function Page() {
const { id = '' } = useParams(); const { id = '' } = useParams();
const { product, productLoading, productError } = useGetProduct(id); const { currentProduct: product, productLoading, productError } = useGetProduct(id);
return ( return (
<> <>

Some files were not shown because too many files have changed in this diff Show More