Skip to content
Open
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
1,489 changes: 1,489 additions & 0 deletions QUIZ_REDESIGN_PROMPT_GUIDE (1).md

Large diffs are not rendered by default.

1,158 changes: 98 additions & 1,060 deletions QUIZ_REDESIGN_PROMPT_GUIDE.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.intelliquiz.api.auth.internal.application.services.AccessResolutionService;
import com.intelliquiz.api.auth.internal.presentation.dto.request.AccessCodeRequest;
import com.intelliquiz.api.auth.internal.presentation.dto.request.PublicJoinRequest;
import com.intelliquiz.api.auth.internal.presentation.dto.request.UpdateTeamNameRequest;
import com.intelliquiz.api.auth.internal.presentation.dto.response.AccessResolutionResponse;
import com.intelliquiz.api.quiz.QuizFacade;
import com.intelliquiz.api.quiz.dto.QuizInfoDto;
Expand Down Expand Up @@ -235,4 +236,26 @@ public ResponseEntity<AccessResolutionResponse> joinPublicQuiz(

return ResponseEntity.ok(response);
}

/**
* Updates a team's name (used for setting avatars).
* Requires the team's access code for verification.
*/
@PutMapping("/teams/{teamId}/name")
@Operation(
summary = "Update team name",
description = "Updates a team's name. Requires the team's access code for verification. Useful for setting avatars in names."
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Name updated successfully"),
@ApiResponse(responseCode = "400", description = "Invalid request or access code"),
@ApiResponse(responseCode = "404", description = "Team not found")
})
public ResponseEntity<Void> updateTeamName(
@PathVariable Long teamId,
@Valid @RequestBody UpdateTeamNameRequest request) {

teamFacade.updateTeamNameWithAccessCode(teamId, request.name(), request.accessCode());
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.intelliquiz.api.auth.internal.presentation.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

/**
* Request DTO for updating a team's name with access code verification.
*/
@Schema(description = "Request body for updating team name")
public record UpdateTeamNameRequest(
@Schema(description = "New name for the team (can include avatar encoding)", example = "Team Alpha|avatar-1.png", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "Name is required")
@Size(max = 100, message = "Name must not exceed 100 characters")
String name,

@Schema(description = "Access code of the team for verification", example = "ABC123", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "Access code is required")
String accessCode
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,24 @@ private Object buildGameStatePayload(Long quizId, GameState gameState, Integer q
? QuestionPayload.fromDto(orderedQuestions.get(safeQuestionIndex))
: null;

// Calculate current rankings for the initial snapshot
List<com.intelliquiz.api.team.dto.TeamInfoDto> leaderboard = teamFacade.getTeamsByQuiz(quizId).stream()
.sorted(Comparator.comparingInt(com.intelliquiz.api.team.dto.TeamInfoDto::totalScore).reversed())
.toList();

List<Object> rankings = new ArrayList<>();
int rank = 1;
for (com.intelliquiz.api.team.dto.TeamInfoDto team : leaderboard) {
final int itemRank = rank++;
rankings.add(new Object() {
public final Long teamId = team.id();
public final String teamName = team.name();
public final Integer totalScore = team.totalScore();
public final Integer score = team.totalScore();
public final Integer rank = itemRank;
});
}

return new Object() {
public final String state = serializedState;
public final String gameState = serializedState;
Expand All @@ -296,6 +314,8 @@ private Object buildGameStatePayload(Long quizId, GameState gameState, Integer q
public final QuestionPayload currentQuestion = currentQuestionPayload;
public final Boolean participantNavigationEnabled = quizSessionManager.isParticipantNavigationEnabled(quizId);
public final Boolean timerActive = timerService.isTimerActive(quizId);
public final java.util.List<Object> teamResults = rankings;
public final java.util.List<Object> rankingsData = rankings;
public final java.time.LocalDateTime timestamp = java.time.LocalDateTime.now();
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ public void bindDeviceId(Long teamId, String deviceId) {
});
}

/**
* Updates a team's name if the provided access code matches.
*/
public void updateTeamNameWithAccessCode(Long teamId, String newName, String accessCode) {
teamRegistrationService.updateTeamNameWithAccessCode(teamId, newName, accessCode);
}

private TeamInfoDto toDto(Team team) {
return new TeamInfoDto(
team.getId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,24 @@ public Team updateTeamName(Long teamId, String newName) {
return teamRepository.save(team);
}

/**
* Updates a team's name if the provided access code matches.
* Used by participants to update their profile (e.g. adding an avatar).
*/
public Team updateTeamNameWithAccessCode(Long teamId, String newName, String accessCode) {
Team team = teamRepository.findById(teamId)
.orElseThrow(() -> new EntityNotFoundException("Team", teamId));

if (!team.getAccessCode().equals(accessCode)) {
throw new IllegalArgumentException("Invalid access code");
}

assertQuizNotArchived(team.getQuizId());

team.setName(newName);
return teamRepository.save(team);
}

private void assertQuizNotArchived(Long quizId) {
var quiz = quizFacade.findQuizInfo(quizId)
.orElseThrow(() -> new EntityNotFoundException("Quiz", quizId));
Expand Down
2 changes: 1 addition & 1 deletion backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ spring.jpa.open-in-view=false
spring.jpa.hibernate.ddl-auto=${SPRING_JPA_HIBERNATE_DDL_AUTO:update}
spring.jpa.show-sql=false

server.port=${SERVER_PORT:8082}
server.port=${SERVER_PORT:8090}

# SSL Configuration (disabled - HTTPS handled by frontend/reverse proxy)
server.ssl.enabled=${SSL_ENABLED:false}
Expand Down
18 changes: 18 additions & 0 deletions frontend/intelliquiz-frontend/build.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

> intelliquiz-frontend@0.0.0 build
> tsc -b && vite build

vite v7.3.1 building client environment for production...
transforming...
✓ 1821 modules transformed.
rendering chunks...
computing gzip size...
dist/index.html  0.48 kB │ gzip: 0.31 kB
dist/assets/index-b6b0w2t4.css 230.57 kB │ gzip: 39.17 kB
dist/assets/index-CWcYmhFj.js 596.26 kB │ gzip: 167.28 kB

(!) Some chunks are larger than 500 kB after minification. Consider:
- Using dynamic import() to code-split the application
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
✓ built in 6.12s
6 changes: 5 additions & 1 deletion frontend/intelliquiz-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
"dependencies": {
"@tanstack/react-query": "^5.90.16",
"canvas-confetti": "^1.9.4",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.562.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.1.0"
"react-router-dom": "^7.1.0",
"tailwind-merge": "^3.5.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Place your background images here.
Supported filenames:
- bg-1.jpg
- bg-2.jpg
- bg-3.jpg
- bg-4.jpg
- bg-5.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions frontend/intelliquiz-frontend/public/sounds/.gitkeep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Sound files will be placed here
30 changes: 13 additions & 17 deletions frontend/intelliquiz-frontend/src/components/admin/AdminLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useState, useRef, useEffect } from 'react';
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
import {
BiHomeAlt,
BiBookOpen,
BiLogOut,
BiChevronDown,
} from 'react-icons/bi';
Home,
BookOpen,
LogOut,
ChevronDown,
} from 'lucide-react';
import '../../styles/admin.css';
import { authApi, currentUserApi } from '../../services/api';
import { useAuth } from '../../contexts/AuthContext';
Expand All @@ -28,8 +28,8 @@ export default function AdminLayout() {

// Build nav items based on user's permissions
const navItems: NavItem[] = [
{ path: '/admin', label: 'Dashboard', icon: <BiHomeAlt size={18} /> },
{ path: '/admin/quizzes', label: 'My Quizzes', icon: <BiBookOpen size={18} /> },
{ path: '/admin', label: 'Dashboard', icon: <Home size={18} /> },
{ path: '/admin/quizzes', label: 'My Quizzes', icon: <BookOpen size={18} /> },
];

useEffect(() => {
Expand Down Expand Up @@ -111,12 +111,8 @@ export default function AdminLayout() {
};

return (
<div style={{ minHeight: '100vh', background: '#fffaf2', fontFamily: "'Nunito', sans-serif" }}>
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;500;600;700;800;900&display=swap');
`}</style>

{/* Top Navigation - Same as SuperAdmin */}
<div style={{ minHeight: '100vh', background: '#ffffff', fontFamily: 'var(--font-body)' }}>
{/* Top Navigation */}
<header className="pb-nav">
<div className="pb-nav-inner">
{/* Logo */}
Expand All @@ -125,7 +121,7 @@ export default function AdminLayout() {
className="pb-nav-logo-wrap"
>
<div className="pb-nav-logo-icon">
<BiBookOpen size={22} />
<BookOpen size={20} />
</div>
<span className="pb-nav-logo-text">IntelliQuiz</span>
</div>
Expand Down Expand Up @@ -159,7 +155,7 @@ export default function AdminLayout() {
<div className="pb-nav-profile-name">{username || 'Admin'}</div>
<div className="pb-nav-profile-role">Admin</div>
</div>
<BiChevronDown size={18} className={`pb-nav-chevron ${profileDropdownOpen ? 'open' : ''}`} />
<ChevronDown size={18} className={`pb-nav-chevron ${profileDropdownOpen ? 'open' : ''}`} />
</button>

{profileDropdownOpen && (
Expand All @@ -168,7 +164,7 @@ export default function AdminLayout() {
onClick={handleLogout}
className="pb-nav-dropdown-item"
>
<BiLogOut size={18} />
<LogOut size={18} />
Logout
</button>
</div>
Expand All @@ -179,7 +175,7 @@ export default function AdminLayout() {
</header>

{/* Main Content */}
<main style={{ minHeight: 'calc(100vh - 64px)' }}>
<main style={{ minHeight: 'calc(100vh - 64px)', background: '#ffffff' }}>
<div style={{ maxWidth: 1400, margin: '0 auto', padding: 24 }}>
<Outlet />
</div>
Expand Down
Loading