| 1 | /** |
| 2 | * WechatBrowserOverlay - 全局 App 内置浏览器打开提示蒙版 |
| 3 | * 在微信、支付宝内置浏览器打开页面时,引导用户切换到系统浏览器继续使用。 |
| 4 | */ |
| 5 | |
| 6 | 'use client' |
| 7 | |
| 8 | import { Ellipsis, Globe } from 'lucide-react' |
| 9 | import { useEffect, useState } from 'react' |
| 10 | import { useTransClient } from '@/app/i18n/client' |
| 11 | import { isBrowserOpenGuideWebView } from '@/utils/browser' |
| 12 | |
| 13 | interface WechatBrowserOverlayProps { |
| 14 | variant?: 'themed' | 'standalone' |
| 15 | } |
| 16 | |
| 17 | interface WechatBrowserOverlayCopy { |
| 18 | cornerHint: string |
| 19 | bubbleLine1: string |
| 20 | bubbleLine2: string |
| 21 | openInBrowser: string |
| 22 | description: string |
| 23 | } |
| 24 | |
| 25 | export function WechatBrowserOverlay({ variant = 'themed' }: WechatBrowserOverlayProps) { |
| 26 | const { t } = useTransClient('common') |
| 27 | const [open, setOpen] = useState(false) |
| 28 | |
| 29 | useEffect(() => { |
| 30 | setOpen(isBrowserOpenGuideWebView()) |
| 31 | }, []) |
| 32 | |
| 33 | if (!open) |
| 34 | return null |
| 35 | |
| 36 | const copy = { |
| 37 | cornerHint: t('wechatBrowserMask.cornerHint'), |
| 38 | bubbleLine1: t('wechatBrowserMask.bubbleLine1'), |
| 39 | bubbleLine2: t('wechatBrowserMask.bubbleLine2'), |
| 40 | openInBrowser: t('wechatBrowserMask.openInBrowser'), |
| 41 | description: t('wechatBrowserMask.description'), |
| 42 | } |
| 43 | |
| 44 | if (variant === 'standalone') |
| 45 | return <StandaloneWechatBrowserOverlay copy={copy} /> |
| 46 | |
| 47 | return ( |
| 48 | <div |
| 49 | aria-label={copy.openInBrowser} |
| 50 | aria-modal="true" |
| 51 | role="dialog" |
| 52 | className="fixed inset-0 z-[1200] bg-slate-900/92 text-slate-50" |
| 53 | > |
| 54 | <div className="pointer-events-none absolute right-6 top-3 flex flex-col items-end gap-1.5 md:right-8 md:top-5"> |
| 55 | <div className="rounded-full border border-slate-200/20 bg-slate-800/70 px-2.5 py-1 text-[11px] font-medium tracking-[0.08em] text-slate-100"> |
| 56 | {copy.cornerHint} |
| 57 | </div> |
| 58 | |
| 59 | <div className="relative h-16 w-16 text-slate-50 md:h-20 md:w-20"> |
| 60 | <svg |
| 61 | aria-hidden="true" |
| 62 | viewBox="0 0 96 96" |
| 63 | className="h-full w-full overflow-visible drop-shadow-lg" |
| 64 | > |
| 65 | <defs> |
| 66 | <marker |
| 67 | id="wechat-browser-arrow-head" |
| 68 | markerWidth="10" |
| 69 | markerHeight="10" |
| 70 | refX="8" |
| 71 | refY="5" |
| 72 | orient="auto-start-reverse" |
| 73 | > |
| 74 | <path d="M0 0L9 5L0 10" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" /> |
| 75 | </marker> |
| 76 | </defs> |
| 77 | <path |
| 78 | d="M14 74C28 71 42 62 54 47C64 35 71 23 78 11" |
| 79 | fill="none" |
| 80 | stroke="currentColor" |
| 81 | strokeWidth="4.5" |
| 82 | strokeLinecap="round" |
| 83 | strokeLinejoin="round" |
| 84 | strokeDasharray="7 7" |
| 85 | markerEnd="url(#wechat-browser-arrow-head)" |
| 86 | /> |
| 87 | </svg> |
| 88 | </div> |
| 89 | </div> |
| 90 | |
| 91 | <div className="flex min-h-full flex-col items-center justify-center px-6 pb-20 pt-24"> |
| 92 | <div className="pointer-events-none flex w-full max-w-sm flex-col items-center"> |
| 93 | <div className="rounded-[28px] border border-dashed border-slate-200/70 px-6 py-5 text-center text-lg leading-8 text-slate-50"> |
| 94 | <p>{copy.bubbleLine1}</p> |
| 95 | <p>{copy.bubbleLine2}</p> |
| 96 | </div> |
| 97 | |
| 98 | <div className="mt-8 flex items-center gap-4"> |
| 99 | <div className="flex h-14 w-14 items-center justify-center rounded-full bg-slate-50 text-slate-900 shadow-lg"> |
| 100 | <Ellipsis className="h-6 w-6" /> |
| 101 | </div> |
| 102 | <div className="flex items-center gap-2 rounded-md bg-slate-100 px-4 py-3 text-sm font-semibold text-slate-900 shadow-lg"> |
| 103 | <Globe className="h-4 w-4" /> |
| 104 | <span>{copy.openInBrowser}</span> |
| 105 | </div> |
| 106 | </div> |
| 107 | |
| 108 | <p className="mt-6 max-w-xs text-center text-sm leading-6 text-slate-200/90"> |
| 109 | {copy.description} |
| 110 | </p> |
| 111 | </div> |
| 112 | </div> |
| 113 | </div> |
| 114 | ) |
| 115 | } |
| 116 | |
| 117 | function StandaloneWechatBrowserOverlay({ copy }: { copy: WechatBrowserOverlayCopy }) { |
| 118 | return ( |
| 119 | <div |
| 120 | aria-label={copy.openInBrowser} |
| 121 | aria-modal="true" |
| 122 | role="dialog" |
| 123 | style={{ |
| 124 | position: 'fixed', |
| 125 | inset: 0, |
| 126 | zIndex: 1200, |
| 127 | backgroundColor: 'rgb(15 23 42 / 0.92)', |
| 128 | color: 'rgb(248 250 252)', |
| 129 | }} |
| 130 | > |
| 131 | <div |
| 132 | style={{ |
| 133 | pointerEvents: 'none', |
| 134 | position: 'absolute', |
| 135 | top: 12, |
| 136 | right: 24, |
| 137 | display: 'flex', |
| 138 | flexDirection: 'column', |
| 139 | alignItems: 'flex-end', |
| 140 | gap: 6, |
| 141 | }} |
| 142 | > |
| 143 | <div |
| 144 | style={{ |
| 145 | borderRadius: 999, |
| 146 | border: '1px solid rgb(226 232 240 / 0.2)', |
| 147 | backgroundColor: 'rgb(30 41 59 / 0.7)', |
| 148 | padding: '4px 10px', |
| 149 | fontSize: 11, |
| 150 | fontWeight: 600, |
| 151 | letterSpacing: '0.08em', |
| 152 | color: 'rgb(241 245 249)', |
| 153 | }} |
| 154 | > |
| 155 | {copy.cornerHint} |
| 156 | </div> |
| 157 | |
| 158 | <svg |
| 159 | aria-hidden="true" |
| 160 | viewBox="0 0 96 96" |
| 161 | style={{ |
| 162 | width: 72, |
| 163 | height: 72, |
| 164 | overflow: 'visible', |
| 165 | color: 'rgb(248 250 252)', |
| 166 | }} |
| 167 | > |
| 168 | <defs> |
| 169 | <marker |
| 170 | id="wechat-browser-standalone-arrow-head" |
| 171 | markerWidth="10" |
| 172 | markerHeight="10" |
| 173 | refX="8" |
| 174 | refY="5" |
| 175 | orient="auto-start-reverse" |
| 176 | > |
| 177 | <path d="M0 0L9 5L0 10" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" /> |
| 178 | </marker> |
| 179 | </defs> |
| 180 | <path |
| 181 | d="M14 74C28 71 42 62 54 47C64 35 71 23 78 11" |
| 182 | fill="none" |
| 183 | stroke="currentColor" |
| 184 | strokeWidth="4.5" |
| 185 | strokeLinecap="round" |
| 186 | strokeLinejoin="round" |
| 187 | strokeDasharray="7 7" |
| 188 | markerEnd="url(#wechat-browser-standalone-arrow-head)" |
| 189 | /> |
| 190 | </svg> |
| 191 | </div> |
| 192 | |
| 193 | <div |
| 194 | style={{ |
| 195 | display: 'flex', |
| 196 | minHeight: '100%', |
| 197 | flexDirection: 'column', |
| 198 | alignItems: 'center', |
| 199 | justifyContent: 'center', |
| 200 | padding: '96px 24px 80px', |
| 201 | boxSizing: 'border-box', |
| 202 | fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif', |
| 203 | }} |
| 204 | > |
| 205 | <div style={{ pointerEvents: 'none', width: '100%', maxWidth: 384, textAlign: 'center' }}> |
| 206 | <div |
| 207 | style={{ |
| 208 | borderRadius: 28, |
| 209 | border: '1px dashed rgb(226 232 240 / 0.7)', |
| 210 | padding: '20px 24px', |
| 211 | fontSize: 18, |
| 212 | lineHeight: 1.75, |
| 213 | }} |
| 214 | > |
| 215 | <p style={{ margin: 0 }}>{copy.bubbleLine1}</p> |
| 216 | <p style={{ margin: 0 }}>{copy.bubbleLine2}</p> |
| 217 | </div> |
| 218 | |
| 219 | <div |
| 220 | style={{ |
| 221 | display: 'flex', |
| 222 | alignItems: 'center', |
| 223 | justifyContent: 'center', |
| 224 | gap: 16, |
| 225 | marginTop: 32, |
| 226 | }} |
| 227 | > |
| 228 | <div |
| 229 | style={{ |
| 230 | display: 'flex', |
| 231 | width: 56, |
| 232 | height: 56, |
| 233 | alignItems: 'center', |
| 234 | justifyContent: 'center', |
| 235 | borderRadius: 999, |
| 236 | backgroundColor: 'rgb(248 250 252)', |
| 237 | color: 'rgb(15 23 42)', |
| 238 | fontSize: 28, |
| 239 | fontWeight: 700, |
| 240 | boxShadow: '0 10px 24px rgb(2 6 23 / 0.35)', |
| 241 | }} |
| 242 | > |
| 243 | ⋯ |
| 244 | </div> |
| 245 | <div |
| 246 | style={{ |
| 247 | display: 'flex', |
| 248 | alignItems: 'center', |
| 249 | gap: 8, |
| 250 | borderRadius: 8, |
| 251 | backgroundColor: 'rgb(241 245 249)', |
| 252 | color: 'rgb(15 23 42)', |
| 253 | padding: '12px 16px', |
| 254 | fontSize: 14, |
| 255 | fontWeight: 700, |
| 256 | boxShadow: '0 10px 24px rgb(2 6 23 / 0.35)', |
| 257 | }} |
| 258 | > |
| 259 | <span aria-hidden="true">🌐</span> |
| 260 | <span>{copy.openInBrowser}</span> |
| 261 | </div> |
| 262 | </div> |
| 263 | |
| 264 | <p style={{ margin: '24px auto 0', maxWidth: 320, color: 'rgb(226 232 240 / 0.9)', fontSize: 14, lineHeight: 1.7 }}> |
| 265 | {copy.description} |
| 266 | </p> |
| 267 | </div> |
| 268 | </div> |
| 269 | </div> |
| 270 | ) |
| 271 | } |
| 272 |