From b6268d4dff7f49024356f39a0fe9901439cd54d9 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 27 Feb 2026 19:35:50 +0000 Subject: [PATCH] 1 --- frontend/src/components/NavBarItem.tsx | 5 +- .../src/components/Posts/SocialPostCard.tsx | 88 ++++++ frontend/src/layouts/Authenticated.tsx | 5 +- frontend/src/menuAside.ts | 7 +- frontend/src/pages/feed.tsx | 61 ++++ frontend/src/pages/index.tsx | 292 +++++++++--------- 6 files changed, 308 insertions(+), 150 deletions(-) create mode 100644 frontend/src/components/Posts/SocialPostCard.tsx create mode 100644 frontend/src/pages/feed.tsx diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx index eb155e3..1986306 100644 --- a/frontend/src/components/NavBarItem.tsx +++ b/frontend/src/components/NavBarItem.tsx @@ -1,6 +1,5 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' import Link from 'next/link' -import { useState } from 'react' import { mdiChevronUp, mdiChevronDown } from '@mdi/js' import BaseDivider from './BaseDivider' import BaseIcon from './BaseIcon' @@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) { } return
{NavBarItemComponentContents}
-} +} \ No newline at end of file diff --git a/frontend/src/components/Posts/SocialPostCard.tsx b/frontend/src/components/Posts/SocialPostCard.tsx new file mode 100644 index 0000000..2393000 --- /dev/null +++ b/frontend/src/components/Posts/SocialPostCard.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { mdiHeart, mdiHeartOutline, mdiChatOutline, mdiSendOutline, mdiBookmarkOutline } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; +import UserAvatar from '../UserAvatar'; +import dataFormatter from '../../helpers/dataFormatter'; +import Link from 'next/link'; + +type Props = { + post: any; + onLike?: (id: string) => void; + onComment?: (id: string) => void; +}; + +export default function SocialPostCard({ post, onLike, onComment }: Props) { + const imageUrl = post.images && post.images[0] ? post.images[0].publicUrl : null; + const authorName = post.author ? `${post.author.firstName} ${post.author.lastName}` : 'Anonymous'; + const authorAvatar = post.author ? post.author.avatar : null; + + return ( +
+ {/* Header */} +
+
+ +
+

{authorName}

+ {post.location_name &&

{post.location_name}

} +
+
+ +
+ + {/* Image/Video */} +
+ {imageUrl ? ( + Post content + ) : ( +
No media
+ )} +
+ + {/* Interactions */} +
+
+
+ + + +
+ +
+ + {/* Likes Count */} +

+ {post.post_reactions_count || 0} likes +

+ + {/* Caption */} +
+ {authorName} + {post.caption} +
+ + {/* View Comments */} + {post.post_comments_count > 0 && ( + + )} + + {/* Time */} +

+ {dataFormatter.dateTimeFormatter(post.published_at)} +

+
+
+ ); +} diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -126,4 +125,4 @@ export default function LayoutAuthenticated({ ) -} +} \ No newline at end of file diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 58459f5..21e7bf0 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -2,6 +2,11 @@ import * as icon from '@mdi/js'; import { MenuAsideItem } from './interfaces' const menuAside: MenuAsideItem[] = [ + { + href: '/feed', + icon: icon.mdiHomeOutline, + label: 'Feed', + }, { href: '/dashboard', icon: icon.mdiViewDashboardOutline, @@ -232,4 +237,4 @@ const menuAside: MenuAsideItem[] = [ }, ] -export default menuAside +export default menuAside \ No newline at end of file diff --git a/frontend/src/pages/feed.tsx b/frontend/src/pages/feed.tsx new file mode 100644 index 0000000..e6c1ae1 --- /dev/null +++ b/frontend/src/pages/feed.tsx @@ -0,0 +1,61 @@ +import React, { useEffect } from 'react'; +import type { ReactElement } from 'react'; +import Head from 'next/head'; +import LayoutAuthenticated from '../layouts/Authenticated'; +import SectionMain from '../components/SectionMain'; +import { getPageTitle } from '../config'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { fetch as fetchPosts } from '../stores/posts/postsSlice'; +import SocialPostCard from '../components/Posts/SocialPostCard'; +import LoadingSpinner from '../components/LoadingSpinner'; + +export default function FeedPage() { + const dispatch = useAppDispatch(); + const { posts, loading } = useAppSelector((state) => state.posts); + + useEffect(() => { + dispatch(fetchPosts({ page: 1, limit: 10 })); + }, [dispatch]); + + return ( + <> + + {getPageTitle('Feed')} + + +
+

Your Feed

+ + {loading && ( +
+ +
+ )} + + {!loading && posts && Array.isArray(posts) && posts.length > 0 && ( +
+ {posts.map((post: any) => ( + console.log('Like', id)} + onComment={(id) => console.log('Comment', id)} + /> + ))} +
+ )} + + {!loading && (!posts || (Array.isArray(posts) && posts.length === 0)) && ( +
+

No posts yet. Be the first to share something!

+
+ )} +
+
+ + ); +} + +FeedPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; \ No newline at end of file diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 4330860..f1a17fe 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -1,166 +1,172 @@ - import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; import SectionFullScreen from '../components/SectionFullScreen'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import { getPexelsImage } from '../helpers/pexels'; +import BaseIcon from '../components/BaseIcon'; +import { mdiInstagram, mdiLightningBolt, mdiEarth, mdiAccountGroup } from '@mdi/js'; +export default function LandingPage() { + const [illustrationImage, setIllustrationImage] = useState(null); + const { currentUser } = useAppSelector((state) => state.auth); -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('image'); - const [contentPosition, setContentPosition] = useState('left'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'Ultra Sosyal' - - // Fetch Pexels image/video useEffect(() => { async function fetchData() { const image = await getPexelsImage(); - const video = await getPexelsVideo(); setIllustrationImage(image); - setIllustrationVideo(video); } fetchData(); }, []); - const imageBlock = (image) => ( -
-
- - Photo by {image?.photographer} on Pexels - -
+ return ( +
+ + {getPageTitle('Ultra Sosyal - Connect. Share. Shine.')} + + + {/* Neon Glow Accents */} +
+
+ + +
+ {/* Header/Nav */} + + + {/* Hero Section */} +
+
+
+ + THE NEXT GENERATION OF SOCIAL +
+

+ Share your
+ + Digital Soul. + +

+

+ Experience a borderless world of connection. Posts, Stories, Reels, and real-time interaction, all in a stunning neon aesthetic. +

+
+ +
+ {[1, 2, 3, 4].map((i) => ( +
+ U{i} +
+ ))} +
+ Joined by 20k+ creators +
+
+
+
+ + {/* Interactive Card Mockup */} +
+
+
+ {illustrationImage && ( + Mockup + )} +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ + {/* Features Grid */} +
+ {[ + { icon: mdiEarth, title: "Global Reach", desc: "Connect with people from every corner of the world instantly." }, + { icon: mdiLightningBolt, title: "Hyper Fast", desc: "Built for speed. Real-time notifications and instant uploads." }, + { icon: mdiAccountGroup, title: "Community Driven", desc: "Groups, forums, and shared collections to grow together." } + ].map((f, i) => ( +
+
+ +
+

{f.title}

+

{f.desc}

+
+ ))} +
+ + {/* Footer */} +
+

© 2026 ULTRA SOSYAL. Built for the bold.

+
+ Privacy + Terms + Support +
+
+
+
); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -
- -
- - Video by {video.user.name} on Pexels - -
-
) - } - }; - - return ( -
- - {getPageTitle('Starter Page')} - - - -
- {contentType === 'image' && contentPosition !== 'background' - ? imageBlock(illustrationImage) - : null} - {contentType === 'video' && contentPosition !== 'background' - ? videoBlock(illustrationVideo) - : null} -
- - - -
-

This is a React.js/Node.js app generated by the Flatlogic Web App Generator

-

For guides and documentation please check - your local README.md and the Flatlogic documentation

-
- - - - - -
-
-
-
-
-

© 2026 {title}. All rights reserved

- - Privacy Policy - -
- -
- ); } -Starter.getLayout = function getLayout(page: ReactElement) { - return {page}; -}; - +LandingPage.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; \ No newline at end of file