Skip to content

Commit

Permalink
feat: Login page with basic validation
Browse files Browse the repository at this point in the history
 - Config Codegen for graphql schema and operations
 - Create auth logic
 - Create login wall
  • Loading branch information
Marcelo Reis committed May 2, 2021
1 parent e871ef5 commit 90b7636
Show file tree
Hide file tree
Showing 15 changed files with 2,637 additions and 126 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/generated/**/*
File renamed without changes.
13 changes: 13 additions & 0 deletions codegen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
overwrite: true
schema: "./schema.graphql"
documents: "src/**/*.graphql"
generates:
src/generated/graphql.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
config:
withHooks: true
withMutationFn: true

11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
"dependencies": {
"@aircall/tractor": "^1.9.18",
"@apollo/client": "^3.3.16",
"@graphql-codegen/cli": "^1.21.4",
"@graphql-codegen/typescript": "^1.22.0",
"@graphql-codegen/typescript-operations": "^1.17.16",
"@graphql-codegen/typescript-react-apollo": "^2.2.4",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^13.1.8",
Expand All @@ -13,6 +17,7 @@
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-router-dom": "^5.1.7",
"codegen": "^0.1.0",
"graphql": "^15.5.0",
"prettier": "^2.2.1",
"react": "^17.0.2",
Expand All @@ -29,7 +34,8 @@
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "eslint src/**/*.{tsx,ts} && prettier --check src/**/*.{tsx,ts}",
"format": "prettier --write src/**/*.{tsx,ts}"
"format": "prettier --write src/**/*.{tsx,ts}",
"generate": "graphql-codegen --config codegen.yml"
},
"eslintConfig": {
"extends": [
Expand All @@ -48,5 +54,6 @@
"last 1 firefox version",
"last 1 safari version"
]
}
},
"devDependencies": {}
}
4 changes: 2 additions & 2 deletions src/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import { useAuth } from "./apollo/auth";
import LoginPage from "./pages/Login";

const Routes = () => {
const { isLogged } = useAuth();
const { user } = useAuth();

if (!isLogged) {
if (!user) {
return <LoginPage />;
}

Expand Down
40 changes: 37 additions & 3 deletions src/apollo/auth.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
import { makeVar, useReactiveVar } from "@apollo/client";
import { LoginMutation, useLoginMutation } from "../generated/graphql";

export const tokenVar = makeVar<string | null>(null);
const auth = JSON.parse(localStorage.getItem("login")!) as
| LoginMutation["login"]
| null;

export const authVar = makeVar<LoginMutation["login"] | null>(auth);

authVar.onNextChange((auth) => {
localStorage.setItem("login", JSON.stringify(auth));
});

export const useAuth = () => {
const token = useReactiveVar(tokenVar);
const [loginMutation, result] = useLoginMutation();
const auth = useReactiveVar(authVar);

const login = async ({
username,
password,
}: {
username: string;
password: string;
}) => {
const result = await loginMutation({
variables: { input: { username, password } },
});

authVar(result.data?.login);
};

const logout = () => {
authVar(null);
};

return { token, isLogged: !!token };
return {
user: auth?.user,
loading: result.loading,
error: result.error,
login,
logout,
};
};
5 changes: 3 additions & 2 deletions src/apollo/link.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { createHttpLink } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { authVar } from "./auth";

const httpLink = createHttpLink({
uri: "https://frontend-test-api.aircall.io/graphql",
credentials: "same-origin",
});

const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("tokenID");
const token = authVar();

return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
authorization: token ? `Bearer ${token.access_token}` : "",
},
};
});
Expand Down
8 changes: 4 additions & 4 deletions src/apollo/mocked-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { ComponentProps } from "react";
import { MockedProvider } from "@apollo/client/testing";
import { MockedProvider as Provider } from "@apollo/client/testing";
import { createCache } from "./cache";

const createMockedProvider = (props: ComponentProps<typeof MockedProvider>) => {
const MockedProvider = (props: ComponentProps<typeof Provider>) => {
const cache = createCache();

return () => <MockedProvider addTypename={false} cache={cache} {...props} />;
return <Provider cache={cache} {...props} />;
};

export default createMockedProvider;
export default MockedProvider;
166 changes: 166 additions & 0 deletions src/generated/graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
export type Maybe<T> = T | null;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
const defaultOptions = {}
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
};

export type AddNoteInput = {
activityId: Scalars['ID'];
content: Scalars['String'];
};

export type AuthResponseType = {
__typename?: 'AuthResponseType';
access_token?: Maybe<Scalars['String']>;
user?: Maybe<UserType>;
};

export type Call = {
__typename?: 'Call';
call_type: Scalars['String'];
created_at: Scalars['String'];
direction: Scalars['String'];
duration: Scalars['Float'];
from: Scalars['String'];
id: Scalars['ID'];
is_archived: Scalars['Boolean'];
notes: Array<Note>;
to: Scalars['String'];
via: Scalars['String'];
};

export type LoginInput = {
password: Scalars['String'];
username: Scalars['String'];
};

export type Mutation = {
__typename?: 'Mutation';
addNote: Call;
archiveCall: Call;
login: AuthResponseType;
refreshToken: AuthResponseType;
};


export type MutationAddNoteArgs = {
input: AddNoteInput;
};


export type MutationArchiveCallArgs = {
id: Scalars['ID'];
};


export type MutationLoginArgs = {
input: LoginInput;
};

export type Note = {
__typename?: 'Note';
content: Scalars['String'];
id: Scalars['ID'];
};

export type PaginatedCalls = {
__typename?: 'PaginatedCalls';
hasNextPage: Scalars['Boolean'];
nodes?: Maybe<Array<Call>>;
totalCount: Scalars['Int'];
};

export type Query = {
__typename?: 'Query';
call?: Maybe<Call>;
me: UserType;
paginatedCalls: PaginatedCalls;
};


export type QueryCallArgs = {
id: Scalars['ID'];
};


export type QueryPaginatedCallsArgs = {
limit?: Maybe<Scalars['Float']>;
offset?: Maybe<Scalars['Float']>;
};

export type Subscription = {
__typename?: 'Subscription';
onUpdatedCall?: Maybe<Call>;
};

export type UserType = {
__typename?: 'UserType';
id: Scalars['String'];
username: Scalars['String'];
};

export type LoginMutationVariables = Exact<{
input: LoginInput;
}>;


export type LoginMutation = (
{ __typename?: 'Mutation' }
& { login: (
{ __typename?: 'AuthResponseType' }
& Pick<AuthResponseType, 'access_token'>
& { user?: Maybe<(
{ __typename?: 'UserType' }
& Pick<UserType, 'id' | 'username'>
)> }
) }
);


export const LoginDocument = gql`
mutation Login($input: LoginInput!) {
login(input: $input) {
access_token
user {
id
username
}
}
}
`;
export type LoginMutationFn = Apollo.MutationFunction<LoginMutation, LoginMutationVariables>;

/**
* __useLoginMutation__
*
* To run a mutation, you first call `useLoginMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useLoginMutation` 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 [loginMutation, { data, loading, error }] = useLoginMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useLoginMutation(baseOptions?: Apollo.MutationHookOptions<LoginMutation, LoginMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<LoginMutation, LoginMutationVariables>(LoginDocument, options);
}
export type LoginMutationHookResult = ReturnType<typeof useLoginMutation>;
export type LoginMutationResult = Apollo.MutationResult<LoginMutation>;
export type LoginMutationOptions = Apollo.BaseMutationOptions<LoginMutation, LoginMutationVariables>;
9 changes: 9 additions & 0 deletions src/operations/Login.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
mutation Login($input: LoginInput!) {
login(input: $input) {
access_token
user {
id
username
}
}
}
9 changes: 8 additions & 1 deletion src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import React from "react";
import { Spacer, Typography } from "@aircall/tractor";
import { Button, Spacer, Typography } from "@aircall/tractor";
import { useAuth } from "../apollo/auth";

const HomePage = () => {
const { logout } = useAuth();

return (
<div>
<Spacer space="s" direction="vertical">
<Typography variant="displayXL">AirCall</Typography>

<Button onClick={logout} variant="destructive">
Logout
</Button>
</Spacer>
</div>
);
Expand Down
Loading

0 comments on commit 90b7636

Please sign in to comment.