Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { PrivilegeLevel } from './auth/ducks/types';
import { C4CState } from './store';
import { getPrivilegeLevel } from './auth/ducks/selectors';
import { useSelector } from 'react-redux';
import Onboarding from './containers/onboarding';

const { Content } = Layout;

Expand All @@ -32,6 +33,7 @@ export enum Routes {
FORGOT_PASSWORD_REQUEST = '/forgot-password',
FORGOT_PASSWORD_RESET = '/forgot-password-reset/:key',
VERIFY_EMAIL = '/verify/:key',
ONBOARDING = '/onboarding',
}

const App: React.FC = () => {
Expand Down Expand Up @@ -72,6 +74,11 @@ const App: React.FC = () => {
exact
component={VerifyEmail}
/>
<Route
path={Routes.ONBOARDING}
exact
component={Onboarding}
/>
<Route path="*" exact component={NotFound} />
</Switch>
);
Expand All @@ -96,6 +103,11 @@ const App: React.FC = () => {
exact
component={VerifyEmail}
/>
<Route
path={Routes.ONBOARDING}
exact
component={Onboarding}
/>
<Route path="*" exact component={NotFound} />
</Switch>
);
Expand Down
37 changes: 37 additions & 0 deletions src/api/protectedApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,48 @@ export interface ProtectedApiClient {
newPassword: string;
}) => Promise<void>;
readonly deleteUser: (request: { password: string }) => Promise<void>;
readonly postOnboardingForm: (
request: PostRequestData,
) => Promise<PostRequestData>;
readonly getOnboardingData: () => Promise<GetResponseData[]>;
}

export enum ProtectedApiClientRoutes {
CHANGE_PASSWORD = '/api/v1/protected/user/change_password',
DELETE_USER = '/api/v1/protected/user/',
}

export interface PostRequestData {
title: string;
body: string;
userId: number;
}

export interface GetResponseData {
id: number;
title: string;
body: string;
userId: number;
}

const postOnboardingForm = async (
request: PostRequestData,
): Promise<PostRequestData> => {
const res = await AppAxiosInstance.post(
'https://jsonplaceholder.typicode.com/posts',
request,
{ headers: { 'Access-Control-Allow-Origin': '*' } },
);
return res.data;
};

const getOnboardingData = async (): Promise<GetResponseData[]> => {
const res = await AppAxiosInstance.get(
'https://jsonplaceholder.typicode.com/posts',
);
return res.data;
};

const changePassword = (request: {
currentPassword: string;
newPassword: string;
Expand All @@ -34,6 +69,8 @@ const deleteUser = (request: { password: string }): Promise<void> => {
const Client: ProtectedApiClient = Object.freeze({
changePassword,
deleteUser,
postOnboardingForm,
getOnboardingData,
});

export default Client;
24 changes: 24 additions & 0 deletions src/components/onboarding/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Card, Typography } from 'antd';
import React from 'react';
const { Title, Paragraph } = Typography;

interface ResponseCardProps {
id: number;
title: string;
body: string;
userId: number;
}

export const ResponseCard: React.FC<ResponseCardProps> = ({
id,
title,
body,
userId,
}) => {
return (
<Card bodyStyle={{ width: '200px' }}>
<Title level={1}>{id}</Title>
<Paragraph>{body}</Paragraph>
</Card>
);
};
178 changes: 178 additions & 0 deletions src/containers/onboarding/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import React, { useState } from 'react';
import { Button, Card, Form, Input, Typography } from 'antd';
import styled from 'styled-components';
import { ResponseCard } from '../../components/onboarding';
import ProtectedApiClient, {
GetResponseData,
PostRequestData,
} from '../../api/protectedApiClient';
import {
AsyncRequest,
AsyncRequestCompleted,
AsyncRequestFailed,
asyncRequestIsComplete,
AsyncRequestLoading,
AsyncRequestNotStarted,
} from '../../utils/asyncRequest';

const { Title, Paragraph } = Typography;

const Container = styled.div`
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-items: center;
width: 100%;
`;

const FormCard = styled(Card)`
display: flex;
width: 50%;
border-radius: 5px;
`;

const OnboardingPageTitle = styled(Title)`
display: flex;
width: 100%;
justify-content: center;
`;

const FormInput = styled(Input)`
display: flex;
width: 80%;
border: 1px 1px black;
`;

const StyledButton = styled(Button)`
width: 30%;
margin-top: 16px;
`;

const ResponseContainer = styled.div`
width: 75%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-evenly;
margin-top: 16px;
`;

const SuccessMessage = styled(Paragraph)`
color: green;
`;

const Onboarding: React.FC = () => {
const [createPostRequest, setCreatePostRequest] = useState<
AsyncRequest<GetResponseData[], any>
>(AsyncRequestNotStarted());
const [getPostsRequest, setGetPostsRequest] = useState<
AsyncRequest<PostRequestData, any>
>(AsyncRequestNotStarted());

const getPosts = async () => {
setCreatePostRequest(AsyncRequestLoading());
await ProtectedApiClient.getOnboardingData()
.then((res) => {
setCreatePostRequest(AsyncRequestCompleted(res));
})
.catch((error) => {
setCreatePostRequest(AsyncRequestFailed(error));
});
};

const onFinish = async (values: PostRequestData) => {
setGetPostsRequest(AsyncRequestLoading());
await ProtectedApiClient.postOnboardingForm(values)
.then((res) => {
setGetPostsRequest(AsyncRequestCompleted(res));
})
.catch((error) => {
setGetPostsRequest(AsyncRequestFailed(error));
});
};

const onFinishFailed = (errorInfo: any) => {
setGetPostsRequest(AsyncRequestFailed(errorInfo));
};

return (
<>
<Container>
<OnboardingPageTitle>
Code4Community Frontend Onboarding Tutorial!
</OnboardingPageTitle>
<FormCard bodyStyle={{ width: '100%' }}>
<Title>Form!</Title>
<Paragraph>This is an example form card to create a post.</Paragraph>
<Form onFinish={onFinish} onFinishFailed={onFinishFailed}>
<Form.Item
label="User ID"
name="userId"
rules={[
{
required: true,
message: 'Please input your user ID.',
},
]}
>
<FormInput />
</Form.Item>
<Form.Item
label="Body"
name="body"
rules={[
{
required: true,
message:
'Please input the body text of your getPostsRequest.',
},
]}
>
<FormInput />
</Form.Item>
<Form.Item
label="Title"
name="title"
rules={[
{
required: true,
message: 'Please input the title of your getPostsRequest.',
},
]}
>
<FormInput />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
{asyncRequestIsComplete(getPostsRequest) && (
<SuccessMessage>
Successfully created getPostsRequest with title '
{getPostsRequest.result.title}'!
</SuccessMessage>
)}
</FormCard>
<StyledButton onClick={getPosts}>Get Posts</StyledButton>
<ResponseContainer>
{asyncRequestIsComplete(createPostRequest) &&
createPostRequest.result.map((post: GetResponseData, i: number) => {
return (
<ResponseCard
id={post.id}
key={i}
title={post.title}
body={post.body}
userId={post.userId}
/>
);
})}
</ResponseContainer>
</Container>
</>
);
};

export default Onboarding;
6 changes: 3 additions & 3 deletions src/utils/test/asyncRequest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import {
AsyncRequest,
AsyncRequestCompleted,
AsyncRequestFailed,
asyncRequestIsComplete, asyncRequestIsFailed, asyncRequestIsLoading,
asyncRequestIsComplete,
asyncRequestIsFailed,
asyncRequestIsLoading,
asyncRequestIsNotStarted,
AsyncRequestLoading,
AsyncRequestNotStarted,
Expand Down Expand Up @@ -183,7 +185,6 @@ describe('asyncRequest ', () => {
);
});


it('asyncRequestIsFailed identifies Failed asyncRequests', () => {
asyncRequests.map(
(asyncRequest: AsyncRequest<string, string>, index: number) => {
Expand All @@ -195,6 +196,5 @@ describe('asyncRequest ', () => {
},
);
});

});
});