feat: implement Hotel Service Wi-Fi intro page with Swiper slider and HTML content display

This commit is contained in:
louiscklaw
2025-06-20 02:04:49 +08:00
parent d865fca058
commit 53b498d881
42 changed files with 1562 additions and 283 deletions

View File

@@ -47,9 +47,10 @@
"react-spinners": "^0.17.0",
"react-star-ratings": "^2.3.0",
"react-use": "^17.6.0",
"react-virtuoso": "^4.13.0",
"reselect": "^4.0.0",
"sass": "^1.59.3",
"swiper": "^9.1.1",
"swiper": "^11.2.8",
"use-sound": "^5.0.0",
"zod": "^3.25.56"
},
@@ -2073,9 +2074,9 @@
}
},
"node_modules/decode-named-character-reference": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
"integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
"integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
"license": "MIT",
"dependencies": {
"character-entities": "^2.0.0"
@@ -4937,6 +4938,16 @@
"react-dom": "*"
}
},
"node_modules/react-virtuoso": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.13.0.tgz",
"integrity": "sha512-XHv2Fglpx80yFPdjZkV9d1baACKghg/ucpDFEXwaix7z0AfVQj+mF6lM+YQR6UC/TwzXG2rJKydRMb3+7iV3PA==",
"license": "MIT",
"peerDependencies": {
"react": ">=16 || >=17 || >= 18 || >= 19",
"react-dom": ">=16 || >=17 || >= 18 || >=19"
}
},
"node_modules/reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
@@ -5300,11 +5311,6 @@
"node": ">= 10.x"
}
},
"node_modules/ssr-window": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz",
"integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ=="
},
"node_modules/stack-generator": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
@@ -5427,18 +5433,18 @@
}
},
"node_modules/style-to-js": {
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz",
"integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==",
"version": "1.1.17",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz",
"integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==",
"license": "MIT",
"dependencies": {
"style-to-object": "1.0.8"
"style-to-object": "1.0.9"
}
},
"node_modules/style-to-object": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
"integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz",
"integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==",
"license": "MIT",
"dependencies": {
"inline-style-parser": "0.2.4"
@@ -5484,9 +5490,9 @@
}
},
"node_modules/swiper": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-9.1.1.tgz",
"integrity": "sha512-D1zArOwI6XCXCYBULPA4jTxpqp5SQtvntjinbXNZwXzj6P3KS51zSWuMarCLXq5oRISay4nX+TuShpxz8qhtbw==",
"version": "11.2.8",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.8.tgz",
"integrity": "sha512-S5FVf6zWynPWooi7pJ7lZhSUe2snTzqLuUzbd5h5PHUOhzgvW0bLKBd2wv0ixn6/5o9vwc/IkQT74CRcLJQzeg==",
"funding": [
{
"type": "patreon",
@@ -5497,9 +5503,7 @@
"url": "http://opencollective.com/swiper"
}
],
"dependencies": {
"ssr-window": "^4.0.2"
},
"license": "MIT",
"engines": {
"node": ">= 4.7.0"
}
@@ -7588,9 +7592,9 @@
}
},
"decode-named-character-reference": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz",
"integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz",
"integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==",
"requires": {
"character-entities": "^2.0.0"
}
@@ -9482,6 +9486,12 @@
"tslib": "^2.1.0"
}
},
"react-virtuoso": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/react-virtuoso/-/react-virtuoso-4.13.0.tgz",
"integrity": "sha512-XHv2Fglpx80yFPdjZkV9d1baACKghg/ucpDFEXwaix7z0AfVQj+mF6lM+YQR6UC/TwzXG2rJKydRMb3+7iV3PA==",
"requires": {}
},
"reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
@@ -9738,11 +9748,6 @@
"integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
"dev": true
},
"ssr-window": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/ssr-window/-/ssr-window-4.0.2.tgz",
"integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ=="
},
"stack-generator": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
@@ -9841,17 +9846,17 @@
"dev": true
},
"style-to-js": {
"version": "1.1.16",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz",
"integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==",
"version": "1.1.17",
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz",
"integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==",
"requires": {
"style-to-object": "1.0.8"
"style-to-object": "1.0.9"
}
},
"style-to-object": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
"integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz",
"integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==",
"requires": {
"inline-style-parser": "0.2.4"
}
@@ -9885,12 +9890,9 @@
"dev": true
},
"swiper": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-9.1.1.tgz",
"integrity": "sha512-D1zArOwI6XCXCYBULPA4jTxpqp5SQtvntjinbXNZwXzj6P3KS51zSWuMarCLXq5oRISay4nX+TuShpxz8qhtbw==",
"requires": {
"ssr-window": "^4.0.2"
}
"version": "11.2.8",
"resolved": "https://registry.npmjs.org/swiper/-/swiper-11.2.8.tgz",
"integrity": "sha512-S5FVf6zWynPWooi7pJ7lZhSUe2snTzqLuUzbd5h5PHUOhzgvW0bLKBd2wv0ixn6/5o9vwc/IkQT74CRcLJQzeg=="
},
"tar": {
"version": "6.1.13",

View File

@@ -45,9 +45,10 @@
"react-spinners": "^0.17.0",
"react-star-ratings": "^2.3.0",
"react-use": "^17.6.0",
"react-virtuoso": "^4.13.0",
"reselect": "^4.0.0",
"sass": "^1.59.3",
"swiper": "^9.1.1",
"swiper": "^11.2.8",
"use-sound": "^5.0.0",
"zod": "^3.25.56"
},

View File

@@ -71,6 +71,8 @@ import DummyEventPayPage from './pages/DummyEventPayPage';
import PaymentSuccess from './pages/PaymentSuccess';
import PaymentFailed from './pages/PaymentFailed';
import settings from './pages/tabs/carousell_me/settings';
import HotelWelcomeTour from './pages/HotelWelcomeTour';
import HotelServiceWifi from './pages/HotelIntro';
setupIonicReact();
@@ -150,6 +152,9 @@ const IonicApp: React.FC<IonicAppProps> = ({ darkMode, schedule, setIsLoggedIn,
<Route exact={true} path="/order_detail/:id" component={OrderDetail} />
<Route exact={true} path="/helloworld" component={Helloworld} />
<Route path={PATHS.HOTEL_WELCOME_TOUR} component={HotelWelcomeTour} exact={true} />
{/* <Route path={PATHS.HOTEL_SERVICE_INTRO} component={HotelServiceIntro} exact={true} /> */}
{/* tabs/hotel_service_intro */}
<Route
path="/logout"

View File

@@ -107,5 +107,10 @@ const PATHS = {
CAROUSELL_ME: '/tabs/carousell_me',
CAROUSELL_ME_QR: '/tabs/carousell_me/qr_page',
CAROUSELL_ME_SETTINGS: '/carousell_me/settings',
CAROUSELL_ME_INSIGHTS: '/tabs/carousell_me/insights',
//
HOTEL_WELCOME_TOUR: '/hotel_service_intro',
HOTEL_INTRO: '/tabs/hotel_intro',
HOTEL_SERVICE_WIFI: '/tabs/hotel_service_wifi',
};
export default PATHS;

View File

@@ -0,0 +1,95 @@
import React from 'react';
const contentMd = `
全館Wi-Fiをご利用いただけます。無料
■ID: helloworld Life
■Password: 0362751510
LYNKED HOTELでは、
全客室・宴会場の一部に
有線LAN接続によるインターネット
接続環境を開業時より
導入しておりましたが、
さらなる利便性向上を図るため、
このたび、ロビー・客室フロアに無線LAN
アクセスポイントを設置し、
各客室内でもWi-Fi接続による
インターネットを利用できる
環境を構築いたしました。
より快適なWi-Fi接続サービスによる
無料のインターネット接続を
ご利用いただけます。
■ ご利用いただける端末
・LAN / 無線LANWi-Fi規格アダプタ内蔵PC
・WindowsXP以上、Mac OSX以上のOSを搭載したPC
・無線LANWi-Fi規格対応のiOS機器iPhone・iPad等
・無線LANWi-Fi規格のAndroid機器スマートフォン・タブレットPC等
■ Wi-Fi接続サービスの規格・接続
IEEE802.11 n/b/gの規格に準拠しており、同時使用が可能です。
ネットアクセスポイントにつきましては、各ホテルSSIDをフロントにてご案内いたします。
■ 各ホテル内でインターネット接続サービスが可能なエリア
・全客室内有線LAN・Wi-Fi接続
・フロント前ロビーWi-Fi接続
・各宴会場有線LAN接続
■ ご注意
※有線・無線LANを通してインターネット接続サービスを無料でご利用いただけます。
※Wi-Fi接続サービスは、ホテルのSSIDが発出されている場所でご利用になれますが、場所により電波の届かないエリア、もしくは電波が弱くご利用が難しい場合もあります。
※ご利用に際してのセキュリティ設定は、お客様ご自身の責任において行っていただくようにお願いいたします。
※本サービスのご利用・予期せぬ停止や不良が原因となり発生した損失や損害については、ホテルは一切の責任は負いかねますので、予めご了承ください。
※ネット対戦ゲーム、大容量ファイルの送受信など、回線を長時間占有してのご利用は、他のお客様のご迷惑となりますので、ご遠慮ください。
`.trim();
function getContentMarkdown(): Promise<string> {
return new Promise((res, rej) => {
res(contentMd);
});
}
export default getContentMarkdown;

View File

@@ -0,0 +1,119 @@
import React from 'react';
const testHtml = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.asscent-color1 { color: #800000 }
.text-deep-grey {color: rgb(0,0,0)}
.text-grey {color: rgb(0,0,0, 0.6)}
.text-smoothing {opacity: 0.8}
h1 { font-size: 1.2rem}
h2 { font-size: 1.1rem}
h3 { font-size: 1rem}
h4 { font-size: 0.8rem}
h5 { font-size: 0.7rem}
h6 { font-size: 0.6rem}
.quote {
background-color: rgba(231, 76, 60, 0.05);
padding: 1rem; border-radius: 5px;
border-left: 5px solid #800000;
width: 90%;
margin: 1rem auto;
line-height:2rem;
}
</style>
</head>
<body style="padding: 0; margin: 0; box-sizing: border-box;" class="text-smoothing">
<div class="text-grey" style="font-size: 0.8rem; text-align: center">
全館Wi-Fiをご利用いただけます。無料
</div>
<div class="quote">
<div>■ ID: hello_user</div>
<div>■ Password: my-best-password</div>
</div>
<h3 class="asscent-color1">HELLOWORLD HOTELでは、</h3>
<p>全客室・宴会場の一部に</p>
<p>有線LAN接続によるインターネット</p>
<p>接続環境を開業時より</p>
<p>導入しておりましたが、</p>
<p>さらなる利便性向上を図るため、</p>
<p>このたび、ロビー・客室フロアに無線LAN</p>
<p>アクセスポイントを設置し、</p>
<p>各客室内でもWi-Fi接続による</p>
<p>インターネットを利用できる</p>
<p>環境を構築いたしました。</p>
<p>より快適なWi-Fi接続サービスによる</p>
<p>無料のインターネット接続を</p>
<p>ご利用いただけます。</p>
<h4 class="asscent-color1">■ ご利用いただける端末</h4>
<p>・LAN / 無線LANWi-Fi規格アダプタ内蔵PC</p>
<p>・WindowsXP以上、Mac OSX以上のOSを搭載したPC</p>
<p>・無線LANWi-Fi規格対応のiOS機器iPhone・iPad等</p>
<p>・無線LANWi-Fi規格のAndroid機器スマートフォン・タブレットPC等</p>
<h4 class="asscent-color1">■ Wi-Fi接続サービスの規格・接続</h4>
<p>IEEE802.11 n/b/gの規格に準拠しており、同時使用が可能です。</p>
<p>ネットアクセスポイントにつきましては、各ホテルSSIDをフロントにてご案内いたします。</p>
<h4 class="asscent-color1">■ 各ホテル内でインターネット接続サービスが可能なエリア</h4>
<p>・全客室内有線LAN・Wi-Fi接続</p>
<p>・フロント前ロビーWi-Fi接続</p>
<p>・各宴会場有線LAN接続</p>
<h4 class="asscent-color1">■ ご注意</h4>
<p>※有線・無線LANを通してインターネット接続サービスを無料でご利用いただけます。</p>
<p>※Wi-Fi接続サービスは、ホテルのSSIDが発出されている場所でご利用になれますが、場所により電波の届かないエリア、もしくは電波が弱くご利用が難しい場合もあります。</p>
<p>※ご利用に際してのセキュリティ設定は、お客様ご自身の責任において行っていただくようにお願いいたします。</p>
<p>※本サービスのご利用・予期せぬ停止や不良が原因となり発生した損失や損害については、ホテルは一切の責任は負いかねますので、予めご了承ください。</p>
<p>※ネット対戦ゲーム、大容量ファイルの送受信など、回線を長時間占有してのご利用は、他のお客様のご迷惑となりますので、ご遠慮ください。</p>
<h1>待進變果沒致友環健問水法代人苦天。📅📍🎻</h1>
<h2>待進變果沒致友環健問水法代人苦天。📅📍🎻</h2>
<h3>待進變果沒致友環健問水法代人苦天。📅📍🎻</h3>
<h4>待進變果沒致友環健問水法代人苦天。📅📍🎻</h4>
<h5>待進變果沒致友環健問水法代人苦天。📅📍🎻</h5>
<h6>待進變果沒致友環健問水法代人苦天。📅📍🎻</h6>
<p>
業立臺四即文善公作有往,等怕準命小電個。
査今聞光洋後化外財強主職。
🌲🔯🍣💵 🐪👫🐈📅📍🎻💼 🐣🍖🐻📩🍨. 🎇👬💨
</p>
<h1>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h1>
<h2>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h2>
<h3>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h3>
<h4>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h4>
<h5>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h5>
<h6>Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information📅📍🎻</h6>
<p>
Lorem Ipsum: Usage, Common examples, Translation, Variants and technical information
Essay: Lorem Ipsum--when, and when not to use it
🌲🔯🍣💵 🐪👫🐈📅📍🎻💼 🐣🍖🐻📩🍨. 🎇👬💨
</p>
<p>📤🏮👀🍮 💃👪👦🌀🌶📈 🍵📊💓🐧🎢👃 🍕🌛🔎🔋🎣🍃 🎡👩📔🍈💭 🎣👅🔽📟📑💋</p>
</body>
</html>`.trim();
function getHtmlContent(): Promise<string> {
return new Promise((res, rej) => {
res(testHtml);
});
}
export default getHtmlContent;

View File

@@ -0,0 +1,11 @@
import React from 'react';
function getRandomInt(max: number) {
return Math.floor(Math.random() * max);
}
function getUnsplashRandomImage({ keyword }: { keyword: string }) {
return `https://media.karousell.com/media/photos/profiles/2025/02/05/louis_coding_1738774979_f1598e0b.jpg`;
}
export default getUnsplashRandomImage;

View File

@@ -0,0 +1,221 @@
import React, { useRef, useEffect, useState } from 'react';
import {
IonContent,
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonButton,
IonIcon,
useIonViewWillEnter,
IonBackButton,
IonText,
} from '@ionic/react';
import { arrowForward, share, star, starOutline } from 'ionicons/icons';
import { setMenuEnabled } from '../../data/sessions/sessions.actions';
import { setHasSeenTutorial } from '../../data/user/user.actions';
import './style.scss';
import { connect } from '../../data/connect';
import { RouteComponentProps } from 'react-router';
import PATHS from '../../PATHS';
import getUnsplashRandomImage from '../../api/getUnsplashRandomImage';
import schedulePng from './sampleHeader.png';
// import type { Swiper } from 'swiper/types';
// import type { Swiper as SwiperClass } from 'swiper/types';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import '@ionic/react/css/ionic-swiper.css';
import getHtmlContent from '../../api/getHtmlContent';
interface OwnProps extends RouteComponentProps {}
interface DispatchProps {
setHasSeenTutorial: typeof setHasSeenTutorial;
setMenuEnabled: typeof setMenuEnabled;
}
interface TutorialProps extends OwnProps, DispatchProps {}
const HotelServiceWifi: React.FC<TutorialProps> = () => {
const [htmlContent, setHtmlContent] = useState<string>('');
let [loadingContent, setLoadingContent] = useState<boolean>(true);
useEffect(() => {
getHtmlContent()
.then((res) => setHtmlContent(res.trim()))
.then(() => setLoadingContent(false));
}, []);
return (
<IonPage id="hotel-service-intro-page">
<IonHeader className="ion-no-border">
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/tabs/schedule"></IonBackButton>
</IonButtons>
<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>
{/* helloworld */}
<Swiper>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '33vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//plus.unsplash.com/premium_photo-1661964071015-d97428970584)`,
}}
></div>
</SwiperSlide>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '33vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//images.unsplash.com/photo-1618773928121-c32242e63f39)`,
}}
></div>
</SwiperSlide>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '33vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//images.unsplash.com/photo-1551882547-ff40c63fe5fa)`,
}}
></div>
</SwiperSlide>
</Swiper>
{/* */}
<div
id="content"
style={{
paddingLeft: '0.5rem',
paddingRight: '0.5rem',
marginTop: '1rem',
marginBottom: '1rem',
}}
>
<div dangerouslySetInnerHTML={{ __html: contentHtml }}>{}</div>
</div>
</IonContent>
</IonPage>
);
};
export default connect<OwnProps, {}, DispatchProps>({
mapDispatchToProps: {
setHasSeenTutorial,
setMenuEnabled,
},
component: HotelServiceWifi,
});
const contentHtml = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {padding:0; margin:0; box-sizing: border-box;}
.text { color: rgba(0,0,0,0.8)}
.text-grey {color: rgba(0,0,0,0.5)}
.text-asscent {color: rgba(64,0,0,0.8)}
.bold {font-weight: bold;}
.description {font-size: 1rem}
.title { font-size: 1.1rem }
.text-subtitle { font-size: 0.8rem }
.center { text-align: center}
.hr-asscent { border-bottom: 3px solid rgba(64,0,0,0.8) }
.hr-grey { border-bottom: 1px solid rgba(64,64,64,0.5) }
.asscent-background { background-color: rgba(64,0,0,0.1); border-radius: 10px; padding: 1rem 0.5rem }
td {padding-right: 2rem; font-size: 0.8rem}
.horizontal-spacer { padding: 2rem 0}
.shadow { box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; }
.hard-shadow { box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; }
</style>
</head>
<body>
<div class="text bold center horizontal-spacer">
<h3>helloworld Hotel</h3>
</div>
<div class="text-grey description">〒107-0062 東京都港区南青山1丁目26-1</div>
<hr class="hr-asscent">
<div class="asscent-background shadow">
<div class="text-asscent">ホテル情報</div>
<hr class="hr-grey" >
<table>
<tr>
<td class="text-grey">チェックイン</td><td class="text-grey">15:00</td>
</tr>
<tr>
<td class="text-grey">チェックアウト</td><td class="text-grey">10:00</td>
</tr>
<tr>
<td class="text-grey">夜間玄関施錠</td><td class="text-grey">25:00</td>
</tr>
</table>
</div>
<div class="text-asscent center">
<h5>ご利用可能なクレジットカード</h5>
</div>
<hr class="hr-grey" >
<div class="horizontal-spacer">
<div class="text-asscent bold center">helloworld 物語</div>
<div class="text-subtitle bold center">"TRAVEL IS LIFE, DX IS HOW"</div>
</div>
<div
class="horizontal-spacer shadow"
style="
width:100%;
height: 200px;
background-image: url('https://images.unsplash.com/photo-1566073771259-6a8506099945');
background-size:cover;
background-position:center;
background-repeat: no-repeat;
border-radius: 10px;
"></div>
<div class="horizontal-spacer text-grey bold description">
<p>異国情緒あふれる館に招かれたような優雅なひととき。</p>
<p>helloworld LifeHOTELはヨーロッパを中心とした様々なコンセプトで全国に展開しており、ホテルに一歩足を踏み入れた瞬間から、異国情緒あふれるLYNKEDならではの世界観を楽しんでいただけます。</p>
<p>ご宿泊の際は、1日の締めくくりにほっとくつろいでいただけるあたたかさと機能性を兼ね備えた客室で。</p>
<p>邸宅に招かれたような雰囲気を兼ね備えたレストランでのお食事は、記念日など特別な日にも。</p>
<p>オーセンティックな教会や華やかで明るいパーティールームで最高にロマンティックな結婚式を。</p>
<p>そのほか各種パーティーや会議、天然温泉をひいたスパ施設など、心あたたまるおもてなしで、みなさまをお迎えいたします。</p>
<p>helloworld HOTELのドラマティックステージへようこそ。</p>
</div>
</body>
</html>
`;

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -10,7 +10,7 @@
.slider {
display: grid;
grid-template-columns: repeat(4, 100%);
// grid-template-columns: repeat(4, 100%);
grid-template-rows: 1fr;
height: 100%;

View File

@@ -0,0 +1,210 @@
import {
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon,
IonPage,
IonText,
IonToolbar,
useIonRouter,
} from '@ionic/react';
import React, { useEffect, useState } from 'react';
import { star, starOutline, share, chevronBack } from 'ionicons/icons';
// import type { Swiper } from 'swiper/types';
// import type { Swiper as SwiperClass } from 'swiper/types';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import '@ionic/react/css/ionic-swiper.css';
import './style.scss';
import Sampleheader from './sampleHeader.png';
import getUnsplashRandomImage from '../../api/getUnsplashRandomImage';
// import ContentHtml from './ContentHtml.tsx.del';
import getHtmlContent from '../../api/getHtmlContent';
import PATHS from '../../PATHS';
const tutorialSample1 = {
image: getUnsplashRandomImage({ keyword: 'seafood' }),
title: `
## ホテルの利益率は?`.trim(),
content: `
新型コロナウイルス感染症拡大により、ホテル業界は厳しい経営を強いられる中、ウィズコロナに向けて。`.trim(),
};
const tutorialSample2 = {
image: getUnsplashRandomImage({ keyword: 'hotel' }),
title: `
## ホテルの収益構造`.trim(),
content: `
ホテルの収益構造を分解すると、以下の3部門に分けられます。`.trim(),
};
const tutorialSample3 = {
image: getUnsplashRandomImage({ keyword: 'hotel' }),
title: `
## 宿泊部門`.trim(),
content: `
ホテル・旅館の主軸となるサービスが宿泊部門で、利用者の宿泊に関する料金が計上されます。`.trim(),
};
const tutorialSample4 = {
image: getUnsplashRandomImage({ keyword: 'hotel' }),
title: `
## 飲食部門`.trim(),
content: `
ホテルに内設されているレストランやバーで、飲食サービスを提供するのが飲食部門です。`.trim(),
};
const tutorialSample5 = {
image: getUnsplashRandomImage({ keyword: 'hotel' }),
title: `
## 宴会・イベント部門`.trim(),
content: `
団体・グループで宴会やイベントを催したい場合に、会場の貸し出しや料理の提供を行うのが宴会・イベント部門です。`.trim(),
};
const tutorialSetup = [
tutorialSample1,
tutorialSample2,
tutorialSample3,
tutorialSample4,
tutorialSample5,
];
const tutorialLastSlideIdx = tutorialSetup.length - 1;
interface HotelServiceIntro {}
const HotelServiceIntro: React.FC<HotelServiceIntro> = () => {
const route = useIonRouter();
// const pageRef = useRef();
// let [swiper, setSwiper] = useState<SwiperCore>();
const [htmlContent, setHtmlContent] = useState<string>('');
let [loadingContent, setLoadingContent] = useState<boolean>(true);
useEffect(() => {
getHtmlContent()
.then((res) => setHtmlContent(res.trim()))
.then(() => setLoadingContent(false));
}, []);
return (
<IonPage id="hotel-service-intro-page">
<IonHeader className="no-ion-border">
<IonToolbar>
<IonButtons slot="start">
<IonButton onClick={() => route.push(PATHS.EVENT_LIST)}>
<IonIcon slot="icon-only" icon={chevronBack}></IonIcon>
</IonButton>
</IonButtons>
<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>
<Swiper>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '25vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//plus.unsplash.com/premium_photo-1661964071015-d97428970584)`,
}}
></div>
</SwiperSlide>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '25vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//images.unsplash.com/photo-1618773928121-c32242e63f39)`,
}}
></div>
</SwiperSlide>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '25vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//images.unsplash.com/photo-1551882547-ff40c63fe5fa)`,
}}
></div>
</SwiperSlide>
</Swiper>
<div
style={{
position: 'relative',
top: '-50px',
left: '1rem',
zIndex: 102,
height: '50px',
}}
>
<div
style={{
width: '100px',
height: '100px',
backgroundImage: `url("${getUnsplashRandomImage({ keyword: 'hotel' })}")`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
borderRadius: '50px',
border: '3px solid white',
//
boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
}}
></div>
</div>
<div style={{ paddingLeft: '1rem', paddingRight: '1rem', marginTop: '1rem' }}>
{loadingContent ? (
<>loading content</>
) : (
<>
<div style={{}}>
<div
dangerouslySetInnerHTML={{ __html: htmlContent }}
style={{ width: '100%', minHeight: '300px' }}
></div>
</div>
<div
style={{
marginTop: '1.5rem',
marginBottom: '1.5rem',
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
}}
>
<IonText style={{ opacity: '0.5' }}>end</IonText>
</div>
</>
)}
</div>
</IonContent>
</IonPage>
);
};
export default React.memo(HotelServiceIntro);

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -0,0 +1,2 @@
#hotel-service-intro-page {
}

View File

@@ -0,0 +1,126 @@
import React, { useEffect, useState } from 'react';
import {
IonContent,
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonButton,
IonIcon,
IonBackButton,
} from '@ionic/react';
import { share, star, starOutline } from 'ionicons/icons';
import { setMenuEnabled } from '../../data/sessions/sessions.actions';
import { setHasSeenTutorial } from '../../data/user/user.actions';
import './style.scss';
import { connect } from '../../data/connect';
import { RouteComponentProps } from 'react-router';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import '@ionic/react/css/ionic-swiper.css';
import getHtmlContent from '../../api/getHtmlContent';
interface OwnProps extends RouteComponentProps {}
interface DispatchProps {
setHasSeenTutorial: typeof setHasSeenTutorial;
setMenuEnabled: typeof setMenuEnabled;
}
interface TutorialProps extends OwnProps, DispatchProps {}
const HotelServiceWifi: React.FC<TutorialProps> = () => {
const [htmlContent, setHtmlContent] = useState<string>('');
let [loadingContent, setLoadingContent] = useState<boolean>(true);
useEffect(() => {
getHtmlContent()
.then((res) => setHtmlContent(res.trim()))
.then(() => setLoadingContent(false));
}, []);
return (
<IonPage id="hotel-service-intro-page">
<IonHeader className="ion-no-border">
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/tabs/schedule"></IonBackButton>
</IonButtons>
<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>
{/* helloworld */}
<Swiper>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '33vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//plus.unsplash.com/premium_photo-1661964071015-d97428970584)`,
}}
></div>
</SwiperSlide>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '33vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//images.unsplash.com/photo-1618773928121-c32242e63f39)`,
}}
></div>
</SwiperSlide>
<SwiperSlide>
<div
style={{
width: '100vw',
height: '33vh',
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundImage: `url(//images.unsplash.com/photo-1551882547-ff40c63fe5fa)`,
}}
></div>
</SwiperSlide>
</Swiper>
{/* */}
<div
id="content"
style={{
paddingLeft: '0.5rem',
paddingRight: '0.5rem',
marginTop: '1rem',
marginBottom: '1rem',
}}
>
<div dangerouslySetInnerHTML={{ __html: htmlContent }}>{}</div>
</div>
</IonContent>
</IonPage>
);
};
export default connect<OwnProps, {}, DispatchProps>({
mapDispatchToProps: {
setHasSeenTutorial,
setMenuEnabled,
},
component: HotelServiceWifi,
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -0,0 +1,56 @@
#tutorial-page {
ion-toolbar {
--background: transparent;
--border-color: transparent;
}
.slide-title {
margin-top: 2.8rem;
}
.slider {
display: grid;
// grid-template-columns: repeat(4, 100%);
grid-template-rows: 1fr;
height: 100%;
overflow: scroll;
scroll-snap-type: x mandatory;
}
section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
scroll-snap-align: center;
scroll-snap-stop: always;
}
.slide-image {
max-height: 50%;
max-width: 60%;
margin: -5vh 0 0;
pointer-events: none;
}
b {
font-weight: 500;
}
p {
padding: 0 40px;
font-size: 14px;
line-height: 1.5;
color: var(--ion-color-step-600, #60646b);
b {
color: var(--ion-text-color, #000000);
}
}
}

View File

@@ -0,0 +1,155 @@
import React, { useRef, useEffect } from 'react';
import {
IonContent,
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonButton,
IonIcon,
useIonViewWillEnter,
} from '@ionic/react';
import { arrowForward } from 'ionicons/icons';
import { setMenuEnabled } from '../../data/sessions/sessions.actions';
import { setHasSeenTutorial } from '../../data/user/user.actions';
import './style.scss';
import { connect } from '../../data/connect';
import { RouteComponentProps } from 'react-router';
import PATHS from '../../PATHS';
import getUnsplashRandomImage from '../../api/getUnsplashRandomImage';
import schedulePng from './sampleHeader.png';
interface OwnProps extends RouteComponentProps {}
interface DispatchProps {
setHasSeenTutorial: typeof setHasSeenTutorial;
setMenuEnabled: typeof setMenuEnabled;
}
interface TutorialProps extends OwnProps, DispatchProps {}
const HotelWelcomeTour: React.FC<TutorialProps> = ({
history,
setHasSeenTutorial,
setMenuEnabled,
}) => {
const sliderRef = useRef<HTMLDivElement>(null);
useIonViewWillEnter(() => {
setMenuEnabled(false);
// Scroll to first slide when entering the tutorial
if (sliderRef.current) {
sliderRef.current.scrollTo({
left: 0,
behavior: 'smooth',
});
}
});
const startApp = async () => {
await setHasSeenTutorial(true);
await setMenuEnabled(true);
history.push(PATHS.EVENT_LIST, { direction: 'none' });
};
return (
<IonPage id="tutorial-page">
<IonHeader className="ion-no-border">
<IonToolbar>
<IonButtons slot="end">
<IonButton color="primary" onClick={startApp}>
Skip
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
{/* */}
<IonContent fullscreen>
<div
className="slider"
ref={sliderRef}
style={{
gridTemplateColumns: `repeat(4, 100%)`,
}}
>
{/* */}
<section>
<div
style={{
width: '100%',
height: 'calc( 100vh - 70px )',
backgroundImage: `url(https://images.unsplash.com/photo-1549294413-26f195200c16)`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
></div>
<div
style={{
color: 'white',
position: 'relative',
top: '-500px',
height: '0',
}}
>
<h2 className="slide-title"></h2>
<p style={{ color: 'white' }}>
</p>
</div>
</section>
{/* */}
<section>
<div
style={{
width: '100%',
height: 'calc( 100vh - 70px )',
backgroundImage: `url(https://images.unsplash.com/photo-1482049016688-2d3e1b311543)`,
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
></div>
<div style={{ color: 'white', position: 'relative', top: '-500px', height: '0' }}>
<h2 className="slide-title"></h2>
<p style={{ color: 'white' }}>
</p>
</div>
</section>
{/* */}
<section>
<div className="swiper-item">
<img src={schedulePng} alt="" className="slide-image" />
<h2 className="slide-title"></h2>
<p>
</p>
</div>
</section>
{/* */}
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-4.png" alt="" className="slide-image" />
<h2 className="slide-title"></h2>
<p>
</p>
<IonButton fill="clear" onClick={startApp}>
Continue
<IonIcon slot="end" icon={arrowForward} />
</IonButton>
</div>
</section>
</div>
</IonContent>
</IonPage>
);
};
export default connect<OwnProps, {}, DispatchProps>({
mapDispatchToProps: {
setHasSeenTutorial,
setMenuEnabled,
},
component: HotelWelcomeTour,
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -0,0 +1,56 @@
#tutorial-page {
ion-toolbar {
--background: transparent;
--border-color: transparent;
}
.slide-title {
margin-top: 2.8rem;
}
.slider {
display: grid;
// grid-template-columns: repeat(4, 100%);
grid-template-rows: 1fr;
height: 100%;
overflow: scroll;
scroll-snap-type: x mandatory;
}
section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
scroll-snap-align: center;
scroll-snap-stop: always;
}
.slide-image {
max-height: 50%;
max-width: 60%;
margin: -5vh 0 0;
pointer-events: none;
}
b {
font-weight: 500;
}
p {
padding: 0 40px;
font-size: 14px;
line-height: 1.5;
color: var(--ion-color-step-600, #60646b);
b {
color: var(--ion-text-color, #000000);
}
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -0,0 +1,124 @@
import {
IonBackButton,
IonButton,
IonButtons,
IonContent,
IonHeader,
IonIcon,
IonPage,
IonPopover,
IonSegment,
IonSegmentButton,
IonText,
IonTitle,
IonToolbar,
} from '@ionic/react';
import { star, starOutline } from 'ionicons/icons';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
// import AboutPopover from '../../components/AboutPopover';
import './style.scss';
import histogramJpg from './histogram.jpg';
import buySellSvg from './buy_sell.svg';
type SessionListProps = {
hide: boolean;
};
const ShoutoutContent: React.FC<SessionListProps> = ({ hide }) => {
if (hide) return <></>;
return (
<>
<div style={{ margin: '3rem 1rem 0 1rem' }}>
<IonText>
<h6>Create a shoutout to reach more buyers</h6>
</IonText>
<div
style={{
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
gap: '3rem',
}}
>
<IonText color={'tertiary'} style={{ fontSize: '0.8rem' }}>
der of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it
is pleasure, but because those who do not know how to pursue pleasure rationally
encounter consequences that are extremely painful. Nor again is there
</IonText>
<img src={buySellSvg} width="30%" height="auto" style={{ minWidth: '100px' }} />
</div>
</div>
</>
);
};
const VisitsContent: React.FC<SessionListProps> = ({ hide }) => {
if (hide) return <></>;
return (
<>
<img src={histogramJpg} />
</>
);
};
interface InsightsProps {}
const Insights: React.FC<InsightsProps> = () => {
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 [segment, setSegment] = useState<'visits' | 'shoutout'>('visits');
const { t } = useTranslation();
return (
<IonPage id="insights-page">
<IonHeader translucent={true} className="ion-no-border">
<IonToolbar>
<IonButtons slot="start">
<IonBackButton defaultHref="/tabs/schedule"></IonBackButton>
</IonButtons>
<IonButtons slot="end">
<IonButton onClick={() => {}}>
{false ? (
<IonIcon slot="icon-only" icon={star}></IonIcon>
) : (
<IonIcon slot="icon-only" icon={starOutline}></IonIcon>
)}
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent>
<div style={{ marginLeft: '1rem', marginRight: '1rem' }}>
<IonText>
<h3>Insights</h3>
</IonText>
</div>{' '}
<IonSegment
mode={'md'}
value={segment}
onIonChange={(e) => setSegment(e.detail.value as any)}
>
<IonSegmentButton value="visits">{t('Visits')}</IonSegmentButton>
<IonSegmentButton value="shoutout">{t('Shoutout')}</IonSegmentButton>
</IonSegment>
<VisitsContent hide={segment != 'visits'} />
<ShoutoutContent hide={segment != 'shoutout'} />
</IonContent>
{/*
<IonPopover isOpen={showPopover} event={popoverEvent} onDidDismiss={() => setShowPopover(false)}>
<AboutPopover dismiss={() => setShowPopover(false)} />
</IonPopover>
*/}
</IonPage>
);
};
export default React.memo(Insights);

View File

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

View File

@@ -65,24 +65,29 @@ import svgIcon36 from './icons/holiday-hotel-motel-sign-travel-svgrepo-com.svg';
import svgIcon37 from './icons/holiday-journey-luggage-suitcase-vacation-2-svgrepo-com.svg';
import svgIcon38 from './icons/hotel-location-map-pin-travel-svgrepo-com.svg';
import getButtonSvg from '../../api/getButtonSvg';
import HotelServiceIntro from '../HotelWelcomeTour';
import HotelIntro from '../HotelIntro';
import HotelServiceWifi from '../HotelServiceIntro';
import OrderHistory from '../OrderHistory';
import Insights from '../Insights';
//
const hotelServiceMenu = [
{ name: '停車場', icon: svgIcon1, targetUrl: '/tabs/hotel_service_intro' },
{ name: '接機服務', icon: svgIcon2, targetUrl: '/tabs/hotel_service_intro' },
{ name: '停車場', icon: svgIcon1, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '接機服務', icon: svgIcon2, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '餐廳', icon: svgIcon3, targetUrl: '/tabs/hotel/:hotelid/restaurants' },
{ name: '咖啡室', icon: svgIcon4, targetUrl: '/tabs/restaurant/:restaurant_id/food/:food_id/intro' },
{ name: '酒吧', icon: svgIcon5, targetUrl: '/tabs/hotel_service_intro' },
{ name: '會議室', icon: svgIcon6, targetUrl: '/tabs/hotel_service_intro' },
{ name: '托兒服務', icon: svgIcon7, targetUrl: '/tabs/hotel_service_intro' },
{ name: '茶室', icon: svgIcon8, targetUrl: '/tabs/hotel_service_intro' },
{ name: '多功能室', icon: svgIcon9, targetUrl: '/tabs/hotel_service_intro' },
{ name: '商務便利設施', icon: svgIcon10, targetUrl: '/tabs/hotel_service_intro' },
{ name: '健身室', icon: svgIcon11, targetUrl: '/tabs/hotel_service_intro' },
{ name: '公共區域 Wi-Fi免費', icon: svgIcon12, targetUrl: '/tabs/hotel_service_intro' },
{ name: '貨幣兌換', icon: svgIcon13, targetUrl: '/tabs/hotel_service_intro' },
{ name: '行李寄存免費', icon: svgIcon14, targetUrl: '/tabs/hotel_service_intro' },
{ name: '叫醒服務', icon: svgIcon16, targetUrl: '/tabs/hotel_service_intro' },
{ name: '酒吧', icon: svgIcon5, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '會議室', icon: svgIcon6, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '托兒服務', icon: svgIcon7, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '茶室', icon: svgIcon8, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '多功能室', icon: svgIcon9, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '商務便利設施', icon: svgIcon10, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '健身室', icon: svgIcon11, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '公共區域 Wi-Fi免費', icon: svgIcon12, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '貨幣兌換', icon: svgIcon13, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: 'wifi', icon: svgIcon14, targetUrl: PATHS.HOTEL_SERVICE_WIFI },
{ name: '叫醒服務', icon: svgIcon16, targetUrl: PATHS.HOTEL_WELCOME_TOUR },
{ name: '關於酒店', icon: svgIcon16, targetUrl: '/tabs/hotel_intro' },
];
@@ -136,13 +141,18 @@ 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="/tabs/carousell_me/insights" component={Insights} 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={PATHS.HOTEL_INTRO} component={HotelIntro} exact={true} />
<Route path={PATHS.HOTEL_SERVICE_WIFI} component={HotelServiceWifi} exact={true} />
<Route path="/tabs/restaurant/:restaurant_id/order_history" render={() => <OrderHistory />} exact={true} />
<TabAppRoute />
</IonRouterOutlet>

View File

@@ -0,0 +1,71 @@
import { IonItem, IonText } from '@ionic/react';
import React from 'react';
interface OrderHistoryItemProps {
status: string;
quantity: number;
price: number;
name: string;
time: string;
}
const OrderHistoryItem: React.FC<OrderHistoryItemProps> = ({
status,
quantity,
price,
name,
time,
}) => {
let statusBgColor = ['rgba(128,0,0,0.1)', 'rgba(0,128,0,0.1)', 'rgba(0,0,128,0.1)'];
let statusTextcolor = ['rgba(128,0,0,0.9)', 'rgba(0,128,0,0.9)', 'rgba(0,0,128,0.9)'];
let statusMap = ['準備中', '提供済', 'キャンセル'];
let statusIdx = statusMap.indexOf(status) || 0;
return (
<>
<IonItem lines="full" slot="fixed" style={{ width: '100%', height: '100%' }}>
<div style={{ width: '80px' }}>
<IonText
color="danger"
style={{
backgroundColor: statusBgColor[statusIdx],
color: statusTextcolor[statusIdx],
borderRadius: '3px',
fontSize: '0.8rem',
padding: '5px',
minWidth: '80px',
}}
>
{status}
</IonText>
</div>
<div
style={{
width: 'calc( 100% - 80px )',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
}}
>
<div>
<IonText>{name}</IonText>
<div style={{ fontSize: '0.8rem', color: 'grey' }}>: {time}</div>
</div>
<div
style={{
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
}}
>
<IonText>
¥{price} x {quantity}
</IonText>
</div>
</div>
</IonItem>
</>
);
};
export default React.memo(OrderHistoryItem);

View File

@@ -0,0 +1,229 @@
import {
IonAvatar,
IonBackButton,
IonButton,
IonButtons,
IonCard,
IonCardContent,
IonCardHeader,
IonCardSubtitle,
IonCardTitle,
IonChip,
IonContent,
IonHeader,
IonIcon,
IonItem,
IonLabel,
IonList,
IonPage,
IonText,
IonTitle,
IonToolbar,
useIonRouter,
} from '@ionic/react';
import React from 'react';
import { Virtuoso } from 'react-virtuoso';
import { arrowBackCircleOutline, share, star, starOutline } from 'ionicons/icons';
import './style.scss';
import OrderHistoryItem from './OrderHistoryItem';
const orderList = [
{
status: '準備中',
quantity: 1,
price: 1,
name: 'テスト',
time: '23年02月08日 16時43分',
},
{
status: '提供済',
quantity: 1,
price: 1,
name: 'テスト',
time: '23年02月08日 16時43分',
},
{
status: 'キャンセル',
quantity: 1,
price: 1,
name: 'テスト',
time: '23年02月08日 16時43分',
},
];
interface OrderHistoryProps {}
const OrderHistory: React.FC<OrderHistoryProps> = () => {
const route = useIonRouter();
const test = () => {
console.log(route);
};
return (
<>
<IonPage>
<IonHeader className="ion-no-border">
<IonToolbar>
<IonButtons slot="start">
<IonBackButton text="" defaultHref="/tabs/schedule"></IonBackButton>
</IonButtons>
<IonText></IonText>
<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>
<div
style={{
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
}}
>
<IonCard
slot="fixed"
style={{ top: '0.5rem', height: 'calc( 100% - 30% - 3rem )', flexGrow: 1 }}
>
<IonCardContent
style={{
padding: 0,
margin: 0,
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-start',
}}
>
<IonText style={{ padding: '1rem', fontSize: '1.1rem', fontWeight: 'bold' }}>
order
</IonText>
<Virtuoso
style={{ flexGrow: 1, height: '100%' }}
totalCount={100}
itemContent={(index) => {
return (
<>
<OrderHistoryItem
status={'準備中'}
quantity={1}
price={1}
name={'テスト'}
time={'23年02月08日 16時43分'}
/>
<OrderHistoryItem
status={'提供済'}
quantity={1}
price={1}
name={'テスト'}
time={'23年02月08日 16時43分'}
/>
<OrderHistoryItem
status={'キャンセル'}
quantity={1}
price={1}
name={'テスト'}
time={'23年02月08日 16時43分'}
/>
</>
);
}}
/>
</IonCardContent>
</IonCard>
<div>
<IonCard slot="fixed" style={{ bottom: '0.5rem', left: '0.1rem', right: '0.1rem' }}>
<IonCardContent>
<div style={{ opacity: 0.9 }}>
<div
style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-end' }}
>
<div>
<IonText
style={{ color: '#16a085', fontSize: '1.3rem', fontWeight: 'bold' }}
>
</IonText>
</div>
<div
style={{
width: '45%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
}}
>
<IonText
style={{ color: '#16a085', fontSize: '1.3rem', fontWeight: 'bold' }}
>
¥1000
</IonText>
</div>
</div>
<div
style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-end' }}
>
<div>
<IonText style={{ color: '#2c3e50', fontSize: '1rem', fontWeight: 'bold' }}>
</IonText>
</div>
<div
style={{
width: '45%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
}}
>
1
</div>
</div>
<div
style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-end' }}
>
<div>
<IonText style={{ color: '#2c3e50', fontSize: '1rem', fontWeight: 'bold' }}>
</IonText>
</div>
<div
style={{
color: '#2c3e50',
width: '45%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-end',
}}
>
¥1
</div>
</div>
</div>
</IonCardContent>
</IonCard>
</div>
</div>
</IonContent>
</IonPage>
</>
);
};
export default React.memo(OrderHistory);

View File

@@ -0,0 +1,2 @@
#order-history-page {
}

View File

@@ -1,106 +0,0 @@
import React from 'react';
import {
IonContent,
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonButton,
IonIcon,
useIonViewWillEnter,
} from '@ionic/react';
import { arrowForward } from 'ionicons/icons';
import { setMenuEnabled } from '../data/sessions/sessions.actions';
import { setHasSeenTutorial } from '../data/user/user.actions';
import './Tutorial.scss';
import { connect } from '../data/connect';
import { RouteComponentProps } from 'react-router';
interface OwnProps extends RouteComponentProps {}
interface DispatchProps {
setHasSeenTutorial: typeof setHasSeenTutorial;
setMenuEnabled: typeof setMenuEnabled;
}
interface TutorialProps extends OwnProps, DispatchProps {}
const Tutorial: React.FC<TutorialProps> = ({ history, setHasSeenTutorial, setMenuEnabled }) => {
useIonViewWillEnter(() => {
setMenuEnabled(false);
});
const startApp = async () => {
await setHasSeenTutorial(true);
await setMenuEnabled(true);
history.push('/tabs/schedule', { direction: 'none' });
};
return (
<IonPage id="tutorial-page">
<IonHeader no-border>
<IonToolbar>
<IonButtons slot="end">
<IonButton color="primary" onClick={startApp}>
Skip
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<div className="slider">
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-1.png" alt="" className="slide-image" />
<h2 className="slide-title">
Welcome to <b>ICA</b>
</h2>
<p>
The <b>ionic conference app</b> is a practical preview of the ionic framework in
action, and a demonstration of proper code use.
</p>
</div>
</section>
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-2.png" alt="" className="slide-image" />
<h2 className="slide-title">What is Ionic?</h2>
<p>
<b>Ionic Framework</b> is an open source SDK that enables developers to build high
quality mobile apps with web technologies like HTML, CSS, and JavaScript.
</p>
</div>
</section>
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-3.png" alt="" className="slide-image" />
<h2 className="slide-title">What is Ionic Appflow?</h2>
<p>
<b>Ionic Appflow</b> is a powerful set of services and features built on top of
Ionic Framework that brings a totally new level of app development agility to mobile
dev teams.
</p>
</div>
</section>
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-4.png" alt="" className="slide-image" />
<h2 className="slide-title">Ready to Play?</h2>
<IonButton fill="clear" onClick={startApp}>
Continue
<IonIcon slot="end" icon={arrowForward} />
</IonButton>
</div>
</section>
</div>
</IonContent>
</IonPage>
);
};
export default connect<OwnProps, {}, DispatchProps>({
mapDispatchToProps: {
setHasSeenTutorial,
setMenuEnabled,
},
component: Tutorial,
});

View File

@@ -1,115 +0,0 @@
import React, { useRef, useEffect } from 'react';
import {
IonContent,
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonButton,
IonIcon,
useIonViewWillEnter,
} from '@ionic/react';
import { arrowForward } from 'ionicons/icons';
import { setMenuEnabled } from '../../data/sessions/sessions.actions';
import { setHasSeenTutorial } from '../../data/user/user.actions';
import './Tutorial.scss';
import { connect } from '../../data/connect';
import { RouteComponentProps } from 'react-router';
interface OwnProps extends RouteComponentProps {}
interface DispatchProps {
setHasSeenTutorial: typeof setHasSeenTutorial;
setMenuEnabled: typeof setMenuEnabled;
}
interface TutorialProps extends OwnProps, DispatchProps {}
const Tutorial: React.FC<TutorialProps> = ({ history, setHasSeenTutorial, setMenuEnabled }) => {
const sliderRef = useRef<HTMLDivElement>(null);
useIonViewWillEnter(() => {
setMenuEnabled(false);
// Scroll to first slide when entering the tutorial
if (sliderRef.current) {
sliderRef.current.scrollTo({
left: 0,
behavior: 'smooth',
});
}
});
const startApp = async () => {
await setHasSeenTutorial(true);
await setMenuEnabled(true);
history.push('/tabs/schedule', { direction: 'none' });
};
return (
<IonPage id="tutorial-page">
<IonHeader className="ion-no-border">
<IonToolbar>
<IonButtons slot="end">
<IonButton color="primary" onClick={startApp}>
Skip
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<div className="slider" ref={sliderRef}>
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-1.png" alt="" className="slide-image" />
<h2 className="slide-title">
Welcome to <b>ICA</b>
</h2>
<p>
The <b>ionic conference app</b> is a practical preview of the ionic framework in
action, and a demonstration of proper code use.
</p>
</div>
</section>
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-2.png" alt="" className="slide-image" />
<h2 className="slide-title">What is Ionic?</h2>
<p>
<b>Ionic Framework</b> is an open source SDK that enables developers to build high
quality mobile apps with web technologies like HTML, CSS, and JavaScript.
</p>
</div>
</section>
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-3.png" alt="" className="slide-image" />
<h2 className="slide-title">What is Ionic Appflow?</h2>
<p>
<b>Ionic Appflow</b> is a powerful set of services and features built on top of
Ionic Framework that brings a totally new level of app development agility to mobile
dev teams.
</p>
</div>
</section>
<section>
<div className="swiper-item">
<img src="assets/img/ica-slidebox-img-4.png" alt="" className="slide-image" />
<h2 className="slide-title">Ready to Play?</h2>
<IonButton fill="clear" onClick={startApp}>
Continue
<IonIcon slot="end" icon={arrowForward} />
</IonButton>
</div>
</section>
</div>
</IonContent>
</IonPage>
);
};
export default connect<OwnProps, {}, DispatchProps>({
mapDispatchToProps: {
setHasSeenTutorial,
setMenuEnabled,
},
component: Tutorial,
});

View File

@@ -20,9 +20,6 @@
},
{
"path": "98_AI_workspace"
},
{
"path": "99_references"
}
],
"settings": {