pwa setting

This commit is contained in:
hamed
2026-06-13 13:28:39 +03:30
parent 0b5d017b14
commit d50fb96542
15 changed files with 782 additions and 0 deletions
@@ -0,0 +1,45 @@
import React from 'react';
import { ArrowDownTrayIcon, XMarkIcon } from '@heroicons/react/24/outline';
import { usePwaInstall } from '../../hooks/usePwaInstall';
export default function PwaInstallBanner() {
const { promptEvent, isInstalled, isDismissed, install, dismiss } = usePwaInstall();
if (isInstalled || isDismissed || !promptEvent) return null;
const handleInstall = async () => {
await install();
};
return (
<div style={{
position: 'fixed', bottom: 20, right: 20, left: 20, zIndex: 9999,
maxWidth: 420, margin: '0 auto',
background: 'var(--surface)',
border: '1px solid var(--border)',
borderRadius: 'var(--r)',
boxShadow: '0 8px 32px rgba(0,0,0,.12)',
padding: '14px 16px',
display: 'flex', alignItems: 'center', gap: 12,
direction: 'rtl',
}}>
<div style={{ width: 36, height: 36, borderRadius: 10, background: 'var(--primary-soft)', color: 'var(--primary-700)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<ArrowDownTrayIcon style={{ width: 18, height: 18 }} />
</div>
<div style={{ flex: 1, minWidth: 0 }}>
<p style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', margin: 0 }}>نصب ClinicPro</p>
<p style={{ fontSize: 12, color: 'var(--text-3)', margin: '2px 0 0' }}>دسترسی سریعتر از روی صفحه اصلی</p>
</div>
<button
onClick={handleInstall}
style={{ height: 34, padding: '0 14px', borderRadius: 'var(--r-sm)', border: 'none', background: 'var(--primary)', color: 'var(--on-primary)', fontSize: 13, fontWeight: 700, cursor: 'pointer', flexShrink: 0 }}>
نصب
</button>
<button
onClick={dismiss}
style={{ width: 28, height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center', border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--text-3)', borderRadius: 6, flexShrink: 0 }}>
<XMarkIcon style={{ width: 16, height: 16 }} />
</button>
</div>
);
}
+167
View File
@@ -0,0 +1,167 @@
import React, { useState } from 'react';
import { createPortal } from 'react-dom';
import { ArrowDownTrayIcon, XMarkIcon, CheckIcon } from '@heroicons/react/24/outline';
import { usePwaInstall } from '../../hooks/usePwaInstall';
function detectIOS(): boolean {
return /iphone|ipad|ipod/i.test(navigator.userAgent);
}
export default function PwaLoginCard() {
const { promptEvent, isInstalled, isDismissed, install, dismiss } = usePwaInstall();
const [showSteps, setShowSteps] = useState(false);
if (isInstalled || isDismissed) return null;
const isIOS = detectIOS();
const handleInstallClick = async () => {
if (promptEvent) {
const accepted = await install();
if (accepted) return;
}
// native prompt not available or dismissed → show manual steps
setShowSteps(true);
};
const steps = isIOS
? [
'Safari را باز کنید (فقط Safari از نصب پشتیبانی می‌کند)',
'دکمه Share (مربع با فلش رو به بالا) را در پایین صفحه بزنید',
'گزینه «Add to Home Screen» را انتخاب کنید',
'«Add» را بزنید — تمام!',
]
: [
'در Chrome منوی سه‌نقطه (⋮) را در گوشه بالا باز کنید',
'گزینه «Install ClinicPro» یا «Install app» را انتخاب کنید',
'در پنجره‌ای که باز می‌شود «Install» را بزنید',
];
return createPortal(
<div
onClick={dismiss}
style={{
position: 'fixed', inset: 0, zIndex: 10000,
background: 'rgba(0,0,0,0.45)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
padding: 20, direction: 'rtl',
}}
>
<div
onClick={(e) => e.stopPropagation()}
style={{
width: '100%', maxWidth: 380,
background: 'var(--surface)',
borderRadius: 'var(--r)',
boxShadow: '0 20px 60px rgba(0,0,0,0.2)',
overflow: 'hidden',
}}
>
{/* Header */}
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '16px 18px 14px',
borderBottom: '1px solid var(--border)',
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{
width: 36, height: 36, borderRadius: 10,
background: 'var(--primary)', color: 'var(--on-primary)',
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
}}>
<ArrowDownTrayIcon style={{ width: 18, height: 18 }} />
</div>
<span style={{ fontSize: 15, fontWeight: 800, color: 'var(--text)' }}>نصب ClinicPro</span>
</div>
<button onClick={dismiss} style={{
width: 30, height: 30, display: 'flex', alignItems: 'center', justifyContent: 'center',
border: 'none', background: 'transparent', cursor: 'pointer',
color: 'var(--text-3)', borderRadius: 8,
}}>
<XMarkIcon style={{ width: 18, height: 18 }} />
</button>
</div>
{/* Body */}
<div style={{ padding: '20px 18px' }}>
{!showSteps ? (
<>
{/* App icon + name */}
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', marginBottom: 20 }}>
<div style={{
width: 72, height: 72, borderRadius: 18,
background: 'var(--primary-soft)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 36, marginBottom: 10,
}}></div>
<p style={{ fontSize: 16, fontWeight: 800, color: 'var(--text)', margin: 0 }}>ClinicPro</p>
<p style={{ fontSize: 12, color: 'var(--text-3)', margin: '4px 0 0' }}>پنل مدیریت کلینیک</p>
</div>
<p style={{ fontSize: 13, color: 'var(--text-2)', textAlign: 'center', lineHeight: 1.7, margin: '0 0 20px' }}>
اپلیکیشن را روی دستگاه خود نصب کنید و بدون باز کردن مرورگر وارد شوید.
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<button
onClick={handleInstallClick}
style={{
width: '100%', height: 46, borderRadius: 'var(--r-sm)',
border: 'none', background: 'var(--primary)', color: 'var(--on-primary)',
fontSize: 15, fontWeight: 700, cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
}}>
<ArrowDownTrayIcon style={{ width: 18, height: 18 }} />
نصب اپلیکیشن
</button>
<button
onClick={dismiss}
style={{
width: '100%', height: 40, borderRadius: 'var(--r-sm)',
border: '1px solid var(--border)', background: 'transparent',
color: 'var(--text-2)', fontSize: 13, fontWeight: 600, cursor: 'pointer',
}}>
بعداً
</button>
</div>
</>
) : (
<>
<p style={{ fontSize: 13, fontWeight: 700, color: 'var(--text)', margin: '0 0 16px' }}>
مراحل نصب:
</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, marginBottom: 20 }}>
{steps.map((step, i) => (
<div key={i} style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
<div style={{
width: 24, height: 24, borderRadius: '50%', flexShrink: 0,
background: 'var(--primary-soft)', color: 'var(--primary-700)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 12, fontWeight: 700,
}}>
{i + 1}
</div>
<p style={{ fontSize: 13, color: 'var(--text-2)', lineHeight: 1.6, margin: 0 }}>{step}</p>
</div>
))}
</div>
<button
onClick={dismiss}
style={{
width: '100%', height: 44, borderRadius: 'var(--r-sm)',
border: 'none', background: 'var(--primary)', color: 'var(--on-primary)',
fontSize: 14, fontWeight: 700, cursor: 'pointer',
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
}}>
<CheckIcon style={{ width: 16, height: 16 }} />
متوجه شدم
</button>
</>
)}
</div>
</div>
</div>,
document.body
);
}