Compare commits
73 Commits
develop/cm
...
develop/mo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b9fbe4e476 | ||
![]() |
0d844eed3f | ||
![]() |
2862cddb6b | ||
![]() |
55509b483c | ||
![]() |
3f2ac2a285 | ||
![]() |
1216de4ff9 | ||
![]() |
7966d8abf5 | ||
![]() |
e975aebfcd | ||
![]() |
7f6970b183 | ||
![]() |
e83854ed2a | ||
![]() |
6961f058df | ||
![]() |
b515337acc | ||
![]() |
c732d89c34 | ||
![]() |
2b71d06c8d | ||
![]() |
3ec9d87f8b | ||
![]() |
546fb72732 | ||
![]() |
4303704753 | ||
![]() |
4bb213ef0c | ||
![]() |
657c652657 | ||
![]() |
5be77aae23 | ||
![]() |
ffbe63e421 | ||
![]() |
ba1e718039 | ||
![]() |
8b32d153db | ||
![]() |
d3e554b218 | ||
![]() |
5b10977a64 | ||
![]() |
a40b0fa4b1 | ||
![]() |
a4692a7d1f | ||
![]() |
c3f680aa22 | ||
![]() |
5b680f2219 | ||
![]() |
d3d95469ea | ||
![]() |
04eaf91d60 | ||
![]() |
be571ba4db | ||
![]() |
a6d549b2e8 | ||
![]() |
beb1e0ae68 | ||
![]() |
b2adcff61b | ||
![]() |
8200e0d902 | ||
![]() |
731cb7500e | ||
![]() |
d6c87e33f0 | ||
![]() |
e4b6c5e92d | ||
![]() |
03782cde24 | ||
![]() |
7cc6d939f5 | ||
![]() |
3bcb40c5ef | ||
![]() |
43a285dd2c | ||
![]() |
09a8dc539e | ||
![]() |
8f75226763 | ||
![]() |
7cfbcc1573 | ||
![]() |
5234ac06cd | ||
![]() |
aec59fb328 | ||
![]() |
7f9b4c2224 | ||
![]() |
197b006df3 | ||
![]() |
3692b2204b | ||
![]() |
3217a8d594 | ||
![]() |
d453144500 | ||
![]() |
e8d12f34e8 | ||
![]() |
56d43062c9 | ||
![]() |
c2a02cff77 | ||
![]() |
d3ef280b20 | ||
![]() |
b2e9616178 | ||
![]() |
d909805283 | ||
![]() |
8c46a93e61 | ||
![]() |
82507b4b31 | ||
![]() |
85651ff204 | ||
![]() |
9caca30e1e | ||
![]() |
d76d43d17f | ||
![]() |
dff07ddcb0 | ||
![]() |
b78709db9b | ||
![]() |
df9992454b | ||
![]() |
22fb620eef | ||
![]() |
c0fad42f0a | ||
![]() |
c2c6181332 | ||
![]() |
99239c32a5 | ||
![]() |
ef0c0ab389 | ||
![]() |
7610d80005 |
@@ -28,7 +28,8 @@
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push --force-reset",
|
||||
"db:push:w": "npx nodemon --delay 1 --watch prisma --ext \"ts,tsx,prisma\" --exec \"yarn db:push && yarn seed\"",
|
||||
"db:studio": "prisma studio"
|
||||
"db:studio": "prisma studio",
|
||||
"db:studio:w": "npx nodemon --delay 1 --watch prisma --ext \"prisma\" --exec \"yarn db:studio\""
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
@@ -71,6 +71,15 @@ model VerificationToken {
|
||||
@@unique([identifier, token])
|
||||
}
|
||||
|
||||
model Student {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
//
|
||||
email String @unique
|
||||
metadata Json @default("{}")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// frontend/src/_mock/_user.ts
|
||||
|
||||
@@ -525,6 +534,7 @@ model UserCard {
|
||||
totalFollowing Float
|
||||
}
|
||||
|
||||
// `UserItem` obsoleted, use `UserMeta` instead
|
||||
model UserItem {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
@@ -550,6 +560,31 @@ model UserItem {
|
||||
isAdmin Boolean @default(false)
|
||||
}
|
||||
|
||||
model UserMeta {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
//
|
||||
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
|
||||
//
|
||||
isAdmin Boolean @default(false)
|
||||
}
|
||||
|
||||
model UserAccountBillingHistory {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
|
@@ -30,6 +30,7 @@ import { EventItemSeed } from './seeds/eventItem';
|
||||
import { EventReviewSeed } from './seeds/eventReview';
|
||||
import { appLogSeed } from './seeds/AppLog';
|
||||
import { accessLogSeed } from './seeds/AccessLog';
|
||||
import { userMetaSeed } from './seeds/userMeta';
|
||||
|
||||
//
|
||||
// import { Blog } from './seeds/blog';
|
||||
@@ -46,7 +47,10 @@ import { accessLogSeed } from './seeds/AccessLog';
|
||||
await ProductReview;
|
||||
await FileStore;
|
||||
await ProductItem;
|
||||
|
||||
await userItemSeed;
|
||||
await userMetaSeed;
|
||||
|
||||
await orderItemSeed;
|
||||
await invoiceItemSeed;
|
||||
//
|
||||
|
130
03_source/cms_backend/prisma/seeds/userMeta.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
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 userMeta() {
|
||||
const config: Config = { dictionaries: [names] };
|
||||
const firstName = uniqueNamesGenerator(config);
|
||||
const lastName = uniqueNamesGenerator(config);
|
||||
const username = `${firstName.toLowerCase()}-${lastName.toLowerCase()}`;
|
||||
|
||||
const alice = await prisma.userMeta.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'),
|
||||
//
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (let i = 1; i < 3; 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.userMeta.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!'),
|
||||
//
|
||||
isAdmin: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('seed userMeta done');
|
||||
}
|
||||
|
||||
const userMetaSeed = userMeta()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export { userMetaSeed };
|
||||
|
||||
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'];
|
@@ -25,6 +25,7 @@ export async function POST(req: NextRequest) {
|
||||
const { data } = await req.json();
|
||||
|
||||
try {
|
||||
// TODO: obsolete createNewAppLog
|
||||
const createResult = await createNewAppLog(data);
|
||||
|
||||
return response(createResult, STATUS.OK);
|
||||
|
@@ -1,13 +1,20 @@
|
||||
import type { User } from '@prisma/client';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { headers } from 'next/headers';
|
||||
|
||||
import { verify } from 'src/utils/jwt';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { _users, JWT_SECRET } from 'src/_mock/_auth';
|
||||
import { JWT_SECRET } from 'src/_mock/_auth';
|
||||
import { getUserById } from 'src/app/services/user.service';
|
||||
import { createAccessLog } from 'src/app/services/AccessLog.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const runtime = 'edge';
|
||||
// export const runtime = 'edge';
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
@@ -17,25 +24,44 @@ export const runtime = 'edge';
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
export async function GET() {
|
||||
const USER_TOKEN_CHECK_FAILED = 'user token check failed';
|
||||
const INVALID_AUTH_TOKEN = 'Invalid authorization token';
|
||||
const USER_ID_NOT_FOUND = 'userId not found';
|
||||
const USER_TOKEN_OK = 'user token check ok';
|
||||
const AUTHORIZATION_TOKEN_MISSING_OR_INVALID = 'Authorization token missing or invalid';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const headersList = headers();
|
||||
const authorization = headersList.get('authorization');
|
||||
|
||||
if (!authorization || !authorization.startsWith('Bearer ')) {
|
||||
return response({ message: 'Authorization token missing or invalid' }, STATUS.UNAUTHORIZED);
|
||||
return response({ message: AUTHORIZATION_TOKEN_MISSING_OR_INVALID }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const accessToken = `${authorization}`.split(' ')[1];
|
||||
const data = await verify(accessToken, JWT_SECRET);
|
||||
console.log(data.userId);
|
||||
|
||||
const currentUser = _users.find((user) => user.id === data.userId);
|
||||
if (data.userId) {
|
||||
// TODO: remove me
|
||||
// const currentUser = _users.find((user) => user.id === data.userId);
|
||||
const currentUser: User | null = await getUserById(data.userId);
|
||||
|
||||
if (!currentUser) {
|
||||
return response({ message: 'Invalid authorization token' }, STATUS.UNAUTHORIZED);
|
||||
if (!currentUser) {
|
||||
createAccessLog('', USER_TOKEN_CHECK_FAILED, debug);
|
||||
|
||||
return response({ message: INVALID_AUTH_TOKEN }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
createAccessLog(currentUser.id, USER_TOKEN_OK, debug);
|
||||
|
||||
return response({ user: currentUser }, STATUS.OK);
|
||||
} else {
|
||||
return response({ message: USER_ID_NOT_FOUND }, STATUS.ERROR);
|
||||
}
|
||||
|
||||
return response({ user: currentUser }, 200);
|
||||
} catch (error) {
|
||||
return handleError('[Auth] - Me', error);
|
||||
}
|
||||
|
25
03_source/cms_backend/src/app/api/auth/me/test.http
Normal file
@@ -0,0 +1,25 @@
|
||||
###
|
||||
# username and password ok
|
||||
GET http://localhost:7272/api/auth/me
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWJnbnUyengwMDBjaHEzaGZ3dmtjejlvIiwiaWF0IjoxNzQ4OTY0ODkyLCJleHAiOjE3NTAxNzQ0OTJ9.lo04laCxtm0IVeYaETEV3hXKyDmXPEn7SyWtY2VR4dI
|
||||
|
||||
|
||||
###
|
||||
# There is no user corresponding to the email address.
|
||||
POST http://localhost:7272/api/auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals1.cc",
|
||||
"password": "@2Minimal"
|
||||
}
|
||||
|
||||
###
|
||||
# Wrong password
|
||||
POST http://localhost:7272/api/auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals.cc",
|
||||
"password": "@2Min111imal"
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
export function flattenNextjsRequest(req: NextRequest) {
|
||||
return Object.fromEntries(req.headers.entries());
|
||||
}
|
@@ -7,6 +7,7 @@ import { JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth';
|
||||
import { createAccessLog } from 'src/app/services/AccessLog.service';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
import { flattenNextjsRequest } from './flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@@ -22,7 +23,7 @@ const ERR_USER_NOT_FOUND = 'There is no user corresponding to the email address.
|
||||
const ERR_WRONG_PASSWORD = 'Wrong password';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const debug = { 'req.headers': Object.fromEntries(req.headers.entries()) };
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const { email, password } = await req.json();
|
||||
|
25
03_source/cms_backend/src/app/api/event/_GUIDELINES.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# GUIDELINE
|
||||
|
||||
## Event / event
|
||||
|
||||
- this is a `event` api endpoint
|
||||
- this is a demo to handle `event` record
|
||||
- use single file for single db table/collection only
|
||||
|
||||
## `route.ts`
|
||||
|
||||
handle `GET`, `POST`, `PUT`, `DELETE`
|
||||
|
||||
## `test.http`
|
||||
|
||||
store test request
|
||||
|
||||
## `../../services/event.service.ts`
|
||||
|
||||
event schema CRUD handler
|
||||
|
||||
`listEvents` - list `event` record
|
||||
`getEvent` - get `event` record by id
|
||||
`createNewEvent` - create `event` record
|
||||
`updateEvent` - update `event` record by id
|
||||
`deleteEvent` - delete `event` record by id
|
@@ -8,10 +8,11 @@
|
||||
|
||||
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';
|
||||
import { L_INFO, L_ERROR } from 'src/constants';
|
||||
import { getEvent } from 'src/app/services/eventItem.service';
|
||||
import { createAppLog } from 'src/app/services/AppLog.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@@ -19,29 +20,32 @@ import prisma from '../../../lib/prisma';
|
||||
* GET Event detail
|
||||
*************************************** */
|
||||
export async function GET(req: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const debug = { 'req.headers': Object.fromEntries(req.headers.entries()) };
|
||||
|
||||
// RULES: eventId must exist
|
||||
const eventId = searchParams.get('eventId');
|
||||
const { searchParams } = req.nextUrl;
|
||||
// RULES: for the incoming request, the `eventId` must exist
|
||||
const eventId = searchParams.get('eventId');
|
||||
|
||||
try {
|
||||
if (!eventId) {
|
||||
return response({ message: 'Event ID is required!' }, STATUS.BAD_REQUEST);
|
||||
}
|
||||
|
||||
// NOTE: eventId confirmed exist, run below
|
||||
const event = await prisma.eventItem.findFirst({
|
||||
include: { reviews: true },
|
||||
where: { id: eventId },
|
||||
});
|
||||
// NOTE: `eventId` confirmed exist, run below
|
||||
const event = await getEvent(eventId);
|
||||
console.log({ event });
|
||||
|
||||
// RULES: show error if not found
|
||||
if (!event) {
|
||||
return response({ message: 'Event not found!' }, STATUS.NOT_FOUND);
|
||||
}
|
||||
|
||||
logger('[Event] details', event.id);
|
||||
// logger('[Event] details', event.id);
|
||||
await createAppLog(L_INFO, 'get event detail ok', { eventId });
|
||||
|
||||
return response({ event }, STATUS.OK);
|
||||
} catch (error) {
|
||||
await createAppLog(L_ERROR, 'error during getting event detail', { debug, eventId });
|
||||
return handleError('Event - Get details', error);
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/event/details?eventId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01
|
||||
|
||||
###
|
||||
GET http://localhost:7272/api/event/details
|
||||
|
@@ -1,17 +1,17 @@
|
||||
// src/app/api/event/list/route.ts
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
// src/app/api/event/list/route.ts
|
||||
import { listEvents } from 'src/app/services/eventItem.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Events
|
||||
* GET - Events, obsoleted
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const events = await prisma.eventItem.findMany();
|
||||
const events = await listEvents();
|
||||
|
||||
logger('[Event] list', events.length);
|
||||
|
||||
|
@@ -10,7 +10,11 @@ import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { L_INFO, L_ERROR } from 'src/constants';
|
||||
import { createAppLog } from 'src/app/services/AppLog.service';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@@ -21,6 +25,8 @@ import prisma from '../../../lib/prisma';
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
// Original user details functionality
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
|
||||
@@ -32,8 +38,12 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
if (!helloworld) return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
|
||||
|
||||
createAppLog(L_INFO, 'Get OK', debug);
|
||||
|
||||
return response({ helloworld }, STATUS.OK);
|
||||
} catch (error) {
|
||||
createAppLog(L_ERROR, 'Get error', debug);
|
||||
|
||||
return handleError('Product - Get details', error);
|
||||
}
|
||||
}
|
||||
|
3
03_source/cms_backend/src/app/api/product/_GUIDELINES.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# GUIDELINES
|
||||
|
||||
T.B.A.
|
@@ -1,7 +1,7 @@
|
||||
// src/app/api/product/details/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// save product to db by id
|
||||
// get product from db by id
|
||||
//
|
||||
// RULES:
|
||||
// T.B.A.
|
||||
@@ -11,14 +11,22 @@ 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';
|
||||
import { L_INFO, L_ERROR } from 'src/constants';
|
||||
import { getProduct } from 'src/app/services/product.service';
|
||||
import { createAppLog } from 'src/app/services/AppLog.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
/**
|
||||
**************************************
|
||||
* GET Product detail
|
||||
*************************************** */
|
||||
***************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
|
||||
@@ -29,10 +37,7 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
|
||||
// NOTE: productId confirmed exist, run below
|
||||
const product = await prisma.productItem.findFirst({
|
||||
include: { reviews: true },
|
||||
where: { id: productId },
|
||||
});
|
||||
const product = await getProduct(productId);
|
||||
|
||||
if (!product) {
|
||||
return response({ message: 'Product not found!' }, STATUS.NOT_FOUND);
|
||||
@@ -40,8 +45,12 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
logger('[Product] details', product.id);
|
||||
|
||||
createAppLog(L_INFO, 'Get product detail OK', debug);
|
||||
|
||||
return response({ product }, STATUS.OK);
|
||||
} catch (error) {
|
||||
createAppLog(L_ERROR, 'product detail error', debug);
|
||||
|
||||
return handleError('Product - Get details', error);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,3 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/product/details?productId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01
|
@@ -1,22 +1,41 @@
|
||||
// src/app/api/product/list/route.ts
|
||||
import { logger } from 'src/utils/logger';
|
||||
//
|
||||
// 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';
|
||||
import { L_INFO, L_ERROR } from 'src/constants';
|
||||
import { createAppLog } from 'src/app/services/AppLog.service';
|
||||
import { listProducts } from 'src/app/services/product.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Products
|
||||
* GET - Products list
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const products = await prisma.productItem.findMany();
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
logger('[Product] list', products.length);
|
||||
try {
|
||||
// const products = await prisma.productItem.findMany();
|
||||
const products = await listProducts();
|
||||
|
||||
// logger('[Product] list', products.length);
|
||||
|
||||
createAppLog(L_INFO, 'product list ok', {});
|
||||
|
||||
return response({ products }, STATUS.OK);
|
||||
} catch (error) {
|
||||
createAppLog(L_ERROR, 'product list error', debug);
|
||||
|
||||
return handleError('Product - Get list', error);
|
||||
}
|
||||
}
|
||||
|
3
03_source/cms_backend/src/app/api/product/list/test.http
Normal file
@@ -0,0 +1,3 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/product/list
|
23
03_source/cms_backend/src/app/api/student/_GUIDELINES.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# GUIDELINE
|
||||
|
||||
- this is a helloworld api endpoint
|
||||
- this is a demo to handle helloworld record
|
||||
- use single file for single db table/collection only
|
||||
|
||||
## `route.ts`
|
||||
|
||||
handle `GET`, `POST`, `PUT`, `DELETE`
|
||||
|
||||
## `test.http`
|
||||
|
||||
store test request
|
||||
|
||||
## `../../services/helloworld.service.ts`
|
||||
|
||||
helloworld schema CRUD handler
|
||||
|
||||
`listHelloworlds` - list helloworld record
|
||||
`getHelloworld` - get helloworld record by id
|
||||
`createNewHelloworld` - create helloworld record
|
||||
`updateHelloworld` - update helloworld record by id
|
||||
`deleteHelloworld` - delete helloworld record by id
|
39
03_source/cms_backend/src/app/api/student/detail/route.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// src/app/api/helloworld/detail/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// Get single helloworld record detail
|
||||
//
|
||||
// RULES:
|
||||
// - For helloworld requests, return simple response
|
||||
//
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
**************************************
|
||||
* GET - Handle both helloworld and user details
|
||||
**************************************
|
||||
*/
|
||||
export async function GET(req: NextRequest) {
|
||||
// Original user details functionality
|
||||
try {
|
||||
const { searchParams } = req.nextUrl;
|
||||
|
||||
// RULES: helloworldId must exist
|
||||
const helloworldId = searchParams.get('helloworldId');
|
||||
if (!helloworldId) return response({ message: 'helloworldId is required!' }, STATUS.BAD_REQUEST);
|
||||
|
||||
const helloworld = await prisma.userItem.findFirst({ where: { id: helloworldId } });
|
||||
|
||||
if (!helloworld) return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
|
||||
|
||||
return response({ helloworld }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Product - Get details', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/helloworld/details?helloworldId=1165ce3a-29b8-4e1a-9148-1ae08d7e8e01
|
||||
|
34
03_source/cms_backend/src/app/api/student/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import prisma from '@/lib/prisma';
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
// GET: 获取所有学生
|
||||
export async function GET() {
|
||||
try {
|
||||
const students = await prisma.student.findMany();
|
||||
return NextResponse.json(students);
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to fetch students' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST: 创建新学生
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const { email, metadata } = await request.json();
|
||||
|
||||
if (!email) {
|
||||
return NextResponse.json({ error: 'Email is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
const student = await prisma.student.create({
|
||||
data: {
|
||||
email,
|
||||
metadata: metadata || {},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(student, { status: 201 });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: 'Failed to create student' }, { status: 500 });
|
||||
}
|
||||
}
|
25
03_source/cms_backend/src/app/api/student/test.http
Normal file
@@ -0,0 +1,25 @@
|
||||
###
|
||||
GET http://localhost:7272/api/helloworld
|
||||
|
||||
###
|
||||
GET http://localhost:7272/api/helloworld?helloworldId=1
|
||||
|
||||
###
|
||||
POST http://localhost:7272/api/helloworld?helloworldId=1
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"data":{"hello": "hell"}
|
||||
}
|
||||
|
||||
###
|
||||
PUT http://localhost:7272/api/helloworld?helloworldId=1
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"data": {"hello": "hell"}
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
DELETE http://localhost:7272/api/helloworld?helloworldId=1
|
@@ -16,6 +16,7 @@ import prisma from '../lib/prisma';
|
||||
type CreateAppLog = {
|
||||
level: number;
|
||||
message: string;
|
||||
metadata: Record<string, any>;
|
||||
};
|
||||
|
||||
// type UpdateAppLog = {
|
||||
@@ -31,6 +32,10 @@ async function getAppLog(appLogId: string) {
|
||||
return prisma.appLog.findFirst({ where: { id: appLogId } });
|
||||
}
|
||||
|
||||
async function createAppLog(level: number, message: string, metadata: Record<string, any>) {
|
||||
return prisma.appLog.create({ data: { level, message, metadata } });
|
||||
}
|
||||
|
||||
async function createNewAppLog(createForm: CreateAppLog) {
|
||||
return prisma.appLog.create({ data: createForm });
|
||||
}
|
||||
@@ -48,5 +53,6 @@ export {
|
||||
listAppLogs,
|
||||
// updateAppLog,
|
||||
deleteAppLog,
|
||||
createAppLog,
|
||||
createNewAppLog,
|
||||
};
|
||||
|
@@ -7,10 +7,6 @@
|
||||
// - Follows same pattern as helloworld.service.ts
|
||||
//
|
||||
|
||||
import type { Event } from '@prisma/client';
|
||||
|
||||
import prisma from '../lib/prisma';
|
||||
|
||||
type CreateEvent = {
|
||||
eventDate: DateTime;
|
||||
title: string;
|
||||
@@ -39,27 +35,33 @@ type UpdateEvent = {
|
||||
memberId?: number;
|
||||
};
|
||||
|
||||
async function listEvents(): Promise<Event[]> {
|
||||
return prisma.event.findMany();
|
||||
}
|
||||
// async function listEvents(): Promise<Event[]> {
|
||||
// return prisma.event.findMany();
|
||||
// }
|
||||
|
||||
async function getEvent(eventId: number) {
|
||||
return prisma.event.findFirst({ where: { id: eventId } });
|
||||
}
|
||||
// async function getEvent(eventId: number) {
|
||||
// return prisma.event.findFirst({ where: { id: eventId } });
|
||||
// }
|
||||
|
||||
async function createNewEvent(createForm: CreateEvent) {
|
||||
return prisma.event.create({ data: createForm });
|
||||
}
|
||||
// async function createNewEvent(createForm: CreateEvent) {
|
||||
// return prisma.event.create({ data: createForm });
|
||||
// }
|
||||
|
||||
async function updateEvent(eventId: number, updateForm: UpdateEvent) {
|
||||
return prisma.event.update({
|
||||
where: { id: eventId },
|
||||
data: updateForm,
|
||||
});
|
||||
}
|
||||
// async function updateEvent(eventId: number, updateForm: UpdateEvent) {
|
||||
// return prisma.event.update({
|
||||
// where: { id: eventId },
|
||||
// data: updateForm,
|
||||
// });
|
||||
// }
|
||||
|
||||
async function deleteEvent(eventId: number) {
|
||||
return prisma.event.delete({ where: { id: eventId } });
|
||||
}
|
||||
// async function deleteEvent(eventId: number) {
|
||||
// return prisma.event.delete({ where: { id: eventId } });
|
||||
// }
|
||||
|
||||
export { getEvent, listEvents, updateEvent, deleteEvent, createNewEvent };
|
||||
export {
|
||||
listEvents,
|
||||
// getEvent,
|
||||
// updateEvent,
|
||||
// deleteEvent,
|
||||
// createNewEvent,
|
||||
};
|
||||
|
71
03_source/cms_backend/src/app/services/eventItem.service.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// src/app/services/event.service.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Service for handling Event records
|
||||
//
|
||||
// RULES:
|
||||
// - Follows same pattern as helloworld.service.ts
|
||||
//
|
||||
|
||||
import type { EventItem } from '@prisma/client';
|
||||
|
||||
import prisma from '../lib/prisma';
|
||||
|
||||
type CreateEvent = {
|
||||
eventDate: DateTime;
|
||||
title: string;
|
||||
joinMembers?: Json[];
|
||||
price: number;
|
||||
currency: string;
|
||||
duration_m: number;
|
||||
ageBottom: number;
|
||||
ageTop: number;
|
||||
location: string;
|
||||
avatar?: string;
|
||||
memberId?: number;
|
||||
};
|
||||
|
||||
type UpdateEvent = {
|
||||
eventDate?: DateTime;
|
||||
title?: string;
|
||||
joinMembers?: Json[];
|
||||
price?: number;
|
||||
currency?: string;
|
||||
duration_m?: number;
|
||||
ageBottom?: number;
|
||||
ageTop?: number;
|
||||
location?: string;
|
||||
avatar?: string;
|
||||
memberId?: number;
|
||||
};
|
||||
|
||||
async function listEvents(): Promise<EventItem[]> {
|
||||
return prisma.eventItem.findMany();
|
||||
}
|
||||
|
||||
async function getEvent(eventId: string): Promise<EventItem | null> {
|
||||
return prisma.eventItem.findFirst({ where: { id: eventId } });
|
||||
}
|
||||
|
||||
// async function createNewEvent(createForm: CreateEvent) {
|
||||
// return prisma.event.create({ data: createForm });
|
||||
// }
|
||||
|
||||
// async function updateEvent(eventId: number, updateForm: UpdateEvent) {
|
||||
// return prisma.event.update({
|
||||
// where: { id: eventId },
|
||||
// data: updateForm,
|
||||
// });
|
||||
// }
|
||||
|
||||
// async function deleteEvent(eventId: number) {
|
||||
// return prisma.event.delete({ where: { id: eventId } });
|
||||
// }
|
||||
|
||||
export {
|
||||
getEvent,
|
||||
listEvents,
|
||||
// updateEvent,
|
||||
// deleteEvent,
|
||||
// createNewEvent,
|
||||
};
|
@@ -68,7 +68,7 @@ async function listProducts(): Promise<ProductItem[]> {
|
||||
return prisma.productItem.findMany();
|
||||
}
|
||||
|
||||
async function getProduct(productId: string) {
|
||||
async function getProduct(productId: string): Promise<ProductItem | null> {
|
||||
return prisma.productItem.findUnique({ where: { id: productId } });
|
||||
}
|
||||
|
||||
|
127
03_source/cms_backend/src/app/services/user.service.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
// src/app/services/user.service.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Handle User Record CRUD operations
|
||||
//
|
||||
// RULES:
|
||||
// - Follow Prisma best practices for database operations
|
||||
// - Validate input data before processing
|
||||
//
|
||||
|
||||
import type { User } from '@prisma/client';
|
||||
|
||||
import prisma from '../lib/prisma';
|
||||
|
||||
type CreateUser = {
|
||||
email: string;
|
||||
// name?: string;
|
||||
// password: string;
|
||||
// role?: Role;
|
||||
// isEmailVerified?: boolean;
|
||||
// admin?: boolean;
|
||||
};
|
||||
|
||||
type UpdateUser = {
|
||||
email?: string;
|
||||
// name?: string;
|
||||
// password?: string;
|
||||
// role?: Role;
|
||||
// isEmailVerified?: boolean;
|
||||
isAdmin?: boolean;
|
||||
};
|
||||
|
||||
// async function listUsers(): Promise<UserItem[]> {
|
||||
// return prisma.userItem.findMany();
|
||||
// }
|
||||
|
||||
// async function getUserItem(userId: string): Promise<UserItem | null> {
|
||||
// return prisma.userItem.findFirst({ where: { id: userId } });
|
||||
// }
|
||||
|
||||
// async function updateUser(userId: string, updateForm: UpdateUser): Promise<User> {
|
||||
// return prisma.userItem.update({
|
||||
// where: { id: userId },
|
||||
// data: updateForm,
|
||||
// });
|
||||
// }
|
||||
|
||||
// // check if userId is a admin
|
||||
// // check if userId is a admin
|
||||
// async function isAdmin(userId: string): Promise<boolean> {
|
||||
// const user = await getUserItem(userId);
|
||||
// return user?.isAdmin === true;
|
||||
// }
|
||||
|
||||
// async function changeToAdmin(userIdToPromote: string, userIdOfApplicant: string) {
|
||||
// // check the applicant is admin or not
|
||||
// const userApplicant = await getUserItem(userIdOfApplicant);
|
||||
// let promoteResult = {};
|
||||
|
||||
// if (userApplicant && userApplicant.isAdmin) {
|
||||
// // applicant is an admin
|
||||
// promoteResult = await updateUser(userIdToPromote, { isAdmin: true });
|
||||
// } else {
|
||||
// promoteResult = { status: 'failed', message: 'applicant is not a admin' };
|
||||
// }
|
||||
|
||||
// return promoteResult;
|
||||
// }
|
||||
|
||||
// async function changeToUser(userIdToPromote: string, userIdOfApplicant: string) {
|
||||
// // check the applicant is admin or not
|
||||
// const userApplicant = await getUserItem(userIdOfApplicant);
|
||||
// let promoteResult = {};
|
||||
|
||||
// if (userApplicant && userApplicant.isAdmin) {
|
||||
// // applicant is an admin
|
||||
// promoteResult = await updateUser(userIdToPromote, { isAdmin: false });
|
||||
// } else {
|
||||
// promoteResult = { status: 'failed', message: 'applicant is not a admin' };
|
||||
// }
|
||||
|
||||
// return promoteResult;
|
||||
// }
|
||||
|
||||
async function getUserById(id: string): Promise<User | null> {
|
||||
return prisma.user.findFirst({ where: { id } });
|
||||
}
|
||||
|
||||
// async function getUserByEmail(email: string): Promise<void> {
|
||||
// // return prisma.userItem.findUnique({
|
||||
// // where: { email },
|
||||
// // include: {
|
||||
// // Token: true,
|
||||
// // },
|
||||
// // });
|
||||
// }
|
||||
|
||||
// async function createNewUser(createForm: CreateUser): Promise<void> {
|
||||
// // return prisma.userItem.create({
|
||||
// // data: {
|
||||
// // email: createForm.email,
|
||||
// // name: createForm.name,
|
||||
// // password: createForm.password,
|
||||
// // role: createForm.role || 'USER',
|
||||
// // isEmailVerified: createForm.isEmailVerified || false,
|
||||
// // },
|
||||
// // });
|
||||
// }
|
||||
|
||||
// async function deleteUser(userId: number): Promise<void> {
|
||||
// // return prisma.userItem.delete({ where: { id: userId } });
|
||||
// }
|
||||
|
||||
export {
|
||||
// getUser,
|
||||
getUserById,
|
||||
// isAdmin,
|
||||
// listUsers,
|
||||
// updateUser,
|
||||
// deleteUser,
|
||||
// changeToUser,
|
||||
// createNewUser,
|
||||
// changeToAdmin,
|
||||
// getUserByEmail,
|
||||
// type CreateUser,
|
||||
// type UpdateUser,
|
||||
};
|
@@ -82,6 +82,10 @@ async function changeToUser(userIdToPromote: string, userIdOfApplicant: string)
|
||||
return promoteResult;
|
||||
}
|
||||
|
||||
async function getUserById1(id: string): Promise<UserItem | null> {
|
||||
return prisma.userItem.findFirst({ where: { id } });
|
||||
}
|
||||
|
||||
async function getUserByEmail(email: string): Promise<void> {
|
||||
// return prisma.userItem.findUnique({
|
||||
// where: { email },
|
||||
@@ -113,6 +117,7 @@ export {
|
||||
listUsers,
|
||||
updateUser,
|
||||
deleteUser,
|
||||
getUserById,
|
||||
changeToUser,
|
||||
createNewUser,
|
||||
changeToAdmin,
|
||||
|
7
03_source/cms_backend/src/constants.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
const L_ERROR = 0;
|
||||
const L_WARN = 1;
|
||||
const L_INFO = 2;
|
||||
const L_DEBUG = 3;
|
||||
const L_TRACE = 4;
|
||||
|
||||
export { L_WARN, L_INFO, L_ERROR, L_DEBUG, L_TRACE };
|
@@ -12,11 +12,7 @@ type Token = {
|
||||
* SignJWT
|
||||
* https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md
|
||||
*/
|
||||
export async function sign(
|
||||
payload: Token,
|
||||
secret: string,
|
||||
options: { expiresIn: string | number }
|
||||
): Promise<string> {
|
||||
export async function sign(payload: Token, secret: string, options: { expiresIn: string | number }): Promise<string> {
|
||||
const iat = Math.floor(Date.now() / 1000);
|
||||
|
||||
return new SignJWT({ ...payload })
|
||||
|
@@ -1,7 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
yarn --dev
|
||||
|
||||
while true; do
|
||||
yarn --dev
|
||||
|
||||
# yarn tsc:print
|
||||
yarn lint:print
|
||||
|
||||
yarn dev --force --clearScreen
|
||||
|
||||
|
1721
03_source/frontend/eslint-show-config.json
Normal file
@@ -105,6 +105,7 @@
|
||||
"react-organizational-chart": "^2.2.1",
|
||||
"react-phone-number-input": "^3.4.12",
|
||||
"react-router": "^7.4.1",
|
||||
"react-use": "^17.6.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// 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 { IInvoiceItem, SaveInvoiceData } from 'src/types/invoice';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR from 'swr';
|
||||
|
||||
@@ -97,7 +97,7 @@ export function useSearchInvoices(query: string) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type SaveInvoiceData = IInvoiceItem;
|
||||
// type SaveInvoiceData = IInvoiceItem;
|
||||
|
||||
export async function saveInvoice(invoiceId: string, saveInvoiceData: SaveInvoiceData) {
|
||||
const url = endpoints.invoice.saveInvoice(invoiceId);
|
||||
|
@@ -99,6 +99,7 @@ export function useSearchProducts(query: string) {
|
||||
|
||||
type SaveProductData = {
|
||||
// id: string;
|
||||
|
||||
sku: string;
|
||||
name: string;
|
||||
code: string;
|
||||
|
@@ -17,6 +17,8 @@ export type SignUpParams = {
|
||||
lastName: string;
|
||||
};
|
||||
|
||||
const ERR_ACCESS_TOKEN_NOT_FOUND = `Access token not found in response`;
|
||||
|
||||
/** **************************************
|
||||
* Sign in
|
||||
*************************************** */
|
||||
@@ -29,7 +31,7 @@ export const signInWithPassword = async ({ email, password }: SignInParams): Pro
|
||||
const { accessToken } = res.data;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('Access token not found in response');
|
||||
throw new Error(ERR_ACCESS_TOKEN_NOT_FOUND);
|
||||
}
|
||||
|
||||
setSession(accessToken);
|
||||
|
@@ -69,6 +69,8 @@ export function tokenExpired(exp: number) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const INVALID_ACCESS_TOKEN = 'Invalid access token!';
|
||||
|
||||
export async function setSession(accessToken: string | null) {
|
||||
try {
|
||||
if (accessToken) {
|
||||
@@ -81,7 +83,7 @@ export async function setSession(accessToken: string | null) {
|
||||
if (decodedToken && 'exp' in decodedToken) {
|
||||
tokenExpired(decodedToken.exp);
|
||||
} else {
|
||||
throw new Error('Invalid access token!');
|
||||
throw new Error(INVALID_ACCESS_TOKEN);
|
||||
}
|
||||
} else {
|
||||
sessionStorage.removeItem(JWT_STORAGE_KEY);
|
||||
|
@@ -11,7 +11,7 @@ const metadata = { title: `User edit | Dashboard - ${CONFIG.appName}` };
|
||||
export default function Page() {
|
||||
const { id = '' } = useParams();
|
||||
|
||||
// TODO: remove me
|
||||
// TODO: remove unused code
|
||||
// const currentUser = _userList.find((user) => user.id === id);
|
||||
const { user } = useGetUser(id);
|
||||
|
||||
|
@@ -106,7 +106,8 @@ export function CalendarToolbar({
|
||||
<Iconify icon="eva:arrow-ios-back-fill" />
|
||||
</IconButton>
|
||||
|
||||
<Typography variant="h6">{date}</Typography>
|
||||
{/* FIXME: no raw json output in html */}
|
||||
<Typography variant="h6">{JSON.stringify({ date })}</Typography>
|
||||
|
||||
<IconButton onClick={onNextDate}>
|
||||
<Iconify icon="eva:arrow-ios-forward-fill" />
|
||||
|
@@ -37,19 +37,22 @@ export function InvoiceDetails({ invoice }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const [currentStatus, setCurrentStatus] = useState(invoice?.status);
|
||||
|
||||
const handleChangeStatus = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// setCurrentStatus(event.target.value);
|
||||
const handleChangeStatus = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
// setCurrentStatus(event.target.value);
|
||||
|
||||
try {
|
||||
changeStatus(invoice.id, event.target.value);
|
||||
setCurrentStatus(event.target.value);
|
||||
try {
|
||||
changeStatus(invoice.id, event.target.value);
|
||||
setCurrentStatus(event.target.value);
|
||||
|
||||
toast.success('status changed!');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.warning('error during changing status');
|
||||
}
|
||||
}, []);
|
||||
toast.success('status changed!');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.warning('error during changing status');
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const renderFooter = () => (
|
||||
<Box
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { IInvoiceItem } from 'src/types/invoice';
|
||||
import type { IInvoiceItem, IInvoiceItemItem } from 'src/types/invoice';
|
||||
|
||||
import { sumBy } from 'es-toolkit';
|
||||
import { useEffect, useCallback } from 'react';
|
||||
@@ -22,7 +22,7 @@ import { InvoiceTotalSummary } from './invoice-total-summary';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const defaultItem: Omit<IInvoiceItem, 'id'> = {
|
||||
export const defaultItem: Omit<IInvoiceItemItem, 'id'> = {
|
||||
title: '',
|
||||
description: '',
|
||||
service: INVOICE_SERVICE_OPTIONS[0].name,
|
||||
@@ -50,7 +50,7 @@ export function InvoiceNewEditDetails() {
|
||||
const discount = getValues('discount');
|
||||
const shipping = getValues('shipping');
|
||||
|
||||
const subtotal = sumBy(items, (item: IInvoiceItem) => item.quantity * item.price);
|
||||
const subtotal = sumBy(items, (item: IInvoiceItemItem) => item.quantity * item.price);
|
||||
const subtotalWithTax = subtotal + subtotal * (taxes / 100);
|
||||
const totalAmount = subtotalWithTax - discount - shipping;
|
||||
|
||||
|
@@ -81,6 +81,8 @@ export function InvoiceNewEditForm({ currentInvoice }: Props) {
|
||||
const loadingSend = useBoolean();
|
||||
|
||||
const defaultValues: NewInvoiceSchemaType = {
|
||||
id: '',
|
||||
sent: 0,
|
||||
invoiceNumber: 'INV-1990',
|
||||
createDate: today(),
|
||||
dueDate: null,
|
||||
@@ -129,6 +131,8 @@ export function InvoiceNewEditForm({ currentInvoice }: Props) {
|
||||
|
||||
try {
|
||||
if (currentInvoice) {
|
||||
data.dueDate = '2029-01-01';
|
||||
|
||||
await saveInvoice(currentInvoice.id, data);
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,7 @@ type Props = {
|
||||
invoice?: IInvoiceItem;
|
||||
currentStatus: string;
|
||||
statusOptions: { value: string; label: string }[];
|
||||
onChangeStatus: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onChangeStatus: (event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
||||
};
|
||||
|
||||
export function InvoiceToolbar({ invoice, currentStatus, statusOptions, onChangeStatus }: Props) {
|
||||
|
@@ -19,7 +19,7 @@ export type IAddressItem = {
|
||||
addressType?: string;
|
||||
};
|
||||
|
||||
export type IDateValue = string | number | null;
|
||||
export type IDateValue = string | number | Date | null;
|
||||
|
||||
export type IDatePickerControl = Dayjs | null;
|
||||
|
||||
|
@@ -13,15 +13,16 @@ export type IInvoiceTableFilters = {
|
||||
export type IInvoiceItemItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
service: string;
|
||||
price: number;
|
||||
total: number;
|
||||
service: string;
|
||||
quantity: number;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type IInvoiceItem = {
|
||||
id: string;
|
||||
|
||||
sent: number;
|
||||
taxes: number;
|
||||
status: string;
|
||||
@@ -36,3 +37,26 @@ export type IInvoiceItem = {
|
||||
invoiceTo: IAddressItem;
|
||||
invoiceFrom: IAddressItem;
|
||||
};
|
||||
|
||||
export type SaveInvoiceData = {
|
||||
sent: number;
|
||||
taxes: number;
|
||||
status: string;
|
||||
subtotal: number;
|
||||
discount: number;
|
||||
shipping: number;
|
||||
totalAmount: number;
|
||||
dueDate: IDateValue;
|
||||
invoiceNumber: string;
|
||||
items: {
|
||||
title: string;
|
||||
service: string;
|
||||
price: number;
|
||||
total: number;
|
||||
quantity: number;
|
||||
description: string;
|
||||
}[];
|
||||
createDate: IDateValue;
|
||||
invoiceTo: IAddressItem | null;
|
||||
invoiceFrom: IAddressItem | null;
|
||||
};
|
||||
|
@@ -766,6 +766,11 @@
|
||||
dependencies:
|
||||
"@babel/types" "^7.27.0"
|
||||
|
||||
"@babel/runtime@^7.1.2":
|
||||
version "7.27.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.4.tgz#a91ec580e6c00c67118127777c316dfd5a5a6abf"
|
||||
integrity sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==
|
||||
|
||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.13", "@babel/runtime@^7.23.2", "@babel/runtime@^7.25.0", "@babel/runtime@^7.25.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.26.10", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
|
||||
version "7.27.0"
|
||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz"
|
||||
@@ -1753,7 +1758,7 @@
|
||||
resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz"
|
||||
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz"
|
||||
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
|
||||
@@ -3298,6 +3303,11 @@
|
||||
dependencies:
|
||||
"@types/unist" "*"
|
||||
|
||||
"@types/js-cookie@^2.2.6":
|
||||
version "2.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
|
||||
integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==
|
||||
|
||||
"@types/json-schema@^7.0.15":
|
||||
version "7.0.15"
|
||||
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
|
||||
@@ -3627,6 +3637,11 @@
|
||||
dependencies:
|
||||
"@swc/core" "^1.11.11"
|
||||
|
||||
"@xobotyi/scrollbar-width@^1.9.5":
|
||||
version "1.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d"
|
||||
integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==
|
||||
|
||||
"@yr/monotone-cubic-spline@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz"
|
||||
@@ -4090,6 +4105,13 @@ cookie@^1.0.1:
|
||||
resolved "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz"
|
||||
integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==
|
||||
|
||||
copy-to-clipboard@^3.3.1:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
|
||||
integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==
|
||||
dependencies:
|
||||
toggle-selection "^1.0.6"
|
||||
|
||||
cosmiconfig@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz"
|
||||
@@ -4130,6 +4152,21 @@ crypto-js@^4.2.0:
|
||||
resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz"
|
||||
integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
|
||||
|
||||
css-in-js-utils@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz#640ae6a33646d401fc720c54fc61c42cd76ae2bb"
|
||||
integrity sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==
|
||||
dependencies:
|
||||
hyphenate-style-name "^1.0.3"
|
||||
|
||||
css-tree@^1.1.2:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
|
||||
integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
|
||||
dependencies:
|
||||
mdn-data "2.0.14"
|
||||
source-map "^0.6.1"
|
||||
|
||||
csscolorparser@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz"
|
||||
@@ -4140,7 +4177,7 @@ cssjanus@^2.0.1:
|
||||
resolved "https://registry.npmjs.org/cssjanus/-/cssjanus-2.3.0.tgz"
|
||||
integrity sha512-ZZXXn51SnxRxAZ6fdY7mBDPmA4OZd83q/J9Gdqz3YmE9TUq+9tZl+tdOnCi7PpNygI6PEkehj9rgifv5+W8a5A==
|
||||
|
||||
csstype@^3.0.2, csstype@^3.1.3:
|
||||
csstype@^3.0.2, csstype@^3.1.2, csstype@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
@@ -4332,6 +4369,13 @@ error-ex@^1.3.1:
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
error-stack-parser@^2.0.6:
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286"
|
||||
integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==
|
||||
dependencies:
|
||||
stackframe "^1.3.4"
|
||||
|
||||
es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9:
|
||||
version "1.23.9"
|
||||
resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz"
|
||||
@@ -4749,6 +4793,11 @@ fast-levenshtein@^2.0.6:
|
||||
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
|
||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||
|
||||
fast-shallow-equal@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b"
|
||||
integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==
|
||||
|
||||
fast-xml-parser@4.4.1:
|
||||
version "4.4.1"
|
||||
resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz"
|
||||
@@ -4763,6 +4812,11 @@ fast-xml-parser@^4.4.1:
|
||||
dependencies:
|
||||
strnum "^1.1.1"
|
||||
|
||||
fastest-stable-stringify@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76"
|
||||
integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==
|
||||
|
||||
fastq@^1.6.0:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz"
|
||||
@@ -5256,6 +5310,11 @@ hyphen@^1.6.4:
|
||||
resolved "https://registry.npmjs.org/hyphen/-/hyphen-1.10.6.tgz"
|
||||
integrity sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==
|
||||
|
||||
hyphenate-style-name@^1.0.3:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
|
||||
integrity sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==
|
||||
|
||||
i18next-browser-languagedetector@^8.0.4:
|
||||
version "8.0.4"
|
||||
resolved "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.4.tgz"
|
||||
@@ -5325,6 +5384,13 @@ inline-style-parser@0.2.4:
|
||||
resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz"
|
||||
integrity sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==
|
||||
|
||||
inline-style-prefixer@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz#9310f3cfa2c6f3901d1480f373981c02691781e8"
|
||||
integrity sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==
|
||||
dependencies:
|
||||
css-in-js-utils "^3.1.0"
|
||||
|
||||
input-format@^0.3.10:
|
||||
version "0.3.14"
|
||||
resolved "https://registry.npmjs.org/input-format/-/input-format-0.3.14.tgz"
|
||||
@@ -5631,6 +5697,11 @@ jay-peg@^1.1.1:
|
||||
dependencies:
|
||||
restructure "^3.0.0"
|
||||
|
||||
js-cookie@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
|
||||
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
|
||||
|
||||
js-cookie@^3.0.5:
|
||||
version "3.0.5"
|
||||
resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz"
|
||||
@@ -6029,6 +6100,11 @@ mdast-util-to-string@^4.0.0:
|
||||
dependencies:
|
||||
"@types/mdast" "^4.0.0"
|
||||
|
||||
mdn-data@2.0.14:
|
||||
version "2.0.14"
|
||||
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
|
||||
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
|
||||
|
||||
mdurl@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz"
|
||||
@@ -6397,6 +6473,20 @@ murmurhash-js@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz"
|
||||
integrity sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==
|
||||
|
||||
nano-css@^5.6.2:
|
||||
version "5.6.2"
|
||||
resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.2.tgz#584884ddd7547278f6d6915b6805069742679a32"
|
||||
integrity sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==
|
||||
dependencies:
|
||||
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||
css-tree "^1.1.2"
|
||||
csstype "^3.1.2"
|
||||
fastest-stable-stringify "^2.0.2"
|
||||
inline-style-prefixer "^7.0.1"
|
||||
rtl-css-js "^1.16.1"
|
||||
stacktrace-js "^2.0.2"
|
||||
stylis "^4.3.0"
|
||||
|
||||
nanoid@^3.3.8:
|
||||
version "3.3.11"
|
||||
resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz"
|
||||
@@ -7014,6 +7104,31 @@ react-transition-group@^4.4.5:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react-universal-interface@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b"
|
||||
integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==
|
||||
|
||||
react-use@^17.6.0:
|
||||
version "17.6.0"
|
||||
resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.6.0.tgz#2101a3a79dc965a25866b21f5d6de4b128488a14"
|
||||
integrity sha512-OmedEScUMKFfzn1Ir8dBxiLLSOzhKe/dPZwVxcujweSj45aNM7BEGPb9BEVIgVEqEXx6f3/TsXzwIktNgUR02g==
|
||||
dependencies:
|
||||
"@types/js-cookie" "^2.2.6"
|
||||
"@xobotyi/scrollbar-width" "^1.9.5"
|
||||
copy-to-clipboard "^3.3.1"
|
||||
fast-deep-equal "^3.1.3"
|
||||
fast-shallow-equal "^1.0.0"
|
||||
js-cookie "^2.2.1"
|
||||
nano-css "^5.6.2"
|
||||
react-universal-interface "^0.6.2"
|
||||
resize-observer-polyfill "^1.5.1"
|
||||
screenfull "^5.1.0"
|
||||
set-harmonic-interval "^1.0.1"
|
||||
throttle-debounce "^3.0.1"
|
||||
ts-easing "^0.2.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
react@^19.0.0, react@^19.1.0:
|
||||
version "19.1.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz"
|
||||
@@ -7137,6 +7252,11 @@ reselect@^5.1.1:
|
||||
resolved "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz"
|
||||
integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
|
||||
|
||||
resize-observer-polyfill@^1.5.1:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
|
||||
@@ -7216,6 +7336,13 @@ rope-sequence@^1.3.0:
|
||||
resolved "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz"
|
||||
integrity sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==
|
||||
|
||||
rtl-css-js@^1.16.1:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.16.1.tgz#4b48b4354b0ff917a30488d95100fbf7219a3e80"
|
||||
integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.1.2"
|
||||
|
||||
run-parallel@^1.1.9:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
|
||||
@@ -7278,6 +7405,11 @@ scheduler@^0.26.0:
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz"
|
||||
integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
|
||||
|
||||
screenfull@^5.1.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba"
|
||||
integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==
|
||||
|
||||
semver@^6.3.1:
|
||||
version "6.3.1"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
|
||||
@@ -7325,6 +7457,11 @@ set-function-name@^2.0.2:
|
||||
functions-have-names "^1.2.3"
|
||||
has-property-descriptors "^1.0.2"
|
||||
|
||||
set-harmonic-interval@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249"
|
||||
integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==
|
||||
|
||||
set-proto@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz"
|
||||
@@ -7449,11 +7586,21 @@ source-map-js@^1.2.1:
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz"
|
||||
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
|
||||
|
||||
source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
integrity sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==
|
||||
|
||||
source-map@^0.5.7:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz"
|
||||
integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==
|
||||
|
||||
source-map@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
space-separated-tokens@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz"
|
||||
@@ -7471,6 +7618,35 @@ stable-hash@^0.0.5:
|
||||
resolved "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz"
|
||||
integrity sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==
|
||||
|
||||
stack-generator@^2.0.5:
|
||||
version "2.0.10"
|
||||
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.10.tgz#8ae171e985ed62287d4f1ed55a1633b3fb53bb4d"
|
||||
integrity sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==
|
||||
dependencies:
|
||||
stackframe "^1.3.4"
|
||||
|
||||
stackframe@^1.3.4:
|
||||
version "1.3.4"
|
||||
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"
|
||||
integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==
|
||||
|
||||
stacktrace-gps@^3.0.4:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.1.2.tgz#0c40b24a9b119b20da4525c398795338966a2fb0"
|
||||
integrity sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==
|
||||
dependencies:
|
||||
source-map "0.5.6"
|
||||
stackframe "^1.3.4"
|
||||
|
||||
stacktrace-js@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b"
|
||||
integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==
|
||||
dependencies:
|
||||
error-stack-parser "^2.0.6"
|
||||
stack-generator "^2.0.5"
|
||||
stacktrace-gps "^3.0.4"
|
||||
|
||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
|
||||
@@ -7609,7 +7785,7 @@ stylis@4.2.0:
|
||||
resolved "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz"
|
||||
integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==
|
||||
|
||||
stylis@^4.3.6:
|
||||
stylis@^4.3.0, stylis@^4.3.6:
|
||||
version "4.3.6"
|
||||
resolved "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz"
|
||||
integrity sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==
|
||||
@@ -7646,6 +7822,11 @@ swr@^2.3.3:
|
||||
dequal "^2.0.3"
|
||||
use-sync-external-store "^1.4.0"
|
||||
|
||||
throttle-debounce@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb"
|
||||
integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
|
||||
|
||||
tiny-inflate@^1.0.0, tiny-inflate@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz"
|
||||
@@ -7683,6 +7864,11 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
toggle-selection@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
|
||||
integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
|
||||
|
||||
tr46@~0.0.3:
|
||||
version "0.0.3"
|
||||
resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz"
|
||||
@@ -7703,6 +7889,11 @@ ts-api-utils@^2.0.1:
|
||||
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz"
|
||||
integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
|
||||
|
||||
ts-easing@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec"
|
||||
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
|
||||
|
||||
tsconfig-paths@^3.15.0:
|
||||
version "3.15.0"
|
||||
resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz"
|
||||
|
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 240
|
||||
}
|
||||
"printWidth": 100
|
||||
}
|
@@ -9,7 +9,12 @@ android {
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
implementation project(':capacitor-barcode-scanner')
|
||||
implementation project(':capacitor-clipboard')
|
||||
implementation project(':capacitor-geolocation')
|
||||
implementation project(':capacitor-google-maps')
|
||||
implementation project(':capacitor-preferences')
|
||||
implementation project(':capacitor-share')
|
||||
|
||||
}
|
||||
|
||||
|
@@ -2,5 +2,20 @@
|
||||
include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
|
||||
|
||||
include ':capacitor-barcode-scanner'
|
||||
project(':capacitor-barcode-scanner').projectDir = new File('../node_modules/@capacitor/barcode-scanner/android')
|
||||
|
||||
include ':capacitor-clipboard'
|
||||
project(':capacitor-clipboard').projectDir = new File('../node_modules/@capacitor/clipboard/android')
|
||||
|
||||
include ':capacitor-geolocation'
|
||||
project(':capacitor-geolocation').projectDir = new File('../node_modules/@capacitor/geolocation/android')
|
||||
|
||||
include ':capacitor-google-maps'
|
||||
project(':capacitor-google-maps').projectDir = new File('../node_modules/@capacitor/google-maps/android')
|
||||
|
||||
include ':capacitor-preferences'
|
||||
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
||||
|
||||
include ':capacitor-share'
|
||||
project(':capacitor-share').projectDir = new File('../node_modules/@capacitor/share/android')
|
||||
|
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
while true; do
|
||||
npm i -D
|
||||
yarn -D
|
||||
|
||||
npm run dev
|
||||
yarn run dev
|
||||
|
||||
echo "restarting..."
|
||||
sleep 1
|
||||
|
@@ -11,7 +11,12 @@ install! 'cocoapods', :disable_input_output_paths => true
|
||||
def capacitor_pods
|
||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||
pod 'CapacitorBarcodeScanner', :path => '../../node_modules/@capacitor/barcode-scanner'
|
||||
pod 'CapacitorClipboard', :path => '../../node_modules/@capacitor/clipboard'
|
||||
pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation'
|
||||
pod 'CapacitorGoogleMaps', :path => '../../node_modules/@capacitor/google-maps'
|
||||
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
|
||||
pod 'CapacitorShare', :path => '../../node_modules/@capacitor/share'
|
||||
end
|
||||
|
||||
target 'App' do
|
||||
|
6651
03_source/mobile/package-lock.json
generated
@@ -7,33 +7,50 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@capacitor/android": "7.0.1",
|
||||
"@capacitor/barcode-scanner": "^2.0.3",
|
||||
"@capacitor/clipboard": "^7.0.1",
|
||||
"@capacitor/core": "^7.0.0",
|
||||
"@capacitor/geolocation": "^7.1.2",
|
||||
"@capacitor/google-maps": "^7.0.2",
|
||||
"@capacitor/ios": "7.0.1",
|
||||
"@capacitor/preferences": "^7.0.0",
|
||||
"@capacitor/share": "^7.0.1",
|
||||
"@hookform/resolvers": "^4.1.3",
|
||||
"@ionic/react": "^8.5.0",
|
||||
"@ionic/react-router": "^8.5.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@react-hook/window-size": "^3.1.1",
|
||||
"@types/leaflet": "^1.9.17",
|
||||
"@types/react-redux": "^7.1.34",
|
||||
"axios": "^1.9.0",
|
||||
"date-fns": "^2.25.0",
|
||||
"ionicons": "^7.1.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"pigeon-maps": "^0.22.1",
|
||||
"pullstate": "^1",
|
||||
"react": "19.0.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-hook-form": "^7.55.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-qr-code": "^2.0.15",
|
||||
"react-qr-reader": "^3.0.0-beta-1",
|
||||
"react-redux": "^9.2.0",
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-spinners": "^0.17.0",
|
||||
"react-use": "^17.6.0",
|
||||
"reselect": "^4.0.0",
|
||||
"sass": "^1.85.1",
|
||||
"swiper": "^9.1.1"
|
||||
"swiper": "^11.2.8",
|
||||
"use-sound": "^5.0.0",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "npm run dev",
|
||||
"dev": "vite --host 0.0.0.0 --cors",
|
||||
"dev": "vite --force --host 0.0.0.0 --cors",
|
||||
"ionic:serve": "vite",
|
||||
"ionic:build": "tsc && vite build",
|
||||
"build": "tsc && vite build",
|
||||
@@ -60,7 +77,7 @@
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"lint-staged": "^13.2.0",
|
||||
"prettier": "^2.8.6",
|
||||
"prettier": "^3.5.3",
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.0"
|
||||
},
|
||||
|
BIN
03_source/mobile/public/assets/DemoBankingUi/alan.jpg
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
03_source/mobile/public/assets/DemoBankingUi/chip.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
03_source/mobile/public/assets/DemoBankingUi/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoBankingUi/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoBankingUi/mastercard.png
Normal file
After Width: | Height: | Size: 26 KiB |
1
03_source/mobile/public/assets/DemoBankingUi/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoBankingUi/visa.png
Normal file
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoDictionaryApp/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoQuizApp/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoQuizApp/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoQuizApp/main.png
Normal file
After Width: | Height: | Size: 39 KiB |
1
03_source/mobile/public/assets/DemoQuizApp/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoRecipeApp/bookmark.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoRecipeApp/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoRecipeApp/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoRecipeApp/placeholder.png
Normal file
After Width: | Height: | Size: 42 KiB |
1
03_source/mobile/public/assets/DemoRecipeApp/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoShopAppUi/cart.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
03_source/mobile/public/assets/DemoShopAppUi/icon/favicon.png
Normal file
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoShopAppUi/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
1
03_source/mobile/public/assets/DemoShopAppUi/shapes.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoShopAppUi/shop.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/autumn.png
Normal file
After Width: | Height: | Size: 235 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar.jpeg
Normal file
After Width: | Height: | Size: 52 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar1.png
Normal file
After Width: | Height: | Size: 358 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar2.png
Normal file
After Width: | Height: | Size: 424 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar3.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar4.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar5.png
Normal file
After Width: | Height: | Size: 264 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/avatar6.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover1.jpeg
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover2.jpeg
Normal file
After Width: | Height: | Size: 216 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover4.jpeg
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover5.jpeg
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/cover6.jpeg
Normal file
After Width: | Height: | Size: 124 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/flower.jpeg
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/h.jpeg
Normal file
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 930 B |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/icon/icon.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/ocean.jpeg
Normal file
After Width: | Height: | Size: 64 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="350" height="140" xmlns="http://www.w3.org/2000/svg" style="background:#f6f7f9"><g fill="none" fill-rule="evenodd"><path fill="#F04141" style="mix-blend-mode:multiply" d="M61.905-34.23l96.194 54.51-66.982 54.512L22 34.887z"/><circle fill="#10DC60" style="mix-blend-mode:multiply" cx="155.5" cy="135.5" r="57.5"/><path fill="#3880FF" style="mix-blend-mode:multiply" d="M208.538 9.513l84.417 15.392L223.93 93.93z"/><path fill="#FFCE00" style="mix-blend-mode:multiply" d="M268.625 106.557l46.332-26.75 46.332 26.75v53.5l-46.332 26.75-46.332-26.75z"/><circle fill="#7044FF" style="mix-blend-mode:multiply" cx="299.5" cy="9.5" r="38.5"/><rect fill="#11D3EA" style="mix-blend-mode:multiply" transform="rotate(-60 148.47 37.886)" x="143.372" y="-7.056" width="10.196" height="89.884" rx="5.098"/><path d="M-25.389 74.253l84.86 8.107c5.498.525 9.53 5.407 9.004 10.905a10 10 0 0 1-.057.477l-12.36 85.671a10.002 10.002 0 0 1-11.634 8.42l-86.351-15.226c-5.44-.959-9.07-6.145-8.112-11.584l13.851-78.551a10 10 0 0 1 10.799-8.219z" fill="#7044FF" style="mix-blend-mode:multiply"/><circle fill="#0CD1E8" style="mix-blend-mode:multiply" cx="273.5" cy="106.5" r="20.5"/></g></svg>
|
After Width: | Height: | Size: 1.1 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/spring.png
Normal file
After Width: | Height: | Size: 288 KiB |
BIN
03_source/mobile/public/assets/DemoSlidingProfile/summer.png
Normal file
After Width: | Height: | Size: 210 KiB |