Skip to content

Commit

Permalink
massive update, cached stroage, performance improvement, pagination f…
Browse files Browse the repository at this point in the history
…or all pages, couple bug fixes , Readme updates, UI updates
  • Loading branch information
tomyRomero committed Jan 18, 2024
1 parent f0b09b2 commit af5dce2
Show file tree
Hide file tree
Showing 45 changed files with 809 additions and 603 deletions.
72 changes: 53 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ Sparks is a full stack social media web app that is designed to help users disco
## Features

- List the main features of your application.
- AI-powered post generation with various categories and the ability to delete them if you created them, AI image generation also included.
- AI-powered post generation with various categories and the ability to delete ones you do not like if you are the author, AI image generation also included.
- Categories:
- Movies and Novels
- Artworks, Fashion , Photography
- Movies and Novels (includes AI generated images if the User desires, optional)
- Artworks, Fashion , Photography , (These are all AI generated images)
- Haikus , Quote, Joke , Aphorism
- Image Storage System: Cloud image storage powered by the cloud , allows all profile images, as well as profile posts and even AI generated images to be saved for future usage in a secure S3 bucket privately where only the developer can access them.
- Image Storage System: Cloud image storage powered by the cloud , allows all profile images, as well as profile posts and even AI generated images to be saved for future usage in a secure S3 bucket privately where only the developer can access them. Cache system included with local storage so images do not have to be fetched every single time.
- User profile management: Onboarding, Profile Edit
- User-to-user Messaging system: power by Pusher for realtime updates, ability to leave messages on read, ability to see when a user is online in chat.
- User-to-user Messaging system: powered by Pusher(Web Sockets) for realtime updates, ability to leave messages on read, ability to see when a user is online in chat.
- Activity feed - and pagination for performance
- Home Page Feed Filtering - Allow users to filter out the type of posts they would like to see.
- Like comment and share functionality: Allows the liking of posts, no user can like a post more than once, users can unlike posts as well, everything is reflected in database, posts can have children posts (comments) and they are all recursively structured where every comment has a parent ID, therefore comments can have comments of their own, providing a twitter-like comment structure, all powered by a SQL dynamic structure, users can share posts directly to other users in their inboxes by clicking the share button which will send a message with the posts' link.
- Search functionality
- Like comment and share functionality: Allows the liking of posts, no user can like a post more than once, users can unlike posts as well, everything is reflected in database, posts can have children posts (comments, which can be liked as well, authors can delete their comments as well) and they are all recursively structured where every comment has a parent ID, therefore comments can have comments of their own, providing a twitter-like comment structure, all powered by a SQL dynamic structure, users can share posts directly to other users in their inboxes by clicking the share button which will send a message with the posts' link.
- Search functionality, search for user profiles.
- Notification System: updates when user recieves new message or activity
- Profile Page: Profile page for users with posts they have made, comments they have made and posts they have liked, as well as the ability to message users from there or even edit your bio, and image if it is your profile.
- Database System: all changes are saved within database so you can pick back up where you left off.
- Database System: all changes are saved within the database so you can pick back up where you left off.
- Fully Responsive for all screens, phones, tablets and desktops.
- Global State System: using app context the app has a global state which helps with real-time functionalities for layout components
- Global State System: using app context the app has a global state which helps with real-time functionalities for layout components.
- Form Validation: Uses Zod Forms to put into place form validations where as users can only submit certain inputs depending on what is allowed.
- Liverages the latest of Next.js by using server actions and API routes, API routes include, openAIChat, openAIImage, and S3

Expand All @@ -45,11 +45,20 @@ The webapp is live and hosted by vercel https://sparkify.vercel.app
tomyfletcher99@hotmail.com

## Data Base Schema
<img src="public/assets/sqlSchema.png" alt="Screenshot of SqlSchema" >
<img src="public/assets/DataBaseSchema.png" alt="Screenshot of SqlSchema" >

Chats Table
<img src="public/assets/chatsTable.png" alt="Screenshot of Chats Table" >
We have a one-to-many relationship, as one user can have multiple chats, but each chat is associated with only one user. The same for posts and users, one user can have mutiple posts.
### Database Relationships
- User and Post Relationship:
One-to-Many relationship: A user can create multiple posts, but each post is associated with one user.

- Post and User (author) Relationship:
Many-to-One relationship: Many posts can be associated with one user (the author).

- Post and Post (parent-child) Relationship:
Recursive relationship: A post can have multiple child posts, creating a hierarchical structure.

- Chat and User (sender and receiver) Relationship:
Many-to-Many relationship: A user can be both the sender and receiver in multiple chats.

## Screenshots

Expand All @@ -64,8 +73,9 @@ User interface and different functionalities of Sparks.
### Home
<img src="public/assets/sparks-home.png" alt="Screenshot of Home" >

### Mobile
<img src="public/assets/sparks-home-moblie.png" alt="Screenshot of Home in Mobile">
### Responsive
<img src="public/assets/sparks-moblie.png" alt="Screenshot of Home in Mobile">
<img src="public/assets/sparks-tablet.png" alt="Screenshot of Home in Tablet">

### Create Studio
<img src="public/assets/sparks-studio.png" alt="Screenshot of User Profile">
Expand All @@ -86,8 +96,8 @@ User interface and different functionalities of Sparks.
### Profile
<img src="public/assets/sparks-profile.png" alt="Screenshot of User Profile">

## AI Post Examples
Below I tested all AI Post Categories with the same prompt , "apples". These are the results.
# AI Post Examples
Below I tested all AI Post Categories with the same prompt , "apples in a warm summer's glow". These are the results.

### Movie Spark
<img src="public/assets/movieSpark.png" alt="Screenshot of AI Spark">
Expand Down Expand Up @@ -120,8 +130,32 @@ Below I tested all AI Post Categories with the same prompt , "apples". These are
This project is open source and contributors are welcomed

## Future Improvements
Sparks still has a lot that can be worked, performance would be top priority as start up times sometimes lag and hinder interactivity at start up for couple seconds, whereas I would love to make server request more efficent as well as avoid memory leaks, It is scalable and as more users grow pagination would be needed on search results, activity and chats, the gathering of data can also always be improved for faster load times and efficency and so much more! I welcome any contributors to this project. AI for generating posts can be strengten and load times can decrease, right now the server functions time out when making large API requests to open AI such as movies and novels, this is due to vercel's free tier hosting plan that only allows a limit of 10 seconds for server functions.
New features could include post search, just like how it is for users, as well as a followers list , a followers feed, groupchats , and so much more!
### Performance Optimization

- Optimizing performance is my top priority. I aim to minimize unnecessary re-renders on the client and reduce function executions, both on the server and client sides. Contributions in this area will greatly enhance the overall user experience.

- Currently, large API requests to OpenAI, such as those for movies and novels, may lead to server function timeouts. This limitation is due to Vercel's free-tier hosting plan, which imposes a 10-second limit on server functions. Collaborative efforts to address this issue are essential for ensuring smooth interactions with external APIs.

## Project Status

- **Literature Studio**: Works on the deployed version.
- **Story and Gallery Studio**: Currently functional on localhost.

## New Features and Contributions

Future updates may be focused on these new features that I have in mind:

### 1. Post Search

Enhance user experience by implementing a post search functionality, allowing users to discover content more efficiently.

### 2. Followers List and Feed

Introduce a followers list and feed, providing users with a personalized stream of content from accounts they follow.

### 3. Group Chats

Explore the implementation of group chats, fostering community interactions and group discussions.

## Acknowledgments
Shout out to https://loading.io/ for all the icons provided
Expand Down
23 changes: 21 additions & 2 deletions app/(root)/activity/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,28 @@ import { fetchPostById} from "@/lib/actions/post.actions";
import { fetchLikesAndCommentsByUser } from "@/lib/actions/user.actions";
import Activity from "@/components/shared/Activity";
import { updateOnlineStatus } from "@/lib/actions/chat.actions";
import Pagination from "@/components/shared/Pagination";

async function Page() {
async function Page({
searchParams,
}: {
searchParams: { [key: string]: string | undefined };
}) {
const user = await currentUser();
if (!user) return null;

const userInfo = await fetchUser(user.id);
if (!userInfo?.onboarded) redirect("/onboarding");

const activity = await fetchLikesAndCommentsByUser(user.id, 5);
const results = await fetchLikesAndCommentsByUser(user.id,
//Limit of how much Activity I want to get
40,
//Page Number
searchParams.page ? + searchParams.page : 1,
//Page Size
8);

const activity = results.activity

function getLastUserId(likes: string) {
// Split the string using commas and filter out empty strings
Expand Down Expand Up @@ -77,6 +90,12 @@ async function Page() {
) : (
<p className='!text-base-regular text-light-3'>No activity yet</p>
)}
<Pagination
path="activity"
pageNumber={searchParams?.page ? + searchParams.page : 1}
isNext={results.isNext}
filter={false}
/>
</section>
</>
);
Expand Down
27 changes: 7 additions & 20 deletions app/(root)/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { redirect } from "next/navigation";
import HorizontalScroll from "@/components/shared/HorizontalScroll";
import { getChatsWithUsersByUserId, updateOnlineStatus } from "@/lib/actions/chat.actions";
import ChatLogs from "@/components/cards/ChatLogs";
import Pagination from "@/components/shared/Pagination";
import Chatbox from "@/components/Chatbox";


async function Page({
searchParams,
Expand All @@ -25,21 +28,15 @@ async function Page({
pageSize: 15,
});

let chats: any[] = await getChatsWithUsersByUserId(user.id)
let chats = await getChatsWithUsersByUserId(user.id)


const sortedChats = chats.sort((chatA, chatB) => {
const lastMessageA = chatA.messages[chatA.messages.length - 1];
const lastMessageB = chatB.messages[chatB.messages.length - 1];

const dateA = new Date(lastMessageA.timestamp);
const dateB = new Date(lastMessageB.timestamp);

console.log("Timestamp A: ", lastMessageA.timestamp);
console.log("Date A: ", dateA);

console.log("Timestamp B: ", lastMessageB.timestamp);
console.log("Date B: ", dateB);

const dateB = new Date(lastMessageB.timestamp);
// Compare the dates (descending order, latest time first)
//@ts-ignore
return dateB - dateA;
Expand Down Expand Up @@ -78,17 +75,7 @@ async function Page({

<br></br>
<h2 className="text-left ml-4 text-heading3-bold text-light-1">Recent Chats..</h2>
<div className="p-4 flex flex-col overflow-y-auto overflow-hidden">
{chats.length === 0 ? (
<p className="text-light-1 text-center">No Recent Chats, Click on A User Bubble To Get Started, Can Also Search for Users to Message</p>
): (
<>
{chats.map((chat: any) => (
<ChatLogs chatRead={chat.read_status} senderID={chat.sender_id} receiverID={chat.receiver_id} chatMessages={chat.messages} receiverPicture={chat.user_image} chatName={chat.user_username} isHome={false} path={'/chat'}/>
))}
</>
)}
</div>
<Chatbox chats= {chats}/>
</section>
);
}
Expand Down
34 changes: 19 additions & 15 deletions app/(root)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import React from "react";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import { ClerkProvider, currentUser} from "@clerk/nextjs";
import { dark } from "@clerk/themes";
Expand All @@ -9,48 +9,52 @@ import Topbar from "@/components/shared/TopBar";
import LeftSidebar from "@/components/shared/LeftBar";
import Bottombar from "@/components/shared/BottomBar";
import RightBar from "@/components/shared/RightBar";
import { fetchUser } from "@/lib/actions/user.actions";
import { getChatsWithUsersByUserId, updateOnlineStatus } from "@/lib/actions/chat.actions";

//Global State
import { AppProvider } from "@/lib/AppContext";
import { getClerkUser } from "@/lib/actions/user.actions";
import Loading from "./loading";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Sparks",
description: "Discover New AI Powered Ideas like never before",
};

export default async function RootLayout({
children
}: {
children: React.ReactNode;
}) {

const userid = await getClerkUser();
if (!userid) return null;

return (
userid?.length ? (
<ClerkProvider
appearance={{
baseTheme: dark,
}}
>
<html lang='en'>
<body className={inter.className}>
<AppProvider>
<Topbar />
<AppProvider>
<Topbar userId={userid}/>
<main className='flex flex-row'>
<LeftSidebar />
<LeftSidebar userid={userid} />
<section className='main-container'>
<div className='w-full max-w-4xl'>{children}</div>
</section>
<RightBar />
<RightBar userid={userid}/>
</main>
<Bottombar />
<Bottombar userid={userid}/>
</AppProvider>
</body>
</html>
</ClerkProvider>
);
)
: (
<div>
<Loading />
</div>
)
)
}


Expand Down
2 changes: 1 addition & 1 deletion app/(root)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async function Home({

const result: any = await fetchPosts(
searchParams.page ? + searchParams.page : 1,
10,
5,
searchParams.title ? searchParams.title : ""
)

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/search/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async function Page({
userId: user.id,
searchString: searchParams.q,
pageNumber: searchParams?.page ? +searchParams.page : 1,
pageSize: 15,
pageSize: 6,
});


Expand Down
83 changes: 83 additions & 0 deletions components/Chatbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"use client"

import React, { useEffect, useState } from 'react';
import ChatLogs from './cards/ChatLogs';
import { Button } from './ui/button';

const Chatbox = ({ chats }: any) => {
const [page, setPage] = useState(0);
const itemsPerPage = 3;

// Calculate the starting index for slicing the array
const startIndex = page * itemsPerPage;

// Slice the array to get the items for the current page
const slicedChats = chats.slice(startIndex, startIndex + itemsPerPage);

const addNewValue = () => {
setPage((prevState) => {
// Add the new value to the previous state
return prevState + 1;
});
};

const subtractNewValue = () => {
setPage((prevState) => {
// Subtract the new value from the previous state
return Math.max(0, prevState - 1);
});
};

const handleNavigation = (type: string) => {
if (type === 'prev') {
subtractNewValue();
} else if (type === 'next') {
addNewValue();
}
};

return (
<div className="p-4 flex flex-col overflow-y-auto overflow-hidden">
{chats.length === 0 ? (
<p className="text-light-1 text-center">
No Recent Chats, Click on A User Bubble To Get Started, Can Also Search for Users to Message
</p>
) : (
<>
{slicedChats.map((chat: any) => (
<ChatLogs
key={chat.receiver_id} // Add a unique key to each mapped element
chatRead={chat.read_status}
senderID={chat.sender_id}
receiverID={chat.receiver_id}
chatMessages={chat.messages}
receiverPicture={chat.user_image}
chatName={chat.user_username}
isHome={false}
path={'/chat'}
/>
))}
</>
)}
<div className="pagination">
<Button
onClick={() => handleNavigation('prev')}
disabled={page === 0}
className='!text-small-regular text-light-2 bg-primary-500'
>
Prev
</Button>
<p className='text-small-semibold text-light-2'>{page + 1}</p>
<Button
onClick={() => handleNavigation('next')}
disabled={startIndex + itemsPerPage >= chats.length}
className='!text-small-regular text-light-2 bg-primary-500'
>
Next
</Button>
</div>
</div>
);
};

export default Chatbox;
Loading

0 comments on commit af5dce2

Please sign in to comment.