Merge pull request 'added pwa support' (#1) from feature/Capacitor into dev
Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
import type { CapacitorConfig } from "@capacitor/cli";
|
||||||
|
|
||||||
|
const config: CapacitorConfig = {
|
||||||
|
appId: "bettersplit.shrt.solutions",
|
||||||
|
appName: "bettersplit",
|
||||||
|
webDir: "out",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
+11
-3
@@ -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 },
|
||||||
|
});
|
||||||
|
|||||||
+79
-73
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+4936
-10
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
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,
|
||||||
/**
|
/**
|
||||||
|
|||||||
+38
-38
@@ -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"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user