added pwa support
This commit is contained in:
parent
b1823f5294
commit
f1ee071e62
9
capacitor.config.ts
Normal file
9
capacitor.config.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import type { CapacitorConfig } from "@capacitor/cli";
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: "bettersplit.shrt.solutions",
|
||||||
|
appName: "bettersplit",
|
||||||
|
webDir: "out",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@ -3,8 +3,16 @@
|
|||||||
* for Docker builds.
|
* for Docker builds.
|
||||||
*/
|
*/
|
||||||
import "./src/env.js";
|
import "./src/env.js";
|
||||||
|
import pwa from "next-pwa";
|
||||||
|
import { env } from "./src/env.js";
|
||||||
|
|
||||||
/** @type {import("next").NextConfig} */
|
const withPWA = pwa({
|
||||||
const config = {};
|
dest: "public",
|
||||||
|
disable: env.NODE_ENV === "development",
|
||||||
|
register: true, // register the PWA service worker
|
||||||
|
skipWaiting: true,
|
||||||
|
});
|
||||||
|
|
||||||
export default config;
|
export default withPWA({
|
||||||
|
eslint: { ignoreDuringBuilds: true },
|
||||||
|
});
|
||||||
|
|||||||
152
package.json
152
package.json
@ -1,75 +1,81 @@
|
|||||||
{
|
{
|
||||||
"name": "betterwise",
|
"name": "betterwise",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"check": "biome check .",
|
"export": "next export",
|
||||||
"check:unsafe": "biome check --write --unsafe .",
|
"check": "biome check .",
|
||||||
"check:write": "biome check --write .",
|
"check:unsafe": "biome check --write --unsafe .",
|
||||||
"db:generate": "drizzle-kit generate",
|
"check:write": "biome check --write .",
|
||||||
"db:migrate": "drizzle-kit migrate",
|
"db:generate": "drizzle-kit generate",
|
||||||
"db:push": "drizzle-kit push",
|
"db:migrate": "drizzle-kit migrate",
|
||||||
"db:studio": "drizzle-kit studio",
|
"db:push": "drizzle-kit push",
|
||||||
"dev": "next dev --turbo",
|
"db:studio": "drizzle-kit studio",
|
||||||
"preview": "next build && next start",
|
"dev": "next dev --turbo",
|
||||||
"start": "next start",
|
"preview": "next build && next start",
|
||||||
"typecheck": "tsc --noEmit"
|
"start": "next start",
|
||||||
},
|
"typecheck": "tsc --noEmit",
|
||||||
"dependencies": {
|
"android": "pnpm build && pnpm export && pnpm dlx cap sync android && pnpm dlx cap open android"
|
||||||
"@auth/drizzle-adapter": "^1.7.2",
|
},
|
||||||
"@hookform/resolvers": "^5.0.1",
|
"dependencies": {
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@auth/drizzle-adapter": "^1.7.2",
|
||||||
"@radix-ui/react-avatar": "^1.1.3",
|
"@capacitor/cli": "^7.2.0",
|
||||||
"@radix-ui/react-dialog": "^1.1.6",
|
"@capacitor/core": "^7.2.0",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@radix-ui/react-label": "^2.1.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@radix-ui/react-popover": "^1.1.6",
|
"@radix-ui/react-avatar": "^1.1.3",
|
||||||
"@radix-ui/react-select": "^2.1.6",
|
"@radix-ui/react-dialog": "^1.1.6",
|
||||||
"@radix-ui/react-separator": "^1.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
"@radix-ui/react-tabs": "^1.1.3",
|
"@radix-ui/react-popover": "^1.1.6",
|
||||||
"@t3-oss/env-nextjs": "^0.12.0",
|
"@radix-ui/react-select": "^2.1.6",
|
||||||
"@tanstack/react-query": "^5.69.0",
|
"@radix-ui/react-separator": "^1.1.2",
|
||||||
"@trpc/client": "^11.0.0",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@trpc/react-query": "^11.0.0",
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
"@trpc/server": "^11.0.0",
|
"@t3-oss/env-nextjs": "^0.12.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"@tanstack/react-query": "^5.69.0",
|
||||||
"clsx": "^2.1.1",
|
"@trpc/client": "^11.0.0",
|
||||||
"cmdk": "^1.1.1",
|
"@trpc/react-query": "^11.0.0",
|
||||||
"drizzle-orm": "^0.41.0",
|
"@trpc/server": "^11.0.0",
|
||||||
"lucide-react": "^0.487.0",
|
"@types/next-pwa": "^5.6.9",
|
||||||
"next": "^15.2.3",
|
"class-variance-authority": "^0.7.1",
|
||||||
"next-auth": "5.0.0-beta.25",
|
"clsx": "^2.1.1",
|
||||||
"next-themes": "^0.4.6",
|
"cmdk": "^1.1.1",
|
||||||
"postgres": "^3.4.4",
|
"drizzle-orm": "^0.41.0",
|
||||||
"react": "^19.0.0",
|
"lucide-react": "^0.487.0",
|
||||||
"react-dom": "^19.0.0",
|
"next": "^15.2.3",
|
||||||
"react-hook-form": "^7.55.0",
|
"next-auth": "5.0.0-beta.25",
|
||||||
"server-only": "^0.0.1",
|
"next-pwa": "^5.6.0",
|
||||||
"sonner": "^2.0.3",
|
"next-themes": "^0.4.6",
|
||||||
"superjson": "^2.2.1",
|
"postgres": "^3.4.4",
|
||||||
"tailwind-merge": "^3.1.0",
|
"react": "^19.0.0",
|
||||||
"tw-animate-css": "^1.2.5",
|
"react-dom": "^19.0.0",
|
||||||
"use-debounce": "^10.0.4",
|
"react-hook-form": "^7.55.0",
|
||||||
"vaul": "^1.1.2",
|
"server-only": "^0.0.1",
|
||||||
"zod": "^3.24.2",
|
"sonner": "^2.0.3",
|
||||||
"zustand": "^5.0.3"
|
"superjson": "^2.2.1",
|
||||||
},
|
"tailwind-merge": "^3.1.0",
|
||||||
"devDependencies": {
|
"tw-animate-css": "^1.2.5",
|
||||||
"@biomejs/biome": "1.9.4",
|
"use-debounce": "^10.0.4",
|
||||||
"@tailwindcss/postcss": "^4.0.15",
|
"vaul": "^1.1.2",
|
||||||
"@types/node": "^20.14.10",
|
"zod": "^3.24.2",
|
||||||
"@types/react": "^19.0.0",
|
"zustand": "^5.0.3"
|
||||||
"@types/react-dom": "^19.0.0",
|
},
|
||||||
"drizzle-kit": "^0.30.5",
|
"devDependencies": {
|
||||||
"postcss": "^8.5.3",
|
"@biomejs/biome": "1.9.4",
|
||||||
"tailwindcss": "^4.0.15",
|
"@tailwindcss/postcss": "^4.0.15",
|
||||||
"typescript": "^5.8.2"
|
"@types/node": "^20.14.10",
|
||||||
},
|
"@types/react": "^19.0.0",
|
||||||
"ct3aMetadata": {
|
"@types/react-dom": "^19.0.0",
|
||||||
"initVersion": "7.39.2"
|
"drizzle-kit": "^0.30.5",
|
||||||
},
|
"postcss": "^8.5.3",
|
||||||
"packageManager": "pnpm@10.3.0"
|
"tailwindcss": "^4.0.15",
|
||||||
|
"typescript": "^5.8.2"
|
||||||
|
},
|
||||||
|
"ct3aMetadata": {
|
||||||
|
"initVersion": "7.39.2"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.3.0"
|
||||||
}
|
}
|
||||||
|
|||||||
4946
pnpm-lock.yaml
generated
4946
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
1
public/sw.js
Normal file
1
public/sw.js
Normal file
File diff suppressed because one or more lines are too long
1
public/workbox-e9849328.js
Normal file
1
public/workbox-e9849328.js
Normal file
File diff suppressed because one or more lines are too long
@ -4,7 +4,7 @@ type AppConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const appConfig: AppConfig = {
|
export const appConfig: AppConfig = {
|
||||||
name: "Betterwise",
|
name: "Bettesplit",
|
||||||
navigator: [
|
navigator: [
|
||||||
{
|
{
|
||||||
name: "Home",
|
name: "Home",
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import AddFriendDrawer from "@/app/_components/friend/add-friend-drawer";
|
import AddFriendDrawer from "@/app/_components/friend/add-friend-drawer";
|
||||||
import FriendList from "@/app/_components/friend/friend-list";
|
import FriendList from "@/app/_components/friend/friend-list";
|
||||||
import Header from "@/components/header";
|
import Header from "@/components/header";
|
||||||
|
import { auth } from "@/server/auth";
|
||||||
import { api, HydrateClient } from "@/trpc/server";
|
import { api, HydrateClient } from "@/trpc/server";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
void api.friend.getAll.prefetch();
|
const session = await auth();
|
||||||
|
if (session?.user) void api.friend.getAll.prefetch();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HydrateClient>
|
<HydrateClient>
|
||||||
|
|||||||
@ -4,13 +4,14 @@ import React from "react";
|
|||||||
export default function FriendRequestButton({}: {}) {
|
export default function FriendRequestButton({}: {}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Button
|
{/* <Button
|
||||||
variant={requestedBy === "me" ? "destructive" : "outline"}
|
variant={requestedBy === "me" ? "destructive" : "outline"}
|
||||||
className="ml-auto"
|
className="ml-auto"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
{requestedBy === "me" ? "Cancel" : "Accept"}
|
{requestedBy === "me" ? "Cancel" : "Accept"}
|
||||||
</Button>
|
</Button> */}
|
||||||
|
friendRequestButton
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
86
src/app/_components/install-prompt.tsx
Normal file
86
src/app/_components/install-prompt.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// "use client";
|
||||||
|
// import React, { useEffect, useState } from "react";
|
||||||
|
// export function usePwaInstallStatus() {
|
||||||
|
// const [isStandalone, setIsStandalone] = useState(false);
|
||||||
|
// const [isIOS, setIsIOS] = useState(false);
|
||||||
|
// const [isAndroid, setIsAndroid] = useState(false);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const userAgent =
|
||||||
|
// navigator.userAgent || navigator.vendor || (window as any).opera;
|
||||||
|
|
||||||
|
// // iOS detection
|
||||||
|
// const iOS = /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream;
|
||||||
|
// setIsIOS(iOS);
|
||||||
|
|
||||||
|
// // Android detection
|
||||||
|
// const android = /android/i.test(userAgent);
|
||||||
|
// setIsAndroid(android);
|
||||||
|
|
||||||
|
// // PWA "standalone" mode detection (works for both iOS & Android)
|
||||||
|
// const isInStandaloneMode =
|
||||||
|
// window.matchMedia("(display-mode: standalone)").matches ||
|
||||||
|
// (navigator as any).standalone === true; // for iOS Safari
|
||||||
|
|
||||||
|
// setIsStandalone(isInStandaloneMode);
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// return { isIOS, isAndroid, isStandalone };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function InstallPrompt() {
|
||||||
|
// const { isIOS, isAndroid, isStandalone } = usePwaInstallStatus();
|
||||||
|
// const [deferredPrompt, setDeferredPrompt] = useState<any>(null);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// const handler = (e: any) => {
|
||||||
|
// e.preventDefault();
|
||||||
|
// setDeferredPrompt(e);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// window.addEventListener("beforeinstallprompt", handler);
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// window.removeEventListener("beforeinstallprompt", handler);
|
||||||
|
// };
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// const handleInstallClick = () => {
|
||||||
|
// console.log("install clicked");
|
||||||
|
|
||||||
|
// if (deferredPrompt) {
|
||||||
|
// deferredPrompt.prompt();
|
||||||
|
// deferredPrompt.userChoice.then((choiceResult: any) => {
|
||||||
|
// if (choiceResult.outcome === "accepted") {
|
||||||
|
// console.log("User accepted the A2HS prompt");
|
||||||
|
// } else {
|
||||||
|
// console.log("User dismissed the A2HS prompt");
|
||||||
|
// }
|
||||||
|
// setDeferredPrompt(null);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// if (isStandalone) {
|
||||||
|
// return null; // Already running as a PWA, no need to show install prompt
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div className="absolute w-full h-20 z-50 bg-red-500 left-0 flex items-center justify-center right-0 bottom-0 text-white">
|
||||||
|
// {isIOS && <p>Tap "Share" and "Add to Home Screen" to install the app.</p>}
|
||||||
|
// {isAndroid && <button onClick={handleInstallClick}>Install App</button>}
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export default function PWAWrapper({
|
||||||
|
// children,
|
||||||
|
// }: {
|
||||||
|
// children: React.ReactNode;
|
||||||
|
// }) {
|
||||||
|
// return (
|
||||||
|
// <>
|
||||||
|
// <InstallPrompt />
|
||||||
|
// {children}
|
||||||
|
// </>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
@ -5,6 +5,7 @@ import { Geist } from "next/font/google";
|
|||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { TRPCReactProvider } from "@/trpc/react";
|
import { TRPCReactProvider } from "@/trpc/react";
|
||||||
import { ThemeProvider } from "@/components/theme-provider";
|
import { ThemeProvider } from "@/components/theme-provider";
|
||||||
|
// import PWAWrapper from "./_components/install-prompt";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Create T3 App",
|
title: "Create T3 App",
|
||||||
@ -30,6 +31,8 @@ export default function RootLayout({
|
|||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
|
{/* <PWAWrapper>
|
||||||
|
</PWAWrapper> */}
|
||||||
{children}
|
{children}
|
||||||
<Toaster position="top-center" />
|
<Toaster position="top-center" />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|||||||
26
src/app/manifest.ts
Normal file
26
src/app/manifest.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { appConfig } from "@/app.config";
|
||||||
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
|
export default function manifest(): MetadataRoute.Manifest {
|
||||||
|
return {
|
||||||
|
name: appConfig.name,
|
||||||
|
short_name: "NextPWA",
|
||||||
|
description: "A Progressive Web App built with Next.js",
|
||||||
|
start_url: "/",
|
||||||
|
display: "standalone",
|
||||||
|
background_color: "#ffffff",
|
||||||
|
theme_color: "#000000",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/icon-192x192.png",
|
||||||
|
sizes: "192x192",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "/icon-512x512.png",
|
||||||
|
sizes: "512x512",
|
||||||
|
type: "image/png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@ function NavLink({ href, name, icon }: NavLink) {
|
|||||||
asChild
|
asChild
|
||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
className={cn(
|
className={cn(
|
||||||
"p-2 size-12 text-muted-foreground/75 flex flex-col gap-2 ",
|
"p-2 size-12 text-muted-foreground/75 flex flex-col gap-2 hover:bg-transparent dark:hover:bg-transparent ",
|
||||||
active && "text-foreground "
|
active && "text-foreground "
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes";
|
||||||
import { Toaster as Sonner, ToasterProps } from "sonner"
|
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme()
|
const { theme = "system" } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
@ -19,7 +19,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
|||||||
}
|
}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export { Toaster }
|
export { Toaster };
|
||||||
|
|||||||
0
src/in.ts → src/index.d.ts
vendored
0
src/in.ts → src/index.d.ts
vendored
@ -38,6 +38,7 @@ declare module "next-auth" {
|
|||||||
* @see https://next-auth.js.org/configuration/options
|
* @see https://next-auth.js.org/configuration/options
|
||||||
*/
|
*/
|
||||||
export const authConfig = {
|
export const authConfig = {
|
||||||
|
trustHost: true,
|
||||||
providers: [
|
providers: [
|
||||||
DiscordProvider,
|
DiscordProvider,
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,43 +1,43 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Base Options: */
|
/* Base Options: */
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"target": "es2022",
|
"target": "es2022",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
|
|
||||||
/* Strictness */
|
/* Strictness */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
|
|
||||||
/* Bundled projects */
|
/* Bundled projects */
|
||||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"plugins": [{ "name": "next" }],
|
"plugins": [{ "name": "next" }],
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
|
||||||
/* Path Aliases */
|
/* Path Aliases */
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
".eslintrc.cjs",
|
".eslintrc.cjs",
|
||||||
"next-env.d.ts",
|
"next-env.d.ts",
|
||||||
"**/*.ts",
|
"**/*.ts",
|
||||||
"**/*.tsx",
|
"**/*.tsx",
|
||||||
"**/*.cjs",
|
"**/*.cjs",
|
||||||
"**/*.js",
|
"**/*.js",
|
||||||
".next/types/**/*.ts"
|
".next/types/**/*.ts"
|
||||||
],
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "public/sw.js", "public/workbox-*.js"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user