diff --git a/packages/backend/apps/workshops/schema.py b/packages/backend/apps/workshops/schema.py index 1bf115c..d2c2d7f 100644 --- a/packages/backend/apps/workshops/schema.py +++ b/packages/backend/apps/workshops/schema.py @@ -3,7 +3,8 @@ from graphene import relay from graphene_django import DjangoObjectType -from . import models +from common.graphql import mutations +from . import models, serializers class WorkshopItemType(DjangoObjectType): @@ -24,3 +25,11 @@ class Query(graphene.ObjectType): @staticmethod def resolve_all_workshop_items(root, info, **kwargs): return models.WorkshopItem.objects.all() + + +class WorkshopItemCreateMutation(mutations.CreateModelMutation): + class Meta: + serializer_class = serializers.WorkshopItemSerializer + +class Mutation(graphene.ObjectType): + create_workshop = WorkshopItemCreateMutation.Field() diff --git a/packages/backend/apps/workshops/serializers.py b/packages/backend/apps/workshops/serializers.py new file mode 100644 index 0000000..bb8c12e --- /dev/null +++ b/packages/backend/apps/workshops/serializers.py @@ -0,0 +1,13 @@ +from hashid_field import rest as hidrest +from rest_framework import serializers + +from . import models + + +class WorkshopItemSerializer(serializers.ModelSerializer): + id = hidrest.HashidSerializerCharField(source_field="workshops.WorkshopItem.id", read_only=True) + name = serializers.CharField(min_length=10) + + class Meta: + model = models.WorkshopItem + fields = ('id', 'name') diff --git a/packages/backend/config/schema.py b/packages/backend/config/schema.py index 6d4ec5d..b611489 100644 --- a/packages/backend/config/schema.py +++ b/packages/backend/config/schema.py @@ -27,6 +27,7 @@ users_schema.Mutation, finances_schema.Mutation, integrations_schema.Mutation, + workshops_schema.Mutation, ] ), subscription=graphql_subscription(([notifications_schema.Subscription])), diff --git a/packages/webapp-libs/webapp-api-client/graphql/schema/api.graphql b/packages/webapp-libs/webapp-api-client/graphql/schema/api.graphql index a13885f..5f163b1 100644 --- a/packages/webapp-libs/webapp-api-client/graphql/schema/api.graphql +++ b/packages/webapp-libs/webapp-api-client/graphql/schema/api.graphql @@ -729,6 +729,7 @@ type FileFieldType { name: String } type ApiMutation { + createWorkshop(input: WorkshopItemCreateMutationInput!): WorkshopItemCreateMutationPayload generateSaasIdeas(input: GenerateSaasIdeasMutationInput!): GenerateSaasIdeasMutationPayload changeActiveSubscription(input: ChangeActiveSubscriptionMutationInput!): ChangeActiveSubscriptionMutationPayload cancelActiveSubscription(input: CancelActiveSubscriptionMutationInput!): CancelActiveSubscriptionMutationPayload @@ -758,6 +759,10 @@ type ApiMutation { createFavoriteContentfulDemoItem(input: CreateFavoriteContentfulDemoItemMutationInput!): CreateFavoriteContentfulDemoItemMutationPayload deleteFavoriteContentfulDemoItem(input: DeleteFavoriteContentfulDemoItemMutationInput!): DeleteFavoriteContentfulDemoItemMutationPayload } +type WorkshopItemCreateMutationPayload { + workshopItem: WorkshopItemType + clientMutationId: String +} type GenerateSaasIdeasMutationPayload { ideas: [String] clientMutationId: String @@ -1366,6 +1371,10 @@ enum DjstripeSetupIntentUsageChoices { "On session" ON_SESSION } +input WorkshopItemCreateMutationInput { + name: String! + clientMutationId: String +} input GenerateSaasIdeasMutationInput { keywords: [String] clientMutationId: String diff --git a/packages/webapp-libs/webapp-api-client/src/graphql/__generated/gql/gql.ts b/packages/webapp-libs/webapp-api-client/src/graphql/__generated/gql/gql.ts index b6186df..0b2c9f4 100644 --- a/packages/webapp-libs/webapp-api-client/src/graphql/__generated/gql/gql.ts +++ b/packages/webapp-libs/webapp-api-client/src/graphql/__generated/gql/gql.ts @@ -63,6 +63,7 @@ const documents = { "\n fragment notificationsListContentFragment on Query {\n hasUnreadNotifications\n allNotifications(first: $count, after: $cursor) {\n edges {\n node {\n id\n data\n createdAt\n readAt\n type\n issuer {\n id\n avatar\n email\n }\n }\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n": types.NotificationsListContentFragmentFragmentDoc, "\n mutation notificationsListMarkAsReadMutation($input: MarkReadAllNotificationsMutationInput!) {\n markReadAllNotifications(input: $input) {\n ok\n }\n }\n": types.NotificationsListMarkAsReadMutationDocument, "\n mutation authConfirmUserEmailMutation($input: ConfirmEmailMutationInput!) {\n confirm(input: $input) {\n ok\n }\n }\n": types.AuthConfirmUserEmailMutationDocument, + "\n mutation WorkshopItemCreateMutation($input: WorkshopItemCreateMutationInput!) {\n createWorkshop(input: $input) {\n workshopItem {\n id\n name\n }\n }\n }\n": types.WorkshopItemCreateMutationDocument, "\n query WorkshopItemsList {\n allWorkshopItems {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n": types.WorkshopItemsListDocument, "\n mutation authChangePasswordMutation($input: ChangePasswordMutationInput!) {\n changePassword(input: $input) {\n access\n refresh\n }\n }\n": types.AuthChangePasswordMutationDocument, "\n mutation authUpdateUserProfileMutation($input: UpdateCurrentUserMutationInput!) {\n updateCurrentUser(input: $input) {\n userProfile {\n id\n user {\n ...commonQueryCurrentUserFragment\n }\n }\n }\n }\n": types.AuthUpdateUserProfileMutationDocument, @@ -290,6 +291,10 @@ export function gql(source: "\n mutation notificationsListMarkAsReadMutation($i * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql(source: "\n mutation authConfirmUserEmailMutation($input: ConfirmEmailMutationInput!) {\n confirm(input: $input) {\n ok\n }\n }\n"): (typeof documents)["\n mutation authConfirmUserEmailMutation($input: ConfirmEmailMutationInput!) {\n confirm(input: $input) {\n ok\n }\n }\n"]; +/** + * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function gql(source: "\n mutation WorkshopItemCreateMutation($input: WorkshopItemCreateMutationInput!) {\n createWorkshop(input: $input) {\n workshopItem {\n id\n name\n }\n }\n }\n"): (typeof documents)["\n mutation WorkshopItemCreateMutation($input: WorkshopItemCreateMutationInput!) {\n createWorkshop(input: $input) {\n workshopItem {\n id\n name\n }\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/packages/webapp-libs/webapp-api-client/src/graphql/__generated/gql/graphql.ts b/packages/webapp-libs/webapp-api-client/src/graphql/__generated/gql/graphql.ts index fe98537..0139c28 100644 --- a/packages/webapp-libs/webapp-api-client/src/graphql/__generated/gql/graphql.ts +++ b/packages/webapp-libs/webapp-api-client/src/graphql/__generated/gql/graphql.ts @@ -66,6 +66,7 @@ export type ApiMutation = { createFavoriteContentfulDemoItem?: Maybe; createPaymentIntent?: Maybe; createSetupIntent?: Maybe; + createWorkshop?: Maybe; deleteCrudDemoItem?: Maybe; deleteDocumentDemoItem?: Maybe; deleteFavoriteContentfulDemoItem?: Maybe; @@ -133,6 +134,11 @@ export type ApiMutationCreateSetupIntentArgs = { }; +export type ApiMutationCreateWorkshopArgs = { + input: WorkshopItemCreateMutationInput; +}; + + export type ApiMutationDeleteCrudDemoItemArgs = { input: DeleteCrudDemoItemMutationInput; }; @@ -2666,6 +2672,17 @@ export type WorkshopItemConnection = { pageInfo: PageInfo; }; +export type WorkshopItemCreateMutationInput = { + clientMutationId?: InputMaybe; + name: Scalars['String']['input']; +}; + +export type WorkshopItemCreateMutationPayload = { + __typename?: 'WorkshopItemCreateMutationPayload'; + clientMutationId?: Maybe; + workshopItem?: Maybe; +}; + /** A Relay edge containing a `WorkshopItem` and its cursor. */ export type WorkshopItemEdge = { __typename?: 'WorkshopItemEdge'; @@ -3030,6 +3047,13 @@ export type AuthConfirmUserEmailMutationMutationVariables = Exact<{ export type AuthConfirmUserEmailMutationMutation = { __typename?: 'ApiMutation', confirm?: { __typename?: 'ConfirmEmailMutationPayload', ok?: boolean | null } | null }; +export type WorkshopItemCreateMutationMutationVariables = Exact<{ + input: WorkshopItemCreateMutationInput; +}>; + + +export type WorkshopItemCreateMutationMutation = { __typename?: 'ApiMutation', createWorkshop?: { __typename?: 'WorkshopItemCreateMutationPayload', workshopItem?: { __typename?: 'WorkshopItemType', id: string, name: string } | null } | null }; + export type WorkshopItemsListQueryVariables = Exact<{ [key: string]: never; }>; @@ -3160,6 +3184,7 @@ export const NotificationsListQueryDocument = {"kind":"Document","definitions":[ export const NotificationsListSubscriptionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"notificationsListSubscription"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notificationCreated"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"readAt"}},{"kind":"Field","name":{"kind":"Name","value":"data"}},{"kind":"Field","name":{"kind":"Name","value":"issuer"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const NotificationsListMarkAsReadMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"notificationsListMarkAsReadMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MarkReadAllNotificationsMutationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"markReadAllNotifications"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; export const AuthConfirmUserEmailMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"authConfirmUserEmailMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ConfirmEmailMutationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"confirm"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"ok"}}]}}]}}]} as unknown as DocumentNode; +export const WorkshopItemCreateMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"WorkshopItemCreateMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"WorkshopItemCreateMutationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createWorkshop"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"workshopItem"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]} as unknown as DocumentNode; export const WorkshopItemsListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"WorkshopItemsList"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"allWorkshopItems"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"edges"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"node"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const AuthChangePasswordMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"authChangePasswordMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ChangePasswordMutationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"changePassword"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"access"}},{"kind":"Field","name":{"kind":"Name","value":"refresh"}}]}}]}}]} as unknown as DocumentNode; export const AuthUpdateUserProfileMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"authUpdateUserProfileMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateCurrentUserMutationInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateCurrentUser"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"userProfile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"user"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"commonQueryCurrentUserFragment"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"commonQueryCurrentUserFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CurrentUserType"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"email"}},{"kind":"Field","name":{"kind":"Name","value":"firstName"}},{"kind":"Field","name":{"kind":"Name","value":"lastName"}},{"kind":"Field","name":{"kind":"Name","value":"roles"}},{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"otpVerified"}},{"kind":"Field","name":{"kind":"Name","value":"otpEnabled"}}]}}]} as unknown as DocumentNode; diff --git a/packages/webapp/src/app/app.component.tsx b/packages/webapp/src/app/app.component.tsx index 425978c..9d35dac 100644 --- a/packages/webapp/src/app/app.component.tsx +++ b/packages/webapp/src/app/app.component.tsx @@ -23,7 +23,7 @@ import { Admin } from '../routes/admin'; import { PasswordReset } from '../routes/auth/passwordReset'; import ValidateOtp from '../routes/auth/validateOtp'; import { AnonymousRoute, AuthRoute } from '../shared/components/routes'; -import { ConfirmEmail, Home, Login, Logout, NotFound, Profile, Signup, WorkshopItemsList } from './asyncComponents'; +import { ConfirmEmail, Home, Login, Logout, NotFound, Profile, Signup, WorkshopItemsList, WorkshopItemCreate } from './asyncComponents'; import { LANG_PREFIX, RoutesConfig } from './config/routes'; import { ValidRoutesProviders } from './providers'; @@ -66,6 +66,7 @@ export const App = () => { } /> } /> } /> + } /> } /> }> diff --git a/packages/webapp/src/app/asyncComponents.ts b/packages/webapp/src/app/asyncComponents.ts index cedd14f..b26984d 100644 --- a/packages/webapp/src/app/asyncComponents.ts +++ b/packages/webapp/src/app/asyncComponents.ts @@ -9,4 +9,5 @@ export const Profile = asyncComponent(() => import('../routes/profile')); export const ConfirmEmail = asyncComponent(() => import('../routes/auth/confirmEmail')); export const WorkshopItemsList = asyncComponent(() => import('../routes/workshops/workshopItemsList')); +export const WorkshopItemCreate = asyncComponent(() => import('../routes/workshops/workshopItemCreate')); //<-- IMPORT ROUTE --> diff --git a/packages/webapp/src/routes/workshops/workshopItemCreate/index.ts b/packages/webapp/src/routes/workshops/workshopItemCreate/index.ts new file mode 100644 index 0000000..a8fd7cf --- /dev/null +++ b/packages/webapp/src/routes/workshops/workshopItemCreate/index.ts @@ -0,0 +1 @@ +export { WorkshopItemCreate as default } from './workshopItemCreate.component'; diff --git a/packages/webapp/src/routes/workshops/workshopItemCreate/workshopItemCreate.component.tsx b/packages/webapp/src/routes/workshops/workshopItemCreate/workshopItemCreate.component.tsx new file mode 100644 index 0000000..caac897 --- /dev/null +++ b/packages/webapp/src/routes/workshops/workshopItemCreate/workshopItemCreate.component.tsx @@ -0,0 +1,47 @@ +import { useMutation } from '@apollo/client'; +import { gql } from '@sb/webapp-api-client'; +import { useApiForm } from '@sb/webapp-api-client/hooks'; +import { Input } from '@sb/webapp-core/components/forms'; +import { FC } from 'react'; + +import { WorkshopItemFields } from './workshopItemCreate.types'; + +export const workshopItemCreateMutation = gql(/* GraphQL */ ` + mutation WorkshopItemCreateMutation($input: WorkshopItemCreateMutationInput!) { + createWorkshop(input: $input) { + workshopItem { + id + name + } + } + } +`); + +export const WorkshopItemCreate: FC = () => { + const { form, setApolloGraphQLResponseErrors } = useApiForm({ + defaultValues: { + name: '', + }, + }); + + const [commitWorkshopItemCreateMutation] = useMutation(workshopItemCreateMutation); + + const onSubmit = async (formData: WorkshopItemFields) => { + const { data, errors } = await commitWorkshopItemCreateMutation({ + variables: { + input: { + name: formData.name, + }, + }, + onError(error) { + setApolloGraphQLResponseErrors(error.graphQLErrors); + }, + }); + }; + + return ( +
+ +
+ ); +}; diff --git a/packages/webapp/src/routes/workshops/workshopItemCreate/workshopItemCreate.types.ts b/packages/webapp/src/routes/workshops/workshopItemCreate/workshopItemCreate.types.ts new file mode 100644 index 0000000..1961f7c --- /dev/null +++ b/packages/webapp/src/routes/workshops/workshopItemCreate/workshopItemCreate.types.ts @@ -0,0 +1,3 @@ +export type WorkshopItemFields = { + name: string; +}