diff --git a/.env.example b/.env.example index e69de29..e4fd78d 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1,3 @@ +NEXT_PUBLIC_VAPID_PUBLIC_KEY=BBVuTTKBNkLp2WCh0mXH-Biy8A50XlAvCcHU5tEJFfuUIDEdKg4gw_evU08KLAGJzgC8iurciHX8Ac_VclRDrJE +VAPID_PRIVATE_KEY=7RHcigeLtd48aZReOYVGrQ-KCin1tWMtIwDSqNuM7F8 +VAPID_SUBJECT_EMAIL="mailto:witel@info.com" \ No newline at end of file diff --git a/messages/ar.json b/messages/ar.json index 2e55808..0503f04 100644 --- a/messages/ar.json +++ b/messages/ar.json @@ -1,5 +1,5 @@ -{ - "Home": { - "title": "مرحبا" - } -} +{ + "Home": { + "title": "مرحبا" + } +} diff --git a/messages/en.json b/messages/en.json index 6e88cea..849045a 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1,5 +1,5 @@ -{ - "Home": { - "title": "hello" - } -} +{ + "Home": { + "title": "hello" + } +} diff --git a/messages/fa.json b/messages/fa.json index 9cf9fe4..ef0eebd 100644 --- a/messages/fa.json +++ b/messages/fa.json @@ -1,5 +1,5 @@ -{ - "Home": { - "title": "سلام" - } -} +{ + "Home": { + "title": "سلام" + } +} diff --git a/messages/ku.json b/messages/ku.json index 2e55808..0503f04 100644 --- a/messages/ku.json +++ b/messages/ku.json @@ -1,5 +1,5 @@ -{ - "Home": { - "title": "مرحبا" - } -} +{ + "Home": { + "title": "مرحبا" + } +} diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts index 9027e79..f496203 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,16 +1,6 @@ -// next.config.ts import type { NextConfig } from "next"; import createNextIntlPlugin from "next-intl/plugin"; -// @ts-ignore – no types for next-pwa -const withPWA = require("next-pwa")({ - dest: "public", - disable: process.env.NODE_ENV === "development", - register: true, - skipWaiting: true, - navigateFallback: "/offline.html", // fallback page -}); - const withNextIntl = createNextIntlPlugin(); const nextConfig: NextConfig = { @@ -22,4 +12,4 @@ const nextConfig: NextConfig = { const configWithIntl = withNextIntl(nextConfig); -export default withPWA(configWithIntl); +export default configWithIntl; diff --git a/package.json b/package.json index f907ae5..1a298fc 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "cinematales", + "name": "ruya", "private": true, "scripts": { "dev": "next dev", @@ -9,6 +9,7 @@ }, "dependencies": { "@types/js-cookie": "^3.0.6", + "@types/web-push": "^3.6.4", "axios": "^1.15.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -34,6 +35,7 @@ "react-hot-toast": "^2.6.0", "tailwind-merge": "^3.5.0", "tailwind-scrollbar": "^4.0.2", + "web-push": "^3.6.7", "zustand": "^5.0.12" }, "devDependencies": { diff --git a/public/manifest.json b/public/manifest.webmanifest similarity index 84% rename from public/manifest.json rename to public/manifest.webmanifest index 426125a..c512053 100644 --- a/public/manifest.json +++ b/public/manifest.webmanifest @@ -1,8 +1,8 @@ { "id": "/", - "name": "Theater of dreams", - "short_name": "Theater of dreams", - "description": "Theater of dreams", + "name": "Ru'ya", + "short_name": "Ru'ya", + "description": "Ru'ya", "start_url": "/", "display": "standalone", "orientation": "portrait", diff --git a/public/offline.html b/public/offline.html new file mode 100644 index 0000000..0ac3cbd --- /dev/null +++ b/public/offline.html @@ -0,0 +1,10 @@ + + + + Offline + + +

You are offline

+

Please check your connection.

+ + diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..7ff9098 --- /dev/null +++ b/public/sw.js @@ -0,0 +1,77 @@ +const CACHE_NAME = "141-cache-v2"; + +const STATIC_ASSETS = ["/", "/manifest.webmanifest", "/logo/192px.png", "/logo/512px.png"]; + +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then(async (cache) => { + await Promise.allSettled( + STATIC_ASSETS.map(async (url) => { + try { + const response = await fetch(url); + if (response.ok) { + await cache.put(url, response); + } + } catch (e) { + console.warn("❌ Failed to cache:", url); + } + }) + ); + }) + ); + + self.skipWaiting(); +}); + +const sendDeliveryReportAction = () => { + console.log("Web push delivered."); +}; + +self.addEventListener("push", function (event) { + if (event.data) { + try { + // Try to parse as JSON first + let data; + try { + data = event.data.json(); + } catch (jsonError) { + // If JSON parsing fails, treat it as plain text + const text = event.data.text(); + data = { + title: "Notification", + body: text, + icon: "/icon.png", + }; + } + + const options = { + body: data.body || "Default notification body", + icon: data.icon || "/logo/192px.png", + badge: "/badge.png", + vibrate: [100, 50, 100], + data: { + dateOfArrival: Date.now(), + primaryKey: "2", + url: data.url || "/", + }, + }; + event.waitUntil( + self.registration.showNotification(data.title, options).then(() => { + sendDeliveryReportAction(); + }) + ); + } catch (error) { + console.error("Error processing push notification:", error); + } + } +}); +self.addEventListener("notificationclick", function (event) { + event.notification.close(); + event.waitUntil(clients.openWindow(event.notification.data.url)); +}); + +self.addEventListener("message", (event) => { + if (event.data && event.data.type === "SKIP_WAITING") { + self.skipWaiting(); + } +}); diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 7303fe5..4f55e96 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,55 +1,80 @@ -import { getDirection } from "@/i18n/direction"; -import { routing } from "@/i18n/routing"; -import { ClientAppProvider } from "@/providers/ClientAppProvider"; -import "@/styles/globals.css"; -import type { Metadata } from "next"; -import { hasLocale, NextIntlClientProvider } from "next-intl"; -import { getMessages } from "next-intl/server"; -import { notFound } from "next/navigation"; - -export const metadata: Metadata = { - title: "Theater of dreams", -}; - -export default async function RootLayout({ - children, - params, -}: Readonly<{ - children: React.ReactNode; - params: Promise<{ locale: string }>; -}>) { - const { locale } = await params; - if (!hasLocale(routing.locales, locale)) { - notFound(); - } - - const messages = await getMessages(); - const dir = getDirection(locale); - - return ( - - - {/* Prevent theme flicker before hydration */} -