Commit 314d7508 authored by k1350's avatar k1350
Browse files

[update] ブログ確認画面、ブログ編集画面

- ブログ確認画面作成
- ブログ編集画面の初期レンダリング実装
parent 8ad96207
VITE_API_BASE_URL=http://localhost:8080/query
\ No newline at end of file
VITE_API_BASE_URL=http://localhost:8080/query
VITE_FRONT_URL_BASE=http://localhost/
\ No newline at end of file
......@@ -8,6 +8,7 @@
"name": "front",
"version": "0.0.0",
"dependencies": {
"@chakra-ui/icons": "^2.0.2",
"@chakra-ui/react": "^2.1.2",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
......@@ -17,6 +18,7 @@
"@graphql-codegen/typescript-operations": "^2.4.0",
"@graphql-codegen/typescript-react-query": "^3.5.12",
"@tanstack/react-location": "^3.7.4",
"dayjs": "^1.11.3",
"framer-motion": "^6.3.10",
"graphql": "^15.8.0",
"react": "^18.0.0",
......@@ -1090,6 +1092,42 @@
"react": ">=18"
}
},
"node_modules/@chakra-ui/icons": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.0.2.tgz",
"integrity": "sha512-UY7vP8E5pv2a4sd1SiezgVbmq1/tRfnehk6PatunrTvGMxQNhSKJJoiRI/wCtUfMoMz+yp9+ekc1ksJVCnokRg==",
"dependencies": {
"@chakra-ui/icon": "3.0.2",
"@types/react": "^18.0.1"
},
"peerDependencies": {
"@chakra-ui/system": ">=2.0.0-next.0",
"react": ">=18"
}
},
"node_modules/@chakra-ui/icons/node_modules/@chakra-ui/icon": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.0.2.tgz",
"integrity": "sha512-sas37byldn5O/TTIKHzxHBujEYqVPXegisoMqutLtF60fpXnb62aG1JTyorXSG3zJxJWQW7+AvjbOGyWKHXc0Q==",
"dependencies": {
"@chakra-ui/utils": "2.0.2"
},
"peerDependencies": {
"@chakra-ui/system": ">=2.0.0-next.0",
"react": ">=18"
}
},
"node_modules/@chakra-ui/icons/node_modules/@chakra-ui/utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.2.tgz",
"integrity": "sha512-9AC/ir9zm0shgFG7kdzOKUH2Wx5VB71M3uRMEsMZf75YlhhiU7AvBNtWXnJu+CBiTi41rKa5A+2ImMOsuPfGbA==",
"dependencies": {
"@types/lodash.mergewith": "4.6.6",
"css-box-model": "1.2.1",
"framesync": "5.3.0",
"lodash.mergewith": "4.6.2"
}
},
"node_modules/@chakra-ui/image": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.2.tgz",
......@@ -2705,14 +2743,12 @@
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
"devOptional": true
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"node_modules/@types/react": {
"version": "18.0.9",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz",
"integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==",
"devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
......@@ -2731,8 +2767,7 @@
"node_modules/@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"devOptional": true
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"node_modules/@types/ws": {
"version": "8.5.3",
......@@ -3557,6 +3592,11 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
},
"node_modules/dayjs": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
"integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A=="
},
"node_modules/debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
......@@ -8502,6 +8542,36 @@
"@chakra-ui/utils": "2.0.1"
}
},
"@chakra-ui/icons": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.0.2.tgz",
"integrity": "sha512-UY7vP8E5pv2a4sd1SiezgVbmq1/tRfnehk6PatunrTvGMxQNhSKJJoiRI/wCtUfMoMz+yp9+ekc1ksJVCnokRg==",
"requires": {
"@chakra-ui/icon": "3.0.2",
"@types/react": "^18.0.1"
},
"dependencies": {
"@chakra-ui/icon": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@chakra-ui/icon/-/icon-3.0.2.tgz",
"integrity": "sha512-sas37byldn5O/TTIKHzxHBujEYqVPXegisoMqutLtF60fpXnb62aG1JTyorXSG3zJxJWQW7+AvjbOGyWKHXc0Q==",
"requires": {
"@chakra-ui/utils": "2.0.2"
}
},
"@chakra-ui/utils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@chakra-ui/utils/-/utils-2.0.2.tgz",
"integrity": "sha512-9AC/ir9zm0shgFG7kdzOKUH2Wx5VB71M3uRMEsMZf75YlhhiU7AvBNtWXnJu+CBiTi41rKa5A+2ImMOsuPfGbA==",
"requires": {
"@types/lodash.mergewith": "4.6.6",
"css-box-model": "1.2.1",
"framesync": "5.3.0",
"lodash.mergewith": "4.6.2"
}
}
}
},
"@chakra-ui/image": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.2.tgz",
......@@ -9744,14 +9814,12 @@
"@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
"devOptional": true
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"@types/react": {
"version": "18.0.9",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.9.tgz",
"integrity": "sha512-9bjbg1hJHUm4De19L1cHiW0Jvx3geel6Qczhjd0qY5VKVE2X5+x77YxAepuCwVh4vrgZJdgEJw48zrhRIeF4Nw==",
"devOptional": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
......@@ -9770,8 +9838,7 @@
"@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"devOptional": true
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"@types/ws": {
"version": "8.5.3",
......@@ -10417,6 +10484,11 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
},
"dayjs": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
"integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A=="
},
"debounce": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz",
......
......@@ -9,6 +9,7 @@
"generate": "graphql-codegen"
},
"dependencies": {
"@chakra-ui/icons": "^2.0.2",
"@chakra-ui/react": "^2.1.2",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
......@@ -18,6 +19,7 @@
"@graphql-codegen/typescript-operations": "^2.4.0",
"@graphql-codegen/typescript-react-query": "^3.5.12",
"@tanstack/react-location": "^3.7.4",
"dayjs": "^1.11.3",
"framer-motion": "^6.3.10",
"graphql": "^15.8.0",
"react": "^18.0.0",
......
import {
Link,
Outlet,
Router,
} from '@tanstack/react-location';
import { location, routes } from "./Router";
import './App.css'
import "./App.css";
import AuthMenu from "./ui/AuthMenu";
function App() {
return (
<Router location={location} routes={routes}>
<div>
<Link
to="/"
activeOptions={{ exact: true }}
>
TOP
</Link>
<Link to="about">About</Link>
<Link to="home">Home</Link>
</div>
<hr />
<Outlet /> {/* パスが一致した際にレンダリングされるコンポーネント */}
</Router>
)
return <AuthMenu></AuthMenu>;
}
export default App
export default App;
import { ReactLocation, Route } from '@tanstack/react-location';
import Index from './components/page/Index';
import { ReactLocation, Route } from "@tanstack/react-location";
import Index from "./pages/Index";
export const location = new ReactLocation();
export const routes: Route[] = [
{
path: '/',
element: <Index />
},
{
path: 'about',
{
path: "/",
element: <Index />,
},
{
path: "login",
element: () => import("./pages/Login").then((res) => <res.Login />),
},
{
path: "about",
element: () => import("./pages/About").then((res) => <res.About />),
},
{
path: "home",
element: () => import("./pages/Home").then((res) => <res.Home />),
},
{
path: "blogs",
children: [
{
path: ":blogId",
children: [
{
path: '/',
element: () => import('./components/page/About').then((res) => <res.About />)
},
{
path: ':postId',
element: () => import('./components/page/Login').then((res) => <res.Login />)
}
]
},
{
path: 'home',
element: () => import('./components/page/Home').then((res) => <res.Home />)
}
];
\ No newline at end of file
{
path: "/",
element: () =>
import("./pages/BlogDetail").then((res) => <res.BlogDetail />),
},
{
path: "edit",
element: () =>
import("./pages/BlogEdit").then((res) => <res.BlogEdit />),
},
],
},
],
},
];
......@@ -228,6 +228,13 @@ export type FindBlogsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindBlogsQuery = { __typename?: 'Query', blogs?: Array<{ __typename?: 'Blog', id: string, blogKey: string, author: string, name: string, description: string, publishOption: PublishOption, createdAt: string, updatedAt: string, links?: Array<{ __typename?: 'BlogLink', id: string, name: string, url: string } | null> | null } | null> | null };
export type GetBlogQueryVariables = Exact<{
id: Scalars['ID'];
}>;
export type GetBlogQuery = { __typename?: 'Query', blogById?: { __typename?: 'Blog', id: string, blogKey: string, author: string, name: string, description: string, publishOption: PublishOption, createdAt: string, updatedAt: string, links?: Array<{ __typename?: 'BlogLink', id: string, name: string, url: string } | null> | null } | null };
export const FindBlogsDocument = `
query findBlogs {
......@@ -259,4 +266,35 @@ export const useFindBlogsQuery = <
variables === undefined ? ['findBlogs'] : ['findBlogs', variables],
fetcher<FindBlogsQuery, FindBlogsQueryVariables>(FindBlogsDocument, variables),
options
);
export const GetBlogDocument = `
query getBlog($id: ID!) {
blogById(id: $id) {
id
blogKey
author
name
description
publishOption
createdAt
updatedAt
links {
id
name
url
}
}
}
`;
export const useGetBlogQuery = <
TData = GetBlogQuery,
TError = any
>(
variables: GetBlogQueryVariables,
options?: UseQueryOptions<GetBlogQuery, TError, TData>
) =>
useQuery<GetBlogQuery, TError, TData>(
['getBlog', variables],
fetcher<GetBlogQuery, GetBlogQueryVariables>(GetBlogDocument, variables),
options
);
\ No newline at end of file
......@@ -14,4 +14,22 @@ query findBlogs {
url
}
}
}
\ No newline at end of file
}
query getBlog($id: ID!) {
blogById(id: $id) {
id
blogKey
author
name
description
publishOption
createdAt
updatedAt
links {
id
name
url
}
}
}
\ No newline at end of file
import { Heading } from '@chakra-ui/react'
export function About() {
return (
<Heading as='h1' size='4xl' noOfLines={1}>
About
</Heading>
)
}
\ No newline at end of file
import { useFindBlogsQuery } from '../../api/generated'
import { Heading, Spinner } from '@chakra-ui/react'
export function Home() {
const { status, data, error, isFetching } = useFindBlogsQuery({});
return (
<div>
<Heading as='h1' size='4xl' noOfLines={1}>ホーム</Heading>
<div>
{status === "loading" ? (
<Spinner size='xl' />
) : status === "error" ? (
<span>Error: {error?.message}</span>
) : (
<>
{isFetching ? (
<Spinner size='xl' />
) : (
<div>
{data?.blogs?.map((blog) => (
<p key={blog?.id}>
{blog?.name}
</p>
))}
</div>)}
</>
)}
</div>
</div>
);
}
\ No newline at end of file
import { Heading } from '@chakra-ui/react'
function Index() {
return (
<Heading as='h1' size='4xl' noOfLines={1}>
Hello world!
</Heading>
)
}
export default Index
\ No newline at end of file
import { useEffect, useState } from 'react';
import { useMatch } from '@tanstack/react-location';
import { Heading } from '@chakra-ui/react'
export function Login() {
const { postId } = useMatch().params;
const [post, setPost] = useState<string>();
useEffect(() => {
setPost(postId + ": Login");
}, [postId]);
if (!post) {
return null;
}
return (
<div>
<Heading as='h1' size='4xl' noOfLines={1}>
{post}
</Heading>
</div>
)
}
import { Heading } from "@chakra-ui/react";
export function About() {
return (
<Heading as="h1" size="4xl" noOfLines={1}>
About
</Heading>
);
}
import { useMatch } from "@tanstack/react-location";
import {
Heading,
Spinner,
ButtonGroup,
Link,
Box,
Text,
Grid,
Button,
Center,
} from "@chakra-ui/react";
import { useGetBlogQuery } from "../api/generated";
import dayjs from "dayjs";
import { LinkText } from "../ui/LinkText";
import { CloseIcon, EditIcon } from "@chakra-ui/icons";
export function BlogDetail() {
const { blogId } = useMatch().params;
const { status, data, error, isFetching } = useGetBlogQuery({ id: blogId });
return (
<div>
<Heading as="h2" mt={4} mb={4}>
ブログ情報
</Heading>
<div>
{status === "loading" ? (
<Spinner size="xl" />
) : status === "error" ? (
<span>Error: {error?.message}</span>
) : (
<>
{isFetching ? (
<Spinner size="xl" />
) : (
<Box>
<Grid templateColumns="max-content 1fr" gap={1} mb={2}>
<Text mr={2}>URL:</Text>
<Text>
<Link
href={
import.meta.env.VITE_FRONT_URL_BASE +
data?.blogById?.blogKey
}
isExternal
>
<LinkText
text={
import.meta.env.VITE_FRONT_URL_BASE +
data?.blogById?.blogKey
}
isExternal={true}
/>
</Link>
</Text>
<Text mr={2}>著者名:</Text>
<Text>{data?.blogById?.author}</Text>
<Text mr={2}>ブログ名:</Text>
<Text>{data?.blogById?.name}</Text>
<Text mr={2}>ブログ説明:</Text>
<Text>{data?.blogById?.description}</Text>
<Text mr={2}>公開状態:</Text>
<Text>{data?.blogById?.publishOption}</Text>
<Text mr={2}>リンク: </Text>
<Grid gap={2}>
{data?.blogById?.links?.map((link) => (
<Grid
key={link?.id}
templateColumns="max-content 1fr"
gap={1}
>
<Text mr={2}>リンクタイトル: </Text>
<Text>{link?.name}</Text>
<Text mr={2}>URL: </Text>
<Link href={link?.url}>
<LinkText text={link?.url ?? ""} isExternal={true} />
</Link>
</Grid>
))}
</Grid>
<Text mr={2}>作成日時:</Text>
<Text>
<time dateTime={data?.blogById?.createdAt}>
{dayjs(data?.blogById?.createdAt).format(
"YYYY/M/D HH:mm:ss"
)}
</time>
</Text>
<Text mr={2}>更新日時:</Text>
<Text>
<time dateTime={data?.blogById?.updatedAt}>
{dayjs(data?.blogById?.updatedAt).format(
"YYYY/M/D HH:mm:ss"
)}
</time>
</Text>
</Grid>
<Center>
<ButtonGroup justifyContent="center" size="sm">
<Button
colorScheme="blue"
leftIcon={<EditIcon />}
as="a"
href={`/blogs/${blogId}/edit`}
>
<Text>編集する</Text>
</Button>
<Button
colorScheme="blue"
variant="outline"
leftIcon={<CloseIcon />}
as="a"
href="/home"
>
<Text>戻る</Text>
</Button>
</ButtonGroup>
</Center>
</Box>
)}
</>
)}
</div>
</div>
);
}
import { useState, useEffect } from "react";
import { useMatch } from "@tanstack/react-location";
import {
Heading,
Spinner,
ButtonGroup,
Link,
Box,
Text,
Grid,
Button,
Center,
Input,
Textarea,
Radio,
RadioGroup,
Stack,
} from "@chakra-ui/react";
import { useGetBlogQuery } from "../api/generated";
import { LinkText } from "../ui/LinkText";
import { CloseIcon, CheckIcon } from "@chakra-ui/icons";