clean css and fetch image uniterally

This commit is contained in:
TDLaouer 2025-07-09 13:10:38 +02:00
parent 89da3d4c9f
commit 92de3895d7
12 changed files with 79 additions and 163 deletions

View File

@ -2,6 +2,9 @@
<template> <template>
<div class="container"> <div class="container">
<header>
<h1>Smash or Pass</h1>
</header>
<main> <main>
<router-view :key="$route.fullPath"></router-view> <router-view :key="$route.fullPath"></router-view>
</main> </main>

View File

@ -68,7 +68,6 @@
*, *,
*::before, *::before,
*::after { *::after {
box-sizing: border-box;
margin: 0; margin: 0;
font-weight: normal; font-weight: normal;
} }

View File

@ -4,14 +4,14 @@
:class="{ 'slide-right': animateTo === 'smash', 'slide-left': animateTo === 'pass' }" :class="{ 'slide-right': animateTo === 'smash', 'slide-left': animateTo === 'pass' }"
> >
<img <img
:src="image.imageSrc" :src="image?.imageSrc"
:alt="image.name" :alt="image?.name"
class="card-image" class="card-image"
@click="openImageInNewTab" @click="openImageInNewTab"
style="cursor: pointer" style="cursor: pointer"
/> />
<div class="controls"> <div class="controls">
<h2>{{ image.name }}</h2> <h2>{{ image?.name }}</h2>
<div> <div>
<button class="button button-pass" @click="choose('pass')">PASS</button> <button class="button button-pass" @click="choose('pass')">PASS</button>
<button class="button button-smash" @click="choose('smash')">SMASH</button> <button class="button button-smash" @click="choose('smash')">SMASH</button>
@ -22,11 +22,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import type { FrontVisualAsset, VisualAsset } from '@/types/types.ts' import type { FrontVisualAssetWithSrc } from '@/types/types.ts'
const props = defineProps({ const props = defineProps({
image: { image: {
type: Object as () => FrontVisualAsset, type: Object as () => FrontVisualAssetWithSrc | null,
required: true, required: true,
}, },
}) })
@ -44,7 +44,7 @@ const choose = (choice: string) => {
} }
const openImageInNewTab = () => { const openImageInNewTab = () => {
window.open(props.image.imageSrc, '_blank')?.focus() window.open(props.image?.imageSrc, '_blank')?.focus()
} }
</script> </script>

View File

@ -1,38 +1,49 @@
<template> <template>
<div v-if="visualAssetStore.visualAssets.length"> <div v-if="visualAssetStore.visualAssets.length">
<Card <Card
v-if="currentIndex < visualAssetStore.visualAssets.length" v-if="currentIndex < visualAssetStore.visualAssets.length"
:image="visualAssetStore.visualAssets[currentIndex]" :image="currentImage"
@choose="handleChoice" @choose="handleChoice"
/> />
<Result <Result v-else :smashList="smashList" :passList="passList" @restart="restart" />
v-else </div>
:smashList="smashList"
:passList="passList"
@restart="restart"
/>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Card from './Card.vue' import Card from './Card.vue'
import Result from './Result.vue' import Result from './Result.vue'
import { ref } from 'vue' import { computed, ref, watch } from 'vue'
import type { FrontVisualAsset, VisualAsset } from '@/types/types' import type { FrontVisualAsset, FrontVisualAssetWithSrc } from '@/types/types'
import { useVisualAssetStore } from '@/stores/visualAssetStore'; import { useVisualAssetStore } from '@/stores/visualAssetStore'
import router from '@/route'; import router from '@/route'
import { fetchVisualAssetById } from '@/services/visualAssetService'
const visualAssetStore = useVisualAssetStore(); const visualAssetStore = useVisualAssetStore()
const currentIndex = ref(0) const currentIndex = ref(0)
const smashList = ref<FrontVisualAsset[]>([]) const smashList = ref<FrontVisualAssetWithSrc[]>([])
const passList = ref<FrontVisualAsset[]>([]) const passList = ref<FrontVisualAssetWithSrc[]>([])
const currentImage = ref<FrontVisualAssetWithSrc | null>(null)
watch(
() => visualAssetStore.visualAssets[currentIndex.value],
async (newImage: FrontVisualAsset) => {
if (newImage) {
currentImage.value = await fetchVisualAssetById(newImage._id)
} else {
currentImage.value = null
}
},
{ immediate: true },
)
const handleChoice = (choice: string) => { const handleChoice = (choice: string) => {
if (!currentImage.value) return
if (choice === 'smash') { if (choice === 'smash') {
smashList.value.push(visualAssetStore.visualAssets[currentIndex.value]) smashList.value.push(currentImage.value)
} else { } else {
passList.value.push(visualAssetStore.visualAssets[currentIndex.value]) passList.value.push(currentImage.value)
} }
currentIndex.value++ currentIndex.value++
} }

View File

@ -1,13 +1,12 @@
<template> <template>
<div class="flex-container"> <div class="flex-container">
<h1>Smash or Pass</h1>
<ul class="collection-list"> <ul class="collection-list">
<li class="collection-item" v-for="(collection, name) in groupedAssets" :key="name"> <li class="collection-item" v-for="(collection, name) in groupedAssets" :key="name">
<span class="collection-name">{{ name }}</span> <span class="collection-name">{{ name }}</span>
<button class="button button-start" @click="startGame(name)">Start</button> <button class="button button-start" @click="startGame(name)">Start</button>
</li> </li>
</ul> </ul>
<UploadImages></UploadImages> <UploadImages v-if="isAdmin"></UploadImages>
</div> </div>
</template> </template>
@ -34,6 +33,10 @@ onMounted(() => {
}) })
}) })
const isAdmin = computed(() => {
return false
})
const groupedAssets = computed(() => { const groupedAssets = computed(() => {
const groupByCollection: Record<string, FrontVisualAsset[]> = {} const groupByCollection: Record<string, FrontVisualAsset[]> = {}
for (const asset of assets.value) { for (const asset of assets.value) {
@ -58,17 +61,20 @@ const startGame = (collection: string) => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%;
max-width: 900px;
margin: 0 auto;
} }
.collection-list { .collection-list {
width: 100%; width: 100%;
max-width: 400px; max-width: 800px;
padding: 0; padding: 0 2rem;
margin: 1.5rem 0; margin: 2rem 0;
list-style: none; list-style: none;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 2rem;
} }
.collection-item { .collection-item {

View File

@ -1,74 +0,0 @@
<template>
<div class="login-container">
<h2>Login</h2>
<form @submit.prevent="login">
<div class="form-group">
<label for="username">Username</label>
<input id="username" v-model="username" type="text" required />
</div>
<div class="form-group">
<label for="password">Password</label>
<input id="password" v-model="password" type="password" required />
</div>
<button class="button" type="submit">Login</button>
<p v-if="error" class="error">{{ error }}</p>
</form>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const username = ref('')
const password = ref('')
const error = ref('')
const login = () => {
if (username.value === 'admin' && password.value === 'password') {
error.value = ''
// Replace with your actual login logic or navigation
alert('Login successful!')
} else {
error.value = 'Invalid username or password'
}
}
</script>
<style scoped>
.login-container {
max-width: 350px;
margin: 3rem auto;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
text-align: center;
}
.form-group {
margin-bottom: 1.25rem;
text-align: left;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
}
input[type='text'],
input[type='password'] {
width: 100%;
padding: 0.5rem;
border-radius: 4px;
border: 1px solid #ccc;
font-size: 1rem;
}
.button {
width: 100%;
background: #4f8cff;
}
.button:hover {
background: #2563eb;
}
.error {
color: #e74c3c;
margin-top: 1rem;
}
</style>

View File

@ -24,23 +24,28 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { FrontVisualAsset } from '@/types/types.ts'; import type { FrontVisualAssetWithSrc } from '@/types/types.ts'
defineProps({ defineProps({
smashList: { smashList: {
type: Array<FrontVisualAsset>, type: Array<FrontVisualAssetWithSrc>,
required: true, required: true,
}, },
passList: { passList: {
type: Array<FrontVisualAsset>, type: Array<FrontVisualAssetWithSrc>,
required: true, required: true,
}, },
}); })
</script> </script>
<style scoped> <style scoped>
.result { .result {
text-align: center; text-align: center;
padding: 2rem; padding: 2rem;
gap: 2rem;
display: flex;
flex-direction: column;
align-items: center;
} }
.lists { .lists {
display: flex; display: flex;
@ -55,6 +60,7 @@ defineProps({
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 5rem;
} }
.image-grid { .image-grid {
@ -78,7 +84,7 @@ defineProps({
max-width: 100%; max-width: 100%;
min-width: 60px; min-width: 60px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
object-fit: contain; object-fit: contain;
aspect-ratio: 1/1; aspect-ratio: 1/1;
} }

View File

@ -7,7 +7,7 @@ import 'vue3-toastify/dist/index.css'
import axios from 'axios' import axios from 'axios'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
axios.defaults.baseURL = 'http://localhost:3000' axios.defaults.baseURL = 'http://localhost:3001'
const pinia = createPinia() const pinia = createPinia()

View File

@ -17,16 +17,7 @@ const routes: RouteRecordRaw[] = [
meta: { meta: {
title: 'Smash or Pass Game', title: 'Smash or Pass Game',
}, },
}, }
{
path: '/login',
name: 'login',
props: true,
component: () => import('@/components/Login.vue'),
meta: {
title: 'Login',
},
},
] ]
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),

View File

@ -1,5 +1,5 @@
import axios from 'axios' import axios from 'axios'
import type { FrontVisualAsset, VisualAsset } from '@/types/types.ts' import type { FrontVisualAsset, FrontVisualAssetWithSrc, VisualAsset } from '@/types/types.ts'
export const upload = async (visualAssets: VisualAsset[]): Promise<void> => { export const upload = async (visualAssets: VisualAsset[]): Promise<void> => {
try { try {
@ -44,10 +44,10 @@ export const fetchVisualAssetsByCollection = async (
} }
} }
export const fetchVisualAssetById = async (id: string): Promise<FrontVisualAsset> => { export const fetchVisualAssetById = async (id: string): Promise<FrontVisualAssetWithSrc> => {
try { try {
const response = await axios.get(`/api/visualAssets/${id}`) const response = await axios.get(`/api/visualAssets/${id}`)
return response.data as FrontVisualAsset return response.data as FrontVisualAssetWithSrc
} catch (error) { } catch (error) {
console.error(`Error fetching visual asset with id ${id}:`, error) console.error(`Error fetching visual asset with id ${id}:`, error)
throw error throw error

View File

@ -1,32 +0,0 @@
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
export const useUserStore = defineStore('user', () => {
const user = ref<string | null>(localStorage.getItem('user') || null)
const isLoggedIn = ref<boolean>(!!user.value)
function login(username: string) {
user.value = username
isLoggedIn.value = true
localStorage.setItem('user', username)
}
function logout() {
user.value = null
isLoggedIn.value = false
localStorage.removeItem('user')
}
// Keep session persistent if user changes
watch(user, (newUser) => {
if (newUser) {
localStorage.setItem('user', newUser)
isLoggedIn.value = true
} else {
localStorage.removeItem('user')
isLoggedIn.value = false
}
})
return { user, isLoggedIn, login, logout }
})

View File

@ -1,2 +1,8 @@
export type VisualAsset = { collection: string; image: File; name: string } export type VisualAsset = { collection: string; image: File; name: string }
export type FrontVisualAsset = { _id: string; collection: string; imageSrc: string; name: string } export type FrontVisualAsset = { _id: string; collection: string; name: string; imageSrc: string }
export type FrontVisualAssetWithSrc = {
_id: string
collection: string
name: string
imageSrc: string
}