Versiyon güncelleme
This commit is contained in:
parent
6766d1129d
commit
9e85780623
14 changed files with 371 additions and 250 deletions
|
|
@ -7528,7 +7528,7 @@
|
|||
{
|
||||
"key": "admin.changeLog",
|
||||
"path": "/admin/changeLog",
|
||||
"componentPath": "@/views/docs/ChangeLog",
|
||||
"componentPath": "@/views/version/ChangeLog",
|
||||
"routeType": "protected",
|
||||
"authority": []
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"AttachmentsPath": "C:\\Private\\Projects\\sozsoft\\configs\\mail-queue\\attachments",
|
||||
"CdnPath": "C:\\Private\\Projects\\sozsoft\\configs\\docker\\data\\cdn",
|
||||
"ImportPath": "C:\\Private\\Projects\\sozsoft\\configs\\docker\\data\\import",
|
||||
"Version": "1.0.4"
|
||||
"Version": "1.0.1"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"SqlServer": "Server=localhost;Database=KURS;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;TrustServerCertificate=True;",
|
||||
|
|
|
|||
1
ui/.gitignore
vendored
1
ui/.gitignore
vendored
|
|
@ -9,6 +9,7 @@ lerna-debug.log*
|
|||
|
||||
node_modules
|
||||
dist
|
||||
dev-dist
|
||||
dist-ssr
|
||||
build
|
||||
*.local
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ define(['./workbox-a959eb95'], (function (workbox) { 'use strict';
|
|||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "/index.html",
|
||||
"revision": "0.agtbclbpej8"
|
||||
"revision": "0.jq60tbu0hgg"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("/index.html"), {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "kurs-platform-ui",
|
||||
"private": true,
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.1",
|
||||
"elstarVersion": "2.1.6",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"build": "vite build && node scripts/write-version.js",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.json",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
|
|
|
|||
35
ui/public/version.json
Normal file
35
ui/public/version.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"commit": "6766d11",
|
||||
"releases": [
|
||||
{
|
||||
"version": "1.0.5",
|
||||
"buildDate": "2025-09-19",
|
||||
"changeLog": [
|
||||
"Form ekranındaki Butonlar güncellemeleri yapıldı",
|
||||
"Edit Form ekranındaki Info butonu eklendi.",
|
||||
"New Form ekranındaki Geri butonu eklendi."
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.0.4",
|
||||
"buildDate": "2025-09-19",
|
||||
"changeLog": [
|
||||
"Subformlar üzerinde extra filters ve Widget çalışmaları yapıldı."
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.0.3",
|
||||
"buildDate": "2025-09-19",
|
||||
"changeLog": [
|
||||
"Manage Grid üzerinde Extra filtre tanımlaması yapıldı."
|
||||
]
|
||||
},
|
||||
{
|
||||
"version": "1.0.2",
|
||||
"buildDate": "2025-09-16",
|
||||
"changeLog": [
|
||||
"Genel Static olan Url bilgileri kaldırıldı."
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
46
ui/scripts/write-version.js
Normal file
46
ui/scripts/write-version.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import fs from "fs"
|
||||
import { execSync } from "child_process"
|
||||
|
||||
function safeExec(cmd) {
|
||||
try {
|
||||
return execSync(cmd, { stdio: ["pipe", "pipe", "ignore"] }).toString().trim()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Tüm tag isimlerini al
|
||||
const rawTags = safeExec("git tag --list --sort=creatordate")
|
||||
|
||||
if (!rawTags) {
|
||||
console.log("> No git tags found, skipping version.json")
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const tags = rawTags
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map((tag) => {
|
||||
const date = safeExec(`git log -1 --format=%ad --date=short ${tag}`)
|
||||
const messageRaw = safeExec(`git tag -l --format="%(contents)" ${tag}`)
|
||||
|
||||
const changeLog = messageRaw
|
||||
? messageRaw.split("\n").map((s) => s.trim()).filter(Boolean)
|
||||
: []
|
||||
|
||||
return {
|
||||
version: tag.replace(/^v/, ""), // v1.0.5 → 1.0.5
|
||||
buildDate: date,
|
||||
changeLog
|
||||
}
|
||||
})
|
||||
|
||||
const commit = safeExec("git rev-parse --short HEAD")
|
||||
|
||||
const versionInfo = {
|
||||
commit,
|
||||
releases: tags.reverse()
|
||||
}
|
||||
|
||||
fs.writeFileSync("public/version.json", JSON.stringify(versionInfo, null, 2))
|
||||
console.log("> Version file written to public/version.json:", versionInfo)
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// src/components/UpdateNotifier.jsx
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
const UpdateNotifier = () => {
|
||||
const [updateAvailable, setUpdateAvailable] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const checkUpdate = () => {
|
||||
navigator.serviceWorker.getRegistration().then(registration => {
|
||||
if (registration) {
|
||||
registration.addEventListener('updatefound', () => {
|
||||
setUpdateAvailable(true)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
checkUpdate()
|
||||
const interval = setInterval(checkUpdate, 30000) // 30 saniyede bir kontrol
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleUpdate = () => {
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
if (!updateAvailable) return null
|
||||
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
bottom: '20px',
|
||||
right: '20px',
|
||||
padding: '12px',
|
||||
background: '#FF99C8',
|
||||
color: 'white',
|
||||
borderRadius: '8px',
|
||||
zIndex: 1000
|
||||
}}>
|
||||
<p>Yeni güncelleme mevcut!</p>
|
||||
<button onClick={handleUpdate} style={{
|
||||
background: 'white',
|
||||
color: '#FF99C8',
|
||||
border: 'none',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '4px',
|
||||
cursor: 'pointer'
|
||||
}}>
|
||||
Yenile
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateNotifier
|
||||
|
|
@ -15,7 +15,7 @@ import useLocale from '@/utils/hooks/useLocale'
|
|||
import { useDynamicRoutes } from '@/routes/dynamicRoutesContext'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
import { hasSubdomain } from '@/utils/subdomain'
|
||||
import UpdateNotifier from '../UpdateNotifier'
|
||||
import UpdateNotifier from '../../views/version/UpdateNotifier'
|
||||
|
||||
export type LayoutType =
|
||||
| typeof LAYOUT_TYPE_CLASSIC
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
import classNames from 'classnames'
|
||||
import Container from '@/components/shared/Container'
|
||||
import { APP_NAME } from '@/constants/app.constant'
|
||||
import { PAGE_CONTAINER_GUTTER_X } from '@/constants/theme.constant'
|
||||
import { useStoreActions, useStoreState } from '@/store'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import UiDialog from '@/views/shared/UiDialog'
|
||||
import { useEffect, useState } from "react"
|
||||
import classNames from "classnames"
|
||||
import Container from "@/components/shared/Container"
|
||||
import { APP_NAME } from "@/constants/app.constant"
|
||||
import { PAGE_CONTAINER_GUTTER_X } from "@/constants/theme.constant"
|
||||
import { useStoreActions, useStoreState } from "@/store"
|
||||
import { Link, useNavigate } from "react-router-dom"
|
||||
import { ROUTES_ENUM } from "@/routes/route.constant"
|
||||
import UiDialog from "@/views/shared/UiDialog"
|
||||
|
||||
export type FooterPageContainerType = 'gutterless' | 'contained'
|
||||
export type FooterPageContainerType = "gutterless" | "contained"
|
||||
|
||||
type FooterProps = {
|
||||
pageContainerType: FooterPageContainerType
|
||||
|
|
@ -21,7 +22,20 @@ const FooterContent = () => {
|
|||
const apiConfig = useStoreState((state) => state.abpConfig.config?.extraProperties)
|
||||
|
||||
const uiMode = import.meta.env.MODE
|
||||
const reactAppVersion = import.meta.env.VITE_REACT_APP_VERSION
|
||||
|
||||
const [latestVersion, setLatestVersion] = useState<string | null>(null)
|
||||
|
||||
// version.json'dan en güncel UI versiyonunu al
|
||||
useEffect(() => {
|
||||
fetch("/version.json?ts=" + Date.now())
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data?.releases?.length > 0) {
|
||||
setLatestVersion(data.releases[0].version) // en güncel hep en üstte
|
||||
}
|
||||
})
|
||||
.catch(() => setLatestVersion(null))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -40,24 +54,25 @@ const FooterContent = () => {
|
|||
{apiConfig && (
|
||||
<span>
|
||||
<b>API: </b>
|
||||
{apiConfig['environment'].toString()}:{apiConfig['version'].toString()}
|
||||
{apiConfig["environment"].toString()}:{apiConfig["version"].toString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
{reactAppVersion != currentUiVersion && (
|
||||
|
||||
{latestVersion && latestVersion !== currentUiVersion && (
|
||||
<UiDialog
|
||||
key={`version-${reactAppVersion }`}
|
||||
key={`version-${latestVersion}`}
|
||||
isOpen={true}
|
||||
type="info"
|
||||
onConfirm={() => {
|
||||
setUiVersion(reactAppVersion )
|
||||
setUiVersion(latestVersion)
|
||||
navigate(ROUTES_ENUM.protected.admin.changeLog)
|
||||
}}
|
||||
title="🎉 Yeni Güncelleme"
|
||||
>
|
||||
Sözsoft Kurs Platform Sistemi güncellendi.
|
||||
Sözsoft Kurs Platform Sistemi <b>v{latestVersion}</b> sürümüne güncellendi.
|
||||
<p>Detayları, "Güncelleme Günlüğü" ekranında görebilirsiniz.</p>
|
||||
</UiDialog>
|
||||
)}
|
||||
|
|
@ -65,12 +80,14 @@ const FooterContent = () => {
|
|||
)
|
||||
}
|
||||
|
||||
export default function Footer({ pageContainerType = 'contained' }: FooterProps) {
|
||||
export default function Footer({ pageContainerType = "contained" }: FooterProps) {
|
||||
return (
|
||||
<footer
|
||||
className={classNames(`print:hidden footer flex flex-auto items-center h-6 ${PAGE_CONTAINER_GUTTER_X}`)}
|
||||
className={classNames(
|
||||
`print:hidden footer flex flex-auto items-center h-6 ${PAGE_CONTAINER_GUTTER_X}`
|
||||
)}
|
||||
>
|
||||
{pageContainerType === 'contained' ? (
|
||||
{pageContainerType === "contained" ? (
|
||||
<Container>
|
||||
<FooterContent />
|
||||
</Container>
|
||||
|
|
|
|||
|
|
@ -1,81 +0,0 @@
|
|||
import AdaptableCard from '@/components/shared/AdaptableCard'
|
||||
import Container from '@/components/shared/Container'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
import type { ReactNode } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
|
||||
type Log = {
|
||||
version: string
|
||||
date: string
|
||||
updateContent: string[]
|
||||
}
|
||||
|
||||
type LogProps = Omit<Log, 'updateContent'> & {
|
||||
border?: boolean
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
const logData: Log[] = [
|
||||
{
|
||||
version: '1.0.3',
|
||||
date: '04 Şubat 2025',
|
||||
updateContent: ['[Fix] GridBoxEditorComponent bug', '[Fix] TagBoxEditorComponent bug'],
|
||||
},
|
||||
{
|
||||
version: '1.0.2',
|
||||
date: '27 Ocak 2025',
|
||||
updateContent: ['[Add] jspdf kurulumu', '[Add] exceljs kurulumu', '[Add] file-saver kurulumu'],
|
||||
},
|
||||
{
|
||||
version: '1.0.1',
|
||||
date: '23 Ocak 2025',
|
||||
updateContent: ['[Fix] Bağımlılık güvenlik açığı'],
|
||||
},
|
||||
{
|
||||
version: '1.0.0',
|
||||
date: '20 Ocak 2025',
|
||||
updateContent: ['[Update] İlk yeni sürüm'],
|
||||
},
|
||||
]
|
||||
|
||||
const Log = (props: LogProps) => {
|
||||
return (
|
||||
<div className={`py-4 ${props.border && 'border-bottom'}`}>
|
||||
<div className="flex items-center">
|
||||
<h5 className="font-weight-normal mb-0 mr-3">{props.version}</h5>
|
||||
<code>{props.date}</code>
|
||||
</div>
|
||||
<div className="api-container p-0 border-0 mt-3">{props.children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Changelog = () => {
|
||||
const { translate } = useLocalization()
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Helmet
|
||||
titleTemplate="%s | Sözsoft Kurs Platform"
|
||||
title={translate('::' + 'App.ChangeLog')}
|
||||
defaultTitle="Sözsoft Kurs Platform"
|
||||
></Helmet>
|
||||
<AdaptableCard>
|
||||
<h4>Platform Güncelleme Günlüğü</h4>
|
||||
{logData.map((elm) => (
|
||||
<Log key={elm.version} version={`v${elm.version}`} date={elm.date}>
|
||||
{elm.updateContent.length > 0 ? (
|
||||
<ul>
|
||||
{elm.updateContent.map((item, i) => (
|
||||
<li key={i}>- {item}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
</Log>
|
||||
))}
|
||||
</AdaptableCard>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default Changelog
|
||||
77
ui/src/views/version/ChangeLog.tsx
Normal file
77
ui/src/views/version/ChangeLog.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { useEffect, useState } from "react"
|
||||
import AdaptableCard from "@/components/shared/AdaptableCard"
|
||||
import Container from "@/components/shared/Container"
|
||||
import { HiTag, HiCheckCircle } from "react-icons/hi"
|
||||
import { useLocalization } from "@/utils/hooks/useLocalization"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
type Release = {
|
||||
version: string
|
||||
buildDate: string
|
||||
changeLog: string[]
|
||||
}
|
||||
|
||||
const Log = ({ version, date, children }: { version: string; date: string; children?: React.ReactNode }) => {
|
||||
return (
|
||||
<div className="relative pl-4 sm:pl-32 py-4 group">
|
||||
<div className="flex flex-col sm:flex-row items-start mb-1 group-last:before:hidden before:absolute before:left-2 sm:before:left-0 before:h-full before:px-px before:bg-slate-200 sm:before:ml-[6.5rem] before:self-start before:-translate-x-1/2 before:translate-y-3 after:absolute after:left-2 sm:after:left-0 after:w-2 after:h-2 after:bg-indigo-600 after:border-4 after:box-content after:border-slate-50 after:rounded-full sm:after:ml-[6.5rem] after:-translate-x-1/2 after:translate-y-1.5">
|
||||
<time className="sm:absolute left-0 translate-y-0.5 inline-flex items-center justify-center text-xs font-semibold uppercase w-20 h-6 mb-3 sm:mb-0 text-emerald-600 bg-emerald-100 rounded-full">
|
||||
{date}
|
||||
</time>
|
||||
<div className="flex items-center text-xl font-bold text-gray-900">
|
||||
<HiTag className="mr-2 text-indigo-500" />
|
||||
v{version}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-slate-500">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Changelog = () => {
|
||||
const { translate } = useLocalization()
|
||||
const [releases, setReleases] = useState<Release[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/version.json?ts=" + Date.now())
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (data?.releases) {
|
||||
setReleases(data.releases)
|
||||
}
|
||||
})
|
||||
.catch(() => setReleases([]))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Helmet
|
||||
titleTemplate="%s | Sözsoft Kurs Platform"
|
||||
title={translate("::App.ChangeLog")}
|
||||
defaultTitle="Sözsoft Kurs Platform"
|
||||
/>
|
||||
<AdaptableCard>
|
||||
<div className="p-2">
|
||||
<div className="space-y-1">
|
||||
{releases.map((rel) => (
|
||||
<Log key={rel.version} version={rel.version} date={rel.buildDate}>
|
||||
{rel.changeLog?.length > 0 && (
|
||||
<ul className="list-none mt-2 space-y-2">
|
||||
{rel.changeLog.map((item, i) => (
|
||||
<li key={i} className="flex items-start">
|
||||
<HiCheckCircle className="w-4 h-4 text-emerald-500 mr-2 flex-shrink-0" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</Log>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</AdaptableCard>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default Changelog
|
||||
71
ui/src/views/version/UpdateNotifier.tsx
Normal file
71
ui/src/views/version/UpdateNotifier.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { HiOutlineRefresh, HiX } from 'react-icons/hi'
|
||||
import { useStoreState, useStoreActions } from '@/store'
|
||||
|
||||
const UpdateNotifier = () => {
|
||||
const [lastUiVersion, setLastUiVersion] = useState('')
|
||||
const [updateAvailable, setUpdateAvailable] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { currentUiVersion } = useStoreState((s) => s.locale)
|
||||
const { setUiVersion } = useStoreActions((s) => s.locale)
|
||||
|
||||
useEffect(() => {
|
||||
const checkVersion = async () => {
|
||||
try {
|
||||
const res = await fetch('/version.json?ts=' + Date.now())
|
||||
const data = await res.json()
|
||||
const latestVersion = data?.releases?.[0]?.version
|
||||
if (latestVersion && latestVersion !== currentUiVersion) {
|
||||
setUpdateAvailable(true)
|
||||
setLastUiVersion(latestVersion)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Version check failed', err)
|
||||
}
|
||||
}
|
||||
|
||||
checkVersion()
|
||||
const interval = setInterval(checkVersion, 30000) // 30s’de bir kontrol
|
||||
return () => clearInterval(interval)
|
||||
}, [currentUiVersion])
|
||||
|
||||
if (!updateAvailable) return null
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed bottom-5 right-5 z-50 flex items-center justify-between gap-4 rounded-lg bg-sky-500 p-4 text-white shadow-lg animate-bounce"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<HiOutlineRefresh className="h-8 w-8 animate-spin" />
|
||||
<div>
|
||||
<p className="font-semibold">Yeni güncelleme mevcut!</p>
|
||||
<p className="text-sm">En son özellikler için sayfayı yenileyin.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setUiVersion(lastUiVersion)
|
||||
navigate(ROUTES_ENUM.protected.admin.changeLog)
|
||||
setUpdateAvailable(false)
|
||||
}}
|
||||
className="rounded bg-white px-4 py-2 text-sm font-bold text-sky-600 transition hover:bg-sky-100"
|
||||
>
|
||||
Yenile
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setUpdateAvailable(false)}
|
||||
className="rounded-full p-1 transition hover:bg-sky-600"
|
||||
aria-label="Kapat"
|
||||
>
|
||||
<HiX className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UpdateNotifier
|
||||
|
|
@ -19,95 +19,100 @@ export default defineConfig(async ({ mode }) => {
|
|||
return {
|
||||
plugins: [
|
||||
react(),
|
||||
VitePWA({
|
||||
// Deploy'dan sonra otomatik güncelle
|
||||
registerType: 'autoUpdate',
|
||||
// Kayıt kodunu otomatik enjekte et (virtual:pwa-register yazmadan da çalışır)
|
||||
injectRegister: 'auto',
|
||||
// Dev ortamında SW'yi aç, prod'da kapalı tut (build edilmiş SW prod'da zaten aktif olur)
|
||||
devOptions: {
|
||||
enabled: mode !== 'production',
|
||||
type: 'module' // Modern module worker kullan
|
||||
},
|
||||
mode === 'production'
|
||||
? VitePWA({
|
||||
// Deploy'dan sonra otomatik güncelle
|
||||
registerType: 'autoUpdate',
|
||||
// Kayıt kodunu otomatik enjekte et (virtual:pwa-register yazmadan da çalışır)
|
||||
injectRegister: 'auto',
|
||||
// Dev ortamında SW'yi aç, prod'da kapalı tut (build edilmiş SW prod'da zaten aktif olur)
|
||||
devOptions: {
|
||||
enabled: mode !== 'production',
|
||||
type: 'module', // Modern module worker kullan
|
||||
},
|
||||
|
||||
workbox: {
|
||||
// Büyük asset'leri de cache'leyebil
|
||||
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
|
||||
workbox: {
|
||||
// Büyük asset'leri de cache'leyebil
|
||||
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
|
||||
|
||||
// EN KRİTİK: yeni SW beklemeden kontrolü alsın
|
||||
clientsClaim: true,
|
||||
skipWaiting: true,
|
||||
// EN KRİTİK: yeni SW beklemeden kontrolü alsın
|
||||
clientsClaim: true,
|
||||
skipWaiting: true,
|
||||
|
||||
// Eski workbox cache'lerini temizle
|
||||
cleanupOutdatedCaches: true,
|
||||
// Eski workbox cache'lerini temizle
|
||||
cleanupOutdatedCaches: mode === 'production',
|
||||
|
||||
// SPA fallback'i API çağrılarına uygulama
|
||||
navigateFallbackDenylist: [/^\/api\//],
|
||||
// SPA fallback'i API çağrılarına uygulama
|
||||
navigateFallbackDenylist: [/^\/api\//],
|
||||
|
||||
// ⭐⭐ BU KISMI EKLEYİN: Cache sorununu çözecek runtime caching
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: /\.(?:js|css|html|json)$/,
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'static-resources',
|
||||
expiration: {
|
||||
maxEntries: 50,
|
||||
maxAgeSeconds: 24 * 60 * 60 // 24 saat
|
||||
// ⭐⭐ BU KISMI EKLEYİN: Cache sorununu çözecek runtime caching
|
||||
runtimeCaching:
|
||||
mode === 'production'
|
||||
? [
|
||||
{
|
||||
urlPattern: /\.(?:js|css|html|json)$/,
|
||||
handler: 'NetworkFirst',
|
||||
options: {
|
||||
cacheName: 'static-resources',
|
||||
expiration: {
|
||||
maxEntries: 50,
|
||||
maxAgeSeconds: 24 * 60 * 60, // 24 saat
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp|ico)$/,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'images',
|
||||
expiration: {
|
||||
maxEntries: 100,
|
||||
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 gün
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: [],
|
||||
|
||||
// ⭐ YENİ EKLENEN: Additional navigation route for SPA
|
||||
navigateFallback: '/index.html',
|
||||
navigateFallbackAllowlist: [/^(?!\/__).*/],
|
||||
},
|
||||
|
||||
manifest: {
|
||||
name: 'Sözsoft Kurs Platform',
|
||||
short_name: 'Sözsoft Kurs Platform',
|
||||
theme_color: '#FF99C8',
|
||||
background_color: '#f0e7db',
|
||||
display: 'standalone',
|
||||
icons: [
|
||||
{
|
||||
src: '/img/logo/logo-400.png',
|
||||
sizes: '400x400',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable',
|
||||
},
|
||||
cacheableResponse: {
|
||||
statuses: [0, 200]
|
||||
}
|
||||
}
|
||||
{
|
||||
src: '/img/logo/logo-192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable',
|
||||
},
|
||||
{
|
||||
src: '/img/logo/logo-512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable',
|
||||
},
|
||||
],
|
||||
categories: ['business', 'productivity'],
|
||||
description: 'Sözsoft Kurs Platform Application',
|
||||
},
|
||||
{
|
||||
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp|ico)$/,
|
||||
handler: 'CacheFirst',
|
||||
options: {
|
||||
cacheName: 'images',
|
||||
expiration: {
|
||||
maxEntries: 100,
|
||||
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 gün
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// ⭐ YENİ EKLENEN: Additional navigation route for SPA
|
||||
navigateFallback: '/index.html',
|
||||
navigateFallbackAllowlist: [/^(?!\/__).*/]
|
||||
},
|
||||
|
||||
manifest: {
|
||||
name: 'Sözsoft Kurs Platform',
|
||||
short_name: 'Sözsoft Kurs Platform',
|
||||
theme_color: '#FF99C8',
|
||||
background_color: '#f0e7db',
|
||||
display: 'standalone',
|
||||
icons: [
|
||||
{
|
||||
src: '/img/logo/logo-400.png',
|
||||
sizes: '400x400',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable'
|
||||
},
|
||||
{
|
||||
src: '/img/logo/logo-192.png',
|
||||
sizes: '192x192',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable'
|
||||
},
|
||||
{
|
||||
src: '/img/logo/logo-512.png',
|
||||
sizes: '512x512',
|
||||
type: 'image/png',
|
||||
purpose: 'any maskable'
|
||||
}
|
||||
],
|
||||
categories: ['business', 'productivity'],
|
||||
description: 'Sözsoft Kurs Platform Application'
|
||||
},
|
||||
}),
|
||||
})
|
||||
: null,
|
||||
],
|
||||
|
||||
server: {
|
||||
|
|
@ -116,8 +121,8 @@ export default defineConfig(async ({ mode }) => {
|
|||
// ⭐ YENİ EKLENEN: Hot reload için polling
|
||||
watch: {
|
||||
usePolling: true,
|
||||
interval: 1000
|
||||
}
|
||||
interval: 1000,
|
||||
},
|
||||
},
|
||||
|
||||
assetsInclude: ['**/*.md'],
|
||||
|
|
@ -133,7 +138,14 @@ export default defineConfig(async ({ mode }) => {
|
|||
build: {
|
||||
outDir: 'dist',
|
||||
sourcemap: false,
|
||||
emptyOutDir: true, // ✅ Build öncesi otomatik temizlik
|
||||
emptyOutDir: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `assets/[name].[hash].js`,
|
||||
chunkFileNames: `assets/[name].[hash].js`,
|
||||
assetFileNames: `assets/[name].[hash].[ext]`,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
preview: {
|
||||
|
|
@ -147,9 +159,9 @@ export default defineConfig(async ({ mode }) => {
|
|||
define: {
|
||||
'process.env': {},
|
||||
// ⭐ YENİ EKLENEN: Version tracking için global değişkenler
|
||||
__APP_VERSION__: JSON.stringify(process.env.npm_package_version || '1.0.0'),
|
||||
__APP_VERSION__: JSON.stringify(process.env.VITE_APP_VERSION || '1.0.0'),
|
||||
__BUILD_DATE__: JSON.stringify(new Date().toISOString()),
|
||||
__APP_MODE__: JSON.stringify(mode)
|
||||
__APP_MODE__: JSON.stringify(mode),
|
||||
},
|
||||
}
|
||||
})
|
||||
Loading…
Reference in a new issue