Skip to content

Latest commit

 

History

History
421 lines (364 loc) · 15.9 KB

File metadata and controls

421 lines (364 loc) · 15.9 KB
You are an expert Vue 3 frontend developer and UI/UX designer. Your task is to build the complete DevQuiz frontend — a developer quiz platform with a polished, trustworthy, and addictive interface.

## PROJECT CONTEXT

Backend is already deployed at: https://devquiz.fastapicloud.dev/
GraphQL endpoint: https://devquiz.fastapicloud.dev/graphql
Health check: https://devquiz.fastapicloud.dev/health

Read all documentation files in the docs/ folder before writing any code.
The docs describe all available GraphQL queries and mutations — use every single one.

## TECH STACK
- Vue 3 (Composition API, <script setup>)
- Vue Router 4
- Pinia (state management)
- Apollo Client (@apollo/client + @vue/apollo-composable)
- Tailwind CSS v3
- Vite
- Project is already initialized inside app/ folder

## DESIGN PHILOSOPHY — MOST IMPORTANT

The UI must feel: calm, trustworthy, focused, slightly gamified — like a tool a senior developer would actually use and enjoy. NOT a rainbow toy, NOT a generic SaaS template.

### Color Palette (use EXACTLY these):
```css
:root {
  --bg-primary:     #0f1117;   /* deep dark background */
  --bg-secondary:   #1a1d27;   /* card background */
  --bg-tertiary:    #242736;   /* hover states, input bg */
  --border:         #2e3147;   /* subtle borders */
  --accent:         #6c63ff;   /* primary purple — buttons, active states */
  --accent-hover:   #5a52d5;   /* darker purple on hover */
  --accent-glow:    rgba(108, 99, 255, 0.15); /* soft glow for cards */
  --success:        #22c55e;   /* correct answer */
  --error:          #ef4444;   /* wrong answer */
  --warning:        #f59e0b;   /* streak, hints */
  --text-primary:   #e8eaf0;   /* main text */
  --text-secondary: #8b90a7;   /* muted text */
  --text-muted:     #4a4f6a;   /* very muted */
  --xp-color:       #f59e0b;   /* XP gold */
  --badge-color:    #a78bfa;   /* badge purple-light */
}

Typography:

  • Font: Inter (Google Fonts)
  • Code snippets in questions: JetBrains Mono
  • Headings: font-weight 700, slightly tight letter-spacing
  • Body: font-weight 400, line-height 1.6

Design Rules:

  • Dark theme ONLY — no light mode toggle
  • Rounded corners: 12px for cards, 8px for buttons, 6px for inputs
  • Subtle shadows: box-shadow with accent-glow on hover
  • Smooth transitions: 200ms ease on all interactive elements
  • NO gradients except: subtle radial on hero section and XP bar
  • NO heavy animations — only purposeful micro-interactions
  • Cards lift slightly on hover (translateY(-2px))
  • Active states use accent border + glow, not background fill

Spacing system: use Tailwind spacing scale consistently

  • Page padding: px-6 on mobile, px-12 on desktop
  • Card padding: p-6
  • Section gaps: gap-6 or gap-8
  • Never cramped, never wasteful

PROJECT STRUCTURE

app/
├── src/
│   ├── main.js
│   ├── App.vue
│   ├── apollo.js           # Apollo Client setup with auth headers
│   ├── router/
│   │   └── index.js        # All routes with auth guards
│   ├── stores/
│   │   ├── auth.js         # Pinia: user, token, login/logout/register
│   │   └── quiz.js         # Pinia: current quiz state, answers, timer
│   ├── graphql/
│   │   ├── auth.js         # REGISTER, LOGIN mutations
│   │   ├── topics.js       # TOPICS, TOPIC queries
│   │   ├── quiz.js         # QUIZ query, SUBMIT_QUIZ mutation
│   │   ├── user.js         # ME, MY_STATS, MY_BADGES, MY_HISTORY
│   │   ├── leaderboard.js  # LEADERBOARD, TOPIC_LEADERBOARD
│   │   ├── challenge.js    # CREATE_CHALLENGE, SUBMIT_CHALLENGE, CHALLENGE_LEADERBOARD
│   │   └── profile.js      # PUBLIC_PROFILE query
│   ├── views/
│   │   ├── HomeView.vue
│   │   ├── LoginView.vue
│   │   ├── RegisterView.vue
│   │   ├── DashboardView.vue
│   │   ├── TopicsView.vue
│   │   ├── QuizView.vue
│   │   ├── QuizResultView.vue
│   │   ├── LeaderboardView.vue
│   │   ├── ProfileView.vue
│   │   ├── PublicProfileView.vue
│   │   └── ChallengeView.vue
│   └── components/
│       ├── layout/
│       │   ├── AppNav.vue
│       │   └── AppFooter.vue
│       ├── ui/
│       │   ├── AppButton.vue
│       │   ├── AppCard.vue
│       │   ├── AppBadge.vue
│       │   ├── AppInput.vue
│       │   ├── LoadingSpinner.vue
│       │   └── EmptyState.vue
│       ├── quiz/
│       │   ├── QuestionCard.vue
│       │   ├── ChoiceOption.vue
│       │   ├── ProgressBar.vue
│       │   ├── QuizTimer.vue
│       │   └── ResultCard.vue
│       ├── gamification/
│       │   ├── XPBar.vue
│       │   ├── BadgeCard.vue
│       │   ├── StreakBadge.vue
│       │   └── LevelBadge.vue
│       └── leaderboard/
│           ├── LeaderboardTable.vue
│           └── LeaderboardRow.vue

PAGES — DETAILED REQUIREMENTS

HomeView.vue (public)

  • Hero section: large heading "Test Your Dev Skills", subtitle, two CTA buttons (Start Quiz → /topics, Sign Up → /register)
  • Subtle radial glow behind hero text using --accent-glow
  • Three feature cards below: "Track Progress", "Compete", "Earn Badges" with icons
  • If user is logged in: redirect to /dashboard

LoginView.vue / RegisterView.vue (public)

  • Centered card, max-width 420px
  • Logo/title at top
  • Form with AppInput components
  • Error messages inline under fields
  • Link to opposite page (Login ↔ Register)
  • On success: redirect to /dashboard
  • Show loading state on button during request

DashboardView.vue (protected)

Layout: two columns on desktop, single on mobile

Left column:

  • Welcome message with username
  • UserStats card: XP bar with level name, current XP / next level XP, streak flame 🔥 with count, total quizzes, total correct
  • Recent badges (last 3 earned) with BadgeCard
  • "View all badges" link

Right column:

  • "Quick Start" — list of topics as clickable cards, each shows topic name and description
  • "Your History" — last 5 QuizResults: topic name, score/total, percentage, date
  • "Active Challenges" section with join by code input

TopicsView.vue (public)

  • Grid of topic cards (2 cols mobile, 3 cols desktop)
  • Each card: topic name, description, "Start Quiz" button
  • Clicking card → /quiz/:topicId
  • Filter by level (easy/medium/hard) — tabs above grid

QuizView.vue (protected)

This is the CORE experience — make it exceptional:

  • Full page focus mode — hide nav during quiz
  • Top bar: topic name, progress (3/10), level badge
  • Progress bar animates smoothly between questions
  • Question displayed in large readable text
  • If question text contains backticks → render as code block with JetBrains Mono
  • 4 choices as large clickable cards
    • Default: --bg-secondary border --border
    • Hover: border --accent, slight glow
    • Selected: border --accent, bg --accent-glow
    • After submit: correct → border --success bg green/10, wrong → border --error bg red/10
  • "Next Question" button appears after selection
  • Last question: "See Results" button
  • Hint button (💡) — calls getHint query, shows hint in a subtle tooltip/panel below question. Only once per quiz session.
  • Store all answers in quiz Pinia store
  • On finish: navigate to /quiz/result with full answers payload

QuizResultView.vue (protected)

  • Score header: big number "7/10", percentage, level badge
  • XP earned this quiz: animated counter +80 XP
  • New badges earned (if any): show with animation — slide in from bottom
  • Streak info: "🔥 3 day streak!"
  • Per-question breakdown:
    • ✅ or ❌ icon
    • Question text
    • Your answer (green if correct, red if wrong)
    • Correct answer (always shown in green)
    • Explanation in muted text
  • Two buttons: "Try Again" (same topic) / "Back to Topics"
  • "Challenge a friend" button → opens create challenge modal

LeaderboardView.vue (public)

  • Two tabs: Global / By Topic
  • Global tab: LeaderboardTable with rank, username, level badge, XP, total correct
  • By Topic tab: topic selector dropdown, then TopicLeaderboardTable
  • Top 3 rows highlighted: gold/silver/bronze subtle left border
  • Current user row highlighted with --accent border

ProfileView.vue (protected — own profile)

  • Avatar placeholder (initials in colored circle based on username)
  • Username, level badge, member since date
  • Stats grid: XP, streak, longest streak, total quizzes, total correct
  • XP bar showing progress to next level
  • All badges grid — earned ones full color, locked ones grayscale with lock icon
  • Quiz history table: topic, score, percentage, date
  • "Create Challenge" button

PublicProfileView.vue (public — /profile/:username)

  • Same as ProfileView but read-only, no history table
  • Shows: username, level, stats, earned badges only

ChallengeView.vue (protected)

  • Two sections:
    1. "Create Challenge" — select topic, level, number of questions → createChallenge mutation → show generated code in big copyable box
    2. "Join Challenge" — enter code → fetch challengeLeaderboard to preview → start quiz → submitChallenge

COMPONENTS — DETAILED REQUIREMENTS

AppNav.vue

  • Fixed top, height 64px, backdrop-blur with semi-transparent bg
  • Logo left: "DevQuiz" with subtle accent color on "Quiz"
  • Nav links: Topics, Leaderboard (always visible)
  • If logged in: Dashboard, Profile, logout button
  • If logged out: Login, Register buttons
  • Mobile: hamburger menu

XPBar.vue

Props: currentXp, level, nextLevelXp

  • Label: "Level: Mid • 540 XP"
  • Progress bar with gradient fill (--accent to --badge-color)
  • Animated on mount using CSS transition
  • Show percentage of current level completion

BadgeCard.vue

Props: badge (name, icon, description, earnedAt), locked (bool)

  • Square card with icon large centered
  • Badge name below
  • Locked state: grayscale filter + lock emoji overlay
  • Tooltip on hover: description + earned date

QuestionCard.vue

Props: question, questionNumber, total

  • Renders question.text — detect backtick code blocks and render with styled pre/code
  • Shows "Question N of Total" subtitle
  • Smooth fade-in animation on mount

ChoiceOption.vue

Props: choice, selected, revealed, isCorrect

  • Large button-like card, full width
  • States: default / hover / selected / correct / wrong
  • Smooth color transition between states
  • Letter prefix: A, B, C, D in small badge left side

ProgressBar.vue

Props: current, total

  • Thin bar (4px height) at top of quiz
  • Smooth width transition
  • Color: --accent

StreakBadge.vue

Props: streak

  • Shows 🔥 + number
  • If streak >= 7: add subtle pulsing glow animation
  • If streak == 0: show in --text-muted

LevelBadge.vue

Props: level

  • Small pill badge
  • Colors per level:
    • Beginner: gray
    • Junior: blue
    • Mid: green
    • Senior: purple (--accent)
    • Expert: gold (--xp-color)

APOLLO CLIENT SETUP

// apollo.js
import { ApolloClient, InMemoryCache, createHttpLink, from } from '@apollo/client/core'
import { setContext } from '@apollo/client/link/context'
import { onError } from '@apollo/client/link/error'

const httpLink = createHttpLink({
  uri: 'https://devquiz.fastapicloud.dev/graphql',
})

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem('token')
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : '',
    }
  }
})

const errorLink = onError(({ graphQLErrors }) => {
  if (graphQLErrors?.some(e => e.message.includes('Authentication required'))) {
    localStorage.removeItem('token')
    window.location.href = '/login'
  }
})

export const apolloClient = new ApolloClient({
  link: from([errorLink, authLink, httpLink]),
  cache: new InMemoryCache(),
})

PINIA STORES

auth.js store

state: {
  user: null,        // {id, email, username}
  token: null,       // JWT string
  stats: null,       // UserStats
  badges: [],
}
actions:
  - login(email, password)  mutation  save token to localStorage + store
  - register(email, username, password)  mutation  auto-login after
  - logout()  clear store + localStorage  redirect /
  - fetchMe()  query me + myStats + myBadges  populate store
  - initFromStorage()  on app mount, restore token, call fetchMe

quiz.js store

state: {
  questions: [],      // current quiz questions
  answers: {},        // { questionId: choiceId }
  topicId: null,
  level: null,
  hintUsed: false,
  result: null,       // QuizSubmitResultType after submit
}
actions:
  - startQuiz(topicId, level, questions)
  - selectAnswer(questionId, choiceId)
  - useHint()
  - submitQuiz()  mutation  save result  return result
  - reset()

ROUTING

const routes = [
  { path: '/',                    component: HomeView },
  { path: '/login',               component: LoginView },
  { path: '/register',            component: RegisterView },
  { path: '/dashboard',           component: DashboardView,      meta: { requiresAuth: true } },
  { path: '/topics',              component: TopicsView },
  { path: '/quiz/:topicId',       component: QuizView,           meta: { requiresAuth: true } },
  { path: '/quiz/result',         component: QuizResultView,     meta: { requiresAuth: true } },
  { path: '/leaderboard',         component: LeaderboardView },
  { path: '/profile',             component: ProfileView,        meta: { requiresAuth: true } },
  { path: '/profile/:username',   component: PublicProfileView },
  { path: '/challenge',           component: ChallengeView,      meta: { requiresAuth: true } },
]

// Navigation guard: if requiresAuth and no token → redirect /login
// If logged in and visiting /login or /register → redirect /dashboard

GRAPHQL DOCUMENTS

Write all queries and mutations as gql`` tagged template literals. Every query/mutation must match exactly what the backend accepts. Group them by feature in separate files under src/graphql/.

Include ALL fields that exist in the backend types:

  • UserStats: xp, level, streak, longestStreak, totalQuizzes, totalCorrect
  • QuizResult: score, total, percentage, results { questionText, yourChoice, correctChoice, isCorrect, explanation }
  • Badge: name, icon, description, earnedAt
  • LeaderboardEntry: rank, username, xp, level, totalCorrect
  • ChallengeType: id, code, topicId, topicName, level, limit, createdAt, creatorUsername

UX DETAILS — DO NOT SKIP

  1. Loading states: every async operation shows LoadingSpinner, never blank page
  2. Error states: inline error messages, never silent failures
  3. Empty states: EmptyState component with icon + message (e.g. "No quizzes yet — start one!")
  4. Page transitions: simple fade (opacity 0→1, 150ms) on route change
  5. Quiz answers: once selected, cannot change (disabled state)
  6. Quiz navigation: no back button during quiz — forward only
  7. Result page: scroll to top on mount
  8. Token expiry: errorLink catches auth errors → logout → redirect login
  9. Responsive: every page works on 375px mobile and 1440px desktop
  10. No page requires refresh to update — reactivity via Pinia + Apollo

DEPENDENCIES TO INSTALL

npm install @apollo/client @vue/apollo-composable graphql
npm install pinia
npm install vue-router@4
npm install @tailwindcss/typography
npm install -D tailwindcss postcss autoprefixer

OUTPUT FORMAT

  • Write every file completely — no TODOs, no placeholders
  • Start with: package.json → vite.config.js → tailwind.config.js → src/main.js → apollo.js → router → stores → graphql documents → components (ui first, then quiz, then gamification) → views
  • Each component must be self-contained and immediately usable
  • Use <script setup> syntax everywhere
  • All styles via Tailwind utility classes + CSS variables in style blocks where needed

---

Копируй в Sonnet 4.8. После генерации скинь если будут ошибки — разберём вместе.