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 : Financial page implementation #1

Merged
merged 3 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
5 changes: 5 additions & 0 deletions app/(root)/payment/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { BillingPage } from "@/components/component/billing-page";

export default function Page() {
return <BillingPage/>;
}
78 changes: 78 additions & 0 deletions components/component/billing-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use client';
import BillingCard from '@/components/ui/BillingCard';
import { financialList } from '@/lib/financial-data';
import {
Dialog,
DialogTrigger,
DialogContent,
DialogOverlay
} from '@/components/ui/dialog';
import { RecaptchaDialog } from './recaptcha-dialog';

export function BillingPage() {
return (
<div className="w-full px-4">
<section className="bg-muted py-4 md:py-10">
<div className="container px-4 md:px-6">
<div className="space-y-4 text-left">
<h1 className="text-3xl font-bold md:text-4xl text-default-700">
Billing connections
</h1>
<p className="text-tremor-content md:text-xl font-medium">
Choose your financial to get started
</p>
</div>
</div>
</section>
<section className="py-3 md:py-5">
<div className="container grid grid-cols-2 gap-6 p-4 sm:grid-cols-3 md:grid-cols-5 md:px-6 overflow-x-auto">
{financialList.map((financialItem) => (
<div
key={financialItem.id}
className="flex flex-col items-center gap-3 shrink-0"
>
<BillingCard
image={financialItem.image}
className="shadow-none border-1 border-default-200"
imageClassName="aspect-square"
bodyClassName="min-h-40 min-w-40"
/>
</div>
))}
</div>
</section>
<section className="bg-muted py-8 md:py-12">
<div className="container px-4 md:px-6 flex flex-col items-center justify-center">
<div className="flex flex-col items-center gap-2">
<p className="text-sm font-bold text-default-700">
Don`&apos;`t see your financial? We support over 14,000 financial
institution.{' '}
<Dialog>
<DialogTrigger asChild>
<span className="underline text-primary-600 cursor-pointer">
Find your financial
</span>
</DialogTrigger>
<DialogOverlay>
<DialogContent
className="sm:max-w-[500px] sm:min-h-[300px] w-full border-2 border-default-300 rounded-small"
onPointerDownOutside={(e) => e.preventDefault()}
>
<RecaptchaDialog />
</DialogContent>
</DialogOverlay>
</Dialog>
</p>
<p className="text-xs font-semibold text-default-600">
Financial import is available with any paid plan. By connecting
your financial you agree to our{' '}
<a href="#" className="underline text-primary-600">
Terms of service
</a>
</p>
</div>
</div>
</section>
</div>
);
}
46 changes: 46 additions & 0 deletions components/component/recaptcha-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use client';

import React from 'react';
import { Button } from '@nextui-org/react';
import { Dialog, DialogTrigger, DialogContent } from '@/components/ui/dialog';
import SearchFinancialDialog from './search-financial-dialog';
import Script from 'next/script';

export function RecaptchaDialog() {
const recaptchaSiteKey = '0x4AAAAAAAdYos5Bq6s-5evG';

return (
<>
<Script
src="http://challenges.cloudflare.com/turnstile/v0/api.js"
strategy="lazyOnload"
/>
<div className="flex flex-col items-center gap-6 py-4">
<div className="text-3xl font-bold text-default-700">
You`&apos;`re not a robot, right?
</div>
<p className="text-md font-medium">
To connect a financial, please confirm you are 100% human.
</p>
<div
className="cf-turnstile"
data-sitekey={recaptchaSiteKey}
data-theme="light"
/>
<Dialog>
<DialogTrigger asChild>
<Button className="bg-success-500 rounded-small font-semibold text-tremor-background px-6">
Continue
</Button>
</DialogTrigger>
<DialogContent
onPointerDownOutside={(e) => e.preventDefault()}
className="sm:max-w-[600px] sm:max-h-[600px] w-full h-full border-2 border-default-300 rounded-small"
>
<SearchFinancialDialog />
</DialogContent>
</Dialog>
</div>
</>
);
}
55 changes: 55 additions & 0 deletions components/component/search-financial-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { ChangeEvent, useState } from 'react';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import BillingCard from '../ui/BillingCard';
import { financialList } from '@/lib/financial-data';
import { Input } from '@nextui-org/react';

const SearchFinancialDialog = () => {
const [input, setInput] = useState('');
const [filteredFinancialData, setFilteredFinancialData] =
useState(financialList);

const onSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
const text = event.target.value;
setInput(text);
setFilteredFinancialData(
financialList.filter((item) =>
item.name.toLowerCase().includes(text.toLowerCase())
)
);
};

return (
<div className="p-4 flex flex-col object-contain">
<div className="relative">
<Input
type="text"
variant="bordered"
placeholder="Search your financial..."
value={input}
onChange={(e) => onSearchChange(e as ChangeEvent<HTMLInputElement>)}
startContent={
<MagnifyingGlassIcon className="w-5 h-5 text-default-300" />
}
/>
</div>
{filteredFinancialData.length === 0 ? (
<div className="p-8 text-center text-default-600 font-semibold">
No financials found.
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 px-4 py-8 overflow-auto max-h-[70vh]">
{filteredFinancialData.map((financialItem) => (
<BillingCard
key={financialItem.id}
image={financialItem.image}
className="shadow-none border-1 border-default-200 rounded-small object-contain flex justify-center items-center min-h-24 p-4"
/>
))}
</div>
)}
</div>
);
};

export default SearchFinancialDialog;
38 changes: 38 additions & 0 deletions components/ui/BillingCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { Card, CardHeader, CardBody, Image } from '@nextui-org/react';
import { cn } from '@/lib/utils';

interface BillingCardProps {
image: string;
className?: string;
imageClassName?: string;
bodyClassName?: string;
}

export default function BillingCard({
image,
className,
imageClassName,
bodyClassName
}: BillingCardProps) {
return (
<Card className={cn('px-2 cursor-pointer rounded-small', className)}>
<CardBody
className={cn(
'flex justify-center items-center p-0 overflow-hidden',
bodyClassName
)}
>
<Image
alt="financial logo"
className={cn(
'object-contain w-full h-full rounded-xl',
imageClassName
)}
src={image}
width={270}
/>
</CardBody>
</Card>
);
}
123 changes: 123 additions & 0 deletions components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
'use client';

import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';

import { cn } from '@/lib/utils';

const Dialog = DialogPrimitive.Root;

const DialogTrigger = DialogPrimitive.Trigger;

const DialogPortal = DialogPrimitive.Portal;

const DialogClose = DialogPrimitive.Close;

const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;

const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay>
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogOverlay>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;

const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-1.5 text-center sm:text-left',
className
)}
{...props}
/>
);
DialogHeader.displayName = 'DialogHeader';

const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className
)}
{...props}
/>
);
DialogFooter.displayName = 'DialogFooter';

const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;

const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;

export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription
};
Loading
Loading