feat: branded signin-signup

This commit is contained in:
Nevo David 2025-12-18 14:27:57 +07:00
parent f4ce7f74f9
commit 7294cad60b
29 changed files with 433 additions and 181 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -4,7 +4,7 @@ export const dynamic = 'force-dynamic';
import { ReactNode } from 'react';
import Image from 'next/image';
import loadDynamic from 'next/dynamic';
import { Testimonial } from '@gitroom/frontend/components/auth/testimonial';
import { TestimonialComponent } from '@gitroom/frontend/components/auth/testimonial.component';
const ReturnUrlComponent = loadDynamic(() => import('./return.url.component'));
export default async function AuthLayout({
children,
@ -15,55 +15,22 @@ export default async function AuthLayout({
return (
<div className="bg-[#0E0E0E] flex flex-1 p-[12px] gap-[12px] min-h-screen w-screen text-white">
<style>{`html, body {overflow-x: hidden;}`}</style>
{/*<style>{`html, body {overflow-x: hidden;}`}</style>*/}
<ReturnUrlComponent />
<div className="flex flex-col py-[40px] px-[20px] w-[600px] rounded-[12px] text-white p-[12px] bg-[#1A1919]">
<div className="flex flex-col py-[40px] px-[20px] flex-1 lg:w-[600px] lg:flex-none rounded-[12px] text-white p-[12px] bg-[#1A1919]">
<div className="w-full max-w-[440px] mx-auto justify-center gap-[20px] h-full flex flex-col">
<Image width={100} height={33} src="/logo-text.svg" alt="Postiz" />
<div className="flex">{children}</div>
</div>
</div>
<div className="text-[36px] flex-1 pt-[88px] flex flex-col items-center">
<div className="text-[36px] flex-1 pt-[88px] hidden lg:flex flex-col items-center">
<div className="text-center">
Over <span className="text-[42px] text-[#FC69FF]">18,000+</span>{' '}
Entrepreneurs use<br />Postiz
To Grow Their Social Presence
</div>
<div className="flex-1 relative w-full mt-[30px] overflow-hidden">
<div className="absolute gap-[12px] w-full h-full left-0 top-0 overflow-hidden flex justify-center px-[40px]">
<div className="absolute w-full h-[120px] left-0 top-0 blackGradTopBg z-[100]" />
<div className="absolute w-full h-[120px] left-0 bottom-0 blackGradBottomBg z-[100]" />
<div className="flex flex-col h-full animate-marqueeUp gap-[12px] flex-1">
{[1, 2].map((p) => (
<div key={p} className="flex flex-col gap-[12px]">
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
</div>
))}
</div>
<div className="flex flex-col h-full animate-marqueeDown gap-[12px] flex-1">
{[1, 2].map((p) => (
<div key={p} className="flex flex-col gap-[12px]">
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
<Testimonial />
</div>
))}
</div>
</div>
Entrepreneurs use
<br />
Postiz To Grow Their Social Presence
</div>
<TestimonialComponent />
</div>
</div>
);

View File

@ -706,21 +706,21 @@ html[dir='rtl'] [dir='ltr'] {
}
@keyframes marquee-up {
//0% {
// transform: translateY(0);
//}
//100% {
// transform: translateY(-50%);
//}
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
}
@keyframes marquee-down {
//0% {
// transform: translateY(-50%);
//}
//100% {
// transform: translateY(0%);
//}
0% {
transform: translateY(-50%);
}
100% {
transform: translateY(0%);
}
}
.blackGradBottomBg {

View File

@ -5,7 +5,7 @@ import { useT } from '@gitroom/react/translation/get.transation.service.client';
export function Activate() {
const t = useT();
return (
<>
<div className="flex flex-col">
<div>
<h1 className="text-3xl font-bold text-start mb-4 cursor-pointer">
{t('activate_your_account', 'Activate your account')}
@ -19,6 +19,6 @@ export function Activate() {
'Please check your email to activate your account.'
)}
</div>
</>
</div>
);
}

View File

@ -36,52 +36,57 @@ export function Forgot() {
setLoading(false);
};
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<h1 className="text-3xl font-bold text-start mb-4 cursor-pointer">
{t('forgot_password_1', 'Forgot Password')}
</h1>
</div>
{!state ? (
<>
<div className="space-y-4 text-textColor">
<Input
label="Email"
{...form.register('email')}
type="email"
placeholder="Email Address"
/>
</div>
<div className="text-center mt-6">
<div className="w-full flex">
<Button type="submit" className="flex-1" loading={loading}>
{t('send_password_reset_email', 'Send Password Reset Email')}
</Button>
<div className="flex flex-1 flex-col">
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<h1 className="text-3xl font-bold text-start mb-4 cursor-pointer">
{t('forgot_password_1', 'Forgot Password')}
</h1>
</div>
{!state ? (
<>
<div className="space-y-4 text-textColor">
<Input
label="Email"
{...form.register('email')}
type="email"
placeholder="Email Address"
/>
</div>
<div className="text-center mt-6">
<div className="w-full flex">
<Button type="submit" className="flex-1 !h-[52px] !rounded-[10px]" loading={loading}>
{t(
'send_password_reset_email',
'Send Password Reset Email'
)}
</Button>
</div>
<p className="mt-4 text-sm">
<Link href="/auth/login" className="underline cursor-pointer">
{t('go_back_to_login', 'Go back to login')}
</Link>
</p>
</div>
</>
) : (
<>
<div className="text-start mt-6">
{t(
'we_have_send_you_an_email_with_a_link_to_reset_your_password',
'We have send you an email with a link to reset your password.'
)}
</div>
<p className="mt-4 text-sm">
<Link href="/auth/login" className="underline cursor-pointer">
{t('go_back_to_login', 'Go back to login')}
</Link>
</p>
</div>
</>
) : (
<>
<div className="text-start mt-6">
{t(
'we_have_send_you_an_email_with_a_link_to_reset_your_password',
'We have send you an email with a link to reset your password.'
)}
</div>
<p className="mt-4 text-sm">
<Link href="/auth/login" className="underline cursor-pointer">
{t('go_back_to_login', 'Go back to login')}
</Link>
</p>
</>
)}
</form>
</FormProvider>
</>
)}
</form>
</FormProvider>
</div>
);
}

View File

@ -55,70 +55,81 @@ export function Login() {
};
return (
<FormProvider {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<h1 className="text-3xl font-bold text-start mb-4 cursor-pointer">
{t('sign_in', 'Sign In')}
</h1>
</div>
{isGeneral && genericOauth ? (
<OauthProvider />
) : !isGeneral ? (
<GithubProvider />
) : (
<div className="gap-[5px] flex flex-col">
<GoogleProvider />
{!!neynarClientId && <FarcasterProvider />}
{billingEnabled && <WalletProvider />}
<form className="flex-1 flex" onSubmit={form.handleSubmit(onSubmit)}>
<div className="flex flex-col flex-1">
<div>
<h1 className="text-[40px] font-[500] -tracking-[0.8px] text-start cursor-pointer">
{t('sign_in', 'Sign In')}
</h1>
</div>
)}
<div className="h-[20px] mb-[24px] mt-[24px] relative">
<div className="absolute w-full h-[1px] bg-fifth top-[50%] -translate-y-[50%]" />
<div
className={`absolute z-[1] justify-center items-center w-full start-0 top-0 flex`}
>
<div className="bg-customColor15 px-[16px]">{t('or', 'OR')}</div>
<div className="text-[14px] mt-[32px] mb-[12px]">
{t('continue_with', 'Continue With')}
</div>
</div>
<div className="text-textColor">
<Input
label="Email"
translationKey="label_email"
{...form.register('email')}
type="email"
placeholder="Email Address"
/>
<Input
label="Password"
translationKey="label_password"
{...form.register('password')}
autoComplete="off"
type="password"
placeholder="Password"
/>
</div>
<div className="text-center mt-6">
<div className="w-full flex">
<Button
type="submit"
className="flex-1 rounded-[4px]"
loading={loading}
>
{t('sign_in_1', 'Sign in')}
</Button>
<div className="flex flex-col">
{isGeneral && genericOauth ? (
<OauthProvider />
) : !isGeneral ? (
<GithubProvider />
) : (
<div className="gap-[8px] flex">
<GoogleProvider />
{!!neynarClientId && <FarcasterProvider />}
{billingEnabled && <WalletProvider />}
</div>
)}
<div className="h-[20px] mb-[24px] mt-[24px] relative">
<div className="absolute w-full h-[1px] bg-fifth top-[50%] -translate-y-[50%]" />
<div
className={`absolute z-[1] justify-center items-center w-full start-0 -top-[4px] flex`}
>
<div className="px-[16px]">or</div>
</div>
</div>
<div className="flex flex-col gap-[12px]">
<div className="text-textColor">
<Input
label="Email"
translationKey="label_email"
{...form.register('email')}
type="email"
placeholder="Email Address"
/>
<Input
label="Password"
translationKey="label_password"
{...form.register('password')}
autoComplete="off"
type="password"
placeholder="Password"
/>
</div>
<div className="text-center mt-6">
<div className="w-full flex">
<Button
type="submit"
className="flex-1 rounded-[10px] !h-[52px]"
loading={loading}
>
{t('sign_in_1', 'Sign in')}
</Button>
</div>
<p className="mt-4 text-sm">
{t('don_t_have_an_account', "Don't Have An Account?")}&nbsp;
<Link href="/auth" className="underline cursor-pointer">
{t('sign_up', 'Sign Up')}
</Link>
</p>
<p className="mt-4 text-sm">
<Link
href="/auth/forgot"
className="underline hover:font-bold cursor-pointer"
>
{t('forgot_password', 'Forgot password')}
</Link>
</p>
</div>
</div>
</div>
<p className="mt-4 text-sm">
{t('don_t_have_an_account', "Don't Have An Account?")}&nbsp;
<Link href="/auth" className="underline cursor-pointer">
{t('sign_up', 'Sign Up')}
</Link>
</p>
<p className="mt-4 text-sm text-red-600">
<Link href="/auth/forgot" className="underline cursor-pointer">
{t('forgot_password', 'Forgot password')}
</Link>
</p>
</div>
</form>
</FormProvider>

View File

@ -26,7 +26,7 @@ export const ButtonCaster: FC<{
>
<NeynarAuthButton onLogin={login}>
<div
className={`cursor-pointer bg-white h-[52px] flex-1 rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[4px]`}
className={`cursor-pointer bg-white h-[52px] flex-1 rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[10px]`}
>
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -11,7 +11,7 @@ export const GoogleProvider = () => {
return (
<div
onClick={gotoLogin}
className={`cursor-pointer flex-1 bg-white h-[52px] rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[4px]`}
className={`cursor-pointer flex-1 bg-white h-[52px] rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[10px]`}
>
<div>
<svg

View File

@ -5,7 +5,7 @@ export const WalletUiProvider: FC = () => {
const t = useT();
return (
<div
className={`cursor-pointer bg-white flex-1 h-[52px] rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[7px]`}
className={`cursor-pointer bg-white flex-1 h-[52px] rounded-[10px] flex justify-center items-center text-[#0E0E0E] gap-[10px]`}
>
<svg
xmlns="http://www.w3.org/2000/svg"

View File

@ -0,0 +1,44 @@
'use client';
import {
testimonials1,
testimonials2,
} from '@gitroom/react/helpers/testomonials';
import { Testimonial } from '@gitroom/frontend/components/auth/testimonial';
export const TestimonialComponent = () => {
return (
<div className="flex-1 relative w-full my-[30px]">
<div className="absolute w-full h-full left-0 top-0 px-[40px] overflow-hidden">
<div className="absolute w-full h-[120px] left-0 top-0 blackGradTopBg z-[100]" />
<div className="absolute w-full h-[120px] left-0 bottom-0 blackGradBottomBg z-[100]" />
<div className="flex justify-center gap-[12px]">
<div className="flex flex-col animate-marqueeUp flex-1 gap-[12px]">
{[1, 2].flatMap((p) =>
testimonials1.flatMap((a) => (
<div
key={p + '_' + a.name}
className="flex flex-col gap-[12px]"
>
<Testimonial {...a} />
</div>
))
)}
</div>
<div className="flex flex-col animate-marqueeDown flex-1 gap-[12px]">
{[1, 2].flatMap((p) =>
testimonials2.flatMap((a) => (
<div
key={p + '_' + a.name}
className="flex flex-col gap-[12px]"
>
<Testimonial {...a} />
</div>
))
)}
</div>
</div>
</div>
</div>
);
};

View File

@ -1,15 +1,31 @@
export const Testimonial = () => {
import { FC } from 'react';
import Image from 'next/image';
export const Testimonial: FC<{
picture: string;
name: string;
description: string;
content: any;
}> = ({ content, description, name, picture }) => {
return (
<div className="rounded-[16px] w-full flex flex-col gap-[16px] p-[20px] bg-[#1A1919] border border-[#2b2a2a]">
<div className="flex gap-[12px]">
<div className="w-[36px] h-[36px] rounded-full overflow-hidden bg-white" />
<div className="flex-col -mt-[4px]">
<div className="text-[16px] font-[700]">name</div>
<div className="text-[11px] font-[400] text-[#D1D1D1]">description</div>
{/* Header */}
<div className="flex gap-[12px] min-w-0">
<div className="w-[36px] h-[36px] rounded-full overflow-hidden shrink-0">
<Image src={picture} alt={name} width={36} height={36} />
</div>
<div className="flex flex-col -mt-[4px] min-w-0">
<div className="text-[16px] font-[700] truncate">{name}</div>
<div className="text-[11px] font-[400] text-[#D1D1D1]">
{description}
</div>
</div>
</div>
<div className="text-[12px] font-[400] text-[#FFF]">
content
{/* Content */}
<div className="text-[12px] font-[400] text-[#FFF] whitespace-pre-line w-full min-w-0">
{typeof content === 'string' ? content.replace(/\\n/g, '\n') : content}
</div>
</div>
);

View File

@ -116,8 +116,8 @@ module.exports = {
fadeDown: 'fadeDown 4s ease-in-out forwards',
normalFadeDown: 'normalFadeDown 0.5s ease-in-out forwards',
newMessages: 'newMessages 1s ease-in-out 4s forwards',
marqueeUp: 'marquee-up 10s linear infinite',
marqueeDown: 'marquee-down 10s linear infinite',
marqueeUp: 'marquee-up 100s linear infinite',
marqueeDown: 'marquee-down 100s linear infinite',
},
boxShadow: {
yellow: '0 0 60px 20px #6b6237',

View File

@ -1,18 +1,227 @@
export const testomonials1 = [
export const testimonials1 = [
{
picture: '',
name: '',
handle: '',
content: <>Content</>,
picture: '/auth/avatars/vincent.jpg',
name: 'Vincent L.',
description: 'Marketing Coordinator',
content: (
<>
The UI is friendly and the AI content assistant is surprisingly
effective for professional tones. I especially like how it adjusts to
different industries.
</>
),
},
{
picture: '/auth/avatars/dilini.jpeg',
name: 'Dilini R.',
description: 'AI & Tech Consultant',
content: (
<>
I just found out about Postiz, a tool for scheduling social media.{' '}
{'\n'}
Exactly what I wish there was a few years back, I even thought of
building one myself at one point, but didn't have the time to. {'\n'}
What I like about it so far: {'\n\n'}
It connects to LinkedIn, X, Instagram, Facebook (and others) from one
dashboard. {'\n'}
{'\n'}
Because it's open-source, you can see how it works and even tweak it if
you need to. {'\n'}
{'\n'}
I've used a few scheduling tools before and most of them are either
expensive or try to be "all-in-one marketing platforms." {'\n'}
{'\n'}
Postiz seems to focus on just doing one thing well. {'\n'}
{'\n'}
</>
),
},
{
picture: '/auth/avatars/johna.jpg',
name: 'Johannes D.',
description: 'CEO',
content: (
<>
As a privacy-first company we appreciate being able to self-host Postiz!
It brings all the core functionality of a social media scheduler plus a
lot of AI to make things faster. It's also very easy to deploy and use,
great work!
</>
),
},
{
picture: '/auth/avatars/george.jpg',
name: 'George B.',
description: 'Marketing Assistant',
content: (
<>
It's so easy to jump in and start scheduling. I like that I can see all
planned posts at a glance and edit them quickly if needed.
</>
),
},
{
picture: '/auth/avatars/maria.jpg',
name: 'Maria Camila A.',
description: 'Data Analyst',
content: (
<>
Postiz changed how we manage our social media presence by aggregating
our platforms into one effective tool. Post scheduling, and AI post
ideation are two of the many features that come with Postiz, and have
made our management of social media simple and effective! Highly
recommend
</>
),
},
{
picture: '/auth/avatars/bart.jpg',
name: 'Bartolomeo H.',
description: 'CEO',
content: (
<>
It only takes 10 minutes to set up your X scheduling automation. {'\n'}
n8n + Postiz =🔥Never miss a day of posting again: {'\n\n'} Easy to get
started {'\n'} Tutorial video included {'\n'} Automated content
creation {'\n'} Multi-platform publishing {'\n'} Self-hosted (no
monthly fees) {'\n'} Open-source (customize everything) {'\n'}
</>
),
},
{
picture: '/auth/avatars/henry.jpg',
name: 'Henry H.',
description: 'Social Media Coordinator',
content: (
<>
The interface is clean and simple. I love how the AI assistant helps
speed up caption writing without sounding generic. It's really helpful
when I'm on a tight schedule.
</>
),
},
{
picture: '/auth/avatars/andy.jpeg',
name: 'Andy C.',
description: 'AI Specialist',
content: (
<>
Manage all your social media accounts from a single place: Postiz, a
really cool tool I recently discovered :D! {'\n'}
It comes with a bunch of cool tools for posting at specific times,
posting across multiple platforms simultaneously, etc. And all of this
can potentially be self-hosted for free; you just need a small server to
configure everything ;D! It works really well :D!
</>
),
},
];
export const testomonials2 = [
export const testimonials2 = [
{
picture: '',
name: '',
handle: '',
content: <>Content</>,
picture: '/auth/avatars/michael.jpeg',
name: 'Michael H.',
description: 'Senior frontend developer',
content: (
<>
🌟 Exciting news! 🚀 I've just started using Postiz, a fantastic new
tool for scheduling my social media content! {'\n\n'}
Why did I choose Postiz? The ability to self-host it means significant
savings for me! 💰 {'\n\n'}
Postiz is an open-source scheduling tool that allows you to plan and
automate posts across 19+ platforms, including X, LinkedIn, BlueSky, and
Mastodon. {'\n\n'}
With its powerful editor, you can easily connect your accounts, create
rich scheduled posts, and manage multiple channels all in one place.
Plus, it supports image uploads, recurring posts, and timezone-aware
scheduling! 📅 {'\n\n'}
Built with privacy and flexibility in mind, Postiz can run on your own
infrastructure or be used as a hosted service. It's perfect for
individuals, teams, and communities looking for control and automation
without the unnecessary bloat. {'\n\n'}
</>
),
},
{
picture: '/auth/avatars/kiley.jpeg',
name: 'Kiley H.',
description: 'Content Creator',
content: (
<>
The unified dashboard helps me manage Instagram, Facebook, and LinkedIn
from one place. I love that it saves time and keeps our campaigns
aligned across all platforms.
</>
),
},
{
picture: '/auth/avatars/iorn.jpg',
name: 'Iornienge S.',
description: 'Social Media Manager',
content: (
<>
There are several things I love about this suite. Some of these things
include {'\n'}- Ease of use {'\n'}- Helps me organize my social media
accounts {'\n'}- I get work done faster {'\n'}- It does not consume my
time {'\n'}- it has a professional interface {'\n'}
</>
),
},
{
picture: '/auth/avatars/david.jpg',
name: 'David C.',
description: 'Digital Marketing Manager',
content: (
<>
Postiz makes it so easy to plan ahead. The AI suggestions are relevant,
and the platform feels lightweight but powerful
</>
),
},
{
picture: '/auth/avatars/serge.jpeg',
name: 'Serge A.',
description: 'CEO',
content: (
<>
Good tool for social media campaigns. The great thing is that the
platform constantly evolves - new features appear all the time, so I can
follow the latest trends (latest AI developments) without leaving
Postiz.
</>
),
},
{
picture: '/auth/avatars/anica.jpg',
name: 'Anica R.',
description: 'University Applications Specialist',
content: (
<>
It is easy to use, manages your posts simple.It is a helpful tool that
let you organize your content.
</>
),
},
{
picture: '/auth/avatars/josh.jpg',
name: 'Josh W.',
description: 'Content Manager',
content: (
<>
It's super easy to use even if you're not very techy. The AI writing
tool gives good drafts so I don't have to start from scratch every time
</>
),
},
{
picture: '/auth/avatars/vince.jpeg',
name: 'Vince C.',
description: 'Developer Relations Engineer',
content: (
<>
I work in Developer Relations, so having a tool that helps me manage and
crosspost to different platforms saves me so, so, so much time!
</>
),
},
];