Flatlogic Bot f741ab0364 Base
2026-06-29 19:07:34 +00:00

413 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import BaseIcon from "../components/BaseIcon";
import { mdiInformation, mdiEye, mdiEyeOff } from '@mdi/js';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
import { Field, Form, Formik } from 'formik';
import FormField from '../components/FormField';
import FormCheckRadio from '../components/FormCheckRadio';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import { useRouter } from 'next/router';
import { getPageTitle } from '../config';
import { findMe, loginUser, resetAction } from '../stores/authSlice';
import { useAppDispatch, useAppSelector } from '../stores/hooks';
import Link from 'next/link';
import {toast, ToastContainer} from "react-toastify";
import { getPexelsImage } from '../helpers/pexels'
export default function Login() {
const router = useRouter();
const dispatch = useAppDispatch();
const textColor = useAppSelector((state) => state.style.linkColor);
const iconsColor = useAppSelector((state) => state.style.iconsColor);
const notify = (type, msg) => toast(msg, { type });
const [ illustrationImage, setIllustrationImage ] = useState({
src: undefined,
photographer: undefined,
photographer_url: undefined,
})
const [contentPosition] = useState<'left' | 'right' | 'background'>('left');
const [showPassword, setShowPassword] = useState(false);
const { currentUser, isFetching, errorMessage, token, notify:notifyState } = useAppSelector(
(state) => state.auth,
);
const [initialValues, setInitialValues] = React.useState({ email:'admin@flatlogic.com',
password: 'fc6e39e3',
remember: true })
const title = 'Review Flow'
const appHighlights = [
'Automated review requests after payments, jobs, or service milestones.',
'Customer, business, transaction, and delivery follow-up data in one customer workspace.',
'Dashboards, CRM records, payment events, email logs, and separate internal admin controls already built in.',
];
const competitorAdvantages = [
{
title: 'Built around review operations',
description:
'Review Flow combines CRM records, payments, follow-up, review requests, and reputation workflows in one focused system.',
},
{
title: 'Designed for logistics teams',
description:
'Transportation teams can manage businesses, customers, transactions, payment events, and review requests without jumping tools.',
},
{
title: 'Clear Starter and Pro tiers',
description:
'Starter is $49/month for the core review workflow. Pro is $99/month for higher limits, automation, AI, and reputation marketing tools.',
},
];
const pricingPlans = [
{
name: 'Starter',
price: '$49',
description:
'Best for small teams that need the core Review Flow workflow and simple monthly limits.',
sections: [
{
title: 'Core review workflow',
features: [
'Review Flow workspace for creating, scheduling, and tracking review requests.',
'Manual review request creation and hosted public review forms.',
'Customer, business, transaction, and delivery follow-up records.',
],
},
{
title: 'Starter limits',
features: [
'250 review requests per month.',
'1 business profile.',
'2 team members.',
'Stripe, Square, PayPal, Shopify, and WooCommerce webhook intake.',
],
},
],
},
{
name: 'Pro',
price: '$99',
description:
'Best for growing teams that want higher limits, automation, AI assistance, and reputation marketing tools.',
sections: [
{
title: 'Everything in Starter',
features: [
'2,500 review requests per month.',
'10 business profiles.',
'10 team members.',
'Priority support and advanced reporting.',
],
},
{
title: 'Growth tools',
features: [
'Advanced automation rules.',
'AI review reply assistant.',
'Social proof widgets, referral campaigns, repeat booking reminders, NPS surveys, and broadcasts.',
],
},
],
},
];
// Fetch Pexels image
useEffect( () => {
async function fetchData() {
const image = await getPexelsImage()
setIllustrationImage(image);
}
fetchData();
}, []);
// Fetch user data
useEffect(() => {
if (token) {
dispatch(findMe());
}
}, [token, dispatch]);
// Redirect to dashboard if user is logged in
useEffect(() => {
if (currentUser?.id) {
router.push('/dashboard');
}
}, [currentUser?.id, router]);
// Show error message if there is one
useEffect(() => {
if (errorMessage){
notify('error', errorMessage)
}
}, [errorMessage])
// Show notification if there is one
useEffect(() => {
if (notifyState?.showNotification) {
notify('success', notifyState?.textNotification)
dispatch(resetAction());
}
}, [notifyState?.showNotification])
const togglePasswordVisibility = () => {
setShowPassword(!showPassword);
};
const handleSubmit = async (value) => {
const {remember, ...rest} = value
await dispatch(loginUser(rest));
};
const setLogin = (target: HTMLElement) => {
setInitialValues(prev => ({
...prev,
email : target.innerText.trim(),
password: target.dataset.password ?? '',
}));
};
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>
</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('Login')}</title>
</Head>
<SectionFullScreen bg='violet'>
<div className={`flex ${contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'} min-h-screen w-full`}>
{contentPosition !== 'background' ? imageBlock(illustrationImage) : null}
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
<CardBox id="loginRoles" className='w-full md:w-3/5 lg:w-2/3'>
<h2 className="text-4xl font-semibold my-4">{title}</h2>
<div className='flex flex-row text-gray-500 justify-between'>
<div>
<p className='mb-2'>Use{' '}
<code className={`cursor-pointer ${textColor} `}
data-password="fc6e39e3"
onClick={(e) => setLogin(e.target)}>admin@flatlogic.com</code>{' / '}
<code className={`${textColor}`}>fc6e39e3</code>{' / '}
to login as Internal Admin</p>
<p>Use <code
className={`cursor-pointer ${textColor} `}
data-password="874c3b951385"
onClick={(e) => setLogin(e.target)}>john@doe.com</code>{' / '}
<code className={`${textColor}`}>874c3b951385</code>{' / '}
to login as Customer Owner</p>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w='w-16'
h='h-16'
size={48}
path={mdiInformation}
/>
</div>
</div>
</CardBox>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<Formik
initialValues={initialValues}
enableReinitialize
onSubmit={(values) => handleSubmit(values)}
>
<Form>
<FormField
label='Login'
help='Please enter your login'>
<Field name='email' />
</FormField>
<div className='relative'>
<FormField
label='Password'
help='Please enter your password'>
<Field name='password' type={showPassword ? 'text' : 'password'} />
</FormField>
<div
className='absolute bottom-8 right-0 pr-3 flex items-center cursor-pointer'
onClick={togglePasswordVisibility}
>
<BaseIcon
className='text-gray-500 hover:text-gray-700'
size={20}
path={showPassword ? mdiEyeOff : mdiEye}
/>
</div>
</div>
<div className={'flex justify-between'}>
<FormCheckRadio type='checkbox' label='Remember'>
<Field type='checkbox' name='remember' />
</FormCheckRadio>
<Link className={`${textColor} text-blue-600`} href={'/forgot'}>
Forgot password?
</Link>
</div>
<BaseDivider />
<BaseButtons>
<BaseButton
className={'w-full'}
type='submit'
label={isFetching ? 'Loading...' : 'Login'}
color='info'
disabled={isFetching}
/>
</BaseButtons>
<br />
<p className={'text-center'}>
Dont have an account yet?{' '}
<Link className={`${textColor}`} href={'/register'}>
New Account
</Link>
</p>
</Form>
</Formik>
</CardBox>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<div className='space-y-8'>
<div>
<p className='text-sm font-semibold uppercase tracking-[0.2em] text-blue-600'>About Us</p>
<h3 className='mt-2 text-3xl font-semibold text-gray-900 dark:text-white'>
Review management built for transportation teams.
</h3>
<p className='mt-4 text-base leading-7 text-gray-600 dark:text-slate-300'>
Review Flow helps logistics and transportation businesses turn completed jobs, payments,
and customer interactions into organized review requests. Your team can manage customer
records, monitor follow-up, and keep reputation-building work moving from one secure
workspace.
</p>
</div>
<div className='grid gap-3 md:grid-cols-3'>
{appHighlights.map((highlight) => (
<div
key={highlight}
className='rounded-2xl border border-blue-100 bg-blue-50/70 p-4 text-sm leading-6 text-blue-900 dark:border-dark-700 dark:bg-dark-800 dark:text-slate-200'
>
{highlight}
</div>
))}
</div>
<div>
<h4 className='text-xl font-semibold text-gray-900 dark:text-white'>Why we&apos;re better</h4>
<div className='mt-4 grid gap-4 md:grid-cols-3'>
{competitorAdvantages.map((item) => (
<div key={item.title} className='rounded-2xl border border-gray-200 p-4 dark:border-dark-700'>
<h5 className='font-semibold text-gray-900 dark:text-white'>{item.title}</h5>
<p className='mt-2 text-sm leading-6 text-gray-600 dark:text-slate-300'>
{item.description}
</p>
</div>
))}
</div>
</div>
<div>
<div className='flex flex-col justify-between gap-2 md:flex-row md:items-end'>
<div>
<p className='text-sm font-semibold uppercase tracking-[0.2em] text-blue-600'>Pricing</p>
<h4 className='mt-2 text-2xl font-semibold text-gray-900 dark:text-white'>Simple monthly plans</h4>
</div>
<p className='text-sm text-gray-500 dark:text-slate-400'>Upgrade when your review workflow grows.</p>
</div>
<div className='mt-4 grid gap-4 md:grid-cols-2'>
{pricingPlans.map((plan) => (
<div
key={plan.name}
className='rounded-2xl border border-gray-200 bg-white p-5 shadow-sm dark:border-dark-700 dark:bg-dark-900'
>
<div className='flex items-start justify-between gap-3'>
<div>
<h5 className='text-lg font-semibold text-gray-900 dark:text-white'>{plan.name}</h5>
<p className='mt-1 text-sm leading-6 text-gray-600 dark:text-slate-300'>{plan.description}</p>
</div>
<div className='text-right'>
<span className='text-3xl font-bold text-blue-600'>{plan.price}</span>
<span className='block text-xs text-gray-500 dark:text-slate-400'>/month</span>
</div>
</div>
<div className='mt-5 space-y-5'>
{plan.sections.map((section) => (
<div key={section.title}>
<h6 className='text-sm font-semibold uppercase tracking-wide text-gray-900 dark:text-white'>
{section.title}
</h6>
<ul className='mt-2 space-y-2 text-sm text-gray-700 dark:text-slate-300'>
{section.features.map((feature) => (
<li key={feature} className='flex gap-2'>
<span className='font-semibold text-blue-600'></span>
<span>{feature}</span>
</li>
))}
</ul>
</div>
))}
</div>
</div>
))}
</div>
</div>
</div>
</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>
<ToastContainer />
</div>
);
}
Login.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>;
};