diff --git a/backend/experiment/serializers.py b/backend/experiment/serializers.py
index 4bbe2d529..4ec2a57d4 100644
--- a/backend/experiment/serializers.py
+++ b/backend/experiment/serializers.py
@@ -50,12 +50,15 @@ def serialize_experiment_collection_group(group: ExperimentCollectionGroup, part
next_experiment = get_upcoming_experiment(
grouped_experiments, participant, group.dashboard)
+ total_score = get_total_score(grouped_experiments, participant)
+
if not next_experiment:
return None
return {
'dashboard': [serialize_experiment(experiment.experiment, participant) for experiment in grouped_experiments] if group.dashboard else [],
- 'next_experiment': next_experiment
+ 'next_experiment': next_experiment,
+ 'total_score': total_score
}
@@ -93,3 +96,13 @@ def get_finished_session_count(experiment, participant):
count = Session.objects.filter(
experiment=experiment, participant=participant, finished_at__isnull=False).count()
return count
+
+
+def get_total_score(grouped_experiments, participant):
+ '''Calculate total score of all experiments on the dashboard'''
+ total_score = 0
+ for grouped_experiment in grouped_experiments:
+ sessions = Session.objects.filter(experiment=grouped_experiment.experiment, participant=participant)
+ for session in sessions:
+ total_score += session.final_score
+ return total_score
diff --git a/backend/theme/serializers.py b/backend/theme/serializers.py
index 980b2bda3..00a3a9309 100644
--- a/backend/theme/serializers.py
+++ b/backend/theme/serializers.py
@@ -20,8 +20,11 @@ def serialize_header(header: HeaderConfig) -> dict:
return {
'nextExperimentButtonText': _('Next experiment'),
'aboutButtonText': _('About us'),
- 'showScore': header.show_score
- }
+ 'showScore': header.show_score,
+ 'scoreClass': 'gold',
+ 'scoreLabel': _('Points'),
+ 'noScoreLabel': _('No points yet!')
+ }
def serialize_theme(theme: ThemeConfig) -> dict:
diff --git a/frontend/src/components/ExperimentCollection/ExperimentCollection.tsx b/frontend/src/components/ExperimentCollection/ExperimentCollection.tsx
index 32f27472c..75412834b 100644
--- a/frontend/src/components/ExperimentCollection/ExperimentCollection.tsx
+++ b/frontend/src/components/ExperimentCollection/ExperimentCollection.tsx
@@ -34,7 +34,8 @@ const ExperimentCollection = ({ match }: ExperimentCollectionProps) => {
const nextExperiment = experimentCollection?.next_experiment;
const displayDashboard = experimentCollection?.dashboard.length;
const showConsent = experimentCollection?.consent;
-
+ const totalScore = experimentCollection?.total_score
+ const scoreClass = experimentCollection?.score_class
const onNext = () => {
setHasShownConsent(true);
}
@@ -72,7 +73,7 @@ const ExperimentCollection = ({ match }: ExperimentCollectionProps) => {
} />
- } />
+ } />
)
diff --git a/frontend/src/components/ExperimentCollection/ExperimentCollectionDashboard/ExperimentCollectionDashboard.tsx b/frontend/src/components/ExperimentCollection/ExperimentCollectionDashboard/ExperimentCollectionDashboard.tsx
index e9ff1d349..71c8185a7 100644
--- a/frontend/src/components/ExperimentCollection/ExperimentCollectionDashboard/ExperimentCollectionDashboard.tsx
+++ b/frontend/src/components/ExperimentCollection/ExperimentCollectionDashboard/ExperimentCollectionDashboard.tsx
@@ -12,16 +12,19 @@ interface ExperimentCollectionDashboardProps {
participantIdUrl: string | null;
}
-export const ExperimentCollectionDashboard: React.FC = ({ experimentCollection, participantIdUrl }) => {
-
+export const ExperimentCollectionDashboard: React.FC = ({ experimentCollection, participantIdUrl, totalScore }) => {
+
const dashboard = experimentCollection.dashboard;
- const nextExperimentSlug = experimentCollection.nextExperiment?.slug;
+ const nextExperimentSlug = experimentCollection.nextExperiment?.slug;
+
const headerProps = experimentCollection.theme?.header? {
- nextExperimentSlug,
+ nextExperimentSlug,
collectionSlug: experimentCollection.slug,
- ... experimentCollection.theme.header
+ ...experimentCollection.theme.header,
+ totalScore: totalScore
+
} : undefined;
-
+
const getExperimentHref = (slug: string) => `/${slug}${participantIdUrl ? `?participant_id=${participantIdUrl}` : ""}`;
return (
diff --git a/frontend/src/components/Header/Header.tsx b/frontend/src/components/Header/Header.tsx
index f3018f02f..6be6346ac 100644
--- a/frontend/src/components/Header/Header.tsx
+++ b/frontend/src/components/Header/Header.tsx
@@ -1,15 +1,84 @@
-import React from "react";
+import React, { useEffect, useState, useRef } from "react";
import { Link } from "react-router-dom";
+import Rank from "../Rank/Rank";
+import Social from "../Social/Social"
+
interface HeaderProps {
nextExperimentSlug: string | undefined;
nextExperimentButtonText: string;
collectionSlug: string;
aboutButtonText: string;
showScore: boolean;
+ totalScore: BigInteger;
+ scoreClass: string;
+ scoreLabel: string;
+ noScoreLabel: string;
}
-export const Header: React.FC = ({ nextExperimentSlug, nextExperimentButtonText, collectionSlug, aboutButtonText, showScore }) => {
+export const Header: React.FC = ({ nextExperimentSlug, nextExperimentButtonText, collectionSlug, aboutButtonText, showScore, totalScore, scoreClass, scoreLabel, noScoreLabel }) => {
+
+ const social = {
+ 'apps': ['facebook', 'twitter'],
+ 'message': `I scored ${totalScore} points`,
+ 'url': 'wwww.amsterdammusiclab.nl',
+ 'hashtags': ["amsterdammusiclab", "citizenscience"]
+ }
+
+ const useAnimatedScore = (targetScore) => {
+ const [score, setScore] = useState(0);
+
+ const scoreValue = useRef(0);
+
+ useEffect(() => {
+ if (targetScore === 0) {
+ return;
+ }
+
+ let id = -1;
+
+ const nextStep = () => {
+ // Score step
+ const scoreStep = Math.max(
+ 1,
+ Math.min(10, Math.ceil(Math.abs(scoreValue.current - targetScore) / 10))
+ );
+
+ // Scores are equal, stop
+ if (targetScore === scoreValue.current) {
+ return;
+ }
+
+ // Add / subtract score
+ scoreValue.current += Math.sign(targetScore - scoreValue.current) * scoreStep;
+ setScore(scoreValue.current);
+
+ id = setTimeout(nextStep, 50);
+ };
+ id = setTimeout(nextStep, 50);
+
+ return () => {
+ window.clearTimeout(id);
+ };
+ }, [targetScore]);
+
+ return score;
+ };
+
+ const Score = ({ score, label, scoreClass }) => {
+ const currentScore = useAnimatedScore(score);
+
+ return (
+
+
+
+ {currentScore ? currentScore + " " : ""}
+ {label}
+
+
+ );
+ };
+
return (
@@ -18,6 +87,21 @@ export const Header: React.FC = ({ nextExperimentSlug, nextExperime
{aboutButtonText && {aboutButtonText}}
+ {showScore, totalScore !== 0 && (
+
+
+
+
+ )}
+ {showScore, totalScore === 0 && (
+
{noScoreLabel}
+ )}
);
}
diff --git a/frontend/src/types/Theme.ts b/frontend/src/types/Theme.ts
index b9aeae5df..9b674bd63 100644
--- a/frontend/src/types/Theme.ts
+++ b/frontend/src/types/Theme.ts
@@ -2,6 +2,7 @@ export interface Header {
nextExperimentButtonText: string;
aboutButtonText: string;
showScore: boolean;
+ totalScore: BigInteger;
};
export default interface Theme {
@@ -13,4 +14,4 @@ export default interface Theme {
name: string;
footer: null;
header: Header | null;
-}
\ No newline at end of file
+}