Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CTA banners (BAL-3603) #3065

Open
wants to merge 3 commits into
base: bal3607
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export const buttonVariants = cva(
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'underline-offset-4 hover:underline text-primary',
'wp-primary': 'bg-wp-primary text-wp-primary-foreground hover:bg-wp-primary/90',
'wp-outline':
'border border-wp-primary text-wp-primary hover:bg-wp-primary hover:text-wp-primary-foreground',
},
size: {
default: 'h-10 py-2 px-4',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { ctw } from '../../../utils/ctw/ctw';
import React, { ComponentProps } from 'react';

interface IUserAvatarProps extends Omit<ComponentProps<typeof Avatar>, 'src' | 'alt'> {
fullName: string;
avatarUrl: string | undefined;
fullName?: string | null;
avatarUrl?: string | null;
}

export const UserAvatar: React.FC<IUserAvatarProps> = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { ctw } from '@ballerine/ui';
import { t } from 'i18next';
import { ArrowRightIcon } from 'lucide-react';
import { ComponentPropsWithoutRef } from 'react';
import { Link } from 'react-router-dom';

import { Button } from '@/common/components/atoms/Button/Button';
import { UserAvatar } from '@/common/components/atoms/UserAvatar/UserAvatar';
import { env } from '@/common/env/env';
import { useLocale } from '@/common/hooks/useLocale/useLocale';
import { useCustomerQuery } from '@/domains/customer/hooks/queries/useCustomerQuery/useCustomerQuery';
import { getDemoStateErrorText } from './getDemoStateErrorText';

export type ExperienceBallerineCardProps = {
firstName?: string | null;
fullName?: string | null;
avatarUrl?: string | null;
onClick?: () => void;
className?: string;
};

const CreateReportButtonBase = ({
className,
...props
}: ComponentPropsWithoutRef<typeof Button>) => (
<Button
variant="wp-outline"
role="link"
className={ctw('space-x-2 self-start text-base', className)}
{...props}
/>
);

const CreateReportButton = ({
onClick,
locale,
...props
}: Pick<ExperienceBallerineCardProps, 'onClick'> &
ComponentPropsWithoutRef<typeof Button> & {
locale: ReturnType<typeof useLocale>;
}) => {
const buttonContent = (
<>
<span>Create a report</span>
<ArrowRightIcon className="d-4" />
</>
);

if (onClick) {
return (
<CreateReportButtonBase onClick={onClick} {...props}>
{buttonContent}
</CreateReportButtonBase>
);
}

return (
<CreateReportButtonBase asChild {...props}>
<Link to={`/${locale}/merchant-monitoring?isCreating=true`}>{buttonContent}</Link>
</CreateReportButtonBase>
);
};

export const ExperienceBallerineCard = ({
firstName,
fullName,
avatarUrl,
className,
onClick,
}: ExperienceBallerineCardProps) => {
const { data: customer, isLoading } = useCustomerQuery();
const locale = useLocale();

if (env.VITE_ENVIRONMENT_NAME === 'production' || isLoading || !customer?.config?.isDemoAccount) {
return null;
}

const { reportsLeft, demoDaysLeft } = customer?.config?.demoAccessDetails ?? {};
const error = getDemoStateErrorText({ reportsLeft, demoDaysLeft });

return (
<div
className={ctw(
'flex flex-col justify-between gap-2 rounded-md border border-gray-300 bg-white px-6 py-4 shadow-sm',
className,
)}
>
<div className={`flex items-center gap-2`}>
{avatarUrl && <UserAvatar fullName={fullName} className={`!d-7`} avatarUrl={avatarUrl} />}
<h3 className={`text-xl font-semibold`}>
{t(`home.greeting`)}
{firstName && ` ${firstName}`}
</h3>
{!error && <span className="ml-2 text-destructive">{demoDaysLeft} days left</span>}
</div>

<p className="leading-loose">
{error ? (
<>
<span className="text-destructive">{error}</span>
<br />
To continue using the system,{' '}
<span className="font-semibold">book a quick call with us.</span>
</>
) : (
<>
💎 You have <span className="font-semibold">{reportsLeft} Web Presence reports</span>{' '}
available.
<br />
Get started now! 🚀
</>
)}
</p>

<CreateReportButton
className="aria-disabled:pointer-events-none aria-disabled:cursor-not-allowed aria-disabled:opacity-60"
onClick={onClick}
locale={locale}
aria-disabled={!!error}
tabIndex={error ? -1 : 0}
/>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ctw } from '@ballerine/ui';
import { ArrowRightIcon, CrownIcon } from 'lucide-react';

import { env } from '@/common/env/env';
import { useCustomerQuery } from '@/domains/customer/hooks/queries/useCustomerQuery/useCustomerQuery';
import { Button } from '../../atoms/Button/Button';
import dashboardImage from './dashboard.png';

export type GetFullAccessCardProps = {
className?: string;
};

export const GetFullAccessCard = ({ className }: GetFullAccessCardProps) => {
const { data: customer, isLoading } = useCustomerQuery();

if (isLoading || !customer?.config?.isDemoAccount) {
return null;
}

return (
<div
className={ctw(
'relative overflow-hidden rounded-md border border-wp-primary px-6 py-4',
className,
)}
style={{
background:
'linear-gradient(120deg, rgba(88, 78, 197, 0.22) 0%, rgba(255, 255, 255, 0.1) 30%, rgba(255, 255, 255, 0.1) 85%, rgba(88, 78, 197, 0.22) 92%)',
}}
>
<div className="!w-2/3 shrink-0 space-y-4 xl:w-1/2">
<div className="flex items-center gap-2">
<CrownIcon className="rounded-full bg-wp-primary/30 p-[6px] font-bold text-wp-primary d-7" />
<span className="text-lg font-medium">Get Full Access / Learn More</span>
</div>

<p className="leading-relaxed">
Get unlimited access to Ballerine, for smarter onboarding and monitoring decisions.
</p>

<Button asChild variant="wp-primary" className="justify-start space-x-2 text-base">
<a href={env.VITE_BALLERINE_CALENDLY} target="_blank" rel="noreferrer">
<span>Book a quick call</span>
<ArrowRightIcon className="d-4" />
</a>
</Button>
</div>

<div className="absolute -right-16 top-1/3 -z-10">
<img src={dashboardImage} alt="Dashboard image" className="h-full" />
</div>
</div>
);
};
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { isNumber } from 'lodash-es';

export const getDemoStateErrorText = ({
reportsLeft,
demoDaysLeft,
}: {
reportsLeft: number | null | undefined;
demoDaysLeft: number | null | undefined;
}) => {
if (isNumber(demoDaysLeft) && demoDaysLeft <= 0) {
return 'Your trial period has expired.';
}

if (isNumber(reportsLeft) && reportsLeft <= 0) {
return 'You have used all of your Web Presence reports credits.';
}

return null;
};
Loading