Skip to content
Merged
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
4 changes: 4 additions & 0 deletions front_end/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1941,5 +1941,9 @@
"yourOrganization": "Vaše organizace",
"yourMessageForm": "Vaše zpráva",
"readFullReport": "Přečíst celou zprávu",
"weeklyTopCommentsSubscribeCta": "Dostávejte ty nejlepší komentáře Metaculus každý týden do své schránky!",
"weeklyTopCommentsSubscribeSuccess": "Nyní jste přihlášeni k odběru Nejlepších komentářů. Odběr můžete kdykoli zrušit ve svém účtu <settings>nastavení</settings>.",
"weeklyTopCommentsSubscribeSuccessInline": "Nyní jste přihlášeni k odběru Nejlepších komentářů.",
"weeklyTopCommentsSubscribeError": "Něco se pokazilo. Zkuste to prosím znovu.",
"thousandsOfOpenQuestions": "20 000+ otevřených otázek"
}
4 changes: 4 additions & 0 deletions front_end/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@
"signUpNewsletterOptin": "Get updates on tournaments, prizes, and platform news",
"weeklyTopComments": "Top Comments of the Week",
"weeklyTopCommentsShort": "Top Comments",
"weeklyTopCommentsSubscribeCta": "Receive the best Metaculus comments in your inbox every week!",
"weeklyTopCommentsSubscribeSuccess": "You're now subscribed to Top Comments. Unsubscribe at any time in your account <settings>settings</settings>.",
"weeklyTopCommentsSubscribeSuccessInline": "You're now subscribed to Top Comments.",
"weeklyTopCommentsSubscribeError": "Something went wrong. Please try again.",
"withdrawingSoon": "Withdrawing soon",
"createArticle": "+ Create Article",
"newsletterFormHeading": "Join Our Newsletter",
Expand Down
4 changes: 4 additions & 0 deletions front_end/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1941,5 +1941,9 @@
"yourOrganization": "Tu organización",
"yourMessageForm": "Tu mensaje",
"readFullReport": "Leer el informe completo",
"weeklyTopCommentsSubscribeCta": "¡Recibe los mejores comentarios de Metaculus en tu bandeja de entrada cada semana!",
"weeklyTopCommentsSubscribeSuccess": "Ahora estás suscrito a los Mejores Comentarios. Cancela tu suscripción en cualquier momento en la <settings>configuración</settings> de tu cuenta.",
"weeklyTopCommentsSubscribeSuccessInline": "Ahora estás suscrito a los Mejores Comentarios.",
"weeklyTopCommentsSubscribeError": "Algo salió mal. Por favor, inténtalo de nuevo.",
"thousandsOfOpenQuestions": "20,000+ preguntas abiertas"
}
4 changes: 4 additions & 0 deletions front_end/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1939,5 +1939,9 @@
"yourOrganization": "Sua organização",
"yourMessageForm": "Sua mensagem",
"readFullReport": "Leia o relatório completo",
"weeklyTopCommentsSubscribeCta": "Receba os melhores comentários do Metaculus na sua caixa de entrada toda semana!",
"weeklyTopCommentsSubscribeSuccess": "Você está agora inscrito nos Comentários Principais. Cancele a inscrição a qualquer momento nas <settings>configurações</settings> da sua conta.",
"weeklyTopCommentsSubscribeSuccessInline": "Você está agora inscrito nos Comentários Principais.",
"weeklyTopCommentsSubscribeError": "Algo deu errado. Por favor, tente novamente.",
"thousandsOfOpenQuestions": "20.000+ perguntas abertas"
}
4 changes: 4 additions & 0 deletions front_end/messages/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -1938,5 +1938,9 @@
"yourOrganization": "您的組織",
"yourMessageForm": "您的訊息",
"readFullReport": "閱讀完整報告",
"weeklyTopCommentsSubscribeCta": "每週在收件箱中接收來自 Metaculus 的最佳評論!",
"weeklyTopCommentsSubscribeSuccess": "您現在已訂閱熱門評論。隨時可以在您的帳戶<settings>設定</settings>中取消訂閱。",
"weeklyTopCommentsSubscribeSuccessInline": "您現在已訂閱熱門評論。",
"weeklyTopCommentsSubscribeError": "出了些問題。請再試一次。",
"thousandsOfOpenQuestions": "20,000+ 開放問題"
}
4 changes: 4 additions & 0 deletions front_end/messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1943,5 +1943,9 @@
"yourOrganization": "您的组织",
"yourMessageForm": "您的信息",
"readFullReport": "阅读完整报告",
"weeklyTopCommentsSubscribeCta": "每周收到你的收件箱中的最佳 Metaculus 评论!",
"weeklyTopCommentsSubscribeSuccess": "你已订阅顶级评论。随时在你的账户<settings>设置</settings>中取消订阅。",
"weeklyTopCommentsSubscribeSuccessInline": "你已订阅顶级评论。",
"weeklyTopCommentsSubscribeError": "出现问题。请再试一次。",
"thousandsOfOpenQuestions": "20,000+ 开放问题"
}
17 changes: 14 additions & 3 deletions front_end/src/components/auth/signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@ import { useModal } from "@/contexts/modal_context";
import { usePublicSettings } from "@/contexts/public_settings_context";
import { useServerAction } from "@/hooks/use_server_action";
import { ErrorResponse } from "@/types/fetch";
import { CurrentUser } from "@/types/users";
import { sendAnalyticsEvent } from "@/utils/analytics";

import usePostLoginActionHandler from "./hooks/usePostLoginActionHandler";

type SignInModalType = {
isOpen: boolean;
onClose: (isOpen: boolean) => void;
onSuccess?: (authenticatedUser: CurrentUser) => void | Promise<void>;
};

const SignInModal: FC<SignInModalType> = ({
isOpen,
onClose,
onSuccess,
}: SignInModalType) => {
const { PUBLIC_ALLOW_SIGNUP } = usePublicSettings();

Expand Down Expand Up @@ -73,10 +76,16 @@ const SignInModal: FC<SignInModalType> = ({
sendAnalyticsEvent("login");
setUser(state.user);
setCurrentModal(null);
handlePostLoginAction(state.postLoginAction);
try {
await onSuccess?.(state.user);
} catch (error) {
console.error("SignIn onSuccess callback failed", error);
} finally {
handlePostLoginAction(state.postLoginAction);
}
}
},
[setCurrentModal, setUser, handlePostLoginAction, resetField]
[setCurrentModal, setUser, handlePostLoginAction, onSuccess, resetField]
);
const [submit, isPending] = useServerAction(onSubmit);

Expand All @@ -95,7 +104,9 @@ const SignInModal: FC<SignInModalType> = ({
<Button
variant="link"
size="md"
onClick={() => setCurrentModal({ type: "signup" })}
onClick={() =>
setCurrentModal({ type: "signup", data: { onSuccess } })
}
>
{t("createAnAccount")}
</Button>
Expand Down
26 changes: 22 additions & 4 deletions front_end/src/components/auth/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { usePublicSettings } from "@/contexts/public_settings_context";
import useAppTheme from "@/hooks/use_app_theme";
import { useServerAction } from "@/hooks/use_server_action";
import { AppTheme } from "@/types/theme";
import { CurrentUser } from "@/types/users";
import { sendAnalyticsEvent } from "@/utils/analytics";

import usePostLoginActionHandler from "./hooks/usePostLoginActionHandler";
Expand All @@ -34,6 +35,7 @@ type SignInModalType = {
isOpen: boolean;
onClose: (isOpen: boolean) => void;
className?: string;
onSuccess?: (authenticatedUser: CurrentUser) => void | Promise<void>;
};

export const SignupForm: FC<{
Expand All @@ -42,12 +44,14 @@ export const SignupForm: FC<{
inviteToken?: string;
withNewsletterOptin?: boolean;
redirectLocation?: string;
onSuccess?: (authenticatedUser: CurrentUser) => void | Promise<void>;
}> = ({
addToProject,
email,
inviteToken,
withNewsletterOptin,
redirectLocation,
onSuccess,
}) => {
const t = useTranslations();
const { themeChoice } = useAppTheme();
Expand Down Expand Up @@ -116,7 +120,15 @@ export const SignupForm: FC<{
data: { email: watch("email"), username: watch("username") },
});
}
handlePostLoginAction(response?.postLoginAction);
try {
if (response?.is_active && response.user) {
await onSuccess?.(response.user);
}
} catch (error) {
console.error("Signup onSuccess callback failed", error);
} finally {
handlePostLoginAction(response?.postLoginAction);
}
}

return response;
Expand Down Expand Up @@ -230,7 +242,11 @@ export const AccountInactive: FC<AccountInactiveModalProps> = ({
);
};

export const SignUpModal: FC<SignInModalType> = ({ isOpen, onClose }) => {
export const SignUpModal: FC<SignInModalType> = ({
isOpen,
onClose,
onSuccess,
}) => {
const t = useTranslations();
const { setCurrentModal } = useModal();

Expand All @@ -246,14 +262,16 @@ export const SignUpModal: FC<SignInModalType> = ({ isOpen, onClose }) => {
<Button
variant="link"
size="md"
onClick={() => setCurrentModal({ type: "signin" })}
onClick={() =>
setCurrentModal({ type: "signin", data: { onSuccess } })
}
>
{t("logIn")}
</Button>
</div>
<div className="flex flex-col text-gray-900 dark:text-gray-900-dark sm:flex-row">
<div className="border-gray-300 dark:border-gray-300-dark sm:w-80 sm:border-r sm:pr-4">
<SignupForm withNewsletterOptin={true} />
<SignupForm withNewsletterOptin={true} onSuccess={onSuccess} />
</div>
<div className="flex flex-col gap-2 sm:w-80 sm:pl-4">
<ul className="hidden leading-tight sm:block">
Expand Down
12 changes: 10 additions & 2 deletions front_end/src/components/global_modals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,18 @@ const GlobalModals: FC = () => {
return (
<>
{isModal(currentModal, "signin") && (
<SignInModal isOpen onClose={onClose} />
<SignInModal
isOpen
onClose={onClose}
onSuccess={currentModal.data?.onSuccess}
/>
)}
{isModal(currentModal, "signup") && (
<SignUpModal isOpen onClose={onClose} />
<SignUpModal
isOpen
onClose={onClose}
onSuccess={currentModal.data?.onSuccess}
/>
)}
{isModal(currentModal, "signupSuccess") && (
<SignUpModalSuccess
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import cn from "@/utils/core/cn";
import { formatDate } from "@/utils/formatters/date";

import HighlightedCommentCard from "./highlighted_comment_card";
import SubscribeTopCommentsCta from "./subscribe_top_comments_cta";
import WeekSelector from "./week_selector";

type Props = {
Expand Down Expand Up @@ -151,7 +152,7 @@ const CommentsOfWeekContent: FC<Props> = ({
onWeekChange={onWeekChange}
/>
</div>

<SubscribeTopCommentsCta />
<div className="relative mb-8">
<p className="mb-5 text-sm leading-relaxed text-gray-700 dark:text-gray-700-dark">
{t("topCommentsDescription")}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
"use client";

import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useTranslations } from "next-intl";
import { FC, useCallback, useEffect, useMemo, useState } from "react";
import toast from "react-hot-toast";

import { updateProfileAction } from "@/app/(main)/accounts/profile/actions";
import Button from "@/components/ui/button";
import { useAuth } from "@/contexts/auth_context";
import { useModal } from "@/contexts/modal_context";
import { SubscriptionEmailType } from "@/types/notifications";
import { CurrentUser } from "@/types/users";
import cn from "@/utils/core/cn";
import { safeLocalStorage } from "@/utils/core/storage";

import { useTopCommentsCtaDismissed } from "../hooks/use_top_comments_cta_dismissed";

const SUBSCRIBE_INTENT_KEY = "weeklyTopCommentsSubscribeIntent:v1";

const SubscribeTopCommentsCta: FC = () => {
const t = useTranslations();
const { user, setUser } = useAuth();
const { setCurrentModal } = useModal();
const { dismissed, dismiss, ready } = useTopCommentsCtaDismissed();
const [isSubscribing, setIsSubscribing] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);

const isSubscribed = useMemo(() => {
if (!user) {
return false;
}
return !user.unsubscribed_mailing_tags.includes(
SubscriptionEmailType.weekly_top_comments
);
}, [user]);

const subscribe = useCallback(
async (authenticatedUser: CurrentUser) => {
if (isSubscribing) {
return;
}

const nextUnsubscribedTags =
authenticatedUser.unsubscribed_mailing_tags.filter(
(tag) => tag !== SubscriptionEmailType.weekly_top_comments
);
const optimisticUser: CurrentUser = {
...authenticatedUser,
unsubscribed_mailing_tags: nextUnsubscribedTags,
};

setIsSubscribing(true);
setUser(optimisticUser);

try {
await updateProfileAction({
unsubscribed_mailing_tags: nextUnsubscribedTags,
});
setIsSuccess(true);
safeLocalStorage.removeItem(SUBSCRIBE_INTENT_KEY);
} catch {
setUser(authenticatedUser);
toast.error(t("weeklyTopCommentsSubscribeError"));
} finally {
Comment thread
cemreinanc marked this conversation as resolved.
setIsSubscribing(false);
}
},
[isSubscribing, setUser, t]
);

useEffect(() => {
if (
!user ||
isSubscribed ||
safeLocalStorage.getItem(SUBSCRIBE_INTENT_KEY) !== "1"
) {
return;
}

safeLocalStorage.removeItem(SUBSCRIBE_INTENT_KEY);
void subscribe(user);
}, [user, isSubscribed, subscribe]);

const handleSubscribeClick = useCallback(() => {
if (!user) {
safeLocalStorage.setItem(SUBSCRIBE_INTENT_KEY, "1");
setCurrentModal({
type: "signin",
data: {
onSuccess: async (authenticatedUser) => {
await subscribe(authenticatedUser);
},
},
});
return;
}

void subscribe(user);
}, [user, setCurrentModal, subscribe]);

if (!ready || dismissed) {
return null;
}

if (!isSuccess && !isSubscribing && user && isSubscribed) {
return null;
}

return (
<div
className={cn(
"group relative mb-6 flex flex-col items-start gap-2.5 overflow-hidden rounded-[6px] border px-5 py-4 pr-10 transition-colors duration-300",
isSuccess
? "border-olive-500 bg-olive-400 dark:border-olive-500-dark dark:bg-olive-400-dark"
: "border-blue-700 bg-[linear-gradient(-86.6deg,rgba(102,165,102,0.5)_0%,rgba(169,192,214,0.5)_100%),linear-gradient(90deg,#ffffff_0%,#ffffff_100%)] dark:border-blue-700-dark dark:bg-[linear-gradient(-86.6deg,rgba(102,165,102,0.5)_0%,rgba(99,135,168,0.5)_100%),linear-gradient(90deg,#262f38_0%,#262f38_100%)]"
)}
>
{!isSuccess && (
<div className="pointer-events-none absolute inset-0 bg-[linear-gradient(-86.6deg,rgba(102,165,102,0.6)_0%,rgba(169,192,214,0.6)_100%),linear-gradient(90deg,#ffffff_0%,#ffffff_100%)] opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:bg-[linear-gradient(-86.6deg,rgba(102,165,102,0.6)_0%,rgba(99,135,168,0.6)_100%),linear-gradient(90deg,#262f38_0%,#262f38_100%)]" />
)}
<div
className={cn(
"pointer-events-none absolute inset-0 bg-olive-400 opacity-0 transition-opacity duration-300 dark:bg-olive-400-dark",
isSuccess && "opacity-100"
)}
/>
<button
type="button"
onClick={dismiss}
aria-label={t("close")}
className="absolute right-4 top-4 z-10 shrink-0 text-lg leading-none text-blue-900/40 transition-colors duration-300 hover:text-blue-900/60 dark:text-blue-900-dark/40 dark:hover:text-blue-900-dark/60"
>
<FontAwesomeIcon icon={faXmark} />
</button>
<div className="relative z-10 flex w-full flex-col items-start gap-2.5">
<p className="text-base font-medium leading-6 text-blue-900 dark:text-blue-900-dark">
{isSuccess
? t("weeklyTopCommentsSubscribeSuccessInline")
: t("weeklyTopCommentsSubscribeCta")}
</p>
{!isSuccess && (
<Button
size="sm"
variant="secondary"
onClick={handleSubscribeClick}
disabled={isSubscribing}
className="rounded-full border-gray-900 bg-gray-0 px-3 py-2 text-gray-900 transition-colors duration-300 hover:bg-gray-200 active:bg-gray-300 dark:border-gray-900-dark dark:bg-gray-0-dark dark:text-gray-900-dark dark:hover:bg-gray-200-dark dark:active:bg-gray-300-dark"
>
{t("subscribe")}
</Button>
)}
</div>
</div>
);
};

export default SubscribeTopCommentsCta;
Loading
Loading