Newer
Older
mobile.raikyakun.app / node / src / app / page.tsx
nutrition on 24 Jul 2024 9 KB first commit
'use client'
import { useEffect, useMemo, useState } from 'react'
import Image from 'next/image'
import styles from './page.module.css'
import { Box, Alert, Button, TextField, Typography, InputAdornment, IconButton, FormControl, InputLabel, OutlinedInput, FormHelperText,
	Divider
 } from '@mui/material'
import { ContentCopy as ContentCopyIcon } from '@mui/icons-material'
import { getMobileOS, getBrowser } from '@/utils'
import useWebpush from './useWebpush'
import LockIcon from '@mui/icons-material/Lock';
import { useIndexedDB } from '@/hooks'
import dynamic from 'next/dynamic'
import { diffTimeCalc } from '@/utils/date'


// ITPについい
// https://webkit.org/tracking-prevention/#intelligent-tracking-prevention-itp
const URL = 'https://mobile.raikyakun.app/'

export default function Home() {
	const [os, setOS] = useState('Other')
	const [browser, setBrowser] = useState('Other')
	const [available, setAvailable] = useState(false)
	const [now, setNow] = useState<Date>(new Date())
	const [isLatestCallActive, setIsLatestCallActive] = useState(false)
	const { latestCall, callList, update } = useIndexedDB()

	
	useEffect(()=> {
		const os = getMobileOS()
		const browser = getBrowser()
		setOS(os)
		setBrowser(browser)
		setAvailable( (os == 'Android' && browser == 'Chrome') || (os == 'iOS' && browser == 'Safari'))
	}, [])

	useEffect(() => {
		update()

		setInterval(()=> {
			setNow(new Date())
		}, 5000)

		const handleVisibilityChange = () => {
			if (document.visibilityState === 'visible') {
				update()
			}
		}
		document.addEventListener('visibilitychange', handleVisibilityChange)
		return () => {
		  document.removeEventListener('visibilitychange', handleVisibilityChange)
		}
	}, [])

	useEffect(()=>{
		if(!latestCall || latestCall.responder) return
		
		// ISO 8601形式の日付文字列をDateオブジェクトに変換
		const callDate = new Date(latestCall.createdAt)
			if (isNaN(callDate.getTime())) {
			console.error('Invalid date format')
			return
		}

		// 両者の差をミリ秒単位で計算し、秒単位に変換
		const now = new Date()
		const durationInSeconds = (now.getTime() - callDate.getTime()) / 1000
		
		// 現在時刻から60秒以内の場合はページを切り替え
		
		if(durationInSeconds >= 60) return
		if(latestCall.responder == null){
			setIsLatestCallActive(true)
		} else {
			location.href = `/actions?data=${JSON.stringify(latestCall)}`
		}
	}, [latestCall])

	const copyToClipboard = async () => {
		await global.navigator.clipboard.writeText(URL)
	}
	
	const { Subscribe, isSubscribed, errorMessage } = useWebpush()
	

	
	return (
		<main className={styles.main}>
			<div style={{padding: '2rem'}}>
				<Typography sx={{textAlign: 'center'}} variant="h5" component="h1">らいきゃくん通知{os != 'Other' ? <><br />for {os}</> : ''}</Typography>
			</div>
			{latestCall && 
			<Box 
				sx={{
					mt: 2, 
					border: `2px solid ${isLatestCallActive?'royalblue':'lightgray'}`, 
					width: '80%', 
					display: 'flex', 
					justifyContent: "space-between", 
					borderRadius: 2, 
					padding: 3, 
					boxShadow: `0px 0px 6px ${isLatestCallActive?'lightskyblue':'gray'} inset`
				}}
				onClick={()=>{if(isLatestCallActive) location.href = `/actions?data=${JSON.stringify(latestCall)}`}}
			>
				<Typography variant="h5">
					<span style={{fontWeight: 'bold', fontSize: '80%'}}>最後の呼び出し</span><br />
					<span style={{fontSize: '50%'}}>宛 先:</span>[{latestCall.dst.group}] {latestCall.dst.name}
					{latestCall.visitor && <><br /><span style={{fontSize: '50%'}}>来訪者:</span> {latestCall.visitor}</>}
					{latestCall.responder && <><br /><span style={{fontSize: '50%'}}>対応者: {latestCall.responder}</span></>}
				</Typography>
				<Box sx={{color: 'gray'}}>
					{diffTimeCalc(now, new Date(latestCall.createdAt))}
				</Box>
			</Box>}
			<div style={{padding: '2rem'}}>
				<div>
					{os == 'Other' && <Alert severity="warning">このページを
					スマートフォンで開いてください</Alert>}
					{os == 'Android' && browser != 'Chrome' && <Alert severity="warning">このページはChromeで開いてください</Alert>}
					{os == 'iOS' && browser != 'Safari' && <Alert severity="warning">このページはSafariで開いてください</Alert>}
				</div>
				<div style={{textAlign: 'center'}}>
					{os == 'Other' && <img style={{width:'40%'}} src="url.png" />}
					{!available && <FormControl
						fullWidth 
						variant="outlined"
					>
						<InputLabel htmlFor="url">URLをコピー</InputLabel>
						<OutlinedInput
							id="url"
							value={URL}
							endAdornment={
								<InputAdornment position="end">
									<IconButton
										edge="end"
										onClick={copyToClipboard}
									>
										<ContentCopyIcon />
									</IconButton>
								</InputAdornment>
							}
							label="URLをコピー"
						/>
					</FormControl>}
				</div>
				<div>
					{os == 'iOS' && browser == 'Safari' && isSubscribed == false && <img style={{width:'100%'}} src="/images/install_for_ios.png" />}
					
				</div>
			</div>
			{ Subscribe }
			{errorMessage != '' && <Alert severity="error">{errorMessage}</Alert>}
			{errorMessage != '' && os == 'iOS' && browser == 'Safari' && <FormHelperText sx={{margin: 2, padding: 2, border: '1px solid gray', borderRadius: 5}}>
				通知許可ダイアログで「許可しない」を選択してしまった場合は<br />
				<span style={{verticalAlign: 'middle'}}>「<img src="/images/iphone-settings-icon.png" width={20} height={20} style={{verticalAlign: 'middle'}} />設定」アプリ
				>「<img src="/images/iphone-bell-icon.png" width={20} height={20} style={{verticalAlign: 'middle'}} />通知」>「通知スタイル」
				>「<img src="/images/maskable_icon_x144.png" width={20} height={20} style={{verticalAlign: 'middle'}} />らいきゃくん通知」>「通知を許可」から許可してください</span>
				<a target="_blank" href="https://support.apple.com/ja-jp/guide/iphone/iph7c3d96bab/ios" ><Button size="small">詳しくはこちら</Button></a>
			</FormHelperText>}
			{errorMessage != '' && os == 'Android' && browser == 'Chrome' && <FormHelperText sx={{margin: 2, padding: 2, border: '1px solid gray', borderRadius: 5}}>
				<span style={{verticalAlign: 'middle'}}>通知許可ダイアログで「ブロック」を選択してしまった場合は<br />
				アドレスバーの左にある<img src="/images/android-chrome.png" width={20} height={20} style={{verticalAlign: 'middle'}} />アイコンをクリックして権限をご確認ください</span>
			</FormHelperText>}
			{errorMessage != '' && os == 'Other' && browser == 'Chrome' && <FormHelperText sx={{margin: 2, padding: 2, border: '1px solid gray', borderRadius: 5}}>
				<span style={{verticalAlign: 'middle'}}>通知許可ダイアログで「ブロック」を選択してしまった場合は<br />
				アドレスバーの左にある<LockIcon sx={{verticalAlign: 'middle'}}/>鍵アイコンをクリックして通知設定をご確認ください</span>
			</FormHelperText>}
			{errorMessage != '' && os == 'Other' && browser == 'Safari' && <FormHelperText sx={{margin: 2, padding: 2, border: '1px solid gray', borderRadius: 5}}>
				通知許可ダイアログで「許可しない」を選択してしまった場合は<br />
				左上メニューバーから「Safari」>「設定…」>「Webサイト」>一般「通知」と進み「mobile.raikyakun.app」を許可に設定してください
			</FormHelperText>}

			{callList.length > 0 && <><Typography variant="h4" sx={{mt: 7}}>通知履歴</Typography>
			<FormHelperText>受付履歴とは異なります</FormHelperText></>}
			{callList.map(call => 
				<Box key={call.callId} sx={{width: '100%', display: 'flex', justifyContent: "space-between", mb: 2, p: 2, borderBottom: '1px solid lightgray'}}>
				<Typography variant="h5">
					<span style={{fontSize: '50%'}}>宛 先:</span>[{call.dst.group}] {call.dst.name}
					{call.visitor && <><br /><span style={{fontSize: '50%'}}>来訪者:</span> {call.visitor}</>}
					{call.responder && <><br /><span style={{fontSize: '50%'}}>対応者: {call.responder}</span></>}
				</Typography>
				<Box sx={{color: 'gray'}}>
					{diffTimeCalc(now, new Date(call.createdAt))}
				</Box>
			</Box>)}
			
		</main>
	)
}


const ServiceWorkerStatus: React.FC = () => {
	const [registrations, setRegistrations] = useState<string[]>(['確認中...']);
  
	useEffect(() => {
	  if ('serviceWorker' in navigator) {
		navigator.serviceWorker.getRegistrations().then(regs => {
		  if (regs.length === 0) {
			setRegistrations(['サービスワーカーは登録されていません。']);
		  } else {
			const swInfo = regs.map((reg, index) => 
			  `(${index + 1}) スコープ: ${reg.scope}, 状態: ${reg.active ? 'アクティブ' : '非アクティブ'}`
			);
			setRegistrations(swInfo);
		  }
		});
	  } else {
		setRegistrations(['このブラウザはサービスワーカーをサポートしていません。']);
	  }
	}, []);
  
	return (
	  <div>
		<h1>Service Workerの状態</h1>
		<ul>
		  {registrations.map((reg, index) => (
			<li key={index}>{reg}</li>
		  ))}
		</ul>
	  </div>
	);
  };