Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions app/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

@layer base {
body {
@apply text-black min-h-screen relative;
@apply relative min-h-screen text-black;
}

::selection {
Expand Down Expand Up @@ -42,11 +42,11 @@
}

::-webkit-scrollbar-track {
@apply bg-gray-100 border border-black;
@apply border border-black bg-gray-100;
}

::-webkit-scrollbar-thumb {
@apply bg-black border border-gray-100;
@apply border border-gray-100 bg-black;
}

::-webkit-scrollbar-thumb:hover {
Expand Down
2 changes: 1 addition & 1 deletion app/components/DebugWebsockets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { data: connections, refresh: refreshConnections } = useFetch('/api/websoc
</script>

<template>
<div class="fixed bottom-0 left-0 right-0 max-h-52 overflow-y-auto border-t border-gray-300 bg-gray-100 p-4">
<div class="fixed inset-x-0 bottom-0 max-h-52 overflow-y-auto border-t border-gray-300 bg-gray-100 p-4">
<h3 class="mb-2 font-bold">
Active WebSocket Connections
</h3>
Expand Down
2 changes: 1 addition & 1 deletion app/components/app/AppFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const yearSpan = yearStart === yearCurrent ? yearStart : `${yearStart} - ${yearC
>
<span>Source Code on</span>
<svg
class="h-5 w-5"
class="size-5"
fill="none"
height="24"
stroke="currentColor"
Expand Down
2 changes: 1 addition & 1 deletion app/components/ui/UiRadioOption.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function handleChange() {
>
<input
:checked="modelValue === value"
class="mr-4 h-5 w-5"
class="mr-4 size-5"
:class="modelValue === value ? 'accent-white' : 'accent-black'"
:disabled="disabled"
type="radio"
Expand Down
20 changes: 13 additions & 7 deletions app/pages/admin/questions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ const newQuestion = ref<{
}>({
key: '',
question_text: '{\n "en": ""\n}',
answer_options: [{ text: '{\n "en": ""\n}', emoji: '' }, { text: '{\n "en": ""\n}', emoji: '' }],
answer_options: [
{ text: '{\n "en": ""\n}', emoji: '' },
{ text: '{\n "en": ""\n}', emoji: '' },
],
note: '{\n "en": ""\n}',
})

Expand Down Expand Up @@ -60,12 +63,12 @@ async function handleCreateQuestion() {
const parsedNote = newQuestion.value.note.trim() ? JSON.parse(newQuestion.value.note) : undefined
const note = parsedNote && typeof parsedNote === 'object'
? (
Object.values(parsedNote as Record<string, string>).some(
v => typeof v === 'string' && v.trim(),
)
? parsedNote
: undefined
Object.values(parsedNote as Record<string, string>).some(
v => typeof v === 'string' && v.trim(),
)
? parsedNote
: undefined
)
: undefined

const answerOptions = newQuestion.value.answer_options.map(opt => ({
Expand Down Expand Up @@ -102,7 +105,10 @@ async function handleCreateQuestion() {
newQuestion.value = {
key: '',
question_text: '{\n "en": ""\n}',
answer_options: [{ text: '{\n "en": ""\n}', emoji: '' }, { text: '{\n "en": ""\n}', emoji: '' }],
answer_options: [
{ text: '{\n "en": ""\n}', emoji: '' },
{ text: '{\n "en": ""\n}', emoji: '' },
],
note: '{\n "en": ""\n}',
}

Expand Down
6 changes: 3 additions & 3 deletions app/pages/admin/results.vue
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,9 @@ async function unpublishActiveQuestion() {
{{ t('waitingForQuestion') }}
</p>
<div class="flex justify-center gap-2.5">
<span class="h-5 w-5 animate-bounce bg-black" />
<span class="h-5 w-5 animate-bounce bg-black [animation-delay:0.2s]" />
<span class="h-5 w-5 animate-bounce bg-black [animation-delay:0.4s]" />
<span class="size-5 animate-bounce bg-black" />
<span class="size-5 animate-bounce bg-black [animation-delay:0.2s]" />
<span class="size-5 animate-bounce bg-black [animation-delay:0.4s]" />
</div>
</div>
</UiSection>
Expand Down
27 changes: 17 additions & 10 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,14 @@ async function submitEmoji() {
}
}

const quickEmojis = ['👍', '❤️', '😂', '🤔', '👏', '❓']
const quickEmojis = [
'👍',
'❤️',
'😂',
'🤔',
'👏',
'❓',
]

async function sendQuickEmoji(emoji: string) {
if (isEmojiCooldown.value) return
Expand All @@ -184,7 +191,7 @@ async function sendQuickEmoji(emoji: string) {
<UiPageTitle>{{ t('pageTitle') }}</UiPageTitle>

<!-- Nickname Prompt -->
<div v-if="!userNickname" class="mx-auto max-w-lg border-[4px] border-black bg-white p-10 text-center">
<div v-if="!userNickname" class="mx-auto max-w-lg border-4 border-black bg-white p-10 text-center">
<h2 class="mb-4 text-3xl">
{{ t('welcome') }}
</h2>
Expand All @@ -207,15 +214,15 @@ async function sendQuickEmoji(emoji: string) {
<!-- With Nickname -->
<div v-else class="flex flex-col gap-8">
<!-- Display Nickname with change function -->
<div class="flex items-center justify-between border-[4px] border-black bg-white p-4">
<div class="flex items-center justify-between border-4 border-black bg-white p-4">
<span>{{ t('playingAs') }} <strong class="text-lg">{{ userNickname }}</strong></span>
<UiButton @click="changeNickname">
{{ t('changeButton') }}
</UiButton>
</div>

<!-- Emoji Submission -->
<div class="border-[4px] border-black bg-white p-6">
<div class="border-4 border-black bg-white p-6">
<div class="flex flex-wrap items-center justify-center gap-3">
<button
v-for="emoji in quickEmojis"
Expand All @@ -231,7 +238,7 @@ async function sendQuickEmoji(emoji: string) {
<form class="flex items-center" @submit.prevent="submitEmoji">
<UiInput
v-model="emojiInput"
class="h-16 w-16 flex-shrink-0 border-r-0 text-center text-2xl"
class="size-16 flex-shrink-0 border-r-0 text-center text-2xl"
placeholder="?"
/>
<UiButton class="h-16" :disabled="isEmojiCooldown" type="submit">
Expand All @@ -243,7 +250,7 @@ async function sendQuickEmoji(emoji: string) {
</div>

<!-- Active Question -->
<div v-if="activeQuestion" class="border-[4px] border-black bg-white p-8">
<div v-if="activeQuestion" class="border-4 border-black bg-white p-8">
<div class="mb-4 flex items-center justify-between">
<UiButton size="small" variant="secondary" @click="refreshQuestion">
🔄 {{ t('refreshButton') }}
Expand Down Expand Up @@ -288,17 +295,17 @@ async function sendQuickEmoji(emoji: string) {
</div>

<!-- No Active Question -->
<div v-else class="border-[4px] border-black bg-white px-8 py-16 text-center">
<div v-else class="border-4 border-black bg-white px-8 py-16 text-center">
<h2 class="mb-4 text-3xl">
{{ t('waitingForQuestion') }}
</h2>
<p class="mb-8 text-xl">
{{ t('presenterWillStart') }}
</p>
<div class="flex justify-center gap-2.5">
<span class="h-4 w-4 animate-pulse bg-black" />
<span class="h-4 w-4 animate-pulse bg-black [animation-delay:0.2s]" />
<span class="h-4 w-4 animate-pulse bg-black [animation-delay:0.4s]" />
<span class="size-4 animate-pulse bg-black" />
<span class="size-4 animate-pulse bg-black [animation-delay:0.2s]" />
<span class="size-4 animate-pulse bg-black [animation-delay:0.4s]" />
</div>
</div>
</div>
Expand Down
4 changes: 3 additions & 1 deletion app/utils/seededShuffle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ function mulberry32(seed: number): () => number {

/** Returns a shuffled copy of the array using a deterministic seed string. */
export function seededShuffle<T>(array: T[], seed: string): T[] {
const copy = [...array]
const copy = [
...array,
]
const rng = mulberry32(hashString(seed))
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1))
Expand Down
23 changes: 11 additions & 12 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
version: "3.9"

networks:
traefik-public:
external: true
services:
app:
build:
context: .
dockerfile: Dockerfile
image: stage-flow-tools
container_name: stage-flow-tools
restart: unless-stopped
networks:
- traefik-public
volumes:
- ./data:/app/data
- ./.data/db:/app/.data/db
environment:
- NUXT_JWT_SECRET=${NUXT_JWT_SECRET}
image: stage-flow-tools
labels:
- traefik.enable=true
- traefik.http.routers.stage-flow-tools.rule=Host(`quiz.your-domain.com`)
- traefik.http.routers.stage-flow-tools.entrypoints=websecure
- traefik.http.services.stage-flow-tools.loadbalancer.server.port=3000
- traefik.http.routers.stage-flow-tools.tls.certresolver=myresolver

networks:
traefik-public:
external: true
networks:
- traefik-public
restart: unless-stopped
volumes:
- ./data:/app/data
- ./.data/db:/app/.data/db
version: "3.9"
94 changes: 47 additions & 47 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,81 +2,81 @@ import { version } from './package.json'

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({

compatibilityDate: '2025-07-15',

css: [
'~/assets/css/main.css',
],

devtools: {
enabled: true,
},

eslint: { // for `@nuxt/eslint`
config: {
stylistic: {
indent: 2,
quotes: 'single',
},
},
},

i18n: {
defaultLocale: 'en',
locales: [
{ code: 'en', language: 'en-US', name: 'English' },
{ code: 'de', language: 'de-DE', name: 'Deutsch' },
{ code: 'ja', language: 'ja-JP', name: '日本語' },
],
vueI18n: './i18n.config.ts', // Using a separate file for better organization
},
modules: [
'@nuxt/eslint',
'@nuxtjs/i18n',
'@nuxtjs/tailwindcss',
'@vueuse/nuxt',
],

ssr: false,

devtools: {
enabled: true,
nitro: {
devStorage: {
data: { base: './.data/db', driver: 'fs' },
},
experimental: {
websocket: true,
},
storage: {
data: { base: './.data/db', driver: 'fs' },
},
},

css: [
'~/assets/css/main.css',
],

runtimeConfig: {
// Private keys (only available server-side)
adminUsername: 'admin',
adminPassword: '123',
adminUsername: 'admin',
jwtSecret: 'tryUJ0zQbstPbTOrezme+Fv+KndzDNRx5lmSeelr2ial2/2yV8HqLeQ2felJafqf',

Comment thread
toddeTV marked this conversation as resolved.
// Public keys (available on both client and server)
public: {
wsUrl: '',
apiUrl: '',
host: '0.0.0.0',
port: '3000',
debug: {
showWebsocketConnectionsInFrontend: false,
showConsoleOutputs: false,
showWebsocketConnectionsInFrontend: false,
},
version: version,
emojiCooldownMs: 1500,
host: '0.0.0.0',
port: '3000',
version,
wsUrl: '',
},
},

compatibilityDate: '2025-07-15',
ssr: false,

nitro: {
experimental: {
websocket: true,
},
storage: {
data: { driver: 'fs', base: './.data/db' },
},
devStorage: {
data: { driver: 'fs', base: './.data/db' },
},
tailwindcss: { // for Nuxt module `@nuxtjs/tailwindcss`
},

typescript: {
shim: false,
},

eslint: { // for `@nuxt/eslint`
config: {
stylistic: {
indent: 2,
quotes: 'single',
},
},
},

i18n: {
locales: [
{ code: 'en', language: 'en-US', name: 'English' },
{ code: 'de', language: 'de-DE', name: 'Deutsch' },
{ code: 'ja', language: 'ja-JP', name: '日本語' },
],
defaultLocale: 'en',
vueI18n: './i18n.config.ts', // Using a separate file for better organization
},

tailwindcss: { // for Nuxt module `@nuxtjs/tailwindcss`
},
})
Loading
Loading