user info + max size
This commit is contained in:
@@ -2,13 +2,15 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import PuzzleCard from './components/PuzzleCard.vue'
|
||||
import SubmissionForm from './components/SubmissionForm.vue'
|
||||
import { puzzleHelpers, submissionHelpers, errorHelpers } from './services/apiService'
|
||||
import type { SteamCollection, SteamCollectionItem, Submission, PuzzleResponse } from './types'
|
||||
import AdminPanel from './components/AdminPanel.vue'
|
||||
import { puzzleHelpers, submissionHelpers, errorHelpers, apiService } from './services/apiService'
|
||||
import type { SteamCollection, SteamCollectionItem, Submission, PuzzleResponse, UserInfo } from './types'
|
||||
|
||||
// API data
|
||||
const collections = ref<SteamCollection[]>([])
|
||||
const puzzles = ref<SteamCollectionItem[]>([])
|
||||
const submissions = ref<Submission[]>([])
|
||||
const userInfo = ref<UserInfo | null>(null)
|
||||
const isLoading = ref(true)
|
||||
const showSubmissionModal = ref(false)
|
||||
const error = ref<string>('')
|
||||
@@ -92,15 +94,22 @@ const mockPuzzles: SteamCollectionItem[] = [
|
||||
}
|
||||
]
|
||||
|
||||
// Computed properties
|
||||
const isSuperuser = computed(() => {
|
||||
return userInfo.value?.is_superuser || false
|
||||
})
|
||||
|
||||
// Computed property to get responses grouped by puzzle
|
||||
const responsesByPuzzle = computed(() => {
|
||||
const grouped: Record<number, PuzzleResponse[]> = {}
|
||||
submissions.value.forEach(submission => {
|
||||
submission.responses.forEach(response => {
|
||||
if (!grouped[response.puzzle_id]) {
|
||||
grouped[response.puzzle_id] = []
|
||||
// Handle both number and object types for puzzle field
|
||||
const puzzleId = typeof response.puzzle === 'number' ? response.puzzle : response.puzzle.id
|
||||
if (!grouped[puzzleId]) {
|
||||
grouped[puzzleId] = []
|
||||
}
|
||||
grouped[response.puzzle_id].push(response)
|
||||
grouped[puzzleId].push(response)
|
||||
})
|
||||
})
|
||||
return grouped
|
||||
@@ -111,9 +120,23 @@ onMounted(async () => {
|
||||
isLoading.value = true
|
||||
error.value = ''
|
||||
|
||||
console.log('Starting data load...')
|
||||
|
||||
// Load user info
|
||||
console.log('Loading user info...')
|
||||
const userResponse = await apiService.getUserInfo()
|
||||
if (userResponse.data) {
|
||||
userInfo.value = userResponse.data
|
||||
console.log('User info loaded:', userResponse.data)
|
||||
} else if (userResponse.error) {
|
||||
console.warn('User info error:', userResponse.error)
|
||||
}
|
||||
|
||||
// Load puzzles from API
|
||||
console.log('Loading puzzles...')
|
||||
const loadedPuzzles = await puzzleHelpers.loadPuzzles()
|
||||
puzzles.value = loadedPuzzles
|
||||
console.log('Puzzles loaded:', loadedPuzzles.length)
|
||||
|
||||
// Create mock collection from loaded puzzles for display
|
||||
if (loadedPuzzles.length > 0) {
|
||||
@@ -129,17 +152,23 @@ onMounted(async () => {
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString()
|
||||
}]
|
||||
console.log('Collection created')
|
||||
}
|
||||
|
||||
// Load existing submissions
|
||||
console.log('Loading submissions...')
|
||||
const loadedSubmissions = await submissionHelpers.loadSubmissions()
|
||||
submissions.value = loadedSubmissions
|
||||
console.log('Submissions loaded:', loadedSubmissions.length)
|
||||
|
||||
console.log('Data load complete!')
|
||||
|
||||
} catch (err) {
|
||||
error.value = errorHelpers.getErrorMessage(err)
|
||||
console.error('Failed to load data:', err)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
console.log('Loading state set to false')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -208,6 +237,17 @@ const findPuzzleByName = (ocrPuzzleName: string): SteamCollectionItem | null =>
|
||||
<div class="flex-1">
|
||||
<h1 class="text-xl font-bold">Opus Magnum Puzzle Submitter</h1>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<div v-if="userInfo?.is_authenticated" class="flex items-center gap-2">
|
||||
<div class="text-sm">
|
||||
<span class="font-medium">{{ userInfo.username }}</span>
|
||||
<span v-if="userInfo.is_superuser" class="badge badge-warning badge-xs ml-1">Admin</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-sm text-base-content/70">
|
||||
Not logged in
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -255,6 +295,11 @@ const findPuzzleByName = (ocrPuzzleName: string): SteamCollectionItem | null =>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Panel (only for superusers) -->
|
||||
<div v-if="isSuperuser">
|
||||
<AdminPanel />
|
||||
</div>
|
||||
|
||||
<!-- Puzzles Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<PuzzleCard
|
||||
|
||||
@@ -183,10 +183,22 @@ const loadData = async () => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
|
||||
// Load stats
|
||||
const statsResponse = await apiService.getStats()
|
||||
if (statsResponse.data) {
|
||||
stats.value = statsResponse.data
|
||||
// Load stats (skip if endpoint doesn't exist)
|
||||
try {
|
||||
const statsResponse = await apiService.getStats()
|
||||
if (statsResponse.data) {
|
||||
stats.value = statsResponse.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Stats endpoint not available:', error)
|
||||
// Set default stats
|
||||
stats.value = {
|
||||
total_submissions: 0,
|
||||
total_responses: 0,
|
||||
needs_validation: 0,
|
||||
validated_submissions: 0,
|
||||
validation_rate: 0
|
||||
}
|
||||
}
|
||||
|
||||
// Load responses needing validation
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-base-content/50">
|
||||
Supported formats: JPG, PNG, GIF (max 10MB each)
|
||||
Supported formats: JPG, PNG, GIF (max 256MB each)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -238,9 +238,9 @@ const isValidFile = (file: File): boolean => {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check file size (10MB limit)
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
error.value = `${file.name} is too large (max 10MB)`
|
||||
// Check file size (256MB limit)
|
||||
if (file.size > 256 * 1024 * 1024) {
|
||||
error.value = `${file.name} is too large (max 256MB)`
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<span class="text-sm font-medium">Solutions ({{ responses.length }})</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<div>
|
||||
<table class="table table-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -2,7 +2,8 @@ import type {
|
||||
SteamCollectionItem,
|
||||
Submission,
|
||||
PuzzleResponse,
|
||||
SubmissionFile
|
||||
SubmissionFile,
|
||||
UserInfo
|
||||
} from '../types'
|
||||
|
||||
// API Configuration
|
||||
@@ -179,6 +180,11 @@ export class ApiService {
|
||||
async healthCheck(): Promise<ApiResponse<{ status: string; service: string }>> {
|
||||
return this.request<{ status: string; service: string }>('/health')
|
||||
}
|
||||
|
||||
// User info
|
||||
async getUserInfo(): Promise<ApiResponse<UserInfo>> {
|
||||
return this.request<UserInfo>('/user')
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
|
||||
@@ -73,3 +73,15 @@ export interface Submission {
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export interface UserInfo {
|
||||
id?: number
|
||||
username?: string
|
||||
first_name?: string
|
||||
last_name?: string
|
||||
email?: string
|
||||
is_authenticated: boolean
|
||||
is_staff: boolean
|
||||
is_superuser: boolean
|
||||
cas_groups?: string[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user