@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.
- package/README.md +204 -18
- package/package.json +5 -5
- package/src/components/errors/index.ts +9 -0
- package/src/components/errors/types.ts +38 -0
- package/src/layouts/AppLayout/AppLayout.tsx +33 -45
- package/src/layouts/AppLayout/BaseApp.tsx +104 -33
- package/src/layouts/AuthLayout/AuthContext.tsx +7 -1
- package/src/layouts/AuthLayout/OAuthProviders.tsx +1 -10
- package/src/layouts/AuthLayout/OTPForm.tsx +1 -0
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +1 -1
- package/src/layouts/PublicLayout/PublicLayout.tsx +1 -1
- package/src/layouts/PublicLayout/components/PublicMobileDrawer.tsx +1 -1
- package/src/layouts/PublicLayout/components/PublicNavigation.tsx +1 -1
- package/src/layouts/_components/UserMenu.tsx +1 -1
- package/src/layouts/index.ts +1 -1
- package/src/layouts/types/index.ts +47 -0
- package/src/layouts/types/layout.types.ts +61 -0
- package/src/layouts/types/providers.types.ts +65 -0
- package/src/layouts/types/ui.types.ts +103 -0
- package/src/snippets/Analytics/index.ts +1 -0
- package/src/snippets/Analytics/types.ts +10 -0
- package/src/snippets/PWAInstall/@docs/README.md +92 -0
- package/src/snippets/PWAInstall/README.md +185 -0
- package/src/snippets/{PWA → PWAInstall}/components/A2HSHint.tsx +85 -84
- package/src/snippets/PWAInstall/components/DesktopGuide.tsx +229 -0
- package/src/snippets/PWAInstall/context/InstallContext.tsx +102 -0
- package/src/snippets/{PWA → PWAInstall}/hooks/useInstallPrompt.ts +3 -0
- package/src/snippets/{PWA → PWAInstall}/index.ts +12 -31
- package/src/snippets/{PWA → PWAInstall}/types/components.ts +0 -6
- package/src/snippets/PWAInstall/types/config.ts +22 -0
- package/src/snippets/{PWA → PWAInstall}/types/index.ts +4 -4
- package/src/snippets/{PWA → PWAInstall}/utils/localStorage.ts +1 -23
- package/src/snippets/PushNotifications/@docs/README.md +191 -0
- package/src/snippets/PushNotifications/@docs/guides/django-integration.md +648 -0
- package/src/snippets/PushNotifications/@docs/guides/service-worker.md +467 -0
- package/src/snippets/PushNotifications/@docs/guides/vapid-setup.md +352 -0
- package/src/snippets/PushNotifications/README.md +328 -0
- package/src/snippets/{PWA → PushNotifications}/config.ts +2 -2
- package/src/snippets/PushNotifications/context/DjangoPushContext.tsx +190 -0
- package/src/snippets/{PWA → PushNotifications}/hooks/useDjangoPush.ts +63 -81
- package/src/snippets/{PWA → PushNotifications}/hooks/usePushNotifications.ts +12 -8
- package/src/snippets/PushNotifications/index.ts +87 -0
- package/src/snippets/PushNotifications/types/config.ts +28 -0
- package/src/snippets/PushNotifications/types/index.ts +9 -0
- package/src/snippets/PushNotifications/utils/localStorage.ts +60 -0
- package/src/snippets/PushNotifications/utils/logger.ts +149 -0
- package/src/snippets/PushNotifications/utils/platform.ts +151 -0
- package/src/snippets/index.ts +37 -12
- package/src/layouts/shared/index.ts +0 -21
- package/src/layouts/shared/types.ts +0 -247
- package/src/snippets/PWA/@refactoring/ARCHITECTURE_ANALYSIS.md +0 -1179
- package/src/snippets/PWA/@refactoring/EXECUTIVE_SUMMARY.md +0 -271
- package/src/snippets/PWA/@refactoring/README.md +0 -204
- package/src/snippets/PWA/@refactoring/REFACTORING_PROPOSALS.md +0 -1109
- package/src/snippets/PWA/@refactoring2/COMPARISON-WITH-NEXTJS.md +0 -718
- package/src/snippets/PWA/@refactoring2/P1-FIXES-COMPLETED.md +0 -188
- package/src/snippets/PWA/@refactoring2/POST-P0-ANALYSIS.md +0 -362
- package/src/snippets/PWA/@refactoring2/README.md +0 -85
- package/src/snippets/PWA/@refactoring2/RECOMMENDATIONS.md +0 -1321
- package/src/snippets/PWA/@refactoring2/REMAINING-ISSUES.md +0 -557
- package/src/snippets/PWA/README.md +0 -387
- package/src/snippets/PWA/context/DjangoPushContext.tsx +0 -105
- package/src/snippets/PWA/context/InstallContext.tsx +0 -118
- package/src/snippets/PWA/context/PushContext.tsx +0 -156
- /package/src/layouts/{shared → types}/README.md +0 -0
- /package/src/snippets/{PWA/@docs/research.md → PWAInstall/@docs/research/ios-android-install-flows.md} +0 -0
- /package/src/snippets/{PWA → PWAInstall}/components/IOSGuide.tsx +0 -0
- /package/src/snippets/{PWA → PWAInstall}/components/IOSGuideDrawer.tsx +0 -0
- /package/src/snippets/{PWA → PWAInstall}/components/IOSGuideModal.tsx +0 -0
- /package/src/snippets/{PWA → PWAInstall}/hooks/useIsPWA.ts +0 -0
- /package/src/snippets/{PWA → PWAInstall}/types/install.ts +0 -0
- /package/src/snippets/{PWA → PWAInstall}/types/platform.ts +0 -0
- /package/src/snippets/{PWA → PWAInstall}/utils/logger.ts +0 -0
- /package/src/snippets/{PWA → PWAInstall}/utils/platform.ts +0 -0
- /package/src/snippets/{PWA → PushNotifications}/components/PushPrompt.tsx +0 -0
- /package/src/snippets/{PWA → PushNotifications}/types/push.ts +0 -0
- /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 {
|
|
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
|
-
*
|
|
52
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
102
|
-
if (!
|
|
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,
|
|
98
|
+
}, [shouldShow, resetAfterDays, delayMs, demo]);
|
|
117
99
|
|
|
118
100
|
const handleDismiss = () => {
|
|
119
101
|
setShow(false);
|
|
120
|
-
// Don't save to localStorage if
|
|
121
|
-
if (!
|
|
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
|
-
//
|
|
129
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
<
|
|
199
|
-
onClick={
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
</
|
|
210
|
+
<X className="w-4 h-4 text-zinc-400" />
|
|
211
|
+
</button>
|
|
207
212
|
</div>
|
|
208
213
|
</div>
|
|
209
214
|
</div>
|
|
210
215
|
|
|
211
|
-
{/*
|
|
212
|
-
{(isIOSPlatform ||
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
);
|