Skip to content

Commit

Permalink
Add proposals endpoint and model
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaron-9900 committed Feb 22, 2021
1 parent fd805fb commit 85689da
Show file tree
Hide file tree
Showing 16 changed files with 441 additions and 17 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
"moment": "^2.29.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.2",
"styled-components": "^5.2.1",
"typescript": "^4.1.2",
"universal-cookie": "^4.0.4",
"web-vitals": "^1.0.1",
"yarn": "^1.22.10"
},
Expand Down
19 changes: 16 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import React, { useState, useEffect } from "react"
import { RootStore } from "./models/root-store/root-store"
import { RootStoreProvider } from "./models/root-store/root-store-context"
import { setupRootStore } from "./models/root-store/setup-root-store"
import { Login } from "./screens"
import { Home, Login, Register } from "./screens"
import { BrowserRouter as Router, Switch, Route } from "react-router-dom"

const App = observer(function App() {
const [rootStore, setRootStore] = useState<RootStore | undefined>(undefined)
const [rootStore, setRootStore] = useState<RootStore | null>(null)
useEffect(() => {
async function setup() {
setupRootStore().then(setRootStore)
Expand All @@ -17,7 +18,19 @@ const App = observer(function App() {
return (
<div className="App">
<RootStoreProvider value={rootStore}>
<Login></Login>
<Router>
<Switch>
<Route exact path="/">
<Home></Home>
</Route>
<Route path="/login">
<Login></Login>
</Route>
<Route path="/register">
<Register></Register>
</Route>
</Switch>
</Router>
</RootStoreProvider>
</div>
)
Expand Down
11 changes: 11 additions & 0 deletions src/models/auth-model/auth-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ export const AuthModel = types
throw err
}
}),
register: flow(function* (email: string, name: string, password: string) {
self.loading = true
try {
const resp = yield self.environment.api.register(email, name, password)
self.loading = false
return resp
} catch (err) {
self.loading = false
throw err
}
}),
}
})

Expand Down
20 changes: 20 additions & 0 deletions src/models/proposals-model/proposal-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { flow, Instance, SnapshotOut, types } from "mobx-state-tree"
import { withEnvironment } from "../extensions/with-environment"
import { UserModel } from "../user-model/user-model"

export const ProposalModel = types
.model("ProposalModel")
.props({
name: "",
description: "",
limit: 0,
user: UserModel,
})
.actions((self) => {
return {}
})

type ProposalModelType = Instance<typeof ProposalModel>
export interface ProposalModel extends ProposalModelType {}
type ProposalModelSnapshotType = SnapshotOut<typeof ProposalModel>
export interface ProposalModelSnapshot extends ProposalModelSnapshotType {}
33 changes: 33 additions & 0 deletions src/models/proposals-model/proposals-model-store.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { applySnapshot, flow, Instance, SnapshotOut, types } from "mobx-state-tree"
import { GetProposals } from "../../services/api-types"
import { withEnvironment } from "../extensions/with-environment"
import { ProposalModel } from "./proposal-model"

export const ProposalsModelStore = types
.model("ProposalsModelStore")
.props({
proposals: types.array(ProposalModel),
})
.extend(withEnvironment)
.actions((self) => {
return {
getProposals: flow(function* (from: number, to: number) {
try {
const response: GetProposals = yield self.environment.api.getProposals(from, to)
if (response.kind === "ok") {
const proposals = response.proposals
applySnapshot(self.proposals, proposals as any)
} else {
throw response
}
} catch (err) {
throw err
}
}),
}
})

type ProposalsModelStoreType = Instance<typeof ProposalsModelStore>
export interface ProposalsModelStore extends ProposalsModelStoreType {}
type ProposalsModelStoreSnapshotType = SnapshotOut<typeof ProposalsModelStore>
export interface ProposalsModelStoreSnapshot extends ProposalsModelStoreSnapshotType {}
2 changes: 2 additions & 0 deletions src/models/root-store/root-store.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Instance, SnapshotOut, types } from "mobx-state-tree"
import { AuthModel } from "../auth-model/auth-model"
import { ProposalsModelStore } from "../proposals-model/proposals-model-store"

/**
* A RootStore model.
*/
export const RootStoreModel = types.model("RootStore").props({
authStore: types.optional(AuthModel, {}),
proposalsStore: types.optional(ProposalsModelStore, {}),
})

/**
Expand Down
17 changes: 17 additions & 0 deletions src/models/user-model/user-model.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flow, Instance, SnapshotOut, types } from "mobx-state-tree"
import { withEnvironment } from "../extensions/with-environment"

export const UserModel = types
.model("UserModel")
.props({
name: "",
id: types.identifierNumber,
})
.actions((self) => {
return {}
})

type UserModelType = Instance<typeof UserModel>
export interface UserModel extends UserModelType {}
type UserModelSnapshotType = SnapshotOut<typeof UserModel>
export interface UserModelSnapshot extends UserModelSnapshotType {}
10 changes: 10 additions & 0 deletions src/screens/home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React, { ReactElement, useEffect } from "react"
import { useStores } from "../models/root-store/root-store-context"

export function Home(props): ReactElement {
const { proposalsStore } = useStores()
useEffect(() => {
proposalsStore.getProposals(0, 15)
}, [])
return <div></div>
}
2 changes: 2 additions & 0 deletions src/screens/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from "./login"
export * from "./register"
export * from "./home"
20 changes: 16 additions & 4 deletions src/screens/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { useStores } from "../models/root-store/root-store-context"
import { parseError } from "../services/error-parser"
import { Typography } from "antd"
import { observer } from "mobx-react-lite"
import { Link } from "react-router-dom"

const { Text } = Typography
const { Text, Link: AntdLink } = Typography

const StyledForm = styled(Form)`
padding: 50px;
Expand All @@ -25,35 +26,46 @@ const StyledSpinner = styled(Spin)`
height: "100%";
width: "100%";
`
const StyledLink = styled(AntdLink)`
margin-bottom: 20px;
`
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
}
const tailLayout = {
wrapperCol: { offset: 8, span: 16 },
}
const StyledTextWrapper = styled.div`
padding: 20px;
`

export const Login = observer(function Login(): ReactElement {
const { authStore } = useStores()
const [err, setErr] = useState<string | null>(null)
const [err, setErr] = useState<string | false>(false)
const onFinish = async (value) => {
try {
const resp = await authStore.login(value.email, value.password)
} catch (e) {
setErr(parseError(e))
}
}
console.log(authStore.loading)
return (
<CenteredBody>
<StyledForm
{...layout}
name="basic"
onFinish={onFinish}
err={err}
onFieldsChange={() => setErr(null)}
onFieldsChange={() => setErr(false)}
>
<StyledTitle level={2}>Login</StyledTitle>
<StyledTextWrapper>
<StyledLink>
<Link to="/register">If you dont have an account, please register</Link>
</StyledLink>
</StyledTextWrapper>

<Form.Item
label="Email"
name="email"
Expand Down
134 changes: 134 additions & 0 deletions src/screens/register.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Form, Input, Button, Spin, Typography, message } from "antd"
import React, { useState } from "react"
import { colors } from "../colors/colors"
import { CenteredBody } from "../components"
import { useStores } from "../models/root-store/root-store-context"
import { parseError } from "../services/error-parser"
import styled from "styled-components"
import { Link, Redirect } from "react-router-dom"
import Title from "antd/lib/typography/Title"
import { observer } from "mobx-react-lite"

const { Text, Link: AntdLink } = Typography

const StyledForm = styled(Form)`
padding: 70px;
background-color: ${colors.backgroundSecondary};
width: 45vw;
border-color: ${(props) => (props.err ? colors.error : colors.secondaryBackground)};
`
const StyledTitle = styled(Title)`
text-align: "left";
margin-bottom: "20px";
`
const StyledSpinner = styled(Spin)`
height: "100%";
width: "100%";
`
const StyledLink = styled(AntdLink)`
margin-bottom: 20px;
`
const layout = {
labelCol: { span: 8 },
wrapperCol: { span: 16 },
}
const StyledTextWrapper = styled.div`
padding: 20px;
`

export const Register = observer(() => {
const [form] = Form.useForm()
const { authStore } = useStores()
const [err, setErr] = useState<string | false>(false)
const [redirect, setRedirect] = useState<boolean>(false)
const verifyPasswordValidator = (_, verifyPassword, callback) => {
console.log(verifyPassword, callback)
if (verifyPassword !== form.getFieldValue("password")) {
callback("Passwords do not match!")
} else {
callback()
}
}
const onFinish = async (value) => {
try {
const resp = await authStore.register(value.email, value.name, value.password)
setRedirect(true)
console.log(resp)
} catch (e) {
console.log(e)
setErr(parseError(e))
}
}
console.log(redirect)
if (redirect) {
return <Redirect to="/login"></Redirect>
}
return (
<CenteredBody>
<StyledForm
{...layout}
form={form}
name="basic"
onFinish={onFinish}
err={err}
onFieldsChange={() => setErr(false)}
>
<StyledTitle level={2}>Register</StyledTitle>
<StyledTextWrapper>
<StyledLink>
<Link to="/">Already have an account? Login here</Link>
</StyledLink>
</StyledTextWrapper>
<Form.Item
label="Name"
name="name"
rules={[
{ required: true, message: "Please input your name!" },
{ min: 4, message: "Username must be minimum 5 characters." },
]}
>
<Input />
</Form.Item>

<Form.Item
label="Email"
name="email"
rules={[
{ required: true, message: "" },
{
type: "email",
message: "Please provide a valid email!",
},
]}
>
<Input />
</Form.Item>

<Form.Item
label="Password"
name="password"
rules={[{ required: true, message: "Please input your password!" }]}
>
<Input.Password />
</Form.Item>
<Form.Item
label="Verify password"
name="verifyPassword"
rules={[
{ required: true, message: "" },
{ validator: verifyPasswordValidator },
{ min: 6, message: "Password must be minimum 6 characters." },
]}
>
<Input.Password />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled={authStore.loading}>
{authStore.loading ? <StyledSpinner /> : "Submit"}
</Button>
</Form.Item>
{err && <Text type="danger">{err}</Text>}
</StyledForm>
</CenteredBody>
)
})
20 changes: 20 additions & 0 deletions src/services/api-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ProposalsModelStore } from "../models/proposals-model/proposals-model-store"
import { UserModel } from "../models/user-model/user-model"

export function parseUser(backendUser: any): UserModel {
return {
name: backendUser.name,
id: backendUser.id,
}
}

export function parseProposals(proposalsList): ProposalsModelStore {
return proposalsList.map((prop) => {
return {
user: parseUser(prop.user),
limit: prop.limit,
name: prop.name,
description: prop.description,
}
})
}
7 changes: 6 additions & 1 deletion src/services/api-types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { ProposalsModelStore } from "../models/proposals-model/proposals-model-store"
import { GeneralApiProblem } from "./api-problem"

export type GetUsersResult = { kind: "ok"; token: string } | GeneralApiProblem
export type GetUsersResult =
| { kind: "ok"; tokens: { accessToken: string; refreshToken: string } }
| GeneralApiProblem
export type PostRegister = { kind: "ok"; username: string } | GeneralApiProblem
export type GetProposals = { kind: "ok"; proposals: ProposalsModelStore } | GeneralApiProblem
Loading

0 comments on commit 85689da

Please sign in to comment.