From 329ecf3b609ee3d7b6d13ffcff74a4d654c7e326 Mon Sep 17 00:00:00 2001 From: MTG2000 Date: Fri, 9 Sep 2022 12:14:56 +0300 Subject: [PATCH] feat: update my hacking_status, filter by hacking status, allow optional fields in inputArgs --- api/functions/graphql/nexus-typegen.ts | 14 +++ api/functions/graphql/schema.graphql | 9 +- api/functions/graphql/types/tournament.js | 75 ++++++++++++++- codegen.yml | 6 +- .../pages/MakersPage/MakerCard/MakerCard.tsx | 37 +++++++- .../ParticipantsSection/MakersList.tsx | 5 +- .../pages/MakersPage/tournamentMakers.graphql | 13 +++ src/graphql/index.tsx | 91 ++++++++++++++++--- src/mocks/handlers.ts | 2 +- 9 files changed, 228 insertions(+), 24 deletions(-) diff --git a/api/functions/graphql/nexus-typegen.ts b/api/functions/graphql/nexus-typegen.ts index 3f35201..364937a 100644 --- a/api/functions/graphql/nexus-typegen.ts +++ b/api/functions/graphql/nexus-typegen.ts @@ -64,6 +64,10 @@ export interface NexusGenInputs { tags: string[]; // [String!]! title: string; // String! } + UpdateTournamentRegistrationInput: { // input type + email?: string | null; // String + hacking_status?: NexusGenEnums['TournamentMakerHackingStatusEnum'] | null; // TournamentMakerHackingStatusEnum + } UserKeyInputType: { // input type key: string; // String! name: string; // String! @@ -194,6 +198,7 @@ export interface NexusGenObjects { } ParticipationInfo: { // root type createdAt: NexusGenScalars['Date']; // Date! + email: string; // String! hacking_status: NexusGenEnums['TournamentMakerHackingStatusEnum']; // TournamentMakerHackingStatusEnum! } PostComment: { // root type @@ -445,6 +450,7 @@ export interface NexusGenFieldTypes { registerInTournament: NexusGenRootTypes['User'] | null; // User updateProfileDetails: NexusGenRootTypes['MyProfile'] | null; // MyProfile updateProfileRoles: NexusGenRootTypes['MyProfile'] | null; // MyProfile + updateTournamentRegistration: NexusGenRootTypes['ParticipationInfo'] | null; // ParticipationInfo updateUserPreferences: NexusGenRootTypes['MyProfile']; // MyProfile! vote: NexusGenRootTypes['Vote']; // Vote! } @@ -475,6 +481,7 @@ export interface NexusGenFieldTypes { } ParticipationInfo: { // field return type createdAt: NexusGenScalars['Date']; // Date! + email: string; // String! hacking_status: NexusGenEnums['TournamentMakerHackingStatusEnum']; // TournamentMakerHackingStatusEnum! } PostComment: { // field return type @@ -798,6 +805,7 @@ export interface NexusGenFieldTypeNames { registerInTournament: 'User' updateProfileDetails: 'MyProfile' updateProfileRoles: 'MyProfile' + updateTournamentRegistration: 'ParticipationInfo' updateUserPreferences: 'MyProfile' vote: 'Vote' } @@ -828,6 +836,7 @@ export interface NexusGenFieldTypeNames { } ParticipationInfo: { // field return type name createdAt: 'Date' + email: 'String' hacking_status: 'TournamentMakerHackingStatusEnum' } PostComment: { // field return type name @@ -1076,6 +1085,10 @@ export interface NexusGenArgTypes { updateProfileRoles: { // args data?: NexusGenInputs['ProfileRolesInput'] | null; // ProfileRolesInput } + updateTournamentRegistration: { // args + data?: NexusGenInputs['UpdateTournamentRegistrationInput'] | null; // UpdateTournamentRegistrationInput + tournament_id: number; // Int! + } updateUserPreferences: { // args userKeys?: NexusGenInputs['UserKeyInputType'][] | null; // [UserKeyInputType!] } @@ -1112,6 +1125,7 @@ export interface NexusGenArgTypes { project_id: number; // Int! } getMakersInTournament: { // args + openToConnect?: boolean | null; // Boolean roleId?: number | null; // Int search?: string | null; // String skip?: number | null; // Int diff --git a/api/functions/graphql/schema.graphql b/api/functions/graphql/schema.graphql index ba9a87e..8d504e7 100644 --- a/api/functions/graphql/schema.graphql +++ b/api/functions/graphql/schema.graphql @@ -152,6 +152,7 @@ type Mutation { registerInTournament(data: RegisterInTournamentInput, tournament_id: Int!): User updateProfileDetails(data: ProfileDetailsInput): MyProfile updateProfileRoles(data: ProfileRolesInput): MyProfile + updateTournamentRegistration(data: UpdateTournamentRegistrationInput, tournament_id: Int!): ParticipationInfo updateUserPreferences(userKeys: [UserKeyInputType!]): MyProfile! vote(amount_in_sat: Int!, item_id: Int!, item_type: VOTE_ITEM_TYPE!): Vote! } @@ -190,6 +191,7 @@ enum POST_TYPE { type ParticipationInfo { createdAt: Date! + email: String! hacking_status: TournamentMakerHackingStatusEnum! } @@ -261,7 +263,7 @@ type Query { getDonationsStats: DonationsStats! getFeed(skip: Int = 0, sortBy: String, tag: Int = 0, take: Int = 10): [Post!]! getLnurlDetailsForProject(project_id: Int!): LnurlDetails! - getMakersInTournament(roleId: Int, search: String, skip: Int = 0, take: Int = 10, tournamentId: Int!): TournamentMakersResponse! + getMakersInTournament(openToConnect: Boolean, roleId: Int, search: String, skip: Int = 0, take: Int = 10, tournamentId: Int!): TournamentMakersResponse! getMyDrafts(type: POST_TYPE!): [Post!]! getPostById(id: Int!, type: POST_TYPE!): Post! getProject(id: Int!): Project! @@ -420,6 +422,11 @@ type TournamentProjectsResponse { projects: [Project!]! } +input UpdateTournamentRegistrationInput { + email: String + hacking_status: TournamentMakerHackingStatusEnum +} + type User implements BaseUser { avatar: String! bio: String diff --git a/api/functions/graphql/types/tournament.js b/api/functions/graphql/types/tournament.js index 121cbb3..f42d335 100644 --- a/api/functions/graphql/types/tournament.js +++ b/api/functions/graphql/types/tournament.js @@ -6,10 +6,11 @@ const { nonNull, enumType, inputObjectType, + booleanArg, } = require('nexus'); const { getUserByPubKey } = require('../../../auth/utils/helperFuncs'); const { prisma } = require('../../../prisma'); -const { paginationArgs } = require('./helpers'); +const { paginationArgs, removeNulls } = require('./helpers'); @@ -196,6 +197,7 @@ const ParticipationInfo = objectType({ name: "ParticipationInfo", definition(t) { t.nonNull.date('createdAt') + t.nonNull.string('email') t.nonNull.field('hacking_status', { type: TournamentMakerHackingStatusEnum }); } @@ -237,6 +239,7 @@ const getMakersInTournament = extendType({ ...paginationArgs({ take: 10 }), search: stringArg(), roleId: intArg(), + openToConnect: booleanArg() }, async resolve(_, args, ctx) { @@ -271,12 +274,33 @@ const getMakersInTournament = extendType({ } }) + if (args.openToConnect) filters.push({ + OR: [ + { + github: { + not: null + } + }, + { + twitter: { + not: null + } + }, + { + linkedin: { + not: null + } + }, + ] + }) + if (user?.id) filters.push({ id: { not: user.id } }) + const makers = (await prisma.tournamentParticipant.findMany({ where: { tournament_id: args.tournamentId, @@ -284,6 +308,9 @@ const getMakersInTournament = extendType({ user: { AND: filters } + }), + ...(args.openToConnect && { + hacking_status: TournamentMakerHackingStatusEnum.value.members.OpenToConnect }) }, orderBy: { @@ -433,6 +460,50 @@ const registerInTournament = extendType({ }, }) +const UpdateTournamentRegistrationInput = inputObjectType({ + name: 'UpdateTournamentRegistrationInput', + definition(t) { + t.string('email') + t.field('hacking_status', { type: TournamentMakerHackingStatusEnum }) + } +}) + +const updateTournamentRegistration = extendType({ + type: 'Mutation', + definition(t) { + t.field('updateTournamentRegistration', { + type: ParticipationInfo, + args: { + data: UpdateTournamentRegistrationInput, + tournament_id: nonNull(intArg()) + }, + async resolve(_root, { tournament_id, data: { email, hacking_status } }, ctx) { + const user = await getUserByPubKey(ctx.userPubKey); + + // Do some validation + // if (!user) + // throw new Error("You have to login"); + + + // Email verification here: + // .... + // .... + + return prisma.tournamentParticipant.update({ + where: { + tournament_id_user_id: { tournament_id, user_id: user.id } + }, + data: removeNulls({ + email, + hacking_status + }), + }); + } + }) + }, +}) + + module.exports = { // Types Tournament, @@ -448,5 +519,5 @@ module.exports = { // Mutations registerInTournament, - + updateTournamentRegistration, } diff --git a/codegen.yml b/codegen.yml index 28cd2dd..b2c805d 100644 --- a/codegen.yml +++ b/codegen.yml @@ -9,4 +9,8 @@ generates: - "typescript-react-apollo" config: withHooks: true - avoidOptionals: true + avoidOptionals: + field: true + inputValue: false + object: true + defaultValue: true diff --git a/src/features/Tournaments/pages/MakersPage/MakerCard/MakerCard.tsx b/src/features/Tournaments/pages/MakersPage/MakerCard/MakerCard.tsx index 697418d..0c2f820 100644 --- a/src/features/Tournaments/pages/MakersPage/MakerCard/MakerCard.tsx +++ b/src/features/Tournaments/pages/MakersPage/MakerCard/MakerCard.tsx @@ -1,5 +1,5 @@ import Button from "src/Components/Button/Button" -import { GetMakersInTournamentQuery, TournamentMakerHackingStatusEnum, } from "src/graphql"; +import { GetMakersInTournamentQuery, TournamentMakerHackingStatusEnum, useUpdateTournamentRegistrationMutation } from "src/graphql"; import { useAppDispatch, } from "src/utils/hooks"; import Card from 'src/Components/Card/Card'; import Avatar from 'src/features/Profiles/Components/Avatar/Avatar'; @@ -8,6 +8,8 @@ import { createRoute } from 'src/utils/routing'; import { openModal } from "src/redux/features/modals.slice"; import InfoCard from "src/Components/InfoCard/InfoCard"; import { Link } from "react-router-dom"; +import { useState } from "react"; +import { NotificationsService } from "src/services"; type MakerType = GetMakersInTournamentQuery['getMakersInTournament']['makers'][number] @@ -19,8 +21,10 @@ interface Props { export default function MakerCard({ maker, isMe }: Props) { const dispatch = useAppDispatch(); + const [hackingStatus, setHackingStatus] = useState(maker.hacking_status) const contactLinksAvailable = maker.user.github || maker.user.linkedin || maker.user.twitter; + const [udpateInfo, updateInfoMutation] = useUpdateTournamentRegistrationMutation() let actionBtn = <> @@ -32,7 +36,23 @@ export default function MakerCard({ maker, isMe }: Props) { actionBtn = - const missingFields = isMe && getMissingFields(maker) + const missingFields = isMe && getMissingFields(maker); + + const changeHacktingStatus = (value: typeof hackingStatus) => { + setHackingStatus(value); + udpateInfo({ + variables: { + tournamentId: 12, + data: { + hacking_status: value + } + }, + }) + .catch(() => { + setHackingStatus(maker.hacking_status) + NotificationsService.error("A network error happened") + }) + } return ( @@ -76,6 +96,19 @@ export default function MakerCard({ maker, isMe }: Props) {

No skills added

} + {isMe &&
+

🚦 Hacking status

+
+ + +
+
}
{actionBtn}
{missingFields && 👾 Complete your profile: make it easy for other makers to find you by adding your {missingFields}. You can add this information in your profile’s Settings ⚙️ menu. diff --git a/src/features/Tournaments/pages/MakersPage/ParticipantsSection/MakersList.tsx b/src/features/Tournaments/pages/MakersPage/ParticipantsSection/MakersList.tsx index b40239c..8b7ee99 100644 --- a/src/features/Tournaments/pages/MakersPage/ParticipantsSection/MakersList.tsx +++ b/src/features/Tournaments/pages/MakersPage/ParticipantsSection/MakersList.tsx @@ -27,6 +27,7 @@ export default function MakersList(props: Props) { search: props.searchFilter ?? null, skip: ITEMS_PER_PAGE * page, take: ITEMS_PER_PAGE, + openToConnect: props.onlyLookingToTeam ?? null }); @@ -40,8 +41,8 @@ export default function MakersList(props: Props) { useEffect(() => { setPage(0); - setQueryFilter(f => ({ ...f, search: props.searchFilter, roleId: props.roleFilter, skip: 0 })) - }, [props.roleFilter, props.searchFilter]); + setQueryFilter(f => ({ ...f, search: props.searchFilter, roleId: props.roleFilter, openToConnect: props.onlyLookingToTeam ?? null, skip: 0 })) + }, [props.onlyLookingToTeam, props.roleFilter, props.searchFilter]); diff --git a/src/features/Tournaments/pages/MakersPage/tournamentMakers.graphql b/src/features/Tournaments/pages/MakersPage/tournamentMakers.graphql index 08448fc..bb38302 100644 --- a/src/features/Tournaments/pages/MakersPage/tournamentMakers.graphql +++ b/src/features/Tournaments/pages/MakersPage/tournamentMakers.graphql @@ -12,6 +12,7 @@ query GetMakersInTournament( $skip: Int $search: String $roleId: Int + $openToConnect: Boolean ) { getMakersInTournament( tournamentId: $tournamentId @@ -19,6 +20,7 @@ query GetMakersInTournament( skip: $skip search: $search roleId: $roleId + openToConnect: $openToConnect ) { hasNext hasPrev @@ -83,3 +85,14 @@ query GetProjectsInTournament( } } } + +mutation UpdateTournamentRegistration( + $tournamentId: Int! + $data: UpdateTournamentRegistrationInput +) { + updateTournamentRegistration(tournament_id: $tournamentId, data: $data) { + createdAt + email + hacking_status + } +} diff --git a/src/graphql/index.tsx b/src/graphql/index.tsx index caced71..e830e51 100644 --- a/src/graphql/index.tsx +++ b/src/graphql/index.tsx @@ -182,6 +182,7 @@ export type Mutation = { registerInTournament: Maybe; updateProfileDetails: Maybe; updateProfileRoles: Maybe; + updateTournamentRegistration: Maybe; updateUserPreferences: MyProfile; vote: Vote; }; @@ -230,6 +231,12 @@ export type MutationUpdateProfileRolesArgs = { }; +export type MutationUpdateTournamentRegistrationArgs = { + data: InputMaybe; + tournament_id: Scalars['Int']; +}; + + export type MutationUpdateUserPreferencesArgs = { userKeys: InputMaybe>; }; @@ -282,6 +289,7 @@ export enum Post_Type { export type ParticipationInfo = { __typename?: 'ParticipationInfo'; createdAt: Scalars['Date']; + email: Scalars['String']; hacking_status: TournamentMakerHackingStatusEnum; }; @@ -309,17 +317,17 @@ export type PostComment = { }; export type ProfileDetailsInput = { - avatar: InputMaybe; - bio: InputMaybe; - email: InputMaybe; - github: InputMaybe; - jobTitle: InputMaybe; - lightning_address: InputMaybe; - linkedin: InputMaybe; - location: InputMaybe; - name: InputMaybe; - twitter: InputMaybe; - website: InputMaybe; + avatar?: InputMaybe; + bio?: InputMaybe; + email?: InputMaybe; + github?: InputMaybe; + jobTitle?: InputMaybe; + lightning_address?: InputMaybe; + linkedin?: InputMaybe; + location?: InputMaybe; + name?: InputMaybe; + twitter?: InputMaybe; + website?: InputMaybe; }; export type ProfileRolesInput = { @@ -407,6 +415,7 @@ export type QueryGetLnurlDetailsForProjectArgs = { export type QueryGetMakersInTournamentArgs = { + openToConnect: InputMaybe; roleId: InputMaybe; search: InputMaybe; skip?: InputMaybe; @@ -533,9 +542,9 @@ export type Story = PostBase & { export type StoryInputType = { body: Scalars['String']; - cover_image: InputMaybe; - id: InputMaybe; - is_published: InputMaybe; + cover_image?: InputMaybe; + id?: InputMaybe; + is_published?: InputMaybe; tags: Array; title: Scalars['String']; }; @@ -636,6 +645,11 @@ export type TournamentProjectsResponse = { projects: Array; }; +export type UpdateTournamentRegistrationInput = { + email?: InputMaybe; + hacking_status?: InputMaybe; +}; + export type User = BaseUser & { __typename?: 'User'; avatar: Scalars['String']; @@ -891,6 +905,7 @@ export type GetMakersInTournamentQueryVariables = Exact<{ skip: InputMaybe; search: InputMaybe; roleId: InputMaybe; + openToConnect: InputMaybe; }>; @@ -907,6 +922,14 @@ export type GetProjectsInTournamentQueryVariables = Exact<{ export type GetProjectsInTournamentQuery = { __typename?: 'Query', getProjectsInTournament: { __typename?: 'TournamentProjectsResponse', hasNext: boolean | null, hasPrev: boolean | null, projects: Array<{ __typename?: 'Project', id: number, title: string, description: string, thumbnail_image: string, category: { __typename?: 'Category', id: number, title: string, icon: string | null }, recruit_roles: Array<{ __typename?: 'MakerRole', id: number, title: string, icon: string, level: RoleLevelEnum }> }> } }; +export type UpdateTournamentRegistrationMutationVariables = Exact<{ + tournamentId: Scalars['Int']; + data: InputMaybe; +}>; + + +export type UpdateTournamentRegistrationMutation = { __typename?: 'Mutation', updateTournamentRegistration: { __typename?: 'ParticipationInfo', createdAt: any, email: string, hacking_status: TournamentMakerHackingStatusEnum } | null }; + export type RegisterInTournamentMutationVariables = Exact<{ tournamentId: Scalars['Int']; data: InputMaybe; @@ -2289,13 +2312,14 @@ export type GetAllRolesQueryHookResult = ReturnType; export type GetAllRolesLazyQueryHookResult = ReturnType; export type GetAllRolesQueryResult = Apollo.QueryResult; export const GetMakersInTournamentDocument = gql` - query GetMakersInTournament($tournamentId: Int!, $take: Int, $skip: Int, $search: String, $roleId: Int) { + query GetMakersInTournament($tournamentId: Int!, $take: Int, $skip: Int, $search: String, $roleId: Int, $openToConnect: Boolean) { getMakersInTournament( tournamentId: $tournamentId take: $take skip: $skip search: $search roleId: $roleId + openToConnect: $openToConnect ) { hasNext hasPrev @@ -2341,6 +2365,7 @@ export const GetMakersInTournamentDocument = gql` * skip: // value for 'skip' * search: // value for 'search' * roleId: // value for 'roleId' + * openToConnect: // value for 'openToConnect' * }, * }); */ @@ -2418,6 +2443,42 @@ export function useGetProjectsInTournamentLazyQuery(baseOptions?: Apollo.LazyQue export type GetProjectsInTournamentQueryHookResult = ReturnType; export type GetProjectsInTournamentLazyQueryHookResult = ReturnType; export type GetProjectsInTournamentQueryResult = Apollo.QueryResult; +export const UpdateTournamentRegistrationDocument = gql` + mutation UpdateTournamentRegistration($tournamentId: Int!, $data: UpdateTournamentRegistrationInput) { + updateTournamentRegistration(tournament_id: $tournamentId, data: $data) { + createdAt + email + hacking_status + } +} + `; +export type UpdateTournamentRegistrationMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateTournamentRegistrationMutation__ + * + * To run a mutation, you first call `useUpdateTournamentRegistrationMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateTournamentRegistrationMutation` 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 [updateTournamentRegistrationMutation, { data, loading, error }] = useUpdateTournamentRegistrationMutation({ + * variables: { + * tournamentId: // value for 'tournamentId' + * data: // value for 'data' + * }, + * }); + */ +export function useUpdateTournamentRegistrationMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateTournamentRegistrationDocument, options); + } +export type UpdateTournamentRegistrationMutationHookResult = ReturnType; +export type UpdateTournamentRegistrationMutationResult = Apollo.MutationResult; +export type UpdateTournamentRegistrationMutationOptions = Apollo.BaseMutationOptions; export const RegisterInTournamentDocument = gql` mutation RegisterInTournament($tournamentId: Int!, $data: RegisterInTournamentInput) { registerInTournament(tournament_id: $tournamentId, data: $data) { diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index dc1fe80..ce88c2b 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -284,7 +284,7 @@ export const handlers = [ return res( ctx.data({ getTournamentById: getTournamentById(12), - getMakersInTournament: getMakersInTournament({ roleId: null, search: null, skip: null, take: 4, tournamentId: 12 }), + getMakersInTournament: getMakersInTournament({ roleId: null, search: null, skip: null, take: 4, tournamentId: 12, openToConnect: null }), me: { ...me() }, tournamentParticipationInfo: { hacking_status: TournamentMakerHackingStatusEnum.OpenToConnect,