This commit is contained in:
Flatlogic Bot 2026-03-02 12:01:37 +00:00
parent 879e1c395c
commit ea6df1d6be
8 changed files with 513 additions and 154 deletions

View File

@ -1,4 +1,3 @@
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
@ -487,6 +486,7 @@ module.exports = class PostsDBApi {
output.author = await posts.getAuthor({
include: [{ model: db.file, as: 'avatar' }],
transaction
});
@ -529,7 +529,7 @@ module.exports = class PostsDBApi {
{
model: db.users,
as: 'author',
include: [{ model: db.file, as: 'avatar' }],
where: filter.author ? {
[Op.or]: [
{ id: { [Op.in]: filter.author.split('|').map(term => Utils.uuid(term)) } },
@ -899,5 +899,4 @@ module.exports = class PostsDBApi {
}
};
};

View File

@ -0,0 +1,75 @@
import React, { useState } from 'react';
import { mdiImageOutline, mdiVideoOutline, mdiEmoticonOutline, mdiSend } from '@mdi/js';
import BaseIcon from '../BaseIcon';
import UserAvatarCurrentUser from '../UserAvatarCurrentUser';
import { useAppDispatch } from '../../stores/hooks';
import { create as createPost } from '../../stores/posts/postsSlice';
import BaseButton from '../BaseButton';
const CreatePost = ({ onPostCreated }: { onPostCreated: () => void }) => {
const dispatch = useAppDispatch();
const [caption, setCaption] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
if (!caption.trim()) return;
setLoading(true);
try {
await dispatch(createPost({
caption,
post_type: 'text',
visibility: 'public',
published_at: new Date().toISOString()
}));
setCaption('');
onPostCreated();
} catch (error) {
console.error('Failed to create post', error);
} finally {
setLoading(false);
}
};
return (
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-sm p-4 mb-6 border border-gray-100 dark:border-dark-700">
<div className="flex items-start space-x-3">
<UserAvatarCurrentUser className="w-10 h-10 mt-1" />
<div className="flex-1">
<textarea
className="w-full bg-gray-50 dark:bg-dark-900 border-none rounded-lg p-3 text-sm focus:ring-2 focus:ring-indigo-500 transition-shadow resize-none min-h-[100px]"
placeholder="What's on your mind?"
value={caption}
onChange={(e) => setCaption(e.target.value)}
/>
<div className="flex items-center justify-between mt-3">
<div className="flex items-center space-x-4">
<button className="text-gray-500 hover:text-indigo-500 transition-colors p-1 rounded-full hover:bg-gray-100 dark:hover:bg-dark-700">
<BaseIcon path={mdiImageOutline} size="22" />
</button>
<button className="text-gray-500 hover:text-rose-500 transition-colors p-1 rounded-full hover:bg-gray-100 dark:hover:bg-dark-700">
<BaseIcon path={mdiVideoOutline} size="22" />
</button>
<button className="text-gray-500 hover:text-yellow-500 transition-colors p-1 rounded-full hover:bg-gray-100 dark:hover:bg-dark-700">
<BaseIcon path={mdiEmoticonOutline} size="22" />
</button>
</div>
<BaseButton
label="Post"
color="info"
icon={mdiSend}
small
disabled={!caption.trim() || loading}
onClick={handleSubmit}
loading={loading}
/>
</div>
</div>
</div>
</div>
);
};
export default CreatePost;

View File

@ -0,0 +1,119 @@
import React, { useState } from 'react';
import { mdiHeart, mdiHeartOutline, mdiCommentOutline, mdiShareVariantOutline } from '@mdi/js';
import BaseIcon from '../BaseIcon';
import UserAvatar from '../UserAvatar';
import ImageField from '../ImageField';
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
import { create as createReaction } from '../../stores/reactions/reactionsSlice';
import { update as updatePost } from '../../stores/posts/postsSlice';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
type Props = {
post: any;
};
const PostCard = ({ post }: Props) => {
const dispatch = useAppDispatch();
const { currentUser } = useAppSelector((state) => state.auth);
const [isLiked, setIsLiked] = useState(false); // Simple local state for demo
const [likeCount, setLikeCount] = useState(post.like_count || 0);
const handleLike = async () => {
if (isLiked) return;
setIsLiked(true);
setLikeCount(likeCount + 1);
try {
await dispatch(createReaction({
target_type: 'post',
reaction_type: 'like',
postId: post.id,
userId: currentUser?.id,
reacted_at: new Date().toISOString()
}));
// Update post like count in DB
await dispatch(updatePost({
id: post.id,
data: {
like_count: likeCount + 1
}
}));
} catch (error) {
console.error('Failed to like post', error);
setIsLiked(false);
setLikeCount(likeCount);
}
};
return (
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-sm mb-6 border border-gray-100 dark:border-dark-700 overflow-hidden">
{/* Header */}
<div className="flex items-center p-4">
<UserAvatar
username={post.author?.firstName || 'User'}
image={post.author?.avatar}
className="w-10 h-10 mr-3"
/>
<div>
<h4 className="font-bold text-sm dark:text-white hover:underline cursor-pointer">
{post.author?.firstName} {post.author?.lastName}
</h4>
<p className="text-xs text-gray-500 dark:text-dark-600">
{dayjs(post.createdAt).fromNow()}
</p>
</div>
</div>
{/* Content */}
<div className="px-4 pb-3 text-sm dark:text-gray-200">
<p>{post.caption}</p>
</div>
{/* Image */}
{post.media_images && post.media_images.length > 0 && (
<div className="w-full">
<ImageField
name="post-image"
image={post.media_images}
className="w-full h-auto max-h-[500px] overflow-hidden bg-gray-100 dark:bg-dark-900"
imageClassName="w-full h-full object-cover"
/>
</div>
)}
{/* Actions */}
<div className="p-4 flex items-center justify-between border-t border-gray-50 dark:border-dark-700">
<div className="flex items-center space-x-6">
<button
onClick={handleLike}
className={`flex items-center space-x-2 transition-colors ${isLiked ? 'text-rose-500' : 'text-gray-500 dark:text-dark-600 hover:text-rose-500'}`}
>
<BaseIcon path={isLiked ? mdiHeart : mdiHeartOutline} size="20" />
<span className="text-xs font-semibold">{likeCount}</span>
</button>
<button className="flex items-center space-x-2 text-gray-500 dark:text-dark-600 hover:text-indigo-500 transition-colors">
<BaseIcon path={mdiCommentOutline} size="20" />
<span className="text-xs font-semibold">{post.comment_count || 0}</span>
</button>
<button className="flex items-center space-x-2 text-gray-500 dark:text-dark-600 hover:text-green-500 transition-colors">
<BaseIcon path={mdiShareVariantOutline} size="20" />
<span className="text-xs font-semibold">{post.share_count || 0}</span>
</button>
</div>
<div className="text-xs text-gray-400">
{post.view_count || 0} views
</div>
</div>
</div>
);
};
export default PostCard;

View File

@ -0,0 +1,92 @@
import React from 'react';
import { mdiPound, mdiAccountPlusOutline } from '@mdi/js';
import BaseIcon from '../BaseIcon';
const TrendingBar = () => {
const trending = [
{ tag: 'reactjs', count: '12.4k' },
{ tag: 'tailwindcss', count: '8.2k' },
{ tag: 'socialmedia', count: '5.6k' },
{ tag: 'nextjs', count: '4.1k' },
{ tag: 'webdev', count: '3.9k' },
];
const suggestions = [
{ name: 'John Doe', handle: '@johndoe', avatar: 'JD' },
{ name: 'Jane Smith', handle: '@janesmith', avatar: 'JS' },
{ name: 'Alex Rivera', handle: '@arivera', avatar: 'AR' },
];
return (
<div className="hidden lg:block w-80 space-y-6 shrink-0 h-screen sticky top-16 overflow-y-auto aside-scrollbars-[slate] dark:aside-scrollbars-dark">
{/* Trending Section */}
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-sm border border-gray-100 dark:border-dark-700 p-5">
<h3 className="font-bold text-lg mb-4 dark:text-white">Trending for you</h3>
<div className="space-y-4">
{trending.map((item) => (
<div key={item.tag} className="flex items-center justify-between group cursor-pointer">
<div className="flex items-center space-x-3">
<div className="bg-gray-100 dark:bg-dark-900 p-2 rounded-lg text-gray-500 group-hover:bg-indigo-50 group-hover:text-indigo-600 transition-colors">
<BaseIcon path={mdiPound} size="18" />
</div>
<div>
<h4 className="text-sm font-semibold dark:text-gray-200 group-hover:text-indigo-600 transition-colors">
#{item.tag}
</h4>
<p className="text-xs text-gray-500 dark:text-dark-600">
{item.count} posts
</p>
</div>
</div>
</div>
))}
</div>
<button className="w-full mt-6 py-2 text-sm font-semibold text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 rounded-lg transition-colors">
Show more
</button>
</div>
{/* Suggested Users */}
<div className="bg-white dark:bg-dark-800 rounded-xl shadow-sm border border-gray-100 dark:border-dark-700 p-5">
<h3 className="font-bold text-lg mb-4 dark:text-white">Who to follow</h3>
<div className="space-y-4">
{suggestions.map((user) => (
<div key={user.handle} className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 rounded-full bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 font-bold text-sm">
{user.avatar}
</div>
<div>
<h4 className="text-sm font-semibold dark:text-gray-200 leading-tight">
{user.name}
</h4>
<p className="text-xs text-gray-500 dark:text-dark-600 leading-tight">
{user.handle}
</p>
</div>
</div>
<button className="text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 p-2 rounded-full transition-colors">
<BaseIcon path={mdiAccountPlusOutline} size="20" />
</button>
</div>
))}
</div>
<button className="w-full mt-6 py-2 text-sm font-semibold text-indigo-600 hover:bg-indigo-50 dark:hover:bg-indigo-900/20 rounded-lg transition-colors">
Show more
</button>
</div>
{/* Footer Links */}
<div className="px-5 text-xs text-gray-500 dark:text-dark-600 flex flex-wrap gap-x-4 gap-y-2">
<span>Terms of Service</span>
<span>Privacy Policy</span>
<span>Cookie Policy</span>
<span>Accessibility</span>
<span>Ads info</span>
<span>© 2026 Socio</span>
</div>
</div>
);
};
export default TrendingBar;

View File

@ -58,7 +58,18 @@ body {
}
.introjs-bullets ul li a.active {
@apply bg-midnightBlueTheme-iconsColor !important;
text-shadow: none !important;
}
.introjs-prevbutton{
@apply bg-transparent border border-midnightBlueTheme-buttonColor text-midnightBlueTheme-buttonColor !important;
}
@layer utilities {
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
}

View File

@ -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,
@ -216,4 +221,4 @@ const menuAside: MenuAsideItem[] = [
},
]
export default menuAside
export default menuAside

View File

@ -0,0 +1,99 @@
import React, { useEffect, useState } 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 PostCard from '../components/Socio/PostCard';
import CreatePost from '../components/Socio/CreatePost';
import TrendingBar from '../components/Socio/TrendingBar';
import LoadingSpinner from '../components/LoadingSpinner';
import UserAvatar from '../components/UserAvatar';
const FeedPage = () => {
const dispatch = useAppDispatch();
const { posts, loading } = useAppSelector((state) => state.posts);
const { currentUser } = useAppSelector((state) => state.auth);
const [refetch, setRefetch] = useState(0);
useEffect(() => {
dispatch(fetchPosts({ query: '?limit=10&page=0' }));
}, [dispatch, refetch]);
const stories = [
{ id: 1, name: 'Your Story', avatar: 'Me', isMe: true },
{ id: 2, name: 'Alice', avatar: 'AL' },
{ id: 3, name: 'Bob', avatar: 'BO' },
{ id: 4, name: 'Charlie', avatar: 'CH' },
{ id: 5, name: 'David', avatar: 'DA' },
{ id: 6, name: 'Eve', avatar: 'EV' },
];
return (
<>
<Head>
<title>{getPageTitle('Home Feed')}</title>
</Head>
<SectionMain>
<div className="flex flex-col lg:flex-row gap-6 max-w-6xl mx-auto">
{/* Main Feed Content */}
<div className="flex-1 max-w-2xl mx-auto w-full">
{/* Stories Bar */}
<div className="flex items-center space-x-4 mb-8 overflow-x-auto pb-4 no-scrollbar">
{stories.map((story) => (
<div key={story.id} className="flex flex-col items-center flex-shrink-0 cursor-pointer group">
<div className={`w-16 h-16 rounded-full p-[2px] mb-1 ring-2 ${story.isMe ? 'ring-indigo-200 dark:ring-indigo-900' : 'ring-indigo-500'}`}>
<div className="w-full h-full rounded-full border-2 border-white dark:border-dark-800 overflow-hidden bg-gray-100 dark:bg-dark-900 flex items-center justify-center font-bold text-indigo-600">
{story.isMe && currentUser ? (
<UserAvatar username={currentUser.firstName} className="w-full h-full" />
) : (
story.avatar
)}
</div>
</div>
<span className="text-[10px] font-semibold text-gray-600 dark:text-dark-600 group-hover:text-indigo-600 transition-colors">
{story.name}
</span>
</div>
))}
</div>
{/* Create Post Widget */}
<CreatePost onPostCreated={() => setRefetch(prev => prev + 1)} />
{/* Posts List */}
{loading && !posts.length ? (
<LoadingSpinner />
) : (
<div className="space-y-6">
{posts.map((post: any) => (
<PostCard key={post.id} post={post} />
))}
{!loading && posts.length === 0 && (
<div className="text-center py-20 bg-white dark:bg-dark-800 rounded-xl shadow-sm border border-gray-100 dark:border-dark-700">
<p className="text-gray-500">No posts yet. Be the first to post something!</p>
</div>
)}
</div>
)}
</div>
{/* Right Sidebar - Trending & Suggestions */}
<TrendingBar />
</div>
</SectionMain>
</>
);
};
FeedPage.getLayout = function getLayout(page: ReactElement) {
return <LayoutAuthenticated>{page}</LayoutAuthenticated>;
};
export default FeedPage;

View File

@ -1,166 +1,125 @@
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 { mdiArrowRight, mdiPlayCircleOutline } from '@mdi/js';
import BaseIcon from '../components/BaseIcon';
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');
export default function SocioLanding() {
const textColor = useAppSelector((state) => state.style.linkColor);
const title = 'Socio';
const title = 'Socio Social App'
return (
<div className="min-h-screen bg-slate-50 dark:bg-dark-900 overflow-hidden">
<Head>
<title>{getPageTitle('Welcome to Socio')}</title>
</Head>
// Fetch Pexels image/video
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
const video = await getPexelsVideo();
setIllustrationImage(image);
setIllustrationVideo(video);
}
fetchData();
}, []);
{/* Navigation */}
<nav className="flex items-center justify-between px-6 py-4 max-w-7xl mx-auto">
<div className="flex items-center space-x-2">
<div className="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center text-white font-black text-2xl italic shadow-lg shadow-indigo-500/30">
S
</div>
<span className="text-2xl font-black tracking-tight dark:text-white uppercase">ocio</span>
</div>
<div className="flex items-center space-x-6">
<Link href="/login" className="text-sm font-bold text-gray-600 dark:text-gray-300 hover:text-indigo-600 transition-colors">
Sign in
</Link>
<BaseButton
href="/register"
label="Join Socio"
color="info"
className="rounded-xl font-bold shadow-md shadow-indigo-500/20"
/>
</div>
</nav>
const imageBlock = (image) => (
<div
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
style={{
backgroundImage: `${
image
? `url(${image?.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}}
>
<div className='flex justify-center w-full bg-blue-300/20'>
<a
className='text-[8px]'
href={image?.photographer_url}
target='_blank'
rel='noreferrer'
>
Photo by {image?.photographer} on Pexels
</a>
</div>
{/* Hero Section */}
<main className="max-w-7xl mx-auto px-6 pt-12 lg:pt-24 flex flex-col lg:flex-row items-center">
<div className="lg:w-1/2 space-y-8 text-center lg:text-left z-10">
<div className="inline-flex items-center px-4 py-2 bg-indigo-50 dark:bg-indigo-900/30 text-indigo-600 dark:text-indigo-400 rounded-full text-xs font-bold tracking-widest uppercase">
Connect, Share, and Discover
</div>
<h1 className="text-5xl lg:text-7xl font-black text-slate-900 dark:text-white leading-[1.1]">
The Social Network for <span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-violet-600 italic">Tomorrow.</span>
</h1>
<p className="text-lg text-slate-600 dark:text-slate-400 max-w-xl mx-auto lg:mx-0">
Socio brings your friends, interests, and world together in one place. Experience the next generation of social interaction.
</p>
<div className="flex flex-col sm:flex-row items-center justify-center lg:justify-start space-y-4 sm:space-y-0 sm:space-x-4">
<BaseButton
href="/register"
label="Start Creating"
color="info"
icon={mdiArrowRight}
className="w-full sm:w-auto px-8 py-4 rounded-2xl text-lg font-bold shadow-xl shadow-indigo-500/30"
/>
<button className="flex items-center space-x-2 px-8 py-4 text-slate-600 dark:text-slate-300 font-bold hover:text-indigo-600 transition-colors group">
<BaseIcon path={mdiPlayCircleOutline} size="24" className="group-hover:scale-110 transition-transform" />
<span>Watch Demo</span>
</button>
</div>
<div className="flex items-center justify-center lg:justify-start space-x-8 pt-8 opacity-60 grayscale hover:grayscale-0 transition-all">
<div className="text-2xl font-black italic">FACEBOOK</div>
<div className="text-2xl font-black italic">INSTAGRAM</div>
<div className="text-2xl font-black italic">TIKTOK</div>
</div>
</div>
{/* Decorative Visuals */}
<div className="lg:w-1/2 mt-12 lg:mt-0 relative">
<div className="relative z-10 transform lg:rotate-2 lg:translate-x-12 hover:rotate-0 transition-transform duration-700">
<div className="bg-white dark:bg-dark-800 p-4 rounded-[2.5rem] shadow-2xl shadow-indigo-500/20 border border-slate-100 dark:border-dark-700 w-full max-w-[400px] mx-auto overflow-hidden">
<div className="h-64 bg-slate-100 dark:bg-dark-900 rounded-[2rem] mb-4 overflow-hidden relative group">
<img
src="https://images.pexels.com/photos/3194521/pexels-photo-3194521.jpeg?auto=compress&cs=tinysrgb&w=800"
alt="Sample content"
className="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent flex items-end p-6">
<p className="text-white font-bold text-lg">Experience more together.</p>
</div>
</div>
<div className="flex items-center justify-between px-2 mb-4">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 rounded-full bg-indigo-100 dark:bg-indigo-900/50 flex items-center justify-center text-indigo-600 font-bold">A</div>
<div>
<div className="h-2 w-24 bg-slate-100 dark:bg-dark-900 rounded mb-2"></div>
<div className="h-2 w-16 bg-slate-100 dark:bg-dark-900 rounded"></div>
</div>
</div>
<div className="w-8 h-8 rounded-full bg-rose-50 dark:bg-rose-900/20 flex items-center justify-center text-rose-500"></div>
</div>
</div>
</div>
{/* Background Blobs */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[120%] h-[120%] bg-indigo-500/10 dark:bg-indigo-500/5 blur-[100px] rounded-full z-0"></div>
</div>
</main>
{/* Footer */}
<footer className="mt-24 border-t border-slate-100 dark:border-dark-800 py-12 px-6">
<div className="max-w-7xl mx-auto flex flex-col md:flex-row items-center justify-between space-y-6 md:space-y-0">
<p className="text-slate-500 text-sm">© 2026 Socio App. All rights reserved.</p>
<div className="flex items-center space-x-8 text-sm font-semibold text-slate-500">
<Link href="/privacy-policy" className="hover:text-indigo-600 transition-colors">Privacy</Link>
<Link href="/terms" className="hover:text-indigo-600 transition-colors">Terms</Link>
<Link href="/login" className="hover:text-indigo-600 transition-colors">Login to Admin</Link>
</div>
</div>
</footer>
</div>
);
const videoBlock = (video) => {
if (video?.video_files?.length > 0) {
return (
<div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
<video
className='absolute top-0 left-0 w-full h-full object-cover'
autoPlay
loop
muted
>
<source src={video?.video_files[0]?.link} type='video/mp4'/>
Your browser does not support the video tag.
</video>
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
<a
className='text-[8px]'
href={video?.user?.url}
target='_blank'
rel='noreferrer'
>
Video by {video.user.name} on Pexels
</a>
</div>
</div>)
}
};
return (
<div
style={
contentPosition === 'background'
? {
backgroundImage: `${
illustrationImage
? `url(${illustrationImage.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}
: {}
}
>
<Head>
<title>{getPageTitle('Starter Page')}</title>
</Head>
<SectionFullScreen bg='violet'>
<div
className={`flex ${
contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
} min-h-screen w-full`}
>
{contentType === 'image' && contentPosition !== 'background'
? imageBlock(illustrationImage)
: null}
{contentType === 'video' && contentPosition !== 'background'
? videoBlock(illustrationVideo)
: null}
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<CardBoxComponentTitle title="Welcome to your Socio Social App app!"/>
<div className="space-y-3">
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
<p className='text-center '>For guides and documentation please check
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
</div>
<BaseButtons>
<BaseButton
href='/login'
label='Login'
color='info'
className='w-full'
/>
</BaseButtons>
</CardBox>
</div>
</div>
</SectionFullScreen>
<div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
<p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
<Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
Privacy Policy
</Link>
</div>
</div>
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};
SocioLanding.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};