-
-
Notifications
You must be signed in to change notification settings - Fork 7
Full PWA Support Implementation #435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { MetadataRoute } from 'next' | ||
|
|
||
| export default function manifest(): MetadataRoute.Manifest { | ||
| return { | ||
| name: 'QCX - AI-powered Search & Analysis', | ||
| short_name: 'QCX', | ||
| description: 'A minimalistic AI-powered search tool that uses advanced models for deep analysis and geospatial data.', | ||
| start_url: '/', | ||
| display: 'standalone', | ||
| background_color: '#171717', | ||
| theme_color: '#171717', | ||
| icons: [ | ||
| { | ||
| src: '/icon-192.png', | ||
| sizes: '192x192', | ||
| type: 'image/png', | ||
| purpose: 'any', | ||
| }, | ||
| { | ||
| src: '/icon-512.png', | ||
| sizes: '512x512', | ||
| type: 'image/png', | ||
| purpose: 'any', | ||
| }, | ||
| { | ||
| src: '/icon-1024-maskable.png', | ||
| sizes: '1024x1024', | ||
| type: 'image/png', | ||
| purpose: 'maskable', | ||
| }, | ||
| ], | ||
|
Comment on lines
+12
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The manifest defines a SuggestionAdd a 512x512 icons: [
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png', purpose: 'any' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png', purpose: 'any' },
{ src: '/icon-512-maskable.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
{ src: '/icon-1024-maskable.png', sizes: '1024x1024', type: 'image/png', purpose: 'maskable' },
],Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion. |
||
| screenshots: [ | ||
| { | ||
| src: '/images/opengraph-image.png', | ||
| sizes: '1200x630', | ||
| type: 'image/png', | ||
| label: 'QCX Home Screen', | ||
| }, | ||
| ], | ||
| categories: ['search', 'ai', 'productivity'], | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| 'use client' | ||
|
|
||
| import { useEffect, useState } from 'react' | ||
| import { X, Share } from 'lucide-react' | ||
| import { Button } from '@/components/ui/button' | ||
|
|
||
| export function IOSInstallPrompt() { | ||
| const [showPrompt, setShowPrompt] = useState(false) | ||
|
|
||
| useEffect(() => { | ||
| // Check if it's iOS | ||
| const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream | ||
|
|
||
| // Check if it's already in standalone mode (installed) | ||
| const isStandalone = window.matchMedia('(display-mode: standalone)').matches || | ||
| (window.navigator as any).standalone | ||
|
|
||
|
Comment on lines
+10
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The iOS detection and SuggestionConsider replacing UA parsing and useEffect(() => {
const ua = navigator.userAgent
const isIOS =
/iPad|iPhone|iPod/.test(ua) ||
// iPadOS sometimes reports as Mac; detect touch support
(ua.includes('Mac') && 'ontouchend' in document)
const isStandalone =
window.matchMedia('(display-mode: standalone)').matches ||
// iOS Safari (installed) exposes `navigator.standalone`
('standalone' in navigator && (navigator as Navigator & { standalone?: boolean }).standalone)
const dismissed = sessionStorage.getItem('ios-pwa-prompt-dismissed') === 'true'
if (isIOS && !isStandalone && !dismissed) setShowPrompt(true)
}, [])This keeps behavior but reduces brittleness and removes Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion. |
||
| // Check if the prompt was already dismissed this session | ||
| const isDismissed = sessionStorage.getItem('ios-pwa-prompt-dismissed') | ||
|
|
||
| if (isIOS && !isStandalone && !isDismissed) { | ||
| setShowPrompt(true) | ||
| } | ||
| }, []) | ||
|
|
||
| const handleDismiss = () => { | ||
| setShowPrompt(false) | ||
| sessionStorage.setItem('ios-pwa-prompt-dismissed', 'true') | ||
| } | ||
|
|
||
| if (!showPrompt) return null | ||
|
|
||
| return ( | ||
| <div className="fixed bottom-4 left-4 right-4 z-[100] md:max-w-sm md:left-auto"> | ||
| <div className="bg-background border rounded-lg shadow-lg p-4 relative animate-in fade-in slide-in-from-bottom-4 duration-300"> | ||
| <button | ||
| onClick={handleDismiss} | ||
| className="absolute top-2 right-2 text-muted-foreground hover:text-foreground" | ||
| > | ||
| <X className="h-4 w-4" /> | ||
| </button> | ||
|
Comment on lines
+36
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The close control is implemented as a plain SuggestionAdd an accessible label (and optionally set <button
type="button"
aria-label="Dismiss install prompt"
onClick={handleDismiss}
className="absolute top-2 right-2 text-muted-foreground hover:text-foreground"
>
<X className="h-4 w-4" />
</button>Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this suggestion. |
||
| <div className="flex items-center gap-3"> | ||
| <div className="bg-primary/10 p-2 rounded-md"> | ||
| <Share className="h-6 w-6 text-primary" /> | ||
| </div> | ||
| <div> | ||
| <h3 className="font-semibold text-sm">Install QCX</h3> | ||
| <p className="text-xs text-muted-foreground mt-1"> | ||
| Tap <span className="inline-block bg-muted px-1 rounded mx-1">Share</span> then <span className="font-semibold">Add to Home Screen</span> to install as an app. | ||
| </p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
titleis currently a plain string ('QCX - AI-powered Search'), while the manifest uses a different name ('QCX - AI-powered Search & Analysis') andappleWebApp.titleuses'QCX'. This mismatch can create inconsistent install/UI labels across platforms and metadata previews.Consider centralizing these values (e.g., a shared
APP_NAME/APP_DESCRIPTIONconstant) sometadata,manifest(), andappleWebApp.titlestay aligned.Suggestion
Unify the app naming/description by extracting constants shared by both
app/layout.tsxandapp/manifest.ts(e.g.,lib/app-metadata.ts). Example:Then in layout/manifest:
Reply with "@CharlieHelps yes please" if you’d like me to add a commit that consolidates these strings and updates both files accordingly.