Compare commits

...

33 Commits

Author SHA1 Message Date
louiscklaw
7c1ac6b546 feat: add new CarousellMe routes for OffersMade and MyProfile pages with corresponding API integrations and UI components 2025-06-20 03:15:15 +08:00
louiscklaw
53b498d881 feat: implement Hotel Service Wi-Fi intro page with Swiper slider and HTML content display 2025-06-20 02:04:49 +08:00
louiscklaw
d865fca058 feat: enhance CarousellMe UI with updated filter button styling, end of list indicator, and improved content padding 2025-06-20 01:40:35 +08:00
louiscklaw
871313449e "fix: update CAROUSELL_ME_SETTINGS path to remove redundant tabs prefix for consistency" 2025-06-19 23:20:01 +08:00
louiscklaw
76840a8e1b feat: add comprehensive ServiceMenu and MainTabs components with hotel service listings, QR code access, and user profile settings integration 2025-06-19 23:06:51 +08:00
louiscklaw
a68cb01585 feat: add new SVG icons for MainTabs including air conditioning, alert, assistance, baggage, and banking symbols 2025-06-19 18:51:59 +08:00
louiscklaw
d6b36a0ca6 feat: add getButtonSvg API to fetch button SVG file as Promise 2025-06-19 18:51:43 +08:00
louiscklaw
360da364ff feat: add CarousellMe feature with profile, listings and reviews components 2025-06-19 17:45:45 +08:00
louiscklaw
1fdf10c0da feat: add requirement doc for importing demo page from old projects (REQ0190) 2025-06-19 17:10:54 +08:00
louiscklaw
c0d8d0cd05 feat: add react-i18next and react-star-ratings packages with dependencies update and typescript upgrade 2025-06-19 17:10:15 +08:00
louiscklaw
b923410f99 feat: add react-i18next package for internationalization support 2025-06-19 17:10:03 +08:00
louiscklaw
65f9b83c9f feat: add react-star-ratings package for star rating component 2025-06-19 17:09:07 +08:00
louiscklaw
d8166d8a3d feat: upgrade typescript from 4.9.3 to 5.8.3 2025-06-19 17:08:28 +08:00
louiscklaw
f59a382d8f feat: update payment route paths to use centralized PATHS constants and add missing dummy pay page route 2025-06-19 16:41:48 +08:00
louiscklaw
c4f8a6902c feat: implement payment flow for event joining including success/failure pages and navigation 2025-06-19 16:21:11 +08:00
louiscklaw
7b230d4f8b feat: add 99_references folder to workspace paths 2025-06-19 16:20:45 +08:00
louiscklaw
13c3399a6e update, 2025-06-18 13:42:35 +08:00
louiscklaw
80a2636f90 feat: remove debug logging from event count endpoint 2025-06-18 13:39:09 +08:00
louiscklaw
9c4637528c feat: add event count endpoints and improve auth logging with constants 2025-06-18 13:38:14 +08:00
louiscklaw
661de6e8d7 refactor: enhance tsc watch script with file touch fallback to prevent watch termination 2025-06-18 13:35:36 +08:00
louiscklaw
4a0ae590b0 feat: extract API endpoints to separate endpoints.ts file and enhance axios configuration with centralized setup and error handling 2025-06-18 13:23:47 +08:00
louiscklaw
53162ed333 feat: update AI initialization guide with git staged files instruction and file comment update reminder 2025-06-18 12:51:14 +08:00
louiscklaw
79c292d943 "feat: update workspace settings with increased editor font size from 15 to 18" 2025-06-18 12:51:07 +08:00
louiscklaw
10a6375347 feat: add party user auth endpoints for authentication flow (me, signIn, signUp) 2025-06-18 12:50:43 +08:00
louiscklaw
f950617372 feat: extend auth endpoint to support both User and PartyUser models with fallback retrieval logic 2025-06-18 12:43:03 +08:00
louiscklaw
99fafda624 feat: enhance party user auth endpoint with token validation, error logging, and security improvements 2025-06-18 12:34:48 +08:00
louiscklaw
3ed3f2fecb update FAQ, 2025-06-18 11:02:00 +08:00
louiscklaw
b80939c78d update eslint to disable export sorting, 2025-06-18 10:54:30 +08:00
louiscklaw
779984f65c feat: enhance party user authentication endpoint with improved error handling, logging, and PartyUser model integration 2025-06-18 10:52:00 +08:00
louiscklaw
8bb6c9e992 update FAQ, 2025-06-18 10:39:57 +08:00
louiscklaw
60ecca48b4 feat: update workspace settings with font size configuration and recommended extensions 2025-06-18 10:32:12 +08:00
louiscklaw
7a6014a115 feat: implement event joining flow with dummy payment page, including route configuration, Redux state management, and UI updates for event detail page 2025-06-18 04:06:16 +08:00
louiscklaw
37ace98e60 feat: add party payment flow with dummy pay page implementation and route configuration 2025-06-18 02:32:09 +08:00
271 changed files with 8396 additions and 623 deletions

View File

@@ -0,0 +1,49 @@
---
tags: mobile, payment
---
# REQ0189 party payment flow
frontend page to handle party-user pay join event
## 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.
## branch
develop/requirements/REQ0189
develop/mobile/DummyPayPage/trunk

View 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

View File

@@ -1,3 +1,5 @@
// src/cms_backend/eslint.config.mjs
//
import globals from 'globals';
import eslintJs from '@eslint/js';
import eslintTs from 'typescript-eslint';
@@ -69,10 +71,7 @@ const importRules = () => ({
*/
const unusedImportsRules = () => ({
'unused-imports/no-unused-imports': 1,
'unused-imports/no-unused-vars': [
0,
{ vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
],
'unused-imports/no-unused-vars': [0, { vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' }],
});
/**
@@ -93,15 +92,17 @@ const sortImportsRules = () => {
return {
'perfectionist/sort-named-imports': [1, { type: 'line-length', order: 'asc' }],
'perfectionist/sort-named-exports': [1, { type: 'line-length', order: 'asc' }],
'perfectionist/sort-exports': [
1,
{
order: 'asc',
type: 'line-length',
groupKind: 'values-first',
},
],
// disable sorting of export, i manage the export ordering
// 'perfectionist/sort-named-exports': [1, { type: 'line-length', order: 'asc' }],
// 'perfectionist/sort-exports': [
// 1,
// {
// order: 'asc',
// type: 'line-length',
// groupKind: 'values-first',
// },
// ],
'perfectionist/sort-imports': [
2,
{

View File

@@ -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);

View File

@@ -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

View 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);
}
}

View File

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

View 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);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/event/numOfEvent

View File

@@ -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);
}
}

View File

@@ -0,0 +1,3 @@
###
GET http://localhost:7272/api/party-event/numOfEvent

View File

@@ -1,5 +1,22 @@
import type { User } from '@prisma/client';
// src/app/api/party-user-auth/me/route.ts
//
// PURPOSE:
// - Handle authentication for party users via JWT
// - Verify and decode JWT tokens
// - Return current authenticated party user details
// - Log all access attempts (success/failure)
// - Validate token structure and user existence
//
// 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';
import { headers } from 'next/headers';
@@ -7,28 +24,19 @@ import { verify } from 'src/utils/jwt';
import { STATUS, response, handleError } from 'src/utils/response';
import { JWT_SECRET } from 'src/_mock/_auth';
import { getUserById } from 'src/app/services/user.service';
import { createAccessLog } from 'src/app/services/access-log.service';
import { getPartyUserById } from 'src/app/services/party-user.service';
import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest';
// ----------------------------------------------------------------------
// export const runtime = 'edge';
const ERR_USER_TOKEN_CHECK_FAILED = 'user token check failed';
const ERR_INVALID_AUTH_TOKEN = 'Invalid authorization token';
const ERR_USER_ID_NOT_FOUND = 'userId not found';
const ERR_AUTHORIZATION_TOKEN_MISSING_OR_INVALID = 'Authorization token missing or invalid';
/**
* 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 USER_TOKEN_CHECK_FAILED = 'user token check failed';
const INVALID_AUTH_TOKEN = 'Invalid authorization token';
const USER_ID_NOT_FOUND = 'userId not found';
const USER_TOKEN_OK = 'user token check ok';
const AUTHORIZATION_TOKEN_MISSING_OR_INVALID = 'Authorization token missing or invalid';
export async function GET(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
@@ -38,29 +46,29 @@ export async function GET(req: NextRequest) {
const authorization = headersList.get('authorization');
if (!authorization || !authorization.startsWith('Bearer ')) {
return response({ message: AUTHORIZATION_TOKEN_MISSING_OR_INVALID }, STATUS.UNAUTHORIZED);
return response({ message: ERR_AUTHORIZATION_TOKEN_MISSING_OR_INVALID }, STATUS.UNAUTHORIZED);
}
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 currentUser: User | null = await getUserById(data.userId);
const currentUser: PartyUser | null = await getPartyUserById(data.userId);
if (!currentUser) {
createAccessLog('', USER_TOKEN_CHECK_FAILED, debug);
createAccessLog('', ERR_USER_TOKEN_CHECK_FAILED, debug);
return response({ message: INVALID_AUTH_TOKEN }, STATUS.UNAUTHORIZED);
return response({ message: ERR_INVALID_AUTH_TOKEN }, STATUS.UNAUTHORIZED);
}
createAccessLog(currentUser.id, USER_TOKEN_OK, debug);
return response({ user: currentUser }, STATUS.OK);
} else {
return response({ message: USER_ID_NOT_FOUND }, STATUS.ERROR);
return response({ message: ERR_USER_ID_NOT_FOUND }, STATUS.ERROR);
}
} catch (error) {
return handleError('[Auth] - Me', error);

View File

@@ -1,22 +1,35 @@
// Test cases for Party User Authentication endpoints
// Tests both successful and error scenarios
// Environment: http://localhost:7272
// Expected responses:
// - 200 OK with user data for valid tokens
// - 401 Unauthorized for invalid/missing tokens
// - 400 Bad Request for invalid credentials
###
# username and password ok
GET http://localhost:7272/api/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWJnbnUyengwMDBjaHEzaGZ3dmtjejlvIiwiaWF0IjoxNzQ4OTY0ODkyLCJleHAiOjE3NTAxNzQ0OTJ9.lo04laCxtm0IVeYaETEV3hXKyDmXPEn7SyWtY2VR4dI
GET http://localhost:7272/api/party-user-auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWMwdWo4azIwMDBxM2Y1eTZlNXJzejRxIiwiaWF0IjoxNzUwMjEzOTkwLCJleHAiOjE3NTE0MjM1OTB9.MoKv3Nmrp_blE0jQ1rG1WyQ_TrJeF7kSe5xfHrF8b64
###
# There is no user corresponding to the email address.
POST http://localhost:7272/api/auth/sign-in
POST http://localhost:7272/api/party-user-auth/sign-in
content-type: application/json
{
"email": "demo@minimals1.cc",
"password": "@2Minimal"
"email": "party_user0@prisma.io",
"password": "Aa12345678"
}
###
# Wrong password
POST http://localhost:7272/api/auth/sign-in
POST http://localhost:7272/api/party-user-auth/sign-in
content-type: application/json
{

View File

@@ -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';

View File

@@ -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);
}

View File

@@ -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"
}
###

View File

@@ -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' },

View File

@@ -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,
};

View File

@@ -1,14 +1,18 @@
// src/app/services/user.service.ts
// src/app/services/party-user.service.ts
//
// PURPOSE:
// - Handle User Record CRUD operations
// - Handle Party User Record CRUD operations
// - Manage party member data and permissions
// - Interface between controllers and database
//
// RULES:
// - Follow Prisma best practices for database operations
// - Validate input data before processing
// - Validate all party user data before processing
// - Enforce party-specific business rules
// - Maintain audit trail for sensitive operations
//
import type { User, PartyUser } from '@prisma/client';
import type { PartyUser } from '@prisma/client';
import prisma from '../lib/prisma';
@@ -47,8 +51,8 @@ async function getPartyUserByEmail(email: string): Promise<PartyUser | null> {
});
}
async function getUserById(id: string): Promise<User | null> {
return prisma.user.findFirst({ where: { id } });
async function getPartyUserById(id: string): Promise<PartyUser | null> {
return prisma.partyUser.findFirst({ where: { id } });
}
async function createPartyUser(partyUserData: any): Promise<PartyUser> {
@@ -69,7 +73,7 @@ async function deletePartyUser(partyUserId: string): Promise<PartyUser | null> {
}
export {
getUserById,
getPartyUserById,
getPartyUser,
listPartyUsers,
createPartyUser,

View File

@@ -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"
}
}
}

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View 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;
}
};

View File

@@ -0,0 +1 @@
export const JWT_STORAGE_KEY = 'jwt_access_token';

View File

@@ -0,0 +1,7 @@
export * from './utils';
export * from './action';
export * from './constant';
// export * from './auth-provider';

View 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;
}
}

View File

@@ -0,0 +1,3 @@
export * from './jwt-sign-in-view';
export * from './jwt-sign-up-view';

View File

@@ -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={
<>
{`Dont 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>
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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}`,
},
};

View 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',
},
};

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -0,0 +1,7 @@
import { SplashScreen } from 'src/components/loading-screen';
// ----------------------------------------------------------------------
export default function CallbackPage() {
return <SplashScreen />;
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View 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 />
</>
);
}

View 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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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 />
</>
);
}

View File

@@ -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`,
// },
},
};

View File

@@ -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 /> },
];

View 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],
},
];

View File

@@ -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"

View File

@@ -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';

View File

@@ -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';

View File

@@ -10,6 +10,12 @@
"jsonRecursiveSort": false,
"jsonSortOrder": "{\"*\": \"lexical\"}",
"overrides": [
{
"files": "src/pages/MainTabs/index.tsx",
"options": {
"printWidth": 240
}
},
{
"files": "src/App.tsx",
"options": {

View File

@@ -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",

View File

@@ -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": {

View File

@@ -66,6 +66,13 @@ import PrivacyAgreement from './pages/PrivacyAgreement';
import EventDetail from './pages/EventDetail';
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();
@@ -131,13 +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="/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 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"
@@ -147,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>

View File

@@ -15,22 +15,30 @@ 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
PARTY_USER_SIGN_IN: '/partyUserlogin',
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',
@@ -94,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;

View 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

View 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;

View 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;

View 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 / 無線LANWi-Fi規格アダプタ内蔵PC
・WindowsXP以上、Mac OSX以上のOSを搭載したPC
・無線LANWi-Fi規格対応のiOS機器iPhone・iPad等
・無線LANWi-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;

View 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;

View 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 / 無線LANWi-Fi規格アダプタ内蔵PC</p>
<p>・WindowsXP以上、Mac OSX以上のOSを搭載したPC</p>
<p>・無線LANWi-Fi規格対応のiOS機器iPhone・iPad等</p>
<p>・無線LANWi-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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

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