From 44c340ef9fbd2cc3bf595b80435b109ef62d1ec3 Mon Sep 17 00:00:00 2001 From: Villagers654 <110007851+Villagers654@users.noreply.github.com> Date: Fri, 20 Feb 2026 03:51:11 -0500 Subject: [PATCH 001/261] Very initial modjam work --- .../config/security/SecurityConfig.java | 7 +- .../resources/ModjamController.java | 75 ++++++ .../java/net/modtale/model/resources/Mod.java | 5 + .../net/modtale/model/resources/Modjam.java | 107 ++++++++ .../model/resources/ModjamSubmission.java | 95 ++++++++ .../java/net/modtale/model/user/User.java | 5 + .../resources/ModjamRepository.java | 14 ++ .../resources/ModjamSubmissionRepository.java | 14 ++ .../service/resources/ModjamService.java | 146 +++++++++++ frontend/src/App.tsx | 6 + frontend/src/components/Navbar.tsx | 22 +- frontend/src/pages/[...all].astro | 9 + .../src/react-pages/jams/CreateJamModal.tsx | 230 ++++++++++++++++++ frontend/src/react-pages/jams/JamDetail.tsx | 215 ++++++++++++++++ frontend/src/react-pages/jams/JamsList.tsx | 117 +++++++++ frontend/src/types.ts | 48 ++++ 16 files changed, 1102 insertions(+), 13 deletions(-) create mode 100644 backend/src/main/java/net/modtale/controller/resources/ModjamController.java create mode 100644 backend/src/main/java/net/modtale/model/resources/Modjam.java create mode 100644 backend/src/main/java/net/modtale/model/resources/ModjamSubmission.java create mode 100644 backend/src/main/java/net/modtale/repository/resources/ModjamRepository.java create mode 100644 backend/src/main/java/net/modtale/repository/resources/ModjamSubmissionRepository.java create mode 100644 backend/src/main/java/net/modtale/service/resources/ModjamService.java create mode 100644 frontend/src/react-pages/jams/CreateJamModal.tsx create mode 100644 frontend/src/react-pages/jams/JamDetail.tsx create mode 100644 frontend/src/react-pages/jams/JamsList.tsx diff --git a/backend/src/main/java/net/modtale/config/security/SecurityConfig.java b/backend/src/main/java/net/modtale/config/security/SecurityConfig.java index 1bb874eb..9a649015 100644 --- a/backend/src/main/java/net/modtale/config/security/SecurityConfig.java +++ b/backend/src/main/java/net/modtale/config/security/SecurityConfig.java @@ -219,6 +219,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/client-metadata.json").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/projects/**", + "/api/v1/modjams/**", "/api/v1/tags", "/api/v1/files/**", "/api/v1/user/profile/**", @@ -282,9 +283,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/api/v1/auth/change-password", "/api/v1/auth/credentials" ).authenticated() - .requestMatchers(HttpMethod.POST, "/api/v1/projects/**").authenticated() - .requestMatchers(HttpMethod.PUT, "/api/v1/projects/**").authenticated() - .requestMatchers(HttpMethod.DELETE, "/api/v1/projects/**").authenticated() + .requestMatchers(HttpMethod.POST, "/api/v1/projects/**", "/api/v1/modjams/**").authenticated() + .requestMatchers(HttpMethod.PUT, "/api/v1/projects/**", "/api/v1/modjams/**").authenticated() + .requestMatchers(HttpMethod.DELETE, "/api/v1/projects/**", "/api/v1/modjams/**").authenticated() .requestMatchers("/api/**").authenticated() .anyRequest().permitAll() ) diff --git a/backend/src/main/java/net/modtale/controller/resources/ModjamController.java b/backend/src/main/java/net/modtale/controller/resources/ModjamController.java new file mode 100644 index 00000000..61aeda9f --- /dev/null +++ b/backend/src/main/java/net/modtale/controller/resources/ModjamController.java @@ -0,0 +1,75 @@ +package net.modtale.controller.resources; + +import net.modtale.model.resources.Modjam; +import net.modtale.model.resources.ModjamSubmission; +import net.modtale.model.user.User; +import net.modtale.service.resources.ModjamService; +import net.modtale.service.user.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/modjams") +public class ModjamController { + + @Autowired private ModjamService modjamService; + @Autowired private UserService userService; + + private User getAuthenticatedUser(Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) return null; + return userService.getPublicProfile(authentication.getName()); + } + + @GetMapping + public ResponseEntity> getAllJams() { + return ResponseEntity.ok(modjamService.getAllJams()); + } + + @GetMapping("/{slug}") + public ResponseEntity getJamBySlug(@PathVariable String slug) { + return ResponseEntity.ok(modjamService.getJamBySlug(slug)); + } + + @PostMapping + public ResponseEntity createJam(@RequestBody Modjam jam, Authentication auth) { + User user = getAuthenticatedUser(auth); + if (user == null) return ResponseEntity.status(401).build(); + return ResponseEntity.ok(modjamService.createJam(jam, user.getId(), user.getUsername())); + } + + @GetMapping("/{jamId}/submissions") + public ResponseEntity> getSubmissions(@PathVariable String jamId) { + return ResponseEntity.ok(modjamService.getSubmissions(jamId)); + } + + @PostMapping("/{jamId}/participate") + public ResponseEntity participate(@PathVariable String jamId, Authentication auth) { + User user = getAuthenticatedUser(auth); + if (user == null) return ResponseEntity.status(401).build(); + return ResponseEntity.ok(modjamService.participate(jamId, user.getId())); + } + + @PostMapping("/{jamId}/submit") + public ResponseEntity submitProject(@PathVariable String jamId, @RequestBody Map body, Authentication auth) { + User user = getAuthenticatedUser(auth); + if (user == null) return ResponseEntity.status(401).build(); + return ResponseEntity.ok(modjamService.submitProject(jamId, body.get("projectId"), user.getId())); + } + + @PostMapping("/{jamId}/vote") + public ResponseEntity vote(@PathVariable String jamId, @RequestBody Map body, Authentication auth) { + User user = getAuthenticatedUser(auth); + if (user == null) return ResponseEntity.status(401).build(); + + String submissionId = (String) body.get("submissionId"); + String categoryId = (String) body.get("categoryId"); + int score = (Integer) body.get("score"); + + return ResponseEntity.ok(modjamService.vote(jamId, submissionId, categoryId, score, user.getId())); + } +} \ No newline at end of file diff --git a/backend/src/main/java/net/modtale/model/resources/Mod.java b/backend/src/main/java/net/modtale/model/resources/Mod.java index 4b3d3584..e522af69 100644 --- a/backend/src/main/java/net/modtale/model/resources/Mod.java +++ b/backend/src/main/java/net/modtale/model/resources/Mod.java @@ -84,6 +84,8 @@ public class Mod { private List childProjectIds; private List modIds; + private List modjamIds = new ArrayList<>(); + private boolean allowModpacks = true; private boolean allowComments = true; @@ -187,6 +189,9 @@ public Mod() {} public List getModIds() { return modIds; } public void setModIds(List modIds) { this.modIds = modIds; } + public List getModjamIds() { return modjamIds; } + public void setModjamIds(List modjamIds) { this.modjamIds = modjamIds; } + public boolean isAllowModpacks() { return allowModpacks; } public void setAllowModpacks(boolean allowModpacks) { this.allowModpacks = allowModpacks; } diff --git a/backend/src/main/java/net/modtale/model/resources/Modjam.java b/backend/src/main/java/net/modtale/model/resources/Modjam.java new file mode 100644 index 00000000..93522ed3 --- /dev/null +++ b/backend/src/main/java/net/modtale/model/resources/Modjam.java @@ -0,0 +1,107 @@ +package net.modtale.model.resources; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Document(collection = "modjams") +@JsonInclude(JsonInclude.Include.NON_NULL) +public class Modjam { + + @Id + private String id; + + @Indexed(unique = true) + private String slug; + + @Indexed + private String title; + + private String description; + private String bannerUrl; + + @Indexed + private String hostId; + + private String hostName; + + @Indexed + private LocalDateTime startDate; + + @Indexed + private LocalDateTime endDate; + + private LocalDateTime votingEndDate; + + @Indexed + private String status = "DRAFT"; + + private List participantIds = new ArrayList<>(); + + private List categories = new ArrayList<>(); + + private boolean allowPublicVoting = true; + + private LocalDateTime createdAt = LocalDateTime.now(); + + public static class Category { + private String id; + private String name; + private String description; + private int maxScore; + + public Category() {} + public Category(String id, String name, String description, int maxScore) { + this.id = id; + this.name = name; + this.description = description; + this.maxScore = maxScore; + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public int getMaxScore() { return maxScore; } + public void setMaxScore(int maxScore) { this.maxScore = maxScore; } + } + + public Modjam() {} + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getSlug() { return slug; } + public void setSlug(String slug) { this.slug = slug; } + public String getTitle() { return title; } + public void setTitle(String title) { this.title = title; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public String getBannerUrl() { return bannerUrl; } + public void setBannerUrl(String bannerUrl) { this.bannerUrl = bannerUrl; } + public String getHostId() { return hostId; } + public void setHostId(String hostId) { this.hostId = hostId; } + public String getHostName() { return hostName; } + public void setHostName(String hostName) { this.hostName = hostName; } + public LocalDateTime getStartDate() { return startDate; } + public void setStartDate(LocalDateTime startDate) { this.startDate = startDate; } + public LocalDateTime getEndDate() { return endDate; } + public void setEndDate(LocalDateTime endDate) { this.endDate = endDate; } + public LocalDateTime getVotingEndDate() { return votingEndDate; } + public void setVotingEndDate(LocalDateTime votingEndDate) { this.votingEndDate = votingEndDate; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + public List getParticipantIds() { return participantIds; } + public void setParticipantIds(List participantIds) { this.participantIds = participantIds; } + public List getCategories() { return categories; } + public void setCategories(List categories) { this.categories = categories; } + public boolean isAllowPublicVoting() { return allowPublicVoting; } + public void setAllowPublicVoting(boolean allowPublicVoting) { this.allowPublicVoting = allowPublicVoting; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } +} \ No newline at end of file diff --git a/backend/src/main/java/net/modtale/model/resources/ModjamSubmission.java b/backend/src/main/java/net/modtale/model/resources/ModjamSubmission.java new file mode 100644 index 00000000..3c1516c7 --- /dev/null +++ b/backend/src/main/java/net/modtale/model/resources/ModjamSubmission.java @@ -0,0 +1,95 @@ +package net.modtale.model.resources; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.CompoundIndex; +import org.springframework.data.mongodb.core.index.CompoundIndexes; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Document(collection = "modjam_submissions") +@JsonInclude(JsonInclude.Include.NON_NULL) +@CompoundIndexes({ + @CompoundIndex(name = "jam_rank_idx", def = "{'jamId': 1, 'rank': 1}") +}) +public class ModjamSubmission { + + @Id + private String id; + + @Indexed + private String jamId; + + @Indexed + private String projectId; + + private String projectTitle; + private String projectImageUrl; + + @Indexed + private String submitterId; + + private List votes = new ArrayList<>(); + + private Map categoryScores = new HashMap<>(); + + private Double totalScore; + + private Integer rank; + + private LocalDateTime createdAt = LocalDateTime.now(); + + public static class Vote { + private String id; + private String voterId; + private String categoryId; + private int score; + + public Vote() {} + public Vote(String id, String voterId, String categoryId, int score) { + this.id = id; + this.voterId = voterId; + this.categoryId = categoryId; + this.score = score; + } + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getVoterId() { return voterId; } + public void setVoterId(String voterId) { this.voterId = voterId; } + public String getCategoryId() { return categoryId; } + public void setCategoryId(String categoryId) { this.categoryId = categoryId; } + public int getScore() { return score; } + public void setScore(int score) { this.score = score; } + } + + public ModjamSubmission() {} + + public String getId() { return id; } + public void setId(String id) { this.id = id; } + public String getJamId() { return jamId; } + public void setJamId(String jamId) { this.jamId = jamId; } + public String getProjectId() { return projectId; } + public void setProjectId(String projectId) { this.projectId = projectId; } + public String getProjectTitle() { return projectTitle; } + public void setProjectTitle(String projectTitle) { this.projectTitle = projectTitle; } + public String getProjectImageUrl() { return projectImageUrl; } + public void setProjectImageUrl(String projectImageUrl) { this.projectImageUrl = projectImageUrl; } + public String getSubmitterId() { return submitterId; } + public void setSubmitterId(String submitterId) { this.submitterId = submitterId; } + public List getVotes() { return votes; } + public void setVotes(List votes) { this.votes = votes; } + public Map getCategoryScores() { return categoryScores; } + public void setCategoryScores(Map categoryScores) { this.categoryScores = categoryScores; } + public Double getTotalScore() { return totalScore; } + public void setTotalScore(Double totalScore) { this.totalScore = totalScore; } + public Integer getRank() { return rank; } + public void setRank(Integer rank) { this.rank = rank; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } +} \ No newline at end of file diff --git a/backend/src/main/java/net/modtale/model/user/User.java b/backend/src/main/java/net/modtale/model/user/User.java index 59e50d2e..0cab9583 100644 --- a/backend/src/main/java/net/modtale/model/user/User.java +++ b/backend/src/main/java/net/modtale/model/user/User.java @@ -65,6 +65,8 @@ public class User implements Serializable { private List followingIds = new ArrayList<>(); private List followerIds = new ArrayList<>(); + private List joinedModjamIds = new ArrayList<>(); + private List connectedAccounts = new ArrayList<>(); private List badges = new ArrayList<>(); @@ -236,6 +238,9 @@ public ConnectedAccount(String provider, String providerId, String username, Str public List getFollowerIds() { return followerIds; } public void setFollowerIds(List followerIds) { this.followerIds = followerIds; } + public List getJoinedModjamIds() { return joinedModjamIds; } + public void setJoinedModjamIds(List joinedModjamIds) { this.joinedModjamIds = joinedModjamIds; } + public List getConnectedAccounts() { return connectedAccounts; } public void setConnectedAccounts(List connectedAccounts) { this.connectedAccounts = connectedAccounts; } diff --git a/backend/src/main/java/net/modtale/repository/resources/ModjamRepository.java b/backend/src/main/java/net/modtale/repository/resources/ModjamRepository.java new file mode 100644 index 00000000..18184fdc --- /dev/null +++ b/backend/src/main/java/net/modtale/repository/resources/ModjamRepository.java @@ -0,0 +1,14 @@ +package net.modtale.repository.resources; + +import net.modtale.model.resources.Modjam; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface ModjamRepository extends MongoRepository { + Optional findBySlug(String slug); + List findByStatusIn(List statuses); +} \ No newline at end of file diff --git a/backend/src/main/java/net/modtale/repository/resources/ModjamSubmissionRepository.java b/backend/src/main/java/net/modtale/repository/resources/ModjamSubmissionRepository.java new file mode 100644 index 00000000..918dca45 --- /dev/null +++ b/backend/src/main/java/net/modtale/repository/resources/ModjamSubmissionRepository.java @@ -0,0 +1,14 @@ +package net.modtale.repository.resources; + +import net.modtale.model.resources.ModjamSubmission; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ModjamSubmissionRepository extends MongoRepository { + List findByJamId(String jamId); + List findByJamIdAndSubmitterId(String jamId, String submitterId); + List findByProjectId(String projectId); +} \ No newline at end of file diff --git a/backend/src/main/java/net/modtale/service/resources/ModjamService.java b/backend/src/main/java/net/modtale/service/resources/ModjamService.java new file mode 100644 index 00000000..edcb9d83 --- /dev/null +++ b/backend/src/main/java/net/modtale/service/resources/ModjamService.java @@ -0,0 +1,146 @@ +package net.modtale.service.resources; + +import net.modtale.model.resources.Modjam; +import net.modtale.model.resources.ModjamSubmission; +import net.modtale.model.resources.Mod; +import net.modtale.model.user.User; +import net.modtale.repository.resources.ModjamRepository; +import net.modtale.repository.resources.ModjamSubmissionRepository; +import net.modtale.repository.resources.ModRepository; +import net.modtale.repository.user.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; + +@Service +public class ModjamService { + + @Autowired private ModjamRepository modjamRepository; + @Autowired private ModjamSubmissionRepository submissionRepository; + @Autowired private UserRepository userRepository; + @Autowired private ModRepository modRepository; + + public List getAllJams() { + return modjamRepository.findAll(); + } + + public Modjam getJamBySlug(String slug) { + return modjamRepository.findBySlug(slug) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Jam not found")); + } + + public Modjam createJam(Modjam jam, String hostId, String hostName) { + jam.setId(null); + jam.setHostId(hostId); + jam.setHostName(hostName); + + String baseSlug = jam.getTitle().toLowerCase().replaceAll("[^a-z0-9]+", "-"); + jam.setSlug(baseSlug + "-" + UUID.randomUUID().toString().substring(0, 6)); + + if (jam.getStartDate() != null && jam.getStartDate().isAfter(LocalDateTime.now())) { + jam.setStatus("UPCOMING"); + } else { + jam.setStatus("ACTIVE"); + } + + jam.setCreatedAt(LocalDateTime.now()); + + if (jam.getCategories() != null) { + for (Modjam.Category cat : jam.getCategories()) { + if (cat.getId() == null || cat.getId().isEmpty()) { + cat.setId(UUID.randomUUID().toString()); + } + } + } + + return modjamRepository.save(jam); + } + + public List getSubmissions(String jamId) { + return submissionRepository.findByJamId(jamId); + } + + public Modjam participate(String jamId, String userId) { + Modjam jam = modjamRepository.findById(jamId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Jam not found")); + User user = userRepository.findById(userId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found")); + + if (!jam.getParticipantIds().contains(userId)) { + jam.getParticipantIds().add(userId); + modjamRepository.save(jam); + } + + if (!user.getJoinedModjamIds().contains(jamId)) { + user.getJoinedModjamIds().add(jamId); + userRepository.save(user); + } + + return jam; + } + + public ModjamSubmission submitProject(String jamId, String projectId, String userId) { + Modjam jam = modjamRepository.findById(jamId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Jam not found")); + Mod project = modRepository.findById(projectId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Project not found")); + + if (!project.getAuthorId().equals(userId)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "You do not own this project"); + } + + List existing = submissionRepository.findByJamIdAndSubmitterId(jamId, userId); + boolean alreadySubmitted = existing.stream().anyMatch(s -> s.getProjectId().equals(projectId)); + if (alreadySubmitted) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Project already submitted to this jam"); + } + + ModjamSubmission sub = new ModjamSubmission(); + sub.setJamId(jamId); + sub.setProjectId(projectId); + sub.setProjectTitle(project.getTitle()); + sub.setProjectImageUrl(project.getImageUrl()); + sub.setSubmitterId(userId); + + submissionRepository.save(sub); + + if (!project.getModjamIds().contains(jamId)) { + project.getModjamIds().add(jamId); + modRepository.save(project); + } + + return sub; + } + + public ModjamSubmission vote(String jamId, String submissionId, String categoryId, int score, String userId) { + Modjam jam = modjamRepository.findById(jamId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Jam not found")); + + if (jam.getStatus().equals("COMPLETED")) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Voting has concluded for this jam."); + } + + if (!jam.isAllowPublicVoting() && !jam.getHostId().equals(userId)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Only judges are permitted to vote on this jam."); + } + + ModjamSubmission sub = submissionRepository.findById(submissionId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Submission not found")); + + sub.getVotes().removeIf(v -> v.getVoterId().equals(userId) && v.getCategoryId().equals(categoryId)); + + ModjamSubmission.Vote vote = new ModjamSubmission.Vote(); + vote.setId(UUID.randomUUID().toString()); + vote.setVoterId(userId); + vote.setCategoryId(categoryId); + vote.setScore(score); + sub.getVotes().add(vote); + + return submissionRepository.save(sub); + } +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index f1c14fe4..a701428b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -38,6 +38,9 @@ const MfaVerify = lazy(() => import('./react-pages/auth/MfaVerify').then(module const Analytics = lazy(() => import('@/components/dashboard/analytics/Analytics').then(module => ({ default: module.Analytics }))); const NotFound = lazy(() => import('./components/ui/error/NotFound.tsx')); +const JamsList = lazy(() => import('./react-pages/jams/JamsList').then(module => ({ default: module.JamsList }))); +const JamDetail = lazy(() => import('./react-pages/jams/JamDetail').then(module => ({ default: module.JamDetail }))); + const ScrollToTop = () => { const { pathname } = useLocation(); useEffect(() => { @@ -190,6 +193,9 @@ const AppContent: React.FC<{ initialClassification?: Classification }> = ({ init + } /> + } /> + : user ? {}} currentUser={user} /> : diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 55671d13..1963dd86 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -1,12 +1,12 @@ import React, { useState, useRef, useEffect } from 'react'; -import { Menu, X, Upload, Home, LayoutDashboard, User as UserIcon, LogOut, Shield, Users, LogIn, Code2, FileText, ChevronDown, Layout, FileCode, Database, Palette, Save, Layers, LayoutGrid } from 'lucide-react'; +import { Menu, X, Upload, Home, LayoutDashboard, User as UserIcon, LogOut, Shield, Users, LogIn, Code2, FileText, ChevronDown, Layout, FileCode, Database, Palette, Save, Layers, LayoutGrid, Trophy } from 'lucide-react'; import { Link } from 'react-router-dom'; import { NotificationMenu } from './user/NotificationMenu'; import { FollowingModal } from './user/FollowingModal'; import { SignInModal } from './user/SignInModal'; import { AnimatedThemeToggler } from './ui/AnimatedThemeToggler'; import { useMobile } from '../context/MobileContext'; -import type { User } from "@/types.ts"; +import type { User } from "@/types"; interface NavbarProps { user: User | null; @@ -60,8 +60,8 @@ export const Navbar: React.FC = ({ } }, [isMobile]); - const homeLikePages = ['home', 'plugins', 'modpacks', 'worlds', 'art', 'data']; - const detailPrefixes = ['mod/', 'world/', 'modpack/']; + const homeLikePages = ['home', 'plugins', 'modpacks', 'worlds', 'art', 'data', 'jams']; + const detailPrefixes = ['mod/', 'world/', 'modpack/', 'jam/']; const isHomeLayout = homeLikePages.includes(currentPage) || detailPrefixes.some(prefix => currentPage.startsWith(prefix) && currentPage.split('/').length < 2); const widthClass = isHomeLayout @@ -100,7 +100,7 @@ export const Navbar: React.FC = ({ + + +
+ {error && ( +
+ {error} +
+ )} + +
+
+ + setTitle(e.target.value)} + className="w-full bg-slate-50 dark:bg-slate-800 border border-slate-200 dark:border-white/10 rounded-xl px-4 py-3 outline-none focus:ring-2 focus:ring-modtale-accent" + placeholder="e.g., Summer Modding Spree 2026" + /> +
+ +
+ +