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>
<div class="container">
<header>
<h1>Smash or Pass</h1>
</header>
<main>
<router-view :key="$route.fullPath"></router-view>
</main>

View File

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

View File

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

View File

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

View File

@ -1,13 +1,12 @@
<template>
<div class="flex-container">
<h1>Smash or Pass</h1>
<ul class="collection-list">
<li class="collection-item" v-for="(collection, name) in groupedAssets" :key="name">
<span class="collection-name">{{ name }}</span>
<button class="button button-start" @click="startGame(name)">Start</button>
</li>
</ul>
<UploadImages></UploadImages>
<UploadImages v-if="isAdmin"></UploadImages>
</div>
</template>
@ -34,6 +33,10 @@ onMounted(() => {
})
})
const isAdmin = computed(() => {
return false
})
const groupedAssets = computed(() => {
const groupByCollection: Record<string, FrontVisualAsset[]> = {}
for (const asset of assets.value) {
@ -58,17 +61,20 @@ const startGame = (collection: string) => {
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
max-width: 900px;
margin: 0 auto;
}
.collection-list {
width: 100%;
max-width: 400px;
padding: 0;
margin: 1.5rem 0;
max-width: 800px;
padding: 0 2rem;
margin: 2rem 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 1rem;
gap: 2rem;
}
.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>
<script setup lang="ts">
import type { FrontVisualAsset } from '@/types/types.ts';
import type { FrontVisualAssetWithSrc } from '@/types/types.ts'
defineProps({
smashList: {
type: Array<FrontVisualAsset>,
type: Array<FrontVisualAssetWithSrc>,
required: true,
},
passList: {
type: Array<FrontVisualAsset>,
type: Array<FrontVisualAssetWithSrc>,
required: true,
},
});
})
</script>
<style scoped>
.result {
text-align: center;
padding: 2rem;
gap: 2rem;
display: flex;
flex-direction: column;
align-items: center;
}
.lists {
display: flex;
@ -55,6 +60,7 @@ defineProps({
display: flex;
flex-direction: column;
align-items: center;
gap: 5rem;
}
.image-grid {
@ -78,7 +84,7 @@ defineProps({
max-width: 100%;
min-width: 60px;
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;
aspect-ratio: 1/1;
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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> => {
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 {
const response = await axios.get(`/api/visualAssets/${id}`)
return response.data as FrontVisualAsset
return response.data as FrontVisualAssetWithSrc
} catch (error) {
console.error(`Error fetching visual asset with id ${id}:`, 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 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
}