From 7258145a60f3201356dd64c2b2d4c91433cc75f1 Mon Sep 17 00:00:00 2001 From: Hossein Rashno Date: Thu, 8 May 2025 12:02:25 +0200 Subject: [PATCH] Add GraphQL --- apps/tax/codegen.yml | 24 +++ apps/tax/components/Example/QueryExample.tsx | 75 +++++++ apps/tax/graphql/apolloClient.ts | 32 +++ apps/tax/graphql/client.ts | 77 ------- apps/tax/graphql/index.ts | 4 +- apps/tax/graphql/taxSchema.graphql | 204 +++++++++++++++++++ apps/tax/graphql/withApollo.tsx | 15 ++ apps/tax/project.json | 7 + apps/tax/screens/queries/Asset.tsx | 44 ++++ apps/tax/screens/queries/Income.tsx | 44 ++++ apps/tax/screens/queries/Liability.tsx | 77 +++++++ apps/tax/screens/queries/TaxReturn.tsx | 74 +++++++ apps/tax/screens/queries/User.tsx | 115 +++++++++++ apps/tax/screens/queries/index.tsx | 5 + 14 files changed, 719 insertions(+), 78 deletions(-) create mode 100644 apps/tax/codegen.yml create mode 100644 apps/tax/components/Example/QueryExample.tsx create mode 100644 apps/tax/graphql/apolloClient.ts delete mode 100644 apps/tax/graphql/client.ts create mode 100644 apps/tax/graphql/taxSchema.graphql create mode 100644 apps/tax/graphql/withApollo.tsx create mode 100644 apps/tax/screens/queries/Asset.tsx create mode 100644 apps/tax/screens/queries/Income.tsx create mode 100644 apps/tax/screens/queries/Liability.tsx create mode 100644 apps/tax/screens/queries/TaxReturn.tsx create mode 100644 apps/tax/screens/queries/User.tsx create mode 100644 apps/tax/screens/queries/index.tsx diff --git a/apps/tax/codegen.yml b/apps/tax/codegen.yml new file mode 100644 index 00000000..d5b4091d --- /dev/null +++ b/apps/tax/codegen.yml @@ -0,0 +1,24 @@ +schema: + - apps/tax/graphql/taxSchema.graphql +documents: apps/tax/screens/queries/*.{ts,tsx} +generates: + apps/tax/graphql/schema.ts: + plugins: + - typescript + - typescript-operations + config: + exportFragmentSpreadSubTypes: true + scalars: + DateTime: Date + JSON: '{ [key: string]: any }' + namingConvention: + typeNames: change-case#pascalCase + apps/tax/graphql/fragmentTypes.json: + plugins: + - fragment-matcher + config: + module: commonjs + apolloClientVersion: 3 +hooks: + afterAllFileWrite: + - prettier --write diff --git a/apps/tax/components/Example/QueryExample.tsx b/apps/tax/components/Example/QueryExample.tsx new file mode 100644 index 00000000..6f28048d --- /dev/null +++ b/apps/tax/components/Example/QueryExample.tsx @@ -0,0 +1,75 @@ +import { + useMutation, + useLazyQuery, +} from "@apollo/client"; + +import {GetUserByPhoneQuery} from '../../graphql/schema' +import { withApollo } from "../../graphql/withApollo"; +import { + GET_USER_BY_PHONE_QUERY, + CREATE_TAX_RETURN_MUTATION, + CREATE_ASSET_MUTATION, + CREATE_INCOME_MUTATION, + CREATE_LIABILITY_MUTATION, +} from "../../screens/queries"; + + + +const QueryExample = () => { + const [fetchUser, { data, loading, error, refetch }] = useLazyQuery(GET_USER_BY_PHONE_QUERY); + + const [createTaxReturn] = useMutation(CREATE_TAX_RETURN_MUTATION); + const [createAsset] = useMutation(CREATE_ASSET_MUTATION); + const [createIncome] = useMutation(CREATE_INCOME_MUTATION); + const [createLiability] = useMutation(CREATE_LIABILITY_MUTATION); + + console.log(data); + + const onFetchUserByPhone = async () => { + await fetchUser({ + variables: { + phone: "772-8391", + }, + }); + } + + const onCreateTaxReturn = async () => { + if (data) { + await createTaxReturn({ variables: { taxReturn: {userId: Number(data.userByPhone.id), year: 2025, status: 'draft' }}}); + refetch(); + } + } + + const onCreateAsset = async () => { + if (data) { + await createAsset({ variables: { asset: {taxReturnId: 3, value: 10000, type: 'vehicle', assetId: '123', address: 'street' }}}); + refetch(); + } + } + + const onCreateIncome = async () => { + if (data) { + await createIncome({ variables: { income: {taxReturnId: 3, source: 'employment', amount: 2000, type: 'salary' }}}); + refetch(); + } + } + + const onCreateLiability = async () => { + if (data) { + await createLiability({ variables: { liability: {taxReturnId: 3, type: 'mortgage', interestPaid: 400, remainingBalance: 300000 }}}); + refetch(); + } + } + + return ( +
+
+
+
+
+
+
+ ); +} + +export default withApollo(QueryExample); diff --git a/apps/tax/graphql/apolloClient.ts b/apps/tax/graphql/apolloClient.ts new file mode 100644 index 00000000..e2ea3d19 --- /dev/null +++ b/apps/tax/graphql/apolloClient.ts @@ -0,0 +1,32 @@ +import { ApolloClient, ApolloLink,HttpLink, InMemoryCache, NormalizedCacheObject } from "@apollo/client"; +import { onError } from "@apollo/client/link/error"; + +// TODO: Move this to env files +const NEXT_PUBLIC_GRAPHQL_ENDPOINT = "https://7d5113d3dba5034.qaack.1xinter.net/graphql"; + +const errorLink = onError(({ graphQLErrors, networkError }) => { + if (graphQLErrors) { + for (const err of graphQLErrors) { + console.error(`[GraphQL error]: Message: ${err.message}, Location: ${err.locations}, Path: ${err.path}`); + } + } + if (networkError) { + console.error(`[Network error]: ${networkError}`); + } +}); + +const httpLink = new HttpLink({ + uri: NEXT_PUBLIC_GRAPHQL_ENDPOINT, + credentials: "omit", +}); + +const cache = new InMemoryCache({}); + +export const createApolloClient= (): ApolloClient => { + return new ApolloClient({ + ssrMode: typeof window === 'undefined', + link: ApolloLink.from([errorLink, httpLink]), + cache, + connectToDevTools: process.env.NODE_ENV === "development", + }); +} diff --git a/apps/tax/graphql/client.ts b/apps/tax/graphql/client.ts deleted file mode 100644 index 957a517a..00000000 --- a/apps/tax/graphql/client.ts +++ /dev/null @@ -1,77 +0,0 @@ -import getConfig from 'next/config' -import fetch from 'cross-fetch' -import { - ApolloClient, - InMemoryCache, - HttpLink, - ApolloLink, -} from '@apollo/client' -import { onError } from '@apollo/client/link/error' -import { RetryLink } from '@apollo/client/link/retry' -import { setContext } from '@apollo/client/link/context' - -const { publicRuntimeConfig, serverRuntimeConfig } = getConfig() -const isBrowser: boolean = process.browser - -let apolloClient = null - -// Polyfill fetch() on the server (used by apollo-client) -if (!isBrowser) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(global as any).fetch = fetch -} - -function create(initialState?: any) { - const { graphqlEndpoint: graphqlServerEndpoint } = serverRuntimeConfig - const { graphqlEndpoint: graphqlClientEndpoint } = publicRuntimeConfig - const graphqlEndpoint = graphqlServerEndpoint || graphqlClientEndpoint - const httpLink = new HttpLink({ - uri: graphqlEndpoint, - fetch, - }) - - const retryLink = new RetryLink() - - const errorLink = onError(({ graphQLErrors, networkError }) => { - if (graphQLErrors) - graphQLErrors.map(({ message, locations, path }) => - console.log( - `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`, - ), - ) - - if (networkError) console.log(`[Network error]: ${networkError}`) - }) - - const authLink = setContext((_, { headers }) => { - const token = 'mock_token' - return { - headers: { - ...headers, - authorization: token ? `Bearer ${token}` : '', - }, - } - }) - - return new ApolloClient({ - link: ApolloLink.from([retryLink, errorLink, authLink, httpLink]), - connectToDevTools: isBrowser, - ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once) - cache: new InMemoryCache().restore(initialState || {}), - }) -} - -export default function initApollo(initialState?: any) { - // Make sure to create a new client for every server-side request so that data - // isn't shared between connections (which would be bad) - if (!isBrowser) { - return create(initialState) - } - - // Reuse client on the client-side - if (!apolloClient) { - apolloClient = create(initialState) - } - - return apolloClient -} diff --git a/apps/tax/graphql/index.ts b/apps/tax/graphql/index.ts index c81a2048..30946209 100644 --- a/apps/tax/graphql/index.ts +++ b/apps/tax/graphql/index.ts @@ -1 +1,3 @@ -export { default as client } from './client' +export * from './apolloClient'; +export * from './withApollo'; +export * from './schema'; diff --git a/apps/tax/graphql/taxSchema.graphql b/apps/tax/graphql/taxSchema.graphql new file mode 100644 index 00000000..a0e0f1b6 --- /dev/null +++ b/apps/tax/graphql/taxSchema.graphql @@ -0,0 +1,204 @@ +# Indicates exactly one field must be supplied and this field must not be `null`. +directive @oneOf on INPUT_OBJECT + +type IncomeType { + id: ID! + taxReturnId: Float! + source: String! + amount: Float! + type: String! + subtype: String +} + +type AssetType { + id: ID! + taxReturnId: Float! + value: Float! + type: String! + assetId: String + address: String +} + +type LiabilityType { + id: ID! + taxReturnId: Float! + type: String! + subtype: String + propertyAddress: String + lenderName: String + lenderSsn: String + loanNumber: String + loanStartDate: String + loanTermYears: Float + annualPayment: Float + principalRepayment: Float + interestPaid: Float! + remainingBalance: Float! + details: String + accountNumber: String + issuer: String +} + +type TaxReturnType { + id: ID! + userId: Float! + year: Float! + status: String! + incomes: [IncomeType!] + assets: [AssetType!] + liabilities: [LiabilityType!] +} + +type UserType { + id: ID! + firstName: String! + lastName: String! + ssn: String! + streetAndHouseNumber: String + postalCode: String + city: String + email: String! + phone: String! + taxReturns: [TaxReturnType!] +} + +type Query { + users: [UserType!]! + user(id: Float!): UserType! + userByPhone(phone: String!): UserType! + incomes: [IncomeType!]! + income(id: Float!): IncomeType! + assets: [AssetType!]! + asset(id: Float!): AssetType! + liabilities: [LiabilityType!]! + liability(id: Float!): LiabilityType! + taxReturns: [TaxReturnType!]! + taxReturn(id: Float!): TaxReturnType! +} + +type Mutation { + createUser(user: CreateUserInput!): UserType! + updateUser(user: UpdateUserInput!): UserType! + deleteUser(id: Int!): Boolean! + createIncome(income: CreateIncomeInput!): IncomeType! + updateIncome(income: UpdateIncomeInput!): IncomeType! + deleteIncome(id: Int!): Boolean! + createAsset(asset: CreateAssetInput!): AssetType! + updateAsset(asset: UpdateAssetInput!): AssetType! + deleteAsset(id: Int!): Boolean! + createLiability(liability: CreateLiabilityInput!): LiabilityType! + updateLiability(liability: UpdateLiabilityInput!): LiabilityType! + deleteLiability(id: Int!): Boolean! + createTaxReturn(taxReturn: CreateTaxReturnInput!): TaxReturnType! + updateTaxReturn(taxReturn: UpdateTaxReturnInput!): TaxReturnType! + deleteTaxReturn(id: Int!): Boolean! +} + +input CreateUserInput { + firstName: String! + lastName: String! + ssn: String! + streetAndHouseNumber: String + postalCode: String + city: String + email: String! + phone: String! + taxReturns: [CreateTaxReturnInput!] +} + +input CreateTaxReturnInput { + userId: Float! + year: Float! + status: String! + assets: [CreateAssetInput!] + incomes: [CreateIncomeInput!] + liabilities: [CreateLiabilityInput!] +} + +input CreateAssetInput { + taxReturnId: Float! + value: Float! + type: String! + assetId: String + address: String +} + +input CreateIncomeInput { + taxReturnId: Float! + source: String! + amount: Float! + type: String! + subtype: String +} + +input CreateLiabilityInput { + taxReturnId: Float! + type: String! + subtype: String + propertyAddress: String + lenderName: String + lenderSsn: String + loanNumber: String + loanStartDate: String + loanTermYears: Float + annualPayment: Float + principalRepayment: Float + interestPaid: Float! + remainingBalance: Float! + details: String + accountNumber: String + issuer: String +} + +input UpdateUserInput { + id: Int! + firstName: String + lastName: String + streetAndHouseNumber: String + postalCode: String + city: String + email: String + phone: String +} + +input UpdateIncomeInput { + id: Int! + source: String + amount: Float + type: String + subtype: String +} + +input UpdateAssetInput { + id: Int! + value: Float + type: String + assetId: String + address: String +} + +input UpdateLiabilityInput { + id: Int! + type: String + subtype: String + propertyAddress: String + lenderName: String + lenderSsn: String + loanNumber: String + loanStartDate: String + loanTermYears: Float + annualPayment: Float + principalRepayment: Float + interestPaid: Float + remainingBalance: Float + details: String + accountNumber: String + issuer: String +} + +input UpdateTaxReturnInput { + id: Int! + userId: Float + year: Float + status: String +} diff --git a/apps/tax/graphql/withApollo.tsx b/apps/tax/graphql/withApollo.tsx new file mode 100644 index 00000000..91945fcd --- /dev/null +++ b/apps/tax/graphql/withApollo.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import { ApolloProvider } from "@apollo/client"; + +import { createApolloClient } from "./apolloClient"; + +export const withApollo =

(Component: React.ComponentType

) => { + return function ApolloWrappedComponent(props: P) { + const client = React.useMemo(() => createApolloClient(), []); + return ( + + + + ) + } +} diff --git a/apps/tax/project.json b/apps/tax/project.json index 721e260c..5f5a1310 100644 --- a/apps/tax/project.json +++ b/apps/tax/project.json @@ -95,6 +95,13 @@ }, "outputs": ["{workspaceRoot}/coverage/apps/tax"] }, + "codegen/frontend-client": { + "executor": "nx:run-commands", + "options": { + "command": "graphql-codegen --config apps/tax/codegen.yml" + }, + "outputs": ["{projectRoot}/tax/graphql/schema.ts"] + }, "extract-strings": { "executor": "nx:run-commands", "options": { diff --git a/apps/tax/screens/queries/Asset.tsx b/apps/tax/screens/queries/Asset.tsx new file mode 100644 index 00000000..4a68a412 --- /dev/null +++ b/apps/tax/screens/queries/Asset.tsx @@ -0,0 +1,44 @@ +import gql from 'graphql-tag' + +export const GET_ASSET_QUERY = gql` + query GetAsset($id: Float!) { + asset(id: $id) { + id + taxReturnId + value + type + assetId + address + } + } +` + +export const CREATE_ASSET_MUTATION = gql` + mutation CreateAsset($asset: CreateAssetInput!) { + createAsset(asset: $asset) { + taxReturnId + value + type + assetId + address + } + } +` + +export const UPDATE_ASSET_MUTATION = gql` + mutation UpdateAsset($asset: UpdateAssetInput!) { + updateAsset(asset: $asset) { + id + value + type + assetId + address + } + } +` + +export const DELETE_ASSET_MUTATION = gql` + mutation DeleteAsset($id: Int!) { + deleteAsset(id: $id) + } +` diff --git a/apps/tax/screens/queries/Income.tsx b/apps/tax/screens/queries/Income.tsx new file mode 100644 index 00000000..6ee37703 --- /dev/null +++ b/apps/tax/screens/queries/Income.tsx @@ -0,0 +1,44 @@ +import gql from 'graphql-tag' + +export const GET_INCOME_QUERY = gql` + query GetIncome($id: Float!) { + income(id: $id) { + id + taxReturnId + source + amount + type + subtype + } + } +` + +export const CREATE_INCOME_MUTATION = gql` + mutation CreateIncome($income: CreateIncomeInput!) { + createIncome(income: $income) { + taxReturnId + source + amount + type + subtype + } + } +` + +export const UPDATE_INCOME_MUTATION = gql` + mutation UpdateIncome($income: UpdateIncomeInput!) { + updateIncome(income: $income) { + id + source + amount + type + subtype + } + } +` + +export const DELETE_INCOME_MUTATION = gql` + mutation DeleteIncome($id: Int!) { + deleteIncome(id: $id) + } +` diff --git a/apps/tax/screens/queries/Liability.tsx b/apps/tax/screens/queries/Liability.tsx new file mode 100644 index 00000000..13db5177 --- /dev/null +++ b/apps/tax/screens/queries/Liability.tsx @@ -0,0 +1,77 @@ +import gql from 'graphql-tag' + +export const GET_LIABILITY_QUERY = gql` + query GetLiability($id: Float!) { + liability(id: $id) { + id + taxReturnId + type + subtype + propertyAddress + lenderName + lenderSsn + loanNumber + loanStartDate + loanTermYears + annualPayment + principalRepayment + interestPaid + remainingBalance + details + accountNumber + issuer + } + } +` + +export const CREATE_LIABILITY_MUTATION = gql` + mutation CreateLiability($liability: CreateLiabilityInput!) { + createLiability(liability: $liability) { + taxReturnId + type + subtype + propertyAddress + lenderName + lenderSsn + loanNumber + loanStartDate + loanTermYears + annualPayment + principalRepayment + interestPaid + remainingBalance + details + accountNumber + issuer + } + } +` + +export const UPDATE_LIABILITY_MUTATION = gql` + mutation UpdateLiability($liability: UpdateLiabilityInput!) { + updateLiability(liability: $liability) { + id + type + subtype + propertyAddress + lenderName + lenderSsn + loanNumber + loanStartDate + loanTermYears + annualPayment + principalRepayment + interestPaid + remainingBalance + details + accountNumber + issuer + } + } +` + +export const DELETE_LIABILITY_MUTATION = gql` + mutation DeleteLiability($id: Int!) { + deleteLiability(id: $id) + } +` diff --git a/apps/tax/screens/queries/TaxReturn.tsx b/apps/tax/screens/queries/TaxReturn.tsx new file mode 100644 index 00000000..9ba30c47 --- /dev/null +++ b/apps/tax/screens/queries/TaxReturn.tsx @@ -0,0 +1,74 @@ +import gql from 'graphql-tag' + +export const GET_TAX_RETURN_QUERY = gql` + query GetTaxReturn($id: Float!) { + taxReturn(id: $id) { + id + userId + year + status + assets { + id + taxReturnId + value + type + assetId + address + } + incomes { + id + taxReturnId + source + amount + type + subtype + } + liabilities { + id + taxReturnId + type + subtype + propertyAddress + lenderName + lenderSsn + loanNumber + loanStartDate + loanTermYears + annualPayment + principalRepayment + interestPaid + remainingBalance + details + accountNumber + issuer + } + } + } +` + +export const CREATE_TAX_RETURN_MUTATION = gql` + mutation CreateTaxReturn($taxReturn: CreateTaxReturnInput!) { + createTaxReturn(taxReturn: $taxReturn) { + userId + year + status + } + } +` + +export const UPDATE_TAX_RETURN_MUTATION = gql` + mutation UpdateTaxReturn($taxReturn: UpdateTaxReturnInput!) { + updateTaxReturn(taxReturn: $taxReturn) { + id + userId + year + status + } + } +` + +export const DELETE_TAX_RETURN_MUTATION = gql` + mutation DeleteTaxReturn($id: Int!) { + deleteTaxReturn(id: $id) + } +` diff --git a/apps/tax/screens/queries/User.tsx b/apps/tax/screens/queries/User.tsx new file mode 100644 index 00000000..34e2e33d --- /dev/null +++ b/apps/tax/screens/queries/User.tsx @@ -0,0 +1,115 @@ +import gql from 'graphql-tag' + +export const GET_USER_QUERY = gql` + query GetUser($id: Float!) { + user(id: $id) { + id + firstName + lastName + ssn + streetAndHouseNumber + postalCode + city + email + phone + taxReturns { + id + userId + year + status + assets { + id + taxReturnId + value + type + assetId + address + } + incomes { + id + taxReturnId + source + amount + type + subtype + } + liabilities { + id + taxReturnId + type + subtype + propertyAddress + lenderName + lenderSsn + loanNumber + loanStartDate + loanTermYears + annualPayment + principalRepayment + interestPaid + remainingBalance + details + accountNumber + issuer + } + } + } + } +` + +export const GET_USER_BY_PHONE_QUERY = gql` + query GetUserByPhone($phone: String!) { + userByPhone(phone: $phone) { + id + firstName + lastName + ssn + streetAndHouseNumber + postalCode + city + email + phone + taxReturns { + id + userId + year + status + assets { + id + taxReturnId + value + type + assetId + address + } + incomes { + id + taxReturnId + source + amount + type + subtype + } + liabilities { + id + taxReturnId + type + subtype + propertyAddress + lenderName + lenderSsn + loanNumber + loanStartDate + loanTermYears + annualPayment + principalRepayment + interestPaid + remainingBalance + details + accountNumber + issuer + } + } + } + } +` diff --git a/apps/tax/screens/queries/index.tsx b/apps/tax/screens/queries/index.tsx new file mode 100644 index 00000000..19119484 --- /dev/null +++ b/apps/tax/screens/queries/index.tsx @@ -0,0 +1,5 @@ +export * from './Income' +export * from './Asset' +export * from './Liability' +export * from './TaxReturn' +export * from './User'