Skip to content

Commit

Permalink
fix: small fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
MatanYadaev committed Feb 12, 2025
1 parent b67d197 commit 63e8d00
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 119 deletions.
21 changes: 11 additions & 10 deletions packages/common/src/schemas/report-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,27 @@ import {

export const FacebookPageSchema = z.object({
id: z.string(),
url: z.string(),
name: z.string(),
url: z.string().nullish(),
name: z.string().nullish(),
email: z.string().nullish(),
likes: z.number(),
likes: z.number().nullish(),
address: z.string().nullish(),
categories: z.array(z.string()).nullish(),
phoneNumber: z.string().nullish(),
creationDate: z.string(),
screenshotUrl: z.string().url(),
creationDate: z.string().nullish(),
screenshotUrl: z.string().url().nullish(),
});

export const InstagramPageSchema = z.object({
id: z.string(),
url: z.string(),
username: z.string(),
username: z.string().nullish(),
biography: z.string().nullish(),
followers: z.number(),
followers: z.number().nullish(),
categories: z.array(z.string()).nullish(),
isVerified: z.boolean(),
screenshotUrl: z.string().url(),
isBusinessProfile: z.boolean(),
isVerified: z.boolean().nullish(),
screenshotUrl: z.string().url().nullish(),
isBusinessProfile: z.boolean().nullish(),
});

export const RiskIndicatorSchema = z
Expand All @@ -45,6 +45,7 @@ export const RiskIndicatorSchema = z
reason: z.string().nullish(),
quoteFromSource: z.string().nullish(),
riskLevel: z.enum(RISK_INDICATOR_RISK_LEVELS).nullish(),
pricingViolationExamples: z.array(z.string()).nullish(),
})
.passthrough();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,31 @@ import {
ThumbsUpIcon,
UsersIcon,
} from 'lucide-react';
import { FunctionComponent, ReactNode } from 'react';
import { ReactNode } from 'react';
import { capitalize, toLowerCase } from 'string-ts';
import { z } from 'zod';
import { FacebookIcon } from './icons/FacebookIcon';
import { InstagramIcon } from './icons/InstagramIcon';
import { ContentTooltip } from '@/components/molecules/ContentTooltip/ContentTooltip';
import { FacebookPageSchema, InstagramPageSchema, ReportSchema } from '@ballerine/common';
import { FacebookPageSchema, InstagramPageSchema } from '@ballerine/common';

const socialMediaMapper: {
facebook: {
icon: ReactNode;
fields: Partial<
Record<keyof z.infer<typeof FacebookPageSchema>, { icon: ReactNode; label: string }>
Record<
keyof z.infer<typeof FacebookPageSchema>,
{ icon: ReactNode; label: string; toDisplay?: (value: unknown) => string }
>
>;
};
instagram: {
icon: ReactNode;
fields: Partial<
Record<keyof z.infer<typeof InstagramPageSchema>, { icon: ReactNode; label: string }>
Record<
keyof z.infer<typeof InstagramPageSchema>,
{ icon: ReactNode; label: string; toDisplay?: (value: unknown) => string }
>
>;
};
} = {
Expand All @@ -57,8 +63,25 @@ const socialMediaMapper: {
isBusinessProfile: {
icon: <BriefcaseIcon className="h-5 w-5 text-gray-500" />,
label: 'Business Profile',
toDisplay: value => {
if (typeof value !== 'boolean') {
return 'N/A';
}

return value ? 'Yes' : 'No';
},
},
isVerified: {
icon: <CheckIcon className="h-5 w-5 text-gray-500" />,
label: 'Verified',
toDisplay: value => {
if (typeof value !== 'boolean') {
return 'N/A';
}

return value ? 'Yes' : 'No';
},
},
isVerified: { icon: <CheckIcon className="h-5 w-5 text-gray-500" />, label: 'Verified' },
followers: { icon: <UsersIcon className="h-5 w-5 text-gray-500" />, label: 'Followers' },
categories: {
icon: <TagIcon className="h-5 w-5 text-gray-500" />,
Expand Down Expand Up @@ -111,6 +134,10 @@ export const AdsAndSocialMedia = (pages: {
{socialMediaMapper[provider].icon}
<h4 className="text-xl">{capitalize(provider)}</h4>
</div>
<div className="my-4 flex items-center gap-2 text-gray-400">
<BanIcon className="h-5 w-5" />
<span className="text-sm">No {capitalize(provider)} profile detected.</span>
</div>
</Card>
);
}
Expand All @@ -126,85 +153,78 @@ export const AdsAndSocialMedia = (pages: {
<h4 className="text-xl">{capitalize(provider)}</h4>
</div>

{page ? (
<div className="flex justify-between gap-4">
<div className="w-2/3 min-w-0 grow-0">
<div className="flex items-center">
<LinkIcon className="h-5 w-5 text-gray-400" />
<a
className={ctw(
buttonVariants({ variant: 'browserLink' }),
'ml-2 p-0 text-base',
)}
href={url}
>
{cleanLink(url)}
</a>
<div className="flex justify-between gap-4">
<div className="w-2/3 min-w-0 grow-0">
<div className="flex items-center">
<LinkIcon className="h-5 w-5 text-gray-400" />
<a
className={ctw(
buttonVariants({ variant: 'browserLink' }),
'ml-2 p-0 text-base',
)}
href={url}
>
{cleanLink(url)}
</a>
</div>
{idValue !== null && (
<span className="text-sm text-gray-400">
{'username' in rest ? `@${idValue}` : `ID ${idValue}`}
</span>
)}

<div className="mt-8 flex gap-6">
<div className="flex flex-col gap-4">
{Object.entries(socialMediaMapper[provider].fields).map(
([, { icon, label }]) => (
<div key={label} className="flex items-center gap-4 whitespace-nowrap">
{icon}
<span className="font-semibold">{label}</span>
</div>
),
)}
</div>
{idValue !== null && (
<span className="text-sm text-gray-400">
{'id' in rest ? `ID ${idValue}` : `@${idValue}`}
</span>
)}

<div className="mt-8 flex gap-6">
<div className="flex flex-col gap-4">
{Object.entries(socialMediaMapper[provider].fields).map(
([, { icon, label }]) => (
<div key={label} className="flex items-center gap-4 whitespace-nowrap">
{icon}
<span className="font-semibold">{label}</span>
</div>
),
)}
</div>

<div className="flex min-w-0 flex-col gap-4">
{Object.entries(socialMediaMapper[provider].fields).map(
([field, { label }]) => {
const value = rest[field as keyof typeof rest];

return (
<TextWithNAFallback
key={label}
className={ctw(
'max-w-full overflow-hidden text-ellipsis',
!value && 'text-gray-400',
label !== 'Biography' && 'whitespace-nowrap',
)}
>
{value}
</TextWithNAFallback>
);
},
)}
</div>

<div className="flex min-w-0 flex-col gap-4">
{Object.entries(socialMediaMapper[provider].fields).map(
([field, { label, toDisplay }]) => {
const value = rest[field as keyof typeof rest];

return (
<TextWithNAFallback
key={label}
className={ctw(
'max-w-full overflow-hidden text-ellipsis',
!value && 'text-gray-400',
label !== 'Biography' && 'whitespace-nowrap',
)}
>
{toDisplay?.(value) ?? value}
</TextWithNAFallback>
);
},
)}
</div>
</div>

<a
className={buttonVariants({
variant: 'link',
className:
'h-[unset] w-1/3 cursor-pointer !p-0 !text-[#14203D] underline decoration-[1.5px]',
})}
href={url}
>
<Image
key={screenshotUrl}
src={screenshotUrl}
alt={`${capitalize(provider)} image`}
role="link"
className="h-auto max-h-96 w-auto"
/>
</a>
</div>
) : (
<div className="my-4 flex items-center gap-2 text-gray-400">
<BanIcon className="h-5 w-5" />
<span className="text-sm">No {capitalize(provider)} profile detected.</span>
</div>
)}

<a
className={buttonVariants({
variant: 'link',
className:
'h-[unset] w-1/3 cursor-pointer !p-0 !text-[#14203D] underline decoration-[1.5px]',
})}
href={url}
>
<Image
key={screenshotUrl}
src={screenshotUrl}
alt={`${capitalize(provider)} image`}
role="link"
className="h-auto max-h-96 w-auto"
/>
</a>
</div>
</Card>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,16 @@ export const WebsiteCredibility: FunctionComponent<{
}) => {
// TODO: Ideally should happen on backend
const trafficSources = useMemo(() => {
const values = Object.entries(trafficData.trafficSources ?? {}).map(([label, value]) => ({
label,
value: Number(value.toFixed(2)),
}));
if (!trafficData.trafficSources?.length) {
return [];
}

const values = Object.entries(trafficData.trafficSources)
.map(([label, value]) => ({
label,
value: Number((value * 100).toFixed(2)),
}))
.sort((a, b) => b.value - a.value);

const remainder = 100 - values.reduce((acc, item) => acc + item.value, 0);

Expand All @@ -93,17 +99,31 @@ export const WebsiteCredibility: FunctionComponent<{
return values;
}, [trafficData.trafficSources]);

const engagements = [
{ label: 'Time on site', value: trafficData.timeOnSite },
{
label: 'Page per visit',
value:
typeof trafficData.pagesPerVisit === 'string'
? parseFloat(trafficData.pagesPerVisit).toFixed(2)
: trafficData.pagesPerVisit,
},
{ label: 'Bounce rate', value: trafficData.bounceRate },
];
const engagements = (
[
{
label: 'Time on site',
value:
typeof trafficData.timeOnSite === 'string'
? parseFloat(trafficData.timeOnSite).toFixed(2)
: trafficData.timeOnSite,
},
{
label: 'Page per visit',
value:
typeof trafficData.pagesPerVisit === 'string'
? parseFloat(trafficData.pagesPerVisit).toFixed(2)
: trafficData.pagesPerVisit,
},
{
label: 'Bounce rate',
value:
typeof trafficData.bounceRate === 'string'
? (parseFloat(trafficData.bounceRate) * 100).toFixed(2)
: trafficData.bounceRate,
},
] as const
).filter(({ value }) => typeof value === 'string');

let minVisitors = 0;
let maxVisitors = 0;
Expand Down Expand Up @@ -269,7 +289,7 @@ export const WebsiteCredibility: FunctionComponent<{
tickLine={false}
axisLine={false}
tickMargin={8}
tickFormatter={value => dayjs(value, 'MMMM YYYY').format('MMM YYYY')}
tickFormatter={value => dayjs(value).format('MMM YYYY')}
/>
<YAxis
ticks={[
Expand Down Expand Up @@ -405,14 +425,15 @@ export const WebsiteCredibility: FunctionComponent<{
{engagements.length > 0 ? (
engagements
.filter(
(obj): obj is { label: string; value: string } =>
typeof obj.value === 'string',
(
obj,
): obj is Readonly<{
value: string;
label: keyof typeof engagementMetricsMapper;
}> => typeof obj.value === 'string',
)
.map(({ label, value }) => {
const { suffix, description, shouldRound } =
engagementMetricsMapper[label as keyof typeof engagementMetricsMapper] ??
{};
const floatValue = parseFloat(value);
const { suffix, description } = engagementMetricsMapper[label];

return (
<div key={label} className="basis-1/3">
Expand All @@ -437,9 +458,7 @@ export const WebsiteCredibility: FunctionComponent<{
</div>

<p>
<span className="font-bold">
{shouldRound ? Math.round(floatValue) : floatValue}
</span>
<span className="font-bold">{value}</span>
<span className={ctw(suffix === '%' && 'font-bold')}>{suffix}</span>
</p>
</div>
Expand Down Expand Up @@ -518,7 +537,11 @@ export const WebsiteCredibility: FunctionComponent<{
})}
>
{!!pricingRiskIndicators?.length &&
pricingRiskIndicators.map(({ reason }) => <li className="list-decimal">{reason}</li>)}
pricingRiskIndicators.map(({ pricingViolationExamples }) =>
pricingViolationExamples?.map(example => (
<li className="list-decimal">{example}</li>
)),
)}
{!pricingRiskIndicators?.length && (
<li>
No indications of suspicious pricing or anomalies in the website’s pricing were
Expand Down
Loading

0 comments on commit 63e8d00

Please sign in to comment.