feat: add new CarousellMe routes for OffersMade and MyProfile pages with corresponding API integrations and UI components
This commit is contained in:
@@ -108,6 +108,8 @@ const PATHS = {
|
|||||||
CAROUSELL_ME_QR: '/tabs/carousell_me/qr_page',
|
CAROUSELL_ME_QR: '/tabs/carousell_me/qr_page',
|
||||||
CAROUSELL_ME_SETTINGS: '/carousell_me/settings',
|
CAROUSELL_ME_SETTINGS: '/carousell_me/settings',
|
||||||
CAROUSELL_ME_INSIGHTS: '/tabs/carousell_me/insights',
|
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_WELCOME_TOUR: '/hotel_service_intro',
|
||||||
HOTEL_INTRO: '/tabs/hotel_intro',
|
HOTEL_INTRO: '/tabs/hotel_intro',
|
||||||
|
49
03_source/mobile/src/api/getCategory.tsx
Normal file
49
03_source/mobile/src/api/getCategory.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import getTestSvg from './getTestSvg';
|
||||||
|
|
||||||
|
async function getCategory(): Promise<any> {
|
||||||
|
let list = [
|
||||||
|
{ name: 'Following', avatar: '' },
|
||||||
|
{ name: 'Computers & Tech', avatar: '' },
|
||||||
|
{ name: "Women's Fashion", avatar: '' },
|
||||||
|
{ name: "Men's Fashion", avatar: '' },
|
||||||
|
{ name: 'Beauty & Personal Care', avatar: '' },
|
||||||
|
{ name: 'Free Items', avatar: '' },
|
||||||
|
{ name: 'Audio', avatar: '' },
|
||||||
|
{ name: 'Furniture & Home Living', avatar: '' },
|
||||||
|
{ name: 'Babies & Kids', avatar: '' },
|
||||||
|
{ name: 'Health & Nutrition', avatar: '' },
|
||||||
|
{ name: 'Food & Drinks', avatar: '' },
|
||||||
|
{ name: 'Tickets & Vouchers', avatar: '' },
|
||||||
|
{ name: 'Auto Accessories', avatar: '' },
|
||||||
|
{ name: 'Community', avatar: '' },
|
||||||
|
{ name: 'Looking For', avatar: '' },
|
||||||
|
{ name: 'Announcements', avatar: '' },
|
||||||
|
{ name: 'Services', avatar: '' },
|
||||||
|
{ name: 'Mobile Phones & Gadgets', avatar: '' },
|
||||||
|
{ name: 'Property', avatar: '' },
|
||||||
|
{ name: 'Cars', avatar: '' },
|
||||||
|
{ name: 'Luxury', avatar: '' },
|
||||||
|
{ name: 'Video Gaming', avatar: '' },
|
||||||
|
{ name: 'Photography', avatar: '' },
|
||||||
|
{ name: 'TV & Home Appliances', avatar: '' },
|
||||||
|
{ name: 'Hobbies & Toys', avatar: '' },
|
||||||
|
{ name: 'Sports', avatar: '' },
|
||||||
|
{ name: 'Equipment', avatar: '' },
|
||||||
|
{ name: 'Pet Supplies', avatar: '' },
|
||||||
|
{ name: 'Motorbikes', avatar: '' },
|
||||||
|
{ name: 'Jobs', avatar: '' },
|
||||||
|
{ name: 'Preorders', avatar: '' },
|
||||||
|
{ name: 'Everything Else', avatar: '' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
list[i].avatar = await getTestSvg();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res(list);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getCategory;
|
76
03_source/mobile/src/api/getFreshFinds.tsx
Normal file
76
03_source/mobile/src/api/getFreshFinds.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import getUnsplashRandomImage from './getUnsplashRandomImage';
|
||||||
|
|
||||||
|
const ContentMd = `幫個忙,睇埋落去先啦....
|
||||||
|
|
||||||
|
🈺 視乎難度收費,或者你比個 budget plan 諗下都得
|
||||||
|
Charge subject to difficulties with the task(s), or bring your budget plan to me...
|
||||||
|
|
||||||
|
💁♂️ 服務內容 :
|
||||||
|
- 代寫程式 ... ( html/javascript/python coding commission)
|
||||||
|
- 簡易 個人 / 公司網站 / 靜態網站 製作 (static/pwa webpage)
|
||||||
|
- 網店 / 網購平台製作, 網址 / 域名註冊,網站發佈 (deploy)
|
||||||
|
- 手機應用程式 (expo/ionic/android)
|
||||||
|
- 任何自定義 / 量身定做方案, 其他 IT 相關解決方案 (solution / AI)
|
||||||
|
- Wordpress 公司/個人網頁
|
||||||
|
- 網上資料搜集 scraping/harvesting/crawing
|
||||||
|
- source code 想知點解咁寫開聲問,唔明講到你明
|
||||||
|
- 補習 / 指導
|
||||||
|
- 如需補習/備課的話,請給我看看所需要課堂資料或者問題,
|
||||||
|
- 我對你個人資料沒有興趣,你大可以將個人資料屏蔽先 send 比我
|
||||||
|
- 世界這麼大,我只是想知道你遇到什麽問題,謝謝
|
||||||
|
- 寫bot (請先 PM 我, 要睇睇目的先答做唔做.... book場/搶飛 唔洗問 😊)
|
||||||
|
|
||||||
|
💬 吹水閒偈/諮詢攞下意見免費 😊 (睇心情 / 難度答 😂)
|
||||||
|
Please DM / PM consultation for free ( But no guaranteed answer... depends... )
|
||||||
|
|
||||||
|
👋 髒話說在前面 ( Salutations being said in front ) :
|
||||||
|
- 一般黎講,我會維持對客人應有嘅禮貌同埋尊重 (a.k.a. 互相尊重, implicatively 講左D乜請自己諗... )
|
||||||
|
- 同一時間我嘅禮貎都只會展示俾對我有禮貌嘅人睇。
|
||||||
|
- 我就是我本人,不是中介,由對接到做嘅都係我,有懷疑者不用找我 👋 👋 。
|
||||||
|
- 我唔係你肚入面條蟲,我唔會知你心入面有乜 requirement ... 最簡單係叫你比份 requirements/pdf 我,如果你覺得比個原始 file 我係好難接受嘅話(前題係當然你可以預先 blur 敏感資料),唔使搵我👋 👋
|
||||||
|
|
||||||
|
多謝你睇到喱度,我知我好長氣,若然仲有興趣搵我做野的話,
|
||||||
|
第一句同我講 “Hi, 寶達邨的豬~”,我會講整個購物體驗應該會對你有利 😊...
|
||||||
|
完...
|
||||||
|
|
||||||
|
🍖 Some demo:
|
||||||
|
- some site demos:
|
||||||
|
https://louiscklaw.github.io/work
|
||||||
|
|
||||||
|
若果想做 opencv/machine learning 野,可到此 post
|
||||||
|
https://www.carousell.com.hk/p/1338018892/
|
||||||
|
|
||||||
|
🔖 Tags:
|
||||||
|
#reactjs #nodejs #nextjs #typescript #programming #python #html #css #coding #vue #expo #frontend #backend #laravel #github #bot #vba #docker #opencv #mobile-app #LLM #GPT #huggingface #llama #ollama #debug #figma #ICT #opensource #processing #flow-field #網站 #爬蟲 #scraping #RPA #ABAP #FYP #STEM #project #tkinter #shopping-cart #網頁製作 #公司網站 #網店 #整網頁 #一對一教學 #私補 #私教 #補習 #教材 #代編程 #定制程序代寫 #internship #intern #colab #jupyter #raspberry-pi #arduino #openai-gym #gymnasium #app-inventor #microbit #團購 #賭波 #賭馬 #股票 #六合彩 #港股工具 #股票工具 #求助 #28car #智慧轉型
|
||||||
|
|
||||||
|
#switch2 #switch-2 #adidas #airpods-pro-2 #babymonster #celine #chanel #chiikawa #coach #crybaby #dear jane #dior #fujifilm #fujifilm #goyard #hermes #hermes #ipad #iphone #jellycat #labubu #loewe #longchamp #lululemon #lululemon #lv #macbook #minecraft #pokemon #prada #ps4 #ps5 #rolex #samsung #sony #lego #metal-build #yoga #hottoys #riize #roblox #part-time #街馬-2025 #Blackpink演唱會 #淘佳佳 #i-am-gloria
|
||||||
|
|
||||||
|
# updated: 2025-06-16
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const userJson = {
|
||||||
|
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||||
|
name: 'louis_coding',
|
||||||
|
since: 'Joined 4 years ago',
|
||||||
|
verified: true,
|
||||||
|
rating: 5.0,
|
||||||
|
total_comment: 37,
|
||||||
|
};
|
||||||
|
|
||||||
|
const productSample = {
|
||||||
|
category: { main: 'Services', sub_cat: ['Learning & Enrichment', 'Enrichment & Tuition'] },
|
||||||
|
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||||
|
title: '#html #css #開發 #指導 ',
|
||||||
|
price: 30,
|
||||||
|
content: ContentMd,
|
||||||
|
user: userJson,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getFreshFinds(): Promise<any> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res([productSample, productSample, productSample, productSample, productSample, productSample]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getFreshFinds;
|
76
03_source/mobile/src/api/getProductList.tsx
Normal file
76
03_source/mobile/src/api/getProductList.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import getUnsplashRandomImage from './getUnsplashRandomImage';
|
||||||
|
|
||||||
|
const ContentMd = `幫個忙,睇埋落去先啦....
|
||||||
|
|
||||||
|
🈺 視乎難度收費,或者你比個 budget plan 諗下都得
|
||||||
|
Charge subject to difficulties with the task(s), or bring your budget plan to me...
|
||||||
|
|
||||||
|
💁♂️ 服務內容 :
|
||||||
|
- 代寫程式 ... ( html/javascript/python coding commission)
|
||||||
|
- 簡易 個人 / 公司網站 / 靜態網站 製作 (static/pwa webpage)
|
||||||
|
- 網店 / 網購平台製作, 網址 / 域名註冊,網站發佈 (deploy)
|
||||||
|
- 手機應用程式 (expo/ionic/android)
|
||||||
|
- 任何自定義 / 量身定做方案, 其他 IT 相關解決方案 (solution / AI)
|
||||||
|
- Wordpress 公司/個人網頁
|
||||||
|
- 網上資料搜集 scraping/harvesting/crawing
|
||||||
|
- source code 想知點解咁寫開聲問,唔明講到你明
|
||||||
|
- 補習 / 指導
|
||||||
|
- 如需補習/備課的話,請給我看看所需要課堂資料或者問題,
|
||||||
|
- 我對你個人資料沒有興趣,你大可以將個人資料屏蔽先 send 比我
|
||||||
|
- 世界這麼大,我只是想知道你遇到什麽問題,謝謝
|
||||||
|
- 寫bot (請先 PM 我, 要睇睇目的先答做唔做.... book場/搶飛 唔洗問 😊)
|
||||||
|
|
||||||
|
💬 吹水閒偈/諮詢攞下意見免費 😊 (睇心情 / 難度答 😂)
|
||||||
|
Please DM / PM consultation for free ( But no guaranteed answer... depends... )
|
||||||
|
|
||||||
|
👋 髒話說在前面 ( Salutations being said in front ) :
|
||||||
|
- 一般黎講,我會維持對客人應有嘅禮貌同埋尊重 (a.k.a. 互相尊重, implicatively 講左D乜請自己諗... )
|
||||||
|
- 同一時間我嘅禮貎都只會展示俾對我有禮貌嘅人睇。
|
||||||
|
- 我就是我本人,不是中介,由對接到做嘅都係我,有懷疑者不用找我 👋 👋 。
|
||||||
|
- 我唔係你肚入面條蟲,我唔會知你心入面有乜 requirement ... 最簡單係叫你比份 requirements/pdf 我,如果你覺得比個原始 file 我係好難接受嘅話(前題係當然你可以預先 blur 敏感資料),唔使搵我👋 👋
|
||||||
|
|
||||||
|
多謝你睇到喱度,我知我好長氣,若然仲有興趣搵我做野的話,
|
||||||
|
第一句同我講 “Hi, 寶達邨的豬~”,我會講整個購物體驗應該會對你有利 😊...
|
||||||
|
完...
|
||||||
|
|
||||||
|
🍖 Some demo:
|
||||||
|
- some site demos:
|
||||||
|
https://louiscklaw.github.io/work
|
||||||
|
|
||||||
|
若果想做 opencv/machine learning 野,可到此 post
|
||||||
|
https://www.carousell.com.hk/p/1338018892/
|
||||||
|
|
||||||
|
🔖 Tags:
|
||||||
|
#reactjs #nodejs #nextjs #typescript #programming #python #html #css #coding #vue #expo #frontend #backend #laravel #github #bot #vba #docker #opencv #mobile-app #LLM #GPT #huggingface #llama #ollama #debug #figma #ICT #opensource #processing #flow-field #網站 #爬蟲 #scraping #RPA #ABAP #FYP #STEM #project #tkinter #shopping-cart #網頁製作 #公司網站 #網店 #整網頁 #一對一教學 #私補 #私教 #補習 #教材 #代編程 #定制程序代寫 #internship #intern #colab #jupyter #raspberry-pi #arduino #openai-gym #gymnasium #app-inventor #microbit #團購 #賭波 #賭馬 #股票 #六合彩 #港股工具 #股票工具 #求助 #28car #智慧轉型
|
||||||
|
|
||||||
|
#switch2 #switch-2 #adidas #airpods-pro-2 #babymonster #celine #chanel #chiikawa #coach #crybaby #dear jane #dior #fujifilm #fujifilm #goyard #hermes #hermes #ipad #iphone #jellycat #labubu #loewe #longchamp #lululemon #lululemon #lv #macbook #minecraft #pokemon #prada #ps4 #ps5 #rolex #samsung #sony #lego #metal-build #yoga #hottoys #riize #roblox #part-time #街馬-2025 #Blackpink演唱會 #淘佳佳 #i-am-gloria
|
||||||
|
|
||||||
|
# updated: 2025-06-16
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const userJson = {
|
||||||
|
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||||
|
name: 'louis_coding',
|
||||||
|
since: 'Joined 4 years ago',
|
||||||
|
verified: true,
|
||||||
|
rating: 5.0,
|
||||||
|
total_comment: 37,
|
||||||
|
};
|
||||||
|
|
||||||
|
const productSample = {
|
||||||
|
category: { main: 'Services', sub_cat: ['Learning & Enrichment', 'Enrichment & Tuition'] },
|
||||||
|
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||||
|
title: '#html #css #開發 #指導 ',
|
||||||
|
price: 30,
|
||||||
|
content: ContentMd,
|
||||||
|
user: userJson,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getProductList(): Promise<any> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res([productSample, productSample, productSample, productSample, productSample, productSample]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getProductList;
|
14
03_source/mobile/src/api/getSegments.tsx
Normal file
14
03_source/mobile/src/api/getSegments.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function getSegments(): Promise<any> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res([
|
||||||
|
{ name: 'Top picks', slug: 'top-picks' },
|
||||||
|
{ name: 'Free items', slug: 'free-items' },
|
||||||
|
{ name: 'Following', slug: 'following' },
|
||||||
|
{ name: 'Nearby', slug: 'nearby' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSegments;
|
33
03_source/mobile/src/api/getSuggestedCategory.tsx
Normal file
33
03_source/mobile/src/api/getSuggestedCategory.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function getSuggestedCategory(): Promise<string[]> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res([
|
||||||
|
'代做',
|
||||||
|
'cisco',
|
||||||
|
'm1',
|
||||||
|
'pro',
|
||||||
|
'顯示器支架',
|
||||||
|
'kfc',
|
||||||
|
'nas',
|
||||||
|
'la',
|
||||||
|
'prairie',
|
||||||
|
'vr',
|
||||||
|
'代做',
|
||||||
|
'cisco',
|
||||||
|
'm1',
|
||||||
|
'pro',
|
||||||
|
'顯示器支架',
|
||||||
|
'kfc',
|
||||||
|
'nas',
|
||||||
|
'la',
|
||||||
|
'prairie',
|
||||||
|
'vr',
|
||||||
|
'顯示器支架',
|
||||||
|
'kfc',
|
||||||
|
'nas',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getSuggestedCategory;
|
97
03_source/mobile/src/api/getTestSvg.tsx
Normal file
97
03_source/mobile/src/api/getTestSvg.tsx
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import airConditioningCoolingHeatTemperatureSvgrepoComSvg from './svgs/air-conditioning-cooling-heat-temperature-svgrepo-com.svg';
|
||||||
|
import alertAttentionCautionDangerousWarningSvgrepoComSvg from './svgs/alert-attention-caution-dangerous-warning-svgrepo-com.svg';
|
||||||
|
import alertBellCallMessageSignSvgrepoComSvg from './svgs/alert-bell-call-message-sign-svgrepo-com.svg';
|
||||||
|
import assistanceBusinessPersonReceptionServiceSvgrepoComSvg from './svgs/assistance-business-person-reception-service-svgrepo-com.svg';
|
||||||
|
import bagBaggageLuggageSuitcaseTravelSvgrepoComSvg from './svgs/bag-baggage-luggage-suitcase-travel-svgrepo-com.svg';
|
||||||
|
import baggageBellhopHotelServiceWaiterSvgrepoComSvg from './svgs/baggage-bellhop-hotel-service-waiter-svgrepo-com.svg';
|
||||||
|
import bagHolidayJourneySuitcaseVacationSvgrepoComSvg from './svgs/bag-holiday-journey-suitcase-vacation-svgrepo-com.svg';
|
||||||
|
import bankingFinancialMoneyPaymentTransactionSvgrepoComSvg from './svgs/banking-financial-money-payment-transaction-svgrepo-com.svg';
|
||||||
|
import barCafeChairClubStoolSvgrepoComSvg from './svgs/bar-cafe-chair-club-stool-svgrepo-com.svg';
|
||||||
|
import bathBathroomCottonTextileTowelSvgrepoComSvg from './svgs/bath-bathroom-cotton-textile-towel-svgrepo-com.svg';
|
||||||
|
import bathroomBathtubBubbleFoamWaterSvgrepoComSvg from './svgs/bathroom-bathtub-bubble-foam-water-svgrepo-com.svg';
|
||||||
|
import bathroomFaucetRoomSinkWashSvgrepoComSvg from './svgs/bathroom-faucet-room-sink-wash-svgrepo-com.svg';
|
||||||
|
import bedFurnitureInteriorPillowRest2SvgrepoComSvg from './svgs/bed-furniture-interior-pillow-rest-2-svgrepo-com.svg';
|
||||||
|
import bedFurnitureInteriorPillowRest3SvgrepoComSvg from './svgs/bed-furniture-interior-pillow-rest-3-svgrepo-com.svg';
|
||||||
|
import bedFurnitureInteriorPillowRestSvgrepoComSvg from './svgs/bed-furniture-interior-pillow-rest-svgrepo-com.svg';
|
||||||
|
import bookCatalogDocumentGuidebookInstructionSvgrepoComSvg from './svgs/book-catalog-document-guidebook-instruction-svgrepo-com.svg';
|
||||||
|
import businessFinanceMoneySavingWalletSvgrepoComSvg from './svgs/business-finance-money-saving-wallet-svgrepo-com.svg';
|
||||||
|
import cafeCardFoodMenuVintageSvgrepoComSvg from './svgs/cafe-card-food-menu-vintage-svgrepo-com.svg';
|
||||||
|
import cafeCupDrinkMugTeaSvgrepoComSvg from './svgs/cafe-cup-drink-mug-tea-svgrepo-com.svg';
|
||||||
|
import calendarDateDayMonthTimeSvgrepoComSvg from './svgs/calendar-date-day-month-time-svgrepo-com.svg';
|
||||||
|
import cappuccinoCoffeeCupDrinkEspresso2SvgrepoComSvg from './svgs/cappuccino-coffee-cup-drink-espresso-2-svgrepo-com.svg';
|
||||||
|
import cappuccinoCoffeeCupDrinkEspressoSvgrepoComSvg from './svgs/cappuccino-coffee-cup-drink-espresso-svgrepo-com.svg';
|
||||||
|
import cardCreditCurrencyFinanceMoneySvgrepoComSvg from './svgs/card-credit-currency-finance-money-svgrepo-com.svg';
|
||||||
|
import cardDoorKeyLockSecuritySvgrepoComSvg from './svgs/card-door-key-lock-security-svgrepo-com.svg';
|
||||||
|
import chairComfortableDecorationGardenTerraceSvgrepoComSvg from './svgs/chair-comfortable-decoration-garden-terrace-svgrepo-com.svg';
|
||||||
|
import cleanClothingLaundryWashingWindSvgrepoComSvg from './svgs/clean-clothing-laundry-washing-wind-svgrepo-com.svg';
|
||||||
|
import comfortableFabricFootwearShoeSlipperSvgrepoComSvg from './svgs/comfortable-fabric-footwear-shoe-slipper-svgrepo-com.svg';
|
||||||
|
import couponEntertainmentEventPaperTicketSvgrepoComSvg from './svgs/coupon-entertainment-event-paper-ticket-svgrepo-com.svg';
|
||||||
|
import doorElevatorEntranceFloorLiftSvgrepoComSvg from './svgs/door-elevator-entrance-floor-lift-svgrepo-com.svg';
|
||||||
|
import doorEnterEntryExitOpenSvgrepoComSvg from './svgs/door-enter-entry-exit-open-svgrepo-com.svg';
|
||||||
|
import doorEntranceHandleKeySecuritySvgrepoComSvg from './svgs/door-entrance-handle-key-security-svgrepo-com.svg';
|
||||||
|
import doorKeyLockRoomSecuritySvgrepoComSvg from './svgs/door-key-lock-room-security-svgrepo-com.svg';
|
||||||
|
import electronicInternetScreenTechnologyTelevisionSvgrepoComSvg from './svgs/electronic-internet-screen-technology-television-svgrepo-com.svg';
|
||||||
|
import holidayHotelJourneyServiceTravel2SvgrepoComSvg from './svgs/holiday-hotel-journey-service-travel-2-svgrepo-com.svg';
|
||||||
|
import holidayHotelJourneyServiceTravelSvgrepoComSvg from './svgs/holiday-hotel-journey-service-travel-svgrepo-com.svg';
|
||||||
|
import holidayHotelMotelSignTravelSvgrepoComSvg from './svgs/holiday-hotel-motel-sign-travel-svgrepo-com.svg';
|
||||||
|
import holidayJourneyLuggageSuitcaseVacation2SvgrepoComSvg from './svgs/holiday-journey-luggage-suitcase-vacation-2-svgrepo-com.svg';
|
||||||
|
import hotelLocationMapPinTravelSvgrepoComSvg from './svgs/hotel-location-map-pin-travel-svgrepo-com.svg';
|
||||||
|
|
||||||
|
const svgList = [
|
||||||
|
airConditioningCoolingHeatTemperatureSvgrepoComSvg,
|
||||||
|
alertAttentionCautionDangerousWarningSvgrepoComSvg,
|
||||||
|
alertBellCallMessageSignSvgrepoComSvg,
|
||||||
|
assistanceBusinessPersonReceptionServiceSvgrepoComSvg,
|
||||||
|
bagBaggageLuggageSuitcaseTravelSvgrepoComSvg,
|
||||||
|
baggageBellhopHotelServiceWaiterSvgrepoComSvg,
|
||||||
|
bagHolidayJourneySuitcaseVacationSvgrepoComSvg,
|
||||||
|
bankingFinancialMoneyPaymentTransactionSvgrepoComSvg,
|
||||||
|
barCafeChairClubStoolSvgrepoComSvg,
|
||||||
|
bathBathroomCottonTextileTowelSvgrepoComSvg,
|
||||||
|
bathroomBathtubBubbleFoamWaterSvgrepoComSvg,
|
||||||
|
bathroomFaucetRoomSinkWashSvgrepoComSvg,
|
||||||
|
bedFurnitureInteriorPillowRest2SvgrepoComSvg,
|
||||||
|
bedFurnitureInteriorPillowRest3SvgrepoComSvg,
|
||||||
|
bedFurnitureInteriorPillowRestSvgrepoComSvg,
|
||||||
|
bookCatalogDocumentGuidebookInstructionSvgrepoComSvg,
|
||||||
|
businessFinanceMoneySavingWalletSvgrepoComSvg,
|
||||||
|
cafeCardFoodMenuVintageSvgrepoComSvg,
|
||||||
|
cafeCupDrinkMugTeaSvgrepoComSvg,
|
||||||
|
calendarDateDayMonthTimeSvgrepoComSvg,
|
||||||
|
cappuccinoCoffeeCupDrinkEspresso2SvgrepoComSvg,
|
||||||
|
cappuccinoCoffeeCupDrinkEspressoSvgrepoComSvg,
|
||||||
|
cardCreditCurrencyFinanceMoneySvgrepoComSvg,
|
||||||
|
cardDoorKeyLockSecuritySvgrepoComSvg,
|
||||||
|
chairComfortableDecorationGardenTerraceSvgrepoComSvg,
|
||||||
|
cleanClothingLaundryWashingWindSvgrepoComSvg,
|
||||||
|
comfortableFabricFootwearShoeSlipperSvgrepoComSvg,
|
||||||
|
couponEntertainmentEventPaperTicketSvgrepoComSvg,
|
||||||
|
doorElevatorEntranceFloorLiftSvgrepoComSvg,
|
||||||
|
doorEnterEntryExitOpenSvgrepoComSvg,
|
||||||
|
doorEntranceHandleKeySecuritySvgrepoComSvg,
|
||||||
|
doorKeyLockRoomSecuritySvgrepoComSvg,
|
||||||
|
electronicInternetScreenTechnologyTelevisionSvgrepoComSvg,
|
||||||
|
holidayHotelJourneyServiceTravel2SvgrepoComSvg,
|
||||||
|
holidayHotelJourneyServiceTravelSvgrepoComSvg,
|
||||||
|
holidayHotelMotelSignTravelSvgrepoComSvg,
|
||||||
|
holidayJourneyLuggageSuitcaseVacation2SvgrepoComSvg,
|
||||||
|
hotelLocationMapPinTravelSvgrepoComSvg,
|
||||||
|
];
|
||||||
|
|
||||||
|
function getRandomInt(max: number) {
|
||||||
|
return Math.floor(Math.random() * max);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTestSvg(): Promise<string> {
|
||||||
|
let { length } = svgList;
|
||||||
|
let randomIdx = getRandomInt(length - 1);
|
||||||
|
|
||||||
|
console.log({ findme: svgList[randomIdx] });
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res(svgList[randomIdx]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getTestSvg;
|
38
03_source/mobile/src/api/getTopPicks.tsx
Normal file
38
03_source/mobile/src/api/getTopPicks.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import getUnsplashRandomImage from './getUnsplashRandomImage';
|
||||||
|
|
||||||
|
const ContentMd = `helloworld `.trim();
|
||||||
|
|
||||||
|
const userJson = {
|
||||||
|
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||||
|
name: 'louis_coding',
|
||||||
|
since: 'Joined 4 years ago',
|
||||||
|
verified: true,
|
||||||
|
rating: 5.0,
|
||||||
|
total_comment: 37,
|
||||||
|
};
|
||||||
|
|
||||||
|
const productSample = {
|
||||||
|
category: { main: 'Services', sub_cat: ['Learning & Enrichment', 'Enrichment & Tuition'] },
|
||||||
|
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||||
|
title: '#html #css #開發 #指導 ',
|
||||||
|
price: 30,
|
||||||
|
content: ContentMd,
|
||||||
|
user: userJson,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getTopPicks(): Promise<any> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res([
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getTopPicks;
|
60
03_source/mobile/src/api/getTopSearches.tsx
Normal file
60
03_source/mobile/src/api/getTopSearches.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function getTopSearches(): Promise<string[]> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res([
|
||||||
|
'blackpink',
|
||||||
|
'ps5',
|
||||||
|
'blackpink 演唱會',
|
||||||
|
'chanel',
|
||||||
|
'iphone',
|
||||||
|
'陳奕迅演唱會',
|
||||||
|
'hermes',
|
||||||
|
'海港城coupon',
|
||||||
|
'利是封',
|
||||||
|
'ipad',
|
||||||
|
'apple watch',
|
||||||
|
'dior',
|
||||||
|
'celine',
|
||||||
|
'迪士尼門票',
|
||||||
|
'lv',
|
||||||
|
'nike',
|
||||||
|
'張敬軒',
|
||||||
|
'gucci',
|
||||||
|
'lego',
|
||||||
|
'ps4',
|
||||||
|
'loewe',
|
||||||
|
'casetify',
|
||||||
|
'單車',
|
||||||
|
'eason 演唱會',
|
||||||
|
'rolex',
|
||||||
|
'slam dunk',
|
||||||
|
'airpods pro',
|
||||||
|
'coach',
|
||||||
|
'電視',
|
||||||
|
'bearbrick',
|
||||||
|
'雪櫃',
|
||||||
|
'mc',
|
||||||
|
'prada',
|
||||||
|
'samsung',
|
||||||
|
'dyson',
|
||||||
|
'burberry',
|
||||||
|
'張敬軒演唱會',
|
||||||
|
'pokemon',
|
||||||
|
'ikea',
|
||||||
|
'linabell',
|
||||||
|
'dunk low',
|
||||||
|
'陳奕迅',
|
||||||
|
'balenciaga',
|
||||||
|
'梳化',
|
||||||
|
'mirror',
|
||||||
|
'sony',
|
||||||
|
'麻雀',
|
||||||
|
'tory burch',
|
||||||
|
'marshall',
|
||||||
|
'行李箱',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getTopSearches;
|
76
03_source/mobile/src/api/getYourDailyPicks.tsx
Normal file
76
03_source/mobile/src/api/getYourDailyPicks.tsx
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import getUnsplashRandomImage from './getUnsplashRandomImage';
|
||||||
|
|
||||||
|
const ContentMd = `幫個忙,睇埋落去先啦....
|
||||||
|
|
||||||
|
🈺 視乎難度收費,或者你比個 budget plan 諗下都得
|
||||||
|
Charge subject to difficulties with the task(s), or bring your budget plan to me...
|
||||||
|
|
||||||
|
💁♂️ 服務內容 :
|
||||||
|
- 代寫程式 ... ( html/javascript/python coding commission)
|
||||||
|
- 簡易 個人 / 公司網站 / 靜態網站 製作 (static/pwa webpage)
|
||||||
|
- 網店 / 網購平台製作, 網址 / 域名註冊,網站發佈 (deploy)
|
||||||
|
- 手機應用程式 (expo/ionic/android)
|
||||||
|
- 任何自定義 / 量身定做方案, 其他 IT 相關解決方案 (solution / AI)
|
||||||
|
- Wordpress 公司/個人網頁
|
||||||
|
- 網上資料搜集 scraping/harvesting/crawing
|
||||||
|
- source code 想知點解咁寫開聲問,唔明講到你明
|
||||||
|
- 補習 / 指導
|
||||||
|
- 如需補習/備課的話,請給我看看所需要課堂資料或者問題,
|
||||||
|
- 我對你個人資料沒有興趣,你大可以將個人資料屏蔽先 send 比我
|
||||||
|
- 世界這麼大,我只是想知道你遇到什麽問題,謝謝
|
||||||
|
- 寫bot (請先 PM 我, 要睇睇目的先答做唔做.... book場/搶飛 唔洗問 😊)
|
||||||
|
|
||||||
|
💬 吹水閒偈/諮詢攞下意見免費 😊 (睇心情 / 難度答 😂)
|
||||||
|
Please DM / PM consultation for free ( But no guaranteed answer... depends... )
|
||||||
|
|
||||||
|
👋 髒話說在前面 ( Salutations being said in front ) :
|
||||||
|
- 一般黎講,我會維持對客人應有嘅禮貌同埋尊重 (a.k.a. 互相尊重, implicatively 講左D乜請自己諗... )
|
||||||
|
- 同一時間我嘅禮貎都只會展示俾對我有禮貌嘅人睇。
|
||||||
|
- 我就是我本人,不是中介,由對接到做嘅都係我,有懷疑者不用找我 👋 👋 。
|
||||||
|
- 我唔係你肚入面條蟲,我唔會知你心入面有乜 requirement ... 最簡單係叫你比份 requirements/pdf 我,如果你覺得比個原始 file 我係好難接受嘅話(前題係當然你可以預先 blur 敏感資料),唔使搵我👋 👋
|
||||||
|
|
||||||
|
多謝你睇到喱度,我知我好長氣,若然仲有興趣搵我做野的話,
|
||||||
|
第一句同我講 “Hi, 寶達邨的豬~”,我會講整個購物體驗應該會對你有利 😊...
|
||||||
|
完...
|
||||||
|
|
||||||
|
🍖 Some demo:
|
||||||
|
- some site demos:
|
||||||
|
https://louiscklaw.github.io/work
|
||||||
|
|
||||||
|
若果想做 opencv/machine learning 野,可到此 post
|
||||||
|
https://www.carousell.com.hk/p/1338018892/
|
||||||
|
|
||||||
|
🔖 Tags:
|
||||||
|
#reactjs #nodejs #nextjs #typescript #programming #python #html #css #coding #vue #expo #frontend #backend #laravel #github #bot #vba #docker #opencv #mobile-app #LLM #GPT #huggingface #llama #ollama #debug #figma #ICT #opensource #processing #flow-field #網站 #爬蟲 #scraping #RPA #ABAP #FYP #STEM #project #tkinter #shopping-cart #網頁製作 #公司網站 #網店 #整網頁 #一對一教學 #私補 #私教 #補習 #教材 #代編程 #定制程序代寫 #internship #intern #colab #jupyter #raspberry-pi #arduino #openai-gym #gymnasium #app-inventor #microbit #團購 #賭波 #賭馬 #股票 #六合彩 #港股工具 #股票工具 #求助 #28car #智慧轉型
|
||||||
|
|
||||||
|
#switch2 #switch-2 #adidas #airpods-pro-2 #babymonster #celine #chanel #chiikawa #coach #crybaby #dear jane #dior #fujifilm #fujifilm #goyard #hermes #hermes #ipad #iphone #jellycat #labubu #loewe #longchamp #lululemon #lululemon #lv #macbook #minecraft #pokemon #prada #ps4 #ps5 #rolex #samsung #sony #lego #metal-build #yoga #hottoys #riize #roblox #part-time #街馬-2025 #Blackpink演唱會 #淘佳佳 #i-am-gloria
|
||||||
|
|
||||||
|
# updated: 2025-06-16
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
const userJson = {
|
||||||
|
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||||
|
name: 'louis_coding',
|
||||||
|
since: 'Joined 4 years ago',
|
||||||
|
verified: true,
|
||||||
|
rating: 5.0,
|
||||||
|
total_comment: 37,
|
||||||
|
};
|
||||||
|
|
||||||
|
const productSample = {
|
||||||
|
category: { main: 'Services', sub_cat: ['Learning & Enrichment', 'Enrichment & Tuition'] },
|
||||||
|
avatar: getUnsplashRandomImage({ keyword: 'hotel' }),
|
||||||
|
title: '#html #css #開發 #指導 ',
|
||||||
|
price: 30,
|
||||||
|
content: ContentMd,
|
||||||
|
user: userJson,
|
||||||
|
};
|
||||||
|
|
||||||
|
function getYourDailyPicks(): Promise<any> {
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
res([productSample, productSample, productSample, productSample, productSample, productSample]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default getYourDailyPicks;
|
71
03_source/mobile/src/components/ProductCard/index.tsx
Normal file
71
03_source/mobile/src/components/ProductCard/index.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IonHeader,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
IonContent,
|
||||||
|
IonPage,
|
||||||
|
IonButtons,
|
||||||
|
IonBadge,
|
||||||
|
IonMenuButton,
|
||||||
|
IonButton,
|
||||||
|
IonIcon,
|
||||||
|
IonDatetime,
|
||||||
|
IonSelectOption,
|
||||||
|
IonList,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonSelect,
|
||||||
|
IonPopover,
|
||||||
|
IonText,
|
||||||
|
IonCard,
|
||||||
|
IonCardHeader,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCardContent,
|
||||||
|
IonTabs,
|
||||||
|
IonTabBar,
|
||||||
|
IonTabButton,
|
||||||
|
IonSegment,
|
||||||
|
IonSegmentButton,
|
||||||
|
useIonRouter,
|
||||||
|
IonSearchbar,
|
||||||
|
IonRow,
|
||||||
|
IonCol,
|
||||||
|
} from '@ionic/react';
|
||||||
|
|
||||||
|
// const ProductCard: React.FC = ({product}) =>
|
||||||
|
function ProductCard({ product }: { product: any }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonCard
|
||||||
|
style={{ width: '45%', minWidth: '120px' }}
|
||||||
|
button
|
||||||
|
onClick={(e) => console.log('helloworld')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundImage: `url("${product.avatar}")`,
|
||||||
|
height: '100px',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<IonCardHeader style={{ padding: '0.5rem' }}>
|
||||||
|
<IonCardTitle style={{ fontSize: '0.8rem' }}>
|
||||||
|
{'#html #css #開發 #指導 #代做 #電子平台 #programming #coding #javascript #python #react #debug'
|
||||||
|
.split('')
|
||||||
|
.slice(0, 20)}
|
||||||
|
</IonCardTitle>
|
||||||
|
<IonCardSubtitle>Card Subtitle</IonCardSubtitle>
|
||||||
|
</IonCardHeader>
|
||||||
|
<IonCardContent>View Insights</IonCardContent>
|
||||||
|
</IonCard>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProductCard;
|
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
IonCard,
|
||||||
|
IonCardHeader,
|
||||||
|
IonCardTitle,
|
||||||
|
IonCardSubtitle,
|
||||||
|
IonCardContent,
|
||||||
|
} from '@ionic/react';
|
||||||
|
|
||||||
|
// const ProductCard: React.FC = ({product}) =>
|
||||||
|
function TopPicksProductCard({ product }: { product: any }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonCard
|
||||||
|
style={{
|
||||||
|
marginInline: 0,
|
||||||
|
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
button
|
||||||
|
onClick={(e) => console.log('helloworld')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
backgroundImage: `url("${product.avatar}")`,
|
||||||
|
height: '100px',
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<IonCardHeader style={{ padding: '0.1rem' }}>
|
||||||
|
<IonCardTitle style={{ fontSize: '0.8rem' }}>
|
||||||
|
{'#html #css #開發 #指導 #代做 #電子平台 #programming #coding #javascript #python #react #debug'
|
||||||
|
.split('')
|
||||||
|
.slice(0, 20)}
|
||||||
|
</IonCardTitle>
|
||||||
|
<IonCardSubtitle>Card Subtitle</IonCardSubtitle>
|
||||||
|
</IonCardHeader>
|
||||||
|
<IonCardContent>View Insights</IonCardContent>
|
||||||
|
</IonCard>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TopPicksProductCard;
|
8
03_source/mobile/src/pages/Carousell/Home/Following.tsx
Normal file
8
03_source/mobile/src/pages/Carousell/Home/Following.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Following({ hide }: { hide: boolean }) {
|
||||||
|
if (hide) return <></>;
|
||||||
|
return <>Following</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Following;
|
8
03_source/mobile/src/pages/Carousell/Home/FreeItems.tsx
Normal file
8
03_source/mobile/src/pages/Carousell/Home/FreeItems.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function FreeItems({ hide }: { hide: boolean }) {
|
||||||
|
if (hide) return <></>;
|
||||||
|
return <>FreeItems</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FreeItems;
|
8
03_source/mobile/src/pages/Carousell/Home/Nearby.tsx
Normal file
8
03_source/mobile/src/pages/Carousell/Home/Nearby.tsx
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function Nearby({ hide }: { hide: boolean }) {
|
||||||
|
if (hide) return <></>;
|
||||||
|
return <>Nearby</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Nearby;
|
57
03_source/mobile/src/pages/Carousell/Home/TopPicks.tsx
Normal file
57
03_source/mobile/src/pages/Carousell/Home/TopPicks.tsx
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { IonCol, IonRow, IonGrid } from '@ionic/react';
|
||||||
|
import getTopPicks from '../../../api/getTopPicks';
|
||||||
|
import TopPicksProductCard from '../../../components/TopPicksProductCard';
|
||||||
|
import { IonInfiniteScrollCustomEvent } from '@ionic/core';
|
||||||
|
|
||||||
|
function TopPicks({ hide }: { hide: boolean }) {
|
||||||
|
const [items, setItems] = useState<string[]>([]);
|
||||||
|
const [topPicks, setTopPicks] = useState<any>([]);
|
||||||
|
|
||||||
|
const generateItems = () => {
|
||||||
|
const newItems = [];
|
||||||
|
for (let i = 0; i < 50; i++) {
|
||||||
|
newItems.push(`Item ${1 + items.length + i}`);
|
||||||
|
}
|
||||||
|
setItems([...items, ...newItems]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialLoadTopPicks = () => {
|
||||||
|
getTopPicks().then((res: any) => setTopPicks(res));
|
||||||
|
};
|
||||||
|
const loadTopPicksOnScroll = (ev: IonInfiniteScrollCustomEvent<void>) => {
|
||||||
|
getTopPicks()
|
||||||
|
.then((res: any) => setTopPicks([...topPicks, ...res]))
|
||||||
|
.then(() => {
|
||||||
|
ev.target.complete();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
generateItems();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initialLoadTopPicks();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (hide) return <></>;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<IonGrid>
|
||||||
|
<IonRow class="ion-justify-content-start">
|
||||||
|
{topPicks.map((product: any) => (
|
||||||
|
<IonCol size="6">
|
||||||
|
<TopPicksProductCard product={product} />
|
||||||
|
</IonCol>
|
||||||
|
))}
|
||||||
|
</IonRow>
|
||||||
|
</IonGrid>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TopPicks;
|
429
03_source/mobile/src/pages/Carousell/Home/index.tsx
Normal file
429
03_source/mobile/src/pages/Carousell/Home/index.tsx
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
import {
|
||||||
|
createGesture,
|
||||||
|
IonBackButton,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonChip,
|
||||||
|
IonCol,
|
||||||
|
IonContent,
|
||||||
|
IonGrid,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonItem,
|
||||||
|
IonItemDivider,
|
||||||
|
IonItemGroup,
|
||||||
|
IonLabel,
|
||||||
|
IonList,
|
||||||
|
IonPage,
|
||||||
|
IonPopover,
|
||||||
|
IonRefresher,
|
||||||
|
IonRefresherContent,
|
||||||
|
IonRow,
|
||||||
|
IonSegment,
|
||||||
|
IonSegmentButton,
|
||||||
|
// IonSlide,
|
||||||
|
// IonSlides,
|
||||||
|
IonText,
|
||||||
|
IonToolbar,
|
||||||
|
RefresherEventDetail,
|
||||||
|
IonCard,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
// import { SuperTabsContainer, SuperTab, SuperTabs, SuperTabsToolbar, SuperTabButton } from '@ionic-super-tabs/react';
|
||||||
|
import {
|
||||||
|
star,
|
||||||
|
home,
|
||||||
|
heart,
|
||||||
|
pin,
|
||||||
|
call,
|
||||||
|
globe,
|
||||||
|
basket,
|
||||||
|
barbell,
|
||||||
|
trash,
|
||||||
|
person,
|
||||||
|
} from 'ionicons/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
mailSharp,
|
||||||
|
chevronDownSharp,
|
||||||
|
chevronForwardSharp,
|
||||||
|
arrowBackSharp,
|
||||||
|
starOutline,
|
||||||
|
share,
|
||||||
|
chevronDownCircleOutline,
|
||||||
|
chevronForwardOutline,
|
||||||
|
chatbubblesOutline,
|
||||||
|
menuOutline,
|
||||||
|
} from 'ionicons/icons';
|
||||||
|
|
||||||
|
import ProductCard from '../../../components/ProductCard';
|
||||||
|
//
|
||||||
|
import getSuggestedCategory from '../../../api/getSuggestedCategory';
|
||||||
|
import getProductList from '../../../api/getProductList';
|
||||||
|
import getTopSearches from '../../../api/getTopSearches';
|
||||||
|
import getCategory from '../../../api/getCategory';
|
||||||
|
import getYourDailyPicks from '../../../api/getYourDailyPicks';
|
||||||
|
import getFreshFinds from '../../../api/getFreshFinds';
|
||||||
|
import getSegments from '../../../api/getSegments';
|
||||||
|
import TopPicks from './TopPicks';
|
||||||
|
import Following from './Following';
|
||||||
|
import FreeItems from './FreeItems';
|
||||||
|
import Nearby from './Nearby';
|
||||||
|
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
interface CarousellHome {}
|
||||||
|
|
||||||
|
const Card = () => (
|
||||||
|
<>
|
||||||
|
<IonCard>HelloCard</IonCard>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
const CarousellHome: React.FC<CarousellHome> = () => {
|
||||||
|
const [showPopover, setShowPopover] = useState(false);
|
||||||
|
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||||
|
const [location, setLocation] = useState<'madison' | 'austin' | 'chicago' | 'seattle'>('madison');
|
||||||
|
const [conferenceDate, setConferenceDate] = useState('2047-05-17T00:00:00-05:00');
|
||||||
|
|
||||||
|
const pageRef = useRef();
|
||||||
|
|
||||||
|
const handleRefresh = (event: CustomEvent<RefresherEventDetail>) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// Any calls to load data go here
|
||||||
|
event.detail.complete();
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [suggestedCategories, setSuggestedCategories] = useState<string[]>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
getSuggestedCategory().then((res: any) => setSuggestedCategories(res));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [categories, setCategories] = useState<any>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
getCategory().then((res: any) => setCategories(res));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [productList, setProductList] = useState<any>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
getProductList().then((res: any) => setProductList(res));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [yourDailyPicks, setYourDailyPicks] = useState<any>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
getYourDailyPicks().then((res: any) => setYourDailyPicks(res));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [freshFinds, setFreshFinds] = useState<any>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
getFreshFinds().then((res: any) => setFreshFinds(res));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [topSearches, setTopSearches] = useState<any>([]);
|
||||||
|
useEffect(() => {
|
||||||
|
getTopSearches().then((res: any) => setTopSearches(res));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [categorySegments, setCategorySegments] = useState<any>([]);
|
||||||
|
const [segment, setSegment] = useState<string>('');
|
||||||
|
useEffect(() => {
|
||||||
|
getSegments().then((res: any) => {
|
||||||
|
setCategorySegments(res);
|
||||||
|
setSegment(res[0].slug);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMove = (detail: any) => {
|
||||||
|
console.log({ detail });
|
||||||
|
};
|
||||||
|
const segmentRef = useRef<HTMLDivElement>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (segmentRef?.current) {
|
||||||
|
const gesture = createGesture({
|
||||||
|
gestureName: 'helloworld',
|
||||||
|
el: segmentRef.current,
|
||||||
|
onMove: (detail) => {
|
||||||
|
onMove(detail);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
gesture.enable();
|
||||||
|
}
|
||||||
|
}, [segmentRef]);
|
||||||
|
|
||||||
|
// const slider = useRef<HTMLIonSlidesElement>(null);
|
||||||
|
const [value, setValue] = useState('0');
|
||||||
|
const slideOpts = {
|
||||||
|
initialSlide: 0,
|
||||||
|
speed: 400,
|
||||||
|
loop: false,
|
||||||
|
pagination: {
|
||||||
|
el: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const handleSlideChange = async (event: any) => {
|
||||||
|
let index: number = 0;
|
||||||
|
await event.target.getActiveIndex().then((value: any) => {
|
||||||
|
index = value;
|
||||||
|
});
|
||||||
|
setSegment(categorySegments[index].slug);
|
||||||
|
setValue('' + index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage id="carousell-home-page" style={{ marginBottom: '3rem' }}>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">CarousellLogo</IonButtons>
|
||||||
|
<IonButtons slot="end">
|
||||||
|
<IonButton onClick={() => {}}>
|
||||||
|
<IonIcon slot="icon-only" icon={chatbubblesOutline}></IonIcon>
|
||||||
|
</IonButton>
|
||||||
|
<IonButton onClick={() => {}}>
|
||||||
|
<IonIcon slot="icon-only" icon={menuOutline}></IonIcon>
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent>
|
||||||
|
<div style={{}}>
|
||||||
|
<div style={{ margin: '0.5rem 0.5rem' }}>
|
||||||
|
<h5>一個網上申請 多個貸款報價任您揀!</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ margin: '0.5rem 0.5rem' }}>
|
||||||
|
<div style={{ height: '75px', width: '100%', backgroundColor: 'tomato' }}>
|
||||||
|
top button placeholder
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ margin: '0.5rem 0.5rem' }}>
|
||||||
|
<div style={{ height: '150px', width: '100%', backgroundColor: 'tomato' }}>
|
||||||
|
slider placeholder
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ margin: '0rem 0.5rem' }}>
|
||||||
|
<IonList>
|
||||||
|
<IonItemGroup>
|
||||||
|
<IonItemDivider style={{ padding: 0, margin: 0, border: 'none', color: 'black' }}>
|
||||||
|
<h6>Looks like your kinda thing</h6>
|
||||||
|
</IonItemDivider>
|
||||||
|
|
||||||
|
<div style={{ overflowX: 'scroll', width: '100%' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
margin: '0.5rem 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{suggestedCategories.map((category: any, index: number) => (
|
||||||
|
<div key={index}>
|
||||||
|
<IonChip outline>{category}</IonChip>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</IonItemGroup>
|
||||||
|
|
||||||
|
<IonItemGroup>
|
||||||
|
<IonItemDivider style={{ padding: 0, margin: 0, border: 'none', color: 'black' }}>
|
||||||
|
<h6>Explore Carousell</h6>
|
||||||
|
</IonItemDivider>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flexStart' }}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: '180px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '5px',
|
||||||
|
overflowX: 'scroll',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{categories.map((category: any, index: number) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
width: '80px',
|
||||||
|
minHeight: '80px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '50px',
|
||||||
|
height: '50px',
|
||||||
|
borderRadius: '25px',
|
||||||
|
backgroundColor: 'gold',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
boxShadow: 'rgba(0, 0, 0, 0.35) 0px 5px 15px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '30px',
|
||||||
|
height: '30px',
|
||||||
|
backgroundImage: `url("${category.avatar}")`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: '0.5rem', fontSize: '0.6rem', textAlign: 'center' }}>
|
||||||
|
{category.name || ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ overflowX: 'scroll', padding: '1rem 0rem' }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row', whiteSpace: 'nowrap' }}></div>
|
||||||
|
</div>
|
||||||
|
</IonItemGroup>
|
||||||
|
|
||||||
|
<IonItemGroup>
|
||||||
|
<IonItemDivider
|
||||||
|
sticky
|
||||||
|
style={{ padding: 0, margin: 0, border: 'none', color: 'black' }}
|
||||||
|
>
|
||||||
|
<h6>Your Daily Picks</h6>
|
||||||
|
</IonItemDivider>
|
||||||
|
|
||||||
|
<div style={{ width: '100%', overflowX: 'scroll' }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
{yourDailyPicks.map((product: any, index: number) => (
|
||||||
|
<ProductCard key={index} product={product} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</IonItemGroup>
|
||||||
|
|
||||||
|
<IonItemGroup>
|
||||||
|
<IonItemDivider
|
||||||
|
sticky
|
||||||
|
style={{ padding: 0, margin: 0, border: 'none', color: 'black' }}
|
||||||
|
>
|
||||||
|
<h6>Fresh Finds</h6>
|
||||||
|
</IonItemDivider>
|
||||||
|
|
||||||
|
<div style={{ width: '100%', overflowX: 'scroll' }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
{freshFinds.map((product: any, index: number) => (
|
||||||
|
<ProductCard key={index} product={product} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</IonItemGroup>
|
||||||
|
|
||||||
|
<IonItemGroup>
|
||||||
|
<IonItemDivider
|
||||||
|
sticky
|
||||||
|
style={{ padding: 0, margin: 0, border: 'none', color: 'black' }}
|
||||||
|
>
|
||||||
|
<h6>Top searches</h6>
|
||||||
|
</IonItemDivider>
|
||||||
|
|
||||||
|
<div style={{ width: '100%', overflowX: 'scroll' }}>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||||
|
{freshFinds.map((product: any, index: number) => (
|
||||||
|
<ProductCard key={index} product={product} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</IonItemGroup>
|
||||||
|
|
||||||
|
<IonItemGroup>
|
||||||
|
<IonItemDivider
|
||||||
|
sticky
|
||||||
|
style={{ padding: 0, margin: 0, border: 'none', color: 'black' }}
|
||||||
|
>
|
||||||
|
<IonSegment
|
||||||
|
value={segment}
|
||||||
|
scrollable
|
||||||
|
onIonChange={(e) => {
|
||||||
|
console.log(e.target);
|
||||||
|
setSegment(e.detail.value as any);
|
||||||
|
}}
|
||||||
|
selectOnFocus
|
||||||
|
>
|
||||||
|
{categorySegments.map((seg: any, index: number) => (
|
||||||
|
<IonSegmentButton key={index} value={seg.slug}>
|
||||||
|
<IonLabel>{seg.name}</IonLabel>
|
||||||
|
</IonSegmentButton>
|
||||||
|
))}
|
||||||
|
</IonSegment>
|
||||||
|
</IonItemDivider>
|
||||||
|
|
||||||
|
<IonItem id="top-picks">ion slide should be here</IonItem>
|
||||||
|
</IonItemGroup>
|
||||||
|
</IonList>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<IonText>
|
||||||
|
<h6>Follow Us</h6>
|
||||||
|
</IonText>
|
||||||
|
<div>2023 louiscklaw</div>
|
||||||
|
|
||||||
|
<div>Help Centre</div>
|
||||||
|
<div>Contact Us</div>
|
||||||
|
<div>Press</div>
|
||||||
|
<div>Jobs</div>
|
||||||
|
<div>Advertise with Us</div>
|
||||||
|
<div>Terms</div>
|
||||||
|
<div>Privacy</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div>English</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ fontSize: '0.5rem' }}>
|
||||||
|
Explore Carousell Following Computers & Tech Women's Fashion Men's Fashion Beauty &
|
||||||
|
Personal Care Free Items Audio Furniture & Home Living Babies & Kids Health & Nutrition
|
||||||
|
Food & Drinks Tickets & Vouchers Auto Accessories Community Looking For Announcements
|
||||||
|
Services Mobile Phones & Gadgets Property Cars Luxury Video Gaming Photography TV & Home
|
||||||
|
Appliances Hobbies & Toys Sports Equipment Pet Supplies Motorbikes Jobs Preorders
|
||||||
|
Everything Else
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<IonRefresher slot="fixed" onIonRefresh={handleRefresh}>
|
||||||
|
<IonRefresherContent
|
||||||
|
pullingIcon={chevronDownCircleOutline}
|
||||||
|
pullingText="Pull to refresh"
|
||||||
|
refreshingSpinner="circles"
|
||||||
|
refreshingText="Refreshing..."
|
||||||
|
></IonRefresherContent>
|
||||||
|
</IonRefresher>
|
||||||
|
<div style={{}}>CarousellHome</div>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(CarousellHome);
|
32
03_source/mobile/src/pages/Carousell/Home/style.scss
Normal file
32
03_source/mobile/src/pages/Carousell/Home/style.scss
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#carousell-home-page {
|
||||||
|
::-webkit-scrollbar,
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-slides {
|
||||||
|
--offset-bottom: auto !important;
|
||||||
|
--overflow: hidden;
|
||||||
|
overflow: auto;
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-pick-product-list {
|
||||||
|
ion-card {
|
||||||
|
--margin-inline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#top-picks {
|
||||||
|
--padding-start: 0;
|
||||||
|
--inner-padding-start: 0;
|
||||||
|
--padding-end: 0;
|
||||||
|
--inner-padding-end: 0;
|
||||||
|
|
||||||
|
ion-item {
|
||||||
|
--background: gold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
410
03_source/mobile/src/pages/Carousell/ProductPage/index.tsx
Normal file
410
03_source/mobile/src/pages/Carousell/ProductPage/index.tsx
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
import {
|
||||||
|
IonBackButton,
|
||||||
|
IonBreadcrumb,
|
||||||
|
IonBreadcrumbs,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonChip,
|
||||||
|
IonContent,
|
||||||
|
IonIcon,
|
||||||
|
IonPage,
|
||||||
|
IonText,
|
||||||
|
IonToolbar,
|
||||||
|
ScrollDetail,
|
||||||
|
useIonRouter,
|
||||||
|
useIonViewDidEnter,
|
||||||
|
useIonViewWillEnter,
|
||||||
|
useIonViewWillLeave,
|
||||||
|
IonCard,
|
||||||
|
} from '@ionic/react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
heartOutline,
|
||||||
|
shareOutline,
|
||||||
|
flagOutline,
|
||||||
|
timerOutline,
|
||||||
|
pricetagOutline,
|
||||||
|
} from 'ionicons/icons';
|
||||||
|
|
||||||
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
// import AboutPopover from '../../components/AboutPopover';
|
||||||
|
import { useIonAlert } from '@ionic/react';
|
||||||
|
import StarRatings from 'react-star-ratings';
|
||||||
|
|
||||||
|
import './style.scss';
|
||||||
|
import { AppContext } from '../../../data/AppContext';
|
||||||
|
import { createGesture } from '@ionic/react';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import getUnsplashRandomImage from '../../../api/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,
|
||||||
|
};
|
||||||
|
|
||||||
|
const commentSample = {
|
||||||
|
user: userJson,
|
||||||
|
content: '準時有禮',
|
||||||
|
product: productSample,
|
||||||
|
time: '5 days',
|
||||||
|
properties: 'Review from seller',
|
||||||
|
};
|
||||||
|
|
||||||
|
const commentList = [commentSample, commentSample, commentSample, commentSample, commentSample];
|
||||||
|
const productList = [
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
productSample,
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ProductPageProps {}
|
||||||
|
|
||||||
|
const ProductPage: React.FC<ProductPageProps> = () => {
|
||||||
|
const [showPopover, setShowPopover] = useState(false);
|
||||||
|
const [popoverEvent, setPopoverEvent] = useState<MouseEvent>();
|
||||||
|
const [location, setLocation] = useState<'madison' | 'austin' | 'chicago' | 'seattle'>('madison');
|
||||||
|
const [conferenceDate, setConferenceDate] = useState('2047-05-17T00:00:00-05:00');
|
||||||
|
const [showAlert, hideAlert] = useIonAlert();
|
||||||
|
|
||||||
|
const { browser_store } = useContext(AppContext);
|
||||||
|
|
||||||
|
const refRectangle = useRef<HTMLDivElement>(null);
|
||||||
|
const [swipeType, setSwipeType] = useState();
|
||||||
|
const [deltaX, setDeltaX] = useState();
|
||||||
|
const [velocityX, setVelocityX] = useState();
|
||||||
|
const [swipeVerdict, setSwipeVerdict] = useState('helloworld');
|
||||||
|
|
||||||
|
const contentRef = useRef<HTMLIonContentElement>(null);
|
||||||
|
|
||||||
|
const route = useIonRouter();
|
||||||
|
const [maxBreadcrumbs, setMaxBreadcrumbs] = useState<number | undefined>(2);
|
||||||
|
|
||||||
|
const [isPageTop, setIsPageTop] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (swipeVerdict == 'swipe-left')
|
||||||
|
if (route.canGoBack() == true) {
|
||||||
|
route.goBack();
|
||||||
|
} else {
|
||||||
|
route.push('/tabs/schedule');
|
||||||
|
}
|
||||||
|
}, [swipeVerdict]);
|
||||||
|
|
||||||
|
useIonViewWillEnter(() => {});
|
||||||
|
|
||||||
|
useIonViewWillLeave(() => {});
|
||||||
|
|
||||||
|
const onMove = (detail: any) => {
|
||||||
|
const type = detail.type;
|
||||||
|
const currentX = detail.currentX;
|
||||||
|
const deltaX = detail.deltaX;
|
||||||
|
const velocityX = detail.velocityX;
|
||||||
|
setSwipeType(type);
|
||||||
|
setDeltaX(deltaX);
|
||||||
|
setVelocityX(velocityX);
|
||||||
|
|
||||||
|
if (type == 'pan')
|
||||||
|
if (Math.abs(deltaX) > 50)
|
||||||
|
if (velocityX > 0) {
|
||||||
|
setSwipeVerdict('swipe-right');
|
||||||
|
} else {
|
||||||
|
setSwipeVerdict('swipe-left');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useIonViewDidEnter(() => {
|
||||||
|
let gesture: any = {};
|
||||||
|
if (refRectangle?.current) {
|
||||||
|
gesture = createGesture({
|
||||||
|
gestureName: 'helloworld',
|
||||||
|
el: refRectangle.current,
|
||||||
|
onMove: (detail) => {
|
||||||
|
onMove(detail);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
gesture.enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
gesture?.destroy();
|
||||||
|
};
|
||||||
|
}, [refRectangle]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IonPage id="product-page">
|
||||||
|
<IonContent
|
||||||
|
ref={contentRef}
|
||||||
|
scrollEvents={true}
|
||||||
|
onIonScroll={(ev: CustomEvent<ScrollDetail>) => {
|
||||||
|
if (ev.detail.deltaY > 10) {
|
||||||
|
setIsPageTop(false);
|
||||||
|
} else {
|
||||||
|
setIsPageTop(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ position: 'fixed', top: 0, width: '100%', height: 0 }}>
|
||||||
|
<IonToolbar
|
||||||
|
className="toolbar"
|
||||||
|
mode="md"
|
||||||
|
style={{
|
||||||
|
'--background': isPageTop ? 'none' : 'white',
|
||||||
|
'--color': isPageTop ? 'white' : 'black',
|
||||||
|
'--border-style': 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonBackButton defaultHref="/tabs/schedule"></IonBackButton>
|
||||||
|
</IonButtons>
|
||||||
|
|
||||||
|
<IonButtons slot="end">
|
||||||
|
<IonButton onClick={() => {}}>
|
||||||
|
<IonIcon slot="icon-only" icon={shareOutline}></IonIcon>
|
||||||
|
</IonButton>
|
||||||
|
<IonButton onClick={() => {}}>
|
||||||
|
<IonIcon slot="icon-only" icon={flagOutline}></IonIcon>
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img src={getUnsplashRandomImage({ keyword: 'helloworld' })} width="100%" />
|
||||||
|
|
||||||
|
<div style={{ padding: '1rem', marginBottom: '3rem' }}>
|
||||||
|
<IonBreadcrumbs
|
||||||
|
maxItems={maxBreadcrumbs}
|
||||||
|
onIonCollapsedClick={() => setMaxBreadcrumbs(undefined)}
|
||||||
|
>
|
||||||
|
<IonBreadcrumb href="#home" style={{ fontSize: '0.8rem' }}>
|
||||||
|
{productSample.category.main}
|
||||||
|
</IonBreadcrumb>
|
||||||
|
<IonBreadcrumb href="#electronics" style={{ fontSize: '0.8rem' }}>
|
||||||
|
{productSample.category.sub_cat[0]}
|
||||||
|
</IonBreadcrumb>
|
||||||
|
<IonBreadcrumb href="#cameras" style={{ fontSize: '0.8rem' }}>
|
||||||
|
{productSample.category.sub_cat[1]}
|
||||||
|
</IonBreadcrumb>
|
||||||
|
</IonBreadcrumbs>
|
||||||
|
<IonText>
|
||||||
|
<h4>{productSample.title}</h4>
|
||||||
|
</IonText>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
margin: '0.5rem 0',
|
||||||
|
fontSize: '1rem',
|
||||||
|
color: 'grey',
|
||||||
|
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '0.5rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<IonIcon icon={timerOutline} />
|
||||||
|
10 hours ago by kylema11201
|
||||||
|
</IonText>
|
||||||
|
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<IonIcon icon={pricetagOutline} />
|
||||||
|
HK$0
|
||||||
|
</IonText>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<IonIcon icon={heartOutline} />
|
||||||
|
234 Likes
|
||||||
|
</IonText>
|
||||||
|
|
||||||
|
<IonText className="product-properties">In Enrichment & Tuition</IonText>
|
||||||
|
<IonText className="product-properties">Type of Rate Hourly</IonText>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<ReactMarkdown>{productSample.content}</ReactMarkdown>
|
||||||
|
</p>
|
||||||
|
<IonButton fill="clear" size="small">
|
||||||
|
read more
|
||||||
|
</IonButton>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<p>Kowloon Tong (九龍塘)</p>
|
||||||
|
</IonText>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<p>Region Kowloon</p>
|
||||||
|
</IonText>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<p>Tutor Qualification Master's</p>
|
||||||
|
</IonText>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<p>Type of Tutor Full-Time Tutor</p>
|
||||||
|
</IonText>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<p>Levels to Tutor University, Asso, IVE, Secondary</p>
|
||||||
|
</IonText>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<p>Subjects Stem, Computer Science, IT</p>
|
||||||
|
</IonText>
|
||||||
|
<IonText className="product-properties">
|
||||||
|
<p>Preferred Days / Times TBA</p>
|
||||||
|
</IonText>
|
||||||
|
<IonText>Share This Listing</IonText>
|
||||||
|
Advertisement
|
||||||
|
<div style={{ width: '100%', height: '300px' }}></div>
|
||||||
|
<div className="seller-properties" style={{ padding: 0, margin: 0 }}>
|
||||||
|
<h4>Meet The Seller</h4>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
gap: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: '80px', height: '80px' }}>
|
||||||
|
<img src={productSample.user.avatar} style={{ borderRadius: '40px' }} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{productSample.user.name}
|
||||||
|
<br />
|
||||||
|
{productSample.user.since}
|
||||||
|
<br />
|
||||||
|
{productSample.user.verified ? 'Verified' : 'not Verified'}
|
||||||
|
<br />
|
||||||
|
<StarRatings
|
||||||
|
rating={3}
|
||||||
|
starRatedColor="green"
|
||||||
|
changeRating={() => {}}
|
||||||
|
numberOfStars={5}
|
||||||
|
name="rating"
|
||||||
|
starDimension="1rem"
|
||||||
|
starSpacing="0px"
|
||||||
|
//
|
||||||
|
/>
|
||||||
|
{productSample.user.rating} ({productSample.user.total_comment})<br />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{commentList.map((commentJson) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
gap: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src={commentJson.user.avatar}
|
||||||
|
width="30px"
|
||||||
|
height="30px"
|
||||||
|
style={{ borderRadius: '15px' }}
|
||||||
|
></img>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
|
||||||
|
<div>{commentJson.user.name}</div>
|
||||||
|
<div>{commentJson.content}</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'lightgrey',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '1rem',
|
||||||
|
padding: '0.5rem',
|
||||||
|
borderRadius: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
backgroundImage: `url("${commentJson.product.avatar}")`,
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
borderRadius: '10px',
|
||||||
|
width: '80px',
|
||||||
|
height: '80px',
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'flex-start',
|
||||||
|
gap: '0.5rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ fontWeight: 'bold', fontSize: '0.8rem' }}>
|
||||||
|
{commentJson.product.title}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '0.8rem' }}>HK${commentJson.product.price}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="txt-grey" style={{ fontSize: '0.8rem' }}>
|
||||||
|
{commentJson.time}∙{commentJson.properties}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<IonButton fill="clear" routerLink={'/tabs/carousell/product/comments'}>
|
||||||
|
Read all
|
||||||
|
</IonButton>
|
||||||
|
<div className="search-suggestion">
|
||||||
|
<h3>What others also search for</h3>
|
||||||
|
<div>
|
||||||
|
<IonChip color="primary">mirror 緣份小卡</IonChip>
|
||||||
|
<IonChip color="primary">mirror 咭</IonChip>
|
||||||
|
<IonChip color="primary">maskon mirror</IonChip>
|
||||||
|
<IonChip color="primary">mirror pin</IonChip>
|
||||||
|
<IonChip color="primary">緣份小卡</IonChip>
|
||||||
|
<IonChip color="primary">csl mirror card</IonChip>
|
||||||
|
<IonChip color="primary">mirror yes card</IonChip>
|
||||||
|
<IonChip color="primary">mirror csl 交換</IonChip>
|
||||||
|
<IonChip color="primary">error yes card</IonChip>
|
||||||
|
<IonChip color="primary">mirror tee</IonChip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* similar-listings */}
|
||||||
|
<div className="similar-listings">
|
||||||
|
<h3>Similar listings</h3>
|
||||||
|
<div className="product-listing">
|
||||||
|
{productList.map((product) => (
|
||||||
|
<IonCard>
|
||||||
|
<div className="helloworld-product-card">
|
||||||
|
<div
|
||||||
|
className="product-image"
|
||||||
|
style={{ backgroundImage: `url("${product.avatar}")` }}
|
||||||
|
></div>
|
||||||
|
<div className="product-title">{product.title}</div>
|
||||||
|
<div className="product-price">HK${product.price}</div>
|
||||||
|
</div>
|
||||||
|
</IonCard>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ProductPage);
|
47
03_source/mobile/src/pages/Carousell/ProductPage/style.scss
Normal file
47
03_source/mobile/src/pages/Carousell/ProductPage/style.scss
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.product-properties {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
ion-icon {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#product-page {
|
||||||
|
.txt-grey {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-listing {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 4%;
|
||||||
|
row-gap: calc(100vw * 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-card {
|
||||||
|
width: 48%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
width: 100%;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
@@ -20,6 +20,7 @@ import Favourites from '../Favourites';
|
|||||||
import Helloworld from '../Helloworld';
|
import Helloworld from '../Helloworld';
|
||||||
import TabAppRoute from '../../TabAppRoute';
|
import TabAppRoute from '../../TabAppRoute';
|
||||||
import CarousellMe from '../CarousellMe';
|
import CarousellMe from '../CarousellMe';
|
||||||
|
import CarousellMeMyProfile from '../CarousellMe/MyProfile';
|
||||||
import ServiceMenu from './ServiceMenu';
|
import ServiceMenu from './ServiceMenu';
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -70,6 +71,8 @@ import HotelIntro from '../HotelIntro';
|
|||||||
import HotelServiceWifi from '../HotelServiceIntro';
|
import HotelServiceWifi from '../HotelServiceIntro';
|
||||||
import OrderHistory from '../OrderHistory';
|
import OrderHistory from '../OrderHistory';
|
||||||
import Insights from '../Insights';
|
import Insights from '../Insights';
|
||||||
|
import OffersMade from '../OffersMade';
|
||||||
|
import CarousellHome from '../Carousell/Home';
|
||||||
//
|
//
|
||||||
|
|
||||||
const hotelServiceMenu = [
|
const hotelServiceMenu = [
|
||||||
@@ -142,11 +145,14 @@ const MainTabs: React.FC<MainTabsProps> = () => {
|
|||||||
<Route path={PATHS.CAROUSELL_ME} render={() => <CarousellMe />} exact={true} />
|
<Route path={PATHS.CAROUSELL_ME} render={() => <CarousellMe />} exact={true} />
|
||||||
<Route path={PATHS.CAROUSELL_ME_QR} component={QRPage} exact={true} />
|
<Route path={PATHS.CAROUSELL_ME_QR} component={QRPage} exact={true} />
|
||||||
<Route path={PATHS.CAROUSELL_ME_INSIGHTS} component={Insights} exact={true} />
|
<Route path={PATHS.CAROUSELL_ME_INSIGHTS} component={Insights} exact={true} />
|
||||||
|
<Route path={PATHS.CAROUSELL_ME_OFFERS_MADE} component={OffersMade} exact={true} />
|
||||||
|
<Route path={PATHS.CAROUSELL_ME_MY_PROFILE} component={CarousellMeMyProfile} exact={true} />
|
||||||
{/*
|
{/*
|
||||||
<Route path="/tabs/carousell_me/OffersMade" component={OffersMade} exact={true} />
|
*/}
|
||||||
<Route path="/tabs/carousell_me/settings" component={Settings} exact={true} />
|
|
||||||
<Route path="/tabs/carousell_me/my_profile" component={MyProfile} exact={true} />
|
<Route path="/tabs/carousell/home" render={() => <CarousellHome />} exact={true} />
|
||||||
*/}
|
{/* <Route path="/tabs/carousell/product" render={() => <ProductPage />} exact={true} /> */}
|
||||||
|
{/* <Route path="/tabs/carousell/product/comments" render={() => <SampleBlankBottomNav />} exact={true} /> */}
|
||||||
|
|
||||||
<Route path={PATHS.HOTEL_INTRO} component={HotelIntro} exact={true} />
|
<Route path={PATHS.HOTEL_INTRO} component={HotelIntro} exact={true} />
|
||||||
<Route path={PATHS.HOTEL_SERVICE_WIFI} component={HotelServiceWifi} exact={true} />
|
<Route path={PATHS.HOTEL_SERVICE_WIFI} component={HotelServiceWifi} exact={true} />
|
||||||
|
68
03_source/mobile/src/pages/OffersMade/index.tsx
Normal file
68
03_source/mobile/src/pages/OffersMade/index.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import {
|
||||||
|
IonBackButton,
|
||||||
|
IonButton,
|
||||||
|
IonButtons,
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonIcon,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonList,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
// import AboutPopover from '../../../components/AboutPopover';
|
||||||
|
|
||||||
|
import { star, starOutline, share } from 'ionicons/icons';
|
||||||
|
import './style.scss';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
interface SampleBlankBottomNav {}
|
||||||
|
|
||||||
|
const OffersMade: React.FC<SampleBlankBottomNav> = ({}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<IonPage>
|
||||||
|
<IonHeader translucent={true}>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonBackButton defaultHref="/tabs/schedule"></IonBackButton>
|
||||||
|
</IonButtons>
|
||||||
|
<IonTitle>
|
||||||
|
<h3 style={{ fontWeight: 'bold', fontSize: '0.9rem' }}>{t('Offers Made')}</h3>
|
||||||
|
</IonTitle>
|
||||||
|
|
||||||
|
<IonButtons slot="end">
|
||||||
|
<IonButton onClick={() => {}}>
|
||||||
|
{false ? (
|
||||||
|
<IonIcon slot="icon-only" icon={star}></IonIcon>
|
||||||
|
) : (
|
||||||
|
<IonIcon slot="icon-only" icon={starOutline}></IonIcon>
|
||||||
|
)}
|
||||||
|
</IonButton>
|
||||||
|
<IonButton onClick={() => {}}>
|
||||||
|
<IonIcon slot="icon-only" icon={share}></IonIcon>
|
||||||
|
</IonButton>
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
|
||||||
|
<IonContent>
|
||||||
|
<IonList lines="none">
|
||||||
|
<IonItem>
|
||||||
|
<IonLabel style={{ textAlign: 'center' }}>
|
||||||
|
<p>{t('No items found')}</p>
|
||||||
|
</IonLabel>
|
||||||
|
</IonItem>
|
||||||
|
</IonList>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(OffersMade);
|
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
2
03_source/mobile/src/pages/OffersMade/style.scss
Normal file
2
03_source/mobile/src/pages/OffersMade/style.scss
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#sample-blank-bottom-nav-page {
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Reference in New Issue
Block a user