From 1a8b2eab05ea638d70d4f59e7863d37acd752ca6 Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" <50504183+MTG2000@users.noreply.github.com> Date: Mon, 18 Dec 2023 13:05:42 +0400 Subject: [PATCH 1/4] update (db): handle user relations in db in case of user deletion --- api/functions/graphql/nexus-typegen.ts | 10 +- api/functions/graphql/schema.graphql | 8 +- api/functions/graphql/types/post.js | 8 +- .../migration.sql | 83 +++++++++ prisma/schema.prisma | 38 ++-- .../PostCard/BountyCard/BountyCard.tsx | 163 +++++++++++------- .../PostCard/QuestionCard/QuestionCard.tsx | 117 +++++++------ .../PostCard/StoryCard/StoryCard.tsx | 16 +- .../Components/TrendingCard/TrendingCard.tsx | 87 ++++++---- .../AuthorCard/AuthorCard.stories.tsx | 29 ++-- .../AuthorCard/AuthorCard.stories.tsx | 29 ++-- .../BountyPageContent/BountyPageContent.tsx | 127 ++++++++------ .../QuestionPageContent.tsx | 14 +- .../StoryPageContent/StoryPageContent.tsx | 16 +- .../pages/PostDetailsPage/PostDetailsPage.tsx | 4 +- src/graphql/index.tsx | 16 +- 16 files changed, 468 insertions(+), 297 deletions(-) create mode 100644 prisma/migrations/20231218084605_set_delete_cascade_rules_for_user/migration.sql diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index 15aa565b..a1125cfe 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -393,7 +393,7 @@ export interface NexusGenObjects { projects: NexusGenRootTypes['ProjectInTournament'][]; // [ProjectInTournament!]! } PostComment: { // root type - author: NexusGenRootTypes['User']; // User! + author?: NexusGenRootTypes['User'] | null; // User body: string; // String! created_at: NexusGenScalars['Date']; // Date! id: number; // Int! @@ -653,7 +653,7 @@ export interface NexusGenFieldTypes { Bounty: { // field return type applicants_count: number; // Int! applications: NexusGenRootTypes['BountyApplication'][]; // [BountyApplication!]! - author: NexusGenRootTypes['User']; // User! + author: NexusGenRootTypes['User'] | null; // User body: string; // String! cover_image: string | null; // String createdAt: NexusGenScalars['Date']; // Date! @@ -784,7 +784,7 @@ export interface NexusGenFieldTypes { projects: NexusGenRootTypes['ProjectInTournament'][]; // [ProjectInTournament!]! } PostComment: { // field return type - author: NexusGenRootTypes['User']; // User! + author: NexusGenRootTypes['User'] | null; // User body: string; // String! created_at: NexusGenScalars['Date']; // Date! id: number; // Int! @@ -878,7 +878,7 @@ export interface NexusGenFieldTypes { usersByNostrKeys: NexusGenRootTypes['NostrKeyWithUser'][]; // [NostrKeyWithUser!]! } Question: { // field return type - author: NexusGenRootTypes['User']; // User! + author: NexusGenRootTypes['User'] | null; // User body: string; // String! createdAt: NexusGenScalars['Date']; // Date! excerpt: string; // String! @@ -892,7 +892,7 @@ export interface NexusGenFieldTypes { votes_count: number; // Int! } Story: { // field return type - author: NexusGenRootTypes['User']; // User! + author: NexusGenRootTypes['User'] | null; // User body: string; // String! comments: NexusGenRootTypes['PostComment'][]; // [PostComment!]! comments_count: number; // Int! diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index 11133552..da2b1cf4 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -54,7 +54,7 @@ interface BaseUser { type Bounty implements PostBase { applicants_count: Int! applications: [BountyApplication!]! - author: User! + author: User body: String! cover_image: String createdAt: Date! @@ -319,7 +319,7 @@ interface PostBase { } type PostComment { - author: User! + author: User body: String! created_at: Date! id: Int! @@ -451,7 +451,7 @@ type Query { } type Question implements PostBase { - author: User! + author: User body: String! createdAt: Date! excerpt: String! @@ -479,7 +479,7 @@ enum RoleLevelEnum { } type Story implements PostBase { - author: User! + author: User body: String! comments: [PostComment!]! comments_count: Int! diff --git a/api/functions/graphql/types/post.js b/api/functions/graphql/types/post.js index 66eb614e..77c01fb0 100644 --- a/api/functions/graphql/types/post.js +++ b/api/functions/graphql/types/post.js @@ -188,7 +188,7 @@ const Story = objectType({ return post._count.comments; }, }); - t.nonNull.field("author", { + t.field("author", { type: "User", resolve: (parent) => { return ( @@ -251,7 +251,7 @@ const Bounty = objectType({ t.nonNull.list.nonNull.field("applications", { type: "BountyApplication", }); - t.nonNull.field("author", { + t.field("author", { type: "User", resolve: (parent) => { return prisma.bounty.findUnique({ where: { id: parent.id } }).user(); @@ -287,7 +287,7 @@ const Question = objectType({ // } // }); - t.nonNull.field("author", { + t.field("author", { type: "User", resolve: (parent) => { return prisma.question.findUnique({ where: { id: parent.id } }).user(); @@ -302,7 +302,7 @@ const PostComment = objectType({ t.nonNull.int("id"); t.nonNull.date("created_at"); t.nonNull.string("body"); - t.nonNull.field("author", { + t.field("author", { type: "User", }); t.int("parentId"); diff --git a/prisma/migrations/20231218084605_set_delete_cascade_rules_for_user/migration.sql b/prisma/migrations/20231218084605_set_delete_cascade_rules_for_user/migration.sql new file mode 100644 index 00000000..af54b40f --- /dev/null +++ b/prisma/migrations/20231218084605_set_delete_cascade_rules_for_user/migration.sql @@ -0,0 +1,83 @@ +-- DropForeignKey +ALTER TABLE "FoundersClubMember" DROP CONSTRAINT "FoundersClubMember_user_id_fkey"; + +-- DropForeignKey +ALTER TABLE "NostrBadgeRequest" DROP CONSTRAINT "NostrBadgeRequest_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "ProjectMember" DROP CONSTRAINT "ProjectMember_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "TournamentJudgingRoundJudge" DROP CONSTRAINT "TournamentJudgingRoundJudge_judge_id_fkey"; + +-- DropForeignKey +ALTER TABLE "TournamentJudgingRoundJudgeScore" DROP CONSTRAINT "TournamentJudgingRoundJudgeScore_judge_id_fkey"; + +-- DropForeignKey +ALTER TABLE "TournamentOrganizer" DROP CONSTRAINT "TournamentOrganizer_user_id_fkey"; + +-- DropForeignKey +ALTER TABLE "TournamentParticipant" DROP CONSTRAINT "TournamentParticipant_user_id_fkey"; + +-- DropForeignKey +ALTER TABLE "UserAction" DROP CONSTRAINT "UserAction_user_id_fkey"; + +-- DropForeignKey +ALTER TABLE "UserBadge" DROP CONSTRAINT "UserBadge_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "UserBadgeProgress" DROP CONSTRAINT "UserBadgeProgress_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "UserEmail" DROP CONSTRAINT "UserEmail_user_id_fkey"; + +-- DropForeignKey +ALTER TABLE "UserKey" DROP CONSTRAINT "UserKey_user_id_fkey"; + +-- DropForeignKey +ALTER TABLE "UserNostrKey" DROP CONSTRAINT "UserNostrKey_user_id_fkey"; + +-- DropForeignKey +ALTER TABLE "UsersOnWorkRoles" DROP CONSTRAINT "UsersOnWorkRoles_userId_fkey"; + +-- AddForeignKey +ALTER TABLE "UserKey" ADD CONSTRAINT "UserKey_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserNostrKey" ADD CONSTRAINT "UserNostrKey_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserEmail" ADD CONSTRAINT "UserEmail_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UsersOnWorkRoles" ADD CONSTRAINT "UsersOnWorkRoles_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TournamentParticipant" ADD CONSTRAINT "TournamentParticipant_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "FoundersClubMember" ADD CONSTRAINT "FoundersClubMember_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserAction" ADD CONSTRAINT "UserAction_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserBadgeProgress" ADD CONSTRAINT "UserBadgeProgress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "UserBadge" ADD CONSTRAINT "UserBadge_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "NostrBadgeRequest" ADD CONSTRAINT "NostrBadgeRequest_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TournamentJudgingRoundJudge" ADD CONSTRAINT "TournamentJudgingRoundJudge_judge_id_fkey" FOREIGN KEY ("judge_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TournamentJudgingRoundJudgeScore" ADD CONSTRAINT "TournamentJudgingRoundJudgeScore_judge_id_fkey" FOREIGN KEY ("judge_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TournamentOrganizer" ADD CONSTRAINT "TournamentOrganizer_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3b44ccb9..152a3537 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -48,7 +48,7 @@ model Vote { paid Boolean @default(false) user_id Int? - user User? @relation(fields: [user_id], references: [id]) + user User? @relation(fields: [user_id], references: [id], onDelete: SetNull) } // ----------------- @@ -109,7 +109,7 @@ model UserKey { name String @default("My new wallet key") createdAt DateTime @default(now()) - user User? @relation(fields: [user_id], references: [id]) + user User? @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id Int? } @@ -120,7 +120,7 @@ model UserNostrKey { is_primary Boolean @default(false) is_default_generated_key Boolean @default(false) - user User? @relation(fields: [user_id], references: [id]) + user User? @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id Int? } @@ -129,7 +129,7 @@ model UserEmail { email String @unique createdAt DateTime @default(now()) - user User? @relation(fields: [user_id], references: [id]) + user User? @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id Int? } @@ -141,7 +141,7 @@ model Otp { } model UsersOnWorkRoles { - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int role WorkRole @relation(fields: [roleId], references: [id]) roleId Int @@ -243,7 +243,7 @@ model ProjectRecruitRoles { model ProjectMember { project Project @relation(fields: [projectId], references: [id]) projectId Int - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int role String // Admin | Maker | (new_roles_later) @@ -284,7 +284,7 @@ model Story { tags Tag[] - user User? @relation(fields: [user_id], references: [id]) + user User? @relation(fields: [user_id], references: [id], onDelete: SetNull) user_id Int? comments PostComment[] @relation("StoryComment") @@ -305,7 +305,7 @@ model Question { tags Tag[] - user User? @relation(fields: [user_id], references: [id]) + user User? @relation(fields: [user_id], references: [id], onDelete: SetNull) user_id Int? comments PostComment[] @relation("QuestionComment") @@ -322,7 +322,7 @@ model PostComment { parent_comment_id Int? parent_comment PostComment? @relation("PostComment_Replies", fields: [parent_comment_id], references: [id]) - user User? @relation(fields: [user_id], references: [id]) + user User? @relation(fields: [user_id], references: [id], onDelete: SetNull) user_id Int? story Story? @relation("StoryComment", fields: [story_id], references: [id]) @@ -363,7 +363,7 @@ model Donation { preimage String? paid Boolean @default(false) - donor User? @relation(fields: [donor_id], references: [id]) + donor User? @relation(fields: [donor_id], references: [id], onDelete: SetNull) donor_id Int? } @@ -492,7 +492,7 @@ model TournamentEvent { model TournamentParticipant { tournament Tournament @relation(fields: [tournament_id], references: [id]) tournament_id Int - user User @relation(fields: [user_id], references: [id]) + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id Int createdAt DateTime @default(now()) @@ -536,7 +536,7 @@ model FoundersClubInvitation { model FoundersClubMember { id Int @id @default(autoincrement()) email String - user User @relation(fields: [user_id], references: [id]) + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id Int @unique // relation scalar field (used in the `@relation` attribute above) } @@ -561,7 +561,7 @@ model UserAction { failReason String? - user User @relation(fields: [user_id], references: [id]) + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id Int createdAt DateTime @default(now()) @@ -596,7 +596,7 @@ model UserBadgeProgress { badge Badge @relation(fields: [badgeId], references: [id]) badgeId Int - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int progress Int @default(0) @@ -608,7 +608,7 @@ model UserBadge { badge Badge @relation(fields: [badgeId], references: [id]) badgeId Int - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int badgeAwardNostrEventId String? @@ -624,7 +624,7 @@ model NostrBadgeRequest { badge Badge @relation(fields: [badgeId], references: [id]) badgeId Int - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int publicKeyToAward String @@ -668,7 +668,7 @@ model TournamentJudgingRoundJudge { round TournamentJudgingRound @relation(fields: [round_id], references: [id]) judge_id Int - judge User @relation(fields: [judge_id], references: [id]) + judge User @relation(fields: [judge_id], references: [id], onDelete: Cascade) @@unique([round_id, judge_id]) } @@ -680,7 +680,7 @@ model TournamentJudgingRoundJudgeScore { round TournamentJudgingRound @relation(fields: [round_id], references: [id]) judge_id Int - judge User @relation(fields: [judge_id], references: [id]) + judge User @relation(fields: [judge_id], references: [id], onDelete: Cascade) project_id Int project Project @relation(fields: [project_id], references: [id]) @@ -697,7 +697,7 @@ model TournamentOrganizer { tournament Tournament @relation(fields: [tournament_id], references: [id]) user_id Int - user User @relation(fields: [user_id], references: [id]) + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) @@id([tournament_id, user_id]) } diff --git a/src/features/Posts/Components/PostCard/BountyCard/BountyCard.tsx b/src/features/Posts/Components/PostCard/BountyCard/BountyCard.tsx index c5c560be..bac45051 100644 --- a/src/features/Posts/Components/PostCard/BountyCard/BountyCard.tsx +++ b/src/features/Posts/Components/PostCard/BountyCard/BountyCard.tsx @@ -1,78 +1,107 @@ -import { Bounty } from "src/features/Posts/types" -import Header from "../Header/Header" -import { FiUsers } from "react-icons/fi" -import Badge from "src/Components/Badge/Badge" -import Button from "src/Components/Button/Button" -import { Link } from "react-router-dom" -import VoteButton from "src/Components/VoteButton/VoteButton" -import { Author, Tag } from "src/graphql" +import { Bounty } from "src/features/Posts/types"; +import Header from "../Header/Header"; +import { FiUsers } from "react-icons/fi"; +import Badge from "src/Components/Badge/Badge"; +import Button from "src/Components/Button/Button"; +import { Link } from "react-router-dom"; +import VoteButton from "src/Components/VoteButton/VoteButton"; +import { Author, Tag } from "src/graphql"; -export type BountyCardType = Pick & { - tags: Array> - author: Pick + tags: Array>; + author: Pick | null; }; interface Props { - bounty: BountyCardType + bounty: BountyCardType; } export default function BountyCard({ bounty }: Props) { + const handleApply = () => {}; - const handleApply = () => { - - } - - return ( -
- {bounty.cover_image && } -
-
-
-
- -

- Bounty {bounty.title} -

- - -
- -
-
- Reward: - {bounty.reward_amount} sats -
-

{bounty.excerpt}

- -
- {bounty.tags.map(tag => - {tag.title} - )} -
- -
-
- -
- {bounty.applicants_count} Applicants -
-
+ return ( +
+ {bounty.cover_image && ( + + )} +
+ {bounty.author && ( +
+ )} +
+
+ +

+ + + Bounty + {" "} + {bounty.title} + +

+ +
+ +
+
+ Reward: + + {bounty.reward_amount} sats + +
+

{bounty.excerpt}

+
+ {bounty.tags.map((tag) => ( + + {tag.title} + + ))} +
- -
+
+
+ +
+ {" "} + + {bounty.applicants_count} Applicants + +
- ) + + +
+
+ ); } diff --git a/src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.tsx b/src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.tsx index 6102ed22..74654470 100644 --- a/src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.tsx +++ b/src/features/Posts/Components/PostCard/QuestionCard/QuestionCard.tsx @@ -1,77 +1,82 @@ -import VotesCount from "src/Components/VotesCount/VotesCount" -import { Question } from "src/features/Posts/types" -import Header from "../Header/Header" -import { FiUsers } from "react-icons/fi" -import Badge from "src/Components/Badge/Badge" -import { Link } from "react-router-dom" -import VoteButton from "src/Components/VoteButton/VoteButton" -import { Author, Tag } from "src/graphql" +import VotesCount from "src/Components/VotesCount/VotesCount"; +import { Question } from "src/features/Posts/types"; +import Header from "../Header/Header"; +import { FiUsers } from "react-icons/fi"; +import Badge from "src/Components/Badge/Badge"; +import { Link } from "react-router-dom"; +import VoteButton from "src/Components/VoteButton/VoteButton"; +import { Author, Tag } from "src/graphql"; -export type QuestionCardType = Pick & { - // comments: Array> - tags: Array> - author: Pick + // comments: Array> + tags: Array>; + author: Pick | null; }; interface Props { - question: QuestionCardType + question: QuestionCardType; } export default function QuestionCard({ question }: Props) { - return ( -
- {/* */} -
-
-
- -

{question.title}

- -
-

{question.excerpt}

+ return ( +
+ {/* */} +
+ {question.author && ( +
+ )} +
+ +

{question.title}

+ +
+

{question.excerpt}

-
- - Help - - {question.tags.map(tag => - {tag.title} - )} -
+
+ + Help + + {question.tags.map((tag) => ( + + {tag.title} + + ))} +
-
-
- - {/*
+
+
+ + {/*
{question.answers_count} Answers
*/} -
+
-
-
- {/* {question.comments.slice(0, 2).map(comment =>
+
+
+ {/* {question.comments.slice(0, 2).map(comment =>

{trimText(comment.body, 80)}

)} */} -
+
- {/*
+ {/*
See all {question.answers_count} comments
*/} -
-
- ) +
+
+ ); } diff --git a/src/features/Posts/Components/PostCard/StoryCard/StoryCard.tsx b/src/features/Posts/Components/PostCard/StoryCard/StoryCard.tsx index f48ebbb3..564e3540 100644 --- a/src/features/Posts/Components/PostCard/StoryCard/StoryCard.tsx +++ b/src/features/Posts/Components/PostCard/StoryCard/StoryCard.tsx @@ -30,7 +30,7 @@ export type StoryCardType = Pick< | "nostr_event_id" > & { tags: Array>; - author: Pick; + author: Pick | null; }; interface Props { @@ -58,16 +58,18 @@ export default function StoryCard({ type: "story", id: story.id, title: story.title, - username: story.author.name, + username: story.author?.name, }); return (
- + {story.author && ( + + )} {story.cover_image && ( -

Trending

-
    - { - trendingPosts.loading ? - Array(4).fill(0).map((_, idx) =>
  • - -

    -

    -
  • - ) - : - trendingPosts.data?.getTrendingPosts.map(post => { - return -
  • - -

    {post.title}

    -
  • - - } - )} -
-
- ) + return ( +
+

Trending

+
    + {trendingPosts.loading + ? Array(4) + .fill(0) + .map((_, idx) => ( +
  • + +

    + + +

    +
  • + )) + : trendingPosts.data?.getTrendingPosts.map((post) => { + return ( + +
  • + {post.author && ( + + )} +

    {post.title}

    +
  • + + ); + })} +
+
+ ); } diff --git a/src/features/Posts/pages/NostrPostDetailsPage/Components/AuthorCard/AuthorCard.stories.tsx b/src/features/Posts/pages/NostrPostDetailsPage/Components/AuthorCard/AuthorCard.stories.tsx index 34a5607d..cbce4a8d 100644 --- a/src/features/Posts/pages/NostrPostDetailsPage/Components/AuthorCard/AuthorCard.stories.tsx +++ b/src/features/Posts/pages/NostrPostDetailsPage/Components/AuthorCard/AuthorCard.stories.tsx @@ -1,22 +1,23 @@ -import { ComponentStory, ComponentMeta } from '@storybook/react'; -import { MOCK_DATA } from 'src/mocks/data'; +import { ComponentStory, ComponentMeta } from "@storybook/react"; +import { MOCK_DATA } from "src/mocks/data"; -import AuthorCard from './AuthorCard'; +import AuthorCard from "./AuthorCard"; export default { - title: 'Posts/Post Details Page/Components/AuthorCard', - component: AuthorCard, - argTypes: { - backgroundColor: { control: 'color' }, - }, + title: "Posts/Post Details Page/Components/AuthorCard", + component: AuthorCard, + argTypes: { + backgroundColor: { control: "color" }, + }, } as ComponentMeta; - -const Template: ComponentStory = (args) =>
+const Template: ComponentStory = (args) => ( +
+ +
+); export const Default = Template.bind({}); Default.args = { - author: MOCK_DATA['posts'].stories[0].author -} - - + author: MOCK_DATA["posts"].stories[0].author!, +}; diff --git a/src/features/Posts/pages/PostDetailsPage/Components/AuthorCard/AuthorCard.stories.tsx b/src/features/Posts/pages/PostDetailsPage/Components/AuthorCard/AuthorCard.stories.tsx index 34a5607d..cbce4a8d 100644 --- a/src/features/Posts/pages/PostDetailsPage/Components/AuthorCard/AuthorCard.stories.tsx +++ b/src/features/Posts/pages/PostDetailsPage/Components/AuthorCard/AuthorCard.stories.tsx @@ -1,22 +1,23 @@ -import { ComponentStory, ComponentMeta } from '@storybook/react'; -import { MOCK_DATA } from 'src/mocks/data'; +import { ComponentStory, ComponentMeta } from "@storybook/react"; +import { MOCK_DATA } from "src/mocks/data"; -import AuthorCard from './AuthorCard'; +import AuthorCard from "./AuthorCard"; export default { - title: 'Posts/Post Details Page/Components/AuthorCard', - component: AuthorCard, - argTypes: { - backgroundColor: { control: 'color' }, - }, + title: "Posts/Post Details Page/Components/AuthorCard", + component: AuthorCard, + argTypes: { + backgroundColor: { control: "color" }, + }, } as ComponentMeta; - -const Template: ComponentStory = (args) =>
+const Template: ComponentStory = (args) => ( +
+ +
+); export const Default = Template.bind({}); Default.args = { - author: MOCK_DATA['posts'].stories[0].author -} - - + author: MOCK_DATA["posts"].stories[0].author!, +}; diff --git a/src/features/Posts/pages/PostDetailsPage/Components/BountyPageContent/BountyPageContent.tsx b/src/features/Posts/pages/PostDetailsPage/Components/BountyPageContent/BountyPageContent.tsx index ff3ce98d..1aa5aba4 100644 --- a/src/features/Posts/pages/PostDetailsPage/Components/BountyPageContent/BountyPageContent.tsx +++ b/src/features/Posts/pages/PostDetailsPage/Components/BountyPageContent/BountyPageContent.tsx @@ -1,7 +1,7 @@ -import Header from "src/features/Posts/Components/PostCard/Header/Header" -import { Bounty, } from "src/features/Posts/types" -import { marked } from 'marked'; -import styles from '../PageContent/styles.module.scss' +import Header from "src/features/Posts/Components/PostCard/Header/Header"; +import { Bounty } from "src/features/Posts/types"; +import { marked } from "marked"; +import styles from "../PageContent/styles.module.scss"; import Badge from "src/Components/Badge/Badge"; import { BiComment } from "react-icons/bi"; import VotesCount from "src/Components/VotesCount/VotesCount"; @@ -12,53 +12,84 @@ import VoteButton from "src/Components/VoteButton/VoteButton"; import { RiFlashlightLine } from "react-icons/ri"; import { numberFormatter } from "src/utils/helperFunctions"; - interface Props { - bounty: Bounty + bounty: Bounty; } export default function BountyPageContent({ bounty }: Props) { - return ( -
- - {/* Header */} -
-
-

{bounty.title} Bounty

-
- Reward: - {bounty.reward_amount} sats -
-
-
- {numberFormatter(bounty.votes_count)} votes -
-
- 32 Comments -
-
-
- - - -
-
-
-
- {/* Body */} -
- {bounty.tags.map(tag => - {tag.title} - )} -
- {/* Applicants */} - + return ( +
+ {/* Header */} +
+ {bounty.author && ( +
+ )} +

+ {bounty.title}{" "} + + Bounty + +

+
+ Reward: + + {bounty.reward_amount} sats + +
+
+
+ {" "} + + {numberFormatter(bounty.votes_count)} votes + +
+
+ {" "} + 32 Comments +
+
+
+ + +
- ) +
+
+ {/* Body */} +
+ {bounty.tags.map((tag) => ( + + {tag.title} + + ))} +
+ {/* Applicants */} + +
+ ); } diff --git a/src/features/Posts/pages/PostDetailsPage/Components/QuestionPageContent/QuestionPageContent.tsx b/src/features/Posts/pages/PostDetailsPage/Components/QuestionPageContent/QuestionPageContent.tsx index e3d96ba8..0c38f74c 100644 --- a/src/features/Posts/pages/PostDetailsPage/Components/QuestionPageContent/QuestionPageContent.tsx +++ b/src/features/Posts/pages/PostDetailsPage/Components/QuestionPageContent/QuestionPageContent.tsx @@ -22,12 +22,14 @@ export default function QuestionPageContent({ question }: Props) { className="bg-white p-32 border-2 border-gray-200 rounded-16" >
-
+ {question.author && ( +
+ )}

{question.title}

{question.tags.map((tag) => ( diff --git a/src/features/Posts/pages/PostDetailsPage/Components/StoryPageContent/StoryPageContent.tsx b/src/features/Posts/pages/PostDetailsPage/Components/StoryPageContent/StoryPageContent.tsx index dc225c80..6244b170 100644 --- a/src/features/Posts/pages/PostDetailsPage/Components/StoryPageContent/StoryPageContent.tsx +++ b/src/features/Posts/pages/PostDetailsPage/Components/StoryPageContent/StoryPageContent.tsx @@ -54,11 +54,13 @@ function StoryPageContent({ story }: Props) {
- + {story.author && ( + + )}
{story.nostr_event_id && ( - {curUser?.id === story.author.id && ( + {curUser?.id && curUser.id === story.author?.id && ( diff --git a/src/features/Posts/pages/PostDetailsPage/PostDetailsPage.tsx b/src/features/Posts/pages/PostDetailsPage/PostDetailsPage.tsx index d9a76597..aa8fc171 100644 --- a/src/features/Posts/pages/PostDetailsPage/PostDetailsPage.tsx +++ b/src/features/Posts/pages/PostDetailsPage/PostDetailsPage.tsx @@ -53,7 +53,7 @@ export default function PostDetailsPage(props: Props) { @@ -62,7 +62,7 @@ export default function PostDetailsPage(props: Props) {
- + {post.author && }
)} diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index cf78acea..6ab20294 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -77,7 +77,7 @@ export type Bounty = PostBase & { __typename?: 'Bounty'; applicants_count: Scalars['Int']; applications: Array; - author: User; + author: Maybe; body: Scalars['String']; cover_image: Maybe; createdAt: Scalars['Date']; @@ -466,7 +466,7 @@ export type PostBase = { export type PostComment = { __typename?: 'PostComment'; - author: User; + author: Maybe; body: Scalars['String']; created_at: Scalars['Date']; id: Scalars['Int']; @@ -777,7 +777,7 @@ export type QueryUsersByNostrKeysArgs = { export type Question = PostBase & { __typename?: 'Question'; - author: User; + author: Maybe; body: Scalars['String']; createdAt: Scalars['Date']; excerpt: Scalars['String']; @@ -806,7 +806,7 @@ export enum RoleLevelEnum { export type Story = PostBase & { __typename?: 'Story'; - author: User; + author: Maybe; body: Scalars['String']; comments: Array; comments_count: Scalars['Int']; @@ -1327,7 +1327,7 @@ export type RecentProjectsInTagQuery = { __typename?: 'Query', recentProjectsInT export type TrendingPostsQueryVariables = Exact<{ [key: string]: never; }>; -export type TrendingPostsQuery = { __typename?: 'Query', getTrendingPosts: Array<{ __typename?: 'Bounty', id: number, title: string, author: { __typename?: 'User', id: number, avatar: string } } | { __typename?: 'Question', id: number, title: string, author: { __typename?: 'User', id: number, avatar: string } } | { __typename?: 'Story', id: number, title: string, author: { __typename?: 'User', id: number, avatar: string } }> }; +export type TrendingPostsQuery = { __typename?: 'Query', getTrendingPosts: Array<{ __typename?: 'Bounty', id: number, title: string, author: { __typename?: 'User', id: number, avatar: string } | null } | { __typename?: 'Question', id: number, title: string, author: { __typename?: 'User', id: number, avatar: string } | null } | { __typename?: 'Story', id: number, title: string, author: { __typename?: 'User', id: number, avatar: string } | null }> }; export type GetAllTopicsQueryVariables = Exact<{ [key: string]: never; }>; @@ -1373,7 +1373,7 @@ export type FeedQueryVariables = Exact<{ }>; -export type FeedQuery = { __typename?: 'Query', getFeed: Array<{ __typename?: 'Bounty', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, comments_count: number, nostr_event_id: string | null, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, project: { __typename?: 'Project', id: number, title: string, thumbnail_image: string | null, hashtag: string } | null }> }; +export type FeedQuery = { __typename?: 'Query', getFeed: Array<{ __typename?: 'Bounty', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, comments_count: number, nostr_event_id: string | null, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, project: { __typename?: 'Project', id: number, title: string, thumbnail_image: string | null, hashtag: string } | null }> }; export type PostDetailsQueryVariables = Exact<{ id: Scalars['Int']; @@ -1381,7 +1381,7 @@ export type PostDetailsQueryVariables = Exact<{ }>; -export type PostDetailsQuery = { __typename?: 'Query', getPostById: { __typename?: 'Bounty', id: number, title: string, excerpt: string, createdAt: any, body: string, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, primary_nostr_key: string | null }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, votes: { __typename?: 'Votes', total: number, total_anonymous_votes: number, voters: Array<{ __typename?: 'Voter', amount_voted: number, user: { __typename?: 'User', id: number, name: string, avatar: string } }> }, applications: Array<{ __typename?: 'BountyApplication', id: number, date: string, workplan: string, author: { __typename?: 'User', id: number, name: string, avatar: string } }> } | { __typename?: 'Question', id: number, title: string, excerpt: string, createdAt: any, body: string, type: string, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, primary_nostr_key: string | null }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, votes: { __typename?: 'Votes', total: number, total_anonymous_votes: number, voters: Array<{ __typename?: 'Voter', amount_voted: number, user: { __typename?: 'User', id: number, name: string, avatar: string } }> } } | { __typename?: 'Story', id: number, title: string, excerpt: string, createdAt: any, body: string, type: string, cover_image: string | null, is_published: boolean | null, nostr_event_id: string | null, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, primary_nostr_key: string | null }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, votes: { __typename?: 'Votes', total: number, total_anonymous_votes: number, voters: Array<{ __typename?: 'Voter', amount_voted: number, user: { __typename?: 'User', id: number, name: string, avatar: string } }> }, project: { __typename?: 'Project', id: number, title: string, thumbnail_image: string | null, hashtag: string } | null } }; +export type PostDetailsQuery = { __typename?: 'Query', getPostById: { __typename?: 'Bounty', id: number, title: string, excerpt: string, createdAt: any, body: string, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, primary_nostr_key: string | null } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, votes: { __typename?: 'Votes', total: number, total_anonymous_votes: number, voters: Array<{ __typename?: 'Voter', amount_voted: number, user: { __typename?: 'User', id: number, name: string, avatar: string } }> }, applications: Array<{ __typename?: 'BountyApplication', id: number, date: string, workplan: string, author: { __typename?: 'User', id: number, name: string, avatar: string } }> } | { __typename?: 'Question', id: number, title: string, excerpt: string, createdAt: any, body: string, type: string, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, primary_nostr_key: string | null } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, votes: { __typename?: 'Votes', total: number, total_anonymous_votes: number, voters: Array<{ __typename?: 'Voter', amount_voted: number, user: { __typename?: 'User', id: number, name: string, avatar: string } }> } } | { __typename?: 'Story', id: number, title: string, excerpt: string, createdAt: any, body: string, type: string, cover_image: string | null, is_published: boolean | null, nostr_event_id: string | null, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, primary_nostr_key: string | null } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, votes: { __typename?: 'Votes', total: number, total_anonymous_votes: number, voters: Array<{ __typename?: 'Voter', amount_voted: number, user: { __typename?: 'User', id: number, name: string, avatar: string } }> }, project: { __typename?: 'Project', id: number, title: string, thumbnail_image: string | null, hashtag: string } | null } }; export type GetTagInfoQueryVariables = Exact<{ tag: InputMaybe; @@ -1398,7 +1398,7 @@ export type TagFeedQueryVariables = Exact<{ }>; -export type TagFeedQuery = { __typename?: 'Query', getFeed: Array<{ __typename?: 'Bounty', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, comments_count: number, nostr_event_id: string | null, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any }, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, project: { __typename?: 'Project', id: number, title: string, thumbnail_image: string | null, hashtag: string } | null }> }; +export type TagFeedQuery = { __typename?: 'Query', getFeed: Array<{ __typename?: 'Bounty', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, deadline: string, reward_amount: number, applicants_count: number, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Question', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }> } | { __typename?: 'Story', id: number, title: string, createdAt: any, excerpt: string, votes_count: number, type: string, cover_image: string | null, comments_count: number, nostr_event_id: string | null, author: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any } | null, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, project: { __typename?: 'Project', id: number, title: string, thumbnail_image: string | null, hashtag: string } | null }> }; export type UserBasicInfoFragment = { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, primary_nostr_key: string | null, role: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, discord: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, last_seen_notification_time: any }; From 0e260136ab834b0c3d0998dad27f5308fe5b2cf9 Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" <50504183+MTG2000@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:18:03 +0400 Subject: [PATCH 2/4] feat (newsletter): create UI of subscribe to newsletter page --- .../SubscribeToNewsletterModal.tsx | 98 +++++++++++ .../SubscribeToNewsletterPage.tsx | 159 ++++++++++++++++++ src/utils/routing/rootRouter.tsx | 11 +- src/utils/validation/misc.ts | 2 + 4 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 src/features/Shared/components/SubscribeToNewsletterModal/SubscribeToNewsletterModal.tsx create mode 100644 src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage.tsx diff --git a/src/features/Shared/components/SubscribeToNewsletterModal/SubscribeToNewsletterModal.tsx b/src/features/Shared/components/SubscribeToNewsletterModal/SubscribeToNewsletterModal.tsx new file mode 100644 index 00000000..a4ae6035 --- /dev/null +++ b/src/features/Shared/components/SubscribeToNewsletterModal/SubscribeToNewsletterModal.tsx @@ -0,0 +1,98 @@ +import { motion } from "framer-motion"; +import { + ModalCard, + modalCardVariants, +} from "src/Components/Modals/ModalsContainer/ModalsContainer"; +import { IoClose } from "react-icons/io5"; +import ChooseLoginMethods from "src/features/Auth/components/ChooseLoginMethods/ChooseLoginMethods"; +import { ComponentProps, useCallback, useState } from "react"; +import LoginWithEmail from "src/features/Auth/components/LoginWithEmail/LoginWithEmail"; +import LoginWithLightning from "src/features/Auth/components/LoginWithLightning/LoginWithLightning"; +import LoginWithNostr from "src/features/Auth/components/LoginWithNostr/LoginWithNostr"; +import { useMeQuery } from "src/graphql"; +import { trimText } from "src/utils/helperFunctions"; +import * as yup from "yup"; +import { emailSchema } from "src/utils/validation"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import Button from "src/Components/Button/Button"; + +interface Props extends ModalCard {} + +const schema = yup + .object({ + email: emailSchema.required(), + }) + .required(); + +type FormType = yup.InferType; + +export default function SubscribeToNewsletterModal({ + onClose, + direction, + ...props +}: Props) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + email: "", + }, + resolver: yupResolver(schema), + }); + + const onSubmit: SubmitHandler = (data) => { + // dispatch(action) + onClose?.(); + }; + + return ( + +
+ +

+ Subscribe to BOLT🔩FUN Newsletter +

+
+
+
+

+ A Newsletter for makers going from 0-1 and building in public! +

+ +
+ +
+ +
+ {errors.email && ( +

{errors.email.message}

+ )} + + +
+
+
+ ); +} diff --git a/src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage.tsx b/src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage.tsx new file mode 100644 index 00000000..db2f8fac --- /dev/null +++ b/src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage.tsx @@ -0,0 +1,159 @@ +import { motion } from "framer-motion"; +import { + ModalCard, + modalCardVariants, +} from "src/Components/Modals/ModalsContainer/ModalsContainer"; +import { IoClose } from "react-icons/io5"; +import ChooseLoginMethods from "src/features/Auth/components/ChooseLoginMethods/ChooseLoginMethods"; +import { ComponentProps, CSSProperties, useCallback, useState } from "react"; +import LoginWithEmail from "src/features/Auth/components/LoginWithEmail/LoginWithEmail"; +import LoginWithLightning from "src/features/Auth/components/LoginWithLightning/LoginWithLightning"; +import LoginWithNostr from "src/features/Auth/components/LoginWithNostr/LoginWithNostr"; +import { useMeQuery } from "src/graphql"; +import { randomItem, trimText } from "src/utils/helperFunctions"; +import * as yup from "yup"; +import { emailSchema } from "src/utils/validation"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import Button from "src/Components/Button/Button"; +import Card from "src/Components/Card/Card"; +import OgTags from "src/Components/OgTags/OgTags"; + +interface Props {} + +const schema = yup + .object({ + email: emailSchema.required(), + }) + .required(); + +type FormType = yup.InferType; + +const trianglesColors = [ + "#FDE68A", + "#7C3AED", + "#C4B5FD", + "#FBCFE8", + "#CBD5E1", + "#BFDBFE", + "#F1F5F9", +]; + +const NUM_TRIANGLES = 20; + +const generatedTriangles = Array(NUM_TRIANGLES) + .fill(0) + .map((_, i) => { + const color = randomItem(...trianglesColors); + const position = [Math.random(), Math.random()]; + const rotation = Math.random() * 360; + const scale = 1 + Math.random(); + const blur = Math.floor(Math.random() * 5); + const animationDuration = Math.floor(Math.random() * 10) + 10; + + return { + color, + position, + rotation, + scale, + blur, + animationDuration, + }; + }); + +export default function SubscribeToNewsletterPage({ ...props }: Props) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + defaultValues: { + email: "", + }, + resolver: yupResolver(schema), + }); + + const onSubmit: SubmitHandler = (data) => { + // dispatch(action) + }; + + return ( +
+ +
+ {generatedTriangles.map((triangle, i) => ( +
+ +
+ ))} +
+
+
+ +
+

+ Subscribe to BOLT🔩FUN Newsletter! 💌 +

+
+
+

+ A Newsletter for makers going from 0-1 and building in public! +

+

+ No spam, ever. You can unsubscribe at any time. & we will never + share your email address with anyone else. +

+ +
+ +
+ +
+ {errors.email && ( +

{errors.email.message}

+ )} + + +
+
+
+
+
+
+ ); +} diff --git a/src/utils/routing/rootRouter.tsx b/src/utils/routing/rootRouter.tsx index 22968d23..065df82f 100644 --- a/src/utils/routing/rootRouter.tsx +++ b/src/utils/routing/rootRouter.tsx @@ -21,6 +21,7 @@ import { feedPageLoader } from "src/features/Posts/pages/FeedPage/feedPage.loade import { Post_Type } from "src/graphql"; import { LandingPage } from "src/features/LandingPage/LandingPage"; import { EventsPage } from "src/features/Events/pages/EventsPage/EventsPage"; +import SubscribeToNewsletterPage from "src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage"; const HomePage = Loadable( React.lazy( @@ -237,6 +238,10 @@ const createRoutes = (queryClient: ApolloClient) => } /> + } + /> }> }> ) => } /> } /> - } /> + } + /> ) => } /> } /> + } diff --git a/src/utils/validation/misc.ts b/src/utils/validation/misc.ts index 519ef7b2..3724059a 100644 --- a/src/utils/validation/misc.ts +++ b/src/utils/validation/misc.ts @@ -23,6 +23,8 @@ export const tagSchema = yup.object().shape({ title: yup.string().trim().min(2).required(), }); +export const emailSchema = yup.string().trim().email(); + export const registerDebounceValidation = ( name: FieldPath, delay: number, From 6b2e3248d90cd77841f5b3c4d41fa801e0e74412 Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" <50504183+MTG2000@users.noreply.github.com> Date: Mon, 18 Dec 2023 16:21:04 +0400 Subject: [PATCH 3/4] feat (newsletter): create api to subscribe for newsletter, connect UI --- api/functions/graphql/nexus-typegen.ts | 5 + api/functions/graphql/schema.graphql | 1 + api/functions/graphql/types/users.js | 32 ++++++ api/services/queue-service/emails-service.js | 8 ++ .../SideNavigation/DefaultSideNavigation.tsx | 5 + .../SubscribeToNewsletterModal.tsx | 98 ------------------- .../SubscribeToNewsletterPage.tsx | 31 +++++- .../subscribeToNewsLetter.graphql | 5 + src/graphql/index.tsx | 46 +++++++++ .../helperFunctions/misc-helper-functions.tsx | 4 + src/utils/routing/rootRouter.tsx | 18 +++- src/utils/routing/routes.ts | 3 + 12 files changed, 151 insertions(+), 105 deletions(-) delete mode 100644 src/features/Shared/components/SubscribeToNewsletterModal/SubscribeToNewsletterModal.tsx create mode 100644 src/features/Shared/pages/SubscribeToNewsletterPage/subscribeToNewsLetter.graphql diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index a1125cfe..e9bc0952 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -756,6 +756,7 @@ export interface NexusGenFieldTypes { linkNostrKey: NexusGenRootTypes['User'] | null; // User registerInTournament: NexusGenRootTypes['User'] | null; // User setUserNostrKeyAsPrimary: NexusGenRootTypes['User'] | null; // User + subscribeToNewsletter: NexusGenRootTypes['User'] | null; // User unlinkNostrKey: NexusGenRootTypes['User'] | null; // User updateLastSeenNotificationTime: NexusGenRootTypes['User'] | null; // User updateProfileDetails: NexusGenRootTypes['User'] | null; // User @@ -1269,6 +1270,7 @@ export interface NexusGenFieldTypeNames { linkNostrKey: 'User' registerInTournament: 'User' setUserNostrKeyAsPrimary: 'User' + subscribeToNewsletter: 'User' unlinkNostrKey: 'User' updateLastSeenNotificationTime: 'User' updateProfileDetails: 'User' @@ -1702,6 +1704,9 @@ export interface NexusGenArgTypes { setUserNostrKeyAsPrimary: { // args key?: string | null; // String } + subscribeToNewsletter: { // args + email: string; // String! + } unlinkNostrKey: { // args key: string; // String! } diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index da2b1cf4..b52a40ff 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -257,6 +257,7 @@ type Mutation { linkNostrKey(event: NostrEventInput): User registerInTournament(data: RegisterInTournamentInput, tournament_id: Int!): User setUserNostrKeyAsPrimary(key: String): User + subscribeToNewsletter(email: String!): User unlinkNostrKey(key: String!): User updateLastSeenNotificationTime(timestamp: String!): User updateProfileDetails(data: ProfileDetailsInput): User diff --git a/api/functions/graphql/types/users.js b/api/functions/graphql/types/users.js index b3551c1b..71a8c8e6 100644 --- a/api/functions/graphql/types/users.js +++ b/api/functions/graphql/types/users.js @@ -23,6 +23,7 @@ const { validateEvent, } = require("../../../utils/nostr-tools"); const { queueService } = require("../../../services/queue-service"); +const { error } = require("console"); const BaseUser = interfaceType({ name: "BaseUser", @@ -767,6 +768,36 @@ const updateLastSeenNotificationTime = extendType({ }, }); +const subscribeToNewsletter = extendType({ + type: "Mutation", + definition(t) { + t.field("subscribeToNewsletter", { + type: "User", + args: { email: nonNull(stringArg()) }, + async resolve(_root, { email }, ctx) { + const user = await getUserById(ctx.user?.id); + + if (!user?.id) throw new Error("You have to login"); + + const userData = await prisma.user.findUnique({ + where: { + id: user.id, + }, + }); + + // TODO: store in the DB that the user is subscribed to the newsletter + await queueService.emailService.subscribeToNewsletter({ + email, + user_id: userData.id, + user_name: userData.name, + }); + + return userData; + }, + }); + }, +}); + const WalletKey = objectType({ name: "WalletKey", definition(t) { @@ -1003,4 +1034,5 @@ module.exports = { unlinkNostrKey, setUserNostrKeyAsPrimary, updateLastSeenNotificationTime, + subscribeToNewsletter, }; diff --git a/api/services/queue-service/emails-service.js b/api/services/queue-service/emails-service.js index be097b6a..adabfa3a 100644 --- a/api/services/queue-service/emails-service.js +++ b/api/services/queue-service/emails-service.js @@ -35,6 +35,14 @@ const emailService = { track_id, }); }, + + subscribeToNewsletter: ({ email, user_id, user_name }) => { + return callQueueApi("/add-job/emails/subscribe-to-newsletter", { + email, + user_id, + user_name, + }); + }, }; module.exports = emailService; diff --git a/src/Components/SideNavigation/DefaultSideNavigation.tsx b/src/Components/SideNavigation/DefaultSideNavigation.tsx index 392bc5fd..397af9e9 100644 --- a/src/Components/SideNavigation/DefaultSideNavigation.tsx +++ b/src/Components/SideNavigation/DefaultSideNavigation.tsx @@ -100,6 +100,11 @@ const navItems: NavItem[] = [ icon: "💬", text: "Host a hackathon", }, + { + href: PAGES_ROUTES.subscribeToNewsletter.default, + icon: "📧", + text: "Subscribe to our Newsletter", + }, { href: "https://www.figma.com/file/73tOKOOvZD8iN3qP9Cr18E/BOLT%F0%9F%94%A9FUN?node-id=878-150109&t=aRcywwDisNlwZfPF-0", icon: "🎨", diff --git a/src/features/Shared/components/SubscribeToNewsletterModal/SubscribeToNewsletterModal.tsx b/src/features/Shared/components/SubscribeToNewsletterModal/SubscribeToNewsletterModal.tsx deleted file mode 100644 index a4ae6035..00000000 --- a/src/features/Shared/components/SubscribeToNewsletterModal/SubscribeToNewsletterModal.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { motion } from "framer-motion"; -import { - ModalCard, - modalCardVariants, -} from "src/Components/Modals/ModalsContainer/ModalsContainer"; -import { IoClose } from "react-icons/io5"; -import ChooseLoginMethods from "src/features/Auth/components/ChooseLoginMethods/ChooseLoginMethods"; -import { ComponentProps, useCallback, useState } from "react"; -import LoginWithEmail from "src/features/Auth/components/LoginWithEmail/LoginWithEmail"; -import LoginWithLightning from "src/features/Auth/components/LoginWithLightning/LoginWithLightning"; -import LoginWithNostr from "src/features/Auth/components/LoginWithNostr/LoginWithNostr"; -import { useMeQuery } from "src/graphql"; -import { trimText } from "src/utils/helperFunctions"; -import * as yup from "yup"; -import { emailSchema } from "src/utils/validation"; -import { SubmitHandler, useForm } from "react-hook-form"; -import { yupResolver } from "@hookform/resolvers/yup"; -import Button from "src/Components/Button/Button"; - -interface Props extends ModalCard {} - -const schema = yup - .object({ - email: emailSchema.required(), - }) - .required(); - -type FormType = yup.InferType; - -export default function SubscribeToNewsletterModal({ - onClose, - direction, - ...props -}: Props) { - const { - register, - handleSubmit, - formState: { errors }, - } = useForm({ - defaultValues: { - email: "", - }, - resolver: yupResolver(schema), - }); - - const onSubmit: SubmitHandler = (data) => { - // dispatch(action) - onClose?.(); - }; - - return ( - -
- -

- Subscribe to BOLT🔩FUN Newsletter -

-
-
-
-

- A Newsletter for makers going from 0-1 and building in public! -

- -
- -
- -
- {errors.email && ( -

{errors.email.message}

- )} - - -
-
-
- ); -} diff --git a/src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage.tsx b/src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage.tsx index db2f8fac..28aecbcb 100644 --- a/src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage.tsx +++ b/src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage.tsx @@ -9,8 +9,13 @@ import { ComponentProps, CSSProperties, useCallback, useState } from "react"; import LoginWithEmail from "src/features/Auth/components/LoginWithEmail/LoginWithEmail"; import LoginWithLightning from "src/features/Auth/components/LoginWithLightning/LoginWithLightning"; import LoginWithNostr from "src/features/Auth/components/LoginWithNostr/LoginWithNostr"; -import { useMeQuery } from "src/graphql"; -import { randomItem, trimText } from "src/utils/helperFunctions"; +import { useMeQuery, useSubscribeToNewsletterMutation } from "src/graphql"; +import { + delay, + extractErrorMessage, + randomItem, + trimText, +} from "src/utils/helperFunctions"; import * as yup from "yup"; import { emailSchema } from "src/utils/validation"; import { SubmitHandler, useForm } from "react-hook-form"; @@ -18,6 +23,9 @@ import { yupResolver } from "@hookform/resolvers/yup"; import Button from "src/Components/Button/Button"; import Card from "src/Components/Card/Card"; import OgTags from "src/Components/OgTags/OgTags"; +import { NotificationsService } from "src/services"; +import { useNavigate } from "react-router-dom"; +import { PAGES_ROUTES } from "src/utils/routing"; interface Props {} @@ -72,9 +80,23 @@ export default function SubscribeToNewsletterPage({ ...props }: Props) { }, resolver: yupResolver(schema), }); + const navigate = useNavigate(); + + const [mutate, { loading }] = useSubscribeToNewsletterMutation(); - const onSubmit: SubmitHandler = (data) => { - // dispatch(action) + const onSubmit: SubmitHandler = async (data) => { + if (loading) return; + try { + await mutate({ variables: { email: data.email } }); + NotificationsService.success("Subscribed to newsletter successfully!"); + await delay(1000); + navigate(PAGES_ROUTES.blog.feed); + } catch (error) { + NotificationsService.error( + extractErrorMessage(error) ?? + "Something went wrong on our side, please try again." + ); + } }; return ( @@ -146,6 +168,7 @@ export default function SubscribeToNewsletterPage({ ...props }: Props) { color="primary" type="submit" className="mt-16" + isLoading={loading} > Subscribe diff --git a/src/features/Shared/pages/SubscribeToNewsletterPage/subscribeToNewsLetter.graphql b/src/features/Shared/pages/SubscribeToNewsletterPage/subscribeToNewsLetter.graphql new file mode 100644 index 00000000..98438d3c --- /dev/null +++ b/src/features/Shared/pages/SubscribeToNewsletterPage/subscribeToNewsLetter.graphql @@ -0,0 +1,5 @@ +mutation SubscribeToNewsletter($email: String!) { + subscribeToNewsletter(email: $email) { + id + } +} diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index 6ab20294..9e28f41f 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -288,6 +288,7 @@ export type Mutation = { linkNostrKey: Maybe; registerInTournament: Maybe; setUserNostrKeyAsPrimary: Maybe; + subscribeToNewsletter: Maybe; unlinkNostrKey: Maybe; updateLastSeenNotificationTime: Maybe; updateProfileDetails: Maybe; @@ -363,6 +364,11 @@ export type MutationSetUserNostrKeyAsPrimaryArgs = { }; +export type MutationSubscribeToNewsletterArgs = { + email: Scalars['String']; +}; + + export type MutationUnlinkNostrKeyArgs = { key: Scalars['String']; }; @@ -1558,6 +1564,13 @@ export type ProjectDetailsModalQueryVariables = Exact<{ export type ProjectDetailsModalQuery = { __typename?: 'Query', getProject: { __typename?: 'Project', id: number, title: string, tagline: string, description: string, hashtag: string, cover_image: string | null, thumbnail_image: string | null, launch_status: ProjectLaunchStatusEnum, twitter: string | null, discord: string | null, github: string | null, slack: string | null, telegram: string | null, figma: string | null, replit: string | null, youtube: string | null, npub: string | null, screenshots: Array, website: string, lightning_address: string | null, votes_count: number, permissions: Array, category: { __typename?: 'Category', id: number, icon: string | null, title: string }, members: Array<{ __typename?: 'ProjectMember', role: Team_Member_Role, user: { __typename?: 'User', id: number, name: string, jobTitle: string | null, avatar: string } }>, tags: Array<{ __typename?: 'Tag', id: number, title: string }>, recruit_roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }>, capabilities: Array<{ __typename?: 'Capability', id: number, title: string, icon: string }> } }; +export type SubscribeToNewsletterMutationVariables = Exact<{ + email: Scalars['String']; +}>; + + +export type SubscribeToNewsletterMutation = { __typename?: 'Mutation', subscribeToNewsletter: { __typename?: 'User', id: number } | null }; + export type GetAllRolesQueryVariables = Exact<{ [key: string]: never; }>; @@ -4072,6 +4085,39 @@ export function useProjectDetailsModalLazyQuery(baseOptions?: Apollo.LazyQueryHo export type ProjectDetailsModalQueryHookResult = ReturnType; export type ProjectDetailsModalLazyQueryHookResult = ReturnType; export type ProjectDetailsModalQueryResult = Apollo.QueryResult; +export const SubscribeToNewsletterDocument = gql` + mutation SubscribeToNewsletter($email: String!) { + subscribeToNewsletter(email: $email) { + id + } +} + `; +export type SubscribeToNewsletterMutationFn = Apollo.MutationFunction; + +/** + * __useSubscribeToNewsletterMutation__ + * + * To run a mutation, you first call `useSubscribeToNewsletterMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useSubscribeToNewsletterMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [subscribeToNewsletterMutation, { data, loading, error }] = useSubscribeToNewsletterMutation({ + * variables: { + * email: // value for 'email' + * }, + * }); + */ +export function useSubscribeToNewsletterMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(SubscribeToNewsletterDocument, options); + } +export type SubscribeToNewsletterMutationHookResult = ReturnType; +export type SubscribeToNewsletterMutationResult = Apollo.MutationResult; +export type SubscribeToNewsletterMutationOptions = Apollo.BaseMutationOptions; export const GetAllRolesDocument = gql` query GetAllRoles { getAllMakersRoles { diff --git a/src/utils/helperFunctions/misc-helper-functions.tsx b/src/utils/helperFunctions/misc-helper-functions.tsx index 615fd1b7..6e334835 100644 --- a/src/utils/helperFunctions/misc-helper-functions.tsx +++ b/src/utils/helperFunctions/misc-helper-functions.tsx @@ -102,3 +102,7 @@ export function overrideErrorMessage(msg: string, errorToOverride: any) { } return err; } + +export function delay(ms: number) { + return new Promise((res) => setTimeout(res, ms)); +} diff --git a/src/utils/routing/rootRouter.tsx b/src/utils/routing/rootRouter.tsx index 065df82f..687527af 100644 --- a/src/utils/routing/rootRouter.tsx +++ b/src/utils/routing/rootRouter.tsx @@ -21,7 +21,6 @@ import { feedPageLoader } from "src/features/Posts/pages/FeedPage/feedPage.loade import { Post_Type } from "src/graphql"; import { LandingPage } from "src/features/LandingPage/LandingPage"; import { EventsPage } from "src/features/Events/pages/EventsPage/EventsPage"; -import SubscribeToNewsletterPage from "src/features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage"; const HomePage = Loadable( React.lazy( @@ -219,6 +218,15 @@ const TermsAndConditionsPage = Loadable( ) ); +const SubscribeToNewsletterPage = Loadable( + React.lazy( + () => + import( + /* webpackChunkName: "subscribe_to_newsletter_page" */ "../../features/Shared/pages/SubscribeToNewsletterPage/SubscribeToNewsletterPage" + ) + ) +); + const createRoutes = (queryClient: ApolloClient) => createRoutesFromElements( } errorElement={}> @@ -239,8 +247,12 @@ const createRoutes = (queryClient: ApolloClient) => } /> } + path={PAGES_ROUTES.subscribeToNewsletter.default} + element={ + + + + } /> }> }> diff --git a/src/utils/routing/routes.ts b/src/utils/routing/routes.ts index 45ceffbe..78dfd2c9 100644 --- a/src/utils/routing/routes.ts +++ b/src/utils/routing/routes.ts @@ -199,4 +199,7 @@ export const PAGES_ROUTES = { loginEmail: "/login-email", logout: "/logout", }, + subscribeToNewsletter: { + default: "/subscribe-to-newsletter", + }, }; From fe043408eb59d7f18cf74cad8c98737464da0d11 Mon Sep 17 00:00:00 2001 From: "MTG\\mtg09" <50504183+MTG2000@users.noreply.github.com> Date: Wed, 20 Dec 2023 16:13:10 +0400 Subject: [PATCH 4/4] update (newsletter): remove the need to be logged in to subscribe to newsletter --- api/functions/graphql/nexus-typegen.ts | 4 +-- api/functions/graphql/schema.graphql | 2 +- api/functions/graphql/types/users.js | 28 ++++++++----------- .../subscribeToNewsLetter.graphql | 4 +-- src/graphql/index.tsx | 8 ++---- src/utils/routing/rootRouter.tsx | 6 +--- 6 files changed, 20 insertions(+), 32 deletions(-) diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index e9bc0952..b2c2f384 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -756,7 +756,7 @@ export interface NexusGenFieldTypes { linkNostrKey: NexusGenRootTypes['User'] | null; // User registerInTournament: NexusGenRootTypes['User'] | null; // User setUserNostrKeyAsPrimary: NexusGenRootTypes['User'] | null; // User - subscribeToNewsletter: NexusGenRootTypes['User'] | null; // User + subscribeToNewsletter: boolean; // Boolean! unlinkNostrKey: NexusGenRootTypes['User'] | null; // User updateLastSeenNotificationTime: NexusGenRootTypes['User'] | null; // User updateProfileDetails: NexusGenRootTypes['User'] | null; // User @@ -1270,7 +1270,7 @@ export interface NexusGenFieldTypeNames { linkNostrKey: 'User' registerInTournament: 'User' setUserNostrKeyAsPrimary: 'User' - subscribeToNewsletter: 'User' + subscribeToNewsletter: 'Boolean' unlinkNostrKey: 'User' updateLastSeenNotificationTime: 'User' updateProfileDetails: 'User' diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index b52a40ff..eb20de47 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -257,7 +257,7 @@ type Mutation { linkNostrKey(event: NostrEventInput): User registerInTournament(data: RegisterInTournamentInput, tournament_id: Int!): User setUserNostrKeyAsPrimary(key: String): User - subscribeToNewsletter(email: String!): User + subscribeToNewsletter(email: String!): Boolean! unlinkNostrKey(key: String!): User updateLastSeenNotificationTime(timestamp: String!): User updateProfileDetails(data: ProfileDetailsInput): User diff --git a/api/functions/graphql/types/users.js b/api/functions/graphql/types/users.js index 71a8c8e6..2b8a48f5 100644 --- a/api/functions/graphql/types/users.js +++ b/api/functions/graphql/types/users.js @@ -771,28 +771,24 @@ const updateLastSeenNotificationTime = extendType({ const subscribeToNewsletter = extendType({ type: "Mutation", definition(t) { - t.field("subscribeToNewsletter", { - type: "User", + t.nonNull.boolean("subscribeToNewsletter", { args: { email: nonNull(stringArg()) }, async resolve(_root, { email }, ctx) { const user = await getUserById(ctx.user?.id); - if (!user?.id) throw new Error("You have to login"); - - const userData = await prisma.user.findUnique({ - where: { - id: user.id, - }, - }); - // TODO: store in the DB that the user is subscribed to the newsletter - await queueService.emailService.subscribeToNewsletter({ - email, - user_id: userData.id, - user_name: userData.name, - }); + await queueService.emailService + .subscribeToNewsletter({ + email, + user_id: user?.id, + user_name: user?.name, + }) + .catch((err) => { + console.log(err); + throw err; + }); - return userData; + return true; }, }); }, diff --git a/src/features/Shared/pages/SubscribeToNewsletterPage/subscribeToNewsLetter.graphql b/src/features/Shared/pages/SubscribeToNewsletterPage/subscribeToNewsLetter.graphql index 98438d3c..e4991e1d 100644 --- a/src/features/Shared/pages/SubscribeToNewsletterPage/subscribeToNewsLetter.graphql +++ b/src/features/Shared/pages/SubscribeToNewsletterPage/subscribeToNewsLetter.graphql @@ -1,5 +1,3 @@ mutation SubscribeToNewsletter($email: String!) { - subscribeToNewsletter(email: $email) { - id - } + subscribeToNewsletter(email: $email) } diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index 9e28f41f..316b371c 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -288,7 +288,7 @@ export type Mutation = { linkNostrKey: Maybe; registerInTournament: Maybe; setUserNostrKeyAsPrimary: Maybe; - subscribeToNewsletter: Maybe; + subscribeToNewsletter: Scalars['Boolean']; unlinkNostrKey: Maybe; updateLastSeenNotificationTime: Maybe; updateProfileDetails: Maybe; @@ -1569,7 +1569,7 @@ export type SubscribeToNewsletterMutationVariables = Exact<{ }>; -export type SubscribeToNewsletterMutation = { __typename?: 'Mutation', subscribeToNewsletter: { __typename?: 'User', id: number } | null }; +export type SubscribeToNewsletterMutation = { __typename?: 'Mutation', subscribeToNewsletter: boolean }; export type GetAllRolesQueryVariables = Exact<{ [key: string]: never; }>; @@ -4087,9 +4087,7 @@ export type ProjectDetailsModalLazyQueryHookResult = ReturnType; export const SubscribeToNewsletterDocument = gql` mutation SubscribeToNewsletter($email: String!) { - subscribeToNewsletter(email: $email) { - id - } + subscribeToNewsletter(email: $email) } `; export type SubscribeToNewsletterMutationFn = Apollo.MutationFunction; diff --git a/src/utils/routing/rootRouter.tsx b/src/utils/routing/rootRouter.tsx index 687527af..c6376138 100644 --- a/src/utils/routing/rootRouter.tsx +++ b/src/utils/routing/rootRouter.tsx @@ -248,11 +248,7 @@ const createRoutes = (queryClient: ApolloClient) => /> - - - } + element={} /> }> }>