diff --git a/.env b/.env index 6f809cc..cf303f0 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -SKIP_PREFLIGHT_CHECK=true +SITE_BASE_URI=http://localhost:5509 +GRAPHQL_PATH=/pairing/graphql diff --git a/apps/film-and-tv-ratings/project.json b/apps/film-and-tv-ratings/project.json index d5673de..a162019 100644 --- a/apps/film-and-tv-ratings/project.json +++ b/apps/film-and-tv-ratings/project.json @@ -11,7 +11,7 @@ "compiler": "babel", "outputPath": "dist/apps/film-and-tv-ratings", "index": "apps/film-and-tv-ratings/src/index.html", - "baseHref": "/", + "baseHref": "/_apps/film-and-tv-reviews/", "main": "apps/film-and-tv-ratings/src/main.tsx", "tsConfig": "apps/film-and-tv-ratings/tsconfig.app.json", "assets": [ diff --git a/apps/film-and-tv-ratings/src/assets/images/film-app-logo-icon.png b/apps/film-and-tv-ratings/src/assets/images/film-app-logo-icon.png new file mode 100644 index 0000000..0813442 Binary files /dev/null and b/apps/film-and-tv-ratings/src/assets/images/film-app-logo-icon.png differ diff --git a/apps/film-and-tv-ratings/src/assets/images/film-app-logo.png b/apps/film-and-tv-ratings/src/assets/images/film-app-logo.png new file mode 100644 index 0000000..3eec952 Binary files /dev/null and b/apps/film-and-tv-ratings/src/assets/images/film-app-logo.png differ diff --git a/apps/film-and-tv-ratings/src/common.ts b/apps/film-and-tv-ratings/src/common.ts deleted file mode 100644 index 5a9b73e..0000000 --- a/apps/film-and-tv-ratings/src/common.ts +++ /dev/null @@ -1,12 +0,0 @@ -import axios from 'axios'; - -export const api_key = '99fdc21a0b0ef55c0cbc002c0ae96fd6'; - -export const client = axios.create({ - baseURL: 'https://api.themoviedb.org', - timeout: 1000, - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - }, -}); diff --git a/apps/film-and-tv-ratings/src/common.tsx b/apps/film-and-tv-ratings/src/common.tsx new file mode 100644 index 0000000..eca16b1 --- /dev/null +++ b/apps/film-and-tv-ratings/src/common.tsx @@ -0,0 +1,33 @@ +import { Card, Image, ImageProps, Text, Paper, Button } from '@mantine/core'; +import RichTextEditor from '@mantine/rte'; +import axios from 'axios'; + +export const api_key = '99fdc21a0b0ef55c0cbc002c0ae96fd6'; + +export function PosterImage({ + posterPath, + imageProps, +}: { + posterPath?: string | null; + imageProps?: ImageProps; +}) { + return ( + Movie poster + ); +} + +export const client = axios.create({ + baseURL: 'https://api.themoviedb.org', + timeout: 1000, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, +}); + +export const devMode = window.location.hostname === 'localhost'; diff --git a/apps/film-and-tv-ratings/src/components/Card.tsx b/apps/film-and-tv-ratings/src/components/Card.tsx new file mode 100644 index 0000000..6fcfe43 --- /dev/null +++ b/apps/film-and-tv-ratings/src/components/Card.tsx @@ -0,0 +1,129 @@ +import { + TvFilmReview, + TvFilmReviewInput, + UpsertReviewMutationVariables, +} from '@juxt-home/site'; +import { Card, Button, Text, Title, Group, Badge } from '@mantine/core'; +import { RichTextEditor } from '@mantine/rte'; +import { Id } from 'react-toastify'; +import { PosterImage } from '../common'; + +export function TvFilmCard({ + title, + posterPath, + overview, + badge1, + badge2, + badge3, +}: { + title: string | null; + posterPath: string | null; + overview: string | null; + badge1?: string | number | null; + badge2?: string | number | null; + badge3?: string | number | null; +}) { + return ( + ({ + border: '0.5px solid #e7e8e7', + })}> + {title} + + ({ + margin: '10px 0 20px 0', + })}> + + + + {overview} + + + + {badge1} + + + {badge2} + + + {badge3} + + + + ); +} + +export function ReviewCard({ + review, + devMode, + username, + handleDeleteFunction, + handleEditFunction, +}: { + review: TvFilmReview; + devMode?: boolean; + username?: string | null; + handleDeleteFunction?: (id: string) => Promise; + handleEditFunction?: ({ + reviewHTML, + score, + }: Partial) => Promise; +}) { + const score = review?.score; + const id = review?.id; + return ( + ({ + border: '0.5px solid #e7e8e7', + })}> + + Review by {review?._siteSubject || 'admin'}: + + {review?.reviewHTML && ( + null} + /> + )} + Score: {score} + {/* Recommended: {review.recommended ? 'true' : 'false'} */} + + {(devMode || review?._siteSubject === username) && handleDeleteFunction && ( + + {handleEditFunction && ( + + )} + + + )} + + ); +} diff --git a/apps/film-and-tv-ratings/src/hooks.ts b/apps/film-and-tv-ratings/src/hooks.ts index 85597f0..cda2144 100644 --- a/apps/film-and-tv-ratings/src/hooks.ts +++ b/apps/film-and-tv-ratings/src/hooks.ts @@ -1,5 +1,8 @@ import { useReviewByIdQuery } from '@juxt-home/site'; -export function useReviews(imdb_id?: string) { - return useReviewByIdQuery({ imdb_id: imdb_id || '' }, { enabled: !!imdb_id }); +export function useReviews(tmdb_id_unique?: string, type = 'movie') { + return useReviewByIdQuery( + { tmdb_id_unique: `${tmdb_id_unique}` }, + { enabled: !!tmdb_id_unique }, + ); } diff --git a/apps/film-and-tv-ratings/src/index.html b/apps/film-and-tv-ratings/src/index.html index 0071c59..36ec742 100644 --- a/apps/film-and-tv-ratings/src/index.html +++ b/apps/film-and-tv-ratings/src/index.html @@ -2,11 +2,12 @@ - JUXT TV/Film Guild - - + JUXT Film App - +
diff --git a/apps/film-and-tv-ratings/src/main.tsx b/apps/film-and-tv-ratings/src/main.tsx index 41f5411..3f0057c 100644 --- a/apps/film-and-tv-ratings/src/main.tsx +++ b/apps/film-and-tv-ratings/src/main.tsx @@ -15,6 +15,7 @@ import { Box } from '@mantine/core'; import 'regenerator-runtime/runtime.js'; import { RecentReviews } from './pages/RecentReviews'; import { newReactLocation } from '@juxt-home/utils'; +import { Group } from '@mantine/core'; const reactLocation = newReactLocation(); @@ -38,13 +39,14 @@ ReactDOM.render( location={reactLocation} + basepath="/_apps/film-and-tv-reviews/app" routes={[ { - path: '/', + path: 'home', element: , }, { - path: '/search/:searchType', + path: 'search/:searchType', element: , children: [ { @@ -55,7 +57,7 @@ ReactDOM.render( case 'movie': return ; case 'tv': - return ; + return ; default: return ; } @@ -71,15 +73,18 @@ ReactDOM.render( element:

TV page

, }, { - path: '/*', + path: '*', element:

not found

, }, ]}> - + - + + + + ( @@ -23,36 +24,72 @@ function useSuggestions(query = '') { ); } +const useStyles = createStyles((theme) => ({ + header: { + paddingLeft: theme.spacing.md, + paddingRight: theme.spacing.md, + position: 'fixed', + }, + + inner: { + height: 120, + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, +})); + export function Home() { + const { classes } = useStyles(); + const navigate = useNavigate(); const [searchType, setSearchType] = useState('movie'); const handleChangeType = (type: TSearchType) => { setSearchType(type); - navigate({ to: `/search/${type}` }); + navigate({ to: `search/${type}` }); }; const handleSubmit = () => { navigate({ - to: `/search/${searchType}`, + to: `search/${searchType}`, }); }; const [search, setSearch] = useSearchQuery(); return ( - <> -

Welcome to the film app

- - - - +
+
+
+ + + + + + + + +
+
+
); } diff --git a/apps/film-and-tv-ratings/src/pages/Movie.tsx b/apps/film-and-tv-ratings/src/pages/Movie.tsx index 89bc250..6a5fced 100644 --- a/apps/film-and-tv-ratings/src/pages/Movie.tsx +++ b/apps/film-and-tv-ratings/src/pages/Movie.tsx @@ -1,16 +1,33 @@ import { + TvFilmReviewInput, UpsertReviewMutationVariables, + useDeleteReviewMutation, useReviewByIdQuery, useUpsertReviewMutation, useUser, } from '@juxt-home/site'; import { notEmpty } from '@juxt-home/utils'; -import { useForm } from 'react-hook-form'; +import { Controller, useForm } from 'react-hook-form'; import { useQuery, useQueryClient } from 'react-query'; -import { api_key, client } from '../common'; +import { api_key, client, devMode } from '../common'; +import { ReviewCard, TvFilmCard } from '../components/Card'; import { TMDBError } from '../components/Errors'; import { useReviews } from '../hooks'; import { TMovie } from '../types'; +import { RichTextEditor } from '@mantine/rte'; +import { + Button, + Text, + Title, + ScrollArea, + Container, + Group, + ActionIcon, + NumberInput, + NumberInputHandlers, +} from '@mantine/core'; +import { useRef } from 'react'; +import { toast } from 'react-toastify'; async function fetchMovieById(id: string) { const response = await client.get( @@ -28,68 +45,209 @@ function useMovieById(id = '') { export function Movie({ itemId }: { itemId: string }) { const movieResponse = useMovieById(itemId); const { data: movieData } = movieResponse; - const imdb_id = movieData?.imdb_id; - const reviewResponse = useReviews(imdb_id); + const tmdb_id = movieData?.id; + const tmdb_id_unique = `${tmdb_id}-movie`; + + const reviewResponse = useReviews(tmdb_id_unique, 'movie'); + + const handlers = useRef(); const queryClient = useQueryClient(); + const refetch = () => + queryClient.refetchQueries(useReviewByIdQuery.getKey({ tmdb_id_unique })); + const { mutate } = useUpsertReviewMutation({ onSuccess: () => { - if (imdb_id) { - queryClient.refetchQueries(useReviewByIdQuery.getKey({ imdb_id })); + if (tmdb_id) { + refetch(); } }, }); + const { mutate: deleteReview } = useDeleteReviewMutation({ + onSuccess: () => { + refetch(); + }, + }); + + const handleDelete = async (id: string) => { + window.confirm('Are you sure you want to delete this review?') && + deleteReview({ + id, + }); + }; + + const handleEdit = async ({ + reviewHTML, + score, + }: Partial) => { + setValue('TVFilmReview.reviewHTML', reviewHTML); + score && setValue('TVFilmReview.score', score); + }; + + const { handleSubmit, reset, control, setValue } = + useForm(); + const { id: username } = useUser(); - const formHooks = useForm(); - const handleSubmit = async (values: UpsertReviewMutationVariables) => { - if (imdb_id) { + + const onSubmit = async (values: UpsertReviewMutationVariables) => { + if (!values.TVFilmReview.reviewHTML || !values.TVFilmReview.score) { + toast.error('Please add a review and a score', { + autoClose: 1000, + }); + } else if (tmdb_id) { console.log('submit', values); - formHooks.reset(); + reset(); mutate({ TVFilmReview: { ...values.TVFilmReview, - imdb_id, - id: `user:${username},imdb_id:${imdb_id}`, + tmdb_id_unique: `${tmdb_id}-movie`, + tmdb_id, + type: 'movie', + id: `user:${username},tmdb_id:${tmdb_id}-movie`, }, }); } }; return ( -
-

Movie page

- {movieResponse.isLoading &&

loading...

} - {movieResponse.isError && } - {movieData && ( -
    -
  • -

    {movieData.title}

    -
  • -
  • -

    {movieData.overview}

    -
  • -
- )} - {reviewResponse.isLoading &&

loading reviews...

} - {reviewResponse.isError &&

error: {reviewResponse.error.message}

} - {reviewResponse.data && ( + + + {movieResponse.isLoading &&

loading...

} + {movieResponse.isError && } + {movieData && ( +
+ { + return e.length > 3; + }) + .join('')} + badge2={`${movieData.runtime} min`} + badge3={`IMDb rating: ${movieData.vote_average}`} + /> +
+ )} + {reviewResponse.isLoading &&

loading reviews...

} + {reviewResponse.isError &&

error: {reviewResponse.error.message}

} + {reviewResponse.data && ( +
+ ({ + margin: '20px 0', + })}> + Reviews + + {reviewResponse.data.tvFilmReviewsById + ?.filter(notEmpty) + .map((review) => ( +
+ +
+ ))} +
+ )}
-

Reviews

- {reviewResponse.data.tvFilmReviewsById - ?.filter(notEmpty) - .map((review) => ( -
- Review by {review._siteSubject || 'admin'} - {review.reviewHTML &&

{review.reviewHTML}

} -

{review.score}

-
- ))} + ({ + margin: '20px 0', + })}> + New Review + +
+ + ({ + marginBottom: 5, + })}> + Score (out of 10): + + + ( + + handlers.current?.decrement()}> + – + + + + + handlers.current?.increment()}> + + + + + )} + /> + +
+ ({ + marginBottom: 5, + })}> + Review: + + ( + + )} + /> +
+ +
- )} - {/* form */} -
+ + ); } diff --git a/apps/film-and-tv-ratings/src/pages/RecentReviews.tsx b/apps/film-and-tv-ratings/src/pages/RecentReviews.tsx index 8e26944..64877f0 100644 --- a/apps/film-and-tv-ratings/src/pages/RecentReviews.tsx +++ b/apps/film-and-tv-ratings/src/pages/RecentReviews.tsx @@ -5,62 +5,91 @@ import { } from '@juxt-home/site'; import { groupBy, notEmpty } from '@juxt-home/utils'; import { useQuery, useQueryClient } from 'react-query'; -import { api_key, client } from '../common'; +import { api_key, client, devMode } from '../common'; +import { ReviewCard } from '../components/Card'; import { TMDBError } from '../components/Errors'; -import { useReviews } from '../hooks'; -import { TMDBItemResponse, TReview } from '../types'; +import { TMDBItemResponse, TMovie, TReview, TTVShow } from '../types'; +import { Title, Text } from '@mantine/core'; +import { Link } from '@tanstack/react-location'; -async function fetchItemById(id: string) { - const response = await client.get( - `/3/find/${id}?api_key=${api_key}&language=en-GB&external_source=imdb_id`, +type ItemDetails = Partial; + +async function fetchItemById(id: string, type: string) { + const response = await client.get( + `/3/${type}/${id}?api_key=${api_key}&language=en-GB`, ); return response.data; } -function useItemById(id = '') { - return useQuery( +function useItemById(id = '', type: string) { + return useQuery( ['finditem', id], - () => fetchItemById(id), + () => fetchItemById(id, type), { enabled: !!id, }, ); } -function Review({ imdb_id, reviews }: { imdb_id: string; reviews: TReview[] }) { - const itemInfo = useItemById(imdb_id); +function Review({ reviews }: { id: string; reviews: Array }) { + const review = reviews[0]; + const itemInfo = useItemById(review?.tmdb_id, review?.type || 'movie'); + const queryClient = useQueryClient(); const { mutate } = useDeleteReviewMutation({ onSuccess: () => { queryClient.refetchQueries(useAllReviewsQuery.getKey()); }, }); + const handleDelete = async (id: string) => { - mutate({ - id, - }); + window.confirm('Are you sure you want to delete this review?') && + mutate({ + id, + }); }; - const result = itemInfo.data?.movie_results[0]; + + const result = itemInfo.data; const { id: username } = useUser(); - const devMode = true; + return (
{result && ( <> -

{result.title}

-

{result.overview}

-

Reviews:

+ + ({ + marginTop: 20, + })}> + {result?.title || result.name} + + + + {result?.overview && ( + ({ + margin: '10px 0', + })}> + {result.overview} + + )} + ({ + marginBottom: 20, + })}> + Reviews: +
    - {reviews.map((review) => ( + {reviews.filter(notEmpty).map((review) => (
    - {(devMode || review._siteSubject === username) && ( - - )} -
  • {review._siteSubject}
  • -
  • {review.reviewHTML}
  • -
  • {review.score}
  • +
    ))}
@@ -76,7 +105,7 @@ function useAllReviews() { return useAllReviewsQuery(undefined, { select: (data) => { const reviews = data.allTVFilmReviews?.filter(notEmpty); - const reviewsById = reviews && groupBy(reviews, (r) => r.imdb_id); + const reviewsById = reviews && groupBy(reviews, (r) => r.tmdb_id); return reviewsById; }, }); @@ -87,13 +116,19 @@ export function RecentReviews() { const { data } = response; return (
-

Recent Reviews

+ ({ + marginTop: 15, + })}> + Recent Reviews +
    {response.isLoading &&

    loading...

    } {response.isError &&

    error: {response.error.message}

    } {data && Object.keys(data).map((id: keyof typeof data) => ( - + ))}
diff --git a/apps/film-and-tv-ratings/src/pages/SearchResults.tsx b/apps/film-and-tv-ratings/src/pages/SearchResults.tsx index c0dbfb0..924930f 100644 --- a/apps/film-and-tv-ratings/src/pages/SearchResults.tsx +++ b/apps/film-and-tv-ratings/src/pages/SearchResults.tsx @@ -1,13 +1,13 @@ import { Link, Outlet, - useMatch, + useMatches, useNavigate, useSearch, } from '@tanstack/react-location'; import { useAtom } from 'jotai'; import { useQuery } from 'react-query'; -import { api_key, client } from '../common'; +import { api_key, client, PosterImage } from '../common'; import { TMDBError } from '../components/Errors'; import { searchAtom } from '../components/Search'; import { @@ -16,14 +16,26 @@ import { TSearchResults, TSearchType, } from '../types'; +import { + Card, + Text, + SimpleGrid, + Pagination, + Button, + ScrollArea, + Box, +} from '@mantine/core'; +import { useMobileDetect } from '@juxt-home/utils'; async function searchQuery( searchTerm: string, searchType: TSearchType, page = 1, ) { + const searchUrl = `/3/search/${searchType}?api_key=${api_key}&language=en-US&query=${searchTerm}&page=${page}&include_adult=false`; + const popularUrl = `/3/${searchType}/popular?api_key=${api_key}&language=en-US&page=${page}`; const response = await client.get( - `/3/search/${searchType}?api_key=${api_key}&language=en-US&query=${searchTerm}&page=${page}&include_adult=false`, + searchTerm.length > 1 ? searchUrl : popularUrl, ); return response.data; } @@ -36,17 +48,14 @@ function useSearchResults( return useQuery( [searchType, searchTerm, page], () => searchQuery(searchTerm, searchType, page), - { - enabled: searchTerm.length > 2, - }, ); } export function SearchResults() { const navigate = useNavigate(); - const { - params: { searchType }, - } = useMatch(); + const matches = useMatches(); + const searchType = matches[0].params?.searchType; + const itemId = matches[1]?.params?.itemId; const { page } = useSearch(); const [search] = useAtom(searchAtom); @@ -60,40 +69,101 @@ export function SearchResults() { }); }; + const isMobile = useMobileDetect().isMobile(); + const showBackButton = isMobile && itemId; + return (
- {search && ( + {showBackButton ? ( <> -

SearchResults page

-

current type is {searchType}

-

current search is {search}

- {response.isLoading &&

loading...

} - {response.isError && } - {response.isSuccess && ( - <> -

current page {response.data.page}

- - - -
    - {response.data.results?.map((result) => ( -
  • - - {result?.title || result?.name} - -
  • - ))} -
- - )} - {/* 'Outlet' renders the remaining route matches from the router. - In this case it will be where the /:itemId route is rendered */} + + ) : ( + ({ + height: '100%', + })}> +
+ {search ? ( + <> + ({ + marginTop: 20, + })}> + You are searching in the {searchType} category + + Showing results for "{search}" + + ) : ( + Showing popular results + )} + {response.isLoading &&

loading...

} + {response.isError && } + {response.isSuccess && ( + <> + ({ + margin: '20px 0 25px 0', + })} + /> + + + {response.data.results?.map((result) => ( +
+ + ({ + height: '300px', + width: '150px', + border: '0.5px solid #e7e8e7', + })}> + + + + + + {result?.title || result?.name} + + + +
+ ))} +
+
+ + )} +
+ +
)}
); diff --git a/apps/film-and-tv-ratings/src/pages/TvShow.tsx b/apps/film-and-tv-ratings/src/pages/TvShow.tsx index 7c27b5c..f951ca5 100644 --- a/apps/film-and-tv-ratings/src/pages/TvShow.tsx +++ b/apps/film-and-tv-ratings/src/pages/TvShow.tsx @@ -1,6 +1,33 @@ -import { useQuery } from 'react-query'; -import { api_key, client } from '../common'; +import { + useUpsertReviewMutation, + useReviewByIdQuery, + useUser, + UpsertReviewMutationVariables, + useDeleteReviewMutation, + TvFilmReviewInput, +} from '@juxt-home/site'; +import { Controller, useForm } from 'react-hook-form'; +import { useQuery, useQueryClient } from 'react-query'; +import { api_key, client, devMode } from '../common'; +import { useReviews } from '../hooks'; import { AxiosTMDBError, TTVShow } from '../types'; +import { toast } from 'react-toastify'; +import { TMDBError } from '../components/Errors'; +import { + Title, + Text, + Button, + ScrollArea, + Container, + Group, + ActionIcon, + NumberInput, + NumberInputHandlers, +} from '@mantine/core'; +import { ReviewCard, TvFilmCard } from '../components/Card'; +import RichTextEditor from '@mantine/rte'; +import { notEmpty } from '@juxt-home/utils'; +import { useRef } from 'react'; async function fetchTvShowById(id: string) { const response = await client.get( @@ -13,12 +40,227 @@ function useTvById(id = '') { return useQuery( ['movie', id], () => fetchTvShowById(id), + // here { enabled: !!id, }, ); } -export function TvShow() { - return

TvShow page

; +export function TvShow({ itemId }: { itemId: string }) { + const movieResponse = useTvById(itemId); + const { data: movieData } = movieResponse; + + const tmdb_id = movieData?.id; + const tmdb_id_unique = `${tmdb_id}-tv`; + + const reviewResponse = useReviews(tmdb_id_unique, 'tv'); + + const handlers = useRef(); + + const queryClient = useQueryClient(); + + const refetch = () => + queryClient.refetchQueries(useReviewByIdQuery.getKey({ tmdb_id_unique })); + + const { mutate } = useUpsertReviewMutation({ + onSuccess: () => { + if (tmdb_id) { + refetch(); + } + }, + }); + + const { mutate: deleteReview } = useDeleteReviewMutation({ + onSuccess: () => { + refetch(); + }, + }); + + const handleDelete = async (id: string) => { + window.confirm('Are you sure you want to delete this review?') && + deleteReview({ + id, + }); + }; + + const handleEdit = async ({ + reviewHTML, + score, + }: Partial) => { + setValue('TVFilmReview.reviewHTML', reviewHTML); + score && setValue('TVFilmReview.score', score); + }; + + const { handleSubmit, reset, control, setValue } = + useForm(); + + const { id: username } = useUser(); + + const onSubmit = async (values: UpsertReviewMutationVariables) => { + if (!values.TVFilmReview.reviewHTML || !values.TVFilmReview.score) { + toast.error('Please add a review and a score', { + autoClose: 1000, + }); + } else if (tmdb_id) { + console.log('submit', values); + reset(); + mutate({ + TVFilmReview: { + ...values.TVFilmReview, + // this is not in movie? + tmdb_id_unique: `${tmdb_id}-tv`, + tmdb_id, + type: 'tv', + id: `user:${username},tmdb_id:${tmdb_id}-tv`, + }, + }); + } + }; + + return ( + + + {movieResponse.isLoading &&

loading...

} + {movieResponse.isError && } + {movieData && ( +
+ { + return e.length > 3; + }) + .join('')}-${ + movieData.last_air_date && + movieData.last_air_date + .split('-') + .filter((e) => { + return e.length > 3; + }) + .join('') + }`} + badge2={`${movieData.number_of_seasons} seasons`} + badge3={`IMDb rating: ${movieData.vote_average}`} + /> +
+ )} + {reviewResponse.isLoading &&

loading reviews...

} + {reviewResponse.isError &&

error: {reviewResponse.error.message}

} + {reviewResponse.data && ( +
+ ({ + margin: '20px 0', + })}> + Reviews + + {reviewResponse.data.tvFilmReviewsById + ?.filter(notEmpty) + .map((review) => ( +
+ +
+ ))} +
+ )} +
+ ({ + margin: '20px 0', + })}> + New Review + +
+ + ({ + marginBottom: 5, + })}> + Score (out of 10): + + ( + + handlers.current?.decrement()}> + – + + + + + handlers.current?.increment()}> + + + + + )} + /> + +
+ ({ + marginBottom: 5, + })}> + Review: + + ( + + )} + /> +
+ + +
+
+
+ ); } diff --git a/apps/film-and-tv-ratings/src/types.ts b/apps/film-and-tv-ratings/src/types.ts index 6a822f8..8f1cecb 100644 --- a/apps/film-and-tv-ratings/src/types.ts +++ b/apps/film-and-tv-ratings/src/types.ts @@ -21,7 +21,7 @@ export type TSearchResult = { }; export type TMovie = { - id: number; + id: string; backdrop_path: string | null; budget: number; genres: { @@ -29,7 +29,7 @@ export type TMovie = { name: string; }[]; homepage: string; - imdb_id: string; + tmdb_id: string; original_language: string; original_title: string; overview: string; @@ -58,7 +58,7 @@ export type TMovie = { export type TSeason = { air_date: string; episode_count: number; - id: number; + id: string; name: string; overview: string; poster_path: string | null; @@ -68,7 +68,7 @@ export type TSeason = { export type TEpisode = { air_date: string; episode_number: number; - id: number; + id: string; name: string; overview: string; production_code: string; @@ -81,13 +81,13 @@ export type TEpisode = { export type TCompany = { name: string; - id: number; + id: string; logo_path: string | null; origin_country: string; }; export type TTVShow = { - id: number; + id: string; backdrop_path: string | null; created_by: { id: number; diff --git a/apps/hiring-kanban/deploydev.sh b/apps/hiring-kanban/deploydev.sh index 936740d..ed6110c 100755 --- a/apps/hiring-kanban/deploydev.sh +++ b/apps/hiring-kanban/deploydev.sh @@ -1,5 +1,5 @@ SITE_BASE_URI='http://localhost:5509' -GRAPHQL_PATH='/kanbantest/graphql' +GRAPHQL_PATH='/pairing/graphql' GRAPHQL_API_URL="${SITE_BASE_URI}${GRAPHQL_PATH}" site -s check-token || site get-token -u admin -p admin site post-resources --file resources.edn diff --git a/apps/hiring-kanban/src/components/CardForms/CardView.tsx b/apps/hiring-kanban/src/components/CardForms/CardView.tsx index 0c4a182..692806c 100644 --- a/apps/hiring-kanban/src/components/CardForms/CardView.tsx +++ b/apps/hiring-kanban/src/components/CardForms/CardView.tsx @@ -13,15 +13,17 @@ import { TipTapContent, MetadataGrid, IconForScore, + Modal, } from '@juxt-home/ui-common'; -import { notEmpty } from '@juxt-home/utils'; +import { notEmpty, searchObjToUrl } from '@juxt-home/utils'; import Tippy from '@tippyjs/react'; import classNames from 'classnames'; import { useState } from 'react'; import { useSearch } from '@tanstack/react-location'; import { toast } from 'react-toastify'; -import { InterviewModal } from './InterviewForms'; +import { InterviewFeedback } from './InterviewForms'; import { QuickEditCardWrapper, TaskListForState } from './UpdateHiringCardForm'; +import { ArrowsExpandIcon, ClipboardCopyIcon } from '@heroicons/react/solid'; function CardInfo({ card }: { card?: CardDetailsFragment }) { const [showFeedbackModal, setShowFeedbackModal] = useState(false); @@ -39,13 +41,14 @@ function CardInfo({ card }: { card?: CardDetailsFragment }) { enabled: notEmpty(card?.id), }, ); - const totalFeedbacks = cardFeedbackData?.feedbackForCard?.length || 0; - const totalScores = cardFeedbackData?.feedbackForCard - ?.filter(notEmpty) + const feedbacks = cardFeedbackData?.feedbackForCard?.filter(notEmpty); + const totalFeedbacks = feedbacks?.length || 0; + const totalScores = feedbacks ?.map((f) => f.overallScore) ?.reduce((acc, curr) => acc + curr || 0, 0); const averageScore = totalScores && totalFeedbacks && Math.floor(totalScores / totalFeedbacks); + return ( <> {!card && ( @@ -57,11 +60,13 @@ function CardInfo({ card }: { card?: CardDetailsFragment }) { )} {card && ( <> - setShowFeedbackModal(false)} - /> + setShowFeedbackModal(false)}> + +
{card && ( - {({ open }) => { - const interviewFeedbackUrl = `${window.location.origin}/_apps/interview/index.html?interviewCardId=${card.id}&filters=~(tabs~(~-feedback~-pdf))`; - return ( - <> - - Interview Feedback - {CloseIcon(open)} - - -
- -

- Send this to the person who will be doing - the interview. -

-

- Their feedback will appear below when they - have completed it. -

- - }> -
- -
-
- {averageScore ? ( - - ) : null} - {averageScore ? ( -
- Average Score - -
- ) : null} -
-
- - ); - }} + {({ open }) => ( + <> + + Comments + {CloseIcon(open)} + + + + + + )}
)} {card?.files && card?.files.length > 0 && ( @@ -242,7 +198,64 @@ function CardInfo({ card }: { card?: CardDetailsFragment }) { )}
- +
+
+ +

+ Send this to the person who will be doing the + interview. +

+

+ Their feedback will appear below when they have + completed it. +

+ + }> +
+ +
+
+ {averageScore ? ( + + + + ) : null} +
+ {averageScore ? ( + + ) : null} + {averageScore ? ( +
+ Average Score + +
+ ) : null} +
diff --git a/deploy-movie.sh b/deploy-movie.sh new file mode 100755 index 0000000..f96f2e8 --- /dev/null +++ b/deploy-movie.sh @@ -0,0 +1,2 @@ +nx build film-and-tv-ratings --prod +site put-static-site -d dist/apps/film-and-tv-ratings -p _apps/film-and-tv-reviews --spa true -b https://alexd.uk \ No newline at end of file diff --git a/libs/site/.env b/libs/site/.env new file mode 100644 index 0000000..263f70a --- /dev/null +++ b/libs/site/.env @@ -0,0 +1,3 @@ +SITE_BASE_URI=http://localhost:5509 +GRAPHQL_PATH=/pairing/graphql +GRAPHQL_URI=${SITE_BASE_URI}${GRAPHQL_PATH} diff --git a/libs/site/codegen-prod.js b/libs/site/codegen-prod.js new file mode 100644 index 0000000..54e3d10 --- /dev/null +++ b/libs/site/codegen-prod.js @@ -0,0 +1,50 @@ +module.exports = { + schema: 'schema.graphql', + documents: 'src/**/*.graphql', + generates: { + 'src/generated/graphql.ts': { + plugins: [ + 'typescript', + 'typescript-operations', + 'typescript-react-query', + ], + config: { + exposeQueryKeys: true, + exposeDocument: true, + exposeFetcher: true, + errorType: 'Error', + fetcher: { + endpoint: "https://alexd.uk/pairing/graphql", + fetchParams: JSON.stringify({ + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + credentials: 'include', + }), + }, + }, + }, + 'src/generated/validation.ts': { + plugins: ['typescript-validation-schema'], + config: { + schema: 'yup', + notAllowEmptyString: true, + importFrom: './graphql', + enumAsTypes: true, + directives: { + required: { + msg: 'required', + }, + constraint: { + minLength: 'min', + startsWith: ['matches', '/^$1/'], + format: { + email: 'email', + }, + }, + }, + }, + }, + }, +}; diff --git a/libs/site/package.json b/libs/site/package.json index 2615f1a..c0aaa05 100644 --- a/libs/site/package.json +++ b/libs/site/package.json @@ -5,8 +5,9 @@ "scripts": { "watch-generate-hooks": "cross-env-shell GRAPHQL_API_URL='${GRAPHQL_URI}' \"yarn graphql-codegen --config ./codegen.js --watch\"", "watch-schema-upload": " cross-env-shell \"ls schema.graphql resources.edn | entr -s './deploy'\"", - "test-generate": "cross-env-shell SITE_BASE_URI=https://alexd.uk GRAPHQL_PATH=/pairing/graphql GRAPHQL_URI='https://alexd.uk/pairing/graphql' concurrently \"yarn:watch-*\"", - "dev-generate": "cross-env-shell SITE_BASE_URI=http://localhost:2021 GRAPHQL_PATH=/pairing/graphql GRAPHQL_URI='http://localhost:2021/pairing/graphql' concurrently \"yarn:watch-*\"", + "test-generate": "concurrently \"yarn:watch-*\"", + "prod-generate": "yarn graphql-codegen --config codegen-prod.js", + "dev-generate": "concurrently \"yarn:watch-*\"", "serve": "yarn prod-generate" }, "license": "ISC", diff --git a/libs/site/schema.graphql b/libs/site/schema.graphql index 5d0fd6f..3b63c9c 100644 --- a/libs/site/schema.graphql +++ b/libs/site/schema.graphql @@ -26,9 +26,9 @@ type Query { allClients: [Client] allProgressItems: [ProgressItem] progressHistory(id: ID!): [ProgressItem] @site(history: "desc") - + # Film Guild allTVFilmReviews: [TVFilmReview] - tvFilmReviewsById(id: ID!): [TVFilmReview] @site(itemForId: "imdb_id") + tvFilmReviewsById(id: ID!): [TVFilmReview] @site(itemForId: "tmdb_id_unique") } type Mutation { @@ -95,6 +95,7 @@ type Mutation { @site(mutation: "update") deleteProgressItem(id: ID!): ProgressItem @site(mutation: "delete") + # Film Guild createTVFilmReview(TVFilmReview: TVFilmReviewInput): TVFilmReview deleteTVFilmReview(id: ID!): TVFilmReview @site(mutation: "delete") } @@ -360,18 +361,23 @@ type ProgressItem { _siteCreatedAt: String! } +# Film Guild input TVFilmReviewInput { id: ID! - imdb_id: String! + tmdb_id: String! + tmdb_id_unique: ID! reviewHTML: String + type: String! score: Int! } type TVFilmReview { id: ID! - imdb_id: ID! + tmdb_id: String! + tmdb_id_unique: ID! reviewHTML: String score: Int! + type: String _siteQuery: String _siteSubject: String _siteValidTime: String! diff --git a/libs/site/src/graphql-operations/TVFilmOperations.graphql b/libs/site/src/graphql-operations/TVFilmOperations.graphql index 5492b52..1800f05 100644 --- a/libs/site/src/graphql-operations/TVFilmOperations.graphql +++ b/libs/site/src/graphql-operations/TVFilmOperations.graphql @@ -4,8 +4,8 @@ query allReviews { } } -query reviewById($imdb_id: ID!) { - tvFilmReviewsById(id: $imdb_id) { +query reviewById($tmdb_id_unique: ID!) { + tvFilmReviewsById(id: $tmdb_id_unique) { ...reviewFields } } @@ -27,7 +27,9 @@ fragment reviewFields on TVFilmReview { _siteSubject _siteValidTime id - imdb_id + type + tmdb_id + tmdb_id_unique reviewHTML score } diff --git a/libs/site/src/lib/hooks.tsx b/libs/site/src/lib/hooks.tsx index 6d4575d..e3ad963 100644 --- a/libs/site/src/lib/hooks.tsx +++ b/libs/site/src/lib/hooks.tsx @@ -202,7 +202,7 @@ export function useUser() { ); const userImg = userAvatar(data); return { - id: data, + id: data || 'admin', avatar: userImg || '', }; } diff --git a/libs/utils/.swcrc b/libs/utils/.swcrc deleted file mode 100644 index 7da23de..0000000 --- a/libs/utils/.swcrc +++ /dev/null @@ -1,22 +0,0 @@ -{ - "jsc": { - "target": "es2017", - "parser": { - "syntax": "typescript", - "decorators": true, - "dynamicImport": true - }, - "transform": { - "decoratorMetadata": true, - "legacyDecorator": true - }, - "keepClassNames": true, - "externalHelpers": true, - "loose": true - }, - "module": { - "type": "commonjs", - "strict": true, - "noInterop": true - } -} \ No newline at end of file diff --git a/libs/utils/project.json b/libs/utils/project.json index f7ded58..d0d7e55 100644 --- a/libs/utils/project.json +++ b/libs/utils/project.json @@ -3,16 +3,6 @@ "sourceRoot": "libs/utils/src", "projectType": "library", "targets": { - "build": { - "executor": "@nrwl/js:swc", - "outputs": ["{options.outputPath}"], - "options": { - "outputPath": "dist/libs/utils", - "main": "libs/utils/src/index.ts", - "tsConfig": "libs/utils/tsconfig.lib.json", - "assets": ["libs/utils/*.md"] - } - }, "lint": { "executor": "@nrwl/linter:eslint", "outputs": ["{options.outputFile}"], diff --git a/package.json b/package.json index 028337a..ea032fa 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,10 @@ "@heroicons/react": "^1.0.6", "@hookform/resolvers": "^2.8.8", "@mantine/core": "^4.0.7", + "@mantine/form": "^4.0.7", "@mantine/hooks": "^4.0.7", + "@mantine/modals": "^4.0.7", + "@mantine/rte": "^4.0.7", "@react-pdf-viewer/core": "3.1.0", "@react-pdf-viewer/toolbar": "^3.1.0", "@splitbee/web": "^0.3.0", diff --git a/yarn.lock b/yarn.lock index 9216b52..75072c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1891,11 +1891,31 @@ react-popper "^2.2.5" react-textarea-autosize "^8.3.2" +"@mantine/form@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@mantine/form/-/form-4.0.7.tgz#3586e5225a27046098cd4b07c93e044843f96e18" + integrity sha512-hnLGdv4zJSQMs/r0lxBudgUIPoqJz7SRCLVn4uhZ9h95mIpE1fjRFlJkDJtOnwOb017lq2tdmgBfhOTNJ6KpEA== + "@mantine/hooks@^4.0.7": version "4.0.7" resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-4.0.7.tgz#7b6d12994f3bc35e653dd3054efeaef40f8af032" integrity sha512-SjP6SZIgri3oMLDZBDYIo+nj9EKAN5OgvCDzREfD9k++BluPk2AfyzCpaZ7mr4+4ZskS6o5FIBvKqvUZYzr9bg== +"@mantine/modals@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-4.0.7.tgz#dae7d11a28a3f4d428c6c28e88f2644188a84552" + integrity sha512-+gn91uEnTtmLHjCtRzOeDUU2zx7jvpAWHDKBV8UKm91rTouuNaETp7aqvMPK6L9BQsYtektOd0Hhe1GkVTNeAw== + +"@mantine/rte@^4.0.7": + version "4.0.7" + resolved "https://registry.yarnpkg.com/@mantine/rte/-/rte-4.0.7.tgz#3b6e0f3831c2518afc8fdf9b8230c951cc90c829" + integrity sha512-4NGmybkHb6vrgv1mX3qWmX7eWgfa+Ko7dcqu40wHH9wSEuw4FrPw/pkfUbimPBQQH77l0LeOmhMBPvEFpo1MVw== + dependencies: + "@modulz/radix-icons" "^4.0.0" + clsx "^1.1.1" + quill-mention "^3.0.8" + react-quill "2.0.0-beta.4" + "@mantine/styles@4.0.7": version "4.0.7" resolved "https://registry.yarnpkg.com/@mantine/styles/-/styles-4.0.7.tgz#bb2e3cde15004b9d9a0f72ed359da8c24bd8fd6a" @@ -1908,6 +1928,11 @@ clsx "^1.1.1" csstype "^3.0.9" +"@modulz/radix-icons@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@modulz/radix-icons/-/radix-icons-4.0.0.tgz#628a7826a4569dd2e3331e266d8d2e26c7d6753c" + integrity sha512-d79T4cYvMGMocVC/q1WiMO578FPFfUoxmXIkduc8cUQAPDr7thuEV5HrCHhqqqkoVaYD0QPct180ilcNUyGPrg== + "@n1ru4l/graphql-live-query@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc" @@ -3625,6 +3650,13 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/quill@^1.3.10": + version "1.3.10" + resolved "https://registry.yarnpkg.com/@types/quill/-/quill-1.3.10.tgz#dc1f7b6587f7ee94bdf5291bc92289f6f0497613" + integrity sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw== + dependencies: + parchment "^1.1.2" + "@types/range-parser@*": version "1.2.4" resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" @@ -5409,6 +5441,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +clone@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18= + clsx@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" @@ -6898,6 +6935,11 @@ eventemitter2@^6.4.3: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.5.tgz#97380f758ae24ac15df8353e0cc27f8b95644655" integrity sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw== +eventemitter3@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba" + integrity sha1-teEHm1n7XhuidxwKmTvgYKWMmbo= + eventemitter3@^3.1.0: version "3.1.2" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" @@ -7001,7 +7043,7 @@ express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -7051,6 +7093,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154" + integrity sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig== + fast-diff@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" @@ -9739,7 +9786,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0, lodash@~4.17.0: +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0, lodash@~4.17.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -10625,6 +10672,11 @@ param-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" +parchment@^1.1.2, parchment@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/parchment/-/parchment-1.1.4.tgz#aeded7ab938fe921d4c34bc339ce1168bc2ffde5" + integrity sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -11375,6 +11427,34 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== +quill-delta@^3.6.2: + version "3.6.3" + resolved "https://registry.yarnpkg.com/quill-delta/-/quill-delta-3.6.3.tgz#b19fd2b89412301c60e1ff213d8d860eac0f1032" + integrity sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg== + dependencies: + deep-equal "^1.0.1" + extend "^3.0.2" + fast-diff "1.1.2" + +quill-mention@^3.0.8: + version "3.1.0" + resolved "https://registry.yarnpkg.com/quill-mention/-/quill-mention-3.1.0.tgz#acf0bf21524b627e9304f63534e6d2b8c59ab4fd" + integrity sha512-uyjGK8QPJHEcjvNc3wUJy6a05Oiu+6JJ0N9SFAwjYHYThgMzlKucyD975fq28Mr1it8ZFRNiRMPa0sCiVOAEwA== + dependencies: + quill "^1.3.7" + +quill@^1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/quill/-/quill-1.3.7.tgz#da5b2f3a2c470e932340cdbf3668c9f21f9286e8" + integrity sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g== + dependencies: + clone "^2.1.1" + deep-equal "^1.0.1" + eventemitter3 "^2.0.3" + extend "^3.0.2" + parchment "^1.1.4" + quill-delta "^3.6.2" + raf-schd@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" @@ -11538,6 +11618,15 @@ react-query@^3.34.16: broadcast-channel "^3.4.1" match-sorter "^6.0.2" +react-quill@2.0.0-beta.4: + version "2.0.0-beta.4" + resolved "https://registry.yarnpkg.com/react-quill/-/react-quill-2.0.0-beta.4.tgz#522bd2680dc55713068c6cac12f2bf2ccfebcd28" + integrity sha512-KyAHvAlPjP4xLElKZJefMth91Z6FbbXRvq9OSu6xN3KBaoasLP9p+3dcxg4Ywr4tBlpMGXcPszYSAgd5CpJ45Q== + dependencies: + "@types/quill" "^1.3.10" + lodash "^4.17.4" + quill "^1.3.7" + react-redux@^7.2.0: version "7.2.6" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa"