This commit is contained in:
Jordi Enric
2024-02-28 21:01:01 +01:00
parent 7f04937947
commit 4e3aa109aa
13 changed files with 164 additions and 73 deletions
+6
View File
@@ -1,3 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
* {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
@@ -1,5 +1,10 @@
import { getSupabaseBrowserClient } from "@/lib/supabase";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
UseQueryOptions,
useMutation,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
type UseTags = {
blogId: string;
@@ -67,9 +67,9 @@ const EditorSettings = (props: Props) => {
<div>Value</div>
<Dialog>
<DialogTrigger asChild>
<Button className="ml-auto" variant={"outline"} size={"sm"}>
<Button className="ml-auto" variant={"ghost"}>
<Code size={16} />
Preview
<div>Preview</div>
</Button>
</DialogTrigger>
<DialogContent className="max-w-md">
@@ -38,9 +38,13 @@ const TagSelect = (props: Props) => {
<h2 className="">Tags</h2>
<Dialog>
<DialogTrigger asChild>
<Button size="icon" variant={"ghost"} title="Create tag">
<div className="sr-only">Create tag</div>
<Button
className="text-slate-500"
variant={"ghost"}
title="Create tag"
>
<Plus />
<div>Create tag</div>
</Button>
</DialogTrigger>
<DialogContent className="max-w-xs">
@@ -85,7 +89,6 @@ const TagSelect = (props: Props) => {
</DialogContent>
</Dialog>
</div>
<pre>{JSON.stringify(selectedTags)}</pre>
{tags.isLoading ? (
<></>
) : (
+8 -8
View File
@@ -15,14 +15,14 @@ const Footer = (props: Props) => {
// label: "Blog",
// href: "/blog",
// },
{
label: "Docs",
href: "/docs/getting-started",
},
{
label: "Pricing",
href: "/pricing",
},
// {
// label: "Docs",
// href: "/docs/getting-started",
// },
// {
// label: "Pricing",
// href: "/pricing",
// },
];
return (
@@ -1,7 +1,10 @@
import { createAPIClient } from "@/lib/http/api";
import { useQuery } from "@tanstack/react-query";
import { UseQueryOptions, useQuery } from "@tanstack/react-query";
const api = createAPIClient();
export const useImages = (blogId: string) =>
useQuery(["images"], () => api.images.getAll(blogId));
useQuery({
queryKey: ["images", blogId],
queryFn: () => api.images.getAll(blogId),
});
+3 -3
View File
@@ -12,7 +12,7 @@ const TabsList = React.forwardRef<
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-10 items-center justify-center rounded-full bg-slate-100 p-1 text-slate-500 dark:bg-slate-800 dark:text-slate-400",
"inline-flex h-10 items-center justify-center rounded-full border bg-zinc-100 p-1 text-zinc-500 dark:bg-zinc-800 dark:text-zinc-400",
className
)}
{...props}
@@ -27,7 +27,7 @@ const TabsTrigger = React.forwardRef<
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-3 py-1.5 text-sm font-medium ring-offset-white transition-all hover:text-slate-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-slate-950 data-[state=active]:shadow-sm dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300 dark:data-[state=active]:bg-slate-950 dark:data-[state=active]:text-slate-50",
"inline-flex items-center justify-center whitespace-nowrap rounded-full px-3 py-1.5 text-sm font-medium ring-offset-white transition-all last:rounded-r-full hover:text-zinc-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-zinc-950 data-[state=active]:shadow-sm dark:ring-offset-zinc-950 dark:focus-visible:ring-zinc-300 dark:data-[state=active]:bg-zinc-950 dark:data-[state=active]:text-zinc-50",
className
)}
{...props}
@@ -42,7 +42,7 @@ const TabsContent = React.forwardRef<
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300",
"mt-2 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-950 focus-visible:ring-offset-2 dark:ring-offset-zinc-950 dark:focus-visible:ring-zinc-300",
className
)}
{...props}
+2 -2
View File
@@ -104,8 +104,8 @@ export const SubscribeSection = () => {
return (
<div>
<h2 className="text-xl font-medium">Pick a subscription</h2>
<p className="text-zinc-500">You can cancel anytime</p>
<h2 className="text-xl font-medium">Pricing</h2>
<p className="font-mono text-sm text-zinc-500">Cancel anytime</p>
<Tabs
className="mt-4"
+105 -33
View File
@@ -6,7 +6,7 @@ import { IoSettingsSharp } from "react-icons/io5";
import { useMutation } from "@tanstack/react-query";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { MoreVertical, Trash } from "lucide-react";
import { MoreVertical, PenLine, PlusIcon, TagIcon, Trash } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@@ -21,6 +21,8 @@ import { useBlogTags } from "@/components/Editor/Editor.queries";
import { usePostsQuery } from "@/queries/posts";
import { useBlogQuery } from "@/queries/blogs";
import { formatDate } from "@/lib/utils";
import { PiGear, PiPencilLine, PiTag, PiTrash } from "react-icons/pi";
import Spinner from "@/components/Spinner";
export function StatePill({ published }: { published: boolean }) {
const text = published ? "Published" : "Draft";
@@ -92,7 +94,7 @@ export default function BlogPosts() {
aria-label="Settings"
>
<div className="sr-only">Settings</div>
<IoSettingsSharp size="24" />
<PiGear size="24" />
</Link>
</Button>
@@ -101,6 +103,7 @@ export default function BlogPosts() {
href={`/blogs/${blog.id}/create`}
className="btn btn-secondary max-w-[120px]"
>
<PiPencilLine size="16" />
New post
</Link>
</Button>
@@ -111,7 +114,7 @@ export default function BlogPosts() {
{posts.length === 0 && (
<div className="p-12 py-32 text-center">
<div className="text-2xl">✏️</div>
<div className="text-lg text-slate-500">
<div className="text-lg text-zinc-500">
Nothing here yet
</div>
<Button asChild>
@@ -128,7 +131,7 @@ export default function BlogPosts() {
return (
<Link
href={`/blogs/${blogId}/post/${post.slug}`}
className="flex items-center gap-4 rounded-sm p-3 hover:bg-zinc-100/60"
className="flex items-center gap-4 rounded-sm p-3 hover:bg-zinc-100/50"
key={post.slug}
>
{post.cover_image && (
@@ -140,23 +143,28 @@ export default function BlogPosts() {
/>
</div>
)}
<div>
<h2 className="text-lg font-medium">{post.title}</h2>
</div>
<StatePill published={post.published || false} />
{post.tags && post.tags.length > 0 && (
<div className="flex items-center gap-2">
{post.tags?.map((tag) => (
<span
key={tag}
className="rounded-md bg-zinc-100 px-2 py-1 text-xs font-semibold text-zinc-600"
>
{tag}
</span>
))}
<div className="flex flex-col gap-0.5">
<div>
<StatePill published={post.published || false} />
</div>
)}
<div className="ml-auto flex items-center gap-2 text-xs text-slate-500">
<h2 className="ml-1 text-lg font-medium">
{post.title}
</h2>
</div>
<div className="ml-auto flex items-center gap-2 text-xs text-zinc-500">
{post.tags && post.tags.length > 0 && (
<div className="flex items-center gap-2">
{post.tags?.map((tag) => (
<span
key={tag}
className="rounded-md bg-zinc-100 px-2 py-1 font-mono text-xs font-semibold text-zinc-600"
>
{tag}
</span>
))}
</div>
)}
<span>{formatDate(post.created_at || "")}</span>
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -203,22 +211,86 @@ export default function BlogPosts() {
</div>
</TabsContent>
<TabsContent value="media">
{images.data?.map((img) => {
return (
<div key={img.id}>
<img src={img.url} alt={img.name} />
{images.isLoading && (
<>
<div className="flex-center p-12">
<Spinner />
</div>
);
})}
</>
)}
<div className="rounded-xl border bg-white py-2 shadow-sm">
{images.data?.length === 0 && (
<>
<div className="p-12 py-32 text-center">
<div className="text-2xl">📷</div>
<div className="text-lg text-zinc-500">
No images uploaded yet
</div>
</div>
</>
)}
<div className="flex flex-col gap-2">
{images.data?.map((img) => {
return (
<div key={img.id} className="flex gap-4 px-4">
<img
className="h-24 w-32 rounded-md object-cover"
src={img.url}
alt={img.name}
height={24}
width={80}
/>
<div>
<h3 className="font-mono">{img.name}</h3>
<p className="mt-1 font-mono text-xs tracking-tighter text-zinc-400">
{img.url}
</p>
</div>
</div>
);
})}
</div>
</div>
</TabsContent>
<TabsContent value="tags">
{blogTags.data?.map((tag) => {
return (
<div key={tag.id}>
<h2>{tag.name}</h2>
</div>
);
})}
<div className="rounded-xl border bg-white py-2 shadow-sm">
{blogTags.data?.map((tag) => {
return (
<div
key={tag.id}
className="flex items-center gap-1.5 px-4 py-2 font-mono"
>
<div className="rounded-md text-zinc-400">
<PiTag size="16" />
</div>
<h2>{tag.name}</h2>
<p className="text-sm text-zinc-500">/{tag.slug}</p>
<div>
<Button asChild size="icon" variant={"ghost"}>
<Link
href={`/blogs/${blogId}/tags/${tag.id}`}
className="btn btn-icon"
title="Edit tag"
aria-label="Edit tag"
>
<PiPencilLine size="16" />
</Link>
</Button>
<Button asChild size="icon" variant={"ghost"}>
<Link
href={`/blogs/${blogId}/tags/${tag.id}`}
className="btn btn-icon"
title="Delete tag"
aria-label="Delete tag"
>
<PiTrash size="16" />
</Link>
</Button>
</div>
</div>
);
})}
</div>
</TabsContent>
</Tabs>
</div>
+2 -2
View File
@@ -47,7 +47,7 @@ export default function Dashboard() {
{data?.map((blog) => {
return (
<li
className="group rounded-xl border bg-gradient-to-b from-white to-zinc-50 shadow-sm transition-all hover:border-orange-400"
className="group rounded-xl border border-b-2 bg-white shadow-sm transition-all hover:border-orange-400"
key={blog.id}
>
<Link
@@ -62,7 +62,7 @@ export default function Dashboard() {
</div>
<div>
<h3 className="text-lg font-medium">{blog.title}</h3>
<p className="text-gray-600">{blog.description}</p>
<p className="text-zinc-500">{blog.description}</p>
</div>
</div>
<div className="actions mt-4">
+7 -7
View File
@@ -48,7 +48,7 @@ const Home = () => {
<meta name="description" content="Open source blogging cms" />
<link rel="icon" href="/static/favicon.ico" />
</Head>
<div className="bg-zinc-100">
<div className="bg-zinc-50">
<div className="mx-auto flex max-w-3xl flex-col">
<nav className="flex items-center justify-between p-3">
<div className="flex-grow cursor-default">
@@ -82,11 +82,11 @@ const Home = () => {
Log in
</Link>
</Button>
<Button asChild>
{/* <Button asChild>
<Link href="/sign-up" title="Sign up" aria-label="Sign up">
Sign up
</Link>
</Button>
</Button> */}
</div>
)}
@@ -144,7 +144,7 @@ const Home = () => {
<hr className="my-12" />
{!hasSubmitted && (
<form
className="mt-6 flex max-w-sm flex-col gap-2 py-2"
className="mt-6 flex max-w-sm flex-col gap-2 py-2 pb-12"
onSubmit={onSubmit}
>
<h2>
@@ -193,11 +193,11 @@ const Home = () => {
</div>
)}
<hr className="my-12" />
{/* <hr className="my-12" /> */}
<section className="pb-8">
{/* <section className="pb-8">
<SubscribeSection />
</section>
</section> */}
</main>
</div>
<Footer />
+8 -9
View File
@@ -12,13 +12,12 @@
## Bugs & Improvements
- [x] Fix email redirect when navigating to protected routes
- [] Force email confirmation
- [] Make layout work on mobile
- [] Make inputs not zoom in on mobile
- [x] Force email confirmation
## Pricing
- [] Add pricing page
- [] Grace period for expired subscriptions
- [x] Sync products between Stripe and Supabase
- [x] Sync subscriptions between Stripe and Supabase
@@ -41,15 +40,15 @@
- [x] Create post
- [x] View post
- [x] Edit post
- [] Delete post
- [x] Delete post
## Tags
- [] Create tag
- [x] Create tag
- [] Delete tag
- [] View tag
- [] View all tags
- [] Add tags to posts
- [] Update tag
- [x] View all tags
- [x] Add tags to posts
## Docs
@@ -62,4 +61,4 @@
- [x] Add method to fetch all posts
- [x] Add method to fetch a single post by slug
- [] Pagination
- [] Filtering by category?
- [] Filtering by tag?
+4 -1
View File
@@ -8,6 +8,8 @@
const posts = await client.posts.list({ withContent: true, limit: 10 });
```
- [] Allow draft/publish from post list.
- [] Allow copy slug from post list.
- [] Allow users to add default metadata for posts in a blog.
- [] Add PolyScale cache to Supabase for faster queries?
- [] Featured Images
@@ -18,9 +20,10 @@ const posts = await client.posts.list({ withContent: true, limit: 10 });
- [] Redesign settings page to be similar to Plausible
- [] Use a generic on createClient to pass the custom metadata
- [] Add files to custom metadata
- [] Allow "undo" for destructive actions - [] Delete post - [] Delete blog
- Allow users to see their trash and restore items from the trash.
- [] Make layout work on mobile
- [] Make inputs not zoom in on mobile
## Teams