diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index e69de29..0000000
diff --git a/api/pkg/group-challenge/auth/auth.go b/api/pkg/group-challenge/auth/auth.go
index f3ce01c..f010edb 100644
--- a/api/pkg/group-challenge/auth/auth.go
+++ b/api/pkg/group-challenge/auth/auth.go
@@ -11,7 +11,7 @@ import (
"golang.org/x/crypto/bcrypt"
)
-// CreateUser creates a user with and hashes the password
+// CreateUser creates a user with username and email and hashes the password
func CreateUser(username, password, email string) *models.User {
userModel := &models.User{
Username: username,
diff --git a/api/pkg/group-challenge/liveparty/live-party-state.go b/api/pkg/group-challenge/liveparty/live-party-state.go
index 99744bc..10431f1 100644
--- a/api/pkg/group-challenge/liveparty/live-party-state.go
+++ b/api/pkg/group-challenge/liveparty/live-party-state.go
@@ -30,7 +30,7 @@ func (liveParty *LiveParty) Previous() {
return
}
- if liveParty.Status.State == LivePartyStateSubmissions {
+ if liveParty.Status.State == LivePartyStateRunning {
liveParty.prevSubmission()
return
}
@@ -68,8 +68,8 @@ func (liveParty *LiveParty) prevReveal() {
}
func (liveParty *LiveParty) Next() {
- if liveParty.Status.State == LivePartyStateWaitingLobby {
- liveParty.Status.State = LivePartyStateSubmissions
+ if liveParty.Status.State == LivePartyStateLobby {
+ liveParty.Status.State = LivePartyStateRunning
}
if liveParty.Status.State == LivePartyStatePreReveal {
@@ -77,7 +77,7 @@ func (liveParty *LiveParty) Next() {
liveParty.Status.State = LivePartyStateReveal
}
- if liveParty.Status.State == LivePartyStateSubmissions {
+ if liveParty.Status.State == LivePartyStateRunning {
liveParty.nextSubmission()
return
}
diff --git a/api/pkg/group-challenge/liveparty/live-party.go b/api/pkg/group-challenge/liveparty/live-party.go
index e0e9e96..87fe48d 100644
--- a/api/pkg/group-challenge/liveparty/live-party.go
+++ b/api/pkg/group-challenge/liveparty/live-party.go
@@ -11,13 +11,13 @@ import (
)
var (
- MaxStars = 6
- LivePartyStateOpen = "open"
- LivePartyStateWaitingLobby = "waitinglobby"
- LivePartyStateSubmissions = "submissions"
- LivePartyStatePreReveal = "prereveal"
- LivePartyStateReveal = "reveal"
- LivePartyStateDone = "done"
+ MaxStars = 6
+ LivePartyStateOpen = "open"
+ LivePartyStateLobby = "lobby"
+ LivePartyStateRunning = "running"
+ LivePartyStatePreReveal = "prereveal"
+ LivePartyStateReveal = "reveal"
+ LivePartyStateDone = "done"
)
type SubmissionStatus struct {
@@ -57,7 +57,7 @@ func createLiveParty(party *models.Party, con *pg.DB, livePartyConfig config.Liv
PartyStartTime: time.Now(),
SubmissionTimeMs: livePartyConfig.DefaultTimePerSubmissionSeconds * 1000,
Participants: 1,
- State: LivePartyStateWaitingLobby,
+ State: LivePartyStateLobby,
Votes: []*models.Vote{},
},
ParticipantsUserIDs: []uuid.UUID{party.UserID},
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 1435e74..9876d7c 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -11,11 +11,13 @@
"@headlessui/react": "2.2.10",
"@tanstack/react-query": "5.104.0",
"date-fns": "4.4.0",
+ "photoswipe": "^5.4.4",
"react": "19.3.0",
"react-date-picker": "12.1.0",
"react-dom": "19.3.0",
"react-hook-form": "7.89.0",
"react-icons": "5.7.0",
+ "react-photoswipe-gallery": "^4.1.2",
"react-router": "8.4.0",
"react-router-dom": "7.18.4",
"react-toastify": "11.1.0",
@@ -347,9 +349,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -367,9 +366,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -387,9 +383,6 @@
"ppc64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -407,9 +400,6 @@
"s390x"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -427,9 +417,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -447,9 +434,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -659,9 +643,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -679,9 +660,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -699,9 +677,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -719,9 +694,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1567,9 +1539,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -1591,9 +1560,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -1615,9 +1581,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -1639,9 +1602,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -1774,6 +1734,15 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/photoswipe": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.4.tgz",
+ "integrity": "sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.12.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -1972,6 +1941,16 @@
"react": "*"
}
},
+ "node_modules/react-photoswipe-gallery": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/react-photoswipe-gallery/-/react-photoswipe-gallery-4.1.2.tgz",
+ "integrity": "sha512-QfohXZVY3pXe0q5/+iH1G6Yx2xr5GsBxALT5S3c9GP1HLCechG4MG3IMXaoujatn/trG5CIlY44fmiuzW1vjjA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "photoswipe": ">= 5.2.2",
+ "react": ">= 16.8.0"
+ }
+ },
"node_modules/react-router": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-8.4.0.tgz",
@@ -2474,9 +2453,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2498,9 +2474,6 @@
"arm64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2522,9 +2495,6 @@
"x64"
],
"dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2546,9 +2516,6 @@
"x64"
],
"dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3459,6 +3426,11 @@
"integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
"dev": true
},
+ "photoswipe": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.4.tgz",
+ "integrity": "sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA=="
+ },
"picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -3557,6 +3529,12 @@
"integrity": "sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==",
"requires": {}
},
+ "react-photoswipe-gallery": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/react-photoswipe-gallery/-/react-photoswipe-gallery-4.1.2.tgz",
+ "integrity": "sha512-QfohXZVY3pXe0q5/+iH1G6Yx2xr5GsBxALT5S3c9GP1HLCechG4MG3IMXaoujatn/trG5CIlY44fmiuzW1vjjA==",
+ "requires": {}
+ },
"react-router": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-8.4.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 36fd83c..b5dfdcd 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,11 +12,13 @@
"@headlessui/react": "2.2.10",
"@tanstack/react-query": "5.104.0",
"date-fns": "4.4.0",
+ "photoswipe": "^5.4.4",
"react": "19.3.0",
"react-date-picker": "12.1.0",
"react-dom": "19.3.0",
"react-hook-form": "7.89.0",
"react-icons": "5.7.0",
+ "react-photoswipe-gallery": "^4.1.2",
"react-router": "8.4.0",
"react-router-dom": "7.18.4",
"react-toastify": "11.1.0",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 9f410ce..ae9e83c 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -66,7 +66,7 @@ function App() {
-
{session ? : }
+
{session ? : }
diff --git a/frontend/src/api/api-models.ts b/frontend/src/api/api-models.ts
index cb6f2ad..d40faf6 100644
--- a/frontend/src/api/api-models.ts
+++ b/frontend/src/api/api-models.ts
@@ -55,7 +55,14 @@ export interface PartySubmissionFormData {
description: string;
}
-export type PartyStatusState = 'open' | 'waitinglobby' | 'submissions' | 'prereveal' | 'reveal' | 'done';
+export enum PartyStatusState {
+ OPEN = 'open',
+ LOBBY = 'lobby',
+ RUNNING = 'running',
+ PRE_REVEAL = 'prereveal',
+ REVEAL = 'reveal',
+ DONE = 'done',
+}
export interface PartyStatusResponse {
current?: {
@@ -72,7 +79,7 @@ export interface PartyStatusResponse {
}
export function isPartyLive(partyStatus?: PartyStatusResponse) {
- return partyStatus?.state !== 'open' && partyStatus?.state !== 'done';
+ return partyStatus?.state !== PartyStatusState.OPEN && partyStatus?.state !== PartyStatusState.DONE;
}
// session
diff --git a/frontend/src/components/ReactionPicker.tsx b/frontend/src/components/ReactionPicker.tsx
index a4695bd..75345d2 100644
--- a/frontend/src/components/ReactionPicker.tsx
+++ b/frontend/src/components/ReactionPicker.tsx
@@ -17,34 +17,36 @@ const ReactionPicker: FunctionComponent<{ onReaction: (reaction: string) => void
);
return (
-
-
setAppState((old) => ({ ...old, reactionPickerOpen: true }))}
- >
- {!appState?.reactionPickerOpen && }
-
- {appState?.reactionPickerOpen && (
-
-
setTextInput(e.target.value)}
- onKeyUp={(event) => {
- if (event.key === 'Enter') {
- onTextSubmit(textInput);
- } else if (event.key === 'Escape') {
- setAppState((old) => ({ ...old, reactionPickerOpen: false }));
- }
- }}
- />
-
+
+
+
setAppState((old) => ({ ...old, reactionPickerOpen: true }))}
+ >
+ {!appState?.reactionPickerOpen && }
- )}
+ {appState?.reactionPickerOpen && (
+
+ setTextInput(e.target.value)}
+ onKeyUp={(event) => {
+ if (event.key === 'Enter') {
+ onTextSubmit(textInput);
+ } else if (event.key === 'Escape') {
+ setAppState((old) => ({ ...old, reactionPickerOpen: false }));
+ }
+ }}
+ />
+
+
+ )}
+
);
};
diff --git a/frontend/src/party/PartyOverviewItem.tsx b/frontend/src/party/PartyOverviewItem.tsx
index 3262e78..fb9208b 100644
--- a/frontend/src/party/PartyOverviewItem.tsx
+++ b/frontend/src/party/PartyOverviewItem.tsx
@@ -13,8 +13,8 @@ function PartyOverviewItem({ party }: { party: PartyResponse }) {
const submissions = sortSubmissions(party.submissions);
return (
-
-
+
+
-
+
{submissions.slice(0, 3).map((submission) => (
-
-
})
-
+
})
))}
diff --git a/frontend/src/party/timeline/PartyTimeline.tsx b/frontend/src/party/timeline/PartyTimeline.tsx
index 3fba54e..4d262da 100644
--- a/frontend/src/party/timeline/PartyTimeline.tsx
+++ b/frontend/src/party/timeline/PartyTimeline.tsx
@@ -44,7 +44,7 @@ export const PartyTimeline: FunctionComponent<{ year: number; parties: PartyResp
{year}
{monthsWithSumissions.map((month) => (
-
+
{month}
{timeline.get(month)!.map((party) => (
diff --git a/frontend/src/party/view/ReactionBubbles.tsx b/frontend/src/party/view/ReactionBubbles.tsx
index 0ff34fa..8fb085e 100644
--- a/frontend/src/party/view/ReactionBubbles.tsx
+++ b/frontend/src/party/view/ReactionBubbles.tsx
@@ -36,7 +36,7 @@ const ReactionBubbles: FunctionComponent<{ partyId: string }> = ({ partyId }) =>
}, [reactions]);
return (
-
+
{reactions.map((reaction, i) => (
{reaction.reaction}
diff --git a/frontend/src/party/view/SubmissionGallery.tsx b/frontend/src/party/view/SubmissionGallery.tsx
new file mode 100644
index 0000000..f34902f
--- /dev/null
+++ b/frontend/src/party/view/SubmissionGallery.tsx
@@ -0,0 +1,92 @@
+import 'photoswipe/dist/photoswipe.css';
+
+import { useEffect, useState } from 'react';
+import { FaStar } from 'react-icons/fa';
+import { Gallery, Item } from 'react-photoswipe-gallery';
+import { PartySubmissionResponse } from '../../api/api-models';
+import { getImageUrl, getThumbnailUrl } from '../../api/api';
+import PartyPosition from './rewards/PartyPosition';
+import { avgRatingTwoDecimals, totalRating } from './util';
+
+interface ImageDimensions {
+ width: number;
+ height: number;
+}
+
+function useImageDimensions(imageUrls: string[], imageIds: string[]): Map
{
+ const [dimensions, setDimensions] = useState
);
- if (partyStatus.state === 'waitinglobby') {
+ if (partyStatus.state === PartyStatusState.LOBBY) {
return (
@@ -98,9 +97,9 @@ const ViewPartyContent = ({
);
}
- if (partyStatus.state === 'prereveal') {
+ if (partyStatus.state === PartyStatusState.PRE_REVEAL) {
return (
-
+
Thanks for voting!
Waiting for host...
@@ -112,7 +111,7 @@ const ViewPartyContent = ({
);
}
- if (partyStatus.state === 'reveal') {
+ if (partyStatus.state === PartyStatusState.REVEAL) {
return (
@@ -171,7 +170,9 @@ function ViewParty() {
const isHost = session!.userId === partyUserId;
const showControlButtons =
- isHost && (partyStatusState === 'submissions' || partyStatusState === 'reveal' || partyStatusState === 'prereveal');
+ isHost && (partyStatusState === PartyStatusState.RUNNING ||
+ partyStatusState === PartyStatusState.REVEAL ||
+ partyStatusState === PartyStatusState.PRE_REVEAL);
return (
diff --git a/frontend/src/party/view/ViewPartyLeaderboard.tsx b/frontend/src/party/view/ViewPartyLeaderboard.tsx
index 6e6edad..46b949a 100644
--- a/frontend/src/party/view/ViewPartyLeaderboard.tsx
+++ b/frontend/src/party/view/ViewPartyLeaderboard.tsx
@@ -1,11 +1,9 @@
-import { FaStar } from 'react-icons/fa';
-import { getImageUrl, getThumbnailUrl } from '../../api/api';
-import { PartyResponse } from '../../api/api-models';
-import PartyPosition from './rewards/PartyPosition';
+import {PartyResponse} from '../../api/api-models';
-import { totalRating, avgRatingTwoDecimals, sortSubmissions } from './util';
-import { useSession } from '../../user/session';
+import {sortSubmissions} from './util';
+import {useSession} from '../../user/session';
import LinkButton from '../../components/LinkButton';
+import SubmissionGallery from "./SubmissionGallery";
function ViewPartyLeaderboard({ party }: { party: PartyResponse }) {
const [session] = useSession();
@@ -13,38 +11,14 @@ function ViewPartyLeaderboard({ party }: { party: PartyResponse }) {
const sortedSubmissions = sortSubmissions(party.submissions);
return (
-
-
+
+
{party.name}
{isHost && }
-
- {sortedSubmissions.map((submission, i) => (
-
-
-
-
-
- {submission.name} {submission.description}
-
-
Ø {avgRatingTwoDecimals(submission.votes)}
-
-
{totalRating(submission.votes)}
-
-
-
{submission.votes.length} vote(s)
-
-
- ))}
+
+
);
diff --git a/frontend/src/party/view/ViewPartyReveal.tsx b/frontend/src/party/view/ViewPartyReveal.tsx
index b5a3563..9ef0a3b 100644
--- a/frontend/src/party/view/ViewPartyReveal.tsx
+++ b/frontend/src/party/view/ViewPartyReveal.tsx
@@ -14,9 +14,9 @@ function ViewPartyReveal({ party, partyStatus }: { party: PartyResponse; partySt
const votes = getSubmissionVotes(partyStatus, currentSumbission);
return (
-
+
-
+
{currentSumbission.name}
{currentSumbission.description}
-
+
diff --git a/frontend/src/party/view/ViewPartyStartPage.tsx b/frontend/src/party/view/ViewPartyStartPage.tsx
index e44a1a3..2891bec 100644
--- a/frontend/src/party/view/ViewPartyStartPage.tsx
+++ b/frontend/src/party/view/ViewPartyStartPage.tsx
@@ -13,7 +13,7 @@ function ViewPartyStartPage({
onPartyStart: () => any;
}) {
return (
-
+
- {partyName} -
Share link:
diff --git a/frontend/src/party/view/ViewPartySubmission.tsx b/frontend/src/party/view/ViewPartySubmission.tsx
index bae9255..2899aa7 100644
--- a/frontend/src/party/view/ViewPartySubmission.tsx
+++ b/frontend/src/party/view/ViewPartySubmission.tsx
@@ -41,15 +41,15 @@ function ViewPartySubmission({
}, [rating, onRating]);
return (
-
-
+
+
-
-
+
{partyStatus.current!.position + 1}
@@ -75,7 +75,7 @@ function ViewPartySubmission({
{partySubmission.description}
-
+
{getSubmissionVotes(partyStatus, partySubmission).length}
diff --git a/frontend/src/party/view/rewards/PartyPosition.tsx b/frontend/src/party/view/rewards/PartyPosition.tsx
index 1a5205d..3e539a9 100644
--- a/frontend/src/party/view/rewards/PartyPosition.tsx
+++ b/frontend/src/party/view/rewards/PartyPosition.tsx
@@ -3,12 +3,12 @@ import TrophyUrl from '/src/assets/trophy.svg';
function PartyPosition({ position }: { position: number }) {
return (
- <>
+
{position === 0 &&

}
{position === 1 &&
}
{position === 2 &&
}
{position > 2 &&
{position + 1}.
}
- >
+
);
}
diff --git a/frontend/src/version.ts b/frontend/src/version.ts
index 802d333..e150b6a 100644
--- a/frontend/src/version.ts
+++ b/frontend/src/version.ts
@@ -9,6 +9,14 @@ export interface Change {
}
export const CHANGES: Change[] = [
+ {
+ name: '0.15.0',
+ changes: [
+ { description: 'Added gallery to party view', type: 'feature' },
+ { description: 'Use more space for photo display in running party', type: 'note' },
+ { description: 'Fixed party overview on homepage for Firefox', type: 'fix' },
+ ],
+ },
{
name: '0.14.2',
changes: [