This commit is contained in:
QkoSad
2023-08-08 16:02:54 +03:00
commit 0a7a469d56
315 changed files with 426907 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
missing 8.23, on 8.24 useSubsriptions seems to not work no errors are thrown
+29637
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"@apollo/client": "^3.7.16",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"graphql": "^16.7.1",
"graphql-ws": "^5.14.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.13.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+43
View File
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+76
View File
@@ -0,0 +1,76 @@
import { Route, Routes, BrowserRouter, Link } from "react-router-dom";
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
import { useEffect, useState } from "react";
import { ALL_AUTHORS, ALL_BOOKS, BOOK_ADDED, ME } from "./queries";
import Authors from "./components/Authors";
import Books from "./components/Books";
import NewBook from "./components/NewBook";
import Login from "./components/Login";
import Recommended from "./components/Recommended";
const App = () => {
const [token, setToken] = useState(null);
const authorsRes = useQuery(ALL_AUTHORS);
const booksRes = useQuery(ALL_BOOKS);
const userRes = useQuery(ME);
const client = useApolloClient();
useEffect(() => {
if (localStorage.getItem("library-user-token"))
setToken(localStorage.getItem("library-user-token"));
}, [setToken]);
useSubscription(BOOK_ADDED, {
onData: ({ data }) => {
console.log(data);
},
});
const logOut = () => {
setToken(null);
localStorage.removeItem("library-user-token");
client.resetStore();
};
if (authorsRes.loading || booksRes.loading) return <div>loading...</div>;
if (!token) {
return <Login token={token} setToken={setToken} />;
}
return (
<BrowserRouter>
<Link to={"/authors"}>
<button>authors</button>
</Link>
<Link to={"/books"}>
<button>books</button>
</Link>
<Link to={"/add"}>
<button>add-book</button>
</Link>
<Link to={"/recommended"}>
<button>recommended</button>
</Link>
<button onClick={logOut}>logout</button>
<Routes>
<Route
path="/authors"
element={<Authors authors={authorsRes.data.authors} />}
/>
<Route path="/books" element={<Books books={booksRes.data.books} />} />
<Route path="/add" element={<NewBook />} />
<Route
path="/login"
element={<Login token={token} setToken={setToken} />}
/>
<Route
path="/recommended"
element={<Recommended user={userRes.data} />}
/>
</Routes>
</BrowserRouter>
);
};
export default App;
@@ -0,0 +1,55 @@
import { useState } from "react";
import { useMutation } from "@apollo/client";
import { ADD_BIRTHYEAR } from "../queries";
const Authors = ({ authors }) => {
const [addBirthyear] = useMutation(ADD_BIRTHYEAR);
const [year, setYear] = useState("");
const [name, setName] = useState("");
const setBirthyear = (e) => {
e.preventDefault();
addBirthyear({ variables: { birthyear: parseInt(year), name } });
setYear("");
setName("");
};
return (
<div>
<h2>authors</h2>
<table>
<tbody>
<tr>
<th></th>
<th>born</th>
<th>books</th>
</tr>
{authors.map((a) => (
<tr key={a.name}>
<td>{a.name}</td>
<td>{a.born}</td>
<td>{a.bookCount}</td>
</tr>
))}
</tbody>
</table>
<h2>set birthyear</h2>
<form onSubmit={setBirthyear}>
<label>name</label>
<select value={name} onChange={(e) => setName(e.target.value)}>
{authors.map((author) => (
<option value={author.name} key={author.id}>
{author.name}
</option>
))}
</select>
<label>birthyear</label>
<input value={year} onChange={(e) => setYear(e.target.value)} />
<br />
<button>set</button>
</form>
</div>
);
};
export default Authors;
@@ -0,0 +1,51 @@
import { useState } from "react";
import { useQuery } from "@apollo/client";
import { BOOKS_BY_GENRE } from "../queries";
const Books = ({ books }) => {
const genres = ["all", "refactoring", "agile", "design", "crime", "classic"];
const [filter, setFilter] = useState('');
const booksRes = useQuery(BOOKS_BY_GENRE, {
variables: { genre: filter },
});
if (booksRes.loading) return <>loading ....</>;
return (
<div>
<h2>books</h2>
<table>
<tbody>
<tr>
<th></th>
<th>author</th>
<th>published</th>
</tr>
{booksRes.data.books.map((a, index) => (
<tr key={index}>
<td>{a.title}</td>
<td>{a.author.name}</td>
<td>{a.published}</td>
</tr>
))}
</tbody>
</table>
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
{genres.map((genre, index) => {
if (genre === "all")
return (
<option value={''} key={index}>
{genre}
</option>
);
return (
<option value={genre} key={index}>
{genre}
</option>
);
})}
</select>
</div>
);
};
export default Books;
@@ -0,0 +1,32 @@
import { useMutation } from "@apollo/client";
import { useEffect, useState } from "react";
import { LOGIN } from "../queries";
const Login = ({ token, setToken }) => {
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const [login, result] = useMutation(LOGIN);
const loginUser = async (e) => {
e.preventDefault();
await login({ variables: { username: name, password } });
};
useEffect(() => {
if (result.data) {
setToken(result.data.login.value);
localStorage.setItem("library-user-token", result.data.login.value);
}
}, [result.data]);
return (
<form onSubmit={loginUser}>
name <input onChange={(e) => setName(e.target.value)} value={name} />
password{" "}
<input
type="password"
onChange={(e) => setPassword(e.target.value)}
value={password}
/>
<button>login</button>
</form>
);
};
export default Login;
@@ -0,0 +1,77 @@
import { useState } from "react";
import { useMutation } from "@apollo/client";
import { ALL_AUTHORS, ALL_BOOKS, ADD_BOOK } from "../queries";
const NewBook = () => {
const [title, setTitle] = useState("");
const [author, setAuthor] = useState("");
const [published, setPublished] = useState("");
const [genre, setGenre] = useState("");
const [genres, setGenres] = useState([]);
const [addBook] = useMutation(ADD_BOOK, {
refetchQueries: [{ query: ALL_BOOKS }, { query: ALL_AUTHORS }],
});
const submit = async (event) => {
event.preventDefault();
console.log("add book...");
addBook({
variables: { title, author, published: parseInt(published), genres },
});
setTitle("");
setPublished("");
setAuthor("");
setGenres([]);
setGenre("");
};
const addGenre = () => {
setGenres(genres.concat(genre));
setGenre("");
};
return (
<div>
<form onSubmit={submit}>
<div>
title
<input
value={title}
onChange={({ target }) => setTitle(target.value)}
/>
</div>
<div>
author
<input
value={author}
onChange={({ target }) => setAuthor(target.value)}
/>
</div>
<div>
published
<input
type="number"
value={published}
onChange={({ target }) => setPublished(target.value)}
/>
</div>
<div>
<input
value={genre}
onChange={({ target }) => setGenre(target.value)}
/>
<button onClick={addGenre} type="button">
add genre
</button>
</div>
<div>genres: {genres.join(" ")}</div>
<button type="submit">create book</button>
</form>
</div>
);
};
export default NewBook;
@@ -0,0 +1,32 @@
import { useQuery } from "@apollo/client";
import { BOOKS_BY_GENRE } from "../queries";
const Recommended = ({ user }) => {
const booksRes = useQuery(BOOKS_BY_GENRE, {
variables: { genre: user.me.favoriteGenre },
});
if (booksRes.loading) return <>loading ....</>
return (
<>
<h2>recommendations</h2>
<div>books in your favotite genre</div>
<table>
<tbody>
<tr>
<th></th>
<th>author</th>
<th>published</th>
</tr>
{booksRes.data.books.map((a, index) => (
<tr key={index}>
<td>{a.title}</td>
<td>{a.author.name}</td>
<td>{a.published}</td>
</tr>
))}
</tbody>
</table>
</>
);
};
export default Recommended;
+52
View File
@@ -0,0 +1,52 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import {
ApolloClient,
InMemoryCache,
ApolloProvider,
createHttpLink,
split,
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
import { getMainDefinition } from "@apollo/client/utilities";
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("library-user-token");
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : null,
},
};
});
const httpLink = createHttpLink({
uri: "http://localhost:4000",
});
const wsLink = new GraphQLWsLink(createClient({ url: "ws://localhost:4000" }));
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === "OperationDefinition" &&
definition.operation === "subscription"
);
},
wsLink,
authLink.concat(httpLink)
);
const client = new ApolloClient({
cache: new InMemoryCache(),
link: splitLink,
});
ReactDOM.createRoot(document.getElementById("root")).render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
+117
View File
@@ -0,0 +1,117 @@
import { gql } from "@apollo/client";
export const ALL_BOOKS = gql`
query {
books {
title
published
id
genres
author {
bookCount
born
id
name
}
}
}
`;
export const ALL_AUTHORS = gql`
query {
authors {
name
id
born
bookCount
}
}
`;
export const ADD_BOOK = gql`
mutation Mutation(
$title: String!
$author: String!
$published: Int!
$genres: [String!]!
) {
addBook(
title: $title
author: $author
published: $published
genres: $genres
) {
author {
bookCount
born
id
name
}
genres
id
published
title
}
}
`;
export const ADD_BIRTHYEAR = gql`
mutation Mutation($birthyear: Int!, $name: String!) {
editAuthor(birthyear: $birthyear, name: $name) {
bookCount
id
born
name
}
}
`;
export const LOGIN = gql`
mutation Mutation($username: String!, $password: String!) {
login(username: $username, password: $password) {
value
}
}
`;
export const ME = gql`
query ExampleQuery {
me {
favoriteGenre
id
username
}
}
`;
export const BOOKS_BY_GENRE = gql`
query Query($genre: String) {
books(genre: $genre) {
author {
name
}
genres
id
published
title
}
}
`;
export const BOOK_DETAILS = gql`
fragment BookDetails on Book {
title
published
id
genres
author {
name
id
born
bookCount
}
}
`;
export const BOOK_ADDED = gql`
subscription {
bookAdded {
title
}
}
${BOOK_DETAILS}
`;