Nuxt3=>4, spa-loader, css shenanigans...:

...css shenanigans: daisyui may have had a breaking change at 5.3.0, it wrecks the ui here, as tailwind forces its own styles over daisyuis overrides, for now staying at ~5.2.5, for good measure also @nuxtjs/tailwindcss stays at ^6.13.2 (6.13.2 in pnpmlock) - this may be, because (but i'm not sure) @nuxtjs/tailwind is tailwind3 and not tailwind4?

Nuxt3=>4 migration:
also changed nuxtimage settings, as the docs have been fixed, path changes, etc
This commit is contained in:
NetMan 2025-12-17 21:33:36 +01:00
parent 08e2060245
commit 625c1013da
31 changed files with 3514 additions and 3150 deletions

View file

@ -23,15 +23,16 @@ A shell script at [media-prawo-jazdy](https://git.mandarynki.eu/netman/media-pra
- [x] exam (& results?) warning leave message on exit and timer end (and definitely on refresh)
- [x] add keybinds:
- S - start, D - next question, X - exam end, T/Y - yes, N - no, A - A, B - B, C - C
- [ ] i18n - pl, en, de, ua (not all questions are available in ua, api handle)
- [ ] i18n - pl, en, de, ua (<del>not all questions are available in ua, api handle</del> - already handled with sql)
- [ ] UI i18n
- [x] pl
- [x] en
- [x] de
- [ ] ua
- [ ] <b>ua</b>
- [x] db: examstore add language field, api handle languages (questions lang)
- [ ] nuxt3 -> nuxt4
- [x] nuxt3 -> nuxt4 (hopefully working fine, seems to do so; so far)
- [ ] clean up js code in exam.vue and result.vue (currently a little bit of a mess)
- [ ] daisyui is stuck on 5.2.5 - newer versions break UI, reason unknown - consider moving to nuxtUI and another tailwind integration with nuxt than nuxtjs/tailwind
## Some information about the project

View file

@ -0,0 +1,55 @@
<!-- <script setup lang="ts">
const themeStore = useThemeStore();
if (
themeStore.theme != 'light' ||
(window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches) ||
) {
sheet.insertRule(
'.spa-flex { background: #0f172b !important; color: #fff !important; }',
sheet.cssRules.length,
);
}
</script> -->
<template>
<div class="min-h-dvh text-2xl w-full">
<div class="spa-loader-flex">
<div class="spa-loader-circle"></div>
<div class="spa-loader-text"></div>
</div>
</div>
</template>
<style>
.spa-loader-flex {
display: flex;
flex-direction: column;
min-height: 100dvh;
width: 100%;
justify-content: center;
align-items: center;
gap: 16px;
}
.spa-loader-circle {
display: block;
width: 36px;
height: 36px;
box-sizing: border-box;
border: solid 4px transparent;
border-top-color: #000;
border-left-color: #000;
border-bottom-color: #efefef;
border-right-color: #efefef;
border-radius: 50%;
animation: spaLoaderAnimation 400ms linear infinite;
}
@keyframes spaLoaderAnimation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View file

@ -39,10 +39,10 @@ const emit = defineEmits<{
class="btn btn-md"
name="chooser"
:class="`${
result.basic[num].chosen_answer == ''
result.basic[num]?.chosen_answer == ''
? 'btn-warning'
: result.basic[num].question?.correct_answer ===
result.basic[num].chosen_answer
: result.basic[num]?.question?.correct_answer ===
result.basic[num]?.chosen_answer
? 'btn-success'
: 'btn-error'
} ${now === 'basic' && countBasic === num ? 'outline-set-solid outline-2' : ''}`"
@ -69,10 +69,10 @@ const emit = defineEmits<{
class="btn btn-md"
name="chooser"
:class="`${
result.advanced[num].chosen_answer == ''
result.advanced[num]?.chosen_answer == ''
? 'btn-warning'
: result.advanced[num].question?.correct_answer ===
result.advanced[num].chosen_answer
: result.advanced[num]?.question?.correct_answer ===
result.advanced[num]?.chosen_answer
? 'btn-success'
: 'btn-error'
} ${now === 'advanced' && countAdvanced === num ? 'outline-set-solid outline-2' : ''}`"

View file

@ -1,7 +1,11 @@
<script lang="ts" setup>
import { intervalToDuration, addMinutes, addSeconds, isAfter } from 'date-fns';
import { useNow } from '@vueuse/core';
import { useExamStore } from '~/store/examStore';
import {
useExamStore,
useBasicStore,
useAdvancedStore,
} from '~/stores/examStore';
definePageMeta({ middleware: 'exam' });

View file

@ -1,6 +1,9 @@
<script setup lang="ts">
import CountryFlag from 'vue-country-flag-next';
import categories from '~/categories';
import categories from '~~/categories';
import { useExamStore } from '~/stores/examStore';
import { useThemeStore } from '~/stores/themeStore';
onMounted(() => {
useHead({
@ -95,6 +98,7 @@ function themeAuto() {
{{ $t('auto') }}
</div>
</div>
<div
class="flex flex-col flex-wrap gap-2 items-start p-4 bg-base-300 border-1 border-slate-500 rounded-xl w-fit"
>

View file

@ -0,0 +1,60 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>nuxt-prawo-jazdy</title>
<style>
.spa-loader-flex {
display: flex;
flex-direction: column;
min-height: 100dvh;
width: 100%;
justify-content: center;
align-items: center;
gap: 16px;
}
.spa-loader-circle {
display: block;
width: 36px;
height: 36px;
box-sizing: border-box;
border: solid 4px transparent;
border-top-color: #000;
border-left-color: #000;
border-bottom-color: #efefef;
border-right-color: #efefef;
border-radius: 50%;
animation: spaLoaderAnimation 400ms linear infinite;
}
@keyframes spaLoaderAnimation {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<script>
const colorMode = localStorage.getItem('themeStore');
const json = JSON.parse(colorMode);
if (
(window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches) ||
Object.hasOwn(colorMode, 'theme') == 'dark'
) {
const sheet = window.document.styleSheets[0];
sheet.insertRule(
'.spa-flex { background: #0f172b !important; color: #fff !important; }',
sheet.cssRules.length,
);
}
</script>
</head>
<body>
<div class="spa-loader-flex">
<div class="spa-loader-circle"></div>
</div>
</body>
</html>

View file

@ -1,8 +0,0 @@
<template>
<div
class="flex min-h-dvh justify-center items-center text-5xl flex-col gap-10"
>
<span class="loading loading-spinner loading-xl scale-[2.5] block" />
<span class="block">{{ $t('loading') }}</span>
</div>
</template>

View file

@ -16,7 +16,7 @@ export default defineNuxtConfig({
dirs: ['types/*.ts', 'store/*.ts', 'types/**/*.ts'],
},
devtools: { enabled: true },
css: ['~/assets/main.css'],
css: ['~/assets/css/tailwind.css'],
runtimeConfig: {
public: {
cdn_url: process.env.CDN_URL,
@ -25,7 +25,7 @@ export default defineNuxtConfig({
routeRules: {
'/': { prerender: true },
},
compatibilityDate: '2024-11-01',
compatibilityDate: '2025-12-17',
eslint: {
config: {
stylistic: {
@ -49,7 +49,7 @@ export default defineNuxtConfig({
providers: {
selfhost: {
name: 'selfhost',
provider: '~/providers/selfhost.ts',
provider: '~~/providers/selfhost.ts',
options: {
baseUrl: process.env.CDN_URL,
},
@ -57,7 +57,5 @@ export default defineNuxtConfig({
},
provider: 'selfhost',
},
pinia: {
storesDirs: ['./store/**'],
},
tailwindcss: { exposeConfig: true },
});

View file

@ -15,22 +15,20 @@
},
"dependencies": {
"@libsql/client": "^0.15.15",
"@nuxt/fonts": "0.11.1",
"@nuxt/image": "1.10.0",
"@nuxt/fonts": "0.12.1",
"@nuxt/image": "2.0.0",
"@nuxtjs/i18n": "10.2.1",
"@nuxtjs/tailwindcss": "6.13.2",
"@pinia/nuxt": "0.11.0",
"@pinia/nuxt": "0.11.3",
"@vueuse/core": "^14.1.0",
"array-shuffle": "^3.0.0",
"daisyui": "^5.0.27",
"array-shuffle": "^4.0.0",
"date-fns": "^4.1.0",
"dotenv": "^16.5.0",
"drizzle-kit": "^0.31.0",
"drizzle-orm": "^0.42.0",
"eslint": "^9.24.0",
"dotenv": "^17.2.3",
"drizzle-kit": "^0.31.8",
"drizzle-orm": "^0.45.1",
"eslint": "^9.39.2",
"lodash": "^4.17.21",
"nuxt": "~3.16.2",
"pinia": "^3.0.2",
"nuxt": "~4.2.2",
"pinia": "^3.0.4",
"pinia-plugin-persistedstate": "^4.7.1",
"ufo": "^1.6.1",
"vue": "latest",
@ -39,12 +37,22 @@
},
"packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af",
"devDependencies": {
"@nuxt/eslint": "1.3.0",
"@types/lodash": "^4.17.16",
"eslint-config-prettier": "^10.1.2",
"prettier": "^3.5.3",
"tsx": "^4.19.3",
"@nuxt/eslint": "1.12.1",
"@nuxtjs/tailwindcss": "^6.13.2",
"@types/lodash": "^4.17.21",
"daisyui": "~5.2.0",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.7.4",
"tsx": "^4.21.0",
"typescript": "^5.8.3",
"vite-plugin-eslint2": "^5.0.3"
"vite-plugin-eslint2": "^5.0.4"
},
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",
"esbuild",
"sharp",
"unrs-resolver"
]
}
}

6414
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,18 @@
import { joinURL } from 'ufo';
import type { ProviderGetImage } from '@nuxt/image';
import { createOperationsGenerator } from '#image';
import { createOperationsGenerator, defineProvider } from '@nuxt/image/runtime';
const operationsGenerator = createOperationsGenerator();
export const getImage: ProviderGetImage = (
src,
{ modifiers = {}, baseUrl } = {},
) => {
baseUrl ??= '';
export default defineProvider<{ baseURL?: string }>({
getImage(src, { modifiers, baseURL }) {
if (!baseURL) {
baseURL = useRuntimeConfig().public.cdn_url;
}
const operations = operationsGenerator(modifiers);
const operations = operationsGenerator(modifiers);
return {
url: joinURL(baseUrl, src + (operations ? '?' + operations : '')),
};
};
return {
url: joinURL(baseURL, src + (operations ? '?' + operations : '')),
};
},
});

View file

@ -2,9 +2,13 @@ import 'dotenv/config';
import { drizzle } from 'drizzle-orm/libsql';
import { sql, eq, and } from 'drizzle-orm';
import arrayShuffle from 'array-shuffle';
import { tasks_advanced, questions_advanced, categories_db } from '~/db/schema';
import type { AdvancedQuestion } from '~/types';
import categories from '~/categories';
import {
tasks_advanced,
questions_advanced,
categories_db,
} from '~~/db/schema';
import type { AdvancedQuestion } from '~~/shared/types';
import categories from '~~/categories';
export default defineEventHandler(async (event) => {
const query = getQuery(event);

View file

@ -2,9 +2,9 @@ import 'dotenv/config';
import { drizzle } from 'drizzle-orm/libsql';
import { sql, eq, and } from 'drizzle-orm';
import arrayShuffle from 'array-shuffle';
import { tasks, questions, categories_db } from '~/db/schema';
import type { BasicQuestion } from '~/types';
import categories from '~/categories';
import { tasks, questions, categories_db } from '~~/db/schema';
import type { BasicQuestion } from '~~/shared/types';
import categories from '~~/categories';
export default defineEventHandler(async (event) => {
const query = getQuery(event);

View file

@ -1,5 +1,3 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
plugins: [require('daisyui')],
daisyui: {