Skip to content

Commit 9ab2b1e

Browse files
committed
hello
1 parent 0d7fa1d commit 9ab2b1e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6696
-457
lines changed

actions/Post.ts

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// "use server"
2+
import { databases, storage } from "@/appwrite/config";
3+
import { ID } from "@/appwrite/config";
4+
import { Query } from "appwrite";
5+
const databaseId = "658523095511f304a8d8";
6+
const collectionId = "6585231472104faa10cc";
7+
export async function CreatePost({
8+
postTitle,
9+
Content,
10+
createdAt,
11+
featuredImage,
12+
authorId,
13+
isPublished,
14+
authorName,
15+
authorImg,
16+
tag,
17+
}: {
18+
postTitle: string;
19+
Content: string;
20+
createdAt: Date;
21+
featuredImage: string;
22+
authorId: string | undefined;
23+
isPublished: boolean;
24+
authorName: string | undefined | null;
25+
authorImg: string | undefined;
26+
tag?: string;
27+
}) {
28+
const promise = await databases.createDocument(
29+
databaseId,
30+
collectionId,
31+
ID.unique(),
32+
{
33+
Content: Content,
34+
createdAt: createdAt,
35+
"Featured-Image": featuredImage,
36+
"Author-id": authorId,
37+
"Is-published": isPublished,
38+
authorName: authorName,
39+
AuthorImg: authorImg,
40+
tag: tag,
41+
PostTitle: postTitle,
42+
}
43+
);
44+
return promise;
45+
}
46+
47+
export async function uploadFeaturedImage(file: File) {
48+
const promise = await storage.createFile(
49+
"657d91f9b8990976735a",
50+
ID.unique(),
51+
file
52+
);
53+
54+
const img = storage.getFilePreview("657d91f9b8990976735a", promise.$id);
55+
return img;
56+
}
57+
58+
export async function getAllPosts() {
59+
try {
60+
const res = await databases.listDocuments(databaseId, collectionId, [
61+
Query.equal("Is-published", true),
62+
Query.orderDesc("createdAt"),
63+
]);
64+
return res; // Return the data
65+
} catch (err) {
66+
console.error("Error fetching posts:", err);
67+
throw err; // Rethrow the error to be caught in the component
68+
}
69+
}
70+
71+
export async function getPostById(id: string) {
72+
try {
73+
const res = await databases.listDocuments(databaseId, collectionId, [
74+
Query.equal("$id", id),
75+
]);
76+
// console.log(res)
77+
return res;
78+
} catch (error) {
79+
console.log(error);
80+
}
81+
}
82+
83+
export async function getPostByUserId(userId?: string) {
84+
try {
85+
if (userId) {
86+
const res = await databases.listDocuments(databaseId, collectionId, [
87+
Query.equal("Author-id", userId),
88+
]);
89+
// console.log(res)
90+
return res;
91+
}
92+
} catch (error) {
93+
console.log(error);
94+
throw error; // Rethrow the error to handle it in the calling code if needed
95+
}
96+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { SignIn } from "@clerk/nextjs";
2+
3+
export default function Page() {
4+
return <div className="flex items-center justify-center w-full h-full"><SignIn /></div >;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { SignUp } from "@clerk/nextjs";
2+
3+
export default function Page() {
4+
return<div className="flex items-center justify-center h-full w-full"> <SignUp /></div >;
5+
}

app/(Private)/create-post/page.tsx

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"use client";
2+
import { CreatePost, uploadFeaturedImage } from "@/actions/Post";
3+
import {
4+
Form,
5+
FormControl,
6+
FormField,
7+
FormItem,
8+
FormMessage,
9+
FormLabel,
10+
} from "@/components/ui/form";
11+
import { Input } from "@/components/ui/input";
12+
import TipTap from "@/components/TipTap";
13+
14+
import { useUser } from "@clerk/nextjs";
15+
import { zodResolver } from "@hookform/resolvers/zod";
16+
import React, { useState } from "react";
17+
import { useForm } from "react-hook-form";
18+
import * as z from "zod";
19+
import { Button } from "@/components/ui/button";
20+
import { toast } from "sonner";
21+
import { SingleImageDropzone } from "@/components/UploadDropZone";
22+
import { Label } from "@/components/ui/label";
23+
import { useRouter } from "next/navigation";
24+
import { HardDriveUpload, Loader2, Save } from "lucide-react";
25+
import { Spinner } from "@/components/Spinner";
26+
const CreatePostPage = () => {
27+
const router = useRouter();
28+
const [File, setFile] = useState<File>();
29+
const { user } = useUser();
30+
const [isLoading, setIsLoading] = useState(false);
31+
const PostSchema = z.object({
32+
postTitle: z.string().min(15).max(300, { message: "Title is to Short" }),
33+
content: z.string(),
34+
isPublished: z.boolean().default(false),
35+
tag: z.string().default("No Tag"),
36+
});
37+
38+
const theform = useForm<z.infer<typeof PostSchema>>({
39+
resolver: zodResolver(PostSchema),
40+
mode: "onChange",
41+
});
42+
const handlePublish = async (values: z.infer<typeof PostSchema>) => {
43+
const currentDateISOString: string = new Date().toISOString();
44+
const currentDate: Date = new Date(currentDateISOString);
45+
let ImgUrl;
46+
if (File) {
47+
const { content, isPublished, postTitle, tag } = values;
48+
const imgpromise = uploadFeaturedImage(File);
49+
setIsLoading(true);
50+
imgpromise
51+
.then(async (res) => {
52+
console.log(res);
53+
ImgUrl = res.href;
54+
await CreatePost({
55+
postTitle: postTitle,
56+
Content: content,
57+
authorId: user?.id,
58+
createdAt: currentDate,
59+
isPublished: isPublished,
60+
featuredImage: ImgUrl,
61+
authorImg: user?.imageUrl,
62+
authorName: user?.firstName,
63+
tag: tag,
64+
});
65+
})
66+
.then(() => {
67+
toast.success("Post saved successfully!");
68+
router.push("/myprofile");
69+
setTimeout(() => {
70+
theform.setValue("postTitle", "");
71+
theform.setValue("content", "");
72+
theform.setValue("isPublished", false);
73+
}, 500);
74+
setIsLoading(false);
75+
})
76+
.catch((error) => {
77+
console.error(error);
78+
toast.error("Failed to create post.");
79+
setIsLoading(false);
80+
})
81+
.finally(() => {
82+
setIsLoading(false);
83+
});
84+
}
85+
};
86+
87+
return (
88+
<div className="w-full h-full flex items-center justify-center ">
89+
<div className="bg-transparent mt-9 p-10 rounded-md w-full mx-10">
90+
<Label className="text-3xl mb-2">Featured Image *</Label>
91+
<div className="">
92+
<SingleImageDropzone
93+
className="mt-8 mb-8 self-center"
94+
height={200}
95+
width={500}
96+
value={File}
97+
onChange={(file) => {
98+
setFile(file);
99+
}}
100+
/>
101+
</div>
102+
<Form {...theform}>
103+
<form onSubmit={theform.handleSubmit(handlePublish)}>
104+
<FormField
105+
control={theform.control}
106+
name="postTitle"
107+
render={({ field }) => (
108+
<FormItem>
109+
<FormLabel className="text-3xl mb-2 ">PostTitle *</FormLabel>
110+
<FormControl>
111+
<Input
112+
className="w-full"
113+
{...field}
114+
placeholder="Enter your post title"
115+
/>
116+
</FormControl>
117+
<FormMessage className="text-red-500 font-extrabold p-2" />
118+
</FormItem>
119+
)}
120+
/>
121+
<FormField
122+
control={theform.control}
123+
name="tag"
124+
render={({ field }) => (
125+
<FormItem>
126+
<FormLabel className="text-3xl mb-2 ">Tag</FormLabel>
127+
<FormControl>
128+
<Input
129+
className="w-full"
130+
{...field}
131+
placeholder="Enter your post title"
132+
/>
133+
</FormControl>
134+
<FormMessage className="text-red-500 font-extrabold p-2" />
135+
</FormItem>
136+
)}
137+
/>
138+
<FormField
139+
control={theform.control}
140+
name="content"
141+
render={({ field }) => (
142+
<FormItem className="mt-10">
143+
<FormLabel className="text-3xl ">Content *</FormLabel>
144+
<FormControl>
145+
<TipTap content={field.value} onChange={field.onChange} />
146+
</FormControl>
147+
<FormMessage className="text-red-500 font-extrabold p-2" />
148+
</FormItem>
149+
)}
150+
/>
151+
<div className="w-full flex gap-3 items-center justify-evenly mt-5">
152+
<Button
153+
disabled={isLoading}
154+
className="w-full gap-2 flex items-center"
155+
type="submit"
156+
onClick={() => theform.setValue("isPublished", false)}
157+
variant={"outline"}
158+
>
159+
{!isLoading ? <Save /> : <Loader2 className="animate-spin w-4 h-4 text-white" />}
160+
Save as Draft
161+
</Button>
162+
<Button
163+
disabled={isLoading}
164+
className="w-full gap-2 flex items-center"
165+
type="submit"
166+
onClick={() => theform.setValue("isPublished", true)}
167+
>
168+
{!isLoading ? <HardDriveUpload /> : <Loader2 className="animate-spin w-4 h-4 text-white" />}
169+
Publish
170+
</Button>
171+
</div>
172+
</form>
173+
</Form>
174+
</div>
175+
</div>
176+
);
177+
};
178+
179+
export default CreatePostPage;

app/(Private)/myprofile/page.tsx

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
"use client";
2+
/**
3+
* v0 by Vercel.
4+
* @see https://v0.dev/t/n56SDiQiKMp
5+
*/
6+
import { AvatarImage, AvatarFallback, Avatar } from "@/components/ui/avatar";
7+
import parse from "html-react-parser"
8+
import Link from "next/link";
9+
import { CardHeader, CardContent, Card } from "@/components/ui/card";
10+
import { Button } from "@/components/ui/button";
11+
import { useUser } from "@clerk/nextjs";
12+
import { useEffect, useState } from "react";
13+
import { getPostByUserId } from "@/actions/Post";
14+
import dayjs from "dayjs";
15+
import cheerio from "cheerio"
16+
import { useRouter } from "next/navigation";
17+
18+
const MyProfilePage = () => {
19+
const { user } = useUser();
20+
const [data, setData] = useState<any>();
21+
const router = useRouter()
22+
useEffect(() => {
23+
const getData = async () => {
24+
try {
25+
if (user?.id) {
26+
const promise = await getPostByUserId(user.id);
27+
setData(promise);
28+
} else {
29+
// Handle the case where user?.id is undefined
30+
console.log("User ID is undefined");
31+
}
32+
} catch (error) {
33+
// Handle any errors from getPostByUserId
34+
console.error(error);
35+
}
36+
};
37+
38+
getData();
39+
}, [user]);
40+
console.log(data);
41+
function createSlug(text: string, maxLength = 50) {
42+
const cleanedText = text
43+
.trim()
44+
.toLowerCase()
45+
.replace(/[^\w\s-]/g, '')
46+
.slice(0, maxLength);
47+
48+
return cleanedText;
49+
}
50+
return (
51+
<>
52+
<div className="flex flex-col items-center py-10">
53+
<Avatar className="h-24 w-24 mb-4">
54+
<AvatarImage alt="User Avatar" src={user?.imageUrl} />
55+
<AvatarFallback>JP</AvatarFallback>
56+
</Avatar>
57+
<h1 className="text-2xl font-bold">{user?.fullName}</h1>
58+
<p className="text-gray-500 dark:text-gray-400">
59+
{user?.primaryEmailAddress?.emailAddress}
60+
</p>
61+
</div>
62+
<div className="px-6">
63+
<h2 className="text-xl font-semibold mb-2">Blog Posts</h2>
64+
<div className="space-y-6 mt-7">
65+
{data?.documents.map((post: any) => {
66+
const createdAt = dayjs(post["createdAt"]).format("DD-MMM-YYYY")
67+
const $ = cheerio.load(post["Content"]); // Load HTML content using cheerio
68+
const textContent = $('body').text(); // Extract text content using cheerio
69+
const slug = createSlug(textContent, 300);
70+
let urlTitle = post["PostTitle"].replace(/ /g, "-");
71+
return (
72+
<Card key={post["$id"]}>
73+
<CardHeader>
74+
<h3 className="text-lg font-semibold">{post["PostTitle"]}</h3>
75+
<p className="text-gray-500 dark:text-gray-400">
76+
{createdAt}
77+
</p>
78+
</CardHeader>
79+
<CardContent>
80+
<p className="text-gray-700 dark:text-gray-300 truncate w-full">
81+
{slug}
82+
</p>
83+
</CardContent>
84+
<Link href={`/post/${urlTitle}/${post["$id"]}`}>
85+
<Button variant={"secondary"} className="mt-4 m-3">
86+
Read more
87+
</Button>
88+
</Link>
89+
</Card>
90+
)
91+
})}
92+
93+
</div>
94+
</div>
95+
</>
96+
);
97+
};
98+
export default MyProfilePage;

0 commit comments

Comments
 (0)