Compare commits
27 Commits
b80939c78d
...
develop/fr
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7c1ac6b546 | ||
![]() |
53b498d881 | ||
![]() |
d865fca058 | ||
![]() |
871313449e | ||
![]() |
76840a8e1b | ||
![]() |
a68cb01585 | ||
![]() |
d6b36a0ca6 | ||
![]() |
360da364ff | ||
![]() |
1fdf10c0da | ||
![]() |
c0d8d0cd05 | ||
![]() |
b923410f99 | ||
![]() |
65f9b83c9f | ||
![]() |
d8166d8a3d | ||
![]() |
f59a382d8f | ||
![]() |
c4f8a6902c | ||
![]() |
7b230d4f8b | ||
![]() |
13c3399a6e | ||
![]() |
80a2636f90 | ||
![]() |
9c4637528c | ||
![]() |
661de6e8d7 | ||
![]() |
4a0ae590b0 | ||
![]() |
53162ed333 | ||
![]() |
79c292d943 | ||
![]() |
10a6375347 | ||
![]() |
f950617372 | ||
![]() |
99fafda624 | ||
![]() |
3ed3f2fecb |
@@ -6,10 +6,39 @@ tags: mobile, payment
|
||||
|
||||
frontend page to handle party-user pay join event
|
||||
|
||||
edit page T.B.A.
|
||||
## User flow
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
a["user trigger paid request"]
|
||||
b["redirect user to payment gateway"]
|
||||
c["payment success, redirect user to payment success page"]
|
||||
d["payment failed, show user"]
|
||||
e["redirect user back to event_detail page"]
|
||||
|
||||
|
||||
a --> b --payment ok --> c --> e
|
||||
b --payment failed --> d
|
||||
d --> e
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
- assume user already login
|
||||
|
||||
| steps | description |
|
||||
| ----- | --------------------------------------------------- |
|
||||
| 1 | user enter event detail page |
|
||||
| 2 | user press join button |
|
||||
| 3 | app redirect to payment gateway page |
|
||||
| 4 | user choose pay |
|
||||
| 5 | payment success, redirect back to event_detail page |
|
||||
| end | test done |
|
||||
|
||||
## TODO
|
||||
|
||||
T.B.A.
|
||||
|
||||
## sources
|
||||
|
||||
T.B.A.
|
||||
|
19
01_Requirements/REQ0190/index.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
tags: mobile, carousell, restaurant-cms
|
||||
---
|
||||
|
||||
# REQ0188 import page from old projects
|
||||
|
||||
import demo page from old projects
|
||||
|
||||
edit page T.B.A.
|
||||
|
||||
## TODO
|
||||
|
||||
## sources
|
||||
|
||||
T.B.A.
|
||||
|
||||
## branch
|
||||
|
||||
develop/requirements/REQ0190
|
@@ -1,4 +1,13 @@
|
||||
import type { User } from '@prisma/client';
|
||||
// src/app/api/auth/me/route.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - T.B.A.
|
||||
//
|
||||
// RULES:
|
||||
// - T.B.A.
|
||||
//
|
||||
|
||||
import type { PartyUser, User } from '@prisma/client';
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
import { headers } from 'next/headers';
|
||||
@@ -11,9 +20,11 @@ import { getUserById } from 'src/app/services/user.service';
|
||||
import { createAccessLog } from 'src/app/services/access-log.service';
|
||||
|
||||
import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest';
|
||||
import { getPartyUserById } from 'src/app/services/party-user.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// NOTE: keep this comment to let prisma running on nextjs
|
||||
// export const runtime = 'edge';
|
||||
|
||||
/**
|
||||
@@ -29,6 +40,7 @@ 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';
|
||||
const USER_BANNED = 'user banned';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
@@ -43,12 +55,17 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
const accessToken = `${authorization}`.split(' ')[1];
|
||||
const data = await verify(accessToken, JWT_SECRET);
|
||||
console.log(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);
|
||||
const { userId } = data;
|
||||
|
||||
let currentUser: User | PartyUser | null = null;
|
||||
|
||||
currentUser = await getPartyUserById(userId);
|
||||
|
||||
if (!currentUser) {
|
||||
currentUser = await getUserById(userId);
|
||||
}
|
||||
|
||||
if (!currentUser) {
|
||||
createAccessLog('', USER_TOKEN_CHECK_FAILED, debug);
|
||||
|
@@ -1,11 +1,26 @@
|
||||
###
|
||||
|
||||
# username and password ok
|
||||
GET http://localhost:7272/api/auth/me
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWJnbnUyengwMDBjaHEzaGZ3dmtjejlvIiwiaWF0IjoxNzQ4OTY0ODkyLCJleHAiOjE3NTAxNzQ0OTJ9.lo04laCxtm0IVeYaETEV3hXKyDmXPEn7SyWtY2VR4dI
|
||||
|
||||
GET http://localhost:7272/api/auth/me
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWMwdWo4aXgwMDBqM2Y1eWhxc29xMW9wIiwiaWF0IjoxNzUwMjE5NTYyLCJleHAiOjE3NTE0MjkxNjJ9.8gKM2oMquccM_HDEfBAgtapCGf3M1eIp6SZ_knx7d1g
|
||||
|
||||
###
|
||||
|
||||
# username and password ok
|
||||
|
||||
POST http://localhost:7272/api/auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals.cc",
|
||||
"password": "@2Minimal"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# There is no user corresponding to the email address.
|
||||
|
||||
POST http://localhost:7272/api/auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
@@ -15,7 +30,9 @@ content-type: application/json
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# Wrong password
|
||||
|
||||
POST http://localhost:7272/api/auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
|
21
03_source/cms_backend/src/app/api/event/helloworld/route.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { countTotalEvents } from 'src/app/services/eventItem.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Events, obsoleted
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const numOfEvent = await countTotalEvents();
|
||||
|
||||
logger('[Event] list', numOfEvent);
|
||||
|
||||
return response({ numOfEvent }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Event - Get list', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/event/helloworld
|
22
03_source/cms_backend/src/app/api/event/numOfEvent/route.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
// src/app/api/event/list/route.ts
|
||||
import { countTotalEvents, listEvents } from 'src/app/services/eventItem.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Events, obsoleted
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const numOfEvents = await countTotalEvents();
|
||||
|
||||
// logger('[Event] list', numOfEvents.length);
|
||||
|
||||
return response({ numOfEvents }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Event - Get list', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/event/numOfEvent
|
@@ -0,0 +1,23 @@
|
||||
//
|
||||
//
|
||||
import { logger } from 'src/utils/logger';
|
||||
import { STATUS, response, handleError } from 'src/utils/response';
|
||||
|
||||
import { countTotalEvents } from 'src/app/services/eventItem.service';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* GET - Events, obsoleted
|
||||
*************************************** */
|
||||
export async function GET() {
|
||||
try {
|
||||
const numOfEvent = await countTotalEvents();
|
||||
|
||||
// logger('[Event] list', numOfEvent);
|
||||
|
||||
return response({ numOfEvent }, STATUS.OK);
|
||||
} catch (error) {
|
||||
return handleError('Event - Get list', error);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/party-event/numOfEvent
|
@@ -7,6 +7,14 @@
|
||||
// - Log all access attempts (success/failure)
|
||||
// - Validate token structure and user existence
|
||||
//
|
||||
// RULES:
|
||||
// - Must validate Bearer token format before processing
|
||||
// - All errors must be logged via access-log service
|
||||
// - User existence must be verified after token validation
|
||||
// - Sensitive data must be filtered from responses
|
||||
// - Mock JWT_SECRET should be replaced in production
|
||||
// - Debug info should be included in error logs
|
||||
//
|
||||
import type { NextRequest } from 'next/server';
|
||||
import type { PartyUser } from '@prisma/client';
|
||||
|
||||
@@ -23,14 +31,6 @@ import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
* You should use a real database
|
||||
* You should hash the password before saving to database
|
||||
* You should not save the password in the database
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
const ERR_USER_TOKEN_CHECK_FAILED = 'user token check failed';
|
||||
const ERR_INVALID_AUTH_TOKEN = 'Invalid authorization token';
|
||||
const ERR_USER_ID_NOT_FOUND = 'userId not found';
|
||||
|
@@ -21,8 +21,8 @@ POST http://localhost:7272/api/party-user-auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals.cc",
|
||||
"password": "@2Minimal"
|
||||
"email": "party_user0@prisma.io",
|
||||
"password": "Aa12345678"
|
||||
}
|
||||
|
||||
###
|
||||
|
@@ -0,0 +1,6 @@
|
||||
export const ERR_USER_NOT_FOUND = 'There is no user corresponding to the email address.';
|
||||
export const ERR_WRONG_PASSWORD = 'Wrong password';
|
||||
export const LOG_USER_TRIED_LOGIN_WITH_EMAIL = `user tried login with email`;
|
||||
export const LOG_USER_LOGGED_WITH_WRONG_PASSWORD = 'user logged with wrong password';
|
||||
export const LOG_ACCESS_GRANTED = 'access granted';
|
||||
export const LOG_ATTEMPTED_LOGIN_BUT_FAILED = 'attempted login but failed';
|
@@ -10,9 +10,16 @@ import { createAccessLog } from 'src/app/services/access-log.service';
|
||||
|
||||
import prisma from '../../../lib/prisma';
|
||||
import { flattenNextjsRequest } from './flattenNextjsRequest';
|
||||
import {
|
||||
LOG_USER_TRIED_LOGIN_WITH_EMAIL,
|
||||
ERR_USER_NOT_FOUND,
|
||||
LOG_USER_LOGGED_WITH_WRONG_PASSWORD,
|
||||
ERR_WRONG_PASSWORD,
|
||||
LOG_ACCESS_GRANTED,
|
||||
LOG_ATTEMPTED_LOGIN_BUT_FAILED,
|
||||
} from './constants';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This API is used for demo purpose only
|
||||
* You should use a real database
|
||||
@@ -21,9 +28,6 @@ import { flattenNextjsRequest } from './flattenNextjsRequest';
|
||||
* You should not expose the JWT_SECRET in the client side
|
||||
*/
|
||||
|
||||
const ERR_USER_NOT_FOUND = 'There is no user corresponding to the email address.';
|
||||
const ERR_WRONG_PASSWORD = 'Wrong password';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const debug = { 'req.headers': flattenNextjsRequest(req) };
|
||||
|
||||
@@ -32,12 +36,12 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const currentUser = await prisma.partyUser.findFirst({ where: { email } });
|
||||
if (!currentUser) {
|
||||
await createAccessLog('', `user tried login with email ${email}`, { debug });
|
||||
await createAccessLog('', LOG_USER_TRIED_LOGIN_WITH_EMAIL, { email, debug });
|
||||
return response({ message: ERR_USER_NOT_FOUND }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (currentUser?.password !== password) {
|
||||
await createAccessLog(currentUser.id, 'user logged with wrong password', { debug });
|
||||
await createAccessLog(currentUser.id, LOG_USER_LOGGED_WITH_WRONG_PASSWORD, { debug });
|
||||
return response({ message: ERR_WRONG_PASSWORD }, STATUS.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@@ -45,11 +49,11 @@ export async function POST(req: NextRequest) {
|
||||
expiresIn: JWT_EXPIRES_IN,
|
||||
});
|
||||
|
||||
await createAccessLog(currentUser.id, 'access granted', { debug });
|
||||
await createAccessLog(currentUser.id, LOG_ACCESS_GRANTED, { debug });
|
||||
|
||||
return response({ user: currentUser, accessToken }, STATUS.OK);
|
||||
} catch (error) {
|
||||
await createAccessLog('', 'attempted login but failed', { debug, error });
|
||||
await createAccessLog('', LOG_ATTEMPTED_LOGIN_BUT_FAILED, { debug, error });
|
||||
|
||||
return handleError('Auth - Sign in', error);
|
||||
}
|
||||
|
@@ -2,14 +2,19 @@
|
||||
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/auth/me
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWMwdWo4bGwwMDByM2Y1eXhob3JuMW1hIiwiaWF0IjoxNzUwMjE5NTgwLCJleHAiOjE3NTE0MjkxODB9.7BtuIKEvwDcHc5j9JYX0Eb1uB37kFH1Ksx4MTDTtEWQ
|
||||
|
||||
###
|
||||
|
||||
# username and password ok
|
||||
|
||||
POST http://localhost:7272/api/party-user-auth/sign-in
|
||||
content-type: application/json
|
||||
|
||||
{
|
||||
"email": "demo@minimals.cc",
|
||||
"password": "@2Minimal"
|
||||
"email": "party_user0@prisma.io",
|
||||
"password": "Aa12345678"
|
||||
}
|
||||
|
||||
###
|
||||
|
@@ -1,28 +1,23 @@
|
||||
// src/app/services/AccessLog.service.ts
|
||||
// src/app/services/access-log.service.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// Service for handling AccessLog records
|
||||
// - Core service for audit logging and access tracking
|
||||
// - Records all authentication attempts and system access
|
||||
// - Provides query capabilities for audit trails
|
||||
// - Integrates with Prisma ORM for database operations
|
||||
//
|
||||
// RULES:
|
||||
// - All methods return Promises
|
||||
// - Input validation should be done at controller level
|
||||
// - Errors should be propagated to caller
|
||||
// - All methods return Promises for async operations
|
||||
// - Input validation must be done at controller level
|
||||
// - Errors should be propagated to caller with context
|
||||
// - Audit records should never be modified after creation
|
||||
// - Sensitive data should be hashed before logging
|
||||
// - Metadata should be stored as JSON for flexibility
|
||||
|
||||
import type { AccessLog } from '@prisma/client';
|
||||
|
||||
import prisma from '../lib/prisma';
|
||||
|
||||
// type CreateAccessLog = {
|
||||
// userId?: string;
|
||||
// message?: string;
|
||||
// metadata?: Record<string, any>;
|
||||
// };
|
||||
|
||||
// type UpdateAccessLog = {
|
||||
// status?: number;
|
||||
// metadata?: object;
|
||||
// };
|
||||
|
||||
async function listAccessLogs(): Promise<AccessLog[]> {
|
||||
return prisma.accessLog.findMany({
|
||||
orderBy: { timestamp: 'desc' },
|
||||
|
@@ -51,6 +51,17 @@ async function getEventItemById(eventId: string): Promise<EventItem | null> {
|
||||
return prisma.eventItem.findFirst({ where: { id: eventId } });
|
||||
}
|
||||
|
||||
async function countTotalEvents(): Promise<number> {
|
||||
try {
|
||||
const result = await prisma.eventItem.findMany();
|
||||
console.log({ result });
|
||||
return result.length;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// async function createNewEvent(createForm: CreateEvent) {
|
||||
// return prisma.event.create({ data: createForm });
|
||||
// }
|
||||
@@ -73,4 +84,5 @@ export {
|
||||
// deleteEvent,
|
||||
// createNewEvent,
|
||||
getEventItemById,
|
||||
countTotalEvents,
|
||||
};
|
||||
|
@@ -23,7 +23,7 @@
|
||||
"re:build-npm": "npm run clean && npm install && npm run build",
|
||||
"tsc:dev": "yarn dev & yarn tsc:watch",
|
||||
"tsc:print": "npx tsc --showConfig",
|
||||
"tsc:w": "npx nodemon --delay 1 --ext ts,tsx --exec \"yarn tsc\"",
|
||||
"tsc:w": "npx nodemon --delay 1 --ext ts,tsx --exec \"yarn tsc || (sleep 10; touch src/app.tsx)\"",
|
||||
"tsc:watch": "tsc --noEmit --watch",
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
@@ -147,4 +147,4 @@
|
||||
"vite": "^6.2.3",
|
||||
"vite-plugin-checker": "^0.9.1"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { endpoints, fetcher } from 'src/lib/axios';
|
||||
import { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IPostItem } from 'src/types/blog';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR from 'swr';
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import axios, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axios, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { ICalendarEvent } from 'src/types/calendar';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { keyBy } from 'es-toolkit';
|
||||
import { useMemo } from 'react';
|
||||
import axios, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axios, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IChatConversation, IChatMessage, IChatParticipant } from 'src/types/chat';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// src/actions/invoice.ts
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axiosInstance, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IInvoiceItem, SaveInvoiceData } from 'src/types/invoice';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR from 'swr';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import type { UniqueIdentifier } from '@dnd-kit/core';
|
||||
import { startTransition, useMemo } from 'react';
|
||||
import axios, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axios, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IKanban, IKanbanColumn, IKanbanTask } from 'src/types/kanban';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { keyBy } from 'es-toolkit';
|
||||
import { useMemo } from 'react';
|
||||
import { endpoints, fetcher } from 'src/lib/axios';
|
||||
import { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IMail, IMailLabel } from 'src/types/mail';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR from 'swr';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
// src/actions/order.ts
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axiosInstance, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IOrderItem } from 'src/types/order';
|
||||
import type { IProductItem } from 'src/types/product';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
|
@@ -1,7 +1,8 @@
|
||||
// src/actions/party-event.ts
|
||||
//
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axiosInstance, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IPartyEventItem } from 'src/types/party-event';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
@@ -1,7 +1,8 @@
|
||||
// src/actions/party-order.ts
|
||||
//
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axiosInstance, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IPartyOrderItem } from 'src/types/party-order';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
@@ -1,7 +1,8 @@
|
||||
// src/actions/party-user1.ts
|
||||
//
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axiosInstance, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IPartyUserItem } from 'src/types/party-user';
|
||||
import type { IProductItem } from 'src/types/product';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
|
@@ -1,7 +1,8 @@
|
||||
// src/actions/product.ts
|
||||
//
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axiosInstance, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IProductItem } from 'src/types/product';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import axiosInstance, { fetcher } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { IProductItem } from 'src/types/product';
|
||||
import { IUserItem } from 'src/types/user';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import axios, { endpoints } from 'src/lib/axios';
|
||||
import axios from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import { JWT_STORAGE_KEY } from './constant';
|
||||
import { setSession } from './utils';
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { useSetState } from 'minimal-shared/hooks';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import axios, { endpoints } from 'src/lib/axios';
|
||||
import axios from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import type { AuthState } from '../../types';
|
||||
import { AuthContext } from '../auth-context';
|
||||
import { JWT_STORAGE_KEY } from './constant';
|
||||
|
@@ -0,0 +1,2 @@
|
||||
export const ERR_ACCESS_TOKEN_NOT_FOUND = `Access token not found in response`;
|
||||
export const ACCESS_TOKEN_NOT_FOUND_IN_RESPONSE = 'Access token not found in response';
|
85
03_source/frontend/src/auth/context/party-user-jwt/action.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import axios from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import { JWT_STORAGE_KEY } from './constant';
|
||||
import { ACCESS_TOKEN_NOT_FOUND_IN_RESPONSE, ERR_ACCESS_TOKEN_NOT_FOUND } from './ERRORS';
|
||||
import { setSession } from './utils';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export type SignInParams = {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type SignUpParams = {
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
|
||||
/** **************************************
|
||||
* Sign in
|
||||
*************************************** */
|
||||
export const signInWithPassword = async ({ email, password }: SignInParams): Promise<void> => {
|
||||
try {
|
||||
const params = { email, password };
|
||||
|
||||
const res = await axios.post(endpoints.partyUserAuth.signIn, params);
|
||||
|
||||
const { accessToken } = res.data;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error(ERR_ACCESS_TOKEN_NOT_FOUND);
|
||||
}
|
||||
|
||||
setSession(accessToken);
|
||||
} catch (error) {
|
||||
console.error('Error during sign in:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/** **************************************
|
||||
* Sign up
|
||||
*************************************** */
|
||||
export const signUp = async ({
|
||||
email,
|
||||
password,
|
||||
firstName,
|
||||
lastName,
|
||||
}: SignUpParams): Promise<void> => {
|
||||
const params = {
|
||||
email,
|
||||
password,
|
||||
firstName,
|
||||
lastName,
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await axios.post(endpoints.auth.signUp, params);
|
||||
|
||||
const { accessToken } = res.data;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error(ACCESS_TOKEN_NOT_FOUND_IN_RESPONSE);
|
||||
}
|
||||
|
||||
sessionStorage.setItem(JWT_STORAGE_KEY, accessToken);
|
||||
} catch (error) {
|
||||
console.error('Error during sign up:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/** **************************************
|
||||
* Sign out
|
||||
*************************************** */
|
||||
export const signOut = async (): Promise<void> => {
|
||||
try {
|
||||
await setSession(null);
|
||||
} catch (error) {
|
||||
console.error('Error during sign out:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
@@ -0,0 +1 @@
|
||||
export const JWT_STORAGE_KEY = 'jwt_access_token';
|
@@ -0,0 +1,7 @@
|
||||
export * from './utils';
|
||||
|
||||
export * from './action';
|
||||
|
||||
export * from './constant';
|
||||
|
||||
// export * from './auth-provider';
|
94
03_source/frontend/src/auth/context/party-user-jwt/utils.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import axios from 'src/lib/axios';
|
||||
import { paths } from 'src/routes/paths';
|
||||
import { JWT_STORAGE_KEY } from './constant';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function jwtDecode(token: string) {
|
||||
try {
|
||||
if (!token) return null;
|
||||
|
||||
const parts = token.split('.');
|
||||
if (parts.length < 2) {
|
||||
throw new Error('Invalid token!');
|
||||
}
|
||||
|
||||
const base64Url = parts[1];
|
||||
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||
const decoded = JSON.parse(atob(base64));
|
||||
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
console.error('Error decoding token:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function isValidToken(accessToken: string) {
|
||||
if (!accessToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = jwtDecode(accessToken);
|
||||
|
||||
if (!decoded || !('exp' in decoded)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentTime = Date.now() / 1000;
|
||||
|
||||
return decoded.exp > currentTime;
|
||||
} catch (error) {
|
||||
console.error('Error during token validation:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function tokenExpired(exp: number) {
|
||||
const currentTime = Date.now();
|
||||
const timeLeft = exp * 1000 - currentTime;
|
||||
|
||||
setTimeout(() => {
|
||||
try {
|
||||
alert('Token expired!');
|
||||
sessionStorage.removeItem(JWT_STORAGE_KEY);
|
||||
window.location.href = paths.auth.jwt.signIn;
|
||||
} catch (error) {
|
||||
console.error('Error during token expiration:', error);
|
||||
throw error;
|
||||
}
|
||||
}, timeLeft);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const INVALID_ACCESS_TOKEN = 'Invalid access token!';
|
||||
|
||||
export async function setSession(accessToken: string | null) {
|
||||
try {
|
||||
if (accessToken) {
|
||||
sessionStorage.setItem(JWT_STORAGE_KEY, accessToken);
|
||||
|
||||
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
|
||||
|
||||
const decodedToken = jwtDecode(accessToken); // ~3 days by minimals server
|
||||
|
||||
if (decodedToken && 'exp' in decodedToken) {
|
||||
tokenExpired(decodedToken.exp);
|
||||
} else {
|
||||
throw new Error(INVALID_ACCESS_TOKEN);
|
||||
}
|
||||
} else {
|
||||
sessionStorage.removeItem(JWT_STORAGE_KEY);
|
||||
delete axios.defaults.headers.common.Authorization;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during set session:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
3
03_source/frontend/src/auth/view/party-user-jwt/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './jwt-sign-in-view';
|
||||
|
||||
export * from './jwt-sign-up-view';
|
@@ -0,0 +1,167 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
import Link from '@mui/material/Link';
|
||||
import { useBoolean } from 'minimal-shared/hooks';
|
||||
import React, { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Field, Form } from 'src/components/hook-form';
|
||||
import { Iconify } from 'src/components/iconify';
|
||||
import { RouterLink } from 'src/routes/components';
|
||||
import { useRouter } from 'src/routes/hooks';
|
||||
import { paths } from 'src/routes/paths';
|
||||
import { z as zod } from 'zod';
|
||||
import { FormHead } from '../../components/form-head';
|
||||
import { signInWithPassword } from '../../context/party-user-jwt';
|
||||
import { useAuthContext } from '../../hooks';
|
||||
import { getErrorMessage } from '../../utils';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function JwtSignInView(): React.JSX.Element {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const showPassword = useBoolean();
|
||||
|
||||
const { checkUserSession } = useAuthContext();
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
type SignInSchemaType = zod.infer<typeof SignInSchema>;
|
||||
|
||||
const EMAIL_IS_REQUIRED = 'Email is required!';
|
||||
const EMAIL_MUST_BE_A_VALID_EMAIL_ADDRESS = 'Email must be a valid email address!';
|
||||
const PASSWORD_IS_REQUIRED = 'Password is required!';
|
||||
const PASSWORD_MUST_BE_AT_LEAST_6_CHARACTERS = 'Password must be at least 6 characters!';
|
||||
|
||||
const SignInSchema = zod.object({
|
||||
email: zod
|
||||
.string()
|
||||
.min(1, { message: EMAIL_IS_REQUIRED })
|
||||
.email({ message: EMAIL_MUST_BE_A_VALID_EMAIL_ADDRESS }),
|
||||
password: zod
|
||||
.string()
|
||||
.min(1, { message: PASSWORD_IS_REQUIRED })
|
||||
.min(6, { message: PASSWORD_MUST_BE_AT_LEAST_6_CHARACTERS }),
|
||||
});
|
||||
|
||||
const defaultValues: SignInSchemaType = {
|
||||
email: 'party_user0@prisma.io',
|
||||
password: 'Aa12345678',
|
||||
};
|
||||
|
||||
const methods = useForm<SignInSchemaType>({
|
||||
resolver: zodResolver(SignInSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = methods;
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
try {
|
||||
await signInWithPassword({ email: data.email, password: data.password });
|
||||
await checkUserSession?.();
|
||||
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const feedbackMessage = getErrorMessage(error);
|
||||
setErrorMessage(feedbackMessage);
|
||||
}
|
||||
});
|
||||
|
||||
const renderForm = () => (
|
||||
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
|
||||
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
|
||||
|
||||
<Box sx={{ gap: 1.5, display: 'flex', flexDirection: 'column' }}>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
href="#"
|
||||
variant="body2"
|
||||
color="inherit"
|
||||
sx={{ alignSelf: 'flex-end' }}
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
|
||||
<Field.Text
|
||||
name="password"
|
||||
label="Password"
|
||||
placeholder="6+ characters"
|
||||
type={showPassword.value ? 'text' : 'password'}
|
||||
slotProps={{
|
||||
inputLabel: { shrink: true },
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={showPassword.onToggle} edge="end">
|
||||
<Iconify
|
||||
icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'}
|
||||
/>
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
color="inherit"
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
loading={isSubmitting}
|
||||
loadingIndicator="Sign in..."
|
||||
>
|
||||
{t('sign-in')}
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormHead
|
||||
title="Sign in to your account"
|
||||
description={
|
||||
<>
|
||||
{`Don’t have an account? `}
|
||||
<Link component={RouterLink} href={paths.partyUserAuth.jwt.signUp} variant="subtitle2">
|
||||
{t('get-started')}
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
sx={{ textAlign: { xs: 'center', md: 'left' } }}
|
||||
/>
|
||||
|
||||
<Alert severity="info" sx={{ mb: 3 }}>
|
||||
Use <strong>{defaultValues.email}</strong>
|
||||
{' with password '}
|
||||
<strong>{defaultValues.password}</strong>
|
||||
</Alert>
|
||||
|
||||
{!!errorMessage && (
|
||||
<Alert severity="error" sx={{ mb: 3 }}>
|
||||
{errorMessage}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Form methods={methods} onSubmit={onSubmit}>
|
||||
{renderForm()}
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,166 @@
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import Alert from '@mui/material/Alert';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import InputAdornment from '@mui/material/InputAdornment';
|
||||
import Link from '@mui/material/Link';
|
||||
import { useBoolean } from 'minimal-shared/hooks';
|
||||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Field, Form } from 'src/components/hook-form';
|
||||
import { Iconify } from 'src/components/iconify';
|
||||
import { RouterLink } from 'src/routes/components';
|
||||
import { useRouter } from 'src/routes/hooks';
|
||||
import { paths } from 'src/routes/paths';
|
||||
import { z as zod } from 'zod';
|
||||
import { FormHead } from '../../components/form-head';
|
||||
import { SignUpTerms } from '../../components/sign-up-terms';
|
||||
import { signUp } from '../../context/jwt';
|
||||
import { useAuthContext } from '../../hooks';
|
||||
import { getErrorMessage } from '../../utils';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export type SignUpSchemaType = zod.infer<typeof SignUpSchema>;
|
||||
|
||||
export const SignUpSchema = zod.object({
|
||||
firstName: zod.string().min(1, { message: 'First name is required!' }),
|
||||
lastName: zod.string().min(1, { message: 'Last name is required!' }),
|
||||
email: zod
|
||||
.string()
|
||||
.min(1, { message: 'Email is required!' })
|
||||
.email({ message: 'Email must be a valid email address!' }),
|
||||
password: zod
|
||||
.string()
|
||||
.min(1, { message: 'Password is required!' })
|
||||
.min(6, { message: 'Password must be at least 6 characters!' }),
|
||||
});
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export function JwtSignUpView() {
|
||||
const router = useRouter();
|
||||
|
||||
const showPassword = useBoolean();
|
||||
|
||||
const { checkUserSession } = useAuthContext();
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
|
||||
const defaultValues: SignUpSchemaType = {
|
||||
firstName: 'Hello',
|
||||
lastName: 'Friend',
|
||||
email: 'hello@gmail.com',
|
||||
password: '@2Minimal',
|
||||
};
|
||||
|
||||
const methods = useForm<SignUpSchemaType>({
|
||||
resolver: zodResolver(SignUpSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = methods;
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
try {
|
||||
await signUp({
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
firstName: data.firstName,
|
||||
lastName: data.lastName,
|
||||
});
|
||||
await checkUserSession?.();
|
||||
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const feedbackMessage = getErrorMessage(error);
|
||||
setErrorMessage(feedbackMessage);
|
||||
}
|
||||
});
|
||||
|
||||
const renderForm = () => (
|
||||
<Box sx={{ gap: 3, display: 'flex', flexDirection: 'column' }}>
|
||||
<Box
|
||||
sx={{ display: 'flex', gap: { xs: 3, sm: 2 }, flexDirection: { xs: 'column', sm: 'row' } }}
|
||||
>
|
||||
<Field.Text
|
||||
name="firstName"
|
||||
label="First name"
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
/>
|
||||
<Field.Text
|
||||
name="lastName"
|
||||
label="Last name"
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Field.Text name="email" label="Email address" slotProps={{ inputLabel: { shrink: true } }} />
|
||||
|
||||
<Field.Text
|
||||
name="password"
|
||||
label="Password"
|
||||
placeholder="6+ characters"
|
||||
type={showPassword.value ? 'text' : 'password'}
|
||||
slotProps={{
|
||||
inputLabel: { shrink: true },
|
||||
input: {
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={showPassword.onToggle} edge="end">
|
||||
<Iconify icon={showPassword.value ? 'solar:eye-bold' : 'solar:eye-closed-bold'} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
color="inherit"
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
loading={isSubmitting}
|
||||
loadingIndicator="Create account..."
|
||||
>
|
||||
Create account
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormHead
|
||||
title="Hi New party user, Get started absolutely free "
|
||||
description={
|
||||
<>
|
||||
{`Already have an account? `}
|
||||
<Link component={RouterLink} href={paths.auth.jwt.signIn} variant="subtitle2">
|
||||
Get started
|
||||
</Link>
|
||||
</>
|
||||
}
|
||||
sx={{ textAlign: { xs: 'center', md: 'left' } }}
|
||||
/>
|
||||
|
||||
{!!errorMessage && (
|
||||
<Alert severity="error" sx={{ mb: 3 }}>
|
||||
{errorMessage}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Form methods={methods} onSubmit={onSubmit}>
|
||||
{renderForm()}
|
||||
</Form>
|
||||
|
||||
<SignUpTerms />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -1,3 +1,18 @@
|
||||
// src/lib/axios.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Centralized Axios instance configuration
|
||||
// - Global response error handling
|
||||
// - Standardized API endpoint definitions
|
||||
// - Reusable fetcher utility
|
||||
//
|
||||
// RULES:
|
||||
// - All API calls must use this axiosInstance
|
||||
// - Custom error handling in interceptor
|
||||
// - Endpoints should be defined here for consistency
|
||||
// - Fetcher should be used for simple GET requests
|
||||
//
|
||||
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import axios from 'axios';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
@@ -27,94 +42,3 @@ export const fetcher = async (args: string | [string, AxiosRequestConfig]) => {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const endpoints = {
|
||||
chat: '/api/chat',
|
||||
kanban: '/api/kanban',
|
||||
calendar: '/api/calendar',
|
||||
auth: {
|
||||
me: '/api/auth/me',
|
||||
signIn: '/api/auth/sign-in',
|
||||
signUp: '/api/auth/sign-up',
|
||||
},
|
||||
mail: {
|
||||
list: '/api/mail/list',
|
||||
details: '/api/mail/details',
|
||||
labels: '/api/mail/labels',
|
||||
},
|
||||
post: {
|
||||
list: '/api/post/list',
|
||||
details: '/api/post/details',
|
||||
latest: '/api/post/latest',
|
||||
search: '/api/post/search',
|
||||
},
|
||||
product: {
|
||||
list: '/api/product/list',
|
||||
details: '/api/product/details',
|
||||
search: '/api/product/search',
|
||||
save: '/api/product/saveProduct',
|
||||
create: '/api/product/create',
|
||||
update: '/api/product/update',
|
||||
delete: '/api/product/delete',
|
||||
},
|
||||
user: {
|
||||
list: '/api/user/list',
|
||||
profile: '/api/user/profile',
|
||||
update: '/api/user/update',
|
||||
settings: '/api/user/settings',
|
||||
details: '/api/user/details',
|
||||
},
|
||||
order: {
|
||||
list: '/api/order/list',
|
||||
profile: '/api/order/profile',
|
||||
update: '/api/order/update',
|
||||
settings: '/api/order/settings',
|
||||
details: '/api/order/details',
|
||||
changeStatus: (orderId: string) => `/api/order/changeStatus?orderId=${orderId}`,
|
||||
},
|
||||
invoice: {
|
||||
list: '/api/invoice/list',
|
||||
profile: '/api/invoice/profile',
|
||||
update: '/api/invoice/update',
|
||||
saveInvoice: (invoiceId: string) => `/api/invoice/saveInvoice?invoiceId=${invoiceId}`,
|
||||
settings: '/api/invoice/settings',
|
||||
details: '/api/invoice/details',
|
||||
changeStatus: (invoiceId: string) => `/api/invoice/changeStatus?invoiceId=${invoiceId}`,
|
||||
search: '/api/invoice/search',
|
||||
},
|
||||
//
|
||||
//
|
||||
//
|
||||
partyEvent: {
|
||||
list: '/api/party-event/list',
|
||||
details: '/api/party-event/details',
|
||||
search: '/api/party-event/search',
|
||||
create: '/api/party-event/create',
|
||||
update: '/api/party-event/update',
|
||||
delete: '/api/party-event/delete',
|
||||
},
|
||||
partyOrder: {
|
||||
create: '/api/party-order/create',
|
||||
delete: '/api/party-order/delete',
|
||||
list: '/api/party-order/list',
|
||||
profile: '/api/party-order/profile',
|
||||
update: '/api/party-order/update',
|
||||
settings: '/api/party-order/settings',
|
||||
details: '/api/party-order/details',
|
||||
changeStatus: (partyOrderId: string) =>
|
||||
`/api/party-order/changeStatus?partyOrderId=${partyOrderId}`,
|
||||
},
|
||||
partyUser: {
|
||||
list: '/api/party-user/list',
|
||||
details: '/api/party-user/details',
|
||||
search: '/api/party-user/search',
|
||||
create: '/api/party-user/create',
|
||||
update: '/api/party-user/update',
|
||||
delete: '/api/party-user/delete',
|
||||
//
|
||||
detailsByPartyUserId: (partyUserId: string) =>
|
||||
`/api/party-user/details?partyUserId=${partyUserId}`,
|
||||
},
|
||||
};
|
||||
|
96
03_source/frontend/src/lib/endpoints.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const endpoints = {
|
||||
chat: '/api/chat',
|
||||
kanban: '/api/kanban',
|
||||
calendar: '/api/calendar',
|
||||
auth: {
|
||||
me: '/api/auth/me',
|
||||
signIn: '/api/auth/sign-in',
|
||||
signUp: '/api/auth/sign-up',
|
||||
},
|
||||
mail: {
|
||||
list: '/api/mail/list',
|
||||
details: '/api/mail/details',
|
||||
labels: '/api/mail/labels',
|
||||
},
|
||||
post: {
|
||||
list: '/api/post/list',
|
||||
details: '/api/post/details',
|
||||
latest: '/api/post/latest',
|
||||
search: '/api/post/search',
|
||||
},
|
||||
product: {
|
||||
list: '/api/product/list',
|
||||
details: '/api/product/details',
|
||||
search: '/api/product/search',
|
||||
save: '/api/product/saveProduct',
|
||||
create: '/api/product/create',
|
||||
update: '/api/product/update',
|
||||
delete: '/api/product/delete',
|
||||
},
|
||||
user: {
|
||||
list: '/api/user/list',
|
||||
profile: '/api/user/profile',
|
||||
update: '/api/user/update',
|
||||
settings: '/api/user/settings',
|
||||
details: '/api/user/details',
|
||||
},
|
||||
order: {
|
||||
list: '/api/order/list',
|
||||
profile: '/api/order/profile',
|
||||
update: '/api/order/update',
|
||||
settings: '/api/order/settings',
|
||||
details: '/api/order/details',
|
||||
changeStatus: (orderId: string) => `/api/order/changeStatus?orderId=${orderId}`,
|
||||
},
|
||||
invoice: {
|
||||
list: '/api/invoice/list',
|
||||
profile: '/api/invoice/profile',
|
||||
update: '/api/invoice/update',
|
||||
saveInvoice: (invoiceId: string) => `/api/invoice/saveInvoice?invoiceId=${invoiceId}`,
|
||||
settings: '/api/invoice/settings',
|
||||
details: '/api/invoice/details',
|
||||
changeStatus: (invoiceId: string) => `/api/invoice/changeStatus?invoiceId=${invoiceId}`,
|
||||
search: '/api/invoice/search',
|
||||
},
|
||||
//
|
||||
//
|
||||
//
|
||||
partyEvent: {
|
||||
list: '/api/party-event/list',
|
||||
details: '/api/party-event/details',
|
||||
search: '/api/party-event/search',
|
||||
create: '/api/party-event/create',
|
||||
update: '/api/party-event/update',
|
||||
delete: '/api/party-event/delete',
|
||||
numOfEvent: '/api/party-event/numOfEvent',
|
||||
},
|
||||
partyOrder: {
|
||||
create: '/api/party-order/create',
|
||||
delete: '/api/party-order/delete',
|
||||
list: '/api/party-order/list',
|
||||
profile: '/api/party-order/profile',
|
||||
update: '/api/party-order/update',
|
||||
settings: '/api/party-order/settings',
|
||||
details: '/api/party-order/details',
|
||||
changeStatus: (partyOrderId: string) =>
|
||||
`/api/party-order/changeStatus?partyOrderId=${partyOrderId}`,
|
||||
},
|
||||
partyUser: {
|
||||
list: '/api/party-user/list',
|
||||
details: '/api/party-user/details',
|
||||
search: '/api/party-user/search',
|
||||
create: '/api/party-user/create',
|
||||
update: '/api/party-user/update',
|
||||
delete: '/api/party-user/delete',
|
||||
//
|
||||
detailsByPartyUserId: (partyUserId: string) =>
|
||||
`/api/party-user/details?partyUserId=${partyUserId}`,
|
||||
},
|
||||
partyUserAuth: {
|
||||
me: '/api/party-user-auth/me',
|
||||
signIn: '/api/party-user-auth/sign-in',
|
||||
signUp: '/api/party-user-auth/sign-up',
|
||||
},
|
||||
};
|
@@ -0,0 +1,16 @@
|
||||
import { AmplifyResetPasswordView } from 'src/auth/view/amplify';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Reset password | Amplify - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<AmplifyResetPasswordView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { AmplifySignInView } from 'src/auth/view/amplify';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign in | Amplify - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<AmplifySignInView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { AmplifySignUpView } from 'src/auth/view/amplify';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign up | Amplify - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<AmplifySignUpView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { AmplifyUpdatePasswordView } from 'src/auth/view/amplify';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Update password | Amplify - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<AmplifyUpdatePasswordView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { AmplifyVerifyView } from 'src/auth/view/amplify';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Verify | Amplify - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<AmplifyVerifyView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
import { SplashScreen } from 'src/components/loading-screen';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export default function CallbackPage() {
|
||||
return <SplashScreen />;
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { Auth0SignInView } from 'src/auth/view/auth0';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign in | Auth0 - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<Auth0SignInView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { FirebaseResetPasswordView } from 'src/auth/view/firebase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Reset password | Firebase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<FirebaseResetPasswordView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { FirebaseSignInView } from 'src/auth/view/firebase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign in | Firebase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<FirebaseSignInView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { FirebaseSignUpView } from 'src/auth/view/firebase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign up | Firebase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<FirebaseSignUpView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { FirebaseVerifyView } from 'src/auth/view/firebase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Verify | Firebase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<FirebaseVerifyView />
|
||||
</>
|
||||
);
|
||||
}
|
16
03_source/frontend/src/pages/party-user-auth/jwt/sign-in.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { JwtSignInView } from 'src/auth/view/party-user-jwt';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign in | Jwt - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<JwtSignInView />
|
||||
</>
|
||||
);
|
||||
}
|
16
03_source/frontend/src/pages/party-user-auth/jwt/sign-up.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { JwtSignUpView } from 'src/auth/view/party-user-jwt';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign up | Jwt - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<JwtSignUpView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { SupabaseResetPasswordView } from 'src/auth/view/supabase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Reset password | Supabase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<SupabaseResetPasswordView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { SupabaseSignInView } from 'src/auth/view/supabase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign in | Supabase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<SupabaseSignInView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { SupabaseSignUpView } from 'src/auth/view/supabase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Sign up | Supabase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<SupabaseSignUpView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { SupabaseUpdatePasswordView } from 'src/auth/view/supabase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Update password | Supabase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<SupabaseUpdatePasswordView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
import { SupabaseVerifyView } from 'src/auth/view/supabase';
|
||||
import { CONFIG } from 'src/global-config';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const metadata = { title: `Verify | Supabase - ${CONFIG.appName}` };
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<>
|
||||
<title>{metadata.title}</title>
|
||||
|
||||
<SupabaseVerifyView />
|
||||
</>
|
||||
);
|
||||
}
|
@@ -10,6 +10,8 @@ const ROOTS = {
|
||||
AUTH: '/auth',
|
||||
AUTH_DEMO: '/auth-demo',
|
||||
DASHBOARD: '/dashboard',
|
||||
//
|
||||
PARTY_USER_AUTH: '/party-user-auth',
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
@@ -207,4 +209,32 @@ export const paths = {
|
||||
demo: { edit: `${ROOTS.DASHBOARD}/party-user/${MOCK_ID}/edit` },
|
||||
},
|
||||
},
|
||||
partyUserAuth: {
|
||||
jwt: {
|
||||
signIn: `${ROOTS.PARTY_USER_AUTH}/jwt/sign-in`,
|
||||
signUp: `${ROOTS.PARTY_USER_AUTH}/jwt/sign-up`,
|
||||
},
|
||||
//
|
||||
// amplify: {
|
||||
// signIn: `${ROOTS.PARTY_USER_AUTH}/amplify/sign-in`,
|
||||
// verify: `${ROOTS.PARTY_USER_AUTH}/amplify/verify`,
|
||||
// signUp: `${ROOTS.PARTY_USER_AUTH}/amplify/sign-up`,
|
||||
// updatePassword: `${ROOTS.PARTY_USER_AUTH}/amplify/update-password`,
|
||||
// resetPassword: `${ROOTS.PARTY_USER_AUTH}/amplify/reset-password`,
|
||||
// },
|
||||
// firebase: {
|
||||
// signIn: `${ROOTS.PARTY_USER_AUTH}/firebase/sign-in`,
|
||||
// verify: `${ROOTS.PARTY_USER_AUTH}/firebase/verify`,
|
||||
// signUp: `${ROOTS.PARTY_USER_AUTH}/firebase/sign-up`,
|
||||
// resetPassword: `${ROOTS.PARTY_USER_AUTH}/firebase/reset-password`,
|
||||
// },
|
||||
// auth0: { signIn: `${ROOTS.PARTY_USER_AUTH}/auth0/sign-in` },
|
||||
// supabase: {
|
||||
// signIn: `${ROOTS.PARTY_USER_AUTH}/supabase/sign-in`,
|
||||
// verify: `${ROOTS.PARTY_USER_AUTH}/supabase/verify`,
|
||||
// signUp: `${ROOTS.PARTY_USER_AUTH}/supabase/sign-up`,
|
||||
// updatePassword: `${ROOTS.PARTY_USER_AUTH}/supabase/update-password`,
|
||||
// resetPassword: `${ROOTS.PARTY_USER_AUTH}/supabase/reset-password`,
|
||||
// },
|
||||
},
|
||||
};
|
||||
|
@@ -9,6 +9,7 @@ import { authDemoRoutes } from './auth-demo';
|
||||
import { componentsRoutes } from './components';
|
||||
import { dashboardRoutes } from './dashboard';
|
||||
import { mainRoutes } from './main';
|
||||
import { partyUserAuthRoutes } from './party-user-auth';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
@@ -48,6 +49,9 @@ export const routesSection: RouteObject[] = [
|
||||
// Components
|
||||
...componentsRoutes,
|
||||
|
||||
// party-user-auth
|
||||
...partyUserAuthRoutes,
|
||||
|
||||
// No match
|
||||
{ path: '*', element: <Page404 /> },
|
||||
];
|
||||
|
282
03_source/frontend/src/routes/sections/party-user-auth.tsx
Normal file
@@ -0,0 +1,282 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import type { RouteObject } from 'react-router';
|
||||
import { Outlet } from 'react-router';
|
||||
import { GuestGuard } from 'src/auth/guard';
|
||||
import { SplashScreen } from 'src/components/loading-screen';
|
||||
import { AuthSplitLayout } from 'src/layouts/auth-split';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/** **************************************
|
||||
* Jwt
|
||||
*************************************** */
|
||||
const Jwt = {
|
||||
SignInPage: lazy(() => import('src/pages/party-user-auth/jwt/sign-in')),
|
||||
SignUpPage: lazy(() => import('src/pages/party-user-auth/jwt/sign-up')),
|
||||
};
|
||||
|
||||
const authJwt = {
|
||||
path: 'jwt',
|
||||
children: [
|
||||
{
|
||||
path: 'sign-in',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout
|
||||
slotProps={{
|
||||
section: { title: 'Hi, Welcome back' },
|
||||
}}
|
||||
>
|
||||
<Jwt.SignInPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'sign-up',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout>
|
||||
<Jwt.SignUpPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/** **************************************
|
||||
* Amplify
|
||||
*************************************** */
|
||||
const Amplify = {
|
||||
SignInPage: lazy(() => import('src/pages/auth/amplify/sign-in')),
|
||||
SignUpPage: lazy(() => import('src/pages/auth/amplify/sign-up')),
|
||||
VerifyPage: lazy(() => import('src/pages/auth/amplify/verify')),
|
||||
UpdatePasswordPage: lazy(() => import('src/pages/auth/amplify/update-password')),
|
||||
ResetPasswordPage: lazy(() => import('src/pages/auth/amplify/reset-password')),
|
||||
};
|
||||
|
||||
const authAmplify = {
|
||||
path: 'amplify',
|
||||
children: [
|
||||
{
|
||||
path: 'sign-in',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout
|
||||
slotProps={{
|
||||
section: { title: 'Hi, Welcome back' },
|
||||
}}
|
||||
>
|
||||
<Amplify.SignInPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'sign-up',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout>
|
||||
<Amplify.SignUpPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'verify',
|
||||
element: (
|
||||
<AuthSplitLayout>
|
||||
<Amplify.VerifyPage />
|
||||
</AuthSplitLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'reset-password',
|
||||
element: (
|
||||
<AuthSplitLayout>
|
||||
<Amplify.ResetPasswordPage />
|
||||
</AuthSplitLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'update-password',
|
||||
element: (
|
||||
<AuthSplitLayout>
|
||||
<Amplify.UpdatePasswordPage />
|
||||
</AuthSplitLayout>
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/** **************************************
|
||||
* Firebase
|
||||
*************************************** */
|
||||
const Firebase = {
|
||||
SignInPage: lazy(() => import('src/pages/auth/firebase/sign-in')),
|
||||
SignUpPage: lazy(() => import('src/pages/auth/firebase/sign-up')),
|
||||
VerifyPage: lazy(() => import('src/pages/auth/firebase/verify')),
|
||||
ResetPasswordPage: lazy(() => import('src/pages/auth/firebase/reset-password')),
|
||||
};
|
||||
|
||||
const authFirebase = {
|
||||
path: 'firebase',
|
||||
children: [
|
||||
{
|
||||
path: 'sign-in',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout
|
||||
slotProps={{
|
||||
section: { title: 'Hi, Welcome back' },
|
||||
}}
|
||||
>
|
||||
<Firebase.SignInPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'sign-up',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout>
|
||||
<Firebase.SignUpPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'verify',
|
||||
element: (
|
||||
<AuthSplitLayout>
|
||||
<Firebase.VerifyPage />
|
||||
</AuthSplitLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'reset-password',
|
||||
element: (
|
||||
<AuthSplitLayout>
|
||||
<Firebase.ResetPasswordPage />
|
||||
</AuthSplitLayout>
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/** **************************************
|
||||
* Auth0
|
||||
*************************************** */
|
||||
const Auth0 = {
|
||||
SignInPage: lazy(() => import('src/pages/auth/auth0/sign-in')),
|
||||
CallbackPage: lazy(() => import('src/pages/auth/auth0/callback')),
|
||||
};
|
||||
|
||||
const authAuth0 = {
|
||||
path: 'auth0',
|
||||
children: [
|
||||
{
|
||||
path: 'sign-in',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout
|
||||
slotProps={{
|
||||
section: { title: 'Hi, Welcome back' },
|
||||
}}
|
||||
>
|
||||
<Auth0.SignInPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'callback',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<Auth0.CallbackPage />
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/** **************************************
|
||||
* Supabase
|
||||
*************************************** */
|
||||
const Supabase = {
|
||||
SignInPage: lazy(() => import('src/pages/auth/supabase/sign-in')),
|
||||
SignUpPage: lazy(() => import('src/pages/auth/supabase/sign-up')),
|
||||
VerifyPage: lazy(() => import('src/pages/auth/supabase/verify')),
|
||||
UpdatePasswordPage: lazy(() => import('src/pages/auth/supabase/update-password')),
|
||||
ResetPasswordPage: lazy(() => import('src/pages/auth/supabase/reset-password')),
|
||||
};
|
||||
|
||||
const authSupabase = {
|
||||
path: 'supabase',
|
||||
children: [
|
||||
{
|
||||
path: 'sign-in',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout
|
||||
slotProps={{
|
||||
section: { title: 'Hi, Welcome back' },
|
||||
}}
|
||||
>
|
||||
<Supabase.SignInPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'sign-up',
|
||||
element: (
|
||||
<GuestGuard>
|
||||
<AuthSplitLayout>
|
||||
<Supabase.SignUpPage />
|
||||
</AuthSplitLayout>
|
||||
</GuestGuard>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'verify',
|
||||
element: (
|
||||
<AuthSplitLayout>
|
||||
<Supabase.VerifyPage />
|
||||
</AuthSplitLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'reset-password',
|
||||
element: (
|
||||
<AuthSplitLayout>
|
||||
<Supabase.ResetPasswordPage />
|
||||
</AuthSplitLayout>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'update-password',
|
||||
element: (
|
||||
<AuthSplitLayout>
|
||||
<Supabase.UpdatePasswordPage />
|
||||
</AuthSplitLayout>
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export const partyUserAuthRoutes: RouteObject[] = [
|
||||
{
|
||||
path: 'party-user-auth',
|
||||
element: (
|
||||
<Suspense fallback={<SplashScreen />}>
|
||||
<Outlet />
|
||||
</Suspense>
|
||||
),
|
||||
children: [authJwt, authAmplify, authFirebase, authAuth0, authSupabase],
|
||||
},
|
||||
];
|
@@ -83,6 +83,44 @@ export function OverviewAppView() {
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
<AppWidgetSummary
|
||||
title="Total party-events"
|
||||
percent={-0.1}
|
||||
total={4}
|
||||
chart={{
|
||||
colors: [theme.palette.error.main],
|
||||
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'],
|
||||
series: [],
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
<AppWidgetSummary
|
||||
title="Total party-events"
|
||||
percent={-0.1}
|
||||
total={5}
|
||||
chart={{
|
||||
colors: [theme.palette.error.main],
|
||||
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'],
|
||||
series: [],
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid size={{ xs: 12, md: 4 }}>
|
||||
<AppWidgetSummary
|
||||
title="Total party-events"
|
||||
percent={-0.1}
|
||||
total={6}
|
||||
chart={{
|
||||
colors: [theme.palette.error.main],
|
||||
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'],
|
||||
series: [],
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
|
||||
<AppCurrentDownload
|
||||
title="Current download"
|
||||
|
@@ -36,7 +36,7 @@ import { EmptyContent } from 'src/components/empty-content';
|
||||
import { Iconify } from 'src/components/iconify';
|
||||
import { toast } from 'src/components/snackbar';
|
||||
import { DashboardContent } from 'src/layouts/dashboard';
|
||||
import { endpoints } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import { RouterLink } from 'src/routes/components';
|
||||
import { paths } from 'src/routes/paths';
|
||||
import type { IPartyEventItem, IProductTableFilters } from 'src/types/party-event';
|
||||
|
@@ -35,7 +35,7 @@ import { EmptyContent } from 'src/components/empty-content';
|
||||
import { Iconify } from 'src/components/iconify';
|
||||
import { toast } from 'src/components/snackbar';
|
||||
import { DashboardContent } from 'src/layouts/dashboard';
|
||||
import { endpoints } from 'src/lib/axios';
|
||||
import { endpoints } from 'src/lib/endpoints';
|
||||
import { RouterLink } from 'src/routes/components';
|
||||
import { paths } from 'src/routes/paths';
|
||||
import type { IProductItem, IProductTableFilters } from 'src/types/product';
|
||||
|
@@ -10,6 +10,12 @@
|
||||
"jsonRecursiveSort": false,
|
||||
"jsonSortOrder": "{\"*\": \"lexical\"}",
|
||||
"overrides": [
|
||||
{
|
||||
"files": "src/pages/MainTabs/index.tsx",
|
||||
"options": {
|
||||
"printWidth": 240
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "src/App.tsx",
|
||||
"options": {
|
||||
|
325
03_source/mobile/package-lock.json
generated
@@ -36,6 +36,7 @@
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"react-i18next": "^15.5.3",
|
||||
"react-iconly": "^2.2.10",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -44,10 +45,12 @@
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-spinners": "^0.17.0",
|
||||
"react-star-ratings": "^2.3.0",
|
||||
"react-use": "^17.6.0",
|
||||
"react-virtuoso": "^4.13.0",
|
||||
"reselect": "^4.0.0",
|
||||
"sass": "^1.59.3",
|
||||
"swiper": "^9.1.1",
|
||||
"swiper": "^11.2.8",
|
||||
"use-sound": "^5.0.0",
|
||||
"zod": "^3.25.56"
|
||||
},
|
||||
@@ -60,12 +63,13 @@
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-star-ratings": "^2.3.3",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.2.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"typescript": "^4.9.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^4.2.0"
|
||||
}
|
||||
},
|
||||
@@ -395,12 +399,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
|
||||
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
},
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
@@ -1326,6 +1328,16 @@
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-star-ratings": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-star-ratings/-/react-star-ratings-2.3.3.tgz",
|
||||
"integrity": "sha512-8vLqJG1uRA2SmYBBMPWpv6QWHLvZ/a3XmELCIf4xh4VFXT7QkkrcthiLSMjQ4ibDiUtYYpyLB0JoR1lBHSHA2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/slice-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz",
|
||||
@@ -1831,6 +1843,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/clean-stack": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||
@@ -2056,9 +2074,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/decode-named-character-reference": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
|
||||
"integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
|
||||
"integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"character-entities": "^2.0.0"
|
||||
@@ -2714,6 +2732,15 @@
|
||||
"integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-url-attributes": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||
@@ -2760,6 +2787,38 @@
|
||||
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "25.2.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz",
|
||||
"integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
@@ -4653,6 +4712,32 @@
|
||||
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.3.tgz",
|
||||
"integrity": "sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"i18next": ">= 23.2.3",
|
||||
"react": ">= 16.8.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
},
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-iconly": {
|
||||
"version": "2.2.10",
|
||||
"resolved": "https://registry.npmjs.org/react-iconly/-/react-iconly-2.2.10.tgz",
|
||||
@@ -4793,6 +4878,31 @@
|
||||
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-star-ratings": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-star-ratings/-/react-star-ratings-2.3.0.tgz",
|
||||
"integrity": "sha512-34Z/oFNDRRn4ZcX7F3t9ccnpo7SQ32gD/vsusQOBc6B6vlqaGR6tke1/Yx3jTDjemKRSmXqhKgpPTR7/JAXq6A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-star-ratings/node_modules/react": {
|
||||
"version": "16.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-universal-interface": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz",
|
||||
@@ -4828,6 +4938,16 @@
|
||||
"react-dom": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-virtuoso": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.13.0.tgz",
|
||||
"integrity": "sha512-XHv2Fglpx80yFPdjZkV9d1baACKghg/ucpDFEXwaix7z0AfVQj+mF6lM+YQR6UC/TwzXG2rJKydRMb3+7iV3PA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16 || >=17 || >= 18 || >= 19",
|
||||
"react-dom": ">=16 || >=17 || >= 18 || >=19"
|
||||
}
|
||||
},
|
||||
"node_modules/reactcss": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
|
||||
@@ -4870,11 +4990,6 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"node_modules/remark-parse": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||
@@ -5196,11 +5311,6 @@
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/ssr-window": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz",
|
||||
"integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ=="
|
||||
},
|
||||
"node_modules/stack-generator": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
|
||||
@@ -5323,18 +5433,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/style-to-js": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz",
|
||||
"integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==",
|
||||
"version": "1.1.17",
|
||||
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz",
|
||||
"integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"style-to-object": "1.0.8"
|
||||
"style-to-object": "1.0.9"
|
||||
}
|
||||
},
|
||||
"node_modules/style-to-object": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
|
||||
"integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz",
|
||||
"integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inline-style-parser": "0.2.4"
|
||||
@@ -5380,9 +5490,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/swiper": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/swiper/-/swiper-9.1.1.tgz",
|
||||
"integrity": "sha512-D1zArOwI6XCXCYBULPA4jTxpqp5SQtvntjinbXNZwXzj6P3KS51zSWuMarCLXq5oRISay4nX+TuShpxz8qhtbw==",
|
||||
"version": "11.2.8",
|
||||
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.8.tgz",
|
||||
"integrity": "sha512-S5FVf6zWynPWooi7pJ7lZhSUe2snTzqLuUzbd5h5PHUOhzgvW0bLKBd2wv0ixn6/5o9vwc/IkQT74CRcLJQzeg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "patreon",
|
||||
@@ -5393,9 +5503,7 @@
|
||||
"url": "http://opencollective.com/swiper"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"ssr-window": "^4.0.2"
|
||||
},
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4.7.0"
|
||||
}
|
||||
@@ -5568,16 +5676,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true,
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.2.0"
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/unified": {
|
||||
@@ -5836,6 +5945,15 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wait-for-expect": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz",
|
||||
@@ -6216,12 +6334,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz",
|
||||
"integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.11"
|
||||
}
|
||||
"version": "7.27.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
|
||||
"integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.22.5",
|
||||
@@ -6954,6 +7069,15 @@
|
||||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"@types/react-star-ratings": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-star-ratings/-/react-star-ratings-2.3.3.tgz",
|
||||
"integrity": "sha512-8vLqJG1uRA2SmYBBMPWpv6QWHLvZ/a3XmELCIf4xh4VFXT7QkkrcthiLSMjQ4ibDiUtYYpyLB0JoR1lBHSHA2A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"@types/slice-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/slice-ansi/-/slice-ansi-4.0.0.tgz",
|
||||
@@ -7301,6 +7425,11 @@
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"dev": true
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
|
||||
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
|
||||
},
|
||||
"clean-stack": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||
@@ -7463,9 +7592,9 @@
|
||||
}
|
||||
},
|
||||
"decode-named-character-reference": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
|
||||
"integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
|
||||
"integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
|
||||
"requires": {
|
||||
"character-entities": "^2.0.0"
|
||||
}
|
||||
@@ -7925,6 +8054,14 @@
|
||||
"resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz",
|
||||
"integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w=="
|
||||
},
|
||||
"html-parse-stringify": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
|
||||
"integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
|
||||
"requires": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"html-url-attributes": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||
@@ -7952,6 +8089,15 @@
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
|
||||
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="
|
||||
},
|
||||
"i18next": {
|
||||
"version": "25.2.1",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-25.2.1.tgz",
|
||||
"integrity": "sha512-+UoXK5wh+VlE1Zy5p6MjcvctHXAhRwQKCxiJD8noKZzIXmnAX8gdHX5fLPA3MEVxEN4vbZkQFy8N0LyD9tUqPw==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
}
|
||||
},
|
||||
"immediate": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
||||
@@ -9191,6 +9337,15 @@
|
||||
"integrity": "sha512-RbEks3+cbvTP84l/VXGUZ+JMrKOS8ykQCRYdm5aYsxnDquL0vspsyNhGRO7pcH6hsZqWlPOjLye7rJqdtdAmlg==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-i18next": {
|
||||
"version": "15.5.3",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.3.tgz",
|
||||
"integrity": "sha512-ypYmOKOnjqPEJZO4m1BI0kS8kWqkBNsKYyhVUfij0gvjy9xJNoG/VcGkxq5dRlVwzmrmY1BQMAmpbbUBLwC4Kw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.27.6",
|
||||
"html-parse-stringify": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"react-iconly": {
|
||||
"version": "2.2.10",
|
||||
"resolved": "https://registry.npmjs.org/react-iconly/-/react-iconly-2.2.10.tgz",
|
||||
@@ -9282,6 +9437,28 @@
|
||||
"integrity": "sha512-L/8HTylaBmIWwQzIjMq+0vyaRXuoAevzWoD35wKpNTxxtYXWZp+xtgkfD7Y4WItuX0YvdxMPU79+7VhhmbmuTQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-star-ratings": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-star-ratings/-/react-star-ratings-2.3.0.tgz",
|
||||
"integrity": "sha512-34Z/oFNDRRn4ZcX7F3t9ccnpo7SQ32gD/vsusQOBc6B6vlqaGR6tke1/Yx3jTDjemKRSmXqhKgpPTR7/JAXq6A==",
|
||||
"requires": {
|
||||
"classnames": "^2.2.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": {
|
||||
"version": "16.14.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
|
||||
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-universal-interface": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz",
|
||||
@@ -9309,6 +9486,12 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"react-virtuoso": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.13.0.tgz",
|
||||
"integrity": "sha512-XHv2Fglpx80yFPdjZkV9d1baACKghg/ucpDFEXwaix7z0AfVQj+mF6lM+YQR6UC/TwzXG2rJKydRMb3+7iV3PA==",
|
||||
"requires": {}
|
||||
},
|
||||
"reactcss": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
|
||||
@@ -9343,11 +9526,6 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
|
||||
},
|
||||
"remark-parse": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||
@@ -9570,11 +9748,6 @@
|
||||
"integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ssr-window": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz",
|
||||
"integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ=="
|
||||
},
|
||||
"stack-generator": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
|
||||
@@ -9673,17 +9846,17 @@
|
||||
"dev": true
|
||||
},
|
||||
"style-to-js": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz",
|
||||
"integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==",
|
||||
"version": "1.1.17",
|
||||
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz",
|
||||
"integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==",
|
||||
"requires": {
|
||||
"style-to-object": "1.0.8"
|
||||
"style-to-object": "1.0.9"
|
||||
}
|
||||
},
|
||||
"style-to-object": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
|
||||
"integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz",
|
||||
"integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==",
|
||||
"requires": {
|
||||
"inline-style-parser": "0.2.4"
|
||||
}
|
||||
@@ -9717,12 +9890,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"swiper": {
|
||||
"version": "9.1.1",
|
||||
"resolved": "https://registry.npmjs.org/swiper/-/swiper-9.1.1.tgz",
|
||||
"integrity": "sha512-D1zArOwI6XCXCYBULPA4jTxpqp5SQtvntjinbXNZwXzj6P3KS51zSWuMarCLXq5oRISay4nX+TuShpxz8qhtbw==",
|
||||
"requires": {
|
||||
"ssr-window": "^4.0.2"
|
||||
}
|
||||
"version": "11.2.8",
|
||||
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.8.tgz",
|
||||
"integrity": "sha512-S5FVf6zWynPWooi7pJ7lZhSUe2snTzqLuUzbd5h5PHUOhzgvW0bLKBd2wv0ixn6/5o9vwc/IkQT74CRcLJQzeg=="
|
||||
},
|
||||
"tar": {
|
||||
"version": "6.1.13",
|
||||
@@ -9852,10 +10022,10 @@
|
||||
"dev": true
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.9.5",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||
"dev": true
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true
|
||||
},
|
||||
"unified": {
|
||||
"version": "11.0.5",
|
||||
@@ -10003,6 +10173,11 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"void-elements": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
|
||||
"integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
|
||||
},
|
||||
"wait-for-expect": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.1.tgz",
|
||||
|
@@ -34,6 +34,7 @@
|
||||
"react-confetti": "^6.4.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"react-i18next": "^15.5.3",
|
||||
"react-iconly": "^2.2.10",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -42,10 +43,12 @@
|
||||
"react-router": "^5.3.4",
|
||||
"react-router-dom": "^5.3.4",
|
||||
"react-spinners": "^0.17.0",
|
||||
"react-star-ratings": "^2.3.0",
|
||||
"react-use": "^17.6.0",
|
||||
"react-virtuoso": "^4.13.0",
|
||||
"reselect": "^4.0.0",
|
||||
"sass": "^1.59.3",
|
||||
"swiper": "^9.1.1",
|
||||
"swiper": "^11.2.8",
|
||||
"use-sound": "^5.0.0",
|
||||
"zod": "^3.25.56"
|
||||
},
|
||||
@@ -59,12 +62,13 @@
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-router": "^5.1.20",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/react-star-ratings": "^2.3.3",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.2.0",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"typescript": "^4.9.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^4.2.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
|
@@ -68,6 +68,11 @@ import MemberProfile from './pages/MemberProfile';
|
||||
import OrderDetail from './pages/OrderDetail';
|
||||
import DummyPayPage from './pages/DummyEventPayPage';
|
||||
import DummyEventPayPage from './pages/DummyEventPayPage';
|
||||
import PaymentSuccess from './pages/PaymentSuccess';
|
||||
import PaymentFailed from './pages/PaymentFailed';
|
||||
import settings from './pages/tabs/carousell_me/settings';
|
||||
import HotelWelcomeTour from './pages/HotelWelcomeTour';
|
||||
import HotelServiceWifi from './pages/HotelIntro';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
@@ -133,17 +138,23 @@ const IonicApp: React.FC<IonicAppProps> = ({ darkMode, schedule, setIsLoggedIn,
|
||||
<Route exact={true} path={PATHS.PRIVACY_AGREEMENT} component={PrivacyAgreement} />
|
||||
|
||||
{/* Event and profile detail pages */}
|
||||
<Route exact={true} path="/dummy_pay_page" component={DummyPayPage} />
|
||||
|
||||
<Route exact={true} path="/event_detail/:id" component={EventDetail} />
|
||||
|
||||
<Route exact={true} path={PATHS.DUMMY_EVENT_PAY_PAGE} component={DummyEventPayPage} />
|
||||
<Route exact={true} path={PATHS.DUMMY_PAY_PAGE} component={DummyPayPage} />
|
||||
<Route exact={true} path={PATHS.PAYMENT_SUCCESS} component={PaymentSuccess} />
|
||||
<Route exact={true} path={PATHS.PAYMENT_FAILED} component={PaymentFailed} />
|
||||
|
||||
<Route exact={true} path="/profile/:id" component={MemberProfile} />
|
||||
|
||||
{/* component make the ":id" available in the "OrderDetail" */}
|
||||
<Route exact={true} path="/order_detail/:id" component={OrderDetail} />
|
||||
|
||||
<Route exact={true} path="/helloworld" component={Helloworld} />
|
||||
|
||||
<Route exact={true} path={PATHS.DUMMY_EVENT_PAY_PAGE} component={DummyEventPayPage} />
|
||||
<Route path={PATHS.HOTEL_WELCOME_TOUR} component={HotelWelcomeTour} exact={true} />
|
||||
{/* <Route path={PATHS.HOTEL_SERVICE_INTRO} component={HotelServiceIntro} exact={true} /> */}
|
||||
{/* tabs/hotel_service_intro */}
|
||||
|
||||
<Route
|
||||
path="/logout"
|
||||
@@ -153,9 +164,9 @@ const IonicApp: React.FC<IonicAppProps> = ({ darkMode, schedule, setIsLoggedIn,
|
||||
/>
|
||||
|
||||
{/* PartyUser */}
|
||||
<Route path={PATHS.PARTY_USER_SIGN_IN} component={PartyUserLogin} />
|
||||
|
||||
<Route exact={true} path={PATHS.PARTY_USER_SIGN_IN} component={PartyUserLogin} />
|
||||
<Route exact={true} path={PATHS.NOT_IMPLEMENTED} component={NotImplemented} />
|
||||
<Route exact={true} path={PATHS.CAROUSELL_ME_SETTINGS} component={settings} />
|
||||
|
||||
<Route path="/" component={HomeOrTutorial} exact />
|
||||
</IonRouterOutlet>
|
||||
|
@@ -15,16 +15,19 @@ const PATHS = {
|
||||
PRIVACY_AGREEMENT: '/privacy_agreement',
|
||||
SIGN_IN: '/mylogin',
|
||||
|
||||
// event detail
|
||||
getEventDetailPath: (eventId: string) => `/event_detail/${eventId}`,
|
||||
|
||||
// Order-related routes
|
||||
ORDER_DETAIL: '/order_detail/:id',
|
||||
getOrderDetail: (id: string) => `/order_detail/${id}`,
|
||||
// Tab navigation routes
|
||||
TAB_NOT_IMPLEMENTED: '/tabs/not_implemented',
|
||||
EVENT_LIST: `/tabs/events`,
|
||||
MESSAGE_LIST: `/tabs/messages`,
|
||||
EVENT_LIST: '/tabs/events',
|
||||
MESSAGE_LIST: '/tabs/messages',
|
||||
NEARBY_LIST: '/tabs/nearby',
|
||||
ORDERS_LIST: '/tabs/orders',
|
||||
FAVOURITES_LIST: `/tabs/favourites`,
|
||||
FAVOURITES_LIST: '/tabs/favourites',
|
||||
PROFILE: '/tabs/my_profile',
|
||||
|
||||
// partyUser
|
||||
@@ -32,6 +35,9 @@ const PATHS = {
|
||||
PARTY_USER_SIGN_UP: '/partyUserSignUp',
|
||||
|
||||
DUMMY_EVENT_PAY_PAGE: '/DummyEventPayPage',
|
||||
DUMMY_PAY_PAGE: 'dummy_pay_page',
|
||||
PAYMENT_SUCCESS: '/payment_success',
|
||||
PAYMENT_FAILED: '/payment_failed',
|
||||
|
||||
//
|
||||
TABS_DEBUG: '/tabs/debug',
|
||||
@@ -96,5 +102,17 @@ const PATHS = {
|
||||
DEMO_SLIDING_PROFILE: '/demo-sliding-profile',
|
||||
DEMO_STICKY_BOTTOM_SHEET_EXAMPLE: '/demo-sticky-bottom-sheet-example',
|
||||
DEMO_STORAGE_EXAMPLE: '/demo-storage-example',
|
||||
|
||||
//
|
||||
CAROUSELL_ME: '/tabs/carousell_me',
|
||||
CAROUSELL_ME_QR: '/tabs/carousell_me/qr_page',
|
||||
CAROUSELL_ME_SETTINGS: '/carousell_me/settings',
|
||||
CAROUSELL_ME_INSIGHTS: '/tabs/carousell_me/insights',
|
||||
CAROUSELL_ME_OFFERS_MADE: '/tabs/carousell_me/OffersMade',
|
||||
CAROUSELL_ME_MY_PROFILE: '/tabs/carousell_me/my_profile',
|
||||
//
|
||||
HOTEL_WELCOME_TOUR: '/hotel_service_intro',
|
||||
HOTEL_INTRO: '/tabs/hotel_intro',
|
||||
HOTEL_SERVICE_WIFI: '/tabs/hotel_service_wifi',
|
||||
};
|
||||
export default PATHS;
|
||||
|
5
03_source/mobile/src/api/buttonSvg.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 12H18" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 18V6" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 439 B |
10
03_source/mobile/src/api/getButtonSvg.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import buttonSvg from './buttonSvg.svg';
|
||||
|
||||
function getButtonSvg(): Promise<string> {
|
||||
return new Promise((res, rej) => {
|
||||
res(buttonSvg);
|
||||
});
|
||||
}
|
||||
|
||||
export default getButtonSvg;
|
49
03_source/mobile/src/api/getCategory.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import getTestSvg from './getTestSvg';
|
||||
|
||||
async function getCategory(): Promise<any> {
|
||||
let list = [
|
||||
{ name: 'Following', avatar: '' },
|
||||
{ name: 'Computers & Tech', avatar: '' },
|
||||
{ name: "Women's Fashion", avatar: '' },
|
||||
{ name: "Men's Fashion", avatar: '' },
|
||||
{ name: 'Beauty & Personal Care', avatar: '' },
|
||||
{ name: 'Free Items', avatar: '' },
|
||||
{ name: 'Audio', avatar: '' },
|
||||
{ name: 'Furniture & Home Living', avatar: '' },
|
||||
{ name: 'Babies & Kids', avatar: '' },
|
||||
{ name: 'Health & Nutrition', avatar: '' },
|
||||
{ name: 'Food & Drinks', avatar: '' },
|
||||
{ name: 'Tickets & Vouchers', avatar: '' },
|
||||
{ name: 'Auto Accessories', avatar: '' },
|
||||
{ name: 'Community', avatar: '' },
|
||||
{ name: 'Looking For', avatar: '' },
|
||||
{ name: 'Announcements', avatar: '' },
|
||||
{ name: 'Services', avatar: '' },
|
||||
{ name: 'Mobile Phones & Gadgets', avatar: '' },
|
||||
{ name: 'Property', avatar: '' },
|
||||
{ name: 'Cars', avatar: '' },
|
||||
{ name: 'Luxury', avatar: '' },
|
||||
{ name: 'Video Gaming', avatar: '' },
|
||||
{ name: 'Photography', avatar: '' },
|
||||
{ name: 'TV & Home Appliances', avatar: '' },
|
||||
{ name: 'Hobbies & Toys', avatar: '' },
|
||||
{ name: 'Sports', avatar: '' },
|
||||
{ name: 'Equipment', avatar: '' },
|
||||
{ name: 'Pet Supplies', avatar: '' },
|
||||
{ name: 'Motorbikes', avatar: '' },
|
||||
{ name: 'Jobs', avatar: '' },
|
||||
{ name: 'Preorders', avatar: '' },
|
||||
{ name: 'Everything Else', avatar: '' },
|
||||
];
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
list[i].avatar = await getTestSvg();
|
||||
}
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
res(list);
|
||||
});
|
||||
}
|
||||
|
||||
export default getCategory;
|
95
03_source/mobile/src/api/getContentMarkdown.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import React from 'react';
|
||||
|
||||
const contentMd = `
|
||||
全館Wi-Fiをご利用いただけます。(無料)
|
||||
|
||||
|
||||
|
||||
■ID: helloworld Life
|
||||
■Password: 0362751510
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
LYNKED HOTELでは、
|
||||
|
||||
全客室・宴会場の一部に
|
||||
|
||||
有線LAN接続によるインターネット
|
||||
|
||||
接続環境を開業時より
|
||||
|
||||
導入しておりましたが、
|
||||
|
||||
さらなる利便性向上を図るため、
|
||||
|
||||
このたび、ロビー・客室フロアに無線LAN
|
||||
|
||||
アクセスポイントを設置し、
|
||||
|
||||
各客室内でもWi-Fi接続による
|
||||
|
||||
インターネットを利用できる
|
||||
|
||||
環境を構築いたしました。
|
||||
|
||||
より快適なWi-Fi接続サービスによる
|
||||
|
||||
無料のインターネット接続を
|
||||
|
||||
ご利用いただけます。
|
||||
|
||||
|
||||
|
||||
■ ご利用いただける端末
|
||||
|
||||
・LAN / 無線LAN(Wi-Fi規格)アダプタ内蔵PC
|
||||
|
||||
・WindowsXP以上、Mac OSX以上のOSを搭載したPC
|
||||
|
||||
・無線LAN(Wi-Fi規格)対応のiOS機器(iPhone・iPad等)
|
||||
|
||||
・無線LAN(Wi-Fi規格)のAndroid機器(スマートフォン・タブレットPC等)
|
||||
|
||||
|
||||
■ Wi-Fi接続サービスの規格・接続
|
||||
|
||||
IEEE802.11 n/b/gの規格に準拠しており、同時使用が可能です。
|
||||
|
||||
ネットアクセスポイントにつきましては、各ホテルSSIDをフロントにてご案内いたします。
|
||||
|
||||
|
||||
■ 各ホテル内でインターネット接続サービスが可能なエリア
|
||||
|
||||
・全客室内(有線LAN・Wi-Fi接続)
|
||||
|
||||
・フロント前ロビー(Wi-Fi接続)
|
||||
|
||||
・各宴会場(有線LAN接続)
|
||||
|
||||
|
||||
■ ご注意
|
||||
|
||||
※有線・無線LANを通してインターネット接続サービスを無料でご利用いただけます。
|
||||
|
||||
|
||||
※Wi-Fi接続サービスは、ホテルのSSIDが発出されている場所でご利用になれますが、場所により電波の届かないエリア、もしくは電波が弱くご利用が難しい場合もあります。
|
||||
|
||||
|
||||
※ご利用に際してのセキュリティ設定は、お客様ご自身の責任において行っていただくようにお願いいたします。
|
||||
|
||||
|
||||
※本サービスのご利用・予期せぬ停止や不良が原因となり発生した損失や損害については、ホテルは一切の責任は負いかねますので、予めご了承ください。
|
||||
|
||||
|
||||
※ネット対戦ゲーム、大容量ファイルの送受信など、回線を長時間占有してのご利用は、他のお客様のご迷惑となりますので、ご遠慮ください。
|
||||
`.trim();
|
||||
|
||||
function getContentMarkdown(): Promise<string> {
|
||||
return new Promise((res, rej) => {
|
||||
res(contentMd);
|
||||
});
|
||||
}
|
||||
|
||||
export default getContentMarkdown;
|
76
03_source/mobile/src/api/getFreshFinds.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import getUnsplashRandomImage from './getUnsplashRandomImage';
|
||||
|
||||
const ContentMd = `幫個忙,睇埋落去先啦....
|
||||
|
||||
🈺 視乎難度收費,或者你比個 budget plan 諗下都得
|
||||
Charge subject to difficulties with the task(s), or bring your budget plan to me...
|
||||
|
||||
💁♂️ 服務內容 :
|
||||
- 代寫程式 ... ( html/javascript/python coding commission)
|
||||
- 簡易 個人 / 公司網站 / 靜態網站 製作 (static/pwa webpage)
|
||||
- 網店 / 網購平台製作, 網址 / 域名註冊,網站發佈 (deploy)
|
||||
- 手機應用程式 (expo/ionic/android)
|
||||
- 任何自定義 / 量身定做方案, 其他 IT 相關解決方案 (solution / AI)
|
||||
- Wordpress 公司/個人網頁
|
||||
- 網上資料搜集 scraping/harvesting/crawing
|
||||
- source code 想知點解咁寫開聲問,唔明講到你明
|
||||
- 補習 / 指導
|
||||
- 如需補習/備課的話,請給我看看所需要課堂資料或者問題,
|
||||
- 我對你個人資料沒有興趣,你大可以將個人資料屏蔽先 send 比我
|
||||
- 世界這麼大,我只是想知道你遇到什麽問題,謝謝
|
||||
- 寫bot (請先 PM 我, 要睇睇目的先答做唔做.... book場/搶飛 唔洗問 😊)
|
||||
|
||||
💬 吹水閒偈/諮詢攞下意見免費 😊 (睇心情 / 難度答 😂)
|
||||
Please DM / PM consultation for free ( But no guaranteed answer... depends... )
|
||||
|
||||
👋 髒話說在前面 ( Salutations being said in front ) :
|
||||
- 一般黎講,我會維持對客人應有嘅禮貌同埋尊重 (a.k.a. 互相尊重, implicatively 講左D乜請自己諗... )
|
||||
- 同一時間我嘅禮貎都只會展示俾對我有禮貌嘅人睇。
|
||||
- 我就是我本人,不是中介,由對接到做嘅都係我,有懷疑者不用找我 👋 👋 。
|
||||
- 我唔係你肚入面條蟲,我唔會知你心入面有乜 requirement ... 最簡單係叫你比份 requirements/pdf 我,如果你覺得比個原始 file 我係好難接受嘅話(前題係當然你可以預先 blur 敏感資料),唔使搵我👋 👋
|
||||
|
||||
多謝你睇到喱度,我知我好長氣,若然仲有興趣搵我做野的話,
|
||||
第一句同我講 “Hi, 寶達邨的豬~”,我會講整個購物體驗應該會對你有利 😊...
|
||||
完...
|
||||
|
||||
🍖 Some demo:
|
||||
- some site demos:
|
||||
https://louiscklaw.github.io/work
|
||||
|
||||
若果想做 opencv/machine learning 野,可到此 post
|
||||
https://www.carousell.com.hk/p/1338018892/
|
||||
|
||||
🔖 Tags:
|
||||
#reactjs #nodejs #nextjs #typescript #programming #python #html #css #coding #vue #expo #frontend #backend #laravel #github #bot #vba #docker #opencv #mobile-app #LLM #GPT #huggingface #llama #ollama #debug #figma #ICT #opensource #processing #flow-field #網站 #爬蟲 #scraping #RPA #ABAP #FYP #STEM #project #tkinter #shopping-cart #網頁製作 #公司網站 #網店 #整網頁 #一對一教學 #私補 #私教 #補習 #教材 #代編程 #定制程序代寫 #internship #intern #colab #jupyter #raspberry-pi #arduino #openai-gym #gymnasium #app-inventor #microbit #團購 #賭波 #賭馬 #股票 #六合彩 #港股工具 #股票工具 #求助 #28car #智慧轉型
|
||||
|
||||
#switch2 #switch-2 #adidas #airpods-pro-2 #babymonster #celine #chanel #chiikawa #coach #crybaby #dear jane #dior #fujifilm #fujifilm #goyard #hermes #hermes #ipad #iphone #jellycat #labubu #loewe #longchamp #lululemon #lululemon #lv #macbook #minecraft #pokemon #prada #ps4 #ps5 #rolex #samsung #sony #lego #metal-build #yoga #hottoys #riize #roblox #part-time #街馬-2025 #Blackpink演唱會 #淘佳佳 #i-am-gloria
|
||||
|
||||
# updated: 2025-06-16
|
||||
`.trim();
|
||||
|
||||
const userJson = {
|
||||
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||
name: 'louis_coding',
|
||||
since: 'Joined 4 years ago',
|
||||
verified: true,
|
||||
rating: 5.0,
|
||||
total_comment: 37,
|
||||
};
|
||||
|
||||
const productSample = {
|
||||
category: { main: 'Services', sub_cat: ['Learning & Enrichment', 'Enrichment & Tuition'] },
|
||||
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||
title: '#html #css #開發 #指導 ',
|
||||
price: 30,
|
||||
content: ContentMd,
|
||||
user: userJson,
|
||||
};
|
||||
|
||||
function getFreshFinds(): Promise<any> {
|
||||
return new Promise((res, rej) => {
|
||||
res([productSample, productSample, productSample, productSample, productSample, productSample]);
|
||||
});
|
||||
}
|
||||
|
||||
export default getFreshFinds;
|
119
03_source/mobile/src/api/getHtmlContent.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React from 'react';
|
||||
|
||||
const testHtml = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<style>
|
||||
.asscent-color1 { color: #800000 }
|
||||
.text-deep-grey {color: rgb(0,0,0)}
|
||||
.text-grey {color: rgb(0,0,0, 0.6)}
|
||||
.text-smoothing {opacity: 0.8}
|
||||
h1 { font-size: 1.2rem}
|
||||
h2 { font-size: 1.1rem}
|
||||
h3 { font-size: 1rem}
|
||||
h4 { font-size: 0.8rem}
|
||||
h5 { font-size: 0.7rem}
|
||||
h6 { font-size: 0.6rem}
|
||||
|
||||
.quote {
|
||||
background-color: rgba(231, 76, 60, 0.05);
|
||||
padding: 1rem; border-radius: 5px;
|
||||
border-left: 5px solid #800000;
|
||||
width: 90%;
|
||||
margin: 1rem auto;
|
||||
line-height:2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="padding: 0; margin: 0; box-sizing: border-box;" class="text-smoothing">
|
||||
<div class="text-grey" style="font-size: 0.8rem; text-align: center">
|
||||
全館Wi-Fiをご利用いただけます。(無料)
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
<div>■ ID: hello_user</div>
|
||||
<div>■ Password: my-best-password</div>
|
||||
</div>
|
||||
|
||||
<h3 class="asscent-color1">HELLOWORLD HOTELでは、</h3>
|
||||
<p>全客室・宴会場の一部に</p>
|
||||
<p>有線LAN接続によるインターネット</p>
|
||||
<p>接続環境を開業時より</p>
|
||||
<p>導入しておりましたが、</p>
|
||||
<p>さらなる利便性向上を図るため、</p>
|
||||
<p>このたび、ロビー・客室フロアに無線LAN</p>
|
||||
<p>アクセスポイントを設置し、</p>
|
||||
<p>各客室内でもWi-Fi接続による</p>
|
||||
<p>インターネットを利用できる</p>
|
||||
<p>環境を構築いたしました。</p>
|
||||
<p>より快適なWi-Fi接続サービスによる</p>
|
||||
<p>無料のインターネット接続を</p>
|
||||
<p>ご利用いただけます。</p>
|
||||
|
||||
|
||||
<h4 class="asscent-color1">■ ご利用いただける端末</h4>
|
||||
<p>・LAN / 無線LAN(Wi-Fi規格)アダプタ内蔵PC</p>
|
||||
<p>・WindowsXP以上、Mac OSX以上のOSを搭載したPC</p>
|
||||
<p>・無線LAN(Wi-Fi規格)対応のiOS機器(iPhone・iPad等)</p>
|
||||
<p>・無線LAN(Wi-Fi規格)のAndroid機器(スマートフォン・タブレットPC等)</p>
|
||||
|
||||
<h4 class="asscent-color1">■ Wi-Fi接続サービスの規格・接続</h4>
|
||||
<p>IEEE802.11 n/b/gの規格に準拠しており、同時使用が可能です。</p>
|
||||
<p>ネットアクセスポイントにつきましては、各ホテルSSIDをフロントにてご案内いたします。</p>
|
||||
|
||||
<h4 class="asscent-color1">■ 各ホテル内でインターネット接続サービスが可能なエリア</h4>
|
||||
<p>・全客室内(有線LAN・Wi-Fi接続)</p>
|
||||
<p>・フロント前ロビー(Wi-Fi接続)</p>
|
||||
<p>・各宴会場(有線LAN接続)</p>
|
||||
|
||||
<h4 class="asscent-color1">■ ご注意</h4>
|
||||
<p>※有線・無線LANを通してインターネット接続サービスを無料でご利用いただけます。</p>
|
||||
<p>※Wi-Fi接続サービスは、ホテルのSSIDが発出されている場所でご利用になれますが、場所により電波の届かないエリア、もしくは電波が弱くご利用が難しい場合もあります。</p>
|
||||
<p>※ご利用に際してのセキュリティ設定は、お客様ご自身の責任において行っていただくようにお願いいたします。</p>
|
||||
<p>※本サービスのご利用・予期せぬ停止や不良が原因となり発生した損失や損害については、ホテルは一切の責任は負いかねますので、予めご了承ください。</p>
|
||||
<p>※ネット対戦ゲーム、大容量ファイルの送受信など、回線を長時間占有してのご利用は、他のお客様のご迷惑となりますので、ご遠慮ください。</p>
|
||||
|
||||
<h1>待進變果沒致友環健問水法代人苦天。📅📍🎻</h1>
|
||||
<h2>待進變果沒致友環健問水法代人苦天。📅📍🎻</h2>
|
||||
<h3>待進變果沒致友環健問水法代人苦天。📅📍🎻</h3>
|
||||
<h4>待進變果沒致友環健問水法代人苦天。📅📍🎻</h4>
|
||||
<h5>待進變果沒致友環健問水法代人苦天。📅📍🎻</h5>
|
||||
<h6>待進變果沒致友環健問水法代人苦天。📅📍🎻</h6>
|
||||
|
||||
<p>
|
||||
業立臺四即文善公作有往,等怕準命小電個。
|
||||
査今聞光洋後化外財強主職。
|
||||
🌲🔯🍣💵 🐪👫🐈📅📍🎻💼 🐣🍖🐻📩🍨. 🎇👬💨
|
||||
</p>
|
||||
|
||||
|
||||
<h1>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h1>
|
||||
<h2>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h2>
|
||||
<h3>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h3>
|
||||
<h4>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h4>
|
||||
<h5>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h5>
|
||||
<h6>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h6>
|
||||
|
||||
<p>
|
||||
Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information
|
||||
Essay: Lorem Ipsum--when, and when not to use it
|
||||
🌲🔯🍣💵 🐪👫🐈📅📍🎻💼 🐣🍖🐻📩🍨. 🎇👬💨
|
||||
</p>
|
||||
|
||||
<p>📤🏮👀🍮 💃👪👦🌀🌶📈 🍵📊💓🐧🎢👃 🍕🌛🔎🔋🎣🍃 🎡👩📔🍈💭 🎣👅🔽📟📑💋</p>
|
||||
|
||||
</body>
|
||||
</html>`.trim();
|
||||
|
||||
function getHtmlContent(): Promise<string> {
|
||||
return new Promise((res, rej) => {
|
||||
res(testHtml);
|
||||
});
|
||||
}
|
||||
|
||||
export default getHtmlContent;
|
76
03_source/mobile/src/api/getProductList.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import getUnsplashRandomImage from './getUnsplashRandomImage';
|
||||
|
||||
const ContentMd = `幫個忙,睇埋落去先啦....
|
||||
|
||||
🈺 視乎難度收費,或者你比個 budget plan 諗下都得
|
||||
Charge subject to difficulties with the task(s), or bring your budget plan to me...
|
||||
|
||||
💁♂️ 服務內容 :
|
||||
- 代寫程式 ... ( html/javascript/python coding commission)
|
||||
- 簡易 個人 / 公司網站 / 靜態網站 製作 (static/pwa webpage)
|
||||
- 網店 / 網購平台製作, 網址 / 域名註冊,網站發佈 (deploy)
|
||||
- 手機應用程式 (expo/ionic/android)
|
||||
- 任何自定義 / 量身定做方案, 其他 IT 相關解決方案 (solution / AI)
|
||||
- Wordpress 公司/個人網頁
|
||||
- 網上資料搜集 scraping/harvesting/crawing
|
||||
- source code 想知點解咁寫開聲問,唔明講到你明
|
||||
- 補習 / 指導
|
||||
- 如需補習/備課的話,請給我看看所需要課堂資料或者問題,
|
||||
- 我對你個人資料沒有興趣,你大可以將個人資料屏蔽先 send 比我
|
||||
- 世界這麼大,我只是想知道你遇到什麽問題,謝謝
|
||||
- 寫bot (請先 PM 我, 要睇睇目的先答做唔做.... book場/搶飛 唔洗問 😊)
|
||||
|
||||
💬 吹水閒偈/諮詢攞下意見免費 😊 (睇心情 / 難度答 😂)
|
||||
Please DM / PM consultation for free ( But no guaranteed answer... depends... )
|
||||
|
||||
👋 髒話說在前面 ( Salutations being said in front ) :
|
||||
- 一般黎講,我會維持對客人應有嘅禮貌同埋尊重 (a.k.a. 互相尊重, implicatively 講左D乜請自己諗... )
|
||||
- 同一時間我嘅禮貎都只會展示俾對我有禮貌嘅人睇。
|
||||
- 我就是我本人,不是中介,由對接到做嘅都係我,有懷疑者不用找我 👋 👋 。
|
||||
- 我唔係你肚入面條蟲,我唔會知你心入面有乜 requirement ... 最簡單係叫你比份 requirements/pdf 我,如果你覺得比個原始 file 我係好難接受嘅話(前題係當然你可以預先 blur 敏感資料),唔使搵我👋 👋
|
||||
|
||||
多謝你睇到喱度,我知我好長氣,若然仲有興趣搵我做野的話,
|
||||
第一句同我講 “Hi, 寶達邨的豬~”,我會講整個購物體驗應該會對你有利 😊...
|
||||
完...
|
||||
|
||||
🍖 Some demo:
|
||||
- some site demos:
|
||||
https://louiscklaw.github.io/work
|
||||
|
||||
若果想做 opencv/machine learning 野,可到此 post
|
||||
https://www.carousell.com.hk/p/1338018892/
|
||||
|
||||
🔖 Tags:
|
||||
#reactjs #nodejs #nextjs #typescript #programming #python #html #css #coding #vue #expo #frontend #backend #laravel #github #bot #vba #docker #opencv #mobile-app #LLM #GPT #huggingface #llama #ollama #debug #figma #ICT #opensource #processing #flow-field #網站 #爬蟲 #scraping #RPA #ABAP #FYP #STEM #project #tkinter #shopping-cart #網頁製作 #公司網站 #網店 #整網頁 #一對一教學 #私補 #私教 #補習 #教材 #代編程 #定制程序代寫 #internship #intern #colab #jupyter #raspberry-pi #arduino #openai-gym #gymnasium #app-inventor #microbit #團購 #賭波 #賭馬 #股票 #六合彩 #港股工具 #股票工具 #求助 #28car #智慧轉型
|
||||
|
||||
#switch2 #switch-2 #adidas #airpods-pro-2 #babymonster #celine #chanel #chiikawa #coach #crybaby #dear jane #dior #fujifilm #fujifilm #goyard #hermes #hermes #ipad #iphone #jellycat #labubu #loewe #longchamp #lululemon #lululemon #lv #macbook #minecraft #pokemon #prada #ps4 #ps5 #rolex #samsung #sony #lego #metal-build #yoga #hottoys #riize #roblox #part-time #街馬-2025 #Blackpink演唱會 #淘佳佳 #i-am-gloria
|
||||
|
||||
# updated: 2025-06-16
|
||||
`.trim();
|
||||
|
||||
const userJson = {
|
||||
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||
name: 'louis_coding',
|
||||
since: 'Joined 4 years ago',
|
||||
verified: true,
|
||||
rating: 5.0,
|
||||
total_comment: 37,
|
||||
};
|
||||
|
||||
const productSample = {
|
||||
category: { main: 'Services', sub_cat: ['Learning & Enrichment', 'Enrichment & Tuition'] },
|
||||
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||
title: '#html #css #開發 #指導 ',
|
||||
price: 30,
|
||||
content: ContentMd,
|
||||
user: userJson,
|
||||
};
|
||||
|
||||
function getProductList(): Promise<any> {
|
||||
return new Promise((res, rej) => {
|
||||
res([productSample, productSample, productSample, productSample, productSample, productSample]);
|
||||
});
|
||||
}
|
||||
|
||||
export default getProductList;
|
14
03_source/mobile/src/api/getSegments.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
function getSegments(): Promise<any> {
|
||||
return new Promise((res, rej) => {
|
||||
res([
|
||||
{ name: 'Top picks', slug: 'top-picks' },
|
||||
{ name: 'Free items', slug: 'free-items' },
|
||||
{ name: 'Following', slug: 'following' },
|
||||
{ name: 'Nearby', slug: 'nearby' },
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
export default getSegments;
|
33
03_source/mobile/src/api/getSuggestedCategory.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
|
||||
function getSuggestedCategory(): Promise<string[]> {
|
||||
return new Promise((res, rej) => {
|
||||
res([
|
||||
'代做',
|
||||
'cisco',
|
||||
'm1',
|
||||
'pro',
|
||||
'顯示器支架',
|
||||
'kfc',
|
||||
'nas',
|
||||
'la',
|
||||
'prairie',
|
||||
'vr',
|
||||
'代做',
|
||||
'cisco',
|
||||
'm1',
|
||||
'pro',
|
||||
'顯示器支架',
|
||||
'kfc',
|
||||
'nas',
|
||||
'la',
|
||||
'prairie',
|
||||
'vr',
|
||||
'顯示器支架',
|
||||
'kfc',
|
||||
'nas',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
export default getSuggestedCategory;
|
97
03_source/mobile/src/api/getTestSvg.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
|
||||
import airConditioningCoolingHeatTemperatureSvgrepoComSvg from './svgs/air-conditioning-cooling-heat-temperature-svgrepo-com.svg';
|
||||
import alertAttentionCautionDangerousWarningSvgrepoComSvg from './svgs/alert-attention-caution-dangerous-warning-svgrepo-com.svg';
|
||||
import alertBellCallMessageSignSvgrepoComSvg from './svgs/alert-bell-call-message-sign-svgrepo-com.svg';
|
||||
import assistanceBusinessPersonReceptionServiceSvgrepoComSvg from './svgs/assistance-business-person-reception-service-svgrepo-com.svg';
|
||||
import bagBaggageLuggageSuitcaseTravelSvgrepoComSvg from './svgs/bag-baggage-luggage-suitcase-travel-svgrepo-com.svg';
|
||||
import baggageBellhopHotelServiceWaiterSvgrepoComSvg from './svgs/baggage-bellhop-hotel-service-waiter-svgrepo-com.svg';
|
||||
import bagHolidayJourneySuitcaseVacationSvgrepoComSvg from './svgs/bag-holiday-journey-suitcase-vacation-svgrepo-com.svg';
|
||||
import bankingFinancialMoneyPaymentTransactionSvgrepoComSvg from './svgs/banking-financial-money-payment-transaction-svgrepo-com.svg';
|
||||
import barCafeChairClubStoolSvgrepoComSvg from './svgs/bar-cafe-chair-club-stool-svgrepo-com.svg';
|
||||
import bathBathroomCottonTextileTowelSvgrepoComSvg from './svgs/bath-bathroom-cotton-textile-towel-svgrepo-com.svg';
|
||||
import bathroomBathtubBubbleFoamWaterSvgrepoComSvg from './svgs/bathroom-bathtub-bubble-foam-water-svgrepo-com.svg';
|
||||
import bathroomFaucetRoomSinkWashSvgrepoComSvg from './svgs/bathroom-faucet-room-sink-wash-svgrepo-com.svg';
|
||||
import bedFurnitureInteriorPillowRest2SvgrepoComSvg from './svgs/bed-furniture-interior-pillow-rest-2-svgrepo-com.svg';
|
||||
import bedFurnitureInteriorPillowRest3SvgrepoComSvg from './svgs/bed-furniture-interior-pillow-rest-3-svgrepo-com.svg';
|
||||
import bedFurnitureInteriorPillowRestSvgrepoComSvg from './svgs/bed-furniture-interior-pillow-rest-svgrepo-com.svg';
|
||||
import bookCatalogDocumentGuidebookInstructionSvgrepoComSvg from './svgs/book-catalog-document-guidebook-instruction-svgrepo-com.svg';
|
||||
import businessFinanceMoneySavingWalletSvgrepoComSvg from './svgs/business-finance-money-saving-wallet-svgrepo-com.svg';
|
||||
import cafeCardFoodMenuVintageSvgrepoComSvg from './svgs/cafe-card-food-menu-vintage-svgrepo-com.svg';
|
||||
import cafeCupDrinkMugTeaSvgrepoComSvg from './svgs/cafe-cup-drink-mug-tea-svgrepo-com.svg';
|
||||
import calendarDateDayMonthTimeSvgrepoComSvg from './svgs/calendar-date-day-month-time-svgrepo-com.svg';
|
||||
import cappuccinoCoffeeCupDrinkEspresso2SvgrepoComSvg from './svgs/cappuccino-coffee-cup-drink-espresso-2-svgrepo-com.svg';
|
||||
import cappuccinoCoffeeCupDrinkEspressoSvgrepoComSvg from './svgs/cappuccino-coffee-cup-drink-espresso-svgrepo-com.svg';
|
||||
import cardCreditCurrencyFinanceMoneySvgrepoComSvg from './svgs/card-credit-currency-finance-money-svgrepo-com.svg';
|
||||
import cardDoorKeyLockSecuritySvgrepoComSvg from './svgs/card-door-key-lock-security-svgrepo-com.svg';
|
||||
import chairComfortableDecorationGardenTerraceSvgrepoComSvg from './svgs/chair-comfortable-decoration-garden-terrace-svgrepo-com.svg';
|
||||
import cleanClothingLaundryWashingWindSvgrepoComSvg from './svgs/clean-clothing-laundry-washing-wind-svgrepo-com.svg';
|
||||
import comfortableFabricFootwearShoeSlipperSvgrepoComSvg from './svgs/comfortable-fabric-footwear-shoe-slipper-svgrepo-com.svg';
|
||||
import couponEntertainmentEventPaperTicketSvgrepoComSvg from './svgs/coupon-entertainment-event-paper-ticket-svgrepo-com.svg';
|
||||
import doorElevatorEntranceFloorLiftSvgrepoComSvg from './svgs/door-elevator-entrance-floor-lift-svgrepo-com.svg';
|
||||
import doorEnterEntryExitOpenSvgrepoComSvg from './svgs/door-enter-entry-exit-open-svgrepo-com.svg';
|
||||
import doorEntranceHandleKeySecuritySvgrepoComSvg from './svgs/door-entrance-handle-key-security-svgrepo-com.svg';
|
||||
import doorKeyLockRoomSecuritySvgrepoComSvg from './svgs/door-key-lock-room-security-svgrepo-com.svg';
|
||||
import electronicInternetScreenTechnologyTelevisionSvgrepoComSvg from './svgs/electronic-internet-screen-technology-television-svgrepo-com.svg';
|
||||
import holidayHotelJourneyServiceTravel2SvgrepoComSvg from './svgs/holiday-hotel-journey-service-travel-2-svgrepo-com.svg';
|
||||
import holidayHotelJourneyServiceTravelSvgrepoComSvg from './svgs/holiday-hotel-journey-service-travel-svgrepo-com.svg';
|
||||
import holidayHotelMotelSignTravelSvgrepoComSvg from './svgs/holiday-hotel-motel-sign-travel-svgrepo-com.svg';
|
||||
import holidayJourneyLuggageSuitcaseVacation2SvgrepoComSvg from './svgs/holiday-journey-luggage-suitcase-vacation-2-svgrepo-com.svg';
|
||||
import hotelLocationMapPinTravelSvgrepoComSvg from './svgs/hotel-location-map-pin-travel-svgrepo-com.svg';
|
||||
|
||||
const svgList = [
|
||||
airConditioningCoolingHeatTemperatureSvgrepoComSvg,
|
||||
alertAttentionCautionDangerousWarningSvgrepoComSvg,
|
||||
alertBellCallMessageSignSvgrepoComSvg,
|
||||
assistanceBusinessPersonReceptionServiceSvgrepoComSvg,
|
||||
bagBaggageLuggageSuitcaseTravelSvgrepoComSvg,
|
||||
baggageBellhopHotelServiceWaiterSvgrepoComSvg,
|
||||
bagHolidayJourneySuitcaseVacationSvgrepoComSvg,
|
||||
bankingFinancialMoneyPaymentTransactionSvgrepoComSvg,
|
||||
barCafeChairClubStoolSvgrepoComSvg,
|
||||
bathBathroomCottonTextileTowelSvgrepoComSvg,
|
||||
bathroomBathtubBubbleFoamWaterSvgrepoComSvg,
|
||||
bathroomFaucetRoomSinkWashSvgrepoComSvg,
|
||||
bedFurnitureInteriorPillowRest2SvgrepoComSvg,
|
||||
bedFurnitureInteriorPillowRest3SvgrepoComSvg,
|
||||
bedFurnitureInteriorPillowRestSvgrepoComSvg,
|
||||
bookCatalogDocumentGuidebookInstructionSvgrepoComSvg,
|
||||
businessFinanceMoneySavingWalletSvgrepoComSvg,
|
||||
cafeCardFoodMenuVintageSvgrepoComSvg,
|
||||
cafeCupDrinkMugTeaSvgrepoComSvg,
|
||||
calendarDateDayMonthTimeSvgrepoComSvg,
|
||||
cappuccinoCoffeeCupDrinkEspresso2SvgrepoComSvg,
|
||||
cappuccinoCoffeeCupDrinkEspressoSvgrepoComSvg,
|
||||
cardCreditCurrencyFinanceMoneySvgrepoComSvg,
|
||||
cardDoorKeyLockSecuritySvgrepoComSvg,
|
||||
chairComfortableDecorationGardenTerraceSvgrepoComSvg,
|
||||
cleanClothingLaundryWashingWindSvgrepoComSvg,
|
||||
comfortableFabricFootwearShoeSlipperSvgrepoComSvg,
|
||||
couponEntertainmentEventPaperTicketSvgrepoComSvg,
|
||||
doorElevatorEntranceFloorLiftSvgrepoComSvg,
|
||||
doorEnterEntryExitOpenSvgrepoComSvg,
|
||||
doorEntranceHandleKeySecuritySvgrepoComSvg,
|
||||
doorKeyLockRoomSecuritySvgrepoComSvg,
|
||||
electronicInternetScreenTechnologyTelevisionSvgrepoComSvg,
|
||||
holidayHotelJourneyServiceTravel2SvgrepoComSvg,
|
||||
holidayHotelJourneyServiceTravelSvgrepoComSvg,
|
||||
holidayHotelMotelSignTravelSvgrepoComSvg,
|
||||
holidayJourneyLuggageSuitcaseVacation2SvgrepoComSvg,
|
||||
hotelLocationMapPinTravelSvgrepoComSvg,
|
||||
];
|
||||
|
||||
function getRandomInt(max: number) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
function getTestSvg(): Promise<string> {
|
||||
let { length } = svgList;
|
||||
let randomIdx = getRandomInt(length - 1);
|
||||
|
||||
console.log({ findme: svgList[randomIdx] });
|
||||
return new Promise((res, rej) => {
|
||||
res(svgList[randomIdx]);
|
||||
});
|
||||
}
|
||||
|
||||
export default getTestSvg;
|
38
03_source/mobile/src/api/getTopPicks.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import getUnsplashRandomImage from './getUnsplashRandomImage';
|
||||
|
||||
const ContentMd = `helloworld `.trim();
|
||||
|
||||
const userJson = {
|
||||
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||
name: 'louis_coding',
|
||||
since: 'Joined 4 years ago',
|
||||
verified: true,
|
||||
rating: 5.0,
|
||||
total_comment: 37,
|
||||
};
|
||||
|
||||
const productSample = {
|
||||
category: { main: 'Services', sub_cat: ['Learning & Enrichment', 'Enrichment & Tuition'] },
|
||||
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||
title: '#html #css #開發 #指導 ',
|
||||
price: 30,
|
||||
content: ContentMd,
|
||||
user: userJson,
|
||||
};
|
||||
|
||||
function getTopPicks(): Promise<any> {
|
||||
return new Promise((res, rej) => {
|
||||
res([
|
||||
productSample,
|
||||
productSample,
|
||||
productSample,
|
||||
productSample,
|
||||
productSample,
|
||||
productSample,
|
||||
productSample,
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
export default getTopPicks;
|
60
03_source/mobile/src/api/getTopSearches.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
|
||||
function getTopSearches(): Promise<string[]> {
|
||||
return new Promise((res, rej) => {
|
||||
res([
|
||||
'blackpink',
|
||||
'ps5',
|
||||
'blackpink 演唱會',
|
||||
'chanel',
|
||||
'iphone',
|
||||
'陳奕迅演唱會',
|
||||
'hermes',
|
||||
'海港城coupon',
|
||||
'利是封',
|
||||
'ipad',
|
||||
'apple watch',
|
||||
'dior',
|
||||
'celine',
|
||||
'迪士尼門票',
|
||||
'lv',
|
||||
'nike',
|
||||
'張敬軒',
|
||||
'gucci',
|
||||
'lego',
|
||||
'ps4',
|
||||
'loewe',
|
||||
'casetify',
|
||||
'單車',
|
||||
'eason 演唱會',
|
||||
'rolex',
|
||||
'slam dunk',
|
||||
'airpods pro',
|
||||
'coach',
|
||||
'電視',
|
||||
'bearbrick',
|
||||
'雪櫃',
|
||||
'mc',
|
||||
'prada',
|
||||
'samsung',
|
||||
'dyson',
|
||||
'burberry',
|
||||
'張敬軒演唱會',
|
||||
'pokemon',
|
||||
'ikea',
|
||||
'linabell',
|
||||
'dunk low',
|
||||
'陳奕迅',
|
||||
'balenciaga',
|
||||
'梳化',
|
||||
'mirror',
|
||||
'sony',
|
||||
'麻雀',
|
||||
'tory burch',
|
||||
'marshall',
|
||||
'行李箱',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
export default getTopSearches;
|
11
03_source/mobile/src/api/getUnsplashRandomImage.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function getRandomInt(max: number) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
|
||||
function getUnsplashRandomImage({ keyword }: { keyword: string }) {
|
||||
return `https://media.karousell.com/media/photos/profiles/2025/02/05/louis_coding_1738774979_f1598e0b.jpg`;
|
||||
}
|
||||
|
||||
export default getUnsplashRandomImage;
|
76
03_source/mobile/src/api/getYourDailyPicks.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import getUnsplashRandomImage from './getUnsplashRandomImage';
|
||||
|
||||
const ContentMd = `幫個忙,睇埋落去先啦....
|
||||
|
||||
🈺 視乎難度收費,或者你比個 budget plan 諗下都得
|
||||
Charge subject to difficulties with the task(s), or bring your budget plan to me...
|
||||
|
||||
💁♂️ 服務內容 :
|
||||
- 代寫程式 ... ( html/javascript/python coding commission)
|
||||
- 簡易 個人 / 公司網站 / 靜態網站 製作 (static/pwa webpage)
|
||||
- 網店 / 網購平台製作, 網址 / 域名註冊,網站發佈 (deploy)
|
||||
- 手機應用程式 (expo/ionic/android)
|
||||
- 任何自定義 / 量身定做方案, 其他 IT 相關解決方案 (solution / AI)
|
||||
- Wordpress 公司/個人網頁
|
||||
- 網上資料搜集 scraping/harvesting/crawing
|
||||
- source code 想知點解咁寫開聲問,唔明講到你明
|
||||
- 補習 / 指導
|
||||
- 如需補習/備課的話,請給我看看所需要課堂資料或者問題,
|
||||
- 我對你個人資料沒有興趣,你大可以將個人資料屏蔽先 send 比我
|
||||
- 世界這麼大,我只是想知道你遇到什麽問題,謝謝
|
||||
- 寫bot (請先 PM 我, 要睇睇目的先答做唔做.... book場/搶飛 唔洗問 😊)
|
||||
|
||||
💬 吹水閒偈/諮詢攞下意見免費 😊 (睇心情 / 難度答 😂)
|
||||
Please DM / PM consultation for free ( But no guaranteed answer... depends... )
|
||||
|
||||
👋 髒話說在前面 ( Salutations being said in front ) :
|
||||
- 一般黎講,我會維持對客人應有嘅禮貌同埋尊重 (a.k.a. 互相尊重, implicatively 講左D乜請自己諗... )
|
||||
- 同一時間我嘅禮貎都只會展示俾對我有禮貌嘅人睇。
|
||||
- 我就是我本人,不是中介,由對接到做嘅都係我,有懷疑者不用找我 👋 👋 。
|
||||
- 我唔係你肚入面條蟲,我唔會知你心入面有乜 requirement ... 最簡單係叫你比份 requirements/pdf 我,如果你覺得比個原始 file 我係好難接受嘅話(前題係當然你可以預先 blur 敏感資料),唔使搵我👋 👋
|
||||
|
||||
多謝你睇到喱度,我知我好長氣,若然仲有興趣搵我做野的話,
|
||||
第一句同我講 “Hi, 寶達邨的豬~”,我會講整個購物體驗應該會對你有利 😊...
|
||||
完...
|
||||
|
||||
🍖 Some demo:
|
||||
- some site demos:
|
||||
https://louiscklaw.github.io/work
|
||||
|
||||
若果想做 opencv/machine learning 野,可到此 post
|
||||
https://www.carousell.com.hk/p/1338018892/
|
||||
|
||||
🔖 Tags:
|
||||
#reactjs #nodejs #nextjs #typescript #programming #python #html #css #coding #vue #expo #frontend #backend #laravel #github #bot #vba #docker #opencv #mobile-app #LLM #GPT #huggingface #llama #ollama #debug #figma #ICT #opensource #processing #flow-field #網站 #爬蟲 #scraping #RPA #ABAP #FYP #STEM #project #tkinter #shopping-cart #網頁製作 #公司網站 #網店 #整網頁 #一對一教學 #私補 #私教 #補習 #教材 #代編程 #定制程序代寫 #internship #intern #colab #jupyter #raspberry-pi #arduino #openai-gym #gymnasium #app-inventor #microbit #團購 #賭波 #賭馬 #股票 #六合彩 #港股工具 #股票工具 #求助 #28car #智慧轉型
|
||||
|
||||
#switch2 #switch-2 #adidas #airpods-pro-2 #babymonster #celine #chanel #chiikawa #coach #crybaby #dear jane #dior #fujifilm #fujifilm #goyard #hermes #hermes #ipad #iphone #jellycat #labubu #loewe #longchamp #lululemon #lululemon #lv #macbook #minecraft #pokemon #prada #ps4 #ps5 #rolex #samsung #sony #lego #metal-build #yoga #hottoys #riize #roblox #part-time #街馬-2025 #Blackpink演唱會 #淘佳佳 #i-am-gloria
|
||||
|
||||
# updated: 2025-06-16
|
||||
`.trim();
|
||||
|
||||
const userJson = {
|
||||
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||
name: 'louis_coding',
|
||||
since: 'Joined 4 years ago',
|
||||
verified: true,
|
||||
rating: 5.0,
|
||||
total_comment: 37,
|
||||
};
|
||||
|
||||
const productSample = {
|
||||
category: { main: 'Services', sub_cat: ['Learning & Enrichment', 'Enrichment & Tuition'] },
|
||||
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||
title: '#html #css #開發 #指導 ',
|
||||
price: 30,
|
||||
content: ContentMd,
|
||||
user: userJson,
|
||||
};
|
||||
|
||||
function getYourDailyPicks(): Promise<any> {
|
||||
return new Promise((res, rej) => {
|
||||
res([productSample, productSample, productSample, productSample, productSample, productSample]);
|
||||
});
|
||||
}
|
||||
|
||||
export default getYourDailyPicks;
|
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#ffd599;}.cls-2{fill:#6d6daa;}.cls-3{fill:#c9c1f5;}.cls-4{fill:#eef5fd;}</style></defs><title/><rect class="cls-1" height="20" rx="2" ry="2" width="28" x="2" y="6"/><path class="cls-2" d="M28,27H4a3,3,0,0,1-3-3V8A3,3,0,0,1,4,5H28a3,3,0,0,1,3,3V24A3,3,0,0,1,28,27ZM4,7A1,1,0,0,0,3,8V24a1,1,0,0,0,1,1H28a1,1,0,0,0,1-1V8a1,1,0,0,0-1-1Z"/><path class="cls-3" d="M8,20H24a2,2,0,0,1,2,2v4a0,0,0,0,1,0,0H6a0,0,0,0,1,0,0V22A2,2,0,0,1,8,20Z"/><path class="cls-2" d="M26,27H6a1,1,0,0,1-1-1V22a3,3,0,0,1,3-3H24a3,3,0,0,1,3,3v4A1,1,0,0,1,26,27ZM7,25H25V22a1,1,0,0,0-1-1H8a1,1,0,0,0-1,1Z"/><rect class="cls-4" height="4" rx="2" ry="2" width="10" x="16" y="10"/><path class="cls-2" d="M24,15H18a3,3,0,0,1,0-6h6a3,3,0,0,1,0,6Zm-6-4a1,1,0,0,0,0,2h6a1,1,0,0,0,0-2Z"/><path class="cls-2" d="M21,15a1,1,0,0,1-1-1V10a1,1,0,0,1,2,0v4A1,1,0,0,1,21,15Z"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#ffd599;}.cls-2{fill:#6d6daa;}.cls-3{fill:#eef5fd;}</style></defs><title/><polygon class="cls-1" points="30 30 25 30 24 26 8 26 7 30 2 30 6 2 26 2 30 30"/><path class="cls-2" d="M30,31H25a1,1,0,0,1-1-.76L23.22,27H8.78L8,30.24A1,1,0,0,1,7,31H2a1,1,0,0,1-.76-.34,1,1,0,0,1-.23-.8l4-28A1,1,0,0,1,6,1H26a1,1,0,0,1,1,.86l4,28a1,1,0,0,1-.23.8A1,1,0,0,1,30,31Zm-4.22-2h3.07L25.13,3H6.87L3.15,29H6.22L7,25.76A1,1,0,0,1,8,25H24a1,1,0,0,1,1,.76Z"/><circle class="cls-3" cx="16" cy="14" r="6"/><path class="cls-2" d="M16,21a7,7,0,1,1,7-7A7,7,0,0,1,16,21ZM16,9a5,5,0,1,0,5,5A5,5,0,0,0,16,9Z"/><path class="cls-2" d="M12,19a1,1,0,0,1-.71-.29,1,1,0,0,1,0-1.42l8.06-8.06a1,1,0,1,1,1.42,1.42l-8.06,8.06A1,1,0,0,1,12,19Z"/></svg>
|
After Width: | Height: | Size: 941 B |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#798bc6;}.cls-2{fill:#6d6daa;}.cls-3{fill:#d6b5b0;}.cls-4{fill:#ff9797;}.cls-5{fill:#ffc661;}</style></defs><title/><rect class="cls-1" height="10" width="4" x="14" y="6"/><path class="cls-2" d="M18,17H14a1,1,0,0,1-1-1V6a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1V16A1,1,0,0,1,18,17Zm-3-2h2V7H15Z"/><rect class="cls-3" height="4" width="28" x="2" y="26"/><path class="cls-2" d="M30,31H2a1,1,0,0,1-1-1V26a1,1,0,0,1,1-1H30a1,1,0,0,1,1,1v4A1,1,0,0,1,30,31ZM3,29H29V27H3Z"/><path class="cls-4" d="M16,10h0A12,12,0,0,1,28,22v4a0,0,0,0,1,0,0H4a0,0,0,0,1,0,0V22A12,12,0,0,1,16,10Z"/><path class="cls-2" d="M28,27H4a1,1,0,0,1-1-1V22a13,13,0,0,1,26,0v4A1,1,0,0,1,28,27ZM5,25H27V22A11,11,0,0,0,5,22Z"/><rect class="cls-5" height="4" width="10" x="11" y="2"/><path class="cls-2" d="M21,7H11a1,1,0,0,1-1-1V2a1,1,0,0,1,1-1H21a1,1,0,0,1,1,1V6A1,1,0,0,1,21,7ZM12,5h8V3H12Z"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#eef5fd;}.cls-2{fill:#6d6daa;}.cls-3{fill:#d6b5b0;}.cls-4{fill:#ff9797;}.cls-5{fill:#f4dab7;}</style></defs><title/><path class="cls-1" d="M20,20H12a6,6,0,0,0-6,6v4H26V26A6,6,0,0,0,20,20Z"/><path class="cls-2" d="M26,31H6a1,1,0,0,1-1-1V26a7,7,0,0,1,7-7h8a7,7,0,0,1,7,7v4A1,1,0,0,1,26,31ZM7,29H25V26a5,5,0,0,0-5-5H12a5,5,0,0,0-5,5Z"/><path class="cls-3" d="M16,2h0a7,7,0,0,1,7,7v8.2A2.8,2.8,0,0,1,20.2,20H11.8A2.8,2.8,0,0,1,9,17.2V9a7,7,0,0,1,7-7Z"/><path class="cls-2" d="M20.2,21H11.8A3.8,3.8,0,0,1,8,17.2V9A8,8,0,0,1,24,9v8.2A3.8,3.8,0,0,1,20.2,21ZM16,3a6,6,0,0,0-6,6v8.2A1.81,1.81,0,0,0,11.8,19h8.4A1.81,1.81,0,0,0,22,17.2V9A6,6,0,0,0,16,3Z"/><path class="cls-4" d="M20,20H19l-3,2-2,2.15V30H26V26A6,6,0,0,0,20,20Z"/><path class="cls-2" d="M26,31H14a1,1,0,0,1-1-1V24.16a1,1,0,0,1,.27-.68l2-2.16.18-.15,3-2A1,1,0,0,1,19,19h1a7,7,0,0,1,7,7v4A1,1,0,0,1,26,31ZM15,29H25V26a5,5,0,0,0-5-5h-.7l-2.65,1.77L15,24.55Z"/><polygon class="cls-5" points="13 12.82 13 20 16 22 19 20 19 12.82 13 12.82"/><path class="cls-2" d="M16,23a1,1,0,0,1-.55-.16l-3-2A1,1,0,0,1,12,20V12.82a1,1,0,0,1,1-1h6a1,1,0,0,1,1,1V20a1,1,0,0,1-.45.84l-3,2A1,1,0,0,1,16,23Zm-2-3.53,2,1.33,2-1.33V13.82H14Z"/><path class="cls-2" d="M17,27a1,1,0,0,1-1-1V25a1,1,0,0,1,2,0v1A1,1,0,0,1,17,27Z"/><rect class="cls-5" height="4" rx="2" ry="2" width="16" x="8" y="9.42"/><path class="cls-2" d="M22,14.42H10a3,3,0,0,1,0-6H22a3,3,0,0,1,0,6Zm-12-4a1,1,0,0,0,0,2H22a1,1,0,0,0,0-2Z"/><path class="cls-5" d="M13.14,5.19h5.72A2.64,2.64,0,0,1,21.5,7.83V12a5.24,5.24,0,0,1-5.24,5.24h-.51A5.24,5.24,0,0,1,10.5,12V7.83A2.64,2.64,0,0,1,13.14,5.19Z"/><path class="cls-2" d="M16.26,18.19h-.52A6.25,6.25,0,0,1,9.5,12V7.83a3.65,3.65,0,0,1,3.64-3.64h5.72A3.65,3.65,0,0,1,22.5,7.83V12A6.25,6.25,0,0,1,16.26,18.19Zm-3.12-12A1.64,1.64,0,0,0,11.5,7.83V12a4.24,4.24,0,0,0,4.24,4.24h.52A4.24,4.24,0,0,0,20.5,12V7.83a1.64,1.64,0,0,0-1.64-1.64Z"/><path class="cls-3" d="M22.7,7a7,7,0,0,0-9.12-4.55,8.66,8.66,0,0,0,2.65,3.21A7.85,7.85,0,0,0,20.9,7.21,7.65,7.65,0,0,0,22.7,7Z"/><path class="cls-2" d="M20.9,8.21a8.89,8.89,0,0,1-5.27-1.75,9.72,9.72,0,0,1-3-3.59,1,1,0,0,1,0-.81,1,1,0,0,1,.58-.56A8,8,0,0,1,23.66,6.7,1,1,0,0,1,22.94,8,8.5,8.5,0,0,1,20.9,8.21ZM15.11,3.07a6.87,6.87,0,0,0,1.71,1.77A6.82,6.82,0,0,0,21.3,6.2,6.05,6.05,0,0,0,15.11,3.07Z"/><path class="cls-3" d="M16,2A7,7,0,0,0,9,8.31a8.11,8.11,0,0,0,1.21.09,8,8,0,0,0,6-2.75,8.84,8.84,0,0,0,1.85-3.33A6.76,6.76,0,0,0,16,2Z"/><path class="cls-2" d="M10.24,9.4a8.89,8.89,0,0,1-1.36-.1A1,1,0,0,1,8,8.21,8,8,0,0,1,18.38,1.36,1,1,0,0,1,19,2.61,10,10,0,0,1,17,6.32,8.89,8.89,0,0,1,10.24,9.4Zm0-2A6.78,6.78,0,0,0,15.48,5,8.06,8.06,0,0,0,16.74,3c-.24,0-.49,0-.74,0A6,6,0,0,0,10.21,7.4Z"/></svg>
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#6d6daa;}.cls-2{fill:#d6b5b0;}.cls-3{fill:#ffd599;}.cls-4{fill:#ff9797;}.cls-5{fill:#91e085;}</style></defs><title/><path class="cls-1" d="M30,27H8a1,1,0,0,1-1-.88L4.12,3H2A1,1,0,0,1,2,1H5a1,1,0,0,1,1,.88L8.88,25H30a1,1,0,0,1,0,2Z"/><circle class="cls-2" cx="27" cy="28" r="2"/><path class="cls-1" d="M27,31a3,3,0,1,1,3-3A3,3,0,0,1,27,31Zm0-4a1,1,0,1,0,1,1A1,1,0,0,0,27,27Z"/><circle class="cls-2" cx="12" cy="28" r="2"/><path class="cls-1" d="M12,31a3,3,0,1,1,3-3A3,3,0,0,1,12,31Zm0-4a1,1,0,1,0,1,1A1,1,0,0,0,12,27Z"/><rect class="cls-3" height="20" width="20" x="10" y="6"/><path class="cls-1" d="M30,27H10a1,1,0,0,1-1-1V6a1,1,0,0,1,1-1H30a1,1,0,0,1,1,1V26A1,1,0,0,1,30,27ZM11,25H29V7H11Z"/><rect class="cls-4" height="12" width="12" x="14" y="10"/><path class="cls-1" d="M26,23H14a1,1,0,0,1-1-1V10a1,1,0,0,1,1-1H26a1,1,0,0,1,1,1V22A1,1,0,0,1,26,23ZM15,21H25V11H15Z"/><rect class="cls-5" height="12" width="4" x="18" y="10"/><path class="cls-1" d="M22,23H18a1,1,0,0,1-1-1V10a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1V22A1,1,0,0,1,22,23Zm-3-2h2V11H19Z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#6d6daa;}.cls-2{fill:#f4dab7;}.cls-3{fill:#96d7ff;}.cls-4{fill:#ffd599;}</style></defs><title/><path class="cls-1" d="M27,11.6H5a1,1,0,0,1-1-1V4A1,1,0,0,1,5,3H27a1,1,0,0,1,1,1v6.6A1,1,0,0,1,27,11.6ZM6,9.6H26V5H6Z"/><rect class="cls-2" height="4" width="14" x="9" y="2"/><path class="cls-1" d="M23,7H9A1,1,0,0,1,8,6V2A1,1,0,0,1,9,1H23a1,1,0,0,1,1,1V6A1,1,0,0,1,23,7ZM10,5H22V3H10Z"/><rect class="cls-3" height="20" width="28" x="2" y="10"/><path class="cls-1" d="M30,31H2a1,1,0,0,1-1-1V10A1,1,0,0,1,2,9H30a1,1,0,0,1,1,1V30A1,1,0,0,1,30,31ZM3,29H29V11H3Z"/><rect class="cls-4" height="20" width="6" x="2" y="10"/><path class="cls-1" d="M8,31H2a1,1,0,0,1-1-1V10A1,1,0,0,1,2,9H8a1,1,0,0,1,1,1V30A1,1,0,0,1,8,31ZM3,29H7V11H3Z"/><rect class="cls-4" height="20" width="6" x="24" y="10"/><path class="cls-1" d="M30,31H24a1,1,0,0,1-1-1V10a1,1,0,0,1,1-1h6a1,1,0,0,1,1,1V30A1,1,0,0,1,30,31Zm-5-2h4V11H25Z"/></svg>
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#eef5fd;}.cls-2{fill:#6d6daa;}.cls-3{fill:#ff9797;}.cls-4{fill:#f4dab7;}</style></defs><title/><path class="cls-1" d="M20,20H12a6,6,0,0,0-6,6v4H26V26A6,6,0,0,0,20,20Z"/><path class="cls-2" d="M26,31H6a1,1,0,0,1-1-1V26a7,7,0,0,1,7-7h8a7,7,0,0,1,7,7v4A1,1,0,0,1,26,31ZM7,29H25V26a5,5,0,0,0-5-5H12a5,5,0,0,0-5,5Z"/><path class="cls-3" d="M20,20H19l-3,2-2,2.15V30H26V26A6,6,0,0,0,20,20Z"/><path class="cls-2" d="M26,31H14a1,1,0,0,1-1-1V24.16a1,1,0,0,1,.27-.68l2-2.16.18-.15,3-2A1,1,0,0,1,19,19h1a7,7,0,0,1,7,7v4A1,1,0,0,1,26,31ZM15,29H25V26a5,5,0,0,0-5-5h-.7l-2.65,1.77L15,24.55Z"/><polygon class="cls-4" points="13 12.82 13 20 16 22 19 20 19 12.82 13 12.82"/><path class="cls-2" d="M16,23a1,1,0,0,1-.55-.16l-3-2A1,1,0,0,1,12,20V12.82a1,1,0,0,1,1-1h6a1,1,0,0,1,1,1V20a1,1,0,0,1-.45.84l-3,2A1,1,0,0,1,16,23Zm-2-3.53,2,1.33,2-1.33V13.82H14Z"/><path class="cls-2" d="M17,27a1,1,0,0,1-1-1V25a1,1,0,0,1,2,0v1A1,1,0,0,1,17,27Z"/><rect class="cls-1" height="4" width="11" x="10.5" y="2"/><path class="cls-2" d="M21.5,7h-11a1,1,0,0,1-1-1V2a1,1,0,0,1,1-1h11a1,1,0,0,1,1,1V6A1,1,0,0,1,21.5,7Zm-10-2h9V3h-9Z"/><rect class="cls-4" height="4" rx="2" ry="2" width="16" x="8" y="10.42"/><path class="cls-2" d="M22,15.42H10a3,3,0,0,1,0-6H22a3,3,0,0,1,0,6Zm-12-4a1,1,0,0,0,0,2H22a1,1,0,0,0,0-2Z"/><path class="cls-4" d="M13.14,6.19h5.72A2.64,2.64,0,0,1,21.5,8.83V13a5.24,5.24,0,0,1-5.24,5.24h-.51A5.24,5.24,0,0,1,10.5,13V8.83a2.64,2.64,0,0,1,2.64-2.64Z"/><path class="cls-2" d="M16.26,19.19h-.52A6.25,6.25,0,0,1,9.5,13V8.83a3.65,3.65,0,0,1,3.64-3.64h5.72A3.65,3.65,0,0,1,22.5,8.83V13A6.25,6.25,0,0,1,16.26,19.19Zm-3.12-12A1.64,1.64,0,0,0,11.5,8.83V13a4.24,4.24,0,0,0,4.24,4.24h.52A4.24,4.24,0,0,0,20.5,13V8.83a1.64,1.64,0,0,0-1.64-1.64Z"/><rect class="cls-3" height="3" width="11" x="10.5" y="6"/><path class="cls-2" d="M21.5,10h-11a1,1,0,0,1-1-1V6a1,1,0,0,1,1-1h11a1,1,0,0,1,1,1V9A1,1,0,0,1,21.5,10Zm-10-2h9V7h-9Z"/></svg>
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#91e085;}.cls-2{fill:#6d6daa;}.cls-3{fill:#798bc6;}.cls-4{fill:#ffd599;}.cls-5{fill:#d6b5b0;}.cls-6{fill:#eef5fd;}</style></defs><title/><rect class="cls-1" height="4" width="20" x="6" y="2"/><path class="cls-2" d="M26,7H6A1,1,0,0,1,5,6V2A1,1,0,0,1,6,1H26a1,1,0,0,1,1,1V6A1,1,0,0,1,26,7ZM7,5H25V3H7Z"/><rect class="cls-3" height="6" width="16" x="8" y="6"/><path class="cls-2" d="M24,13H8a1,1,0,0,1-1-1V6A1,1,0,0,1,8,5H24a1,1,0,0,1,1,1v6A1,1,0,0,1,24,13ZM9,11H23V7H9Z"/><rect class="cls-4" height="18" width="12" x="10" y="12"/><path class="cls-2" d="M22,31H10a1,1,0,0,1-1-1V12a1,1,0,0,1,1-1H22a1,1,0,0,1,1,1V30A1,1,0,0,1,22,31ZM11,29H21V13H11Z"/><rect class="cls-5" height="18" width="4" x="10" y="12"/><path class="cls-2" d="M14,31H10a1,1,0,0,1-1-1V12a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1V30A1,1,0,0,1,14,31Zm-3-2h2V13H11Z"/><circle class="cls-6" cx="18" cy="16.06" r="1.5"/><path class="cls-2" d="M18,18.56a2.5,2.5,0,1,1,2.5-2.5A2.5,2.5,0,0,1,18,18.56Zm0-3a.5.5,0,0,0-.5.5.5.5,0,1,0,1,0A.5.5,0,0,0,18,15.56Z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#d6b5b0;}.cls-2{fill:#6d6daa;}.cls-3{fill:#ffd599;}</style></defs><title/><rect class="cls-1" height="6" rx="3" ry="3" width="20" x="6" y="2"/><path class="cls-2" d="M23,9H9A4,4,0,0,1,9,1H23a4,4,0,0,1,0,8ZM9,3A2,2,0,0,0,9,7H23a2,2,0,0,0,0-4Z"/><rect class="cls-3" height="4" width="6" x="13" y="8"/><path class="cls-2" d="M19,13H13a1,1,0,0,1-1-1V8a1,1,0,0,1,1-1h6a1,1,0,0,1,1,1v4A1,1,0,0,1,19,13Zm-5-2h4V9H14Z"/><path class="cls-2" d="M16,31a1,1,0,0,1-1-1V12a1,1,0,0,1,2,0V30A1,1,0,0,1,16,31Z"/><polygon class="cls-3" points="24 30 8 30 10 26 16 26 22 26 24 30"/><path class="cls-2" d="M24,31H8a1,1,0,0,1-.89-1.45l2-4A1,1,0,0,1,10,25H22a1,1,0,0,1,.89.55l2,4A1,1,0,0,1,24,31ZM9.62,29H22.38l-1-2H10.62Z"/><path class="cls-2" d="M23,15H20a1,1,0,0,1-.89-.55l-1-2a1,1,0,0,1,1.78-.9L20.62,13H23a1,1,0,0,1,0,2Z"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#96d7ff;}.cls-2{fill:#6d6daa;}.cls-3{fill:#798bc6;}.cls-4{fill:#eef5fd;}</style></defs><title/><path class="cls-1" d="M19,6h0a4,4,0,0,1,4,4v9a0,0,0,0,1,0,0H19a0,0,0,0,1,0,0V6A0,0,0,0,1,19,6Z"/><path class="cls-2" d="M23,20H19a1,1,0,0,1-1-1V6a1,1,0,0,1,1-1,5,5,0,0,1,5,5v9A1,1,0,0,1,23,20Zm-3-2h2V10a3,3,0,0,0-2-2.83Z"/><circle class="cls-3" cx="4" cy="10" r="2"/><path class="cls-2" d="M4,13a3,3,0,1,1,3-3A3,3,0,0,1,4,13ZM4,9a1,1,0,1,0,1,1A1,1,0,0,0,4,9Z"/><circle class="cls-3" cx="28" cy="10" r="2"/><path class="cls-2" d="M28,13a3,3,0,1,1,3-3A3,3,0,0,1,28,13Zm0-4a1,1,0,1,0,1,1A1,1,0,0,0,28,9Z"/><path class="cls-2" d="M26,11H6A1,1,0,0,1,6,9H26a1,1,0,0,1,0,2Z"/><path class="cls-4" d="M11,6h8a0,0,0,0,1,0,0V26a0,0,0,0,1,0,0H9a0,0,0,0,1,0,0V8A2,2,0,0,1,11,6Z"/><path class="cls-2" d="M19,27H9a1,1,0,0,1-1-1V8a3,3,0,0,1,3-3h8a1,1,0,0,1,1,1V26A1,1,0,0,1,19,27Zm-9-2h8V7H11a1,1,0,0,0-1,1Z"/><rect class="cls-1" height="4" width="10" x="9" y="22"/><path class="cls-2" d="M19,27H9a1,1,0,0,1-1-1V22a1,1,0,0,1,1-1H19a1,1,0,0,1,1,1v4A1,1,0,0,1,19,27Zm-9-2h8V23H10Z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#eef5fd;}.cls-2{fill:#6d6daa;}.cls-3{fill:#d6b5b0;}.cls-4{fill:#ffc661;}</style></defs><title/><path class="cls-1" d="M3,16H29a0,0,0,0,1,0,0v6a4,4,0,0,1-4,4H7a4,4,0,0,1-4-4V16A0,0,0,0,1,3,16Z"/><path class="cls-2" d="M25,27H7a5,5,0,0,1-5-5V16a1,1,0,0,1,1-1H29a1,1,0,0,1,1,1v6A5,5,0,0,1,25,27ZM4,17v5a3,3,0,0,0,3,3H25a3,3,0,0,0,3-3V17Z"/><rect class="cls-3" height="3" width="28" x="2" y="13"/><path class="cls-2" d="M30,17H2a1,1,0,0,1-1-1V13a1,1,0,0,1,1-1H30a1,1,0,0,1,1,1v3A1,1,0,0,1,30,17ZM3,15H29V14H3Z"/><rect class="cls-3" height="4" width="4" x="7" y="26"/><path class="cls-2" d="M11,31H7a1,1,0,0,1-1-1V26a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1v4A1,1,0,0,1,11,31ZM8,29h2V27H8Z"/><rect class="cls-3" height="4" width="4" x="21" y="26"/><path class="cls-2" d="M25,31H21a1,1,0,0,1-1-1V26a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1v4A1,1,0,0,1,25,31Zm-3-2h2V27H22Z"/><path class="cls-2" d="M26,14a1,1,0,0,1-1-1V4.5a1.5,1.5,0,0,0-3,0V6a1,1,0,0,1-2,0V4.5a3.5,3.5,0,0,1,7,0V13A1,1,0,0,1,26,14Z"/><path class="cls-4" d="M21,6h0a2.5,2.5,0,0,1,2.5,2.5V9a0,0,0,0,1,0,0h-5a0,0,0,0,1,0,0V8.5A2.5,2.5,0,0,1,21,6Z"/><path class="cls-2" d="M23.5,10h-5a1,1,0,0,1-1-1V8.5a3.5,3.5,0,0,1,7,0V9A1,1,0,0,1,23.5,10ZM19.59,8h2.82a1.49,1.49,0,0,0-2.82,0Z"/></svg>
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#ffd599;}.cls-2{fill:#6d6daa;}.cls-3{fill:#eef5fd;}.cls-4{fill:#c9c1f5;}.cls-5{fill:#ff9797;}</style></defs><title/><rect class="cls-1" height="4" width="28" x="2" y="16"/><path class="cls-2" d="M30,21H2a1,1,0,0,1-1-1V16a1,1,0,0,1,1-1H30a1,1,0,0,1,1,1v4A1,1,0,0,1,30,21ZM3,19H29V17H3Z"/><path class="cls-3" d="M4,20A11.88,11.88,0,0,0,16,30,11.88,11.88,0,0,0,28,20Z"/><path class="cls-2" d="M16,31A12.89,12.89,0,0,1,3,20.13a1,1,0,0,1,.24-.79A1,1,0,0,1,4,19H28a1,1,0,0,1,.75.34,1,1,0,0,1,.24.79A12.89,12.89,0,0,1,16,31ZM5.21,21A11,11,0,0,0,16,29a11,11,0,0,0,10.79-8Z"/><rect class="cls-4" height="7" width="3" x="14.5" y="9"/><path class="cls-2" d="M17.5,17h-3a1,1,0,0,1-1-1V9a1,1,0,0,1,1-1h3a1,1,0,0,1,1,1v7A1,1,0,0,1,17.5,17Zm-2-2h1V10h-1Z"/><path class="cls-5" d="M16,2h0a3,3,0,0,1,3,3V9a0,0,0,0,1,0,0H13a0,0,0,0,1,0,0V5A3,3,0,0,1,16,2Z"/><path class="cls-2" d="M19,10H13a1,1,0,0,1-1-1V5a4,4,0,0,1,8,0V9A1,1,0,0,1,19,10ZM14,8h4V5a2,2,0,0,0-4,0Z"/><path class="cls-5" d="M8,12H8a2,2,0,0,1,2,2v2a0,0,0,0,1,0,0H6a0,0,0,0,1,0,0V14A2,2,0,0,1,8,12Z"/><path class="cls-2" d="M10,17H6a1,1,0,0,1-1-1V14a3,3,0,0,1,6,0v2A1,1,0,0,1,10,17ZM7,15H9V14a1,1,0,0,0-2,0Z"/><path class="cls-5" d="M24,12h0a2,2,0,0,1,2,2v2a0,0,0,0,1,0,0H22a0,0,0,0,1,0,0V14A2,2,0,0,1,24,12Z"/><path class="cls-2" d="M26,17H22a1,1,0,0,1-1-1V14a3,3,0,0,1,6,0v2A1,1,0,0,1,26,17Zm-3-2h2V14a1,1,0,0,0-2,0Z"/></svg>
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#ffd599;}.cls-2{fill:#6d6daa;}.cls-3{fill:#d6b5b0;}.cls-4{fill:#f4dab7;}.cls-5{fill:#ff9797;}</style></defs><title/><rect class="cls-1" height="20" width="4" x="2" y="6"/><path class="cls-2" d="M6,27H2a1,1,0,0,1-1-1V6A1,1,0,0,1,2,5H6A1,1,0,0,1,7,6V26A1,1,0,0,1,6,27ZM3,25H5V7H3Z"/><rect class="cls-1" height="10" width="4" x="26" y="16"/><path class="cls-2" d="M30,27H26a1,1,0,0,1-1-1V16a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1V26A1,1,0,0,1,30,27Zm-3-2h2V17H27Z"/><rect class="cls-3" height="4" width="20" x="6" y="18"/><path class="cls-2" d="M26,23H6a1,1,0,0,1-1-1V18a1,1,0,0,1,1-1H26a1,1,0,0,1,1,1v4A1,1,0,0,1,26,23ZM7,21H25V19H7Z"/><path class="cls-4" d="M6,13H28a2,2,0,0,1,2,2v3a0,0,0,0,1,0,0H6a0,0,0,0,1,0,0V13A0,0,0,0,1,6,13Z"/><path class="cls-2" d="M30,19H6a1,1,0,0,1-1-1V13a1,1,0,0,1,1-1H28a3,3,0,0,1,3,3v3A1,1,0,0,1,30,19ZM7,17H29V15a1,1,0,0,0-1-1H7Z"/><path class="cls-5" d="M6,10h5a3,3,0,0,1,3,3v0a0,0,0,0,1,0,0H6a0,0,0,0,1,0,0V10A0,0,0,0,1,6,10Z"/><path class="cls-2" d="M14,14H6a1,1,0,0,1-1-1V10A1,1,0,0,1,6,9h5a4,4,0,0,1,4,4A1,1,0,0,1,14,14ZM7,12h5.73A2,2,0,0,0,11,11H7Z"/></svg>
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#ffd599;}.cls-2{fill:#6d6daa;}.cls-3{fill:#d6b5b0;}.cls-4{fill:#f4dab7;}.cls-5{fill:#ff9797;}</style></defs><title/><path class="cls-1" d="M7.5,6h17a4,4,0,0,1,4,4V20a0,0,0,0,1,0,0H3.5a0,0,0,0,1,0,0V10A4,4,0,0,1,7.5,6Z"/><path class="cls-2" d="M28.5,21H3.5a1,1,0,0,1-1-1V10a5,5,0,0,1,5-5h17a5,5,0,0,1,5,5V20A1,1,0,0,1,28.5,21Zm-24-2h23V10a3,3,0,0,0-3-3H7.5a3,3,0,0,0-3,3Z"/><rect class="cls-3" height="3" width="28" x="2" y="20"/><path class="cls-2" d="M30,24H2a1,1,0,0,1-1-1V20a1,1,0,0,1,1-1H30a1,1,0,0,1,1,1v3A1,1,0,0,1,30,24ZM3,22H29V21H3Z"/><path class="cls-4" d="M5.54,14H26.46a2,2,0,0,1,2,2v4a0,0,0,0,1,0,0H3.5a0,0,0,0,1,0,0V16A2,2,0,0,1,5.54,14Z"/><path class="cls-2" d="M28.5,21H3.5a1,1,0,0,1-1-1V16a3,3,0,0,1,3-3H26.46a3,3,0,0,1,3,3v4A1,1,0,0,1,28.5,21Zm-24-2h23V16a1,1,0,0,0-1-1H5.54a1,1,0,0,0-1,1Z"/><rect class="cls-1" height="3" width="4" x="4" y="23"/><path class="cls-2" d="M8,27H4a1,1,0,0,1-1-1V23a1,1,0,0,1,1-1H8a1,1,0,0,1,1,1v3A1,1,0,0,1,8,27ZM5,25H7V24H5Z"/><rect class="cls-1" height="3" width="4" x="24" y="23"/><path class="cls-2" d="M28,27H24a1,1,0,0,1-1-1V23a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1v3A1,1,0,0,1,28,27Zm-3-2h2V24H25Z"/><rect class="cls-5" height="3" width="8" x="6" y="11"/><path class="cls-2" d="M14,15H6a1,1,0,0,1-1-1V11a1,1,0,0,1,1-1h8a1,1,0,0,1,1,1v3A1,1,0,0,1,14,15ZM7,13h6V12H7Z"/><rect class="cls-5" height="3" width="8" x="18" y="11"/><path class="cls-2" d="M26,15H18a1,1,0,0,1-1-1V11a1,1,0,0,1,1-1h8a1,1,0,0,1,1,1v3A1,1,0,0,1,26,15Zm-7-2h6V12H19Z"/></svg>
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" ?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" id="object" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#d6b5b0;}.cls-2{fill:#6d6daa;}.cls-3{fill:#f4dab7;}.cls-4{fill:#ffd599;}.cls-5{fill:#ff9797;}</style></defs><title/><rect class="cls-1" height="3" width="28" x="2" y="20"/><path class="cls-2" d="M30,24H2a1,1,0,0,1-1-1V20a1,1,0,0,1,1-1H30a1,1,0,0,1,1,1v3A1,1,0,0,1,30,24ZM3,22H29V21H3Z"/><path class="cls-3" d="M6,14H26a2,2,0,0,1,2,2v4a0,0,0,0,1,0,0H4a0,0,0,0,1,0,0V16A2,2,0,0,1,6,14Z"/><path class="cls-2" d="M28,21H4a1,1,0,0,1-1-1V16a3,3,0,0,1,3-3H26a3,3,0,0,1,3,3v4A1,1,0,0,1,28,21ZM5,19H27V16a1,1,0,0,0-1-1H6a1,1,0,0,0-1,1Z"/><rect class="cls-4" height="3" width="4" x="4" y="23"/><path class="cls-2" d="M8,27H4a1,1,0,0,1-1-1V23a1,1,0,0,1,1-1H8a1,1,0,0,1,1,1v3A1,1,0,0,1,8,27ZM5,25H7V24H5Z"/><rect class="cls-4" height="3" width="4" x="24" y="23"/><path class="cls-2" d="M28,27H24a1,1,0,0,1-1-1V23a1,1,0,0,1,1-1h4a1,1,0,0,1,1,1v3A1,1,0,0,1,28,27Zm-3-2h2V24H25Z"/><path class="cls-4" d="M10,6H22a4,4,0,0,1,4,4v4a0,0,0,0,1,0,0H6a0,0,0,0,1,0,0V10A4,4,0,0,1,10,6Z"/><path class="cls-2" d="M26,15H6a1,1,0,0,1-1-1V10a5,5,0,0,1,5-5H22a5,5,0,0,1,5,5v4A1,1,0,0,1,26,15ZM7,13H25V10a3,3,0,0,0-3-3H10a3,3,0,0,0-3,3Z"/><rect class="cls-5" height="3" width="8" x="12" y="11"/><path class="cls-2" d="M20,15H12a1,1,0,0,1-1-1V11a1,1,0,0,1,1-1h8a1,1,0,0,1,1,1v3A1,1,0,0,1,20,15Zm-7-2h6V12H13Z"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |