@djangocfg/layouts 2.1.37 → 2.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +204 -18
  2. package/package.json +5 -5
  3. package/src/components/errors/index.ts +9 -0
  4. package/src/components/errors/types.ts +38 -0
  5. package/src/layouts/AppLayout/AppLayout.tsx +33 -45
  6. package/src/layouts/AppLayout/BaseApp.tsx +104 -33
  7. package/src/layouts/AuthLayout/AuthContext.tsx +7 -1
  8. package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -10
  9. package/src/layouts/AuthLayout/OTPForm.tsx +1 -0
  10. package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
  11. package/src/layouts/PublicLayout/PublicLayout.tsx +1 -1
  12. package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
  13. package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
  14. package/src/layouts/_components/UserMenu.tsx +1 -1
  15. package/src/layouts/index.ts +1 -1
  16. package/src/layouts/types/index.ts +47 -0
  17. package/src/layouts/types/layout.types.ts +61 -0
  18. package/src/layouts/types/providers.types.ts +65 -0
  19. package/src/layouts/types/ui.types.ts +103 -0
  20. package/src/snippets/Analytics/index.ts +1 -0
  21. package/src/snippets/Analytics/types.ts +10 -0
  22. package/src/snippets/PWAInstall/@docs/README.md +92 -0
  23. package/src/snippets/PWAInstall/README.md +185 -0
  24. package/src/snippets/{PWA → PWAInstall}/components/A2HSHint.tsx +85 -84
  25. package/src/snippets/PWAInstall/components/DesktopGuide.tsx +229 -0
  26. package/src/snippets/PWAInstall/context/InstallContext.tsx +102 -0
  27. package/src/snippets/{PWA → PWAInstall}/hooks/useInstallPrompt.ts +3 -0
  28. package/src/snippets/{PWA → PWAInstall}/index.ts +12 -31
  29. package/src/snippets/{PWA → PWAInstall}/types/components.ts +0 -6
  30. package/src/snippets/PWAInstall/types/config.ts +22 -0
  31. package/src/snippets/{PWA → PWAInstall}/types/index.ts +4 -4
  32. package/src/snippets/{PWA → PWAInstall}/utils/localStorage.ts +1 -23
  33. package/src/snippets/PushNotifications/@docs/README.md +191 -0
  34. package/src/snippets/PushNotifications/@docs/guides/django-integration.md +648 -0
  35. package/src/snippets/PushNotifications/@docs/guides/service-worker.md +467 -0
  36. package/src/snippets/PushNotifications/@docs/guides/vapid-setup.md +352 -0
  37. package/src/snippets/PushNotifications/README.md +328 -0
  38. package/src/snippets/{PWA → PushNotifications}/config.ts +2 -2
  39. package/src/snippets/PushNotifications/context/DjangoPushContext.tsx +190 -0
  40. package/src/snippets/{PWA → PushNotifications}/hooks/useDjangoPush.ts +63 -81
  41. package/src/snippets/{PWA → PushNotifications}/hooks/usePushNotifications.ts +12 -8
  42. package/src/snippets/PushNotifications/index.ts +87 -0
  43. package/src/snippets/PushNotifications/types/config.ts +28 -0
  44. package/src/snippets/PushNotifications/types/index.ts +9 -0
  45. package/src/snippets/PushNotifications/utils/localStorage.ts +60 -0
  46. package/src/snippets/PushNotifications/utils/logger.ts +149 -0
  47. package/src/snippets/PushNotifications/utils/platform.ts +151 -0
  48. package/src/snippets/index.ts +37 -12
  49. package/src/layouts/shared/index.ts +0 -21
  50. package/src/layouts/shared/types.ts +0 -247
  51. package/src/snippets/PWA/@refactoring/ARCHITECTURE_ANALYSIS.md +0 -1179
  52. package/src/snippets/PWA/@refactoring/EXECUTIVE_SUMMARY.md +0 -271
  53. package/src/snippets/PWA/@refactoring/README.md +0 -204
  54. package/src/snippets/PWA/@refactoring/REFACTORING_PROPOSALS.md +0 -1109
  55. package/src/snippets/PWA/@refactoring2/COMPARISON-WITH-NEXTJS.md +0 -718
  56. package/src/snippets/PWA/@refactoring2/P1-FIXES-COMPLETED.md +0 -188
  57. package/src/snippets/PWA/@refactoring2/POST-P0-ANALYSIS.md +0 -362
  58. package/src/snippets/PWA/@refactoring2/README.md +0 -85
  59. package/src/snippets/PWA/@refactoring2/RECOMMENDATIONS.md +0 -1321
  60. package/src/snippets/PWA/@refactoring2/REMAINING-ISSUES.md +0 -557
  61. package/src/snippets/PWA/README.md +0 -387
  62. package/src/snippets/PWA/context/DjangoPushContext.tsx +0 -105
  63. package/src/snippets/PWA/context/InstallContext.tsx +0 -118
  64. package/src/snippets/PWA/context/PushContext.tsx +0 -156
  65. /package/src/layouts/{shared → types}/README.md +0 -0
  66. /package/src/snippets/{PWA/@docs/research.md → PWAInstall/@docs/research/ios-android-install-flows.md} +0 -0
  67. /package/src/snippets/{PWA → PWAInstall}/components/IOSGuide.tsx +0 -0
  68. /package/src/snippets/{PWA → PWAInstall}/components/IOSGuideDrawer.tsx +0 -0
  69. /package/src/snippets/{PWA → PWAInstall}/components/IOSGuideModal.tsx +0 -0
  70. /package/src/snippets/{PWA → PWAInstall}/hooks/useIsPWA.ts +0 -0
  71. /package/src/snippets/{PWA → PWAInstall}/types/install.ts +0 -0
  72. /package/src/snippets/{PWA → PWAInstall}/types/platform.ts +0 -0
  73. /package/src/snippets/{PWA → PWAInstall}/utils/logger.ts +0 -0
  74. /package/src/snippets/{PWA → PWAInstall}/utils/platform.ts +0 -0
  75. /package/src/snippets/{PWA → PushNotifications}/components/PushPrompt.tsx +0 -0
  76. /package/src/snippets/{PWA → PushNotifications}/types/push.ts +0 -0
  77. /package/src/snippets/{PWA → PushNotifications}/utils/vapid.ts +0 -0
@@ -0,0 +1,92 @@
1
+ # PWAInstall Documentation
2
+
3
+ Comprehensive documentation for the PWAInstall snippet.
4
+
5
+ ## Overview
6
+
7
+ PWAInstall handles **Progressive Web App installation** on user devices (Add to Home Screen functionality).
8
+
9
+ **Responsibility**: Device installation only (not push notifications)
10
+
11
+ ## Documentation Structure
12
+
13
+ ### `/research/`
14
+ Research and best practices:
15
+ - **[ios-android-install-flows.md](./research/ios-android-install-flows.md)** - iOS vs Android PWA installation patterns, limitations, and best practices (2024-2025)
16
+
17
+ ### `/architecture/`
18
+ Architecture and design:
19
+ - Coming soon: Architecture decisions, component design, state management
20
+
21
+ ### `/legacy/`
22
+ Historical documentation:
23
+ - Old architecture analysis (before snippet split)
24
+ - Refactoring history
25
+
26
+ ## Quick Navigation
27
+
28
+ ### For Users
29
+ Start here if you want to use PWAInstall:
30
+ - [Main README](../README.md) - Quick start and API reference
31
+ - [Migration Guide](../../MIGRATION.md) - Migrating from old PWA snippet
32
+
33
+ ### For Contributors
34
+ Start here if you want to understand or modify PWAInstall:
35
+ - [iOS/Android Install Flows](./research/ios-android-install-flows.md) - Platform-specific behavior
36
+ - [Architecture](./architecture/) - How it's built
37
+
38
+ ### For Researchers
39
+ Start here if you want to understand PWA installation patterns:
40
+ - [Research](./research/) - Industry research and best practices
41
+
42
+ ## Key Concepts
43
+
44
+ ### Platform Asymmetry
45
+
46
+ | Aspect | Android Chrome | iOS Safari |
47
+ |--------|----------------|------------|
48
+ | Install API | `beforeinstallprompt` | ❌ No API |
49
+ | User effort | 1 tap | 3-4 taps |
50
+ | Detection | Event-based | Heuristic |
51
+ | Guidance | Optional | **Required** |
52
+
53
+ **PWAInstall handles this asymmetry transparently.**
54
+
55
+ ### Components
56
+
57
+ ```
58
+ A2HSHint (Unified hint)
59
+ ├── Android → Native install prompt
60
+ └── iOS → Visual guide (IOSGuide)
61
+ ├── Mobile → IOSGuideDrawer
62
+ └── Desktop → IOSGuideModal
63
+ ```
64
+
65
+ ### State Management
66
+
67
+ ```
68
+ useInstall() hook
69
+ ├── Platform detection (isIOS, isAndroid, isSafari)
70
+ ├── Installation state (isInstalled, canPrompt)
71
+ └── Install action (install())
72
+ ```
73
+
74
+ ## Related Documentation
75
+
76
+ - **[PushNotifications Docs](../../PushNotifications/@docs/)** - Web push notifications (separate concern)
77
+ - **[Refactoring Summary](../../REFACTORING_SUMMARY.md)** - Why snippets were split
78
+ - **[Migration Guide](../../MIGRATION.md)** - How to migrate from old PWA snippet
79
+
80
+ ## Contributing
81
+
82
+ When adding documentation:
83
+ 1. **Research** → `/research/` - Industry patterns, browser behavior
84
+ 2. **Architecture** → `/architecture/` - Design decisions, component structure
85
+ 3. **Historical** → `/legacy/` - Old docs (keep for reference)
86
+
87
+ ## Questions?
88
+
89
+ - Implementation questions → See [Main README](../README.md)
90
+ - Architecture questions → See [/architecture/](./architecture/)
91
+ - Platform behavior → See [/research/](./research/)
92
+ - Migration questions → See [Migration Guide](../../MIGRATION.md)
@@ -0,0 +1,185 @@
1
+ # PWA Install
2
+
3
+ Progressive Web App installation snippet for web applications.
4
+
5
+ ## Responsibility
6
+
7
+ **Installation of PWA on user's device** (Add to Home Screen)
8
+
9
+ - iOS Safari: Visual guide for manual installation
10
+ - Android Chrome: Native install prompt
11
+ - Unified UX across platforms
12
+ - Smart dismissal with auto-reset
13
+
14
+ ## Quick Start
15
+
16
+ ```tsx
17
+ // app/layout.tsx
18
+ import { PwaProvider, A2HSHint } from '@/snippets/PWAInstall';
19
+
20
+ export default function RootLayout({ children }) {
21
+ return (
22
+ <html>
23
+ <body>
24
+ <PwaProvider>
25
+ {children}
26
+ <A2HSHint resetAfterDays={3} />
27
+ </PwaProvider>
28
+ </body>
29
+ </html>
30
+ );
31
+ }
32
+ ```
33
+
34
+ ## What You Get
35
+
36
+ - **Unified UX** → Same hint position (bottom) for both iOS & Android
37
+ - **iOS Safari** → Click hint → Opens visual step-by-step guide
38
+ - **Android Chrome** → Click hint → Native install prompt
39
+ - **Visual guide** → Adaptive (drawer on mobile, modal on desktop)
40
+ - **Zero config** → Works out of the box
41
+ - **Smart reset** → Re-appears after 3 days (user gets second chance)
42
+ - **No spam** → Dismissible, respects user choice
43
+
44
+ ## API
45
+
46
+ ### `<PwaProvider>`
47
+
48
+ Wrap your app once:
49
+
50
+ ```tsx
51
+ <PwaProvider>
52
+ {children}
53
+ </PwaProvider>
54
+ ```
55
+
56
+ ### `<A2HSHint />`
57
+
58
+ Unified install hint for iOS & Android (auto-shows, dismissible):
59
+
60
+ ```tsx
61
+ <A2HSHint
62
+ resetAfterDays={3} // Default: 3 days (set to null for never)
63
+ delayMs={3000} // Default: 3 seconds
64
+ forceShow={isDevelopment} // Show on ANY browser (for dev testing)
65
+ logo="/logo192.png" // App logo URL (fallback: Share icon)
66
+ />
67
+ ```
68
+
69
+ **Behavior (iOS Safari):**
70
+ - Shows after 3 seconds
71
+ - Text: "Keep terminal with you → Tap to learn how"
72
+ - Click → Opens visual guide (adaptive: drawer on mobile, modal on desktop)
73
+ - Dismissible (saved to localStorage)
74
+ - Auto-resets after 3 days
75
+
76
+ **Behavior (Android Chrome):**
77
+ - Shows after 3 seconds
78
+ - Text: "Install Cmdop → Tap to install"
79
+ - Click → Triggers native install prompt
80
+ - Dismissible (saved to localStorage)
81
+ - Auto-resets after 3 days
82
+
83
+ ### `useInstall()` hook
84
+
85
+ Access install state anywhere:
86
+
87
+ ```tsx
88
+ import { useInstall } from '@/snippets/PWAInstall';
89
+
90
+ function Header() {
91
+ const { isIOS, isInstalled, canPrompt, install } = useInstall();
92
+
93
+ return (
94
+ <header>
95
+ {canPrompt && <button onClick={install}>Install</button>}
96
+ {isInstalled && <span>✓ Installed</span>}
97
+ </header>
98
+ );
99
+ }
100
+ ```
101
+
102
+ **Returns:**
103
+ ```ts
104
+ {
105
+ isIOS: boolean;
106
+ isAndroid: boolean;
107
+ isSafari: boolean;
108
+ isChrome: boolean;
109
+ isInstalled: boolean;
110
+ canPrompt: boolean;
111
+ install: () => Promise<'accepted' | 'dismissed' | null>;
112
+ }
113
+ ```
114
+
115
+ ## Usage with Push Notifications
116
+
117
+ Use together with the PushNotifications snippet:
118
+
119
+ ```tsx
120
+ import { PwaProvider, A2HSHint } from '@/snippets/PWAInstall';
121
+ import { DjangoPushProvider, PushPrompt } from '@/snippets/PushNotifications';
122
+
123
+ export default function Layout({ children }) {
124
+ return (
125
+ <PwaProvider>
126
+ <DjangoPushProvider vapidPublicKey={VAPID_KEY}>
127
+ {children}
128
+
129
+ {/* PWA Install hint */}
130
+ <A2HSHint resetAfterDays={3} />
131
+
132
+ {/* Push notification prompt (after PWA install) */}
133
+ <PushPrompt
134
+ requirePWA={true}
135
+ delayMs={5000}
136
+ resetAfterDays={7}
137
+ />
138
+ </DjangoPushProvider>
139
+ </PwaProvider>
140
+ );
141
+ }
142
+ ```
143
+
144
+ ## Browser Support
145
+
146
+ | Platform | Browser | Support |
147
+ |----------|---------|---------|
148
+ | iOS | Safari | ✅ Visual guide |
149
+ | iOS | Chrome/Firefox | ❌ No PWA support |
150
+ | Android | Chrome | ✅ Native prompt |
151
+ | Android | Firefox | ⚠️ Manual only |
152
+ | Desktop | Chrome/Edge | ✅ Native prompt |
153
+
154
+ ## Architecture
155
+
156
+ ```
157
+ PWAInstall/
158
+ ├── context/
159
+ │ └── InstallContext.tsx # Install state management
160
+ ├── components/
161
+ │ ├── A2HSHint.tsx # Unified install hint
162
+ │ ├── IOSGuide.tsx # Visual guide wrapper
163
+ │ ├── IOSGuideDrawer.tsx # Mobile guide
164
+ │ └── IOSGuideModal.tsx # Desktop guide
165
+ ├── hooks/
166
+ │ ├── useInstallPrompt.ts # Install prompt logic
167
+ │ └── useIsPWA.ts # PWA detection
168
+ ├── utils/
169
+ │ ├── platform.ts # Platform detection
170
+ │ ├── localStorage.ts # Persistence
171
+ │ └── logger.ts # Logging
172
+ └── types/
173
+ ├── platform.ts
174
+ ├── install.ts
175
+ └── components.ts
176
+ ```
177
+
178
+ ## Separation from Push Notifications
179
+
180
+ This snippet is **completely independent** from push notifications:
181
+
182
+ - **PWAInstall** → Handles device installation
183
+ - **PushNotifications** → Handles web push subscriptions
184
+
185
+ Both can be used together or separately.
@@ -9,9 +9,6 @@
9
9
  * - Unified UX: Same position, same style, same behavior
10
10
  *
11
11
  * Auto-resets after 3 days (configurable)
12
- *
13
- * Optionally shows push notification prompt after PWA installation
14
- * by providing pushNotifications.vapidPublicKey
15
12
  */
16
13
 
17
14
  import React, { useState, useEffect } from 'react';
@@ -21,10 +18,9 @@ import { cn } from '@djangocfg/ui-nextjs/lib';
21
18
 
22
19
  import { useInstall } from '../context/InstallContext';
23
20
  import { IOSGuide } from './IOSGuide';
24
- import { PushPrompt } from './PushPrompt';
21
+ import { DesktopGuide } from './DesktopGuide';
25
22
  import { pwaLogger } from '../utils/logger';
26
23
  import { markA2HSDismissed, isA2HSDismissedRecently } from '../utils/localStorage';
27
- import type { PushNotificationOptions } from '../types';
28
24
 
29
25
  const DEFAULT_RESET_DAYS = 3;
30
26
 
@@ -48,58 +44,44 @@ interface A2HSHintProps {
48
44
  delayMs?: number;
49
45
 
50
46
  /**
51
- * Force show on ANY browser (ignores platform detection)
52
- * Useful for testing on desktop during development
47
+ * Demo mode - shows hint on all platforms with appropriate guides
48
+ * Production: only iOS Safari & Android Chrome
49
+ * Demo: shows on desktop too with desktop install guide
53
50
  * @default false
54
51
  */
55
- forceShow?: boolean;
52
+ demo?: boolean;
56
53
 
57
54
  /**
58
55
  * App logo URL to display in hint
59
56
  * If not provided, uses Share icon
60
57
  */
61
58
  logo?: string;
62
-
63
- /**
64
- * Enable push notifications prompt after PWA install
65
- * Provide VAPID public key to enable
66
- */
67
- pushNotifications?: PushNotificationOptions & {
68
- /**
69
- * Delay before showing push prompt after PWA install (ms)
70
- * @default 5000
71
- */
72
- delayMs?: number;
73
- /**
74
- * Number of days before re-showing dismissed push prompt
75
- * @default 7
76
- */
77
- resetAfterDays?: number;
78
- };
79
59
  }
80
60
 
81
- export function A2HSHint({
61
+ export function A2HSHint({
82
62
  className,
83
- resetAfterDays = DEFAULT_RESET_DAYS,
84
- delayMs = 3000,
85
- forceShow = false,
86
- logo,
87
- pushNotifications
63
+ resetAfterDays = DEFAULT_RESET_DAYS,
64
+ delayMs = 3000,
65
+ demo = false,
66
+ logo,
88
67
  }: A2HSHintProps = {}) {
89
- const { isIOS, isSafari, isInstalled, canPrompt, install } = useInstall();
68
+ const { isIOS, isSafari, isAndroid, isDesktop, isInstalled, canPrompt, install } = useInstall();
90
69
  const [show, setShow] = useState(false);
91
70
  const [showGuide, setShowGuide] = useState(false);
92
71
  const [installing, setInstalling] = useState(false);
93
72
 
94
73
  // Determine if should show hint
95
- const shouldShow = forceShow || (!isInstalled && ((isIOS && isSafari) || canPrompt));
74
+ // Production: only iOS Safari & Android Chrome with native prompt
75
+ // Demo: show on all platforms (desktop, iOS, Android)
76
+ const shouldShow = demo
77
+ ? !isInstalled // Demo: show on all platforms if not installed
78
+ : !isInstalled && ((isIOS && isSafari) || canPrompt); // Production: only supported platforms
96
79
 
97
80
  useEffect(() => {
98
- // Only show on iOS Safari or Android Chrome (unless forceShow for dev testing)
99
81
  if (!shouldShow) return;
100
82
 
101
- // Check if previously dismissed (skip localStorage check if forceShow)
102
- if (!forceShow && typeof window !== 'undefined') {
83
+ // Check if previously dismissed (skip localStorage check in demo mode)
84
+ if (!demo && typeof window !== 'undefined') {
103
85
  // If resetAfterDays is null, never reset (check with very large number)
104
86
  if (resetAfterDays === null) {
105
87
  if (isA2HSDismissedRecently(Number.MAX_SAFE_INTEGER)) {
@@ -113,25 +95,30 @@ export function A2HSHint({
113
95
  // Show after delay (user is already engaged)
114
96
  const timer = setTimeout(() => setShow(true), delayMs);
115
97
  return () => clearTimeout(timer);
116
- }, [shouldShow, resetAfterDays, delayMs, forceShow]);
98
+ }, [shouldShow, resetAfterDays, delayMs, demo]);
117
99
 
118
100
  const handleDismiss = () => {
119
101
  setShow(false);
120
- // Don't save to localStorage if forceShow (dev testing mode)
121
- if (!forceShow) {
102
+ // Don't save to localStorage if demo mode (dev testing)
103
+ if (!demo) {
122
104
  markA2HSDismissed();
123
105
  }
124
106
  };
125
107
 
126
108
  const handleGuideDismiss = () => {
127
109
  setShowGuide(false);
128
- // When guide is dismissed, also dismiss the hint
129
- handleDismiss();
110
+ // In demo mode, keep hint visible after guide is dismissed
111
+ // In production mode, dismiss both guide and hint
112
+ if (!demo) {
113
+ handleDismiss();
114
+ }
130
115
  };
131
116
 
132
117
  const handleClick = async () => {
133
- // forceShow (dev mode) or iOS: Open visual guide
134
- if (forceShow || (isIOS && isSafari)) {
118
+ const isIOSPlatform = isIOS && isSafari;
119
+
120
+ // iOS or Desktop: Open visual guide
121
+ if (isIOSPlatform || isDesktop) {
135
122
  setShowGuide(true);
136
123
  } else if (canPrompt) {
137
124
  // Android: Trigger native install prompt
@@ -152,24 +139,47 @@ export function A2HSHint({
152
139
 
153
140
  // Platform-specific content
154
141
  const isIOSPlatform = isIOS && isSafari;
155
- const title = isIOSPlatform ? 'Keep terminal with you' : 'Install Cmdop';
156
- const subtitle = isIOSPlatform ? (
157
- <>
158
- Tap to learn how <ChevronRight className="w-3 h-3" />
159
- </>
160
- ) : (
161
- <>
162
- Tap to install <Download className="w-3 h-3" />
163
- </>
164
- );
142
+
143
+ // Determine which guide/action to show
144
+ let title: string;
145
+ let subtitle: React.ReactNode;
146
+
147
+ if (isIOSPlatform) {
148
+ title = 'Add to Home Screen';
149
+ subtitle = <>Tap to learn how <ChevronRight className="w-3 h-3" /></>;
150
+ } else if (isDesktop) {
151
+ title = 'Install App';
152
+ subtitle = <>Click to see desktop guide <ChevronRight className="w-3 h-3" /></>;
153
+ } else {
154
+ // Android or other mobile with native prompt
155
+ title = 'Install App';
156
+ subtitle = <>Tap to install <Download className="w-3 h-3" /></>;
157
+ }
165
158
 
166
159
  return (
167
160
  <>
168
161
  <div className={cn(
169
162
  "fixed bottom-4 left-4 right-4 z-50 animate-in slide-in-from-bottom-4 duration-300",
163
+ demo && "relative inset-auto z-auto", // Demo mode: remove fixed positioning
170
164
  className
171
165
  )}>
172
- <div className="w-full bg-zinc-900 border border-zinc-700 rounded-lg p-4 shadow-lg">
166
+ {/* Make entire card clickable */}
167
+ <div
168
+ role="button"
169
+ tabIndex={0}
170
+ onClick={handleClick}
171
+ onKeyDown={(e) => {
172
+ if (e.key === 'Enter' || e.key === ' ') {
173
+ e.preventDefault();
174
+ handleClick();
175
+ }
176
+ }}
177
+ className={cn(
178
+ "w-full bg-zinc-900 border border-zinc-700 rounded-lg p-4 shadow-lg cursor-pointer hover:bg-zinc-800 transition-colors",
179
+ installing && "opacity-70 cursor-not-allowed"
180
+ )}
181
+ aria-disabled={installing}
182
+ >
173
183
  <div className="flex items-center gap-3">
174
184
  {/* App logo or icon */}
175
185
  <div className="flex-shrink-0">
@@ -180,46 +190,37 @@ export function A2HSHint({
180
190
  )}
181
191
  </div>
182
192
 
183
- {/* Content */}
193
+ {/* Content - now just displays, clicking anywhere triggers action */}
184
194
  <div className="flex-1 min-w-0">
185
195
  <p className="text-sm font-medium text-white mb-1">{title}</p>
186
- <Button
187
- onClick={handleClick}
188
- loading={installing}
189
- size="sm"
190
- variant="ghost"
191
- className="text-xs text-zinc-400 hover:text-zinc-300 p-0 h-auto font-normal flex items-center gap-1"
192
- >
193
- {subtitle}
194
- </Button>
196
+ <p className="text-xs text-zinc-400 flex items-center gap-1">
197
+ {installing ? 'Installing...' : subtitle}
198
+ </p>
195
199
  </div>
196
200
 
197
- {/* Close button */}
198
- <Button
199
- onClick={handleDismiss}
200
- size="sm"
201
- variant="ghost"
202
- className="flex-shrink-0 p-1"
201
+ {/* Close button - prevent event bubbling */}
202
+ <button
203
+ onClick={(e) => {
204
+ e.stopPropagation();
205
+ handleDismiss();
206
+ }}
207
+ className="flex-shrink-0 p-1 hover:bg-zinc-700 rounded transition-colors"
203
208
  aria-label="Dismiss"
204
209
  >
205
- <X className="w-4 h-4" />
206
- </Button>
210
+ <X className="w-4 h-4 text-zinc-400" />
211
+ </button>
207
212
  </div>
208
213
  </div>
209
214
  </div>
210
215
 
211
- {/* iOS or forceShow: Detailed guide modal - dismissing guide also dismisses hint */}
212
- {(isIOSPlatform || forceShow) && <IOSGuide open={showGuide} onDismiss={handleGuideDismiss} />}
213
-
214
- {/* Push Notifications Prompt - shown after PWA install if enabled */}
215
- {pushNotifications?.vapidPublicKey && (
216
- <PushPrompt
217
- vapidPublicKey={pushNotifications.vapidPublicKey}
218
- subscribeEndpoint={pushNotifications.subscribeEndpoint}
219
- requirePWA={true}
220
- delayMs={pushNotifications.delayMs || 5000}
221
- resetAfterDays={pushNotifications.resetAfterDays || 7}
222
- />
216
+ {/* Show appropriate guide based on platform */}
217
+ {(isIOSPlatform || (demo && isIOS)) && (
218
+ <IOSGuide open={showGuide} onDismiss={handleGuideDismiss} />
219
+ )}
220
+
221
+ {/* Desktop guide */}
222
+ {isDesktop && (
223
+ <DesktopGuide open={showGuide} onDismiss={handleGuideDismiss} />
223
224
  )}
224
225
  </>
225
226
  );