sanity-studio integrated
This commit is contained in:
parent
61dafc3f3f
commit
497b7d091e
20 changed files with 25057 additions and 89 deletions
|
@ -10,3 +10,5 @@ NEXTAUTH_SECRET=
|
|||
|
||||
EMAIL=
|
||||
EMAIL_PASS=
|
||||
|
||||
SANITY_PROJECT_ID=
|
67
app/(root)/blog/[slug]/page.tsx
Normal file
67
app/(root)/blog/[slug]/page.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { client, urlFor } from "@/lib/sanity";
|
||||
import { PortableText } from "@portabletext/react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
|
||||
async function getData(slug: string) {
|
||||
const query = `
|
||||
*[_type == "blog" && slug.current == '${slug}'] {
|
||||
"currentSlug": slug.current,
|
||||
title,
|
||||
content,
|
||||
titleImage
|
||||
}[0]`;
|
||||
|
||||
const data = await client.fetch(query);
|
||||
return data;
|
||||
}
|
||||
|
||||
interface BlogSlugArticle {
|
||||
currentSlug: string;
|
||||
title: string;
|
||||
content: any;
|
||||
titleImage: string;
|
||||
}
|
||||
|
||||
export default async function BlogSlugArticle({
|
||||
params,
|
||||
}: {
|
||||
params: { slug: string };
|
||||
}) {
|
||||
const data: BlogSlugArticle = await getData(params.slug);
|
||||
|
||||
return (
|
||||
<section className="max-w-5xl container mx-auto py-24 md:py-28 flex flex-col items-center px-4">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="self-start mb-8 text-primary hover:text-green-300 transition-all flex items-center"
|
||||
>
|
||||
<ArrowLeft className="mr-2" />
|
||||
Back to Blog
|
||||
</Link>
|
||||
<header className="text-center mb-12 w-full">
|
||||
{data.titleImage && (
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl md:text-4xl mb-12 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
|
||||
{data.title}
|
||||
</h1>
|
||||
<Image
|
||||
src={urlFor(data.titleImage).url()}
|
||||
alt={data.title}
|
||||
width={1200}
|
||||
height={800}
|
||||
priority
|
||||
className="w-full h-auto object-cover rounded-md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
<Separator className="mb-6" />
|
||||
<article className="prose max-w-full md:prose-lg dark:prose-invert">
|
||||
<PortableText value={data.content} />
|
||||
</article>
|
||||
</section>
|
||||
);
|
||||
}
|
|
@ -5,11 +5,16 @@ import BlogCards from "@/components/cards/BlogCards";
|
|||
export const metadata: Metadata = {
|
||||
title: "Blog - SVRJS",
|
||||
};
|
||||
|
||||
const BlogPage = () => {
|
||||
return (
|
||||
<section id="blog" className="mb-12 grid gap-6 p-6 md:gap-12">
|
||||
<h2 className="text-3xl sm:text-4xl">SVRJS BlogPage</h2>
|
||||
|
||||
<section
|
||||
id="blog"
|
||||
className="wrapper container py-24 md:py-28 gap-2 flex-center flex-col"
|
||||
>
|
||||
<h1 className="text-3xl md:text-5xl mb-12 pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
|
||||
SVRJS Blog Post
|
||||
</h1>
|
||||
<BlogCards />
|
||||
</section>
|
||||
);
|
||||
|
|
|
@ -2,37 +2,66 @@ import React from "react";
|
|||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { client, urlFor } from "@/lib/sanity";
|
||||
import { Card, CardContent } from "../ui/card";
|
||||
|
||||
interface BlogPostcard {
|
||||
title: string;
|
||||
smallDescription: string;
|
||||
currentSlug: string;
|
||||
titleImage: string;
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
const query = `*[_type == 'blog'] | order(_createdAt desc) {
|
||||
title,
|
||||
smallDescription,
|
||||
"currentSlug": slug.current,
|
||||
titleImage
|
||||
}`;
|
||||
|
||||
const data = await client.fetch(query);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const BlogCards = async () => {
|
||||
const data: BlogPostcard[] = await getData();
|
||||
console.log(data);
|
||||
|
||||
const BlogCards = () => {
|
||||
return (
|
||||
<section className="grid max-w-6xl gap-2 fade-in sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="fade-in-bottom group h-fit w-fit rounded-lg border delay-300">
|
||||
<Link
|
||||
href="/blog"
|
||||
className="relative block overflow-hidden rounded-lg border"
|
||||
<section className="grid max-w-6xl gap-4 mx-auto sm:grid-cols-2 lg:grid-cols-3">
|
||||
{data.map((post, idx) => (
|
||||
<Card
|
||||
className="group h-full w-full rounded-lg border overflow-hidden"
|
||||
key={idx}
|
||||
>
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
<Link href={`/blog/${post.currentSlug}`} className="block">
|
||||
<div className="relative overflow-hidden rounded-t-lg">
|
||||
<Image
|
||||
src={"/metadata/svrjs-cover.png"}
|
||||
alt={"svrjs-cover"}
|
||||
src={urlFor(post.titleImage).url()}
|
||||
alt="SVRJS Blog Cover"
|
||||
width={500}
|
||||
height={50}
|
||||
className="h-full w-full object-cover object-center transition-all md:group-hover:scale-[1.01]"
|
||||
height={300}
|
||||
className="w-full object-cover transition-transform duration-200 group-hover:scale-105"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex w-full flex-col justify-between gap-2 rounded-b-lg border-t bg-accent/25 p-4 md:flex-row md:items-start md:p-2 md:group-hover:bg-accent/50">
|
||||
<div>
|
||||
<p>Svrjs Node Server</p>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
Description here
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground opacity-0 group-hover:opacity-100 duration-300">
|
||||
<CardContent className="p-4">
|
||||
<div className="flex-between mb-2 py-2 ">
|
||||
<h3 className="text-xl font-semibold leading-tight">
|
||||
{post.title}
|
||||
</h3>
|
||||
<div className="text-sm text-muted-foreground opacity-0 group-hover:opacity-100 duration-300">
|
||||
<ExternalLink />
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{post.smallDescription}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Link>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as React from "react";
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
|
@ -10,12 +10,12 @@ const Card = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Card.displayName = "Card";
|
||||
))
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
|
@ -26,8 +26,8 @@ const CardHeader = React.forwardRef<
|
|||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardHeader.displayName = "CardHeader";
|
||||
))
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
|
@ -37,12 +37,12 @@ const CardTitle = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardTitle.displayName = "CardTitle";
|
||||
))
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
|
@ -53,16 +53,16 @@ const CardDescription = React.forwardRef<
|
|||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardDescription.displayName = "CardDescription";
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
));
|
||||
CardContent.displayName = "CardContent";
|
||||
))
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
|
@ -73,14 +73,7 @@ const CardFooter = React.forwardRef<
|
|||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardFooter.displayName = "CardFooter";
|
||||
))
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardFooter,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardContent,
|
||||
};
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
|
|
15
lib/sanity.ts
Normal file
15
lib/sanity.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { createClient } from "next-sanity";
|
||||
import imageUrlBuilder from "@sanity/image-url";
|
||||
|
||||
export const client = createClient({
|
||||
apiVersion: "2023-05-03",
|
||||
dataset: "production",
|
||||
projectId: `${process.env.SANITY_PROJECT_ID}`,
|
||||
useCdn: false, // basically enable this for faster loading time
|
||||
});
|
||||
|
||||
const builder = imageUrlBuilder(client);
|
||||
|
||||
export function urlFor(source: any) {
|
||||
return builder.image(source);
|
||||
}
|
|
@ -1,7 +1,17 @@
|
|||
import nextra from "nextra";
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const NextConfig = {};
|
||||
const NextConfig = {
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "cdn.sanity.io",
|
||||
port: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const withNextra = nextra({
|
||||
theme: "nextra-theme-docs",
|
||||
|
|
10386
package-lock.json
generated
10386
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@
|
|||
"dependencies": {
|
||||
"@hookform/resolvers": "^3.6.0",
|
||||
"@mdx-js/mdx": "^3.0.1",
|
||||
"@portabletext/react": "^3.1.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-dialog": "^1.1.1",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.1",
|
||||
|
@ -22,6 +23,7 @@
|
|||
"@radix-ui/react-slot": "^1.1.0",
|
||||
"@radix-ui/react-toast": "^1.2.1",
|
||||
"@radix-ui/themes": "^3.0.5",
|
||||
"@sanity/image-url": "^1.0.2",
|
||||
"@tailwindcss/typography": "^0.5.13",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/cookie": "^0.6.0",
|
||||
|
@ -40,6 +42,7 @@
|
|||
"mongoose": "^8.4.3",
|
||||
"next-auth": "^4.24.7",
|
||||
"next-mdx-remote": "^5.0.0",
|
||||
"next-sanity": "^9.4.4",
|
||||
"next-themes": "^0.3.0",
|
||||
"nextra": "^2.13.4",
|
||||
"nextra-theme-docs": "^2.13.4",
|
||||
|
|
3
svrjs/.eslintrc
Normal file
3
svrjs/.eslintrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "@sanity/eslint-config-studio"
|
||||
}
|
29
svrjs/.gitignore
vendored
Normal file
29
svrjs/.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# Dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# Compiled Sanity Studio
|
||||
/dist
|
||||
|
||||
# Temporary Sanity runtime, generated by the CLI on every dev server start
|
||||
/.sanity
|
||||
|
||||
# Logs
|
||||
/logs
|
||||
*.log
|
||||
|
||||
# Coverage directory used by testing tools
|
||||
/coverage
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# Typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# Dotenv and similar local-only files
|
||||
*.local
|
9
svrjs/README.md
Normal file
9
svrjs/README.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Sanity Clean Content Studio
|
||||
|
||||
Congratulations, you have now installed the Sanity Content Studio, an open-source real-time content editing environment connected to the Sanity backend.
|
||||
|
||||
Now you can do the following things:
|
||||
|
||||
- [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme)
|
||||
- [Join the community Slack](https://slack.sanity.io/?utm_source=readme)
|
||||
- [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme)
|
14359
svrjs/package-lock.json
generated
Normal file
14359
svrjs/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
37
svrjs/package.json
Normal file
37
svrjs/package.json
Normal file
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "svrjs",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "package.json",
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
"dev": "sanity dev",
|
||||
"start": "sanity start",
|
||||
"build": "sanity build",
|
||||
"deploy": "sanity deploy",
|
||||
"deploy-graphql": "sanity graphql deploy"
|
||||
},
|
||||
"keywords": [
|
||||
"sanity"
|
||||
],
|
||||
"dependencies": {
|
||||
"@sanity/vision": "^3.53.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"sanity": "^3.53.0",
|
||||
"styled-components": "^6.1.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sanity/eslint-config-studio": "^4.0.0",
|
||||
"@types/react": "^18.0.25",
|
||||
"eslint": "^8.6.0",
|
||||
"prettier": "^3.0.2",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 100,
|
||||
"bracketSpacing": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
}
|
8
svrjs/sanity.cli.ts
Normal file
8
svrjs/sanity.cli.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import {defineCliConfig} from 'sanity/cli'
|
||||
|
||||
export default defineCliConfig({
|
||||
api: {
|
||||
projectId: '0u8q501s',
|
||||
dataset: 'production'
|
||||
}
|
||||
})
|
18
svrjs/sanity.config.ts
Normal file
18
svrjs/sanity.config.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {defineConfig} from 'sanity'
|
||||
import {structureTool} from 'sanity/structure'
|
||||
import {visionTool} from '@sanity/vision'
|
||||
import {schemaTypes} from './schemaTypes'
|
||||
|
||||
export default defineConfig({
|
||||
name: 'default',
|
||||
title: 'svrjs',
|
||||
|
||||
projectId: '0u8q501s',
|
||||
dataset: 'production',
|
||||
|
||||
plugins: [structureTool(), visionTool()],
|
||||
|
||||
schema: {
|
||||
types: schemaTypes,
|
||||
},
|
||||
})
|
40
svrjs/schemaTypes/blog.ts
Normal file
40
svrjs/schemaTypes/blog.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
export default {
|
||||
name: 'blog',
|
||||
type: 'document',
|
||||
title: 'Blog',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
title: 'Title',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'slug',
|
||||
title: 'Slug Title',
|
||||
options: {
|
||||
source: 'title',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'titleImage',
|
||||
type: 'image',
|
||||
title: 'Title Image',
|
||||
},
|
||||
{
|
||||
name: 'smallDescription',
|
||||
type: 'text',
|
||||
title: 'Small Description',
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'array',
|
||||
title: 'Content',
|
||||
of: [
|
||||
{
|
||||
type: 'block',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
3
svrjs/schemaTypes/index.ts
Normal file
3
svrjs/schemaTypes/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import blog from './blog'
|
||||
|
||||
export const schemaTypes = [blog]
|
1
svrjs/static/.gitkeep
Normal file
1
svrjs/static/.gitkeep
Normal file
|
@ -0,0 +1 @@
|
|||
Files placed here will be served by the Sanity server under the `/static`-prefix
|
17
svrjs/tsconfig.json
Normal file
17
svrjs/tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "Preserve",
|
||||
"moduleDetection": "force",
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
Loading…
Reference in a new issue