diff --git a/api/prisma/migrations/20201009213512-create-posts/README.md b/api/prisma/migrations/20201009213512-create-posts/README.md new file mode 100644 index 0000000..f52912a --- /dev/null +++ b/api/prisma/migrations/20201009213512-create-posts/README.md @@ -0,0 +1,45 @@ +# Migration `20201009213512-create-posts` + +This migration has been generated by Kurt Hutten at 10/10/2020, 8:35:12 AM. +You can check out the [state of the schema](./schema.prisma) after the migration. + +## Database Steps + +```sql +CREATE TABLE "Post" ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "title" TEXT NOT NULL, + "body" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +) +``` + +## Changes + +```diff +diff --git schema.prisma schema.prisma +migration ..20201009213512-create-posts +--- datamodel.dml ++++ datamodel.dml +@@ -1,0 +1,18 @@ ++datasource DS { ++ // optionally set multiple providers ++ // example: provider = ["sqlite", "postgresql"] ++ provider = "sqlite" ++ url = "***" ++} ++ ++generator client { ++ provider = "prisma-client-js" ++ binaryTargets = "native" ++} ++ ++model Post { ++ id Int @id @default(autoincrement()) ++ title String ++ body String ++ createdAt DateTime @default(now()) ++} +``` + + diff --git a/api/prisma/migrations/20201009213512-create-posts/schema.prisma b/api/prisma/migrations/20201009213512-create-posts/schema.prisma new file mode 100644 index 0000000..624cbdb --- /dev/null +++ b/api/prisma/migrations/20201009213512-create-posts/schema.prisma @@ -0,0 +1,18 @@ +datasource DS { + // optionally set multiple providers + // example: provider = ["sqlite", "postgresql"] + provider = "sqlite" + url = "***" +} + +generator client { + provider = "prisma-client-js" + binaryTargets = "native" +} + +model Post { + id Int @id @default(autoincrement()) + title String + body String + createdAt DateTime @default(now()) +} diff --git a/api/prisma/migrations/20201009213512-create-posts/steps.json b/api/prisma/migrations/20201009213512-create-posts/steps.json new file mode 100644 index 0000000..f2edc88 --- /dev/null +++ b/api/prisma/migrations/20201009213512-create-posts/steps.json @@ -0,0 +1,120 @@ +{ + "version": "0.3.14-fixed", + "steps": [ + { + "tag": "CreateSource", + "source": "DS" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Source", + "source": "DS" + }, + "argument": "provider", + "value": "\"sqlite\"" + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Source", + "source": "DS" + }, + "argument": "url", + "value": "\"***\"" + }, + { + "tag": "CreateModel", + "model": "Post" + }, + { + "tag": "CreateField", + "model": "Post", + "field": "id", + "type": "Int", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Post", + "field": "id" + }, + "directive": "id" + } + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Post", + "field": "id" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Post", + "field": "id" + }, + "directive": "default" + }, + "argument": "", + "value": "autoincrement()" + }, + { + "tag": "CreateField", + "model": "Post", + "field": "title", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Post", + "field": "body", + "type": "String", + "arity": "Required" + }, + { + "tag": "CreateField", + "model": "Post", + "field": "createdAt", + "type": "DateTime", + "arity": "Required" + }, + { + "tag": "CreateDirective", + "location": { + "path": { + "tag": "Field", + "model": "Post", + "field": "createdAt" + }, + "directive": "default" + } + }, + { + "tag": "CreateArgument", + "location": { + "tag": "Directive", + "path": { + "tag": "Field", + "model": "Post", + "field": "createdAt" + }, + "directive": "default" + }, + "argument": "", + "value": "now()" + } + ] +} \ No newline at end of file diff --git a/api/prisma/migrations/migrate.lock b/api/prisma/migrations/migrate.lock new file mode 100644 index 0000000..a23e9cb --- /dev/null +++ b/api/prisma/migrations/migrate.lock @@ -0,0 +1,3 @@ +# Prisma Migrate lockfile v1 + +20201009213512-create-posts \ No newline at end of file diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index d2cf1e8..929fed4 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -10,11 +10,9 @@ generator client { binaryTargets = "native" } -// Define your own datamodels here and run `yarn redwood db save` to create -// migrations for them. -// TODO: Please remove the following example: -model UserExample { - id Int @id @default(autoincrement()) - email String @unique - name String? +model Post { + id Int @id @default(autoincrement()) + title String + body String + createdAt DateTime @default(now()) } diff --git a/api/src/graphql/posts.sdl.js b/api/src/graphql/posts.sdl.js new file mode 100644 index 0000000..ad384ff --- /dev/null +++ b/api/src/graphql/posts.sdl.js @@ -0,0 +1,29 @@ +export const schema = gql` + type Post { + id: Int! + title: String! + body: String! + createdAt: DateTime! + } + + type Query { + posts: [Post!]! + post(id: Int!): Post + } + + input CreatePostInput { + title: String! + body: String! + } + + input UpdatePostInput { + title: String + body: String + } + + type Mutation { + createPost(input: CreatePostInput!): Post! + updatePost(id: Int!, input: UpdatePostInput!): Post! + deletePost(id: Int!): Post! + } +` diff --git a/api/src/services/posts/posts.js b/api/src/services/posts/posts.js new file mode 100644 index 0000000..be53d32 --- /dev/null +++ b/api/src/services/posts/posts.js @@ -0,0 +1,30 @@ +import { db } from 'src/lib/db' + +export const posts = () => { + return db.post.findMany() +} + +export const post = ({ id }) => { + return db.post.findOne({ + where: { id }, + }) +} + +export const createPost = ({ input }) => { + return db.post.create({ + data: input, + }) +} + +export const updatePost = ({ id, input }) => { + return db.post.update({ + data: input, + where: { id }, + }) +} + +export const deletePost = ({ id }) => { + return db.post.delete({ + where: { id }, + }) +} diff --git a/api/src/services/posts/posts.test.js b/api/src/services/posts/posts.test.js new file mode 100644 index 0000000..f23483a --- /dev/null +++ b/api/src/services/posts/posts.test.js @@ -0,0 +1,9 @@ +/* +import { posts } from './posts' +*/ + +describe('posts', () => { + it('returns true', () => { + expect(true).toBe(true) + }) +}) diff --git a/web/src/Routes.js b/web/src/Routes.js index 2c8f02a..3a6bffb 100644 --- a/web/src/Routes.js +++ b/web/src/Routes.js @@ -12,6 +12,12 @@ import { Router, Route } from '@redwoodjs/router' const Routes = () => { return ( + + + + + + ) diff --git a/web/src/components/BlogPostsCell/BlogPostsCell.js b/web/src/components/BlogPostsCell/BlogPostsCell.js new file mode 100644 index 0000000..cfd42e4 --- /dev/null +++ b/web/src/components/BlogPostsCell/BlogPostsCell.js @@ -0,0 +1,28 @@ +export const QUERY = gql` + query BlogPostsQuery { + posts { + id + title + body + createdAt + } + } +` + +export const Loading = () =>
Loading...
+ +export const Empty = () =>
Empty
+ +export const Failure = ({ error }) =>
Error: {error.message}
+ +export const Success = ({ posts }) => { + return posts.map((post) => ( +
+
+

{post.title}

+
+

{post.body}

+
Posted on: {post.createdAt.split('T')[0]}
+
+ )) +} \ No newline at end of file diff --git a/web/src/components/BlogPostsCell/BlogPostsCell.mock.js b/web/src/components/BlogPostsCell/BlogPostsCell.mock.js new file mode 100644 index 0000000..70d4d6a --- /dev/null +++ b/web/src/components/BlogPostsCell/BlogPostsCell.mock.js @@ -0,0 +1,6 @@ +// Define your own mock data here: +export const standard = (/* vars, { ctx, req } */) => ({ + blogPosts: { + id: 42, + }, +}) diff --git a/web/src/components/BlogPostsCell/BlogPostsCell.stories.js b/web/src/components/BlogPostsCell/BlogPostsCell.stories.js new file mode 100644 index 0000000..841d3b2 --- /dev/null +++ b/web/src/components/BlogPostsCell/BlogPostsCell.stories.js @@ -0,0 +1,20 @@ +import { Loading, Empty, Failure, Success } from './BlogPostsCell' +import { standard } from './BlogPostsCell.mock' + +export const loading = () => { + return Loading ? : null +} + +export const empty = () => { + return Empty ? : null +} + +export const failure = () => { + return Failure ? : null +} + +export const success = () => { + return Success ? : null +} + +export default { title: 'Cells/BlogPostsCell' } diff --git a/web/src/components/BlogPostsCell/BlogPostsCell.test.js b/web/src/components/BlogPostsCell/BlogPostsCell.test.js new file mode 100644 index 0000000..0d1fc66 --- /dev/null +++ b/web/src/components/BlogPostsCell/BlogPostsCell.test.js @@ -0,0 +1,26 @@ +import { render, screen } from '@redwoodjs/testing' +import { Loading, Empty, Failure, Success } from './BlogPostsCell' +import { standard } from './BlogPostsCell.mock' + +describe('BlogPostsCell', () => { + test('Loading renders successfully', () => { + render() + // Use screen.debug() to see output + expect(screen.getByText('Loading...')).toBeInTheDocument() + }) + + test('Empty renders successfully', async () => { + render() + expect(screen.getByText('Empty')).toBeInTheDocument() + }) + + test('Failure renders successfully', async () => { + render() + expect(screen.getByText(/Oh no/i)).toBeInTheDocument() + }) + + test('Success renders successfully', async () => { + render() + expect(screen.getByText(/42/i)).toBeInTheDocument() + }) +}) diff --git a/web/src/components/EditPostCell/EditPostCell.js b/web/src/components/EditPostCell/EditPostCell.js new file mode 100644 index 0000000..904e44a --- /dev/null +++ b/web/src/components/EditPostCell/EditPostCell.js @@ -0,0 +1,48 @@ +import { useMutation, useFlash } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import PostForm from 'src/components/PostForm' + +export const QUERY = gql` + query FIND_POST_BY_ID($id: Int!) { + post: post(id: $id) { + id + title + body + createdAt + } + } +` +const UPDATE_POST_MUTATION = gql` + mutation UpdatePostMutation($id: Int!, $input: UpdatePostInput!) { + updatePost(id: $id, input: $input) { + id + } + } +` + +export const Loading = () =>
Loading...
+ +export const Success = ({ post }) => { + const { addMessage } = useFlash() + const [updatePost, { loading, error }] = useMutation(UPDATE_POST_MUTATION, { + onCompleted: () => { + navigate(routes.posts()) + addMessage('Post updated.', { classes: 'rw-flash-success' }) + }, + }) + + const onSave = (input, id) => { + updatePost({ variables: { id, input } }) + } + + return ( +
+
+

Edit Post {post.id}

+
+
+ +
+
+ ) +} diff --git a/web/src/components/NewPost/NewPost.js b/web/src/components/NewPost/NewPost.js new file mode 100644 index 0000000..ff4eec2 --- /dev/null +++ b/web/src/components/NewPost/NewPost.js @@ -0,0 +1,38 @@ +import { useMutation, useFlash } from '@redwoodjs/web' +import { navigate, routes } from '@redwoodjs/router' +import PostForm from 'src/components/PostForm' + +const CREATE_POST_MUTATION = gql` + mutation CreatePostMutation($input: CreatePostInput!) { + createPost(input: $input) { + id + } + } +` + +const NewPost = () => { + const { addMessage } = useFlash() + const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, { + onCompleted: () => { + navigate(routes.posts()) + addMessage('Post created.', { classes: 'rw-flash-success' }) + }, + }) + + const onSave = (input) => { + createPost({ variables: { input } }) + } + + return ( +
+
+

New Post

+
+
+ +
+
+ ) +} + +export default NewPost diff --git a/web/src/components/Post/Post.js b/web/src/components/Post/Post.js new file mode 100644 index 0000000..ff9c448 --- /dev/null +++ b/web/src/components/Post/Post.js @@ -0,0 +1,95 @@ +import { useMutation, useFlash } from '@redwoodjs/web' +import { Link, routes, navigate } from '@redwoodjs/router' + +const DELETE_POST_MUTATION = gql` + mutation DeletePostMutation($id: Int!) { + deletePost(id: $id) { + id + } + } +` + +const jsonDisplay = (obj) => { + return ( +
+      {JSON.stringify(obj, null, 2)}
+    
+ ) +} + +const timeTag = (datetime) => { + return ( + + ) +} + +const checkboxInputTag = (checked) => { + return +} + +const Post = ({ post }) => { + const { addMessage } = useFlash() + const [deletePost] = useMutation(DELETE_POST_MUTATION, { + onCompleted: () => { + navigate(routes.posts()) + addMessage('Post deleted.', { classes: 'rw-flash-success' }) + }, + }) + + const onDeleteClick = (id) => { + if (confirm('Are you sure you want to delete post ' + id + '?')) { + deletePost({ variables: { id } }) + } + } + + return ( + <> +
+
+

+ Post {post.id} Detail +

+
+ + + + + + + + + + + + + + + + + + + +
Id{post.id}
Title{post.title}
Body{post.body}
Created at{timeTag(post.createdAt)}
+
+ + + ) +} + +export default Post diff --git a/web/src/components/PostCell/PostCell.js b/web/src/components/PostCell/PostCell.js new file mode 100644 index 0000000..65a507b --- /dev/null +++ b/web/src/components/PostCell/PostCell.js @@ -0,0 +1,20 @@ +import Post from 'src/components/Post' + +export const QUERY = gql` + query FIND_POST_BY_ID($id: Int!) { + post: post(id: $id) { + id + title + body + createdAt + } + } +` + +export const Loading = () =>
Loading...
+ +export const Empty = () =>
Post not found
+ +export const Success = ({ post }) => { + return +} diff --git a/web/src/components/PostForm/PostForm.js b/web/src/components/PostForm/PostForm.js new file mode 100644 index 0000000..90f5ff4 --- /dev/null +++ b/web/src/components/PostForm/PostForm.js @@ -0,0 +1,67 @@ +import { + Form, + FormError, + FieldError, + Label, + TextField, + Submit, +} from '@redwoodjs/forms' + +const PostForm = (props) => { + const onSubmit = (data) => { + props.onSave(data, props?.post?.id) + } + + return ( +
+
+ + + + + + + + + + +
+ + Save + +
+ +
+ ) +} + +export default PostForm diff --git a/web/src/components/Posts/Posts.js b/web/src/components/Posts/Posts.js new file mode 100644 index 0000000..81f4b76 --- /dev/null +++ b/web/src/components/Posts/Posts.js @@ -0,0 +1,105 @@ +import { useMutation, useFlash } from '@redwoodjs/web' +import { Link, routes } from '@redwoodjs/router' + +const DELETE_POST_MUTATION = gql` + mutation DeletePostMutation($id: Int!) { + deletePost(id: $id) { + id + } + } +` + +const MAX_STRING_LENGTH = 150 + +const truncate = (text) => { + let output = text + if (text && text.length > MAX_STRING_LENGTH) { + output = output.substring(0, MAX_STRING_LENGTH) + '...' + } + return output +} + +const jsonTruncate = (obj) => { + return truncate(JSON.stringify(obj, null, 2)) +} + +const timeTag = (datetime) => { + return ( + + ) +} + +const checkboxInputTag = (checked) => { + return +} + +const PostsList = ({ posts }) => { + const { addMessage } = useFlash() + const [deletePost] = useMutation(DELETE_POST_MUTATION, { + onCompleted: () => { + addMessage('Post deleted.', { classes: 'rw-flash-success' }) + }, + }) + + const onDeleteClick = (id) => { + if (confirm('Are you sure you want to delete post ' + id + '?')) { + deletePost({ variables: { id }, refetchQueries: ['POSTS'] }) + } + } + + return ( +
+ + + + + + + + + + + + {posts.map((post) => ( + + + + + + + + ))} + +
IdTitleBodyCreated at 
{truncate(post.id)}{truncate(post.title)}{truncate(post.body)}{timeTag(post.createdAt)} + +
+
+ ) +} + +export default PostsList diff --git a/web/src/components/PostsCell/PostsCell.js b/web/src/components/PostsCell/PostsCell.js new file mode 100644 index 0000000..ddf49cd --- /dev/null +++ b/web/src/components/PostsCell/PostsCell.js @@ -0,0 +1,31 @@ +import { Link, routes } from '@redwoodjs/router' + +import Posts from 'src/components/Posts' + +export const QUERY = gql` + query POSTS { + posts { + id + title + body + createdAt + } + } +` + +export const Loading = () =>
Loading...
+ +export const Empty = () => { + return ( +
+ {'No posts yet. '} + + {'Create one?'} + +
+ ) +} + +export const Success = ({ posts }) => { + return +} diff --git a/web/src/index.js b/web/src/index.js index 63dd948..c7a2415 100644 --- a/web/src/index.js +++ b/web/src/index.js @@ -4,6 +4,7 @@ import FatalErrorPage from 'src/pages/FatalErrorPage' import Routes from 'src/Routes' +import './scaffold.css' import './index.css' ReactDOM.render( diff --git a/web/src/layouts/BlogLayout/BlogLayout.js b/web/src/layouts/BlogLayout/BlogLayout.js new file mode 100644 index 0000000..54134d9 --- /dev/null +++ b/web/src/layouts/BlogLayout/BlogLayout.js @@ -0,0 +1,24 @@ +import { Link, routes } from '@redwoodjs/router' + +const BlogLayout = ({ children }) => { + return ( + <> +
+

Redwood Blog

+ +
+
{children}
+ + ) +} + +export default BlogLayout diff --git a/web/src/layouts/BlogLayout/BlogLayout.stories.js b/web/src/layouts/BlogLayout/BlogLayout.stories.js new file mode 100644 index 0000000..4e249ec --- /dev/null +++ b/web/src/layouts/BlogLayout/BlogLayout.stories.js @@ -0,0 +1,7 @@ +import BlogLayout from './BlogLayout' + +export const generated = () => { + return +} + +export default { title: 'Layouts/BlogLayout' } diff --git a/web/src/layouts/BlogLayout/BlogLayout.test.js b/web/src/layouts/BlogLayout/BlogLayout.test.js new file mode 100644 index 0000000..b87e730 --- /dev/null +++ b/web/src/layouts/BlogLayout/BlogLayout.test.js @@ -0,0 +1,11 @@ +import { render } from '@redwoodjs/testing' + +import BlogLayout from './BlogLayout' + +describe('BlogLayout', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/layouts/PostsLayout/PostsLayout.js b/web/src/layouts/PostsLayout/PostsLayout.js new file mode 100644 index 0000000..b08e8fa --- /dev/null +++ b/web/src/layouts/PostsLayout/PostsLayout.js @@ -0,0 +1,23 @@ +import { Link, routes } from '@redwoodjs/router' +import { Flash } from '@redwoodjs/web' + +const PostsLayout = (props) => { + return ( +
+ +
+

+ + Posts + +

+ +
+
New Post + +
+
{props.children}
+
+ ) +} + +export default PostsLayout diff --git a/web/src/pages/AboutPage/AboutPage.js b/web/src/pages/AboutPage/AboutPage.js new file mode 100644 index 0000000..71a730c --- /dev/null +++ b/web/src/pages/AboutPage/AboutPage.js @@ -0,0 +1,19 @@ +import { Link, routes } from '@redwoodjs/router' +import BlogLayout from 'src/layouts/BlogLayout' + +const AboutPage = () => { + return ( + +

AboutPage

+

+ Find me in ./web/src/pages/AboutPage/AboutPage.js +

+

+ My default route is named about, link to me with ` + About` +

+
+ ) +} + +export default AboutPage diff --git a/web/src/pages/AboutPage/AboutPage.stories.js b/web/src/pages/AboutPage/AboutPage.stories.js new file mode 100644 index 0000000..3ca853b --- /dev/null +++ b/web/src/pages/AboutPage/AboutPage.stories.js @@ -0,0 +1,7 @@ +import AboutPage from './AboutPage' + +export const generated = () => { + return +} + +export default { title: 'Pages/AboutPage' } diff --git a/web/src/pages/AboutPage/AboutPage.test.js b/web/src/pages/AboutPage/AboutPage.test.js new file mode 100644 index 0000000..6878394 --- /dev/null +++ b/web/src/pages/AboutPage/AboutPage.test.js @@ -0,0 +1,11 @@ +import { render } from '@redwoodjs/testing' + +import AboutPage from './AboutPage' + +describe('AboutPage', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/pages/EditPostPage/EditPostPage.js b/web/src/pages/EditPostPage/EditPostPage.js new file mode 100644 index 0000000..419af6b --- /dev/null +++ b/web/src/pages/EditPostPage/EditPostPage.js @@ -0,0 +1,12 @@ +import PostsLayout from 'src/layouts/PostsLayout' +import EditPostCell from 'src/components/EditPostCell' + +const EditPostPage = ({ id }) => { + return ( + + + + ) +} + +export default EditPostPage diff --git a/web/src/pages/HomePage/HomePage.js b/web/src/pages/HomePage/HomePage.js new file mode 100644 index 0000000..7d32e12 --- /dev/null +++ b/web/src/pages/HomePage/HomePage.js @@ -0,0 +1,13 @@ +import BlogLayout from 'src/layouts/BlogLayout' +import BlogPostsCell from 'src/components/BlogPostsCell' + +const HomePage = () => { + return ( + + + + + ) +} + +export default HomePage \ No newline at end of file diff --git a/web/src/pages/HomePage/HomePage.stories.js b/web/src/pages/HomePage/HomePage.stories.js new file mode 100644 index 0000000..7a14a5c --- /dev/null +++ b/web/src/pages/HomePage/HomePage.stories.js @@ -0,0 +1,7 @@ +import HomePage from './HomePage' + +export const generated = () => { + return +} + +export default { title: 'Pages/HomePage' } diff --git a/web/src/pages/HomePage/HomePage.test.js b/web/src/pages/HomePage/HomePage.test.js new file mode 100644 index 0000000..4880eea --- /dev/null +++ b/web/src/pages/HomePage/HomePage.test.js @@ -0,0 +1,11 @@ +import { render } from '@redwoodjs/testing' + +import HomePage from './HomePage' + +describe('HomePage', () => { + it('renders successfully', () => { + expect(() => { + render() + }).not.toThrow() + }) +}) diff --git a/web/src/pages/NewPostPage/NewPostPage.js b/web/src/pages/NewPostPage/NewPostPage.js new file mode 100644 index 0000000..7a41350 --- /dev/null +++ b/web/src/pages/NewPostPage/NewPostPage.js @@ -0,0 +1,12 @@ +import PostsLayout from 'src/layouts/PostsLayout' +import NewPost from 'src/components/NewPost' + +const NewPostPage = () => { + return ( + + + + ) +} + +export default NewPostPage diff --git a/web/src/pages/PostPage/PostPage.js b/web/src/pages/PostPage/PostPage.js new file mode 100644 index 0000000..8b9eec3 --- /dev/null +++ b/web/src/pages/PostPage/PostPage.js @@ -0,0 +1,12 @@ +import PostsLayout from 'src/layouts/PostsLayout' +import PostCell from 'src/components/PostCell' + +const PostPage = ({ id }) => { + return ( + + + + ) +} + +export default PostPage diff --git a/web/src/pages/PostsPage/PostsPage.js b/web/src/pages/PostsPage/PostsPage.js new file mode 100644 index 0000000..7318d9b --- /dev/null +++ b/web/src/pages/PostsPage/PostsPage.js @@ -0,0 +1,12 @@ +import PostsLayout from 'src/layouts/PostsLayout' +import PostsCell from 'src/components/PostsCell' + +const PostsPage = () => { + return ( + + + + ) +} + +export default PostsPage diff --git a/web/src/scaffold.css b/web/src/scaffold.css new file mode 100644 index 0000000..f2cef6b --- /dev/null +++ b/web/src/scaffold.css @@ -0,0 +1,346 @@ +/* + normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css +*/ + +.rw-scaffold *, +.rw-scaffold ::after, +.rw-scaffold ::before { + box-sizing: inherit; + border-width: 0; + border-style: solid; + border-color: #e2e8f0; +} +.rw-scaffold main { + color: #4a5568; + display: block; +} +.rw-scaffold h1, +.rw-scaffold h2 { + margin: 0; +} +.rw-scaffold a { + background-color: transparent; +} +.rw-scaffold ul { + margin: 0; + padding: 0; +} +.rw-scaffold input { + font-family: inherit; + font-size: 100%; + overflow: visible; +} +.rw-scaffold input:-ms-input-placeholder { + color: #a0aec0; +} +.rw-scaffold input::-ms-input-placeholder { + color: #a0aec0; +} +.rw-scaffold input::placeholder { + color: #a0aec0; +} +.rw-scaffold table { + border-collapse: collapse; +} + +/* + Style +*/ + +.rw-scaffold { + background-color: #fff; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; +} +.rw-header { + display: flex; + justify-content: space-between; + padding: 1rem 2rem 1rem 2rem; +} +.rw-main { + margin-left: 1rem; + margin-right: 1rem; + padding-bottom: 1rem; +} +.rw-segment { + background-color: #fff; + border-width: 1px; + border-radius: 0.5rem; + overflow: hidden; +} +.rw-segment-header { + background-color: #e2e8f0; + color: #4a5568; + padding: 0.75rem 1rem; +} +.rw-segment-main { + background-color: #f7fafc; + padding: 1rem; +} +.rw-link { + color: #4299e1; + text-decoration: underline; +} +.rw-link:hover { + color: #2b6cb0; +} +.rw-heading { + font-weight: 600; +} +.rw-heading.rw-heading-primary { + font-size: 1.25rem; +} +.rw-heading.rw-heading-secondary { + font-size: 0.875rem; +} +.rw-heading .rw-link { + color: #4a5568; + text-decoration: none; +} +.rw-heading .rw-link:hover { + color: #1a202c; + text-decoration: underline; +} +.rw-flash-message { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin: 0 1rem; + padding: 1rem; + background: #e2e8f0; + border-radius: 0.25rem; +} +.rw-flash-message .rw-flash-message-dismiss { + font-size: 1.25rem; + font-weight: 600; + line-height: 1; + margin-right: 0.25rem; + transform-origin: center; + transform: rotate(45deg); + cursor: pointer; +} +.rw-flash-message .rw-flash-message-dismiss:hover { + opacity: 0.7; +} +.rw-flash-message.rw-flash-success { + background: #48bb78; + color: #fff; +} +.rw-flash-message.rw-flash-error { + background: #e53e3e; + color: #fff; +} +.rw-form-wrapper { + box-sizing: border-box; + font-size: 0.875rem; + margin-top: -1rem; +} +.rw-form-error-wrapper { + padding: 1rem; + background-color: #fff5f5; + color: #c53030; + border-width: 1px; + border-color: #feb2b2; + border-radius: 0.25rem; + margin: 1rem 0; +} +.rw-form-error-title { + margin-top: 0; + margin-bottom: 0; + font-weight: 600; +} +.rw-form-error-list { + margin-top: 0.5rem; + list-style-type: disc; + list-style-position: inside; +} +.rw-button { + color: #718096; + cursor: pointer; + display: flex; + justify-content: center; + font-size: 0.75rem; + font-weight: 600; + padding: 0.25rem 1rem; + text-transform: uppercase; + text-decoration: none; + letter-spacing: 0.025em; + border-radius: 0.25rem; + line-height: 2; +} +.rw-button:hover { + background-color: #718096; + color: #fff; +} +.rw-button.rw-button-small { + font-size: 0.75rem; + border-radius: 0.125rem; + padding: 0.25rem 0.5rem; + line-height: inherit; +} +.rw-button.rw-button-green { + background-color: #48bb78; + color: #fff; +} +.rw-button.rw-button-green:hover { + background-color: #38a169; + color: #fff; +} +.rw-button.rw-button-blue { + background-color: #3182ce; + color: #fff; +} +.rw-button.rw-button-blue:hover { + background-color: #2b6cb0; +} +.rw-button.rw-button-red { + background-color: #e53e3e; + color: #fff; +} +.rw-button.rw-button-red:hover { + background-color: #c53030; +} +.rw-button-icon { + font-size: 1.25rem; + line-height: 1; + margin-right: 0.25rem; +} +.rw-button-group { + display: flex; + justify-content: center; + margin: 0.75rem 0.5rem; +} +.rw-button-group .rw-button { + margin: 0 0.25rem; +} +.rw-form-wrapper .rw-button-group { + margin-top: 2rem; + margin-bottom: 0; +} +.rw-label { + display: block; + margin-top: 1.5rem; + color: #4a5568; + font-weight: 600; +} +.rw-label.rw-label-error { + color: #c53030; +} +.rw-input { + display: block; + margin-top: 0.5rem; + width: 100%; + padding: 0.5rem; + border-width: 1px; + border-color: #e2e8f0; + color: #4a5568; + border-radius: 0.25rem; + outline: none; +} +.rw-input[type='checkbox'] { + width: initial; + margin-left: 0; +} +.rw-input:focus { + border-color: #a0aec0; +} +.rw-input-error { + border-color: #c53030; + color: #c53030; +} +.rw-field-error { + display: block; + margin-top: 0.25rem; + font-weight: 600; + text-transform: uppercase; + font-size: 0.75rem; + color: #c53030; +} +.rw-table-wrapper-responsive { + overflow-x: scroll; +} +.rw-table-wrapper-responsive .rw-table { + min-width: 48rem; +} +.rw-table { + table-layout: auto; + width: 100%; + font-size: 0.875rem; +} +.rw-table th, +.rw-table td { + padding: 0.75rem; +} +.rw-table thead tr { + background-color: #e2e8f0; + color: #4a5568; +} +.rw-table th { + font-weight: 600; + text-align: left; +} +.rw-table thead th { + text-align: left; +} +.rw-table tbody th { + text-align: right; +} +@media (min-width: 768px) { + .rw-table tbody th { + width: 20%; + } +} +.rw-table tbody tr { + background-color: #f7fafc; + border-top-width: 1px; +} +.rw-table tbody tr:nth-child(even) { + background-color: #fff; +} +.rw-table input { + margin-left: 0; +} +.rw-table-actions { + display: flex; + justify-content: flex-end; + align-items: center; + height: 17px; + padding-right: 0.25rem; +} +.rw-table-actions .rw-button { + background-color: transparent; +} +.rw-table-actions .rw-button:hover { + background-color: #718096; + color: #fff; +} +.rw-table-actions .rw-button-blue { + color: #3182ce; +} +.rw-table-actions .rw-button-blue:hover { + background-color: #3182ce; + color: #fff; +} +.rw-table-actions .rw-button-red { + color: #e53e3e; +} +.rw-table-actions .rw-button-red:hover { + background-color: #e53e3e; + color: #fff; +} +.rw-text-center { + text-align: center; +} +.rw-slide-up { + animation: slideUp 0.5s 1 ease; + animation-fill-mode: forwards; + overflow-y: hidden; +} +@keyframes slideUp { + 100% { + max-height: 0; + padding-top: 0; + padding-bottom: 0; + } +}