This commit is contained in:
Jordi Enric
2024-02-10 14:14:07 +01:00
parent e9982b2202
commit c03db8c7e1
18 changed files with 242 additions and 79 deletions
+3 -3
View File
@@ -31,7 +31,7 @@ const layout = async ({
<Link
data-state={params.slug === post.slug ? "active" : "inactive"}
className={
"group flex items-center gap-1 rounded-md p-1.5 text-slate-600 transition-all hover:text-orange-600 data-[state=active]:bg-orange-50 data-[state=active]:text-orange-600"
"group flex items-center gap-1 rounded-md p-1.5 text-zinc-600 transition-all hover:text-orange-600 data-[state=active]:bg-orange-50 data-[state=active]:text-orange-600"
}
key={post.slug}
href={"/docs/" + post.slug}
@@ -44,7 +44,7 @@ const layout = async ({
</Link>
))}
</ul>
<div className="mt-auto justify-self-end p-4 text-center text-xs text-slate-500">
<div className="mt-auto justify-self-end p-4 text-center text-xs text-zinc-500">
<p>Docs site built with zenblog</p>
</div>
</aside>
@@ -68,7 +68,7 @@ const layout = async ({
</Button>
</div>
<div className="p-2">
{/* <h2 className="text-sm font-medium text-slate-500">On this page</h2> */}
{/* <h2 className="text-sm font-medium text-zinc-500">On this page</h2> */}
<ul className="mt-2">
{/* <li># title 1</li>
<li># title 2</li>
@@ -106,6 +106,11 @@ const EditorSettings = (props: Props) => {
Add metadata
</Button>
</div>
<div>
<h2>Categories</h2>
</div>
<div className="mt-4 flex justify-end">
<Button>Save</Button>
</div>
@@ -260,7 +260,11 @@ export const ZendoEditor = (props: Props) => {
</Button>
</SheetTrigger>
<SheetContent>
<EditorSettings onSave={() => {}}></EditorSettings>
<EditorSettings
onSave={() => {
// TO DO - save settings
}}
></EditorSettings>
</SheetContent>
</Sheet>
<Button type="submit">
@@ -333,9 +337,6 @@ export const ZendoEditor = (props: Props) => {
>
<EditorContent className="" editor={editor} />
</div>
<Debugger>
<pre>{JSON.stringify(editor?.getJSON(), null, 2)}</pre>
</Debugger>
</div>
</div>
</>
+24
View File
@@ -0,0 +1,24 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"border-input bg-background ring-offset-background placeholder:text-muted-foreground flex min-h-[80px] w-full rounded-md border px-3 py-2 text-sm focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
);
}
);
Textarea.displayName = "Textarea";
export { Textarea };
+74 -4
View File
@@ -2,13 +2,24 @@ import UserButton from "@/components/UserButton";
import ZendoLogo from "@/components/ZendoLogo";
import { Github, Twitter } from "lucide-react";
import Link from "next/link";
import { motion } from "framer-motion";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { Button } from "@/components/ui/button";
import { useSupabaseClient } from "@supabase/auth-helpers-react";
import { toast } from "sonner";
type Props = {
children?: React.ReactNode;
loading?: boolean;
};
export default function AppLayout({ children, loading }: Props) {
// const { isSignedIn } = useAuth();
const sb = useSupabaseClient();
return (
<div
@@ -35,9 +46,61 @@ export default function AppLayout({ children, loading }: Props) {
Blogs
</Link>
</div>
<div className="flex gap-4">
<div className="flex gap-1">
<DropdownMenu>
<DropdownMenuTrigger className="rounded-full px-3 py-1 text-sm font-medium text-slate-600 hover:bg-orange-50 hover:text-orange-600">
Feedback
</DropdownMenuTrigger>
<DropdownMenuContent>
<form
onSubmit={async (e) => {
e.preventDefault();
try {
const form = e.target as HTMLFormElement;
const formData = new FormData(
e.target as HTMLFormElement
);
const feedback = formData.get("feedback") as string;
const user = await sb.auth.getUser();
if (user.error) {
throw user.error;
}
const { data, error } = await sb
.from("feedback")
.insert({ feedback, user_email: user.data.user.email });
if (error) {
throw error;
}
toast.success("Thanks for the feedback!");
form.reset();
} catch (error) {
toast.error(
"Failed to submit feedback. Check the console for more details."
);
console.error("Failed to submit feedback", error);
}
}}
className="grid gap-2"
>
<Label htmlFor="feedback">
<div className="sr-only">Feedback</div>
<Textarea
placeholder="Your feedback"
name="feedback"
id="feedback"
/>
</Label>
<Button type="submit">Submit</Button>
</form>
</DropdownMenuContent>
</DropdownMenu>
<Link
className="rounded-full px-3 py-1 text-sm font-medium text-slate-600 hover:bg-orange-50 hover:text-orange-600"
className="mr-2 rounded-full px-3 py-1 text-sm font-medium text-slate-600 hover:bg-orange-50 hover:text-orange-600"
href="/docs/getting-started"
>
Docs
@@ -46,7 +109,14 @@ export default function AppLayout({ children, loading }: Props) {
</div>
</div>
</nav>
<div className="min-h-screen bg-slate-50 pb-24 shadow-md">{children}</div>
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
className="min-h-screen bg-slate-50 pb-24 shadow-md"
>
{children}
</motion.div>
<footer className="flex justify-between border-t bg-slate-100 p-8 font-mono text-slate-700">
<ul>
<li>thanks for checking out zenblog</li>
+1 -19
View File
@@ -49,25 +49,7 @@ function MyApp({ Component, pageProps }: AppProps) {
<QueryClientProvider client={queryClient}>
<Hydrate state={pageProps.dehydratedState}>
<>
<motion.div
key={pathname}
initial="pageInitial"
animate="pageAnimate"
exit={{ opacity: 0 }}
variants={{
pageInitial: {
opacity: 0,
y: -10,
},
pageAnimate: {
opacity: 1,
y: 0,
},
}}
>
<Component key={pathname} {...pageProps} />
</motion.div>
<Component key={pathname} {...pageProps} />
<Toaster />
</>
</Hydrate>
+1 -1
View File
@@ -179,7 +179,7 @@ const Home = () => {
Star us on GitHub
</Link>
</div>
<h1 className="mt-2 bg-gradient-to-b from-zinc-800 via-zinc-500 to-zinc-800 bg-clip-text text-3xl tracking-tight text-transparent md:text-5xl">
<h1 className="mt-2 bg-gradient-to-b from-zinc-800 via-zinc-500 to-zinc-800 bg-clip-text text-3xl font-medium tracking-tight text-transparent md:text-5xl">
Add a blog to your <br /> website{" "}
<span className="bg-gradient-to-br from-orange-400 to-orange-600 bg-clip-text text-transparent">
in 2 minutes
+6 -2
View File
@@ -16,8 +16,12 @@ export type CreateClientOpts = {
};
export declare function createClient({ blogId, _url, debug }: CreateClientOpts): {
posts: {
getAll: () => Promise<Post[]>;
getBySlug: (slug: string) => Promise<PostWithContent>;
getAll: (opts?: {
cache?: RequestInit["cache"];
} | undefined) => Promise<Post[]>;
getBySlug: (slug: string, opts?: {
cache?: RequestInit["cache"];
} | undefined) => Promise<PostWithContent>;
};
};
//# sourceMappingURL=index.d.ts.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,gBAAgB;;sBA8BrC,QAAQ,IAAI,EAAE,CAAC;0BAqBT,MAAM,KAAG,QAAQ,eAAe,CAAC;;EAcvE"}
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,gBAAgB;;;oBA6B1D,WAAW,CAAC,OAAO,CAAC;0BAKe,QAAQ,IAAI,EAAE,CAAC;0BAuBhD,MAAM;oBA5BR,WAAW,CAAC,OAAO,CAAC;0BA8BvB,QAAQ,eAAe,CAAC;;EAehC"}
+4 -2
View File
@@ -50,12 +50,13 @@ function createClient({ blogId, _url, debug }) {
}
return {
posts: {
getAll: async function () {
getAll: async function (opts) {
const posts = await _fetch(`posts`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: opts?.cache || "default",
});
log("posts.getAll", posts);
const normalizedPosts = posts.map((post) => {
@@ -69,12 +70,13 @@ function createClient({ blogId, _url, debug }) {
});
return normalizedPosts; // to do: validate
},
getBySlug: async function (slug) {
getBySlug: async function (slug, opts) {
const post = await _fetch(`post/${slug}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: opts?.cache || "default",
});
log("posts.getBySlug", post);
return post; // to do: validate
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
+11 -2
View File
@@ -73,14 +73,19 @@ export function createClient({ blogId, _url, debug }: CreateClientOpts) {
throwError("blogId is required");
}
type ReqOpts = {
cache?: RequestInit["cache"];
};
return {
posts: {
getAll: async function (): Promise<Post[]> {
getAll: async function (opts?: ReqOpts): Promise<Post[]> {
const posts = await _fetch(`posts`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: opts?.cache || "default",
});
log("posts.getAll", posts);
@@ -96,12 +101,16 @@ export function createClient({ blogId, _url, debug }: CreateClientOpts) {
return normalizedPosts as Post[]; // to do: validate
},
getBySlug: async function (slug: string): Promise<PostWithContent> {
getBySlug: async function (
slug: string,
opts?: ReqOpts
): Promise<PostWithContent> {
const post = await _fetch(`post/${slug}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
cache: opts?.cache || "default",
});
log("posts.getBySlug", post);
+1
View File
@@ -1,3 +1,4 @@
# Supabase
.branches
.temp
.env
+82 -15
View File
@@ -1,8 +1,9 @@
# A string used to distinguish different Supabase projects on the same host. Defaults to the working
# directory name when running `supabase init`.
project_id = "zendo"
# A string used to distinguish different Supabase projects on the same host. Defaults to the
# working directory name when running `supabase init`.
project_id = "zenblog"
[api]
enabled = true
# Port to use for the API URL.
port = 54321
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
@@ -17,35 +18,65 @@ max_rows = 1000
[db]
# Port to use for the local database URL.
port = 54322
# Port used by db diff command to initialize the shadow database.
shadow_port = 54320
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
# server_version;` on the remote database to check.
major_version = 15
[db.pooler]
enabled = false
# Port to use for the local connection pooler.
port = 54329
# Specifies when a server connection can be reused by other clients.
# Configure one of the supported pooler modes: `transaction`, `session`.
pool_mode = "transaction"
# How many server connections to allow per user/database pair.
default_pool_size = 20
# Maximum number of client connections allowed.
max_client_conn = 100
[realtime]
enabled = true
# Bind realtime via either IPv4 or IPv6. (default: IPv6)
# ip_version = "IPv6"
[studio]
enabled = true
# Port to use for Supabase Studio.
port = 54323
# External URL of the API server that frontend connects to.
api_url = "http://127.0.0.1"
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
# are monitored, and you can view the emails that would have been sent from the web interface.
[inbucket]
enabled = true
# Port to use for the email testing server web interface.
port = 54324
smtp_port = 54325
pop3_port = 54326
# Uncomment to expose additional ports for testing user applications that send emails.
# smtp_port = 54325
# pop3_port = 54326
[storage]
enabled = true
# The maximum file size allowed (e.g. "5MB", "500KB").
file_size_limit = "50MiB"
[auth]
enabled = true
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
site_url = "http://localhost:3000"
site_url = "http://127.0.0.1:3000"
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = ["https://localhost:3000"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
# week).
additional_redirect_urls = ["https://127.0.0.1:3000"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week).
jwt_expiry = 3600
# If disabled, the refresh token will never expire.
enable_refresh_token_rotation = true
# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds.
# Requires enable_refresh_token_rotation = true.
refresh_token_reuse_interval = 10
# Allow/disallow new user signups to your project.
enable_signup = true
@@ -58,13 +89,39 @@ double_confirm_changes = true
# If enabled, users need to confirm their email address before signing in.
enable_confirmations = false
# Uncomment to customize email template
# [auth.email.template.invite]
# subject = "You have been invited"
# content_path = "./supabase/templates/invite.html"
[auth.sms]
# Allow/disallow new user signups via SMS to your project.
enable_signup = true
# If enabled, users need to confirm their phone number before signing in.
enable_confirmations = false
# Template for sending OTP to users
template = "Your code is {{ .Code }} ."
# Use pre-defined map of phone number to OTP for testing.
[auth.sms.test_otp]
# 4152127777 = "123456"
# Configure one of the supported SMS providers: `twilio`, `twilio_verify`, `messagebird`, `textlocal`, `vonage`.
[auth.sms.twilio]
enabled = false
account_sid = ""
message_service_sid = ""
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`,
# `twitter`, `slack`, `spotify`, `workos`, `zoom`.
[auth.external.apple]
enabled = false
client_id = ""
secret = ""
# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead:
secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)"
# Overrides the default auth redirectUrl.
redirect_uri = ""
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
@@ -75,8 +132,18 @@ url = ""
enabled = false
port = 54327
vector_port = 54328
# Setup BigQuery project to enable log viewer on local development stack.
# See: https://supabase.com/docs/guides/getting-started/local-development#enabling-local-logging
gcp_project_id = ""
gcp_project_number = ""
gcp_jwt_path = "supabase/gcloud.json"
# Configure one of the supported backends: `postgres`, `bigquery`.
backend = "postgres"
# Experimental features may be deprecated any time
[experimental]
# Configures Postgres storage engine to use OrioleDB (S3)
orioledb_version = ""
# Configures S3 bucket URL, eg. <bucket>.s3-accelerate.amazonaws.com
s3_host = "env(S3_HOST)"
# Configures S3 bucket region, eg. us-east-1
s3_region = "env(S3_REGION)"
# Configures AWS_ACCESS_KEY_ID for S3 bucket
s3_access_key = "env(S3_ACCESS_KEY)"
# Configures AWS_SECRET_ACCESS_KEY for S3 bucket
s3_secret_key = "env(S3_SECRET_KEY)"
-3
View File
@@ -1,3 +0,0 @@
{
"recommendations": ["denoland.vscode-deno"]
}
-5
View File
@@ -1,5 +0,0 @@
{
"deno.enable": true,
"deno.lint": true,
"editor.defaultFormatter": "denoland.vscode-deno"
}
@@ -1,3 +1,4 @@
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
@@ -9,6 +10,8 @@ SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;
CREATE EXTENSION IF NOT EXISTS "timescaledb" WITH SCHEMA "extensions";
CREATE EXTENSION IF NOT EXISTS "pgsodium" WITH SCHEMA "pgsodium";
CREATE EXTENSION IF NOT EXISTS "pg_graphql" WITH SCHEMA "graphql";
@@ -23,7 +26,7 @@ CREATE EXTENSION IF NOT EXISTS "supabase_vault" WITH SCHEMA "vault";
CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA "extensions";
CREATE FUNCTION "public"."generate_slug"("title" "text") RETURNS "text"
CREATE OR REPLACE FUNCTION "public"."generate_slug"("title" "text") RETURNS "text"
LANGUAGE "plpgsql"
AS $$
DECLARE
@@ -47,7 +50,7 @@ $$;
ALTER FUNCTION "public"."generate_slug"("title" "text") OWNER TO "postgres";
CREATE FUNCTION "public"."requesting_user_id"() RETURNS "text"
CREATE OR REPLACE FUNCTION "public"."requesting_user_id"() RETURNS "text"
LANGUAGE "sql" STABLE
AS $$
select nullif(current_setting('request.jwt.claims', true)::json->>'sub', '')::text;
@@ -59,7 +62,7 @@ SET default_tablespace = '';
SET default_table_access_method = "heap";
CREATE TABLE "public"."blogs" (
CREATE TABLE IF NOT EXISTS "public"."blogs" (
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"title" "text" NOT NULL,
@@ -71,7 +74,7 @@ CREATE TABLE "public"."blogs" (
ALTER TABLE "public"."blogs" OWNER TO "postgres";
CREATE TABLE "public"."homepage_signup" (
CREATE TABLE IF NOT EXISTS "public"."homepage_signup" (
"id" bigint NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"(),
"name" "text" NOT NULL,
@@ -89,7 +92,7 @@ ALTER TABLE "public"."homepage_signup" ALTER COLUMN "id" ADD GENERATED BY DEFAUL
CACHE 1
);
CREATE TABLE "public"."invitations" (
CREATE TABLE IF NOT EXISTS "public"."invitations" (
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"blog_id" "uuid" NOT NULL,
"email" "text" NOT NULL,
@@ -99,7 +102,7 @@ CREATE TABLE "public"."invitations" (
ALTER TABLE "public"."invitations" OWNER TO "postgres";
CREATE TABLE "public"."members" (
CREATE TABLE IF NOT EXISTS "public"."members" (
"id" bigint NOT NULL,
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"user_id" "text" NOT NULL,
@@ -117,7 +120,7 @@ ALTER TABLE "public"."members" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDE
CACHE 1
);
CREATE TABLE "public"."posts" (
CREATE TABLE IF NOT EXISTS "public"."posts" (
"created_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"user_id" "text" DEFAULT "public"."requesting_user_id"() NOT NULL,
"blog_id" "uuid" NOT NULL,
@@ -127,7 +130,8 @@ CREATE TABLE "public"."posts" (
"updated_at" timestamp with time zone DEFAULT "now"() NOT NULL,
"slug" "text" NOT NULL,
"id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL,
"cover_image" "text"
"cover_image" "text",
"metadata" "jsonb"[]
);
ALTER TABLE "public"."posts" OWNER TO "postgres";
@@ -150,6 +154,10 @@ ALTER TABLE ONLY "public"."posts"
ALTER TABLE ONLY "public"."posts"
ADD CONSTRAINT "unique_slug_per_user_post_constraint" UNIQUE ("slug", "user_id", "blog_id");
CREATE INDEX "posts_slug_blog_id_idx" ON "public"."posts" USING "btree" ("slug", "blog_id");
CREATE INDEX "posts_slug_idx" ON "public"."posts" USING "btree" ("slug");
ALTER TABLE ONLY "public"."invitations"
ADD CONSTRAINT "invitations_blog_id_fkey" FOREIGN KEY ("blog_id") REFERENCES "public"."blogs"("id");
+7 -9
View File
@@ -1,6 +1,6 @@
# To Do
## Features
## Ideas
- [] Add PolyScale cache to Supabase for faster queries?
- [] Categories
@@ -18,15 +18,15 @@
- [x] View all blogs
- Make description optional
- Add emoji picker to Create Blog
- Redirect to blog after creating it
- [x] Redirect to blog after creating it
## Posts
- [] See "published at" and "updated at" dates in post list
- [x] View all posts
- [x] Create post
- [] View post
- [] Edit post
- [x] View post
- [x] Edit post
- [] Delete post
## SEO
@@ -57,12 +57,10 @@
- [] As a member, edit a post in a blog
- [] As a member, delete a post in a blog
## React Components
## Docs
- [] Create ZendoProvider component
- [] Create PostList component
- [] Create Post component
- [] Create ContentRenderer component
- [] Add documentation for API client
- [] Copypastable Zenblog Renderer component
## User Feedback