Add mobile unsupported splash screne

This commit is contained in:
bkellam 2025-02-27 16:10:55 -08:00
parent 7ce10672e1
commit b9352345a3
8 changed files with 84 additions and 13 deletions

View file

@ -118,6 +118,7 @@
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"psl": "^1.15.0", "psl": "^1.15.0",
"react": "^18", "react": "^18",
"react-device-detect": "^2.2.3",
"react-dom": "^18", "react-dom": "^18",
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.0",
"react-hotkeys-hook": "^4.5.1", "react-hotkeys-hook": "^4.5.1",

View file

@ -15,7 +15,7 @@ import { GithubConnectionConfig, GitlabConnectionConfig, GiteaConnectionConfig,
import { encrypt } from "@sourcebot/crypto" import { encrypt } from "@sourcebot/crypto"
import { getConnection } from "./data/connection"; import { getConnection } from "./data/connection";
import { ConnectionSyncStatus, Prisma, OrgRole, RepoIndexingStatus, StripeSubscriptionStatus } from "@sourcebot/db"; import { ConnectionSyncStatus, Prisma, OrgRole, RepoIndexingStatus, StripeSubscriptionStatus } from "@sourcebot/db";
import { headers } from "next/headers" import { cookies, headers } from "next/headers"
import { getStripe } from "@/lib/stripe" import { getStripe } from "@/lib/stripe"
import { getUser } from "@/data/user"; import { getUser } from "@/data/user";
import { Session } from "next-auth"; import { Session } from "next-auth";
@ -26,6 +26,7 @@ import InviteUserEmail from "./emails/inviteUserEmail";
import { createTransport } from "nodemailer"; import { createTransport } from "nodemailer";
import { repositoryQuerySchema } from "./lib/schemas"; import { repositoryQuerySchema } from "./lib/schemas";
import { RepositoryQuery } from "./lib/types"; import { RepositoryQuery } from "./lib/types";
import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME } from "./lib/constants";
const ajv = new Ajv({ const ajv = new Ajv({
validateFormats: false, validateFormats: false,
@ -1231,6 +1232,10 @@ export const getOrgInvites = async (domain: string) =>
}) })
); );
export const dismissMobileUnsupportedSplashScreen = async () => {
await cookies().set(MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME, 'true');
}
////// Helpers /////// ////// Helpers ///////

View file

@ -0,0 +1,42 @@
'use client';
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { TriangleAlert } from "lucide-react";
import { useCallback, useEffect } from "react";
import { dismissMobileUnsupportedSplashScreen } from "@/actions";
import useCaptureEvent from "@/hooks/useCaptureEvent";
export const MobileUnsupportedSplashScreen = () => {
const captureEvent = useCaptureEvent();
useEffect(() => {
captureEvent('wa_mobile_unsupported_splash_screen_displayed', {});
}, [captureEvent]);
const onDismissed = useCallback(() => {
dismissMobileUnsupportedSplashScreen();
captureEvent('wa_mobile_unsupported_splash_screen_dismissed', {});
}, [captureEvent]);
return (
<div className="flex flex-col items-center justify-center p-2 min-h-screen bg-backgroundSecondary">
<Card className="flex flex-col items-center text-center mb-10 p-4 max-w-sm">
<TriangleAlert className="w-10 h-10 mb-4 text-yellow-600/90 dark:text-yellow-300/90" />
<div className="text-2xl font-semibold flex items-center mb-2">
Mobile is not supported.
</div>
<p className="text-sm text-muted-foreground mb-8">
Sourcebot on mobile is still a work in progress. Please use a desktop computer to get the best experience.
</p>
<Button
className="w-full"
variant="outline"
onClick={onDismissed}
>
Continue anyway
</Button>
</Card>
</div>
)
}

View file

@ -6,6 +6,10 @@ import { isServiceError } from "@/lib/utils";
import { OnboardGuard } from "./components/onboardGuard"; import { OnboardGuard } from "./components/onboardGuard";
import { fetchSubscription } from "@/actions"; import { fetchSubscription } from "@/actions";
import { UpgradeGuard } from "./components/upgradeGuard"; import { UpgradeGuard } from "./components/upgradeGuard";
import { cookies, headers } from "next/headers";
import { getSelectorsByUserAgent } from "react-device-detect";
import { MobileUnsupportedSplashScreen } from "./components/mobileUnsupportedSplashScreen";
import { MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME } from "@/lib/constants";
interface LayoutProps { interface LayoutProps {
children: React.ReactNode, children: React.ReactNode,
@ -65,5 +69,15 @@ export default async function Layout({
) )
} }
const headersList = await headers();
const cookieStore = await cookies()
const userAgent = headersList.get('user-agent');
const { isMobile } = getSelectorsByUserAgent(userAgent ?? '');
if (isMobile && !cookieStore.has(MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME)) {
return (
<MobileUnsupportedSplashScreen />
)
}
return children; return children;
} }

View file

@ -24,15 +24,15 @@ export default async function Upgrade({ params: { domain } }: { params: { domain
const isTrialing = !isServiceError(subscription) ? subscription.status === "trialing" : false; const isTrialing = !isServiceError(subscription) ? subscription.status === "trialing" : false;
return ( return (
<div className="flex flex-col items-center pt-12 min-h-screen bg-backgroundSecondary relative"> <div className="flex flex-col items-center pt-12 px-4 sm:px-12 min-h-screen bg-backgroundSecondary relative">
{isTrialing && ( {isTrialing && (
<Link href={`/${domain}`} className="text-sm text-muted-foreground mb-5 absolute top-0 left-0 p-12"> <Link href={`/${domain}`} className="text-sm text-muted-foreground mb-5 absolute top-0 left-0 p-4 sm:p-12">
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<ArrowLeftIcon className="w-4 h-4" /> Return to dashboard <ArrowLeftIcon className="w-4 h-4" /> Return to dashboard
</div> </div>
</Link> </Link>
)} )}
<LogoutEscapeHatch className="absolute top-0 right-0 p-12" /> <LogoutEscapeHatch className="absolute top-0 right-0 p-4 sm:p-12" />
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<SourcebotLogo <SourcebotLogo
className="h-16 mb-2" className="h-16 mb-2"

View file

@ -21,4 +21,6 @@ export const TEAM_FEATURES = [
"Public and private repos supported.", "Public and private repos supported.",
"Create shareable links to code snippets.", "Create shareable links to code snippets.",
"Powerful regex and symbol search", "Powerful regex and symbol search",
] ]
export const MOBILE_UNSUPPORTED_SPLASH_SCREEN_DISMISSED_COOKIE_NAME = 'sb.mobile-unsupported-splash-screen-dismissed';

View file

@ -219,6 +219,8 @@ export type PosthogEventMap = {
wa_login_with_magic_link: {}, wa_login_with_magic_link: {},
wa_login_with_credentials: {}, wa_login_with_credentials: {},
////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////
wa_mobile_unsupported_splash_screen_dismissed: {},
wa_mobile_unsupported_splash_screen_displayed: {},
} }
export type PosthogEvent = keyof PosthogEventMap; export type PosthogEvent = keyof PosthogEventMap;

View file

@ -7716,6 +7716,13 @@ raw-body@2.5.2:
iconv-lite "0.4.24" iconv-lite "0.4.24"
unpipe "1.0.0" unpipe "1.0.0"
react-device-detect@^2.2.3:
version "2.2.3"
resolved "https://registry.npmjs.org/react-device-detect/-/react-device-detect-2.2.3.tgz#97a7ae767cdd004e7c3578260f48cf70c036e7ca"
integrity sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw==
dependencies:
ua-parser-js "^1.0.33"
react-dom@^18: react-dom@^18:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
@ -8616,14 +8623,7 @@ stringify-entities@^4.0.0:
character-entities-html4 "^2.0.0" character-entities-html4 "^2.0.0"
character-entities-legacy "^3.0.0" character-entities-legacy "^3.0.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1" version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -9030,6 +9030,11 @@ typescript@^5.7.3:
resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz" resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz"
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw== integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
ua-parser-js@^1.0.33:
version "1.0.40"
resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz#ac6aff4fd8ea3e794a6aa743ec9c2fc29e75b675"
integrity sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==
uc.micro@^2.0.0, uc.micro@^2.1.0: uc.micro@^2.0.0, uc.micro@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz" resolved "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz"