Skip to content

Commit d0026bf

Browse files
committed
added subscriptions
1 parent 585a75b commit d0026bf

File tree

9 files changed

+195
-15
lines changed

9 files changed

+195
-15
lines changed

frontend/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
"react-infinite-scroller": "^1.2.4",
1616
"react-router-dom": "^6.0.0-beta.0",
1717
"react-scripts": "3.4.1",
18-
"tailwindcss": "^1.6.2"
18+
"subscriptions-transport-ws": "^0.9.17",
19+
"tailwindcss": "^1.6.2",
20+
"uuidv4": "^6.2.2"
1921
},
2022
"scripts": {
2123
"build:tailwind": "tailwind build src/tailwind.css -o src/tailwind.output.css",
@@ -59,10 +61,11 @@
5961
"@types/react": "16.9.43",
6062
"@types/react-dom": "16.9.8",
6163
"@types/react-infinite-scroller": "^1.2.1",
62-
"prettier": "^2.0.5",
64+
"@types/uuidv4": "5.0.0",
6365
"chokidar-cli": "^2.1.0",
6466
"graphql": "^15.3.0",
6567
"npm-run-all": "^4.1.5",
68+
"prettier": "^2.0.5",
6669
"typescript": "3.9.7"
6770
}
6871
}

frontend/src/App.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,41 @@ import { ApolloProvider } from "@apollo/client";
88
import { NearbyPage } from "./pages/nearby-page";
99
import "./tailwind.output.css";
1010
import { SearchPage } from "./pages/search-page";
11+
import { ReviewPage } from "./pages/review-page";
12+
import { split, HttpLink } from "@apollo/client";
13+
import { getMainDefinition } from "@apollo/client/utilities";
14+
import { WebSocketLink } from "@apollo/client/link/ws";
1115

12-
const client = new ApolloClient({
16+
const httpLink = new HttpLink({
1317
uri: "https://localhost:5001/graphql",
18+
});
19+
20+
const wsLink = new WebSocketLink({
21+
uri: `wss://localhost:5001/graphql`,
22+
options: {
23+
reconnect: true,
24+
},
25+
});
26+
27+
// The split function takes three parameters:
28+
//
29+
// * A function that's called for each operation to execute
30+
// * The Link to use for an operation if the function returns a "truthy" value
31+
// * The Link to use for an operation if the function returns a "falsy" value
32+
const link = split(
33+
({ query }) => {
34+
const definition = getMainDefinition(query);
35+
return (
36+
definition.kind === "OperationDefinition" &&
37+
definition.operation === "subscription"
38+
);
39+
},
40+
wsLink,
41+
httpLink
42+
);
43+
44+
const client = new ApolloClient({
45+
link,
1446
cache: new InMemoryCache(),
1547
});
1648

@@ -27,6 +59,9 @@ function App() {
2759
<Route path="nearby">
2860
<NearbyPage />
2961
</Route>
62+
<Route path="reviews">
63+
<ReviewPage />
64+
</Route>
3065
<Route path="search">
3166
<SearchPage />
3267
</Route>

frontend/src/components/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ const Header = ({ title }: HeaderProps) => {
7777
<div className="flex-grow block w-full lg:flex lg:items-center lg:w-auto">
7878
<div className="text-sm lg:flex-grow">
7979
<MenuLink to="nearby" text="Nearby" />
80-
<MenuLink to="favorites" text="Favorites" />
80+
<MenuLink to="reviews" text="Reviews" />
8181
<AutocompleteTextbox
8282
className="w-64"
8383
onEnter={(text) =>

frontend/src/components/modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useEffect, useCallback, useMemo } from "react";
22

3-
interface ModalProps {
3+
export interface ModalProps {
44
actionButtonText?: string;
55
onAction?: () => void;
66
onClose?: () => void;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
mutation CreateReview($createReviewInput: ReviewInput!) {
2+
createReview(input: $createReviewInput) {
3+
clientMutationId
4+
review {
5+
subject
6+
body
7+
}
8+
}
9+
}

frontend/src/graphql/subscriptions/reviews.gql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
subscription OnReviewAdded {
22
onReviewReceived {
3+
id
34
subject
45
body
56
brewery {
7+
id
68
name
79
}
810
}

frontend/src/pages/brewery-page.tsx

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import React, { useState, useMemo } from "react";
1+
import React, { useState, useMemo, useCallback } from "react";
22
import { Loading } from "../components/loading";
33
import { BreweryMap } from "../components/map";
44
import { useParams } from "react-router-dom";
5-
import { useBreweryByIdQuery } from "../graphql/autogenerate/hooks";
5+
import {
6+
useBreweryByIdQuery,
7+
useCreateReviewMutation,
8+
} from "../graphql/autogenerate/hooks";
69
import { Brewery } from "../graphql/autogenerate/schemas";
710
import { Card } from "../components/card";
811
import { ExternalLink } from "heroicons-react";
9-
import { Modal } from "../components/modal";
12+
import { Modal, ModalProps } from "../components/modal";
13+
import { uuid } from "uuidv4";
1014

1115
const BreweryCard = ({ brewery }: { brewery: Brewery }) => {
1216
const [markedAsFavorite, setMarkedAsFavorite] = useState(false);
@@ -69,10 +73,68 @@ const BreweryCard = ({ brewery }: { brewery: Brewery }) => {
6973
);
7074
};
7175

76+
type ReviewModalProps = Omit<ModalProps, "title"> & { breweryId: string };
77+
78+
const ReviewModal = (props: ReviewModalProps) => {
79+
const [title, setTitle] = useState<string>("");
80+
const [review, setReview] = useState<string>("");
81+
const [createReview] = useCreateReviewMutation();
82+
83+
const handleSubmitAction = useCallback(async () => {
84+
await createReview({
85+
variables: {
86+
createReviewInput: {
87+
clientMutationId: uuid(),
88+
subject: title,
89+
body: review,
90+
breweryId: props.breweryId,
91+
},
92+
},
93+
});
94+
95+
setTitle("");
96+
setReview("");
97+
}, [createReview, props.breweryId, title, review, setTitle, setReview]);
98+
99+
const newProps = {
100+
...props,
101+
onAction: async () => {
102+
await handleSubmitAction();
103+
104+
if (props.onAction) {
105+
props.onAction();
106+
}
107+
},
108+
};
109+
110+
return (
111+
<Modal title="Add a Review!!!" {...newProps}>
112+
<div className="mb-4">
113+
<label className="block text-sm font-bold">Title</label>
114+
<input
115+
className="block w-full p-1 border border-gray-400 rounded-md shadow-inner"
116+
type="text"
117+
value={title}
118+
onChange={(e) => setTitle(e.target.value)}
119+
/>
120+
121+
<label className="block mt-2 text-sm font-bold">Review</label>
122+
<textarea
123+
value={review}
124+
className="block w-full h-12 p-1 border border-gray-400 rounded-md shadow-inner"
125+
onChange={(e) => setReview(e.target.value)}
126+
/>
127+
</div>
128+
</Modal>
129+
);
130+
};
131+
72132
const BreweryReviews = ({
133+
id,
73134
brewery_id,
74135
reviews,
75136
}: {
137+
id: string;
76138
brewery_id: string;
77139
reviews?: string[];
78140
}) => {
@@ -90,13 +152,12 @@ const BreweryReviews = ({
90152
</button>
91153
</div>
92154
</Card>
93-
<Modal
94-
title="Add a Review!!!"
155+
<ReviewModal
156+
breweryId={id}
95157
showModal={showModal}
96158
onAction={() => setShowModal(false)}
97-
onClose={() => setShowModal(false)}>
98-
<div>Add a review</div>
99-
</Modal>
159+
onClose={() => setShowModal(false)}
160+
/>
100161
</React.Fragment>
101162
);
102163
};
@@ -149,7 +210,7 @@ export const BreweryPage = () => {
149210
<div className="flex">
150211
<div className="flex-none pr-4 lg:w-1/4 xl:w-1/5">
151212
<BreweryCard brewery={brewery} />
152-
<BreweryReviews brewery_id={brewery_id} />
213+
<BreweryReviews id={brewery.id} brewery_id={brewery_id} />
153214
<NearbyBreweries breweries={brewery.nearby as Brewery[]} />
154215
</div>
155216
<div className="flex-grow">

frontend/src/pages/review-page.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { useState, useEffect } from "react";
2+
import { Loading } from "../components/loading";
3+
import { useOnReviewAddedSubscription } from "../graphql/autogenerate/hooks";
4+
5+
interface ReviewInternal {
6+
id: string;
7+
subject: string;
8+
body: string;
9+
breweryName: string;
10+
}
11+
12+
export const ReviewPage = () => {
13+
const [reviews, setReviews] = useState<ReviewInternal[]>([]);
14+
const { data, error, loading } = useOnReviewAddedSubscription();
15+
16+
useEffect(() => {
17+
if (data?.onReviewReceived == null) return;
18+
19+
const newReview: ReviewInternal = {
20+
id: data?.onReviewReceived?.id?.toString(),
21+
subject: data?.onReviewReceived?.subject,
22+
body: data?.onReviewReceived?.body,
23+
breweryName: data?.onReviewReceived?.brewery?.name,
24+
};
25+
26+
setReviews((r) => [newReview, ...r]);
27+
}, [data]);
28+
29+
if (loading || reviews.length === 0) return <Loading />;
30+
if (error) return <p>Error :(</p>;
31+
32+
return (
33+
<>
34+
{reviews.map((r: ReviewInternal) => (
35+
<div
36+
key={r.id}
37+
className="block w-full max-w-xl p-4 mx-auto my-4 overflow-hidden">
38+
<div className="text-sm font-semibold">{r.breweryName}</div>
39+
<div className="font-bold">{r.subject}</div>
40+
<div>{r.body}</div>
41+
</div>
42+
))}
43+
</>
44+
);
45+
};

frontend/yarn.lock

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2512,6 +2512,18 @@
25122512
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
25132513
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
25142514

2515+
"@types/uuid@8.0.1":
2516+
version "8.0.1"
2517+
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.1.tgz#42958a1a880640b139eea97a1640e1a3f61016d2"
2518+
integrity sha512-2kE8rEFgJpbBAPw5JghccEevQb0XVU0tewF/8h7wPQTeCtoJ6h8qmBIwuzUVm2MutmzC/cpCkwxudixoNYDp1A==
2519+
2520+
"@types/uuidv4@5.0.0":
2521+
version "5.0.0"
2522+
resolved "https://registry.yarnpkg.com/@types/uuidv4/-/uuidv4-5.0.0.tgz#2c94e67b0c06d5adb28fb7ced1a1b5f0866ecd50"
2523+
integrity sha512-xUrhYSJnkTq9CP79cU3svoKTLPCIbMMnu9Twf/tMpHATYSHCAAeDNeb2a/29YORhk5p4atHhCTMsIBU/tvdh6A==
2524+
dependencies:
2525+
uuidv4 "*"
2526+
25152527
"@types/websocket@1.0.1":
25162528
version "1.0.1"
25172529
resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.1.tgz#039272c196c2c0e4868a0d8a1a27bbb86e9e9138"
@@ -11906,7 +11918,7 @@ stylehacks@^4.0.0:
1190611918
postcss "^7.0.0"
1190711919
postcss-selector-parser "^3.0.0"
1190811920

11909-
subscriptions-transport-ws@0.9.17:
11921+
subscriptions-transport-ws@0.9.17, subscriptions-transport-ws@^0.9.17:
1191011922
version "0.9.17"
1191111923
resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz#e30e40f0caae0d2781903c01a8cb51b6e2682098"
1191211924
integrity sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA==
@@ -12491,11 +12503,24 @@ utils-merge@1.0.1:
1249112503
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
1249212504
integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
1249312505

12506+
uuid@8.3.0:
12507+
version "8.3.0"
12508+
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"
12509+
integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==
12510+
1249412511
uuid@^3.0.1, uuid@^3.3.2:
1249512512
version "3.4.0"
1249612513
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
1249712514
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
1249812515

12516+
uuidv4@*, uuidv4@^6.2.2:
12517+
version "6.2.2"
12518+
resolved "https://registry.yarnpkg.com/uuidv4/-/uuidv4-6.2.2.tgz#5e10dd4704b467e0b6e42ab0368a22bca44f069d"
12519+
integrity sha512-cIwuSIzx+StdJSkLYKDLje7mDfYvK88RNZuVdpCFWujf2bOIDLGCum8vY88KDNLhcFaQGWLBVHYl8U3wlqseQg==
12520+
dependencies:
12521+
"@types/uuid" "8.0.1"
12522+
uuid "8.3.0"
12523+
1249912524
v8-compile-cache@^2.0.3:
1250012525
version "2.1.1"
1250112526
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745"

0 commit comments

Comments
 (0)