Skip to content

Commit

Permalink
New GQL client (Onboarding) (#672)
Browse files Browse the repository at this point in the history
* Experimental: new GQL client

* Remove wonka

* Remove network error check

* Updates

* Use new useDeferredQuery signature

* Updates

* Switch payment to new client

* Update to beta

* Update to beta 2

* Update

* Beta 4
  • Loading branch information
bloodyowl authored Apr 8, 2024
1 parent a00cea7 commit 558b908
Show file tree
Hide file tree
Showing 32 changed files with 351 additions and 544 deletions.
6 changes: 3 additions & 3 deletions clients/banking/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
"@sentry/react": "7.109.0",
"@swan-io/boxed": "2.1.1",
"@swan-io/chicane": "2.0.0",
"@swan-io/lake": "7.3.2",
"@swan-io/request": "1.0.2",
"@swan-io/shared-business": "7.3.2",
"@swan-io/lake": "7.3.3",
"@swan-io/request": "1.0.4",
"@swan-io/shared-business": "7.3.3",
"@swan-io/use-form": "2.0.0",
"@urql/devtools": "2.0.3",
"@urql/exchange-graphcache": "7.0.0",
Expand Down
14 changes: 5 additions & 9 deletions clients/onboarding/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,16 @@
"clean": "tsc --clean"
},
"dependencies": {
"@0no-co/graphql.web": "1.0.6",
"@formatjs/intl": "2.10.1",
"@juggle/resize-observer": "3.4.0",
"@sentry/react": "7.109.0",
"@swan-io/boxed": "2.1.1",
"@swan-io/chicane": "2.0.0",
"@swan-io/lake": "7.3.2",
"@swan-io/request": "1.0.2",
"@swan-io/shared-business": "7.3.2",
"@swan-io/graphql-client": "0.1.0-beta4",
"@swan-io/lake": "7.3.3",
"@swan-io/request": "1.0.4",
"@swan-io/shared-business": "7.3.3",
"@swan-io/use-form": "2.0.0",
"@urql/devtools": "2.0.3",
"@urql/exchange-graphcache": "7.0.0",
"core-js": "3.36.1",
"dayjs": "1.11.10",
"nanoid": "5.0.6",
Expand All @@ -33,9 +31,7 @@
"react-ux-form": "1.5.0",
"tggl-client": "1.13.2",
"ts-pattern": "5.1.0",
"urql": "4.0.7",
"uuid": "9.0.1",
"wonka": "6.3.4"
"uuid": "9.0.1"
},
"devDependencies": {
"@testing-library/react": "14.2.2",
Expand Down
126 changes: 50 additions & 76 deletions clients/onboarding/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { AsyncData, Option, Result } from "@swan-io/boxed";
import { AsyncData, Result } from "@swan-io/boxed";
import { ClientContext, useDeferredQuery, useMutation } from "@swan-io/graphql-client";
import { ErrorBoundary } from "@swan-io/lake/src/components/ErrorBoundary";
import { LoadingView } from "@swan-io/lake/src/components/LoadingView";
import { ToastStack } from "@swan-io/lake/src/components/ToastStack";
import { WithPartnerAccentColor } from "@swan-io/lake/src/components/WithPartnerAccentColor";
import { colors, invariantColors } from "@swan-io/lake/src/constants/design";
import { useBoolean } from "@swan-io/lake/src/hooks/useBoolean";
import { useUrqlMutation } from "@swan-io/lake/src/hooks/useUrqlMutation";
import { useUrqlQuery } from "@swan-io/lake/src/hooks/useUrqlQuery";
import { useEffect } from "react";
import { P, match } from "ts-pattern";
import { Provider as ClientProvider } from "urql";
import { ErrorView } from "./components/ErrorView";
import { Redirect } from "./components/Redirect";
import {
Expand All @@ -24,12 +21,12 @@ import { PopupCallbackPage } from "./pages/PopupCallbackPage";
import { OnboardingCompanyWizard } from "./pages/company/CompanyOnboardingWizard";
import { OnboardingIndividualWizard } from "./pages/individual/OnboardingIndividualWizard";
import { env } from "./utils/env";
import { client } from "./utils/gql";
import { locale } from "./utils/i18n";
import { logFrontendError } from "./utils/logger";
import { TrackingProvider, useSessionTracking } from "./utils/matomo";
import { Router } from "./utils/routes";
import { updateTgglContext } from "./utils/tggl";
import { unauthenticatedClient } from "./utils/urql";

type Props = {
onboardingId: string;
Expand All @@ -55,74 +52,51 @@ const PageMetadata = ({
};

const FlowPicker = ({ onboardingId }: Props) => {
const { data } = useUrqlQuery({
query: GetOnboardingDocument,
variables: { id: onboardingId, language: locale.language },
});

const [isReadyToRender, setIsReadyToRender] = useBoolean(false);

const onboardingInfo = data
.toOption()
.flatMap(data => data.toOption())
.flatMap(({ onboardingInfo }) => Option.fromNullable(onboardingInfo));

const accountHolderType = onboardingInfo
.map(onboardingInfo => onboardingInfo.info.__typename)
.toUndefined();
const onboardingLanguage = onboardingInfo
.flatMap(onboardingInfo => Option.fromNullable(onboardingInfo.language))
.toUndefined();

const [, updateIndividualOnboarding] = useUrqlMutation(UpdateIndividualOnboardingDocument);
const [, updateCompanyOnboarding] = useUrqlMutation(UpdateCompanyOnboardingDocument);

// Set the onboarding language based on the browser locale
// We do this here to avoid having the loader jump
const [data, { query }] = useDeferredQuery(GetOnboardingDocument);
const [updateIndividualOnboarding, individualOnboardingUpdate] = useMutation(
UpdateIndividualOnboardingDocument,
);
const [updateCompanyOnboarding, companyOnboardingUpdate] = useMutation(
UpdateCompanyOnboardingDocument,
);

useEffect(() => {
// Already tried updating, if the language is still not matching, don't retry
if (isReadyToRender) {
return;
}

// Bail out if the onboarding language is already matching the browser one
if (onboardingLanguage === locale.language) {
setIsReadyToRender.on();
return;
}

if (accountHolderType != null) {
const languageUpdate = match(accountHolderType)
.with("OnboardingCompanyAccountHolderInfo", () =>
updateCompanyOnboarding({
input: { onboardingId, language: locale.language },
language: locale.language,
}).tap(() => setIsReadyToRender.on()),
)
.with("OnboardingIndividualAccountHolderInfo", () =>
updateIndividualOnboarding({
input: { onboardingId, language: locale.language },
language: locale.language,
}).tap(() => setIsReadyToRender.on()),
)
.exhaustive();

return () => languageUpdate.cancel();
}
}, [
onboardingId,
accountHolderType,
onboardingLanguage,
updateIndividualOnboarding,
updateCompanyOnboarding,
setIsReadyToRender,
isReadyToRender,
]);

return match(isReadyToRender ? data : AsyncData.Loading())
.with(AsyncData.P.NotAsked, AsyncData.P.Loading, () => <LoadingView color={colors.gray[400]} />)
.with(AsyncData.P.Done(Result.P.Error(P.select())), error => <ErrorView error={error} />)
.with(AsyncData.P.Done(Result.P.Ok(P.select())), data => {
const request = query({ id: onboardingId, language: locale.language }).tapOk(
({ onboardingInfo }) => {
if (onboardingInfo?.language === locale.language) {
return;
}
return match(onboardingInfo?.info.__typename)
.with("OnboardingCompanyAccountHolderInfo", () =>
updateCompanyOnboarding({
input: { onboardingId, language: locale.language },
language: locale.language,
}),
)
.with("OnboardingIndividualAccountHolderInfo", () =>
updateIndividualOnboarding({
input: { onboardingId, language: locale.language },
language: locale.language,
}),
)
.otherwise(() => {});
},
);

return () => request.cancel();
}, [onboardingId]);

Check warning on line 87 in clients/onboarding/src/App.tsx

View workflow job for this annotation

GitHub Actions / Test & build

React Hook useEffect has missing dependencies: 'query', 'updateCompanyOnboarding', and 'updateIndividualOnboarding'. Either include them or remove the dependency array

return match({ data, companyOnboardingUpdate, individualOnboardingUpdate })
.with(
{ data: P.union(AsyncData.P.NotAsked, AsyncData.P.Loading) },
{ companyOnboardingUpdate: AsyncData.P.Loading },
{ individualOnboardingUpdate: AsyncData.P.Loading },
() => <LoadingView color={colors.gray[400]} />,
)
.with({ data: AsyncData.P.Done(Result.P.Error(P.select())) }, error => (
<ErrorView error={error} />
))
.with({ data: AsyncData.P.Done(Result.P.Ok(P.select())) }, data => {
const onboardingInfo = data.onboardingInfo;

if (onboardingInfo == null) {
Expand Down Expand Up @@ -196,9 +170,9 @@ export const App = () => {
<ErrorBoundary
key={route?.name}
onError={error => logFrontendError(error)}
fallback={({ error }) => <ErrorView error={error} />}
fallback={() => <ErrorView />}
>
<ClientProvider value={unauthenticatedClient}>
<ClientContext.Provider value={client}>
{match(route)
.with(
{ name: "PopupCallback" },
Expand All @@ -215,7 +189,7 @@ export const App = () => {
))
.with(P.nullish, () => <NotFoundPage />)
.exhaustive()}
</ClientProvider>
</ClientContext.Provider>

<ToastStack />
</ErrorBoundary>
Expand Down
49 changes: 31 additions & 18 deletions clients/onboarding/src/components/ErrorView.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Array, Option } from "@swan-io/boxed";
import { ClientError } from "@swan-io/graphql-client";
import { BorderedIcon } from "@swan-io/lake/src/components/BorderedIcon";
import { Box } from "@swan-io/lake/src/components/Box";
import { LakeHeading } from "@swan-io/lake/src/components/LakeHeading";
import { LakeText } from "@swan-io/lake/src/components/LakeText";
import { Space } from "@swan-io/lake/src/components/Space";
import { isNotNullish } from "@swan-io/lake/src/utils/nullish";
import { translateError } from "@swan-io/shared-business/src/utils/i18n";
import { useState } from "react";
import { StyleProp, StyleSheet, ViewStyle } from "react-native";
import { isCombinedError } from "../utils/urql";
import { errorToRequestId } from "../utils/gql";

const styles = StyleSheet.create({
base: {
Expand All @@ -16,24 +18,35 @@ const styles = StyleSheet.create({
});

type Props = {
error?: Error;
error?: ClientError;
style?: StyleProp<ViewStyle>;
};

export const ErrorView = ({ error, style }: Props) => (
<Box alignItems="center" justifyContent="center" style={[styles.base, style]}>
<BorderedIcon color="negative" name="lake-error" size={100} padding={24} />
<Space height={24} />
export const ErrorView = ({ error, style }: Props) => {
const [requestId] = useState<Option<string>>(() => {
if (error == undefined) {
return Option.None();
}
return Array.findMap(ClientError.toArray(error), error =>
Option.fromNullable(errorToRequestId.get(error)),
);
});

<LakeHeading level={1} variant="h3" align="center">
{translateError(error)}
</LakeHeading>
return (
<Box alignItems="center" justifyContent="center" style={[styles.base, style]}>
<BorderedIcon color="negative" name="lake-error" size={100} padding={24} />
<Space height={24} />

{isCombinedError(error) && isNotNullish(error.requestId) ? (
<>
<Space height={4} />
<LakeText variant="smallRegular">ID: {error.requestId}</LakeText>
</>
) : null}
</Box>
);
<LakeHeading level={1} variant="h3" align="center">
{translateError(error)}
</LakeHeading>

{requestId.isSome() ? (
<>
<Space height={4} />
<LakeText variant="smallRegular">ID: {requestId.get()}</LakeText>
</>
) : null}
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMutation } from "@swan-io/graphql-client";
import { LakeHeading } from "@swan-io/lake/src/components/LakeHeading";
import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
import { LakeText } from "@swan-io/lake/src/components/LakeText";
Expand All @@ -6,7 +7,6 @@ import { ResponsiveContainer } from "@swan-io/lake/src/components/ResponsiveCont
import { Space } from "@swan-io/lake/src/components/Space";
import { Tile } from "@swan-io/lake/src/components/Tile";
import { breakpoints } from "@swan-io/lake/src/constants/design";
import { useUrqlMutation } from "@swan-io/lake/src/hooks/useUrqlMutation";
import { showToast } from "@swan-io/lake/src/state/toasts";
import { filterRejectionsToResult } from "@swan-io/lake/src/utils/urql";
import { CountryCCA3, companyCountries } from "@swan-io/shared-business/src/constants/countries";
Expand Down Expand Up @@ -86,7 +86,7 @@ const getCompanyTypes = (country: CountryCCA3): RadioGroupItem<CompanyType>[] =>
};

export const OnboardingCompanyBasicInfo = ({ nextStep, onboardingId, initialValues }: Props) => {
const [updateResult, updateOnboarding] = useUrqlMutation(UpdateCompanyOnboardingDocument);
const [updateOnboarding, updateResult] = useMutation(UpdateCompanyOnboardingDocument);

const { Field, submitForm, listenFields, setFieldValue } = useForm({
country: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Array, Option } from "@swan-io/boxed";
import { useMutation } from "@swan-io/graphql-client";
import { LakeText } from "@swan-io/lake/src/components/LakeText";
import { ResponsiveContainer } from "@swan-io/lake/src/components/ResponsiveContainer";
import { Space } from "@swan-io/lake/src/components/Space";
import { Tile } from "@swan-io/lake/src/components/Tile";
import { breakpoints } from "@swan-io/lake/src/constants/design";
import { useBoolean } from "@swan-io/lake/src/hooks/useBoolean";
import { useUrqlMutation } from "@swan-io/lake/src/hooks/useUrqlMutation";
import { filterRejectionsToResult } from "@swan-io/lake/src/utils/urql";
import { ConfirmModal } from "@swan-io/shared-business/src/components/ConfirmModal";
import {
Expand Down Expand Up @@ -56,8 +56,8 @@ export const OnboardingCompanyDocuments = ({
supportingDocumentCollectionStatus,
templateLanguage,
}: Props) => {
const [updateResult, updateOnboarding] = useUrqlMutation(UpdateCompanyOnboardingDocument);
const [, generateSupportingDocumentUploadUrl] = useUrqlMutation(
const [updateOnboarding, updateResult] = useMutation(UpdateCompanyOnboardingDocument);
const [generateSupportingDocumentUploadUrl] = useMutation(
GenerateSupportingDocumentUploadUrlDocument,
);
const [showConfirmModal, setShowConfirmModal] = useBoolean(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useDeferredQuery, useMutation } from "@swan-io/graphql-client";
import { LakeAlert } from "@swan-io/lake/src/components/LakeAlert";
import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
import { LakeText } from "@swan-io/lake/src/components/LakeText";
Expand All @@ -8,8 +9,6 @@ import { Space } from "@swan-io/lake/src/components/Space";
import { Tile } from "@swan-io/lake/src/components/Tile";
import { breakpoints, negativeSpacings } from "@swan-io/lake/src/constants/design";
import { useFirstMountState } from "@swan-io/lake/src/hooks/useFirstMountState";
import { useUrqlMutation } from "@swan-io/lake/src/hooks/useUrqlMutation";
import { useUrqlQuery } from "@swan-io/lake/src/hooks/useUrqlQuery";
import { showToast } from "@swan-io/lake/src/state/toasts";
import { noop } from "@swan-io/lake/src/utils/function";
import { emptyToUndefined } from "@swan-io/lake/src/utils/nullish";
Expand Down Expand Up @@ -123,7 +122,7 @@ export const OnboardingCompanyOrganisation1 = ({
onboardingId,
serverValidationErrors,
}: Props) => {
const [updateResult, updateOnboarding] = useUrqlMutation(UpdateCompanyOnboardingDocument);
const [updateOnboarding, updateResult] = useMutation(UpdateCompanyOnboardingDocument);
const isFirstMount = useFirstMountState();
const canSetTaxIdentification = match({ accountCountry, country })
.with({ accountCountry: "DEU", country: "DEU" }, () => true)
Expand Down Expand Up @@ -269,10 +268,14 @@ export const OnboardingCompanyOrganisation1 = ({

const [siren, setSiren] = useState<string>();

const { data } = useUrqlQuery(
{ query: GetCompanyInfoDocument, variables: { siren: siren as string }, pause: siren == null },
[siren],
);
const [data, { query }] = useDeferredQuery(GetCompanyInfoDocument);

useEffect(() => {
if (siren != null) {
const request = query({ siren });
return () => request.cancel();
}
}, [siren, query]);

const companyInfo = data
.toOption()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useMutation } from "@swan-io/graphql-client";
import { LakeLabel } from "@swan-io/lake/src/components/LakeLabel";
import { Item, LakeSelect } from "@swan-io/lake/src/components/LakeSelect";
import { LakeTextInput } from "@swan-io/lake/src/components/LakeTextInput";
Expand All @@ -6,7 +7,6 @@ import { Space } from "@swan-io/lake/src/components/Space";
import { Tile } from "@swan-io/lake/src/components/Tile";
import { breakpoints } from "@swan-io/lake/src/constants/design";
import { useFirstMountState } from "@swan-io/lake/src/hooks/useFirstMountState";
import { useUrqlMutation } from "@swan-io/lake/src/hooks/useUrqlMutation";
import { showToast } from "@swan-io/lake/src/state/toasts";
import { noop } from "@swan-io/lake/src/utils/function";
import { emptyToUndefined } from "@swan-io/lake/src/utils/nullish";
Expand Down Expand Up @@ -87,7 +87,7 @@ export const OnboardingCompanyOrganisation2 = ({
initialMonthlyPaymentVolume,
serverValidationErrors,
}: Props) => {
const [updateResult, updateOnboarding] = useUrqlMutation(UpdateCompanyOnboardingDocument);
const [updateOnboarding, updateResult] = useMutation(UpdateCompanyOnboardingDocument);
const isFirstMount = useFirstMountState();

const { Field, submitForm, setFieldError } = useForm({
Expand Down
Loading

0 comments on commit 558b908

Please sign in to comment.