Skip to content

Commit 4e74d7d

Browse files
committed
Merge feature/mobile-guide: /mobile guide + dashboard install nudge
2 parents 3a081bf + 06bc198 commit 4e74d7d

4 files changed

Lines changed: 265 additions & 0 deletions

File tree

web/app/(public)/mobile/page.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import type { Metadata } from "next";
2+
import Link from "next/link";
3+
import { MarketingNav } from "@/components/marketing-nav";
4+
import { Footer } from "@/components/footer";
5+
import { Smartphone, Apple, Share2, ImageIcon } from "lucide-react";
6+
7+
export const metadata: Metadata = {
8+
title: "shoplit on your phone · shoplit",
9+
description: "Add products to your shoplit cart in one tap from your phone — share from Nykaa, Myntra, Amazon, Flipkart & AJIO.",
10+
};
11+
12+
const androidSteps = [
13+
{ t: "Install shoplit", b: "Open shoplit.in in Chrome → menu (⋮) → “Add to Home screen”. A shoplit icon lands on your home screen. (You can also tap “Install app” on your dashboard.)" },
14+
{ t: "Open a product & tap Share", b: "On Nykaa, Myntra, Amazon, Flipkart or AJIO, open the product page and tap the app’s Share button." },
15+
{ t: "Choose shoplit", b: "Pick shoplit from the share sheet — it opens with the product link and title already filled in." },
16+
{ t: "Pick a cart & add", b: "Tweak the price or image if you like, choose which cart it goes in, and tap Add. Done." },
17+
];
18+
19+
const iosSteps = [
20+
{ t: "Add to Home Screen", b: "Open shoplit.in in Safari → tap Share → “Add to Home Screen”. (iPhone doesn’t let apps share into shoplit — Apple limitation — so adding is by paste.)" },
21+
{ t: "Copy the product link", b: "On the shopping app or site, tap Share → Copy (or copy the page address)." },
22+
{ t: "Paste into shoplit", b: "Open shoplit, go to Add a product, and paste. shoplit pulls the title from the link automatically." },
23+
{ t: "Pick a cart & add", b: "Adjust the price/image if needed, choose the cart, and add." },
24+
];
25+
26+
export default function MobileGuidePage() {
27+
return (
28+
<>
29+
<MarketingNav />
30+
<main className="mx-auto max-w-2xl px-4 sm:px-6 py-16">
31+
{/* HERO */}
32+
<div className="text-center mb-10">
33+
<span
34+
className="inline-grid place-items-center size-14 rounded-2xl text-accent mb-4"
35+
style={{ backgroundColor: "color-mix(in srgb, var(--accent) 12%, transparent)" }}
36+
>
37+
<Smartphone size={26} />
38+
</span>
39+
<p className="text-sm text-accent uppercase tracking-widest font-medium mb-2">shoplit on your phone</p>
40+
<h1 className="font-serif text-4xl sm:text-5xl leading-[1.05] mb-3">Add products in one tap</h1>
41+
<p className="text-muted text-lg leading-relaxed">
42+
No desktop, no copy-pasting details. Install shoplit on your phone and add products
43+
straight from your shopping apps.
44+
</p>
45+
</div>
46+
47+
{/* ANDROID */}
48+
<section className="rounded-2xl border border-rule bg-cream p-6 sm:p-8">
49+
<div className="flex items-center gap-2 mb-4">
50+
<Smartphone size={18} className="text-accent" />
51+
<h2 className="font-serif text-2xl">On Android</h2>
52+
</div>
53+
<ol className="space-y-5">
54+
{androidSteps.map((s, i) => (
55+
<li key={s.t} className="flex gap-4">
56+
<span className="grid place-items-center size-7 shrink-0 rounded-full bg-accent text-cream text-sm font-semibold">{i + 1}</span>
57+
<div>
58+
<p className="font-medium">{s.t}</p>
59+
<p className="text-sm text-muted leading-relaxed">{s.b}</p>
60+
</div>
61+
</li>
62+
))}
63+
</ol>
64+
<div className="mt-6 inline-flex items-center gap-2 text-sm text-muted">
65+
<Share2 size={15} className="text-accent" />
66+
Look for shoplit in the share sheet after installing.
67+
</div>
68+
</section>
69+
70+
{/* iOS */}
71+
<section className="rounded-2xl border border-rule bg-paper p-6 sm:p-8 mt-6">
72+
<div className="flex items-center gap-2 mb-4">
73+
<Apple size={18} className="text-accent" />
74+
<h2 className="font-serif text-2xl">On iPhone</h2>
75+
</div>
76+
<ol className="space-y-5">
77+
{iosSteps.map((s, i) => (
78+
<li key={s.t} className="flex gap-4">
79+
<span className="grid place-items-center size-7 shrink-0 rounded-full bg-ink text-cream text-sm font-semibold">{i + 1}</span>
80+
<div>
81+
<p className="font-medium">{s.t}</p>
82+
<p className="text-sm text-muted leading-relaxed">{s.b}</p>
83+
</div>
84+
</li>
85+
))}
86+
</ol>
87+
</section>
88+
89+
{/* IMAGE NOTE */}
90+
<div className="mt-6 rounded-2xl border border-rule bg-cream p-5 flex items-start gap-3">
91+
<ImageIcon size={18} className="text-accent mt-0.5 shrink-0" />
92+
<p className="text-sm text-muted leading-relaxed">
93+
<strong className="text-ink">About images:</strong> on mobile, paste an image URL if you have one —
94+
otherwise leave it blank and your cart shows a clean placeholder you can update anytime from the editor.
95+
</p>
96+
</div>
97+
98+
{/* CTA */}
99+
<div className="mt-10 flex flex-col sm:flex-row items-center justify-center gap-3">
100+
<Link
101+
href="/add"
102+
className="inline-flex items-center gap-2 rounded-full bg-ink text-cream px-6 py-3 font-medium hover:opacity-90 transition-opacity"
103+
>
104+
Add a product by link →
105+
</Link>
106+
<Link href="/login" className="text-ink underline-offset-4 hover:underline font-medium">
107+
No account yet? Create one free
108+
</Link>
109+
</div>
110+
</main>
111+
<Footer />
112+
</>
113+
);
114+
}

web/app/dashboard/page.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { Cart, User } from "@/lib/types";
77
import { getCurrentUser, listMyCarts } from "@/lib/api-client";
88
import { CartCard } from "@/components/cart-card";
99
import { AnimatedNumber } from "@/components/animated-number";
10+
import { InstallNudge } from "@/components/install-nudge";
1011

1112
export const dynamic = "force-dynamic";
1213

@@ -64,6 +65,8 @@ export default async function DashboardPage() {
6465
</div>
6566
</div>
6667

68+
<InstallNudge />
69+
6770
{isEmpty ? (
6871
<FirstTimeOnboarding firstName={firstName} />
6972
) : (

web/components/footer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export function Footer({ minimal = false }: { minimal?: boolean }) {
66
<div className="mx-auto max-w-6xl px-4 sm:px-6 py-8 text-sm text-muted flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
77
<p className="font-serif text-base text-ink">shoplit</p>
88
<nav className="flex flex-wrap items-center gap-5">
9+
<Link href="/mobile" className="hover:text-ink transition-colors">On mobile</Link>
910
<Link href="/get-extension" className="hover:text-ink transition-colors">Get extension</Link>
1011
<Link href="/roadmap" className="hover:text-ink transition-colors">Roadmap</Link>
1112
<Link href="/feedback" className="hover:text-ink transition-colors">

web/components/install-nudge.tsx

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import Link from "next/link";
5+
import { Smartphone, Share, X } from "lucide-react";
6+
7+
// The `beforeinstallprompt` event isn't in the TS DOM lib.
8+
interface BeforeInstallPromptEvent extends Event {
9+
prompt: () => Promise<void>;
10+
userChoice: Promise<{ outcome: "accepted" | "dismissed" }>;
11+
}
12+
13+
const DISMISS_KEY = "shoplit:installNudgeDismissed";
14+
15+
// Dismissible dashboard card nudging the creator to install shoplit on their
16+
// phone. Android gets a native install button (beforeinstallprompt); iOS gets
17+
// "Add to Home Screen" instructions; desktop gets a pointer to the mobile
18+
// guide. Hides itself when already installed (standalone) or dismissed.
19+
// Renders nothing until mounted so the server markup (none) matches the client.
20+
export function InstallNudge() {
21+
const [mounted, setMounted] = useState(false);
22+
const [hidden, setHidden] = useState(false);
23+
const [platform, setPlatform] = useState<"android" | "ios" | "desktop">("desktop");
24+
const [installEvt, setInstallEvt] = useState<BeforeInstallPromptEvent | null>(null);
25+
26+
useEffect(() => {
27+
setMounted(true);
28+
29+
const standalone =
30+
window.matchMedia?.("(display-mode: standalone)").matches ||
31+
(window.navigator as unknown as { standalone?: boolean }).standalone === true;
32+
if (standalone || localStorage.getItem(DISMISS_KEY)) {
33+
setHidden(true);
34+
return;
35+
}
36+
37+
const ua = navigator.userAgent || "";
38+
if (/android/i.test(ua)) setPlatform("android");
39+
else if (/iphone|ipad|ipod/i.test(ua)) setPlatform("ios");
40+
else setPlatform("desktop");
41+
42+
const onBIP = (e: Event) => {
43+
e.preventDefault();
44+
setInstallEvt(e as BeforeInstallPromptEvent);
45+
};
46+
const onInstalled = () => setHidden(true);
47+
window.addEventListener("beforeinstallprompt", onBIP);
48+
window.addEventListener("appinstalled", onInstalled);
49+
return () => {
50+
window.removeEventListener("beforeinstallprompt", onBIP);
51+
window.removeEventListener("appinstalled", onInstalled);
52+
};
53+
}, []);
54+
55+
if (!mounted || hidden) return null;
56+
57+
const dismiss = () => {
58+
localStorage.setItem(DISMISS_KEY, "1");
59+
setHidden(true);
60+
};
61+
62+
const doInstall = async () => {
63+
if (!installEvt) return;
64+
await installEvt.prompt();
65+
const choice = await installEvt.userChoice;
66+
if (choice.outcome === "accepted") setHidden(true);
67+
setInstallEvt(null);
68+
};
69+
70+
return (
71+
<div className="relative mb-8 rounded-2xl border border-rule bg-paper p-5 sm:p-6">
72+
<button
73+
onClick={dismiss}
74+
aria-label="Dismiss"
75+
className="absolute top-3 right-3 text-muted hover:text-ink transition-colors"
76+
>
77+
<X size={18} />
78+
</button>
79+
<div className="flex items-start gap-4">
80+
<span
81+
className="grid place-items-center size-11 rounded-xl text-accent shrink-0"
82+
style={{ backgroundColor: "color-mix(in srgb, var(--accent) 12%, transparent)" }}
83+
>
84+
<Smartphone size={22} />
85+
</span>
86+
<div className="flex-1 pr-6">
87+
<h3 className="font-serif text-xl mb-1">Add products from your phone</h3>
88+
89+
{platform === "android" && (
90+
<>
91+
<p className="text-sm text-muted mb-3">
92+
Install shoplit, then share any product from Nykaa, Myntra, Amazon &amp; more straight into a cart.
93+
</p>
94+
<div className="flex flex-wrap items-center gap-3">
95+
{installEvt ? (
96+
<button
97+
onClick={doInstall}
98+
className="rounded-full bg-ink text-cream px-5 py-2.5 text-sm font-medium hover:opacity-90 transition-opacity"
99+
>
100+
Install app
101+
</button>
102+
) : (
103+
<span className="text-sm text-muted">
104+
Open Chrome&apos;s menu (⋮) → &ldquo;Add to Home screen&rdquo;.
105+
</span>
106+
)}
107+
<Link href="/mobile" className="text-sm font-medium text-accent underline underline-offset-4 hover:opacity-80">
108+
How it works
109+
</Link>
110+
</div>
111+
</>
112+
)}
113+
114+
{platform === "ios" && (
115+
<>
116+
<p className="text-sm text-muted mb-3">
117+
Add shoplit to your Home Screen, then add products by pasting the product link.
118+
</p>
119+
<p className="text-sm inline-flex items-center gap-1.5">
120+
<Share size={15} className="text-accent" /> Tap <strong>Share</strong><strong>Add to Home Screen</strong>.
121+
</p>
122+
<div className="mt-3">
123+
<Link href="/mobile" className="text-sm font-medium text-accent underline underline-offset-4 hover:opacity-80">
124+
See the full guide →
125+
</Link>
126+
</div>
127+
</>
128+
)}
129+
130+
{platform === "desktop" && (
131+
<>
132+
<p className="text-sm text-muted mb-3">
133+
On your phone, install shoplit to add products in one tap from any shopping app.
134+
</p>
135+
<Link
136+
href="/mobile"
137+
className="inline-block rounded-full bg-ink text-cream px-5 py-2.5 text-sm font-medium hover:opacity-90 transition-opacity"
138+
>
139+
See the mobile guide →
140+
</Link>
141+
</>
142+
)}
143+
</div>
144+
</div>
145+
</div>
146+
);
147+
}

0 commit comments

Comments
 (0)