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_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',
|
||||
|
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 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} />
|
||||
|
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