This commit is contained in:
Parsa Nazer
2025-03-06 19:37:10 +03:30
11 changed files with 14123 additions and 12054 deletions
+12 -5
View File
@@ -6,18 +6,26 @@ import { VueQueryDevtools } from "@tanstack/vue-query-devtools";
// state
useState('showLoadingOverlay', () => true);
const {
$updateAvailable: updateAvailable,
$handleUpdate: handleUpdate,
$handleUpdateAvailable: handleUpdateAvailable,
} = useNuxtApp();
</script>
<template>
<div>
<LoadingIndicator />
<NuxtRouteAnnouncer />
<NuxtPwaManifest />
<UpdatePwaModal
:isShow="updateAvailable"
@update="handleUpdate"
@close="handleUpdateAvailable"
/>
<NuxtLayout>
<ToastProvider>
<NuxtPage />
@@ -29,6 +37,5 @@ useState('showLoadingOverlay', () => true);
</NuxtLayout>
<VueQueryDevtools dir="ltr" buttonPosition="bottom-left" />
</div>
</template>
@@ -0,0 +1,70 @@
<script setup lang="ts">
// types
type Props = {
isShow: boolean;
};
type Emits = {
update: [value: any];
"update:isShow": [value: boolean];
};
// props
const props = defineProps<Props>();
const { isShow } = toRefs(props);
// emits
const emit = defineEmits<Emits>();
// computed
const visible = computed({
get: () => isShow.value ?? false,
set: (value: boolean) => emit("update:isShow", value),
});
</script>
<template>
<Modal
v-model="visible"
title="ورژن جدید"
contectClass="!w-[90vw] lg:!w-[35vw]"
@close="visible = false"
>
<template #content>
<div class="w-full flex flex-col text-start gap-6 py-5" dir="rtl">
<p class="leading-[0.75rem]">
نسخه جدید و بهبود یافته اپلیکیشن با ویژگیهای جذاب منتظر
شماست
</p>
<p class="leading-[0.75rem]">
برای تجربه بهتر، لطفا نسخه فعلی را بروزرسانی کنید
</p>
<p class="leading-[0.75rem]">
پس از کلیک بر روی گزینه دریافت نسخه جدید، برنامه به صورت
خودکار بروز میشود
</p>
</div>
<div class="py-6 border-t border-slate-200 flex gap-3">
<Button
@click="emit('update', null)"
class="rounded-full px-10"
>
<span>دریافت ورژن جدید</span>
</Button>
<DialogClose aria-label="Close">
<Button variant="outlined" class="rounded-full px-10">
انصراف
</Button>
</DialogClose>
</div>
</template>
</Modal>
</template>
<style scoped></style>
+19 -8
View File
@@ -1,13 +1,24 @@
FROM node:20-alpine as build-stage
# FROM node:20-alpine as build-stage
# WORKDIR /app
# COPY package*.json ./
# RUN npm install
# COPY . .
# RUN npm run build
# FROM node:20-alpine as production-stage
# WORKDIR /app
# COPY --from=build-stage /app /app
# EXPOSE 3000
# ENV NODE_ENV=production
# CMD ["npm", "run", "start"]
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm install -g npm@latest && \
npm install
COPY . .
RUN npm run build
FROM node:20-alpine as production-stage
WORKDIR /app
COPY --from=build-stage /app /app
EXPOSE 3000
ENV NODE_ENV=production
CMD ["npm", "run", "start"]
CMD ["npm", "start"]
+32 -1
View File
@@ -14,6 +14,9 @@ export default defineNuxtConfig({
},
app: {
head: {
title: "فروشگاه هی ملز",
},
pageTransition: {
enterActiveClass:
"animate__animated animate__fadeIn animate__faster",
@@ -69,9 +72,37 @@ export default defineNuxtConfig({
"@nuxt/icon",
"reka-ui/nuxt",
"@vueuse/nuxt",
"@formkit/auto-animate/nuxt"
"@formkit/auto-animate/nuxt",
"@vite-pwa/nuxt",
],
pwa: {
strategies: "injectManifest",
srcDir: "public",
filename: "sw.js",
manifest: {
name: "Heymlz",
short_name: "Heymlz",
theme_color: "#ffffff",
icons: [
{
src: "/logo/logo-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "/logo/logo-512x512.png",
sizes: "512x512",
type: "image/png",
},
],
},
workbox: {
navigateFallback: "/",
},
devOptions: { enabled: true, type: "module" },
},
runtimeConfig: {
public: {
API_BASE_URL: "https://api.heymlz.com",
+13827 -11990
View File
File diff suppressed because it is too large Load Diff
+52 -50
View File
@@ -1,52 +1,54 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"start": "node .output/server/index.mjs",
"build": "nuxt build",
"dev": "nuxt dev",
"dev-network": "nuxi dev --host",
"dev-o": "nuxt dev -- -o",
"test": "vitest",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@nuxt/icon": "^1.10.3",
"@nuxtjs/google-fonts": "^3.2.0",
"@tanstack/vue-query": "^5.66.9",
"@tanstack/vue-query-devtools": "^5.66.9",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/integrations": "^12.7.0",
"@vueuse/nuxt": "^12.7.0",
"animate.css": "^4.1.1",
"axios": "^1.8.1",
"date-fns-jalali": "^4.1.0-0",
"fast-average-color": "^9.4.0",
"gsap": "^3.12.7",
"isomorphic-dompurify": "^2.22.0",
"jalali-ts": "^8.0.0",
"masonry-layout": "^4.2.2",
"nuxt": "^3.15.4",
"reka-ui": "^1.0.0-alpha.6",
"swiper": "^11.2.4",
"universal-cookie": "^7.2.2",
"vue": "latest",
"vue-router": "latest",
"vue-scrollto": "^2.20.0",
"vue-skeletor": "^1.0.6",
"vue3-marquee": "^4.2.2",
"vue3-persian-datetime-picker": "^1.2.2"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.9",
"@types/masonry-layout": "^4.2.8",
"autoprefixer": "^10.4.20",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.9"
}
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"start": "node .output/server/index.mjs",
"build": "nuxt build",
"dev": "nuxt dev",
"dev-network": "nuxi dev --host",
"dev-o": "nuxt dev -- -o",
"test": "vitest",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@nuxt/icon": "^1.10.3",
"@nuxtjs/google-fonts": "^3.2.0",
"@tanstack/vue-query": "^5.62.2",
"@tanstack/vue-query-devtools": "^5.62.3",
"@vite-pwa/nuxt": "^0.10.6",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/integrations": "^12.7.0",
"@vueuse/nuxt": "^12.7.0",
"animate.css": "^4.1.1",
"axios": "^1.8.1",
"date-fns-jalali": "^4.1.0-0",
"fast-average-color": "^9.4.0",
"gsap": "^3.12.7",
"isomorphic-dompurify": "^2.22.0",
"jalali-ts": "^8.0.0",
"masonry-layout": "^4.2.2",
"nuxt": "^3.15.4",
"reka-ui": "^1.0.0-alpha.6",
"swiper": "^11.2.4",
"universal-cookie": "^7.2.2",
"vue": "latest",
"vue-router": "latest",
"vue-scrollto": "^2.20.0",
"vue-skeletor": "^1.0.6",
"vue3-marquee": "^4.2.2",
"vue3-persian-datetime-picker": "^1.2.2",
"workbox-window": "^7.3.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.9",
"@types/masonry-layout": "^4.2.8",
"autoprefixer": "^10.4.20",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.9"
}
}
+58
View File
@@ -0,0 +1,58 @@
import { Workbox } from "workbox-window";
export default defineNuxtPlugin(() => {
const updateAvailable = ref(false);
let wb = null;
if ("serviceWorker" in navigator) {
wb = new Workbox("/sw.js");
// Detect when a new service worker is waiting
wb.addEventListener("waiting", () => {
updateAvailable.value = true;
showUpdateModal();
});
// Register the service worker
wb.register();
}
// Handle update confirmation
const handleUpdate = () => {
if (updateAvailable) {
// Send message to service worker to skip waiting
wb?.messageSW({ type: "SKIP_WAITING" });
// Wait for controller change and reload
navigator.serviceWorker.addEventListener("controllerchange", () => {
window.location.reload();
});
}
};
const handleUpdateAvailable = (state: boolean) => {
updateAvailable.value = state;
};
return {
provide: {
updateAvailable,
handleUpdate,
handleUpdateAvailable,
},
};
});
function showUpdateModal() {
// Create and show your modal UI here
const modal = document.createElement("div");
modal.innerHTML = `
<div class="update-modal">
<h2>New Version Available! 🎉</h2>
<p>A new version of the app is available. Please update to get the latest features.</p>
<button onclick="handleUpdate()">Update Now</button>
</div>
`;
document.body.appendChild(modal);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

+53
View File
@@ -0,0 +1,53 @@
import { precacheAndRoute } from "workbox-precaching";
// Precaching configuration for PWA assets
precacheAndRoute(self.__WB_MANIFEST);
const VERSION = "1.0.0";
// Service Worker Installation
self.addEventListener("install", (event) => {
self.skipWaiting(); // Force activate new SW immediately
console.log("Service Worker installed");
});
// Service Worker Activation
self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim());
console.log("Service Worker activated");
});
// Push Notification Handler for Django Web Push
self.addEventListener("push", (event) => {
try {
const payload = event.data?.json() || {
title: "New Notification",
body: "You have a new message",
icon: "/logo-192x192.png",
data: { url: "/" },
};
event.waitUntil(
self.registration.showNotification(payload.title, {
body: payload.body,
icon: payload.icon || "/logo-192x192.png",
data: payload.data,
})
);
} catch (error) {
console.error("Push handling failed:", error);
}
});
// Notification Click Handler
self.addEventListener("notificationclick", (event) => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data?.url || "/"));
});
self.addEventListener("message", (event) => {
if (event.data === "SKIP_WAITING") {
self.skipWaiting();
self.clients.claim();
}
});