feat: add new CarousellMe routes for OffersMade and MyProfile pages with corresponding API integrations and UI components

This commit is contained in:
louiscklaw
2025-06-20 03:15:15 +08:00
parent 53b498d881
commit 7c1ac6b546
27 changed files with 1717 additions and 4 deletions

View File

@@ -108,6 +108,8 @@ const PATHS = {
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',

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

View File

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

View File

@@ -0,0 +1,8 @@
import React from 'react';
function Following({ hide }: { hide: boolean }) {
if (hide) return <></>;
return <>Following</>;
}
export default Following;

View File

@@ -0,0 +1,8 @@
import React from 'react';
function FreeItems({ hide }: { hide: boolean }) {
if (hide) return <></>;
return <>FreeItems</>;
}
export default FreeItems;

View File

@@ -0,0 +1,8 @@
import React from 'react';
function Nearby({ hide }: { hide: boolean }) {
if (hide) return <></>;
return <>Nearby</>;
}
export default Nearby;

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

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

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

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

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

View File

@@ -20,6 +20,7 @@ import Favourites from '../Favourites';
import Helloworld from '../Helloworld';
import TabAppRoute from '../../TabAppRoute';
import CarousellMe from '../CarousellMe';
import CarousellMeMyProfile from '../CarousellMe/MyProfile';
import ServiceMenu from './ServiceMenu';
//
@@ -70,6 +71,8 @@ import HotelIntro from '../HotelIntro';
import HotelServiceWifi from '../HotelServiceIntro';
import OrderHistory from '../OrderHistory';
import Insights from '../Insights';
import OffersMade from '../OffersMade';
import CarousellHome from '../Carousell/Home';
//
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_QR} component={QRPage} 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_SERVICE_WIFI} component={HotelServiceWifi} exact={true} />

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

View File

@@ -0,0 +1,2 @@
#sample-blank-bottom-nav-page {
}