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
10 changes: 10 additions & 0 deletions front_end/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,16 @@
"failedToLoadAggregation": "Nepodařilo se načíst agregaci",
"questionFallbackLabel": "Otázka {id}",
"openInAggregationExplorer": "Otevřít v Průzkumníku agregací",
"tournamentFollowModalTitle": "Sledovat turnaj",
"tournamentFollowModalDescription": "Obdržíte oznámení o nových otázkách a konečném pořadí.",
"tournamentFollowModalAlsoFollowQuestions": "Sledovat také všechny otázky",
"tournamentFollowModalAlsoFollowQuestionsDescription": "Automaticky sledovat každou otázku s výchozím nastavením oznámení. Nově přidané otázky budou také sledovány.",
"tournamentFollowModalSubmit": "Sledovat",
"tournamentUnfollowModalTitle": "Zrušit sledování turnaje",
"tournamentUnfollowModalDescription": "Už nebudete dostávat oznámení o tomto turnaji.",
"tournamentUnfollowModalAlsoUnfollowQuestions": "Zrušit sledování všech otázek",
"tournamentUnfollowModalAlsoUnfollowQuestionsDescription": "Odstraňte sledování všech otázek v tomto turnaji.",
"tournamentUnfollowModalSubmit": "Zrušit sledování",
"switchBackToSlidersHint": "přepněte zpět na posuvníky pro plynulé přizpůsobení",
"view": "Zobrazit",
"thousandsOfOpenQuestions": "20 000+ otevřených otázek"
Expand Down
10 changes: 10 additions & 0 deletions front_end/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,16 @@
"followModalStatusChanges": "Status changes",
"unfollowModalTitle": "Are you sure?",
"unfollowModalDescription": "You won't be notified about this question anymore",
"tournamentFollowModalTitle": "Follow Tournament",
"tournamentFollowModalDescription": "You'll receive notifications about new questions and final rankings.",
"tournamentFollowModalAlsoFollowQuestions": "Also follow all questions",
"tournamentFollowModalAlsoFollowQuestionsDescription": "Automatically follow each question with default notification settings. Newly added questions will also be followed.",
"tournamentFollowModalSubmit": "Follow",
"tournamentUnfollowModalTitle": "Unfollow Tournament",
"tournamentUnfollowModalDescription": "You will no longer receive notifications about this tournament.",
"tournamentUnfollowModalAlsoUnfollowQuestions": "Also unfollow all questions",
"tournamentUnfollowModalAlsoUnfollowQuestionsDescription": "Remove your follows from all questions in this tournament.",
"tournamentUnfollowModalSubmit": "Unfollow",
"best": "best",
"cmmButton": "Changed my mind",
"resolutionScores": "Resolution Scores",
Expand Down
10 changes: 10 additions & 0 deletions front_end/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,16 @@
"failedToLoadAggregation": "No se pudo cargar la agregación",
"questionFallbackLabel": "Pregunta {id}",
"openInAggregationExplorer": "Abrir en el Explorador de Agregación",
"tournamentFollowModalTitle": "Seguir Torneo",
"tournamentFollowModalDescription": "Recibirás notificaciones sobre nuevas preguntas y clasificaciones finales.",
"tournamentFollowModalAlsoFollowQuestions": "Seguir también todas las preguntas",
"tournamentFollowModalAlsoFollowQuestionsDescription": "Seguir automáticamente cada pregunta con la configuración de notificación predeterminada. Las preguntas recién añadidas también serán seguidas.",
"tournamentFollowModalSubmit": "Seguir",
"tournamentUnfollowModalTitle": "Dejar de Seguir Torneo",
"tournamentUnfollowModalDescription": "Ya no recibirás notificaciones sobre este torneo.",
"tournamentUnfollowModalAlsoUnfollowQuestions": "Dejar de seguir también todas las preguntas",
"tournamentUnfollowModalAlsoUnfollowQuestionsDescription": "Elimina tus seguimientos de todas las preguntas en este torneo.",
"tournamentUnfollowModalSubmit": "Dejar de Seguir",
"switchBackToSlidersHint": "vuelve a los deslizadores para un ajuste suave",
"view": "Ver",
"thousandsOfOpenQuestions": "20,000+ preguntas abiertas"
Expand Down
10 changes: 10 additions & 0 deletions front_end/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,16 @@
"failedToLoadAggregation": "Falha ao carregar agregação",
"questionFallbackLabel": "Pergunta {id}",
"openInAggregationExplorer": "Abrir no Explorador de Agregações",
"tournamentFollowModalTitle": "Seguir Torneio",
"tournamentFollowModalDescription": "Você receberá notificações sobre novas perguntas e classificações finais.",
"tournamentFollowModalAlsoFollowQuestions": "Seguir todas as perguntas",
"tournamentFollowModalAlsoFollowQuestionsDescription": "Siga automaticamente cada pergunta com as configurações padrão de notificação. Perguntas adicionadas recentemente também serão seguidas.",
"tournamentFollowModalSubmit": "Seguir",
"tournamentUnfollowModalTitle": "Deixar de Seguir Torneio",
"tournamentUnfollowModalDescription": "Você não receberá mais notificações sobre este torneio.",
"tournamentUnfollowModalAlsoUnfollowQuestions": "Deixar de seguir todas as perguntas",
"tournamentUnfollowModalAlsoUnfollowQuestionsDescription": "Remova seus seguimentos de todas as perguntas neste torneio.",
"tournamentUnfollowModalSubmit": "Deixar de Seguir",
"switchBackToSlidersHint": "volte para os controles deslizantes para um ajuste suave",
"view": "Visualizar",
"thousandsOfOpenQuestions": "20.000+ perguntas abertas"
Expand Down
10 changes: 10 additions & 0 deletions front_end/messages/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -2066,6 +2066,16 @@
"failedToLoadAggregation": "載入聚合失敗",
"questionFallbackLabel": "問題 {id}",
"openInAggregationExplorer": "在聚合探索器中開啟",
"tournamentFollowModalTitle": "關注比賽",
"tournamentFollowModalDescription": "您將會收到關於新問題和最終排名的通知。",
"tournamentFollowModalAlsoFollowQuestions": "亦關注所有問題",
"tournamentFollowModalAlsoFollowQuestionsDescription": "自動關注每個問題,並使用默認通知設置。新增的問題也將被關注。",
"tournamentFollowModalSubmit": "關注",
"tournamentUnfollowModalTitle": "取消關注比賽",
"tournamentUnfollowModalDescription": "您將不再收到有關此比賽的通知。",
"tournamentUnfollowModalAlsoUnfollowQuestions": "亦取消關注所有問題",
"tournamentUnfollowModalAlsoUnfollowQuestionsDescription": "從此比賽中的所有問題中移除您的關注。",
"tournamentUnfollowModalSubmit": "取消關注",
"switchBackToSlidersHint": "切換回滑桿以平滑調整",
"view": "檢視",
"thousandsOfOpenQuestions": "20,000+ 開放問題"
Expand Down
10 changes: 10 additions & 0 deletions front_end/messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -2071,6 +2071,16 @@
"failedToLoadAggregation": "无法加载聚合",
"questionFallbackLabel": "问题 {id}",
"openInAggregationExplorer": "在聚合探索器中打开",
"tournamentFollowModalTitle": "关注比赛",
"tournamentFollowModalDescription": "您将收到关于新问题和最终排名的通知。",
"tournamentFollowModalAlsoFollowQuestions": "同时关注所有问题",
"tournamentFollowModalAlsoFollowQuestionsDescription": "根据默认通知设置自动关注每个问题。新添加的问题也会被关注。",
"tournamentFollowModalSubmit": "关注",
"tournamentUnfollowModalTitle": "取消关注比赛",
"tournamentUnfollowModalDescription": "您将不再收到关于此比赛的通知。",
"tournamentUnfollowModalAlsoUnfollowQuestions": "同时取消关注所有问题",
"tournamentUnfollowModalAlsoUnfollowQuestionsDescription": "取消您对此比赛中所有问题的关注。",
"tournamentUnfollowModalSubmit": "取消关注",
"switchBackToSlidersHint": "切回滑块以进行更精细的调整",
"view": "查看",
"thousandsOfOpenQuestions": "20,000+ 开放问题"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,16 @@ export async function updateMember(
}
}

export async function subscribeProject(projectId: number) {
return ServerProjectsApi.subscribe(projectId);
export async function subscribeProject(
projectId: number,
params?: { follow_questions?: boolean }
) {
return ServerProjectsApi.subscribe(projectId, params);
}

export async function unsubscribeProject(projectId: number) {
return ServerProjectsApi.unsubscribe(projectId);
export async function unsubscribeProject(
projectId: number,
params?: { unfollow_questions?: boolean }
) {
return ServerProjectsApi.unsubscribe(projectId, params);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
subscribeProject,
unsubscribeProject,
} from "@/app/(main)/(tournaments)/tournament/[slug]/actions";
import TournamentSubscribeModal from "@/app/(main)/(tournaments)/tournament/components/tournament_subscribe_modal";
import Button from "@/components/ui/button";
import { useModal } from "@/contexts/modal_context";
import { Tournament } from "@/types/projects";
Expand All @@ -23,41 +24,71 @@ const TournamentSubscribeButton: FC<Props> = ({ user, tournament }) => {
const [isFollowing, setIsFollowing] = useState(
() => tournament.is_subscribed
);
const [followQuestions, setFollowQuestions] = useState(
() => tournament.follow_questions ?? false
);
const { setCurrentModal } = useModal();
const [isLoading, setIsLoading] = useState(false);
const [modalMode, setModalMode] = useState<"follow" | "unfollow" | null>(
null
);

const handleSubscribe = useCallback(async () => {
const handleFollowClick = useCallback(() => {
if (!user) {
setCurrentModal({ type: "signup" });
} else {
setIsLoading(true);
setModalMode("follow");
}
}, [setCurrentModal, user]);

const handleUnfollowClick = useCallback(() => {
setModalMode("unfollow");
}, []);

const handleModalClose = useCallback(() => {
setModalMode(null);
}, []);

const handleFollowSubmit = useCallback(
async (shouldFollowQuestions: boolean) => {
setIsLoading(true);
try {
await subscribeProject(tournament.id);
await subscribeProject(tournament.id, {
follow_questions: shouldFollowQuestions,
});
setIsFollowing(true);
setFollowQuestions(shouldFollowQuestions);
setModalMode(null);
} finally {
setIsLoading(false);
}
}
}, [setCurrentModal, tournament.id, user]);

const handleUnsubscribe = useCallback(async () => {
setIsLoading(true);
},
[tournament.id]
);

try {
await unsubscribeProject(tournament.id);
setIsFollowing(false);
} finally {
setIsLoading(false);
}
}, [tournament.id]);
const handleUnfollowSubmit = useCallback(
async (shouldUnfollowQuestions: boolean) => {
setIsLoading(true);
try {
await unsubscribeProject(tournament.id, {
unfollow_questions: shouldUnfollowQuestions,
});
setIsFollowing(false);
setFollowQuestions(false);
setModalMode(null);
} finally {
setIsLoading(false);
}
},
[tournament.id]
);

return (
<>
{user && isFollowing ? (
<Button
variant="primary"
onClick={handleUnsubscribe}
onClick={handleUnfollowClick}
disabled={isLoading}
className="border-blue-700 px-4 py-[5px] text-sm font-medium leading-5 text-blue-400 dark:border-blue-700-dark dark:text-blue-400-dark lg:text-base"
>
Expand All @@ -67,14 +98,25 @@ const TournamentSubscribeButton: FC<Props> = ({ user, tournament }) => {
) : (
<Button
variant="secondary"
onClick={handleSubscribe}
onClick={handleFollowClick}
disabled={isLoading}
className="border-blue-400 px-4 py-[5px] text-sm font-medium leading-5 text-blue-700 dark:border-blue-400-dark dark:text-blue-700-dark lg:text-base"
>
<FontAwesomeIcon icon={faBell} type="solid" />
{t("followButton")}
</Button>
)}

<TournamentSubscribeModal
isOpen={modalMode !== null}
onClose={handleModalClose}
mode={modalMode ?? "follow"}
defaultFollowQuestions={followQuestions}
onSubmit={
modalMode === "follow" ? handleFollowSubmit : handleUnfollowSubmit
}
isLoading={isLoading}
/>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use client";

import { useTranslations } from "next-intl";
import { FC, useEffect, useState } from "react";

import BaseModal from "@/components/base_modal";
import Button from "@/components/ui/button";
import Checkbox from "@/components/ui/checkbox";

type Props = {
isOpen: boolean;
onClose: () => void;
mode: "follow" | "unfollow";
defaultFollowQuestions?: boolean;
onSubmit: (followQuestions: boolean) => void;
isLoading: boolean;
};

const TournamentSubscribeModal: FC<Props> = ({
isOpen,
onClose,
mode,
defaultFollowQuestions = false,
onSubmit,
isLoading,
}) => {
const t = useTranslations();
const [followQuestions, setFollowQuestions] = useState(
defaultFollowQuestions
);

useEffect(() => {
setFollowQuestions(defaultFollowQuestions);
}, [defaultFollowQuestions]);

const isFollow = mode === "follow";

return (
<BaseModal
isOpen={isOpen}
onClose={() => onClose()}
label={
isFollow
? t("tournamentFollowModalTitle")
: t("tournamentUnfollowModalTitle")
}
className="max-w-sm"
>
<p className="my-3 text-gray-700 dark:text-gray-700-dark">
{isFollow
? t("tournamentFollowModalDescription")
: t("tournamentUnfollowModalDescription")}
</p>

<div className="my-4">
<Checkbox
checked={followQuestions}
onChange={setFollowQuestions}
label={
isFollow
? t("tournamentFollowModalAlsoFollowQuestions")
: t("tournamentUnfollowModalAlsoUnfollowQuestions")
}
/>
<p className="ml-7 mt-1 text-xs text-gray-500 dark:text-gray-500-dark">
{isFollow
? t("tournamentFollowModalAlsoFollowQuestionsDescription")
: t("tournamentUnfollowModalAlsoUnfollowQuestionsDescription")}
</p>
</div>

<div className="flex justify-end gap-3">
<Button variant="tertiary" onClick={onClose} disabled={isLoading}>
{t("cancel")}
</Button>
<Button
variant="primary"
onClick={() => onSubmit(followQuestions)}
disabled={isLoading}
>
{isFollow
? t("tournamentFollowModalSubmit")
: t("tournamentUnfollowModalSubmit")}
</Button>
</div>
</BaseModal>
);
};

export default TournamentSubscribeModal;
11 changes: 7 additions & 4 deletions front_end/src/services/api/projects/projects.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@ class ServerProjectsApiClass extends ProjectsApi {
);
}

async subscribe(projectId: number) {
return this.post(`/projects/${projectId}/subscribe/`, {});
async subscribe(projectId: number, params?: { follow_questions?: boolean }) {
return this.post(`/projects/${projectId}/subscribe/`, params ?? {});
}

async unsubscribe(projectId: number) {
return this.post(`/projects/${projectId}/unsubscribe/`, {});
async unsubscribe(
projectId: number,
params?: { unfollow_questions?: boolean }
) {
return this.post(`/projects/${projectId}/unsubscribe/`, params ?? {});
}

async updateCommunity(
Expand Down
1 change: 1 addition & 0 deletions front_end/src/types/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export type Tournament = TournamentPreview & {
image_url: string;
};
is_subscribed?: boolean;
follow_questions?: boolean;
add_posts_to_main_feed: boolean;
visibility: ProjectVisibility;
default_permission?: ProjectPermissions | null;
Expand Down
18 changes: 18 additions & 0 deletions projects/migrations/0022_projectsubscription_follow_questions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.9

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("projects", "0021_projectindex_project_index_projectindexpost"),
]

operations = [
migrations.AddField(
model_name="projectsubscription",
name="follow_questions",
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions projects/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ class ProjectSubscription(TimeStampedModel):
project = models.ForeignKey(
Project, on_delete=models.CASCADE, related_name="subscriptions"
)
follow_questions = models.BooleanField(default=False)

class Meta:
constraints = [
Expand Down
Loading
Loading