Skip to content

Commit

Permalink
Merge pull request #30 from canisterism/search
Browse files Browse the repository at this point in the history
  • Loading branch information
canisterism authored Jul 27, 2023
2 parents a63e945 + bf995c2 commit c236a1b
Show file tree
Hide file tree
Showing 17 changed files with 460 additions and 281 deletions.
1 change: 0 additions & 1 deletion backend/app/graphql/types/platform_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ module Types
class PlatformType < Types::BaseObject
implements GraphQL::Types::Relay::Node
field :name, String
field :published_at, GraphQL::Types::ISO8601DateTime
field :games, [Types::GameType], null: false

def games
Expand Down
2 changes: 1 addition & 1 deletion frontend/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CodegenConfig } from "@graphql-codegen/cli";

const config: CodegenConfig = {
schema: "../schema.graphql",
documents: "graphql/**/*.ts",
documents: "./**/*.ts(x)",
generates: {
"./graphql/generated/": {
preset: "client",
Expand Down
19 changes: 19 additions & 0 deletions frontend/components/Game/GameImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Image from "next/image";
type Props = {
imageUrl: string | undefined;
title: string;
};

export default function GameImage({ imageUrl, title }: Props) {
return (
<div className="aspect-h-4 aspect-w-3 overflow-hidden rounded-lg bg-gray-200 ">
<Image
src={imageUrl ? `${imageUrl}` : "https://placeimg.com/320/400/any"}
alt={`${title}`}
className="h-full w-full object-cover object-center"
layout="fill"
objectFit="cover"
/>
</div>
);
}
31 changes: 31 additions & 0 deletions frontend/components/Game/RatingStars.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { classNames } from "@/lib/classNames";
import { StarIcon } from "@heroicons/react/20/solid";

export default function RatingStars({
rating,
size = "md",
}: {
rating: number;
size: "sm" | "md" | "lg";
}) {
const sizeClass = {
sm: "h-5 w-5",
md: "h-6 w-6",
lg: "h-9 w-9",
}[size];

return (
<div className="flex items-start">
{[0, 1, 2, 3, 4].map((n) => (
<StarIcon
key={n}
className={classNames(
Math.floor(rating) > n ? "text-yellow-400" : "text-gray-100",
`${sizeClass} flex-shrink-0`
)}
aria-hidden="true"
/>
))}
</div>
);
}
24 changes: 2 additions & 22 deletions frontend/components/GameList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"use client";

import { classNames } from "@/lib/classNames";
import { StarIcon } from "@heroicons/react/20/solid";

import RatingStars from "@/components/Game/RatingStars";
import Image from "next/image";
import Link from "next/link";
import { useHits } from "react-instantsearch-hooks-web";
Expand Down Expand Up @@ -50,7 +48,7 @@ function Hit({ hit: game }: { hit: any }) {
</h3>
<div className="mt-3 flex flex-col items-start">
<p className="sr-only">{game.ratingAverage} out of 5 stars</p>
<RatingStars ratingAverage={game.ratingTotal / game.ratingCount} />
<RatingStars rating={game.ratingTotal / game.ratingCount} size="sm" />
<p className="mt-1 text-sm text-gray-500">
{game.reviewCount || 0} reviews
</p>
Expand All @@ -62,21 +60,3 @@ function Hit({ hit: game }: { hit: any }) {
</div>
);
}
function RatingStars({ ratingAverage }: { ratingAverage: number }) {
return (
<div className="flex items-start">
{[0, 1, 2, 3, 4].map((rating) => (
<StarIcon
key={rating}
className={classNames(
Math.floor(ratingAverage) > rating
? "text-yellow-400"
: "text-gray-100",
"h-5 w-5 flex-shrink-0"
)}
aria-hidden="true"
/>
))}
</div>
);
}
81 changes: 81 additions & 0 deletions frontend/components/Review/ReviewListItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import RatingStars from "@/components/Game/RatingStars";
import { FragmentType, graphql, useFragment } from "@/graphql/generated";
import { format } from "date-fns";
import Image from "next/image";
import Link from "next/link";

export const ReviewListItemFragment = graphql(`
fragment ReviewListItemFragment on Review {
id
body
rating
createdAt
profile {
id
displayName
photoUrl
}
}
`);

type Props = {
review: FragmentType<typeof ReviewListItemFragment>;
};

export function ReviewListItem(props: Props) {
const review = useFragment(ReviewListItemFragment, props.review);
console.log({ review });
return (
<div
key={review.id}
className="text-gray-100 flex flex-col gap-2 p-4 bg-gray-800 rounded-md border border-gray-600"
>
<div className="flex">
<ProfileHeading {...review.profile} />
<div className="flex-grow" aria-hidden></div>
</div>
<div className="flex items-center gap-2">
<RatingStars rating={review.rating} size="md" />
<span className="text-xl font-semibold flex ">
{review.rating.toFixed(1)}
</span>
</div>
<div className="gap-1">
<p>{review.body}</p>
<span className="text-gray-400 text-sm">
投稿日:{format(new Date(review.createdAt), "yyyy-MM-dd")}
</span>
</div>
</div>
);
}

function ProfileHeading({
id,
photoUrl,
displayName,
}: {
id: string;
photoUrl: string | null | undefined;
displayName: string | null | undefined;
}) {
return (
<Link href={`/users/${id}`} passHref>
<a>
<div className="flex gap-4">
<Image
className="h-8 w-8 rounded-full bg-gray-50"
src={photoUrl ?? ""}
alt="profile icon"
width="40"
height="40"
/>

<span className="text-lg font-semibold text-gray-300 flex items-center">
{displayName ?? "退会済みユーザー"}
</span>
</div>
</a>
</Link>
);
}
23 changes: 23 additions & 0 deletions frontend/components/StatButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type Props = {
icon?: React.ReactNode;
label: string;
stat: string;
onClick?: () => void;
};

export default function StatButton({ icon, label, stat, onClick }: Props) {
return (
<button
className="isolate inline-flex rounded-md shadow-sm"
onClick={onClick}
>
<div className="relative inline-flex items-center gap-x-1.5 rounded-l-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10">
{icon}
{label}
</div>
<div className="relative -ml-px inline-flex items-center rounded-r-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10">
{stat}
</div>
</button>
);
}
2 changes: 1 addition & 1 deletion frontend/components/layouts/SideBarLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ const SideBarLayout: React.FC<Props> = ({ children }) => {
</div>
</div>

<main className="py-10">
<main className="mx-auto py-10 max-w-5xl">
<div className="px-4 sm:px-6 lg:px-8">{children}</div>
</main>
</div>
Expand Down
13 changes: 12 additions & 1 deletion frontend/context/meContext.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { graphql } from "@/graphql/generated";
import { MeDocument, MeQuery } from "@/graphql/generated/graphql";
import { useQuery } from "@apollo/client";
import { ReactNode, createContext } from "react";

export const meQuery = graphql(`
query me {
me {
id
displayName
photoUrl
}
}
`);

type MeContextType = {
me: MeQuery["me"] | null; // Userも多分必要なので後で考える
me: MeQuery["me"];
loading: boolean;
error: any;
};
Expand Down
20 changes: 10 additions & 10 deletions frontend/graphql/generated/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import { TypedDocumentNode as DocumentNode } from "@graphql-typed-document-node/
* Therefore it is highly recommended to use the babel or swc plugin for production.
*/
const documents = {
"\n query game($gameId: ID!) {\n game(id: $gameId) {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n reviews {\n body\n rating\n createdAt\n }\n }\n }\n":
types.GameDocument,
"\n query games($first: Int, $last: Int, $before: String, $after: String) {\n games(first: $first, last: $last, before: $before, after: $after) {\n nodes {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n }\n }\n }\n":
types.GamesDocument,
"\n fragment ReviewListItemFragment on Review {\n id\n body\n rating\n createdAt\n profile {\n id\n displayName\n photoUrl\n }\n }\n":
types.ReviewListItemFragmentFragmentDoc,
"\n query me {\n me {\n id\n displayName\n photoUrl\n }\n }\n":
types.MeDocument,
"\n query game($gameId: ID!) {\n game(id: $gameId) {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n price\n genres {\n id\n name\n }\n publisher {\n id\n name\n }\n platforms {\n id\n name\n }\n reviews {\n ...ReviewListItemFragment\n }\n }\n }\n":
types.GameDocument,
};

/**
Expand All @@ -39,20 +39,20 @@ export function graphql(source: string): unknown;
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: "\n query game($gameId: ID!) {\n game(id: $gameId) {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n reviews {\n body\n rating\n createdAt\n }\n }\n }\n"
): (typeof documents)["\n query game($gameId: ID!) {\n game(id: $gameId) {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n reviews {\n body\n rating\n createdAt\n }\n }\n }\n"];
source: "\n fragment ReviewListItemFragment on Review {\n id\n body\n rating\n createdAt\n profile {\n id\n displayName\n photoUrl\n }\n }\n"
): (typeof documents)["\n fragment ReviewListItemFragment on Review {\n id\n body\n rating\n createdAt\n profile {\n id\n displayName\n photoUrl\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: "\n query games($first: Int, $last: Int, $before: String, $after: String) {\n games(first: $first, last: $last, before: $before, after: $after) {\n nodes {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n }\n }\n }\n"
): (typeof documents)["\n query games($first: Int, $last: Int, $before: String, $after: String) {\n games(first: $first, last: $last, before: $before, after: $after) {\n nodes {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n }\n }\n }\n"];
source: "\n query me {\n me {\n id\n displayName\n photoUrl\n }\n }\n"
): (typeof documents)["\n query me {\n me {\n id\n displayName\n photoUrl\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(
source: "\n query me {\n me {\n id\n displayName\n photoUrl\n }\n }\n"
): (typeof documents)["\n query me {\n me {\n id\n displayName\n photoUrl\n }\n }\n"];
source: "\n query game($gameId: ID!) {\n game(id: $gameId) {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n price\n genres {\n id\n name\n }\n publisher {\n id\n name\n }\n platforms {\n id\n name\n }\n reviews {\n ...ReviewListItemFragment\n }\n }\n }\n"
): (typeof documents)["\n query game($gameId: ID!) {\n game(id: $gameId) {\n id\n title\n imageUrl\n reviewsCount\n clipsCount\n publishedAt\n ratingAverage\n price\n genres {\n id\n name\n }\n publisher {\n id\n name\n }\n platforms {\n id\n name\n }\n reviews {\n ...ReviewListItemFragment\n }\n }\n }\n"];

export function graphql(source: string) {
return (documents as any)[source] ?? {};
Expand Down
Loading

0 comments on commit c236a1b

Please sign in to comment.