Production Build Working

This commit is contained in:
Jason Jordan
2026-09-16 12:39:54 -04:00
parent 8b3bb209b3
commit ead18a4709
96 changed files with 3473 additions and 2144 deletions
+9 -3
View File
@@ -1,3 +1,9 @@
node_modules/
.env
.git
node_modules
.next
out
build
.git
.gitignore
.env*
.local
README.md
+43
View File
@@ -0,0 +1,43 @@
# Stage 1: Base image
FROM node:22-alpine AS base
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=$NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
# Stage 2: Install dependencies
FROM base AS deps
# Check if libc6-compat is needed for Alpine
RUN apk add --no-cache libc6-compat
COPY package.json package-lock.json ./
# Clean install exact production and dev dependencies required for the build
RUN npm ci
# Stage 3: Build the application
FROM base AS builder
# Copy node_modules from the deps stage
COPY --from=deps /app/node_modules ./node_modules
# Copy all project source code ( Tailwind v4 scans these files )
COPY . .
# Execute the production Next.js build
RUN npm run build
# Stage 4: Production runner
FROM base AS runner
ENV NODE_ENV=production
ENV PORT=3001
ENV HOSTNAME="0.0.0.0"
# Create a non-root system user for security
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
# Copy standalone build assets and public files
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3001
CMD ["node", "server.js"]
+5
View File
@@ -3,9 +3,14 @@ services:
build:
context: .
dockerfile: Dockerfile.dev
# dockerfile: Dockerfile
ports:
- "3001:3001"
env_file:
- .env
environment:
- NODE_ENV=development
# - NODE_ENV=production
- BE_API_SERVER_HOST=web_api
- BE_API_SERVER_PORT=3005
volumes:
+1 -8
View File
@@ -1,14 +1,7 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// async rewrites() {
// return [
// {
// source: '/api-backend/:path*',
// destination: `http://web_api:3005`, // The external api URL
// },
// ];
// },
output: 'standalone',
outputFileTracingIncludes: {
'/api/**/*': ['./node_modules/jose/**/*'],
},
+6 -7
View File
@@ -1,6 +1,5 @@
import { Schibsted_Grotesk } from 'next/font/google'
import classnames from 'classnames';
import InlineLink from "@/components/InlineLink";
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
import LandingPageHeader from '@/components/LandingPageHeader';
@@ -8,8 +7,8 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] })
const quickLinks: QuickLink[] = [
{
display: "Member Login/Registration",
url: "/"
display: "Broker SignIn",
url: "/account/signin"
},
{
display: "Link to Thing 1",
@@ -36,17 +35,17 @@ export default function Brokers() {
Partner with Britton Benefit Services to Grow Your Business and Serve Your Clients
</div>
<QuickAccessLinks
imageUrl="/images/brokers-stock.jpg"
imageUrl="/images/broker-image.jpg"
quickLinks={quickLinks}
/>
<div className={classnames("w-full text-2xl px-10", font.className)}>
In the ever-changing health care market, finding cost-effective plans to help your clients save money is a serious challenge. At Britton Benefit Services, LLC we have a proven ourselves as leaders in Third-Party Benefits Administration through our performance in finding employers the right self-funded plans that work for them. Whether you need to review a plan document, manage information reports, review provider directories, or you are looking for access to marketing materials, you’ve come to the right place.
In the ever-changing health care market, finding cost-effective plans to help your clients save money is a serious challenge. At Britton Benefit Services, LLC we have a proven ourselves as leaders in Third-Party Benefits Administration through our performance in finding employers the right self-funded plans that work for them. Whether you need to review a plan document, manage information reports, review provider directories, or you are looking for access to marketing materials, you&apos;ve come to the right place.
<br />
<br />
Broker Services from Britton Benefit Services
<ul className="flex flex-col pl-10 leading-4 list-disc list-inside">
<ul className="flex flex-col pl-10 leading-4 list-disc list-inside space-y-2">
<li>
Discuss your client’s needs and provide additional insight
Discuss your client&apos;s needs and provide additional insight
</li>
<li>
Learn more about our company and self-funding
@@ -1,28 +0,0 @@
import LandingPage from '../../../components/LandingPage';
import InlineLink from "../../../components/InlineLink";
// import TextImage50 from "../../components/TextImage50-50";
export default function Members() {
return (
<div className="w-full">
<LandingPage
headerText='Employers'
imageUrl="/images/employers-stock.jpg"
>
At Britton Benefit Services, LLC we are leading the third-party benefits industry by working hard to make employee benefits more affordable for you and your employees. As a third-party administrator, we negotiate with leading PPOs and other organizations to help you find the best program for your employees without sacrificing the bottom line. This creates a major cost savings that you can pass on to your employees.
<br />
<br />
In addition to the generous cost savings for you and your employees, we also offer a wide range of online services to help streamline and automate the benefit process for you and your employees. This means easier access to the benefit information you need and faster action on individual claims. By logging in to any of the services we provide, you can do the following:
<br />
<br />
Look up in-network providers through our list of PPOs<br />
• File insurance claims and review a claim’s status<br />
• Research which type of plan is best for you<br />
• Control your health care costs by selecting a PPO that’s actively competing for your business.
<br />
<br />
Request a free, no-obligation quote today, and learn how our completely customizable benefits solutions can work for you. To learn more about our company and to discover the benefits of third-party benefits administration, review our services or contact us today.
</LandingPage>
</div>
)
}
+5 -6
View File
@@ -1,6 +1,5 @@
import { Schibsted_Grotesk } from 'next/font/google'
import classnames from 'classnames';
import InlineLink from "@/components/InlineLink";
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
import LandingPageHeader from '@/components/LandingPageHeader';
@@ -8,8 +7,8 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] })
const quickLinks: QuickLink[] = [
{
display: "Employer Login/Registration",
url: "/"
display: "Employer SignIn",
url: "/account/signin"
},
{
display: "Link to Thing 1",
@@ -35,7 +34,7 @@ export default function Employers() {
Making Your Benefits Work for You and Your Employees
</div>
<QuickAccessLinks
imageUrl="/images/employers-stock.jpg"
imageUrl="/images/employer-image.jpg"
quickLinks={quickLinks}
/>
<div className={classnames("w-full text-2xl px-10", font.className)}>
@@ -46,9 +45,9 @@ export default function Employers() {
<br />
<br />
Look up in-network providers through our list of PPOs<br />
• File insurance claims and review a claim’s status<br />
• File insurance claims and review a claim&apos;s status<br />
• Research which type of plan is best for you<br />
• Control your health care costs by selecting a PPO that’s actively competing for your business.
• Control your health care costs by selecting a PPO that&apos;s actively competing for your business.
<br />
<br />
Request a free, no-obligation quote today, and learn how our completely customizable benefits solutions can work for you. To learn more about our company and to discover the benefits of third-party benefits administration, review our services or contact us today.
+1 -29
View File
@@ -1,14 +1,9 @@
import { headers } from "next/headers";
import classnames from 'classnames';
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
export default async function LandingPageLayout({ children }: { children: React.ReactNode }) {
const headersList = await headers();
const pathname = headersList.get("x-current-path");
const landingPageType = pathname?.split('/').pop() || "";
const headerText = landingPageType.charAt(0).toUpperCase() + landingPageType.slice(1);
return (
<>
@@ -23,30 +18,7 @@ export default async function LandingPageLayout({ children }: { children: React.
</div>
</div>
</div>
{/* <div className="w-full flex justify-start items-end pt-30">
<div className="w-1/24 h-full flex justify-end">
<div className="w-[calc(50%+6px)] flex flex-col justify-end z-1 bg-deepcove">
<div className={classnames(
"h-5 -mb-[3px] relative border-b-6 border-l-6 rounded-bl-3xl rounded-out-br-3xl bg-bluetang border-atmosphere"
)} />
<div className={classnames(
"h-5 -mt-[3px] relative border-t-6 border-l-6 rounded-tl-3xl rounded-out-tr-3xl bg-bluetang border-atmosphere"
)} />
</div>
</div>
<div className={`w-11/12 flex flex-col justify-end mb-[14px]`}>
<div className={classnames(
"text-5xl text-left ml-10 z-1 text-shadow-[1px_1px_0,-1px_1px_0,-1px_-1px_0,1px_-1px_0] text-shadow-deepcove text-bronze"
)}>
{headerText}
</div>
<div className={classnames(
"w-full h-[6px] rounded-r bg-atmosphere"
)}>
</div>
</div>
</div> */}
<div className="w-full text-platinum">
<div className="w-full min-h-[75vh] text-platinum">
{children}
</div>
<Footer />
@@ -1,25 +0,0 @@
import LandingPage from '../../../components/LandingPage';
import InlineLink from "../../../components/InlineLink";
// import TextImage50 from "../../components/TextImage50-50";
export default function Members() {
return (
<div className="w-full">
<LandingPage
headerText='Members'
imageUrl="/images/members-stock.jpg"
>
At Britton Benefit Services, LLC we're making employee benefits more affordable. In addition to the cost savings you receive when your employer chooses Britton Benefit Services, LLC we also make sure the important information you need is at your fingertips.
<br />
<br />
That's why our member portal offers you 24/7 access to things like in-network provider lists, claim forms and prescription medication information. Select from any of the links below to get access to the benefit information and forms you need to file a claim, search for a provider or learn more about your benefits plan.
<br />
<br />
At Britton Benefit Services, LLC we're making employee benefits more affordable. In addition to the cost savings you receive when your employer chooses Britton Benefit Services, LLC we also make sure the important information you need is at your fingertips.
<br />
<br />
That's why our member portal offers you 24/7 access to things like in-network provider lists, claim forms and prescription medication information. Select from any of the links below to get access to the benefit information and forms you need to file a claim, search for a provider or learn more about your benefits plan.
</LandingPage>
</div>
)
}
+12 -13
View File
@@ -1,6 +1,5 @@
import { Schibsted_Grotesk } from 'next/font/google'
import classnames from 'classnames';
import InlineLink from "@/components/InlineLink";
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
import LandingPageHeader from '@/components/LandingPageHeader';
@@ -9,19 +8,19 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] })
const quickLinks: QuickLink[] = [
{
display: "Member Login/Registration",
url: "/"
url: "/account/signin"
},
{
display: "Link to Thing 1",
url: "/"
display: "Find a MedCost Medical Provider",
url: "http://medcost.com/"
},
{
display: "Link to Thing 2",
url: "/"
display: "Find a Cigna Medical Provider",
url: "http://cigna.com/"
},
{
display: "Link to another Thing",
url: "/"
display: "Obtain Rx Information",
url: "https://www.fairosrx.com/"
}
]
@@ -36,20 +35,20 @@ export default function Members() {
Member Tagline will go here when we come up with the wording
</div>
<QuickAccessLinks
imageUrl="/images/members-stock.jpg"
imageUrl="/images/member-image.jpg"
quickLinks={quickLinks}
/>
<div className={classnames("w-full text-2xl px-10", font.className)}>
At Britton Benefit Services, LLC we're making employee benefits more affordable. In addition to the cost savings you receive when your employer chooses Britton Benefit Services, LLC we also make sure the important information you need is at your fingertips.
At Britton Benefit Services, LLC we&apos;re making employee benefits more affordable. In addition to the cost savings you receive when your employer chooses Britton Benefit Services, LLC we also make sure the important information you need is at your fingertips.
<br />
<br />
That's why our member portal offers you 24/7 access to things like in-network provider lists, claim forms and prescription medication information. Select from any of the links below to get access to the benefit information and forms you need to file a claim, search for a provider or learn more about your benefits plan.
That&apos;s why our member portal offers you 24/7 access to things like in-network provider lists, claim forms and prescription medication information. Select from any of the links below to get access to the benefit information and forms you need to file a claim, search for a provider or learn more about your benefits plan.
<br />
<br />
At Britton Benefit Services, LLC we're making employee benefits more affordable. In addition to the cost savings you receive when your employer chooses Britton Benefit Services, LLC we also make sure the important information you need is at your fingertips.
At Britton Benefit Services, LLC we&apos;re making employee benefits more affordable. In addition to the cost savings you receive when your employer chooses Britton Benefit Services, LLC we also make sure the important information you need is at your fingertips.
<br />
<br />
That's why our member portal offers you 24/7 access to things like in-network provider lists, claim forms and prescription medication information. Select from any of the links below to get access to the benefit information and forms you need to file a claim, search for a provider or learn more about your benefits plan.
That&apos;s why our member portal offers you 24/7 access to things like in-network provider lists, claim forms and prescription medication information. Select from any of the links below to get access to the benefit information and forms you need to file a claim, search for a provider or learn more about your benefits plan.
</div>
</div>
</div>
+6 -7
View File
@@ -1,6 +1,5 @@
import { Schibsted_Grotesk } from 'next/font/google'
import classnames from 'classnames';
import InlineLink from "@/components/InlineLink";
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
import LandingPageHeader from '@/components/LandingPageHeader';
@@ -8,12 +7,12 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] })
const quickLinks: QuickLink[] = [
{
display: "Member Login/Registration",
url: "/"
display: "Provider SignIn/Registration",
url: "/account/signin"
},
{
display: "Link to Thing 1",
url: "/"
display: "Eligibility & Benefits Lookup",
url: "/public/eligibility-lookup"
},
{
display: "Link to Thing 2",
@@ -36,11 +35,11 @@ export default function Providers() {
Easy Access to HIPAA Compliant Patient Information and Much More
</div>
<QuickAccessLinks
imageUrl="/images/providers-stock.jpg"
imageUrl="/images/provider-image.png"
quickLinks={quickLinks}
/>
<div className={classnames("w-full text-2xl px-10", font.className)}>
You know that as a provider, you have a responsibility to your patients to respond promptly and accurately. At Britton Benefit Services, LLC it’s our goal to provide you with the prompt response to your inquiry and easy access to the information you need to care for your patients. In addition to 24/7 online support and access to HIPAA compliant patient information, you can find the information you need to verify eligibility, confirm benefits and check claim statuses. Simply select the navigation above to access the forms and information you need.
You know that as a provider, you have a responsibility to your patients to respond promptly and accurately. At Britton Benefit Services, LLC it&apos;s our goal to provide you with the prompt response to your inquiry and easy access to the information you need to care for your patients. In addition to 24/7 online support and access to HIPAA compliant patient information, you can find the information you need to verify eligibility, confirm benefits and check claim statuses. Simply select the navigation above to access the forms and information you need.
</div>
</div>
</div>
+81
View File
@@ -0,0 +1,81 @@
// /app/login/page.tsx
'use test' // Marks this component as a client component
'use client'
import classnames from 'classnames';
import { useEffect, useState } from 'react'
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
export default function ForgotPasswordPage() {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [apiUrl, setapiUrl] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => {
setapiUrl(data.apiUrl)
});
}, []);
const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
const res = await fetch(`http://${apiUrl}/forgot_password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json' ,
},
body: JSON.stringify({ user: { email } }),
});
const data = await res.json();
if (res.ok || data.errors[0] == 'Email not found') {
setMessage('If there is an account with this address, a reset link will be sent to your email shortly.');
} else {
setMessage(data.errors?.join(', ') || 'An error occurred.');
}
};
return (
<div
className="h-full flex flex-col items-center justify-center py-6 mt-10 px-4 sm:px-6 lg:px-8"
>
<div className="flex w-full items-center justify-center text-7xl font-bold text-atmosphere">
Britton Benefits
</div>
<div className={classnames("w-md space-y-4", font.className)}>
<div className="mt-10 text-center text-3xl font-extrabold text-light-bronze border-b-2 border-light-bronze">
Forgot Password
</div>
<form onSubmit={handleSubmit}>
<div className="mt-2">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Email</label>
<input
type="email"
placeholder="Enter email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="appearance-none relative block w-full px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
required
/>
</div>
</div>
<button
type="submit"
className="relative w-full flex justify-center py-2 px-4 mt-6 border-4 border-cobalt font-bold rounded-lg text-lg text-platinum hover:text-light-bronze bg-cobalt hover:bg-deepcove hover:cursor-pointer"
>
Send Reset Link
</button>
</form>
{message && <p className="mt-4 text-sm text-center">{message}</p>}
</div>
</div>
)
}
+15 -20
View File
@@ -1,20 +1,12 @@
"user client"
import { sideNavItems } from '@/lib/dashboard';
import { headers } from "next/headers";
import Script from 'next/script';
import AccountDropdown from '../../components/AccountDropdown';
import SideNav from "@/components/SideNav";
import Image from 'next/image';
import classnames from 'classnames';
import Footer from '@/components/Footer';
import Link from 'next/link';
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
const headersList = await headers();
const pathname = headersList.get("x-current-path");
const dashboardType = pathname?.split('/').pop() || "";
const pageNavItems = sideNavItems[dashboardType];
return (
<div className="relative w-full h-full">
@@ -30,25 +22,28 @@ export default async function DashboardLayout({ children }: { children: React.Re
</div>
</div>
</div>
<div className="w-full h-full flex items-start">
<div className="w-full h-screen flex items-start">
<div className="w-full min-w-[87vw] max-w-[94vw] lg:min-w-[89vw] lg:max-w-[95vw] flex flex-col items-end">
<div className="w-[90.15vw] h-20 flex justify-between items-center">
<div className="absolute top-0 left-0 w-full h-20 flex justify-between items-center">
<div className={classnames('flex items-center')}>
<div className="-ml-8.5 z-2">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
<div className="ml-[8.15vw] z-2">
<Link href="/" className="inline-block overflow-hidden rounded-lg">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
</Link>
</div>
</div>
</div>
<div className="w-full h-full flex justify-center ml-[60px]">
<div className="w-full min-h-[75vh] flex justify-center mt-20 ml-[60px]">
{children}
</div>
</div>
</div>
<Footer />
</div>
)
}
+117
View File
@@ -0,0 +1,117 @@
// /app/login/page.tsx
'use client'
import classnames from 'classnames';
import { Suspense, useEffect, useState } from 'react'
import { Schibsted_Grotesk } from 'next/font/google'
import { useRouter, useSearchParams } from 'next/navigation';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
function PasswordResetForm() {
// const resolvedParams = await searchParams;
// const token = resolvedParams.token;
const searchParams = useSearchParams();
const router = useRouter();
const token = searchParams?.get('token'); // Grab token from email link query param
const [password, setPassword] = useState('');
const [passwordConfirmation, setPasswordConfirmation] = useState('');
const [message, setMessage] = useState('');
const [apiUrl, setapiUrl] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => setapiUrl(data.apiUrl));
}, []);
const handleSubmit = async (e: React.SubmitEvent<HTMLFormElement>) => {
e.preventDefault();
const res = await fetch(`http://${apiUrl}/forgot_password`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user: {
reset_password_token: token,
password: password,
password_confirmation: passwordConfirmation,
},
}),
});
const data = await res.json();
if (res.ok) {
setMessage('Password updated successfully! Redirecting to login...');
setTimeout(() => router.push('/account/signin'), 3000);
} else {
setMessage(data.errors?.join(', ') || 'Failed to reset password.');
}
};
return (
<div
className="h-full flex flex-col items-center justify-center py-6 mt-10 px-4 sm:px-6 lg:px-8"
>
<div className="flex w-full items-center justify-center text-7xl font-bold text-atmosphere">
Britton Benefits
{/* <p>Britton</p>
<p>Benefits</p> */}
</div>
<div className={classnames("w-md space-y-4", font.className)}>
<div className="mt-10 text-center text-3xl font-extrabold text-light-bronze border-b-2 border-light-bronze">
Password Reset
</div>
<form onSubmit={handleSubmit}>
<div className="mt-1">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">New Password</label>
<input
type="password"
placeholder="New Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
required
/>
</div>
</div>
<div className="mt-1">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Confirm Password</label>
<input
type="password"
placeholder="Confirm New Password"
value={passwordConfirmation}
onChange={(e) => setPasswordConfirmation(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
required
/>
</div>
</div>
<button
type="submit"
className="relative w-full flex justify-center py-2 px-4 mt-6 border-4 border-cobalt font-bold rounded-lg text-lg text-platinum hover:text-light-bronze bg-cobalt hover:bg-deepcove hover:cursor-pointer"
>
Update Password
</button>
</form>
{message && <p className="mt-4 text-sm text-center">{message}</p>}
</div>
</div>
)
}
export default function PasswordResetPage() {
return (
<Suspense fallback={<div>Loading form...</div>}>
<PasswordResetForm />
</Suspense>
);
}
+14 -15
View File
@@ -9,7 +9,6 @@ import { signInAndFetchUser } from '@/app/actions/signin'
import { Schibsted_Grotesk } from 'next/font/google'
import Link from 'next/link';
import Button from '@/components/Button';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
export default function SignInPage() {
@@ -71,23 +70,23 @@ export default function SignInPage() {
{isPending ? 'Signing In...' : 'Submit'}
</button>
</form>
{/* <div className="text-bluemana">
Not signed up?
<Link
href="/account/signin"
>
<span className="hover:text-light-bronze">Click here.</span>
</Link>
</div> */}
</div>
<div className="text-bluemana mt-4 z-20">
Not signed up?{' '}
<div className="flex w-md justify-between text-platinum mt-4 z-20">
<Link
href="/account/signup"
className="underline text-atmosphere hover:text-light-bronze z-10"
>
Click here.
href="/account/forgot-password"
className={classnames("font-bold underline text-light-bronze hover:text-bluemana z-10", font.className)}
>
Forgot Password?
</Link>
<div>
Not signed up?{' '}
<Link
href="/account/signup"
className={classnames("font-bold underline text-atmosphere hover:text-bluemana z-10", font.className)}
>
Register Here
</Link>
</div>
</div>
</div>
)
+2 -2
View File
@@ -30,7 +30,7 @@ export default function LoginPage() {
return (
<div
className="h-full flex flex-col items-center justify-center py-6 px-4 sm:px-6 lg:px-8"
className="h-full flex flex-col items-center justify-center py-6 mt-10 px-4 sm:px-6 lg:px-8"
>
<div className="flex w-full items-center justify-center text-7xl font-bold text-atmosphere">
Britton Benefits
@@ -165,7 +165,7 @@ export default function LoginPage() {
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Confirm Password</label>
<input
type="confirmPassword"
type="password"
name="confirmPassword"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
-100
View File
@@ -1,100 +0,0 @@
// /app/actions/auth.ts
'use server'
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { SignJWT } from 'jose/jwt/sign';
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET);
function roleMapping(role_number: number) {
const map = [
"member",
"employer",
"broker",
"provider",
"carrier",
"admin"
]
return map[role_number]
}
export async function loginUser(prevState: any, formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
// Simple validation check
if (!email || !password) {
throw new Error('Please fill in all fields.');
}
const response = await fetch(`${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Invalid credentials');
}
const data = await response.json();
const role = roleMapping(data.keychain.role)
const sessionPayload = {
accessToken: data.token,
user: {
id: data.id,
name: data.name,
email: data.email,
keychain: data.keychain,
role: role,
role_id: data.keychain.role_id
}
};
const encryptedSession = await new SignJWT(sessionPayload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('2h')
.sign(SECRET_KEY);
const cookieStore = await cookies();
cookieStore.set('session', encryptedSession, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
});
const initUserResponse = await fetch('https://yourdomain.com', {
headers: { Authorization: `Bearer ${token}` },
});
const userData = await initUserResponse.json();
// return { success: true };
if (role == "member") {
redirect('/dashboard/member')
} else if (role == "employer") {
redirect('/dashboard/employer')
} else if (role == "broker") {
redirect('/dashboard/broker')
} else if (role == "provider") {
redirect('/dashboard/provider')
} else if (role == "carrier") {
redirect('/dashboard/carrier')
} else if (role == "admin") {
redirect('/dashboard/admin')
} else {
return { error: 'Invalid email or password.' }
}
}
+20 -29
View File
@@ -6,19 +6,7 @@ import { SignJWT } from 'jose';
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
function roleMapping(role_number: number) {
const map = [
"member",
"employer",
"broker",
"provider",
"carrier",
"admin"
]
return map[role_number]
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function signInAndFetchUser(prevState: any, formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
@@ -40,8 +28,7 @@ export async function signInAndFetchUser(prevState: any, formData: FormData) {
const { user } = await authResponse.json();
const token = authResponse.headers.get('authorization')?.split(' ')[1];
console.log("token: ", token)
// const token = user.jti
const role = user.role
const sessionPayload = {
@@ -56,8 +43,6 @@ export async function signInAndFetchUser(prevState: any, formData: FormData) {
}
};
console.log("sessionPayload: ", sessionPayload)
const encryptedSession = await new SignJWT(sessionPayload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
@@ -73,19 +58,25 @@ export async function signInAndFetchUser(prevState: any, formData: FormData) {
path: '/',
});
if (role == "member") {
redirect('/dashboard/member')
} else if (role == "employer") {
redirect('/dashboard/employer')
} else if (role == "broker") {
redirect('/dashboard/broker')
} else if (role == "provider") {
redirect('/dashboard/provider')
} else if (role == "carrier") {
redirect('/dashboard/carrier')
} else if (role == "admin") {
redirect('/dashboard/admin')
if (role) {
redirect(`/dashboard/${role}`)
} else {
return { error: 'Invalid email or password.' }
}
// if (role == "member") {
// redirect('/dashboard/member')
// } else if (role == "employer") {
// redirect('/dashboard/employer')
// } else if (role == "broker") {
// redirect('/dashboard/broker')
// } else if (role == "provider") {
// redirect('/dashboard/provider')
// } else if (role == "carrier") {
// redirect('/dashboard/carrier')
// } else if (role == "admin") {
// redirect('/dashboard/admin')
// } else {
// return { error: 'Invalid email or password.' }
// }
}
+28 -34
View File
@@ -7,19 +7,7 @@ import { SignJWT } from 'jose';
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
function roleMapping(role_number: number) {
const map = [
"member",
"employer",
"broker",
"provider",
"carrier",
"admin"
]
return map[role_number]
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function registerUser(prevState: any, formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
@@ -39,7 +27,7 @@ export async function registerUser(prevState: any, formData: FormData) {
throw new Error('Please fill in all fields.');
}
const authResponse = await fetch(`${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/login`, {
const authResponse = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/signup`, {
method: 'POST',
body: JSON.stringify({ user: {email, password, confirm_password, role, role_id, last_four_ssn, date_of_birth} }),
credentials: 'include',
@@ -50,18 +38,18 @@ export async function registerUser(prevState: any, formData: FormData) {
throw new Error('Invalid credentials');
}
const data = await authResponse.json();
const token = data.jti
const { user } = await authResponse.json();
const token = authResponse.headers.get('authorization')?.split(' ')[1];
const sessionPayload = {
token: token,
user: {
id: data.id,
name: data.name,
email: data.email,
role: data.role,
roleId: data.role_id,
keychain: data.keychain
id: user.id,
name: user.name,
email: user.email,
role: user.role,
roleId: user.role_id,
keychain: user.keychain
}
};
@@ -80,19 +68,25 @@ export async function registerUser(prevState: any, formData: FormData) {
path: '/',
});
if (role == "member") {
redirect('/dashboard/member')
} else if (role == "employer") {
redirect('/dashboard/employer')
} else if (role == "broker") {
redirect('/dashboard/broker')
} else if (role == "provider") {
redirect('/dashboard/provider')
} else if (role == "carrier") {
redirect('/dashboard/carrier')
} else if (role == "admin") {
redirect('/dashboard/admin')
if (role) {
redirect(`/dashboard/${role}`)
} else {
return { error: 'Invalid email or password.' }
}
// if (role == "member") {
// redirect('/dashboard/member')
// } else if (role == "employer") {
// redirect('/dashboard/employer')
// } else if (role == "broker") {
// redirect('/dashboard/broker')
// } else if (role == "provider") {
// redirect('/dashboard/provider')
// } else if (role == "carrier") {
// redirect('/dashboard/carrier')
// } else if (role == "admin") {
// redirect('/dashboard/admin')
// } else {
// return { error: 'Invalid email or password.' }
// }
}
+16 -1
View File
@@ -1,6 +1,21 @@
import { JWTPayload, jwtVerify } from 'jose';
import { isIPv4 } from 'net';
import { cookies } from 'next/headers';
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
export async function GET() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
let bearerToken = "";
if (sessionCookie) {
const token = sessionCookie.value;
const { payload } = await jwtVerify(token, SECRET_KEY)
const data = payload as JWTPayload;
bearerToken = data.token as string;
}
// Read the runtime env variable safely on the server
let apiUrl = "";
const backendApiHost = process.env.BE_API_SERVER_HOST || "localhost";
@@ -14,5 +29,5 @@ export async function GET() {
apiUrl = apiUrl.concat(":").concat(process.env.BE_API_SERVER_PORT)
}
return Response.json({ apiUrl });
return Response.json({ apiUrl: apiUrl, token: bearerToken });
}
+45
View File
@@ -0,0 +1,45 @@
'use server'
import { cookies } from 'next/headers';
import { JWTPayload, jwtVerify } from 'jose';
import { NextResponse } from 'next/server';
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
export async function POST() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session")
const token = sessionCookie?.value;
if (token) {
try {
const { payload } = await jwtVerify(token, SECRET_KEY)
const data = payload as JWTPayload;
await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/signout`, {
method: 'DELETE',
credentials: 'include',
headers: {
'Authorization': `Bearer ${data.token}`,
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error('Network error during sign out', error);
}
}
const response = NextResponse.json({ success: true, message: 'Logged out successfully' });
response.cookies.set('session', '', {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
expires: new Date(0), // Instantly expires the cookie
});
return response;
}
@@ -0,0 +1,90 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import EligibilitySelector from '@/components/EligibilitySelector';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function BrokerEligibility() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Tom McSwain",
// role: 'broker',
// roleId: '1'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'broker') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Eligibility Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<EligibilitySelector
role={userData.role}
roleId={userData.roleId}
name={userData.name}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,85 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { redirect } from 'next/navigation';
import CoverageSelector from '@/components/CoverageSelector';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function BrokerEmployerCoverage() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Tom McSwain",
// role: 'broker',
// roleId: '1'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Employer Coverage Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<CoverageSelector
role={userData.role}
roleId={userData.roleId}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,86 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { redirect } from 'next/navigation';
import IdCardSelector from '@/components/IdCardSelector';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function BrokerIdCards() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Tom McSwain",
// role: 'broker',
// roleId: '1'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>ID Card Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<IdCardSelector
role={userData.role}
roleId={userData.roleId}
name={userData.name}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
-141
View File
@@ -1,141 +0,0 @@
import Link from 'next/link';
import Button from '../../../components/Button';
import InlineLink from "../../../components/InlineLink";
import classnames from 'classnames';
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
export default function Broker() {
return (
<div className="w-full h-full flex bg-black">
<div className="w-full h-full flex p-4 bg-bluetang rounded-4xl">
{/* <div className="w-1/24 h-full flex justify-end">
<div className="w-4 flex flex-col justify-end z-1 bg-deepcove">
<div className={classnames(
"h-5 -mb-[3px] relative border-b-6 border-l-6 rounded-bl-3xl rounded-out-br-3xl border-deepcove bg-atmosphere",
)} />
<div className={classnames(
"h-5 -mt-[3px] relative border-t-6 border-l-6 rounded-tl-3xl rounded-out-tr-3xl border-deepcove bg-atmosphere",
)} />
</div>
</div> */}
<div className="w-full h-full flex p-4 bg-atmosphere rounded-4xl">
<div className="w-full h-full flex p-1 bg-bronze rounded-4xl">
<div className="w-full h-full flex p-1 bg-copper rounded-3xl">
<div className="w-full h-full flex p-5 bg-deepcove rounded-3xl flex flex-col justify-start">
<div className="relative w-full h-20 p-2 flex flex-col justify-start">
{/* <div className="w-6/7 h-full pl-3 flex flex-col justify-center text-3xl text-left">
Dashboard | Items | Go | Here | More | Dashboard | Items | Go | Here | Too
</div>
<div className="w-1/7 flex flex-col justify-center items-center space-y-1 text-sm font-semibold">
<Button className="h-8 w-39 bg-deepcove hover:bg-bluetang text-bluemana py-1 px-4 rounded">
Change User Type
</Button>
<Button className="h-8 w-39 bg-platinum text-copper hover:text-bronze py-1 px-4 rounded border-1 border-copper hover:border-bronze">
Sign Out
</Button>
</div> */}
{/* <div className={`w-11/12 flex flex-col justify-start mb-[14px]`}> */}
<div className="flex justify-between bg-deepcore">
<div className={classnames(
"mb-[7px] text-4xl text-left z-1 text-bluemana font-semibold"
)}>
Tom McSwain's Dashboard
</div>
<div className="w-2/7 flex justify-center space-x-2 text-sm font-semibold">
<Button className="h-9 hover:bg-atmosphere bg-deepcove text-platinum py-1 px-4 rounded border-2 border-atmosphere">
Change User Type
</Button>
<Link href="/" passHref>
<button className="h-9 bg-copper text-platnium py-1 px-4 rounded hover:bg-bronze">
Sign Out
</button>
</Link>
{/* <Button className="h-9 bg-copper text-platnium py-1 px-4 rounded hover:bg-bronze">
Sign Out
</Button> */}
</div>
</div>
<div className={classnames(
"w-full h-[6px] ml-[-52px] rounded-r bg-bluetang"
)}>
</div>
</div>
<div className="h-full flex flex-col justify-start rounded-3xl">
<div className="flex gap-[2px] px-10 justify-center text-bluemana text-xl">
<a className="relative rounded-out-b-2xl bg-platinum text-deepcove font-semibold px-8 py-2 rounded-t-2xl z-10">
Dashboard Home
</a>
<a className="relative rounded-out-b-2xl bg-bluetang px-8 py-2 rounded-t-2xl">
Thing 1
</a>
<a className="relative rounded-out-b-2xl bg-bluetang px-8 py-2 rounded-t-2xl">
Thing 2
</a>
<a className="relative rounded-out-b-2xl bg-bluetang px-8 py-2 rounded-t-2xl">
Thing 3
</a>
<a className="relative rounded-out-b-2xl bg-bluetang px-8 py-2 rounded-t-2xl">
Thing 4
</a>
<a className="relative rounded-out-b-2xl bg-bluetang px-8 py-2 rounded-t-2xl">
Thing 5
</a>
<a className="relative rounded-out-b-2xl bg-bluetang px-8 py-2 rounded-t-2xl">
Thing 6
</a>
<a className="relative rounded-out-b-2xl bg-bluetang px-8 py-2 rounded-t-2xl">
Thing 7
</a>
</div>
{/* <div className="w-full p-3 flex justify-between items-center text-2xl">
<InlineLink alt >Dashboard Home</InlineLink>
<InlineLink alt >Thing 1</InlineLink>
<InlineLink alt >Thing 2</InlineLink>
<InlineLink alt >Thing 3</InlineLink>
<InlineLink alt >Thing 4</InlineLink>
<InlineLink alt >Thing 5</InlineLink>
<InlineLink alt >Thing 6</InlineLink>
<InlineLink alt >Thing 7</InlineLink>
</div> */}
<div className={classnames("w-full flex flex-col justify-center items-start px-4 pt-6 pb-4 bg-platinum space-y-4 text-left text-deepcove text-lg rounded-2xl")} >
<div className={classnames("w-full text-2xl font-semibold")} >
Thing 1
</div>
<div className={classnames("w-full", font.className)} >
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec placerat non dolor ut luctus. Suspendisse dictum accumsan dolor. Fusce facilisis ante non dolor cursus, sit amet placerat lectus maximus. Pellentesque rutrum nibh ipsum, quis dignissim massa ornare accumsan. Duis consectetur, mauris sit amet faucibus tempus, neque felis lobortis enim, nec commodo eros neque sed ante. Proin dictum congue risus, ac volutpat neque sagittis vitae. Nam mollis elit ut est hendrerit, at iaculis tellus varius. Proin vitae mi sit amet dolor auctor maximus sit amet in sem. Ut tellus ipsum, mollis sed mi nec, ultrices imperdiet tortor. In sed nisi neque. Donec orci urna, porttitor sit amet auctor non, mattis vitae quam. Donec bibendum est non faucibus vulputate. Quisque sagittis dolor ligula, vitae finibus tortor varius id.
<br />
<br />
Fusce volutpat vitae eros ac ullamcorper. Nulla facilisi. Suspendisse sit amet faucibus orci. Donec blandit mollis placerat. Nullam sed dui turpis. Phasellus laoreet libero non risus ultricies dictum. Pellentesque at bibendum neque. Curabitur eu nisl a massa viverra commodo. Quisque ac arcu vitae mauris dapibus dignissim in porta magna. Etiam feugiat tellus eget enim commodo, nec viverra purus dictum. Curabitur maximus consequat mauris, eu egestas risus tincidunt eget. Donec ipsum est, imperdiet a accumsan in, interdum id tellus. Proin id consequat risus. Donec sit amet augue in lorem venenatis iaculis.
</div>
<div className={classnames("w-full text-2xl font-semibold")} >
Thing 2
</div>
<div className={classnames("w-full", font.className)} >
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec placerat non dolor ut luctus. Suspendisse dictum accumsan dolor. Fusce facilisis ante non dolor cursus, sit amet placerat lectus maximus. Pellentesque rutrum nibh ipsum, quis dignissim massa ornare accumsan. Duis consectetur, mauris sit amet faucibus tempus, neque felis lobortis enim, nec commodo eros neque sed ante. Proin dictum congue risus, ac volutpat neque sagittis vitae. Nam mollis elit ut est hendrerit, at iaculis tellus varius. Proin vitae mi sit amet dolor auctor maximus sit amet in sem. Ut tellus ipsum, mollis sed mi nec, ultrices imperdiet tortor. In sed nisi neque. Donec orci urna, porttitor sit amet auctor non, mattis vitae quam. Donec bibendum est non faucibus vulputate. Quisque sagittis dolor ligula, vitae finibus tortor varius id.
<br />
<br />
Fusce volutpat vitae eros ac ullamcorper. Nulla facilisi. Suspendisse sit amet faucibus orci. Donec blandit mollis placerat. Nullam sed dui turpis. Phasellus laoreet libero non risus ultricies dictum. Pellentesque at bibendum neque. Curabitur eu nisl a massa viverra commodo. Quisque ac arcu vitae mauris dapibus dignissim in porta magna. Etiam feugiat tellus eget enim commodo, nec viverra purus dictum. Curabitur maximus consequat mauris, eu egestas risus tincidunt eget. Donec ipsum est, imperdiet a accumsan in, interdum id tellus. Proin id consequat risus. Donec sit amet augue in lorem venenatis iaculis.
</div>
<div className={classnames("w-full text-2xl font-semibold")} >
Thing 3
</div>
<div className={classnames("w-full", font.className)} >
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec placerat non dolor ut luctus. Suspendisse dictum accumsan dolor. Fusce facilisis ante non dolor cursus, sit amet placerat lectus maximus. Pellentesque rutrum nibh ipsum, quis dignissim massa ornare accumsan. Duis consectetur, mauris sit amet faucibus tempus, neque felis lobortis enim, nec commodo eros neque sed ante. Proin dictum congue risus, ac volutpat neque sagittis vitae. Nam mollis elit ut est hendrerit, at iaculis tellus varius. Proin vitae mi sit amet dolor auctor maximus sit amet in sem. Ut tellus ipsum, mollis sed mi nec, ultrices imperdiet tortor. In sed nisi neque. Donec orci urna, porttitor sit amet auctor non, mattis vitae quam. Donec bibendum est non faucibus vulputate. Quisque sagittis dolor ligula, vitae finibus tortor varius id.
<br />
<br />
Fusce volutpat vitae eros ac ullamcorper. Nulla facilisi. Suspendisse sit amet faucibus orci. Donec blandit mollis placerat. Nullam sed dui turpis. Phasellus laoreet libero non risus ultricies dictum. Pellentesque at bibendum neque. Curabitur eu nisl a massa viverra commodo. Quisque ac arcu vitae mauris dapibus dignissim in porta magna. Etiam feugiat tellus eget enim commodo, nec viverra purus dictum. Curabitur maximus consequat mauris, eu egestas risus tincidunt eget. Donec ipsum est, imperdiet a accumsan in, interdum id tellus. Proin id consequat risus. Donec sit amet augue in lorem venenatis iaculis.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
+87
View File
@@ -0,0 +1,87 @@
import classnames from 'classnames';
import DashboardHeader from "@/components/DashboardHeader";
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import { notFound, redirect } from 'next/navigation';
import ArrowText from '@/components/ArrowText';
// Ensure your secret key matches exactly what you used to sign the token
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function Broker() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Tom McSwain",
// role: 'broker',
// roleId: '1'
// }
const userData = {
role: data.user.role,
role_id: data.user.roleId,
name: data.user.name
}
if (!data || userData.role !== 'broker') {
notFound();
}
return (
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<ArrowText>
Generate Temporary Member or Employer ID Cards.
</ArrowText>
<ArrowText>
Search Eligibility and Benefits by Member.
</ArrowText>
<ArrowText>
Search Coverage by Employer.
</ArrowText>
</div>
</div>
</div>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
+90
View File
@@ -0,0 +1,90 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import ClaimsSelector from '@/components/ClaimsSelectorNew';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function CarrierClaims() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Acentria",
// role: 'carrier',
// roleId: '1'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'carrier') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Claims Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<ClaimsSelector
role={userData.role}
roleId={userData.roleId}
name={userData.name}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,90 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import EligibilitySelector from '@/components/EligibilitySelector';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function CarrierEligibility() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Acentria",
// role: 'carrier',
// roleId: '1'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'carrier') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Eligibility Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<EligibilitySelector
role={userData.role}
roleId={userData.roleId}
name={userData.name}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,89 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import CoverageSelector from '@/components/CoverageSelector';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function CarrierEmployerCoverage() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Acentria",
// role: 'carrier',
// roleId: '1'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'carrier') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Employer Coverage Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<CoverageSelector
role={userData.role}
roleId={userData.roleId}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,90 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import IdCardSelector from '@/components/IdCardSelector';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function CarrierIdCards() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Acentria",
// role: 'carrier',
// roleId: '1'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'carrier') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>ID Card Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<IdCardSelector
role={userData.role}
roleId={userData.roleId}
name={userData.name}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
+94
View File
@@ -0,0 +1,94 @@
import classnames from 'classnames';
import DashboardHeader from "@/components/DashboardHeader";
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import { notFound, redirect } from 'next/navigation';
import ArrowText from '@/components/ArrowText';
// Ensure your secret key matches exactly what you used to sign the token
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function Carrier() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Acentria",
// role: 'carrier',
// roleId: '1'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'carrier') {
notFound();
}
return (
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<ArrowText>
Generate Temporary Member or Employer ID Cards.
</ArrowText>
<ArrowText>
Search Claims by Member.
</ArrowText>
<ArrowText>
Search Eligibility and Benefits by Member.
</ArrowText>
<ArrowText>
Search Coverage by Employer.
</ArrowText>
</div>
</div>
</div>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
// } catch (error) {
// // Fails if token is expired, tampered with, or invalid
// console.error("JWT verification failed:", error);
// return <div>Session invalid or expired.</div>;
// }
}
@@ -0,0 +1,117 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import DocumentDownloader from '@/components/DocumentDownloader';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
interface AvaliableDocumentType {
[key: number]: string;
}
export default async function EmployerDocuments() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// plPlanKey: '69',
// name: "Boys and Girls Club",
// role: 'employer',
// roleId: '69'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'employer') {
notFound();
}
const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/employers/${userData.roleId}/available_document_categories`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${data.token}`
},
});
if (!response.ok) throw new Error("Unauthenticated");
const avaliableDocuments: AvaliableDocumentType = await response.json()
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Documents</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
{avaliableDocuments[1] &&
<DocumentDownloader
plPlanKey={userData.roleId}
documentCategoryId={1}
documentCategoryName={avaliableDocuments[1]}
/>
}
{avaliableDocuments[2] &&
<DocumentDownloader
plPlanKey={userData.roleId}
documentCategoryId={2}
documentCategoryName={avaliableDocuments[2]}
/>
}
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,91 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import EligibilitySelector from '@/components/EligibilitySelector';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function EmployerEligibility() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// plPlanKey: '61',
// name: "Bryan Pest Control",
// role: 'employer',
// roleId: '61'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'employer') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Eligibility Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<EligibilitySelector
role={userData.role}
roleId={userData.roleId}
name={userData.name}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,91 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import IdCardSelector from '@/components/IdCardSelector';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function EmployerIdCards() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// plPlanKey: '61',
// name: "Bryan Pest Control",
// role: 'employer',
// roleId: '61'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'employer') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>ID Card Search</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<IdCardSelector
role={userData.role}
roleId={userData.roleId}
name={userData.name}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
-148
View File
@@ -1,148 +0,0 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { redirect } from 'next/navigation';
import { isRedirectError } from 'next/dist/client/components/redirect-error';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface ClaimType {
claim_number: number;
visit_date: string;
claim_status: string;
patient_name: string;
provider_name: string;
}
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
keychain: UserKeychain;
}
interface UserKeychain {
family_id: string;
mb_member_key: number;
pb_entity_key: number;
pl_plan_key: number;
};
export default async function EmployerIdCards() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userDataTest = {
// pb_entity_key: 377527,
// pl_plan_key: '61',
// name: "John McMember"
// }
const userData = {
pb_entity_key: data.user.keychain.pb_entity_key,
pl_plan_key: data.user.keychain.pl_plan_key,
name: data.user.name
}
const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.pb_entity_key}/claims`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${data.token}`
},
});
if (!response.ok) throw new Error("Unauthenticated");
const claimsData: ClaimType[] = await response.json()
return (
<>
{claimsData &&
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Claims</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<div className='flex w-full mb-4 border-b border-bluemana'>
<div className="flex w-2/20">
Claim #
</div>
<div className="flex w-4/20 pl-3">
Patient Name
</div>
<div className="flex w-6/20 pl-3">
Provider
</div>
<div className="flex w-3/20 pl-3">
Visit Date
</div>
<div className="flex w-4/20 pl-8.5">
Claim Status
</div>
</div>
{(claimsData).map((mc) => (
<div key={mc.claim_number} className="flex w-full text-platinum">
<div className="flex w-2/20">
{mc.claim_number}
</div>
<div className="flex w-4/20 pl-3">
{mc.patient_name}
</div>
<div className="flex w-6/20 pl-3">
{mc.provider_name}
</div>
<div className="flex w-3/20 pl-3">
{mc.visit_date}
</div>
<div className="flex w-4/20 pl-8.5">
{mc.claim_status}
</div>
</div>
))}
</div>
</div>
</div>
</div>
}
</>
);
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,86 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import CoverageViewer from '@/components/CoverageViewer';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function MembersCoverage() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// plPlanKey: '61',
// name: "Bryan Pest Control",
// role: 'employer',
// roleId: '61'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'employer') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<CoverageViewer
plPlanKey={userData.roleId}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
+32 -88
View File
@@ -1,41 +1,14 @@
import classnames from 'classnames';
import DashboardHeader from "@/components/DashboardHeader";
import FindProviderWidget from '@/components/FindProvider';
import InsuranceCardWidget from '@/components/InsuranceCard';
import FindRxInformationWidget from '@/components/FindRxInformation';
import RecentClaimsWidget from '@/components/RecentClaimsWidget';
import BenefitsAAGWidget from '@/components/BenefitsAAGWidget';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import { redirect } from 'next/navigation';
import IdCardViewer from '@/components/IdCardViewer';
import { notFound, redirect } from 'next/navigation';
import ArrowText from '@/components/ArrowText';
// Ensure your secret key matches exactly what you used to sign the token
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface PlanBenefitType {
id: number;
title: string;
sequence: number;
benefit: string;
benefit_desc: string;
}
interface RecentClaimType {
claim_number: number;
visit_date: string;
claim_status: string;
patient_name: string;
provider_name: string;
}
interface DashboardDataType {
network_provider: string;
plan_benefits: PlanBenefitType[];
recent_claims: RecentClaimType[];
}
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
@@ -46,17 +19,9 @@ interface DashboardDataType {
name: string;
role: string;
roleId: string;
keychain: UserKeychain;
}
interface UserKeychain {
family_id: string;
mb_member_key: number;
pb_entity_key: number;
pl_plan_key: number;
};
export default async function Member() {
export default async function Employer() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
@@ -74,66 +39,45 @@ export default async function Member() {
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userDataTest = {
// pb_entity_key: 377527,
// pl_plan_key: '61',
// name: "John McMember"
// const userData = {
// plPlanKey: '61',
// name: "Bryan Pest Control",
// role: 'employer',
// roleId: '61'
// }
const userData = {
pb_entity_key: data.user.keychain.pb_entity_key,
pl_plan_key: data.user.keychain.pl_plan_key,
name: data.user.name
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.pb_entity_key}/initialize_dashboard`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${data.token}`
},
});
if (!data || userData.role !== 'employer') {
notFound();
}
if (!response.ok) throw new Error("Unauthenticated");
const dashboardData: DashboardDataType = await response.json()
return (
<>
{dashboardData &&
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col xl:flex-row pt-4 z-2 space-x-4 space-y-4 xl:space-y-0")}>
<div className="flex flex-col gap-y-4">
<div className="flex flex-col w-full h-full xl:h-auto lg:flex-row gap-y-4 lg:gap-y-0 lg:gap-x-4">
{/* <DeductableProgressWidget /> */}
<IdCardViewer
pbEntityKey={userData.pb_entity_key}
cardLaout="FullPageCard"
/>
<FindProviderWidget
// pl_plan_key='57'
networkProvider={dashboardData.network_provider}
/>
</div>
<FindRxInformationWidget />
<RecentClaimsWidget
recentClaims={dashboardData.recent_claims}
/>
</div>
<BenefitsAAGWidget
planBenefits={dashboardData.plan_benefits}
/>
</div>
</div>
</div>
}
</>
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<ArrowText>
Generate Temporary Member or Employer ID Cards.
</ArrowText>
<ArrowText>
Search Eligibility and Benefits by Member.
</ArrowText>
<ArrowText>
View Members Coverage.
</ArrowText>
</div>
</div>
</div>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
+17 -23
View File
@@ -1,6 +1,3 @@
import { sideNavItems } from '@/lib/dashboard';
import { headers } from "next/headers";
import Script from 'next/script';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
@@ -8,6 +5,8 @@ import AccountDropdown from '../../components/AccountDropdown';
import SideNav from "@/components/SideNav";
import Image from 'next/image';
import classnames from 'classnames';
import Link from 'next/link';
import InlineLink from '@/components/InlineLink';
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
@@ -29,7 +28,7 @@ export default async function DashboardLayout({ children }: { children: React.Re
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
if (!sessionCookie) {
return <div className='flex w-full justify-center'>No active session. Please log in.</div>;
return <div className='flex w-full justify-center'>No active session. Please <InlineLink link="/account/signin">Sign In</InlineLink>.</div>;
}
try {
@@ -44,16 +43,11 @@ export default async function DashboardLayout({ children }: { children: React.Re
const userRole = data.user.role
// const headersList = await headers();
// const pathname = headersList.get("x-current-path");
// const dashboardType = pathname?.split('/').pop() || "";
const pageNavItems = sideNavItems[userRole];
return (
<div className="relative w-full h-full">
<div className="absolute bottom-0 right-0 flex flex-col justify-end items-end w-full h-full">
<div className="w-full -mb-[2px] flex justify-start">
<div className="w-[87vw] sm:w-[91vw] md:w-[93vw] lg:w-[94.5vw] xl:w-[95.5vw] h-20 border-b-2 border-copper z-2" />
<div className="w-[87vw] sm:w-[91vw] md:w-[93vw] lg:w-[94.5vw] xl:w-[96vw] h-20 border-b-2 border-copper z-2" />
</div>
<div className="w-[60px] h-full flex justify-start">
<div className="w-[32.5px] flex flex-col">
@@ -65,27 +59,27 @@ export default async function DashboardLayout({ children }: { children: React.Re
</div>
<div className="w-full h-full flex items-start">
<SideNav
maxWidth="w-[16vw]"
bgColor="bg-atmosphere"
navItems={pageNavItems}
/>
<div className="w-full min-w-[87vw] max-w-[94vw] lg:min-w-[89vw] lg:max-w-[95vw] flex flex-col items-end">
<div className="w-[90.15vw] h-20 flex justify-between items-center">
<div className="absolute top-0 left-0 w-full h-20 flex justify-between items-center z-2">
<div className={classnames('flex items-center')}>
<div className="-ml-4.5 z-2">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
<div className="ml-[8.15vw] z-2">
<Link href="/" className="inline-block overflow-hidden rounded-lg">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
</Link>
</div>
</div>
<div className="pr-20 z-2">
<AccountDropdown userRole="member" />
<div className="mr-[2vw] z-2">
<AccountDropdown userRole={userRole} />
</div>
</div>
<div className="w-full h-full flex justify-start">
<div className="w-full h-full flex justify-start mt-20 pb-20">
{children}
</div>
</div>
-76
View File
@@ -1,76 +0,0 @@
// /app/login/page.tsx
'use test' // Marks this component as a client component
'use client'
import classnames from 'classnames';
import { useActionState } from 'react'
import { loginAndFetchUser } from '@/app/actions/signin'
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
export default function LoginPage() {
// useActionState manages the server state (errors, loading) and form binding
const [state, formAction, isPending] = useActionState(loginAndFetchUser, null)
return (
<div
className="min-h-full flex flex-col items-center justify-center py-6 px-4 sm:px-6 lg:px-8"
>
<div className="flex w-full items-center justify-center text-7xl font-bold text-atmosphere">
Britton Benefits
{/* <p>Britton</p>
<p>Benefits</p> */}
</div>
<div className={classnames("w-sm space-y-4", font.className)}>
<div className="mt-10 text-center text-3xl font-extrabold text-light-bronze border-b-1 border-light-bronze">
Sign In
</div>
<form action={formAction}>
{/* Error Message Display */}
{state?.error && (
<div className="rounded-lg bg-red-50 p-3 text-sm text-red-600">
{state.error}
</div>
)}
<div className="mt-2">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Email</label>
<input
type="email"
name="email"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="you@example.com"
/>
</div>
</div>
<div className="mt-1">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Password</label>
<input
type="password"
name="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder=""
/>
</div>
</div>
<button
type="submit"
disabled={isPending}
className="relative w-full flex justify-center py-2 px-4 mt-6 border-4 border-cobalt font-bold rounded-lg text-lg text-platinum hover:text-light-bronze bg-cobalt hover:bg-deepcove hover:cursor-pointer"
>
{isPending ? 'Signing In...' : 'Submit'}
</button>
</form>
</div>
</div>
)
}
+5 -13
View File
@@ -5,7 +5,6 @@ import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { redirect } from 'next/navigation';
import { isRedirectError } from 'next/dist/client/components/redirect-error';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
@@ -28,16 +27,8 @@ interface ClaimType {
name: string;
role: string;
roleId: string;
keychain: UserKeychain;
}
interface UserKeychain {
family_id: string;
mb_member_key: number;
pb_entity_key: number;
pl_plan_key: number;
};
export default async function MemberClaims() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
@@ -63,12 +54,12 @@ export default async function MemberClaims() {
// }
const userData = {
pb_entity_key: data.user.keychain.pb_entity_key,
pl_plan_key: data.user.keychain.pl_plan_key,
name: data.user.name
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.pb_entity_key}/claims`, {
const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.roleId}/claims`, {
method: 'GET',
credentials: 'include',
headers: {
@@ -138,6 +129,7 @@ export default async function MemberClaims() {
}
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
+114
View File
@@ -0,0 +1,114 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { redirect } from 'next/navigation';
import DocumentDownloader from '@/components/DocumentDownloader';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
keychain: UserKeychain;
}
interface UserKeychain {
pl_plan_key: string;
};
interface AvaliableDocumentType {
[key: number]: string;
}
export default async function EmployerDocuments() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// plPlanKey: '69',
// name: "Bob Gerchin",
// role: 'member',
// roleId: '362869'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId,
plPlanKey: data.user.keychain.pl_plan_key
}
console.log(userData)
const response1 = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/employers/${userData.plPlanKey}/available_document_categories`, {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${data.token}`
},
});
if (!response1.ok) throw new Error("Unauthenticated");
const avaliableDocuments: AvaliableDocumentType = await response1.json()
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Documents</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
{avaliableDocuments[1] &&
<DocumentDownloader
plPlanKey={userData.plPlanKey}
documentCategoryId={1}
documentCategoryName={avaliableDocuments[1]}
/>
}
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
-308
View File
@@ -1,308 +0,0 @@
import AccountDropdown from '../../../components/AccountDropdown';
import SideNav from "@/components/SideNav";
import Image from 'next/image';
import classnames from 'classnames';
import { Schibsted_Grotesk } from 'next/font/google'
import DashboardPieChart from '@/components/DashboardPieChart';
import { FaRegIdCard, FaStethoscope } from "react-icons/fa";
import { LuLayoutDashboard, LuClipboardList } from "react-icons/lu";
import { IoDocumentsOutline } from "react-icons/io5";
import { BsClipboardCheck } from "react-icons/bs";
import { TbProgress } from "react-icons/tb";
import { MdLocationOn } from "react-icons/md";
import LocationSearchInput from '@/components/LocationSearchInput';
import Button from '@/components/Button';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
export default function Member() {
return (
<div className="relative w-full h-full">
<div className="absolute bottom-0 right-0 flex flex-col justify-end items-end w-full h-full">
<div className="w-full -mb-[2px] flex justify-start">
<div className="w-23/24 h-20 border-b-2 border-copper" />
</div>
<div className="w-1/24 h-full flex justify-start">
<div className="w-[calc(50%+6px)] flex flex-col">
<div className={classnames(
"relative h-full border-r-6 rounded-out-tl-4xl rounded-tr-4xl bg-copper border-atmosphere"
)} />
</div>
</div>
</div>
<div className="w-full h-full flex flex-col justify-start items-center">
<div className="w-full">
<div className="h-20 flex justify-between items-center">
<div className={classnames('flex items-center')}>
<div className="w-31 h-20 bg-atmosphere" />
<div className="-ml-8.5">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
</div>
</div>
<div className="z-1 pr-20">
<AccountDropdown userRole="member" />
</div>
</div>
</div>
<div className="w-full h-full flex justify-start">
<SideNav
maxWidth="max-w-31"
bgColor="bg-atmosphere"
navItems={
[
{
title: "Dashboard",
url: "/",
Icon: LuLayoutDashboard
},
{
title: "My Plan & Benefits",
url: "/",
Icon: LuClipboardList
},
{
title: "Plan Utilization",
url: "/",
Icon: TbProgress
},
{
title: "Claims & Authorizations",
url: "/",
Icon: BsClipboardCheck
},
{
title: "Providers & Services",
url: "/",
Icon: FaStethoscope
},
{
title: "Insurance Card",
url: "/",
Icon: FaRegIdCard
},
{
title: "Benefits Documents",
url: "/",
Icon: IoDocumentsOutline
}
]
}
/>
<div className="flex w-full ml-8 pt-5 2xl:pt-7">
<div className="w-full flex flex-col justify-start">
<div className={classnames(
"-mb-[34px] text-4xl text-left z-2 text-bluemana font-semibold text-shadow-[1px_1px_0,-1px_1px_0,-1px_-1px_0,1px_-1px_0] text-shadow-deepcove"
)}>
Robert La'Blah
</div>
<div className="flex items-center">
<div className={classnames(
"w-full h-[6px] rounded-s bg-atmosphere z-1 -mr-[26px] 2xl:-mr-[16px]"
)}>
</div>
<div className="w-1/21 h-full flex justify-start 2xl:pr-7.5">
<div className="w-[calc(50%+6px)] 2xl:w-[calc(100%-6px)] flex flex-col justify-start z-1 bg-deepcove">
<div className={classnames(
"h-9 w-full -mb-[3px] relative border-b-6 border-r-6 rounded-br-4xl rounded-out-bl-4xl bg-copper border-atmosphere"
)} />
<div className={classnames(
"h-9 w-full -mt-[3px] relative border-t-6 border-r-6 rounded-tr-4xl rounded-out-tl-4xl bg-copper border-atmosphere"
)} />
</div>
</div>
</div>
<div className={classnames("w-full flex justify-start pt-4 z-2 space-x-4")}>
<div className="flex flex-col space-y-4">
<div className="flex space-x-4">
<div className="flex flex-col justify-center items-center text-xl border border-6 border-bluetang rounded-4xl">
<div className="flex justify-between items-end px-4">
<p className={classnames("pb-18 text-[#32CD32] font-semibold", font.className)}>Paid</p>
<div className="flex flex-col items-center">
<DashboardPieChart paid={1007.23} remaining={492.77} />
<p className="text-2xl">Deductable Progress</p>
</div>
<p className={classnames("pb-18 font-semibold", font.className)}>Remaining</p>
</div>
</div>
<div className="flex flex-col justify-center items-center px-4 py-3 text-xl border border-6 border-atmosphere rounded-4xl">
<div className="text-deepcove -mb-11 z-3 font-semibold">My Card</div>
<FaRegIdCard size={150} className="text-bluemana" />
<div className={classnames("w-full flex justify-evenly items-center space-x-1", font.className)}>
<Button className="hover:text-bronze">
View
</Button>
<p>|</p>
<Button className="hover:text-bronze">
Download
</Button>
</div>
</div>
</div>
<div className="flex">
<div className="w-full flex flex-col px-4 py-3 border border-6 border-bluemana rounded-4xl">
<div className="w-full pb-2 flex justify-center text-2xl text-bronze font-semibold">
<p>Find A Provider By...</p>
</div>
<div className="w-full flex px-2">
<div className="w-full flex border-x-2 border-t-2 rounded-t-xl font-semibold divide-x-2 divide-bronze">
<a href="#" className="w-full h-9 flex justify-center items-center rounded-tl-lg bg-bluetang text-platinum">
Location
</a>
<a href="#" className="w-full h-9 flex justify-center items-center text-platinum hover:text-bronze">
Procedure
</a>
<a href="#" className="w-full h-9 flex justify-center items-center rounded-tr-lg text-platinum hover:text-bronze">
Provider/Facility
</a>
</div>
</div>
<div className={classnames("w-full flex items-start px-2 pb-2 text-xl", font.className)}>
{/* <p className='pr-3'><GrMapLocation size={30} /></p> */}
<Button className="h-10 text-deepcove hover:text-atmosphere bg-platinum rounded-bl-xl">
<MdLocationOn size={30} />
</Button>
<LocationSearchInput className="h-10 pl-3 text-bluemana border-y-2 border-platinum" />
<Button className="h-10 pr-3 pl-2 bg-platinum hover:bg-bluemana text-center text-deepcove font-semibold rounded-br-xl border-y-2 border-r-2 border-platinum">
Search
</Button>
</div>
<div className="w-full flex justify-end text-platinum font-semibold">
<Button className="mr-2 underline hover:text-bronze">
View Full Provider List
</Button>
</div>
</div>
</div>
<div className="flex">
<div className="w-full flex flex-col px-4 py-3 border border-6 border-bronze rounded-4xl space-y-2">
<div className="w-full flex justify-center text-2xl text-atmosphere font-semibold">
<p>Recent Claims</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<div className="flex">
<div className="w-1/6">
<Button className="hover:text-bronze text-bluemana">
2907457
</Button>
</div>
<div className="w-2/6">
LA'BLAH, ROBERT
</div>
<div className="w-2/6">
HOUSE, GREGORY
</div>
<div className="w-1/6">
07/21/2025
</div>
</div>
<div className="flex">
<div className="w-1/6">
<Button className="hover:text-bronze text-bluemana">
2907201
</Button>
</div>
<div className="w-2/6">
LA'BLAH, ROBERT
</div>
<div className="w-2/6">
QUINN, MICHAELA
</div>
<div className="w-1/6">
07/02/2025
</div>
</div>
<div className="flex">
<div className="w-1/6">
<Button className="hover:text-bronze text-bluemana">
2905891
</Button>
</div>
<div className="w-2/6">
LA'BLAH, JENNY
</div>
<div className="w-2/6">
HOWSER, DOGGIE
</div>
<div className="w-1/6">
05/15/2025
</div>
</div>
<div className="flex">
<div className="w-1/6">
<Button className="hover:text-bronze text-bluemana">
2905547
</Button>
</div>
<div className="w-2/6">
LA'BLAH, ROBERT
</div>
<div className="w-2/6">
HOUSE, GREGORY
</div>
<div className="w-1/6">
04/29/2025
</div>
</div>
</div>
<div className="w-full flex justify-end text-platinum font-semibold">
<Button className="mr-2 underline hover:text-bronze">
View All Claims
</Button>
</div>
</div>
</div>
</div>
<div className="w-2/5 flex flex-col justify-start items-center px-4 py-3 text-xl border border-6 border-platinum rounded-4xl">
<p className="text-3xl underline py-2">Benefits At A Glance</p>
<p className={classnames("text-2xl font-semibold py-2", font.className)}>CLASSIC 5K 100/5000</p>
<div className={classnames("w-full flex justify-between", font.className)}>
<div className="flex flex-col">
<p>Physician Visit</p>
<p>Specialist Visit</p>
<p>Urgent Care</p>
<p>INN-Ind Ded</p>
<p>INN-Family Ded</p>
<p>OON-Ind Ded</p>
<p>OON-Family Ded</p>
<p>Co-Insurance</p>
<p>INN-Ind OOP</p>
<p>INN-Family OOP</p>
<p>OON-Ind OOP</p>
<p>OON-Family OOP</p>
<p>Emergency Room</p>
<p>Preventive Care</p>
</div>
<div className="flex flex-col">
<p>$25.00</p>
<p>$50.00</p>
<p>$50.00</p>
<p>$5,000</p>
<p>$10,000</p>
<p>$10,000</p>
<p>$20,000</p>
<p>100%/0%</p>
<p>$5,000</p>
<p>$10,000</p>
<p>$10,000</p>
<p>$20,000</p>
<p>Ded & Coin</p>
<p>100%</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="w-1/24 h-full" />
</div>
</div>
)
}
+30 -31
View File
@@ -2,19 +2,18 @@ import classnames from 'classnames';
import DashboardHeader from "@/components/DashboardHeader";
import FindProviderWidget from '@/components/FindProvider';
import InsuranceCardWidget from '@/components/InsuranceCard';
import FindRxInformationWidget from '@/components/FindRxInformation';
import RecentClaimsWidget from '@/components/RecentClaimsWidget';
import BenefitsAAGWidget from '@/components/BenefitsAAGWidget';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import { redirect } from 'next/navigation';
import { notFound, redirect } from 'next/navigation';
import IdCardViewer from '@/components/IdCardViewer';
// Ensure your secret key matches exactly what you used to sign the token
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface PlanBenefitType {
interface PlanDetailtType {
id: number;
title: string;
sequence: number;
@@ -32,10 +31,16 @@ interface RecentClaimType {
interface DashboardDataType {
network_provider: string;
plan_benefits: PlanBenefitType[];
plan_benefits: PlanBenefitType;
recent_claims: RecentClaimType[];
}
interface PlanBenefitType {
plan_details: PlanDetailtType[];
effective_date: string;
dependents: string[];
}
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
@@ -46,16 +51,8 @@ interface DashboardDataType {
name: string;
role: string;
roleId: string;
keychain: UserKeychain;
}
interface UserKeychain {
family_id: string;
mb_member_key: number;
pb_entity_key: number;
pl_plan_key: number;
};
export default async function Member() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
@@ -65,15 +62,15 @@ export default async function Member() {
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userDataTest = {
// pb_entity_key: 377527,
// pl_plan_key: '61',
@@ -81,12 +78,16 @@ export default async function Member() {
// }
const userData = {
pb_entity_key: data.user.keychain.pb_entity_key,
pl_plan_key: data.user.keychain.pl_plan_key,
role: data.user.role,
roleId: data.user.roleId,
name: data.user.name
}
const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.pb_entity_key}/initialize_dashboard`, {
if (!data || userData.role !== 'member') {
notFound();
}
const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.roleId}/initialize_dashboard`, {
method: 'GET',
credentials: 'include',
headers: {
@@ -112,7 +113,7 @@ export default async function Member() {
<div className="flex flex-col w-full h-full xl:h-auto lg:flex-row gap-y-4 lg:gap-y-0 lg:gap-x-4">
{/* <DeductableProgressWidget /> */}
<IdCardViewer
pbEntityKey={userData.pb_entity_key}
pbEntityKey={userData.roleId}
cardLaout="FullPageCard"
/>
<FindProviderWidget
@@ -126,7 +127,9 @@ export default async function Member() {
/>
</div>
<BenefitsAAGWidget
planBenefits={dashboardData.plan_benefits}
planDetails={dashboardData.plan_benefits.plan_details}
effectiveDate={dashboardData.plan_benefits.effective_date}
dependents={dashboardData.plan_benefits.dependents}
/>
</div>
</div>
@@ -134,6 +137,7 @@ export default async function Member() {
}
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
@@ -141,9 +145,4 @@ export default async function Member() {
redirect('/account/signin');
}
}
// } catch (error) {
// // Fails if token is expired, tampered with, or invalid
// console.error("JWT verification failed:", error);
// return <div>Session invalid or expired.</div>;
// }
}
@@ -0,0 +1,86 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import { notFound, redirect } from 'next/navigation';
import ProviderClaimSearch from '@/components/ProviderClaimSearch';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function ClaimSearch() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session");
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Kenneth Johnson",
// role: 'provider',
// roleId: '1457336760'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'provider') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<ProviderClaimSearch
role={userData.role}
roleId={userData.roleId}
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
@@ -0,0 +1,85 @@
import classnames from 'classnames';
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import EligibilitySearch from '@/components/EligibilitySearch';
import { notFound, redirect } from 'next/navigation';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function ProviderEligibilityLookup() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Kenneth Johnson",
// role: 'provider',
// roleId: '1457336760'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'provider') {
notFound();
}
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<EligibilitySearch
skipDobValidation
/>
</div>
</div>
</div>
</div>
</>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
+83
View File
@@ -0,0 +1,83 @@
import classnames from 'classnames';
import DashboardHeader from "@/components/DashboardHeader";
import { cookies } from "next/headers";
import { JWTPayload, jwtVerify } from "jose";
import { notFound, redirect } from 'next/navigation';
import ArrowText from '@/components/ArrowText';
// Ensure your secret key matches exactly what you used to sign the token
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
interface UserJwtPayload extends JWTPayload {
user: UserData;
}
interface UserData {
id: string;
email: string;
name: string;
role: string;
roleId: string;
}
export default async function Provider() {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
if (!sessionCookie) {
redirect('/account/signin')
}
try {
// 2. Extract the raw string token from the cookie object
const token = sessionCookie.value;
// 3. Verify and decode the JWT using jose
const { payload } = await jwtVerify(token, SECRET_KEY)
// 3. Cast the payload to your custom interface
const data = payload as UserJwtPayload;
// const userData = {
// name: "Kenneth Johnson",
// role: 'provider',
// roleId: '1457336760'
// }
const userData = {
name: data.user.name,
role: data.user.role,
roleId: data.user.roleId
}
if (!data || userData.role !== 'provider') {
notFound();
}
return (
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<ArrowText>
Search Claims by Member Number.
</ArrowText>
<ArrowText>
Search Eligibility and Benefits by Member Number.
</ArrowText>
</div>
</div>
</div>
);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.message === 'NEXT_REDIRECT') throw error;
if (error.message === 'Unauthenticated') {
redirect('/account/signin');
}
}
}
-3
View File
@@ -1,8 +1,5 @@
import '../styles/globals.css';
import type { Metadata } from 'next';
import { Fraunces } from 'next/font/google'
import Navbar from '../components/Navbar';
import Footer from '../components/Footer';
import classNames from 'classnames';
const font = Fraunces({ subsets: ['latin'] })
+4 -5
View File
@@ -1,6 +1,5 @@
import InfoCard from '../components/InfoCard';
import Tagline from "../components/Tagline";
import InlineLink from "../components/InlineLink";
import Navbar from '@/components/Navbar';
import Footer from '@/components/Footer';
@@ -25,7 +24,7 @@ export default async function Home() {
learnMoreUrl="/members"
imageUrl="/images/member-image.jpg"
>
What we do for members
At Britton Benefit Services, LLC we&apos;re making employee benefits more affordable. In addition to the cost savings you receive when your employer chooses Britton Benefit Services, LLC we also make sure the important information you need is at your fingertips.
</InfoCard>
</div>
<div className='bg-atmosphere rounded-3xl'>
@@ -35,7 +34,7 @@ export default async function Home() {
imageUrl='/images/employer-image.jpg'
invert
>
What we do for employers
At Britton Benefit Services, LLC we are leading the third-party benefits industry by working hard to make employee benefits more affordable for you and your employees. As a third-party administrator, we negotiate with leading PPOs and other organizations to help you find the best program for your employees without sacrificing the bottom line. This creates a major cost savings that you can pass on to your employees.
</InfoCard>
</div>
<div className="w-full">
@@ -44,7 +43,7 @@ export default async function Home() {
learnMoreUrl="/providers"
imageUrl="/images/provider-image.png"
>
What we do for providers
At Britton Benefit Services, LLC it’s our goal to provide you with the prompt response to your inquiry and easy access to the information you need to care for your patients. In addition to 24/7 online support and access to HIPAA compliant patient information, you can find the information you need to verify eligibility, confirm benefits and check claim statuses. Simply select the navigation above to access the forms and information you need.
</InfoCard>
</div>
<div className='bg-light-bronze rounded-3xl'>
@@ -54,7 +53,7 @@ export default async function Home() {
imageUrl="/images/broker-image.jpg"
invert
>
What we do for brokers
At Britton Benefit Services, LLC we have a proven ourselves as leaders in Third-Party Benefits Administration through our performance in finding employers the right self-funded plans that work for them. Whether you need to review a plan document, manage information reports, review provider directories, or you are looking for access to marketing materials, you’ve come to the right place.
</InfoCard>
</div>
<Footer />
@@ -0,0 +1,27 @@
import classnames from 'classnames';
import DashboardHeader from "@/components/DashboardHeader";
import { Schibsted_Grotesk } from 'next/font/google'
import EligibilitySearch from '@/components/EligibilitySearch';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
export default async function EmployerIdCards() {
return (
<>
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName="Eligibility Search"
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<EligibilitySearch />
</div>
</div>
</div>
</div>
</>
);
}
+49
View File
@@ -0,0 +1,49 @@
"user client"
import Image from 'next/image';
import classnames from 'classnames';
import Footer from '@/components/Footer';
import Link from 'next/link';
export default async function PublicLayout({ children }: { children: React.ReactNode }) {
return (
<div className="relative w-full">
<div className="absolute bottom-0 right-0 flex flex-col justify-end items-end w-full h-full">
<div className="w-full -mb-[2px] flex justify-start">
<div className="w-[87vw] sm:w-[91vw] md:w-[93vw] lg:w-[94.5vw] xl:w-[95.5vw] h-20 border-b-2 border-copper z-2" />
</div>
<div className="w-[60px] h-full flex justify-start">
<div className="w-[32.5px] flex flex-col">
<div className={classnames(
"relative h-full border-r-6 rounded-out-tl-4xl rounded-tr-4xl bg-copper border-atmosphere"
)} />
</div>
</div>
</div>
<div className="h-full flex items-start ml-[5vw]">
<div className="w-full min-w-[87vw] max-w-[94vw] lg:min-w-[89vw] lg:max-w-[95vw] flex flex-col items-end">
<div className="w-[90.15vw] h-20 flex justify-between items-center">
<div className={classnames('flex items-center')}>
<div className="-ml-8.5 z-2">
<Link href="/" className="inline-block overflow-hidden rounded-lg">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
</Link>
</div>
</div>
</div>
<div className="w-full min-h-[75vh] flex justify-center ml-[60px]">
{children}
</div>
</div>
</div>
<Footer />
</div>
)
}
+4 -5
View File
@@ -4,9 +4,10 @@ import React, { useState } from 'react'
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { CgProfile } from "react-icons/cg";
import classnames from 'classnames';
import SignOutButton from './SignOut';
interface DropdownProps {
userRole: "member" | "employer";
userRole: string;
}
const AccountDropdown: React.FC<DropdownProps> = ({userRole}) => {
@@ -28,14 +29,12 @@ const AccountDropdown: React.FC<DropdownProps> = ({userRole}) => {
</MenuButton>
<MenuItems anchor="bottom end" className={"py-1 bg-deepcove border-2 border-copper focus:outline-hidden text-platinum text-md text-right font-semibold rounded-lg z-2"}>
<MenuItem as="div" className={"px-3 py-1.5"}>
<a className="block data-focus:text-bluemana hover:text-bluemana" href="/dashboard/member">
<a className="block data-focus:text-bluemana hover:text-bluemana" href={`/dashboard/${userRole}`}>
Dashboard
</a>
</MenuItem>
<MenuItem as="div" className={"px-3 py-1.5"}>
<a className="block data-focus:text-bluemana hover:text-bluemana" href="/">
Sign Out
</a>
<SignOutButton />
</MenuItem>
</MenuItems>
+26
View File
@@ -0,0 +1,26 @@
"use client";
import React from 'react'
import DynamicIcon from './DynamicIcon';
type ArrowTextProps = {
children: React.ReactNode;
};
// interface ArrowTextProps {
// iconName: string;
// };
const ArrowText: React.FC<ArrowTextProps> = ({ children }) => {
return (
<div className="flex space-x-2 h-20">
<DynamicIcon iconName={"FaArrowLeft"} size={30} />
<div className="text-platinum">{ children }</div>
</div>
)
}
export default ArrowText;
+18 -10
View File
@@ -3,14 +3,16 @@
import classnames from 'classnames';
import { Schibsted_Grotesk } from 'next/font/google'
import { useEffect, useState } from 'react';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface BenefitsAAGWidgetProps {
planBenefits: PlanBenefitType[];
planDetails: PlanDetailsType[];
effectiveDate: string;
dependents: string[];
}
interface PlanBenefitType {
interface PlanDetailsType {
id: number;
title: string;
sequence: number;
@@ -18,28 +20,34 @@ interface PlanBenefitType {
benefit_desc: string;
}
const BenefitsAAGWidget: React.FC<BenefitsAAGWidgetProps> = ({ planBenefits }) => {
const BenefitsAAGWidget: React.FC<BenefitsAAGWidgetProps> = ({ planDetails, effectiveDate, dependents }) => {
const planTitle = planBenefits[0]?.title || "Unknown Plan Benefits"
const planTitle = planDetails[0]?.title || "Unknown Plan Benefits"
return (
<div className="2xl:min-w-[32vw] xl:min-w-[25vw] w-full flex flex-col justify-start items-center px-4 py-3 text-xl text-platinum border border-6 border-platinum rounded-4xl">
<p className="text-3xl underline py-2">Benefits At A Glance</p>
<p className="text-3xl underline py-2">Eligibility & Benefits</p>
<p className={classnames("text-2xl font-semibold py-2", font.className)}>{planTitle}</p>
<div className={classnames("w-full flex-col items-between px-4", font.className)}>
{(planBenefits).map((mb) => (
<div key={mb.sequence} className="flex items-center w-full">
{(planDetails).map((pd) => (
<div key={pd.sequence} className="flex items-center w-full">
<div className="flex-none">
{mb.benefit_desc}
{pd.benefit_desc}
</div>
<div className="flex-grow h-[1px] mx-1 mt-1 bg-verdigris"></div>
<div className="flex-none justify-end">
{mb.benefit}
{pd.benefit}
</div>
</div>
))}
</div>
<div className={classnames("mt-6 ml-4 self-start", font.className)}>
Effective Date: {effectiveDate}
</div>
<div className={classnames("mt-4 ml-4 self-start", font.className)}>
Dependents: {dependents.length > 0 ? dependents.join(", ") : "None" }
</div>
</div>
);
};
+51
View File
@@ -0,0 +1,51 @@
'use client';
import classnames from 'classnames';
import { useState } from 'react';
import EmployerMemberSelect from './EmployerMemberSelect';
import ClaimsViewer from './ClaimsViewer';
interface ClaimsSelectorProps {
role: string;
roleId: string;
name: string;
}
const ClaimsSelector: React.FC<ClaimsSelectorProps> = ({ role, roleId, name }) => {
const [selectedMember, setSelectedMember] = useState<string | null>(null);
const handleSelectedEmployer = (): void => {
setSelectedMember(null);
};
const handleSelectedMember = (pbEntityKey: string): void => {
setSelectedMember(pbEntityKey);
};
return (
<div className={classnames("w-full flex flex-col px-2 text-lg")}>
<EmployerMemberSelect
onSelectionCompleteEmployer={handleSelectedEmployer}
onSelectionCompleteMember={handleSelectedMember}
userRole={role}
roleId={roleId}
employerName={name}
/>
{/* {selectedMember && (
<IdCardViewer
cardLaout='FullPageCard'
pbEntityKey={selectedMember}
autoFetchCard
/>
)} */}
{selectedMember && (
<ClaimsViewer
pbEntityKey={selectedMember}
/>
)}
</div>
);
}
export default ClaimsSelector;
+131
View File
@@ -0,0 +1,131 @@
"use client";
import classnames from 'classnames';
import { useState, useEffect } from "react";
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface ClaimType {
claim_number: number;
visit_date: string;
claim_status: string;
patient_name: string;
provider_name: string;
}
interface ClaimsViewerProps {
pbEntityKey: string;
providerTaxId?: string;
}
const ClaimsViewer: React.FC<ClaimsViewerProps> = ({ pbEntityKey, providerTaxId }) => {
const [claimsData, setClaimsData] = useState<ClaimType[]>([]);
const [noClaims, setNoClaims] = useState(false);
const [apiUrl, setapiUrl] = useState("");
const [token, setToken] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => {
setapiUrl(data.apiUrl)
setToken(data.token)
});
}, []);
useEffect(() => {
if (apiUrl) {
handleFetchClaims()
}
}, [apiUrl, pbEntityKey]);
const handleFetchClaims = async () => {
setNoClaims(false)
setClaimsData([])
try {
let query = ''
if (providerTaxId) {
query = `providers/${providerTaxId}/claims/${pbEntityKey}`
} else {
query = `members/${pbEntityKey}/claims`
}
const response = await fetch(`http://${apiUrl}/api/v1/${query}`, {
method: "GET",
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${token}`
},
});
if (!response.ok) throw new Error("Failed to fetch Claims");
const claims = await response.json()
if (claims.length > 0) {
setClaimsData(claims)
} else {
setNoClaims(true)
}
} catch (error) {
console.error("Error fetching Claims:", error);
}
};
return (
<>
{claimsData.length > 0 && (
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-4 font-semibold">
<p>Claims</p>
</div>
<div className={classnames("w-full flex flex-col px-2 text-lg", font.className)}>
<div className='flex w-full mb-4 border-b border-bluemana'>
<div className="flex w-2/20">
Claim #
</div>
<div className="flex w-4/20 pl-3">
Patient Name
</div>
<div className="flex w-6/20 pl-3">
Provider
</div>
<div className="flex w-3/20 pl-3">
Visit Date
</div>
<div className="flex w-4/20 pl-8.5">
Claim Status
</div>
</div>
{(claimsData).map((mc) => (
<div key={mc.claim_number} className="flex w-full text-platinum">
<div className="flex w-2/20">
{mc.claim_number}
</div>
<div className="flex w-4/20 pl-3">
{mc.patient_name}
</div>
<div className="flex w-6/20 pl-3">
{mc.provider_name}
</div>
<div className="flex w-3/20 pl-3">
{mc.visit_date}
</div>
<div className="flex w-4/20 pl-8.5">
{mc.claim_status}
</div>
</div>
))}
</div>
</div>
</div>
)}
{noClaims &&
"No Claims Data Found"
}
</>
);
}
export default ClaimsViewer;
+44
View File
@@ -0,0 +1,44 @@
'use client';
import classnames from 'classnames';
import { useState } from 'react';
import EmployerSelect from './EmployerSelect';
import CoverageViewer from './CoverageViewer';
interface CoverageSelectorProps {
role: string;
roleId: string;
}
const CoverageSelector: React.FC<CoverageSelectorProps> = ({ role, roleId }) => {
const [selectedEmployer, setSelectedEmployer] = useState<string | null>(null);
const handleSelectedEmployer = (plPlanKey: string): void => {
setSelectedEmployer(null);
setSelectedEmployer(plPlanKey);
};
return (
<div className={classnames("w-full flex flex-col px-2 text-lg")}>
<EmployerSelect
onSelectionCompleteEmployer={handleSelectedEmployer}
userRole={role}
roleId={roleId}
/>
{/* {selectedMember && (
<IdCardViewer
cardLaout='FullPageCard'
pbEntityKey={selectedMember}
autoFetchCard
/>
)} */}
{selectedEmployer && (
<CoverageViewer
plPlanKey={selectedEmployer}
/>
)}
</div>
);
}
export default CoverageSelector;
+117
View File
@@ -0,0 +1,117 @@
"use client";
import classnames from 'classnames';
import { useState, useEffect } from "react";
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface CoverageType {
name: string;
pb_entity_key: string;
plan_title: string;
effective_date: string;
dependents: string[];
}
interface CoverageViewerProps {
plPlanKey: string;
}
const CoverageViewer: React.FC<CoverageViewerProps> = ({ plPlanKey }) => {
const [coverage, setCoverage] = useState<CoverageType[]>([]);
const [apiUrl, setapiUrl] = useState("");
const [token, setToken] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => {
setapiUrl(data.apiUrl)
setToken(data.token)
});
}, []);
useEffect(() => {
if (apiUrl) {
handleFetchCoverage()
}
}, [apiUrl, plPlanKey]);
const handleFetchCoverage = async () => {
try {
const response = await fetch(`http://${apiUrl}/api/v1/employers/${plPlanKey}/coverage`, {
method: "GET",
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${token}`
},
});
if (!response.ok) throw new Error("Failed to fetch Coverage");
const coverageData = await response.json()
setCoverage(coverageData)
} catch (error) {
console.error("Error fetching Employer Coverage:", error);
}
};
return (
<>
{coverage.length > 0 && (
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-xl mb-3 font-semibold">
<p>Active Member Coverage</p>
</div>
<div className={classnames("w-full flex-col items-between text-platinum text-sm mb-6", font.className)}>
<div className='flex w-full mb-4 border-b border-bluemana'>
<div className="flex w-4/20">
Member Name
</div>
<div className="flex w-3/20 pl-3">
Member Number
</div>
<div className="flex w-3/20 pl-3">
Plan Title
</div>
<div className="flex w-2/20 pl-3">
Effective Date
</div>
<div className="flex w-8/20 pl-8.5">
Dependents
</div>
</div>
{(coverage).map((cov) => (
<div key={cov.pb_entity_key} className="flex w-full text-platinum mb-1">
<div className="flex w-4/20">
{cov.name}
</div>
<div className="flex w-3/20 pl-3">
{cov.pb_entity_key}
</div>
<div className="flex w-3/20 pl-3">
{cov.plan_title ? cov.plan_title : "N/A" }
</div>
<div className="flex w-2/20 pl-3">
{cov.effective_date}
</div>
<div className="flex w-8/20 pl-8.5">
{cov.dependents.length > 0 ? cov.dependents.join(", ") : "None" }
</div>
</div>
))}
</div>
</div>
</div>
)}
</>
);
}
export default CoverageViewer;
-92
View File
@@ -1,92 +0,0 @@
"use client"
import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts';
type TooltipPayload = ReadonlyArray<any>;
type Coordinate = {
x: number;
y: number;
};
type PieSectorData = {
percent?: number;
name?: string | number;
midAngle?: number;
middleRadius?: number;
tooltipPosition?: Coordinate;
value?: number;
paddingAngle?: number;
dataKey?: string;
payload?: any;
tooltipPayload?: ReadonlyArray<TooltipPayload>;
};
type GeometrySector = {
cx: number;
cy: number;
innerRadius: number;
outerRadius: number;
startAngle: number;
endAngle: number;
};
type PieLabelProps = PieSectorData &
GeometrySector & {
tooltipPayload?: any;
};
const RADIAN = Math.PI / 180;
const COLORS = ['#32CD32', '#E0E0E0'];
const renderCustomizedLabel = ({ cx, cy, midAngle, innerRadius, outerRadius, value }: PieLabelProps) => {
const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
const x = cx + (radius - 25) * Math.cos(-(midAngle ?? 0) * RADIAN);
const y = cy + radius * Math.sin(-(midAngle ?? 0) * RADIAN);
return (
<text x={x} y={y} fill="#04153E" textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central">
{`$${value}`}
</text>
);
};
interface DashboardPieChartProps {
paid: number;
remaining: number;
}
const DashboardPieChart: React.FC<DashboardPieChartProps> = ({paid, remaining}) => {
const data = [
{ name: 'Deductable Paid', value: paid },
{ name: 'Deductable Remaining', value: remaining },
];
return (
<div className="w-full min-h-[135px]">
<ResponsiveContainer width="100%" height="100%">
<PieChart title="Deductable Progress">
<Pie
data={data}
startAngle={180}
endAngle={0}
cx="50%"
cy="100%"
labelLine={false}
label={renderCustomizedLabel}
fill="#2A4B6F"
dataKey="value"
stroke="#04153E"
strokeWidth={2}
>
{data.map((entry, index) => (
<Cell key={`cell-${entry.name}`} fill={COLORS[index % COLORS.length]} />
))}
</Pie>
</PieChart>
</ResponsiveContainer>
</div>
);
};
export default DashboardPieChart;
@@ -1,36 +0,0 @@
"use client";
import React, { useState } from 'react';
import classnames from 'classnames';
import DashboardPieChart from '@/components/DashboardPieChart';
import Button from '@/components/Button';
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface DeductableProgressWidgetProps {
}
const DeductableProgressWidget = () => {
return (
<div className="w-full lg:h-full flex flex-col justify-center items-center text-xl border border-6 border-bluetang rounded-4xl z-1">
<div className="2xl:min-w-[32vw] xl:w-[27vw] lg:w-[27vw] md:w-[27vw] flex flex-col justify-center items-center p-2">
<div className="text-2xl">Deductable Progress</div>
<DashboardPieChart paid={1007.23} remaining={492.77} />
<div className={classnames("w-full flex font-semibold text-center", font.className)}>
<div className="w-[50vw] text-[#32CD32]">Paid</div>
<div className="w-[50vw]">Remaining</div>
</div>
<div className={classnames("w-full flex justify-end text-platinum font-semibold text-base")}>
<Button className="pr-4 underline hover:text-bronze">
View Details
</Button>
</div>
</div>
</div>
);
};
export default DeductableProgressWidget;
+76
View File
@@ -0,0 +1,76 @@
"use client";
import { useState, useEffect } from "react";
import Button from "./Button";
interface DocumentDownloaderProps {
plPlanKey: string;
documentCategoryId: number;
documentCategoryName: string;
}
const DocumentDownloader: React.FC<DocumentDownloaderProps> = ({ plPlanKey, documentCategoryId, documentCategoryName }) => {
const [apiUrl, setapiUrl] = useState("");
const [token, setToken] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => {
setapiUrl(data.apiUrl)
setToken(data.token)
});
}, []);
const handleDownload = async () => {
try {
const response = await fetch(`http://${apiUrl}/api/v1/employers/${plPlanKey}/documents/${documentCategoryId}`, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/zip',
'Authorization': `Bearer ${token}`
},
});
if (!response.ok) throw new Error('Failed to download');
const disposition = response.headers.get('content-disposition') || '';
const filenameMatch = disposition.match(/filename="(.+)"/);
const fileName = filenameMatch ? filenameMatch[1] : 'bbs_document_download.zip';
// Convert the response to a Blob
const blob = await response.blob();
// Create a temporary object URL
const url = window.URL.createObjectURL(blob);
// Create an invisible link and click it
const link = document.createElement('a');
link.href = url;
link.download = fileName;
document.body.appendChild(link);
link.click();
// Clean up memory
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error('Download error:', error);
}
};
return (
<div className="flex flex-col items-center justify-center">
<Button
onClick={handleDownload}
className="text-platinum underline hover:text-bluemana focus:outline-none"
>
Download {documentCategoryName}
</Button>
</div>
);
}
export default DocumentDownloader;
+3 -5
View File
@@ -1,8 +1,6 @@
import React, { useState } from 'react'
import Link from 'next/link';
import classnames from 'classnames';
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { FaAngleDown } from "react-icons/fa";
const userRoles: string[] = ["Member", "Employer"]
@@ -13,7 +11,7 @@ interface DropdownProps {
const Dropdown: React.FC<DropdownProps> = ({currentRole}) => {
const [isHovered, setIsHovered] = useState(false);
let avaliableUserRoles: string[] = userRoles.filter(role => role !== currentRole);
const avaliableUserRoles: string[] = userRoles.filter(role => role !== currentRole);
return (
<div className="">
@@ -31,8 +29,8 @@ const Dropdown: React.FC<DropdownProps> = ({currentRole}) => {
{currentRole}
</MenuButton>
<MenuItems anchor="bottom end" className={"py-2 bg-bluetang border border-copper focus:outline-hidden text-platinum text-md text-left font-semibold rounded-md divide-y divide-[#B8B8B8]"}>
{avaliableUserRoles.map((role) => (
<MenuItem as="div" className={"px-1"}>
{avaliableUserRoles.map((role, i) => (
<MenuItem as="div" className={"px-1"} key={i}>
<a className="block data-focus:text-atmosphere hover:text-atmosphere" href={`/dashboard/${role.toLowerCase()}`}>
Switch To {role} Role
</a>
+4 -3
View File
@@ -1,9 +1,9 @@
"use client";
import React, { type SVGProps } from 'react'
import React from 'react'
import { IconBaseProps, IconType } from 'react-icons';
import { FaRegIdCard, FaStethoscope } from "react-icons/fa";
import { FaRegIdCard, FaStethoscope, FaArrowLeft } from "react-icons/fa";
import { LuLayoutDashboard, LuClipboardList } from "react-icons/lu";
import { IoDocumentsOutline } from "react-icons/io5";
import { BsClipboardCheck } from "react-icons/bs";
@@ -20,7 +20,8 @@ const iconMap: iconMapType = {
LuClipboardList: LuClipboardList,
IoDocumentsOutline: IoDocumentsOutline,
BsClipboardCheck: BsClipboardCheck,
TbProgress: TbProgress
TbProgress: TbProgress,
FaArrowLeft: FaArrowLeft
};
type DynamicIconProps = IconBaseProps & {
+51
View File
@@ -0,0 +1,51 @@
'use client';
import classnames from 'classnames';
import { useState } from 'react';
import EligibilityViewer from './EligibilityViewer';
import MemberNumberDoB from './MemberNumberDoB';
interface EligibilitySearchProps {
skipDobValidation?: boolean;
}
const EligibilitySearch: React.FC<EligibilitySearchProps> = ({ skipDobValidation = false }) => {
const [selectedMember, setSelectedMember] = useState<string | null>(null);
const [notFound, setNotFound] = useState(false);
const handleSelectedMember = (pbEntityKey: string): void => {
setSelectedMember(null);
if (pbEntityKey) {
setNotFound(false)
setSelectedMember(pbEntityKey);
} else {
setNotFound(true)
}
};
return (
<div className={classnames("w-full flex flex-col px-2 text-lg")}>
<MemberNumberDoB
onSelectionCompleteMember={handleSelectedMember}
skipDobValidation={skipDobValidation}
/>
{/* {selectedMember && (
<IdCardViewer
cardLaout='FullPageCard'
pbEntityKey={selectedMember}
autoFetchCard
/>
)} */}
{selectedMember && (
<EligibilityViewer
pbEntityKey={selectedMember}
/>
)}
{notFound && (
"Eligible Member not Found"
)}
</div>
);
}
export default EligibilitySearch;
+51
View File
@@ -0,0 +1,51 @@
'use client';
import classnames from 'classnames';
import { useState } from 'react';
import EmployerMemberSelect from './EmployerMemberSelect';
import EligibilityViewer from './EligibilityViewer';
interface EligibilitySelectorProps {
role: string;
roleId: string;
name: string;
}
const EligibilitySelector: React.FC<EligibilitySelectorProps> = ({ role, roleId, name }) => {
const [selectedMember, setSelectedMember] = useState<string | null>(null);
const handleSelectedEmployer = (): void => {
setSelectedMember(null);
};
const handleSelectedMember = (pbEntityKey: string): void => {
setSelectedMember(pbEntityKey);
};
return (
<div className={classnames("w-full flex flex-col px-2 text-lg")}>
<EmployerMemberSelect
onSelectionCompleteEmployer={handleSelectedEmployer}
onSelectionCompleteMember={handleSelectedMember}
userRole={role}
roleId={roleId}
employerName={name}
/>
{/* {selectedMember && (
<IdCardViewer
cardLaout='FullPageCard'
pbEntityKey={selectedMember}
autoFetchCard
/>
)} */}
{selectedMember && (
<EligibilityViewer
pbEntityKey={selectedMember}
/>
)}
</div>
);
}
export default EligibilitySelector;
+121
View File
@@ -0,0 +1,121 @@
"use client";
import classnames from 'classnames';
import { useState, useEffect } from "react";
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface PlanBenefitType {
id: number;
title: string;
sequence: number;
benefit: string;
benefit_desc: string;
}
interface EligibilityViewerProps {
pbEntityKey: string;
}
const EligibilityViewer: React.FC<EligibilityViewerProps> = ({ pbEntityKey }) => {
const [planBenefits, setPlanBenefits] = useState<PlanBenefitType[]>([]);
const [effDate, setEffDate] = useState("");
const [dependents, setDependents] = useState([]);
const [apiUrl, setapiUrl] = useState("");
const [token, setToken] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => {
setapiUrl(data.apiUrl)
setToken(data.token)
});
}, []);
useEffect(() => {
if (apiUrl) {
handleFetchElegibility()
}
}, [apiUrl, pbEntityKey]);
const handleFetchElegibility = async () => {
try {
const response = await fetch(`http://${apiUrl}/api/v1/members/${pbEntityKey}/plan_benefits`, {
method: "GET",
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${token}`
},
});
if (!response.ok) throw new Error("Failed to fetch PDF");
const eligibilityData = await response.json()
setPlanBenefits(eligibilityData.plan_details)
setEffDate(eligibilityData.effective_date)
if (eligibilityData.dependents){
setDependents(eligibilityData.dependents)
}
} catch (error) {
console.error("Error fetching PDF:", error);
}
};
return (
<>
{planBenefits && (
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
<div className="w-full flex text-3xl ml-2 mb-3 font-semibold">
<p>Eligibility</p>
</div>
<div className={classnames("w-3/4 flex justify-between text-xl text-platinum ml-4 mb-6 font-semibold", font.className)}>
<div className=''>
Plan Name: {planBenefits[0]?.title ? planBenefits[0].title : "N/A" }
</div>
<div className=''>
Effective Date: {effDate}
</div>
<div className=''>
Dependents: {dependents.length > 0 ? dependents.join(", ") : "None" }
</div>
</div>
<div className="w-full flex text-3xl ml-2 mb-3 font-semibold">
<p>Benefits</p>
</div>
<div className={classnames("w-3/4 flex-col items-between text-platinum mb-6 px-4", font.className)}>
{planBenefits.length == 0 &&
<div>
Contact Britton Benefits for plan benefit details.
</div>
}
{planBenefits.length > 0 &&
<>
{(planBenefits).map((pb) => (
<div key={pb.sequence} className="flex items-center w-full">
<div className="flex-none">
{pb.benefit_desc}
</div>
<div className="flex-grow h-[1px] mx-1 mt-1 bg-verdigris"></div>
<div className="flex-none justify-end">
{pb.benefit}
</div>
</div>
))}
</>
}
</div>
</div>
</div>
)}
</>
);
}
export default EligibilityViewer;
+66 -45
View File
@@ -9,17 +9,15 @@ interface SelectOption {
}
interface EmployerMemberSelectProps {
onSelectionCompleteEmployer: (employerValue: string) => void;
onSelectionCompleteMember: (memberValue: string) => void;
userRole: 'employer' | 'broker' | 'carrier';
employerAllButtonText: string;
plPlanKey?: string;
onSelectionCompleteEmployer: (plPlanKey: string) => void;
onSelectionCompleteMember: (pbEntityKey: string) => void;
userRole: string;
roleId: string;
employerAllButtonText?: string;
employerName?: string;
brokerId?: number;
carrierId?: number;
}
const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelectionCompleteEmployer, onSelectionCompleteMember, userRole, employerAllButtonText, plPlanKey, employerName, brokerId, carrierId }) => {
const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelectionCompleteEmployer, onSelectionCompleteMember, userRole, roleId, employerAllButtonText, employerName }) => {
const [optionsEmployer, setOptionsEmployer] = useState<SelectOption[]>([]);
const [selectedEmployer, setSelectedEmployer] = useState<string>('');
const [loadingEmployer, setLoadingEmployer] = useState<boolean>(false);
@@ -28,36 +26,48 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
const [selectedMember, setSelectedMember] = useState<string>('');
const [loadingMember, setLoadingMember] = useState<boolean>(false);
const [apiUrl, setapiUrl] = useState("");
const [apiUrl, setApiUrl] = useState("");
const [token, setToken] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => setapiUrl(data.apiUrl));
.then((data) => {
setApiUrl(data.apiUrl)
setToken(data.token)
});
}, []);
useEffect(() => {
async function fetchOptionsEmployer(role: string, roleId: number) {
setLoadingEmployer(true);
try {
const response = await fetch(`http://${apiUrl}/api/v1/${role}/${roleId}/employers_list`, {
method: "GET",
});
const data = await response.json();
setOptionsEmployer(data);
} catch (error) {
console.error('Error fetching employer options:', error);
} finally {
setLoadingEmployer(false);
if (apiUrl) {
async function fetchOptionsEmployer(role: string, roleId: string) {
setLoadingEmployer(true);
try {
const response = await fetch(`http://${apiUrl}/api/v1/${role}/${roleId}/employers_list`, {
method: "GET",
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${token}`
},
});
const data = await response.json();
setOptionsEmployer(data);
} catch (error) {
console.error('Error fetching employer options:', error);
} finally {
setLoadingEmployer(false);
}
}
if (userRole == "employer" && employerName) {
setSelectedEmployer(roleId)
setOptionsEmployer([{entity_key: roleId, name: employerName}]);
} else if (userRole == "broker") {
fetchOptionsEmployer("brokers", roleId);
} else if (userRole == "carrier") {
fetchOptionsEmployer("carriers", roleId);
}
}
if (plPlanKey && employerName) {
setSelectedEmployer(plPlanKey)
setOptionsEmployer([{entity_key: plPlanKey, name: employerName}]);
} else if (brokerId) {
fetchOptionsEmployer("brokers", brokerId);
} else if (carrierId) {
fetchOptionsEmployer("carriers", carrierId);
}
}, [apiUrl]);
@@ -73,6 +83,11 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
try {
const response = await fetch(`http://${apiUrl}/api/v1/employers/${selectedEmployer}/members_list`, {
method: "GET",
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${token}`
},
});
const data = await response.json();
setOptionsMember(data);
@@ -112,17 +127,16 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
};
return (
<div className="flex flex-col gap-4 max-w-xs">
<div className="flex flex-col w-full gap-4">
{/* First Select Dropdown */}
{!plPlanKey &&
<div className="flex flex-col gap-1">
<label className="text-sm font-medium">Employer</label>
<div className="flex">
<div className="flex flex-col w-full gap-1">
<label className="block text-md font-bold text-platinum">Employer</label>
<div className="flex w-full">
<select
value={selectedEmployer}
onChange={handleFirstChange}
disabled={loadingEmployer}
className="border p-2 rounded"
className="appearance-none rounded-none relative block w-1/4 px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove text-lg rounded-tl-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10"
>
<option value="">{loadingEmployer ? 'Loading...' : '-- Select Employer --'}</option>
{optionsEmployer.map((opt) => (
@@ -131,31 +145,34 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
</option>
))}
</select>
{employerAllButtonText &&
<Button
onClick={handleAllEmployerSelect}
className='w-1/6'
>
<div className="flex justify-center items-center rounded-r-2">
<div className="flex justify-center items-center h-full w-full hover:text-deepcove rounded-r-2 border-2 border-bluemana hover:bg-bluemana px-2 rounded-tr-md">
{employerAllButtonText}
</div>
</Button>
}
</div>
</div>
}
{/* Second Select Dropdown */}
{selectedEmployer &&
<div className="flex flex-col gap-1">
<label className="text-sm font-medium">Member</label>
<div className="flex">
<label className="block text-md font-bold text-platinum">Member</label>
<div className="flex w-full">
<select
value={selectedMember}
onChange={handleSecondChange}
disabled={!selectedEmployer || loadingMember}
className="border p-2 rounded"
className="appearance-none rounded-none relative block w-1/4 px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove text-lg rounded-bl-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10"
>
<option value="">
{!selectedEmployer
? '-- Select a category first --'
: loadingMember ? 'Loading...' : '-- Select Subcategory --'}
: loadingMember ? 'Loading...' : '-- Select Member --'}
</option>
{optionsMember.map((opt) => (
<option key={opt.entity_key} value={opt.entity_key}>
@@ -164,14 +181,18 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
))}
</select>
<Button
onClick={handleAllEmployerSelect}
onClick={handleMemberSelect}
className='w-1/6'
>
<div className="flex justify-center items-center rounded-r-2">
Select Member
<div className="flex justify-center items-center h-full w-full hover:text-deepcove rounded-r-2 border-2 border-bluemana hover:bg-bluemana px-2 rounded-br-md">
Generate Member
</div>
</Button>
</div>
</div>
}
</div>
);
}
export default EmployerMemberSelect;
+107
View File
@@ -0,0 +1,107 @@
'use client';
import { useState, useEffect, ChangeEvent } from 'react';
import Button from './Button';
interface SelectOption {
entity_key: string;
name: string;
}
interface EmployerSelectProps {
onSelectionCompleteEmployer: (plPlanKey: string) => void;
userRole: string;
roleId: string;
}
const EmployerSelect: React.FC<EmployerSelectProps> = ({ onSelectionCompleteEmployer, userRole, roleId }) => {
const [optionsEmployer, setOptionsEmployer] = useState<SelectOption[]>([]);
const [selectedEmployer, setSelectedEmployer] = useState<string>('');
const [loadingEmployer, setLoadingEmployer] = useState<boolean>(false);
const [apiUrl, setApiUrl] = useState("");
const [token, setToken] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => {
setApiUrl(data.apiUrl)
setToken(data.token)
});
}, []);
useEffect(() => {
if (apiUrl) {
async function fetchOptionsEmployer(role: string, roleId: string) {
setLoadingEmployer(true);
try {
const response = await fetch(`http://${apiUrl}/api/v1/${role}/${roleId}/employers_list`, {
method: "GET",
credentials: 'include',
headers: {
'Content-Type': 'application/json' ,
'Authorization': `Bearer ${token}`
},
});
const data = await response.json();
setOptionsEmployer(data);
} catch (error) {
console.error('Error fetching employer options:', error);
} finally {
setLoadingEmployer(false);
}
}
if (userRole == "broker") {
fetchOptionsEmployer("brokers", roleId);
} else if (userRole == "carrier") {
fetchOptionsEmployer("carriers", roleId);
}
}
}, [apiUrl]);
const handleFirstChange = (e: ChangeEvent<HTMLSelectElement>) => {
const val = e.target.value;
setSelectedEmployer(val);
};
const handleAllEmployerSelect = () => {
if (selectedEmployer) {
onSelectionCompleteEmployer(selectedEmployer);
}
};
return (
<div className="flex flex-col w-full gap-4">
<div className="flex flex-col w-full gap-1">
<label className="block text-md font-bold text-platinum">Employer</label>
<div className="flex w-full">
<select
value={selectedEmployer}
onChange={handleFirstChange}
disabled={loadingEmployer}
className="appearance-none rounded-none relative block w-1/4 px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove text-lg rounded-tl-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10"
>
<option value="">{loadingEmployer ? 'Loading...' : '-- Choose Employer --'}</option>
{optionsEmployer.map((opt) => (
<option key={opt.entity_key} value={opt.entity_key}>
{opt.name}
</option>
))}
</select>
<Button
onClick={handleAllEmployerSelect}
className='w-1/6'
>
<div className="flex justify-center items-center h-full w-full hover:text-deepcove rounded-r-2 border-2 border-bluemana hover:bg-bluemana px-2 rounded-tr-md">
Select Employer
</div>
</Button>
</div>
</div>
</div>
);
}
export default EmployerSelect;
-7
View File
@@ -3,19 +3,12 @@
import Button from '@/components/Button';
import Image from 'next/image';
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
import Link from 'next/link';
interface FindProviderWidgetProps {
networkProvider: string;
}
interface NetworkProvider {
network_provider: string;
}
const FindProviderWidget: React.FC<FindProviderWidgetProps> = ({ networkProvider }) => {
return (
-57
View File
@@ -1,57 +0,0 @@
"use client";
import classnames from 'classnames';
import Button from '@/components/Button';
import { MdLocationOn } from "react-icons/md";
import LocationSearchInput from '@/components/LocationSearchInput';
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface FindProviderProps {
zipOnly?: boolean;
}
const FindProvider: React.FC<FindProviderProps> = ({ zipOnly }) => {
return (
<div className="2xl:min-w-[48vw] xl:min-w-[45vw] flex z-1">
<div className="w-full flex flex-col px-4 py-3 border border-6 border-bluemana rounded-4xl">
<div className="w-full pb-2 flex justify-center text-2xl text-bronze font-semibold">
<p>Find A Provider By...</p>
</div>
<div className="w-full flex px-2">
<div className="w-full flex border-x-2 border-t-2 rounded-t-xl font-semibold divide-x-2 divide-bronze">
<a href="#" className="w-full h-9 flex justify-center items-center rounded-tl-lg bg-bluetang text-platinum">
Location
</a>
<a href="#" className="w-full h-9 flex justify-center items-center text-platinum hover:text-bronze">
Procedure
</a>
<a href="#" className="w-full h-9 flex justify-center items-center rounded-tr-lg text-platinum hover:text-bronze">
Provider/Facility
</a>
</div>
</div>
<div className={classnames("w-full flex items-start px-2 pb-2 text-xl", font.className)}>
{/* <p className='pr-3'><GrMapLocation size={30} /></p> */}
<Button className="h-10 text-deepcove hover:text-atmosphere bg-platinum rounded-bl-xl">
<MdLocationOn size={30} />
</Button>
<LocationSearchInput className="h-10 pl-3 text-bluemana border-y-2 border-platinum" />
<Button className="h-10 pr-3 pl-2 bg-platinum hover:bg-bluemana text-center text-deepcove font-semibold rounded-br-xl border-y-2 border-r-2 border-platinum">
Search
</Button>
</div>
<div className="w-full flex justify-end text-platinum font-semibold">
<Button className="mr-2 underline hover:text-bronze">
View Full Provider List
</Button>
</div>
</div>
</div>
);
};
export default FindProvider;
-7
View File
@@ -1,14 +1,7 @@
"use client";
import classnames from 'classnames';
import Button from '@/components/Button';
import { FaRegIdCard } from "react-icons/fa";
import Image from 'next/image';
import { Schibsted_Grotesk } from 'next/font/google'
import { useEffect, useState } from 'react';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
import Link from 'next/link';
const FindRxInformationWidget = () => {
+32 -28
View File
@@ -1,6 +1,5 @@
import Image from 'next/image';
import Link from 'next/link';
import { BsLinkedin } from "react-icons/bs";
import { Schibsted_Grotesk, Fraunces } from 'next/font/google'
import classnames from 'classnames';
@@ -11,29 +10,31 @@ const Footer = () => {
return (
<>
<footer className="m-h-[135vh] bg-deepcove flex justify-center py-30">
<div className={classnames("w-11/12 md:flex md:items-center md:justify-evenly text-platinum", font.className)}>
<div className={classnames("w-11/12 md:flex md:items-center md:justify-evenly text-platinum z-4", font.className)}>
<div className='w-1/2 h-full flex flex-col justify-between'>
<div className='w-full flex justify-start'>
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={410}
height={120}
/>
<Link href="/" className="inline-block overflow-hidden rounded-lg">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={410}
height={120}
/>
</Link>
</div>
<div className='w-full flex flex-col justify-start ml-[22px]'>
<div className='flex'>
<Link href="https://www.linkedin.com/company/discovery-insurance">
{/* <Link href="https://www.linkedin.com/company/discovery-insurance">
<BsLinkedin size={32} className='hover:text-atmosphere' />
</Link>
</Link> */}
</div>
<ul className="flex flex-col pt-2 text-md font-semibold">
<li className='pt-1'>
{/* <li className='pt-1'>
<a href="#" className="hover:text-atmosphere">Terms of Service</a>
</li>
<li className='pt-1'>
<a href="#" className="hover:text-atmosphere">Website Privacy Statement</a>
</li>
</li> */}
<li className='pt-3 font-normal text-xs'>
© Copyright 2025 Britton Benefit Services, LLC All Rights Reserved
<br />
@@ -45,7 +46,7 @@ const Footer = () => {
<div className='w-1/4 h-full flex flex-col justify-start pt-5'>
<div className={classnames("text-xl text-bluemana pb-12", headerFont.className)}>
<div className="pb-6">
<a href="#" className="hover:text-bronze">Log In?</a>
<a href="/account/signin" className="hover:text-bronze">Sign In?</a>
</div>
<div className="text-xl border-b border-bronze mt-4 pb-2 mr-20">Resources</div>
</div>
@@ -54,13 +55,16 @@ const Footer = () => {
Members
</li>
<li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Members Landing</a>
<a href="/members" className='hover:text-bluemana'>Members Landing</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Find a Medical Provider</a>
<a href="http://medcost.com" className='hover:text-bluemana'>Find a MedCost Medical Provider</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Obtain Rx Information</a>
<a href="http://cigna.com" className='hover:text-bluemana'>Find a Cigna Medical Provider</a>
</li>
<li className='pt-2'>
<a href="https://www.fairosrx.com/" className='hover:text-bluemana'>Obtain Rx Information</a>
</li>
</ul>
<ul className="pt-7 text-md font-medium">
@@ -68,9 +72,9 @@ const Footer = () => {
Employers
</li>
<li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Employers Landing</a>
<a href="/employers" className='hover:text-bluemana'>Employers Landing</a>
</li>
<li className='pt-2'>
{/* <li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Self-Funded Plans</a>
</li>
<li className='pt-2'>
@@ -78,45 +82,45 @@ const Footer = () => {
</li>
<li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Control Health Care Costs</a>
</li>
</li> */}
</ul>
<ul className="pt-7 text-md font-medium">
<li className="text-bronze mb-4">
Providers
</li>
<li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Providers Landing</a>
<a href="/providers" className='hover:text-bluemana'>Providers Landing</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Eligibility Lookup</a>
<a href="/public/eligibility-lookup" className='hover:text-bluemana'>Eligibility Lookup</a>
</li>
<li className='pt-2'>
{/* <li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Contact BBS</a>
</li>
</li> */}
</ul>
<ul className="pt-7 text-md font-medium">
<li className="text-bronze mb-4">
Brokers
</li>
<li className='pt-2'>
<a href="#" className='hover:text-bluemana'>Brokers Landing</a>
<a href="/brokers" className='hover:text-bluemana'>Brokers Landing</a>
</li>
</ul>
</div>
<div className='w-1/4 h-full flex flex-col justify-start pt-5'>
<div className='w-1/4 h-full flex flex-col justify-start pt-11.5'>
<div className={classnames("text-xl text-bluemana pb-12", headerFont.className)}>
<div className="pb-6">
<a href="#" className="hover:text-bronze">Get a Quote</a>
{/* <a href="#" className="hover:text-bronze">Get a Quote</a> */}
</div>
<div className="text-xl border-b border-bronze mt-4 pb-2 mr-20">Company</div>
</div>
<ul className="pt-3 text-md font-medium">
<li className='pt-3'>
{/* <li className='pt-3'>
<a href="#" className='hover:text-bluemana' >About</a>
</li>
<li className='pt-6'>
<a href="#" className='hover:text-bluemana' >Careers</a>
</li>
</li> */}
<li className='pt-6'>
<a href="#" className='hover:text-bluemana' >Contact Us</a>
</li>
+28 -46
View File
@@ -3,46 +3,48 @@
import { useState, useEffect } from "react";
interface IdCardDownloaderProps {
plPlanKey: number;
plPlanKey: string;
autoFetchCard: boolean;
}
const IdCardDownloader: React.FC<IdCardDownloaderProps> = ({ plPlanKey }) => {
const IdCardDownloader: React.FC<IdCardDownloaderProps> = ({ plPlanKey, autoFetchCard }) => {
const [loading, setLoading] = useState(false);
const [apiUrl, setapiUrl] = useState("");
const [token, setToken] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => setapiUrl(data.apiUrl));
.then((data) => {
setapiUrl(data.apiUrl)
setToken(data.token)
});
}, []);
useEffect(() => {
if (apiUrl && autoFetchCard) {
handleDownload()
}
}, [apiUrl, plPlanKey]);
const handleDownload = async () => {
setLoading(true);
try {
const response = await fetch(`http://${apiUrl}/api/v1/employers/${plPlanKey}/id_cards`, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/zip',
'Authorization': `Bearer ${token}`
},
});
if (!response.ok) throw new Error('Failed to download');
const contentDisposition = response.headers.get('Content-Disposition');
console.log("contentDisposition: " + contentDisposition);
let fileName = 'employer_id_cards.zip'; // Default fallback name
// 2. Parse the filename using a regular expression
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (filenameMatch && filenameMatch[1]) {
// Remove surrounding quotes if they exist
fileName = filenameMatch[1].replace(/['"]/g, '');
}
}
console.log(fileName)
const disposition = response.headers.get('content-disposition') || '';
const filenameMatch = disposition.match(/filename="(.+)"/);
const fileName = filenameMatch ? filenameMatch[1] : 'employer_id_cards.zip';
// Convert the response to a Blob
const blob = await response.blob();
@@ -69,13 +71,15 @@ const IdCardDownloader: React.FC<IdCardDownloaderProps> = ({ plPlanKey }) => {
return (
<div className="flex flex-col items-center justify-center">
<button
onClick={handleDownload}
disabled={loading}
className="text-platinum underline hover:text-bluemana focus:outline-none"
>
Download Employer Cards
</button>
{!autoFetchCard && (
<button
onClick={handleDownload}
disabled={loading}
className="text-platinum underline hover:text-bluemana focus:outline-none"
>
Download Employer Cards
</button>
)}
{(loading) && (
<div className="fixed inset-x-1/6 inset-y-1/12 flex items-center justify-center w-2/3 h-[80vh] bg-atmosphere border-2 border-platinum shadow-md rounded-lg z-2">
@@ -96,28 +100,6 @@ const IdCardDownloader: React.FC<IdCardDownloaderProps> = ({ plPlanKey }) => {
)}
</div>
)}
{/* Loading Animation */}
{/* {loading && (
<div className="flex h-[70vh] w-screen items-center gap-2 text-gray-500 animate-pulse z-2">
<svg className="animate-spin h-10 w-10 text-blue-600" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
Fetching ID Card, Please Wait...
</div>
)} */}
{/* Display PDF */}
{/* {pdfUrl && (
<div className="w-screen h-[70vh] border border-gray-300 rounded shadow-lg overflow-hidden z-2">
<iframe
src={pdfUrl}
title="PDF Document"
className="w-full h-full"
/>
</div>
)} */}
</div>
);
}
+58
View File
@@ -0,0 +1,58 @@
'use client';
import classnames from 'classnames';
import { useState } from 'react';
import EmployerMemberSelect from './EmployerMemberSelect';
import IdCardViewer from './IdCardViewer';
import IdCardDownloader from './IdCardDownloader';
interface IdCardSelectorProps {
role: string;
roleId: string;
name: string;
}
const IdCardSelector: React.FC<IdCardSelectorProps> = ({ role, roleId, name }) => {
const [selectedEmployer, setSelectedEmployer] = useState<string | null>(null);
const [selectedMember, setSelectedMember] = useState<string | null>(null);
const handleSelectedEmployer = (plPlanKey: string): void => {
setSelectedMember(null);
setSelectedEmployer(null);
setSelectedEmployer(plPlanKey);
};
const handleSelectedMember = (pbEntityKey: string): void => {
setSelectedEmployer(null);
setSelectedMember(null);
setSelectedMember(pbEntityKey);
};
return (
<div className={classnames("w-full flex flex-col px-2 text-lg")}>
<EmployerMemberSelect
onSelectionCompleteEmployer={handleSelectedEmployer}
onSelectionCompleteMember={handleSelectedMember}
userRole={role}
roleId={roleId}
employerName={name}
employerAllButtonText={"Generate All"}
/>
{selectedMember && (
<IdCardViewer
cardLaout='FullPageCard'
pbEntityKey={selectedMember}
autoFetchCard
/>
)}
{selectedEmployer && (
<IdCardDownloader
plPlanKey={selectedEmployer}
autoFetchCard
/>
)}
</div>
);
}
export default IdCardSelector;
+39 -18
View File
@@ -6,27 +6,44 @@ import { FaRegIdCard } from "react-icons/fa";
interface IdCardViewerProps {
cardLaout: 'FullPageCard' | 'MobileDisplayCard';
pbEntityKey: number;
pbEntityKey: string;
autoFetchCard?: boolean;
}
const IdCardViewer: React.FC<IdCardViewerProps> = ({ cardLaout, pbEntityKey }) => {
const IdCardViewer: React.FC<IdCardViewerProps> = ({ cardLaout, pbEntityKey, autoFetchCard }) => {
const [loading, setLoading] = useState(false);
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [apiUrl, setapiUrl] = useState("");
const [token, setToken] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => setapiUrl(data.apiUrl));
.then((data) => {
setapiUrl(data.apiUrl)
setToken(data.token)
});
}, []);
useEffect(() => {
if (apiUrl && autoFetchCard) {
handleFetchPdf()
}
}, [apiUrl, pbEntityKey]);
const handleFetchPdf = async () => {
setLoading(true);
setPdfUrl(null);
console.log(token)
try {
const response = await fetch(`http://${apiUrl}/api/v1/members/${pbEntityKey}/id_card/${cardLaout}`, {
method: "GET",
credentials: 'include',
headers: {
'Accept': 'application/pdf',
'Authorization': `Bearer ${token}`
},
});
if (!response.ok) throw new Error("Failed to fetch PDF");
@@ -51,22 +68,26 @@ const IdCardViewer: React.FC<IdCardViewerProps> = ({ cardLaout, pbEntityKey }) =
};
return (
<div className="2xl:min-w-[16vw] xl:min-w-[15vw] w-full h-full flex lg:flex-col justify-center items-center text-xl space-x-10 lg:space-x-0 border border-6 border-atmosphere rounded-4xl">
<div className="flex flex-col justify-end items-center mb-4">
<div className="flex items-end mb-3">
<div className="text-platinum font-semibold">View Temporary ID Card</div>
<>
{!autoFetchCard && (
<div className="2xl:min-w-[16vw] xl:min-w-[15vw] w-full h-full flex lg:flex-col justify-center items-center text-xl space-x-10 lg:space-x-0 border border-6 border-atmosphere rounded-4xl">
<div className="flex flex-col justify-end items-center mb-4">
<div className="flex items-end mb-3">
<div className="text-platinum font-semibold">View Temporary ID Card</div>
</div>
<div className="flex items-end">
<div className="text-deepcove -mb-11 font-semibold z-1">My Card</div>
</div>
<Button
onClick={handleFetchPdf}
disabled={loading}
className="focus:outline-none"
>
<FaRegIdCard size={150} className="text-bluemana hover:text-light-bronze" />
</Button>
</div>
<div className="flex items-end">
<div className="text-deepcove -mb-11 font-semibold z-1">My Card</div>
</div>
<Button
onClick={handleFetchPdf}
disabled={loading}
className="focus:outline-none"
>
<FaRegIdCard size={150} className="text-bluemana hover:text-light-bronze" />
</Button>
</div>
)}
{/* <div className="flex flex-col items-center justify-center">
<Button
@@ -143,7 +164,7 @@ const IdCardViewer: React.FC<IdCardViewerProps> = ({ cardLaout, pbEntityKey }) =
/>
</div>
)} */}
</div>
</>
);
}
+1 -1
View File
@@ -76,7 +76,7 @@ const InfoCard: React.FC<InfoCardProps> = (
</div>
</div>
<div className={classnames(
"relative xl:text-xl 2xl:text-2xl text-left text-platinum font-medium p-8 h-full",
"relative flex flex-col justify-center xl:text-xl 2xl:text-2xl text-left text-platinum font-medium p-8 h-full",
cardBGColor, font.className,
{
"rounded-l-2xl rounded-out-rt-2xl" : !invert,
+3 -2
View File
@@ -2,12 +2,13 @@ import classnames from 'classnames';
interface InlineLinkProps {
children: React.ReactNode;
link: string;
alt?: boolean;
}
const InlineLink: React.FC<InlineLinkProps> = ({ children, alt }) => {
const InlineLink: React.FC<InlineLinkProps> = ({ children, alt, link }) => {
return (
<a href="#" className={classnames(
<a href={link} className={classnames(
{
"text-bluemana hover:text-bronze" : !alt,
"text-bluemana hover:text-atmosphere" : alt,
-71
View File
@@ -1,71 +0,0 @@
"use client";
import classnames from 'classnames';
import Button from '@/components/Button';
import { FaRegIdCard } from "react-icons/fa";
import IdCardViewer from '@/components/IdCardViewer';
import { Schibsted_Grotesk } from 'next/font/google'
import IdCardDownloader from './IdCardDownloader';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface InsuranceCardWidgettProps {
pbEntityKey?: number;
plPlanKey?: number;
}
const InsuranceCardWidget: React.FC<InsuranceCardWidgettProps> = ({ pbEntityKey, plPlanKey }) => {
return (
<div className="2xl:min-w-[16vw] xl:min-w-[15vw] w-full h-full flex lg:flex-col justify-center items-center text-md space-x-10 lg:space-x-0 border border-6 border-atmosphere rounded-4xl">
<div className="flex flex-col justify-end items-center">
<div className="flex items-end">
<div className="text-deepcove -mb-11 font-semibold z-1">My Card</div>
</div>
<FaRegIdCard size={150} className="text-bluemana" />
</div>
<div className={classnames("flex w-full items-center space-x-1", font.className)}>
{/* <Button className="hover:text-bronze">
View
</Button> */}
{/* <div className={classnames("flex w-[45%] justify-end")}>
<IdCardViewer
pbEntityKey={pbEntityKey}
cardLaout="FullPageCard"
/>
</div> */}
{/* <div className={classnames("flex w-[10%] justify-center")}>
|
</div> */}
{/* <p>|</p> */}
{/* <div className={classnames("flex w-[45%] justify-start")}>
<IdCardViewer
pbEntityKey={pbEntityKey}
cardLaout="MobileDisplayCard"
/>
</div> */}
{/* <Button className="hover:text-bronze">
Download
</Button> */}
</div>
{pbEntityKey &&
<div className={classnames("flex w-[45%] justify-center")}>
<IdCardViewer
pbEntityKey={pbEntityKey}
cardLaout="FullPageCard"
/>
</div>
}
{plPlanKey &&
<div className={classnames("flex w-full justify-center mb-4")}>
<IdCardDownloader
plPlanKey={plPlanKey}
/>
</div>
}
</div>
);
};
export default InsuranceCardWidget;
-88
View File
@@ -1,88 +0,0 @@
import { Schibsted_Grotesk } from 'next/font/google'
import classnames from 'classnames';
import InlineLink from "../components/InlineLink";
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface LandingPageProps {
children: React.ReactNode;
imageUrl: string;
headerText: string;
}
const LandingPage: React.FC<LandingPageProps> = ({
children,
imageUrl,
headerText
}) => {
return (
<div className="relative w-full">
<div className="absolute bottom-0 left-0 flex items-end w-full h-full">
<div className="w-1/24 h-full flex justify-end">
<div className="w-[calc(50%+6px)] flex flex-col">
<div className={classnames(
"relative h-full border-l-6 rounded-out-br-4xl bg-bluetang border-atmosphere"
)} />
</div>
</div>
</div>
<div className="w-full flex justify-start items-end pt-30">
<div className="w-1/24 h-full flex justify-end">
<div className="w-[calc(50%+6px)] flex flex-col justify-end z-1 bg-deepcove">
<div className={classnames(
"h-5 -mb-[3px] relative border-b-6 border-l-6 rounded-bl-3xl rounded-out-br-3xl bg-bluetang border-atmosphere"
)} />
<div className={classnames(
"h-5 -mt-[3px] relative border-t-6 border-l-6 rounded-tl-3xl rounded-out-tr-3xl bg-bluetang border-atmosphere"
)} />
</div>
</div>
<div className={`w-11/12 flex flex-col justify-end mb-[14px]`}>
<div className={classnames(
"text-5xl text-left ml-10 z-1 text-shadow-[1px_1px_0,-1px_1px_0,-1px_-1px_0,1px_-1px_0] text-shadow-deepcove text-bronze"
)}>
{headerText}
</div>
<div className={classnames(
"w-full h-[6px] rounded-r bg-atmosphere"
)}>
</div>
</div>
</div>
<div className="w-full flex justify-center pb-30">
<div className="w-11/12 z-1">
<div className="relative w-full flex justify-start pl-10 py-5">
<div className="absolute flex flex-col justify-center left-0 bottom-0 w-full h-full">
<div className={classnames("relative h-80 w-full rounded-r-2xl rounded-out-lt-3xl rounded-out-lb-3xl border-r-2 bg-bluetang border-atmosphere")} />
</div>
<div className={classnames("w-3/5 flex flex-col justify-center text-platinum z-2")}>
<div className={classnames("text-4xl pb-8")}>
Quick Access to Your Services
</div>
<div className={classnames("text-2xl leading-12", font.className)}>
• <InlineLink>{headerText} Login/Registration</InlineLink><br />
• <InlineLink>Link to Thing 1</InlineLink><br />
• <InlineLink>Link to Thing 2</InlineLink><br />
• <InlineLink>Link to another Thing</InlineLink><br />
</div>
</div>
<div className={classnames("w-2/5 flex justify-center mr-10 rounded-2xl z-2 bg-atmosphere")}>
<div className={classnames(
"relative w-9/10"
)}>
<img src={imageUrl} alt="Description" className="grayscale rounded-2xl bg-cover bg-center" />
<div className="after:absolute after:inset-0 after:bg-bluetang after:mix-blend-color after:rounded-2xl" />
</div>
</div>
</div>
<div className={classnames("w-full text-2xl px-10", font.className)}>
{children}
</div>
</div>
</div>
</div>
);
};
export default LandingPage;
-86
View File
@@ -1,86 +0,0 @@
"use client"
import React, { useState } from 'react';
import PlacesAutocomplete, {
geocodeByAddress,
getLatLng,
} from 'react-places-autocomplete';
interface LocationState {
address: string;
coordinates: {
lat: number | null;
lng: number | null;
};
}
interface LocationSearchInputProps {
className?: string;
}
const LocationSearchInput: React.FC<LocationSearchInputProps> = ({className}) => {
const [location, setLocation] = useState<LocationState>({
address: '',
coordinates: { lat: null, lng: null },
});
const handleChange = (address: string) => {
setLocation((prev) => ({ ...prev, address }));
};
const handleSelect = async (address: string) => {
try {
const results = await geocodeByAddress(address);
const latLng = await getLatLng(results[0]);
setLocation({ address, coordinates: latLng });
} catch (error) {
console.error('Error selecting place:', error);
}
};
return (
<div className="w-full">
<PlacesAutocomplete
value={location.address}
onChange={handleChange}
onSelect={handleSelect}
>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input
{...getInputProps({
placeholder: 'Search by City, Zip Code, or your Full Address...',
className: `location-search-input w-full px-2 focus:outline-hidden ${className}`,
})}
/>
<div className="autocomplete-dropdown-container">
{loading && <div>Loading...</div>}
{suggestions.map((suggestion) => {
const className = suggestion.active
? 'suggestion-item--active bg-atmosphere cursor-pointer'
: 'suggestion-item bg-deepcove cursor-pointer';
return (
<div
{...getSuggestionItemProps(suggestion, {
className,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
{/* {location.coordinates.lat && location.coordinates.lng && (
<p>
Selected Coordinates: Lat: {location.coordinates.lat}, Lng:{' '}
{location.coordinates.lng}
</p>
)} */}
</div>
);
};
export default LocationSearchInput;
+96
View File
@@ -0,0 +1,96 @@
'use client';
import { useState, useEffect } from 'react';
interface MemberNumberDoBProps {
onSelectionCompleteMember: (pbEntityKey: string) => void;
skipDobValidation?: boolean;
}
const MemberNumberDoB: React.FC<MemberNumberDoBProps> = ({ onSelectionCompleteMember, skipDobValidation }) => {
const [memberNumber, setMemberNumber] = useState('');
const [dob, setDob] = useState('');
const [loading, setLoading] = useState(false);
const [apiUrl, setApiUrl] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => setApiUrl(data.apiUrl));
}, []);
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
setLoading(true);
try {
if (skipDobValidation) {
onSelectionCompleteMember(memberNumber);
} else {
const response = await fetch(`http://${apiUrl}/api/v1/public/search_eligibility?member_number=${memberNumber}&dob=${dob}`, {
method: 'GET'
});
if (!response.ok) {
throw new Error('Member not found or invalid credentials.');
}
const data = await response.json();
onSelectionCompleteMember(data);
}
} catch (error) {
console.error('Error fetching member information:', error);
} finally {
setLoading(false);
}
};
return (
<div className="flex flex-col w-full gap-4">
<form onSubmit={handleSubmit} className="flex flex-col w-full gap-1">
<div>
<label htmlFor="memberNumber" className="block text-md font-bold text-platinum">
Member Number
</label>
<input
id="memberNumber"
type="text"
required
placeholder="e.g., 123456"
value={memberNumber}
onChange={(e) => setMemberNumber(e.target.value)}
className="appearance-none rounded-none relative block w-1/4 px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove text-lg rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10"
/>
</div>
{!skipDobValidation &&
<div>
<label htmlFor="dob" className="block text-md font-bold text-platinum">
Date of Birth
</label>
<input
id="dob"
type="date"
required
placeholder="MM/DD/YYYY"
value={dob}
onChange={(e) => setDob(e.target.value)}
className="appearance-none rounded-none relative block w-1/4 px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove text-lg rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10"
/>
</div>
}
<button
type="submit"
disabled={loading}
className="flex justify-center items-center h-full w-1/4 mt-3 hover:text-deepcove rounded-r-2 border-2 border-bluemana hover:bg-bluemana px-2 hover:cursor-pointer"
>
{loading ? 'Searching...' : 'Submit'}
</button>
</form>
</div>
);
}
export default MemberNumberDoB;
-69
View File
@@ -6,73 +6,6 @@ import Link from 'next/link';
import Button from './Button';
import Image from 'next/image';
export interface MenuItem {
id: string;
title: string;
route?: string;
children?: MenuItem[];
}
// const menuItems: MenuItem[] = [
// {
// id: "members",
// title: "Members",
// children: [
// {
// id: "findMedicalProvider",
// title: "Find a Medical Provider",
// route: "",
// },
// {
// id: "obtainRxInformation",
// title: "Obtain Rx Information",
// route: "",
// },
// ],
// },
// {
// id: "employers",
// title: "Employers",
// children: [
// {
// id: "employerServices",
// title: "Employer Services",
// route: "",
// },
// {
// id: "selfFundedPlans",
// title: "Self-Funded Plans",
// route: "",
// },
// {
// id: "controlHealthCareCosts",
// title: "Control Health Care Costs",
// route: "",
// },
// ],
// },
// {
// id: "providers",
// title: "Providers",
// children: [
// {
// id: "eligibilityLookup",
// title: "Eligibility Lookup",
// route: "",
// },
// {
// id: "contactBbs",
// title: "Contact BBS",
// route: "",
// },
// ],
// },
// {
// id: "brokers",
// title: "Brokers",
// route: "",
// },
// ];
interface NavbarProps {
collapseHeight: number;
@@ -83,8 +16,6 @@ const Navbar: React.FC<NavbarProps> = ({ collapseHeight }) => {
useEffect(() => {
const handleScroll = () => {
// const subtractedHeight = window.innerHeight - 80;
const subtractedHeight = window.innerHeight - collapseHeight;
if (window.scrollY > collapseHeight) {
setIsScrolled(true);
} else {
+44
View File
@@ -0,0 +1,44 @@
'use client';
import classnames from 'classnames';
import { useState } from 'react';
import MemberNumberDoB from './MemberNumberDoB';
import ClaimsViewer from './ClaimsViewer';
interface ProviderClaimSearchProps {
role: string;
roleId: string;
}
const ProviderClaimSearch: React.FC<ProviderClaimSearchProps> = ({ roleId }) => {
const [selectedMember, setSelectedMember] = useState<string | null>(null);
const handleSelectedMember = (pbEntityKey: string): void => {
setSelectedMember(null);
setSelectedMember(pbEntityKey);
};
return (
<div className={classnames("w-full flex flex-col px-2 text-lg")}>
<MemberNumberDoB
onSelectionCompleteMember={handleSelectedMember}
skipDobValidation
/>
{/* {selectedMember && (
<IdCardViewer
cardLaout='FullPageCard'
pbEntityKey={selectedMember}
autoFetchCard
/>
)} */}
{selectedMember && (
<ClaimsViewer
pbEntityKey={selectedMember}
providerTaxId={roleId}
/>
)}
</div>
);
}
export default ProviderClaimSearch;
+2 -2
View File
@@ -28,8 +28,8 @@ const QuickAccessLinks: React.FC<QuickAccessLinksProps> = ({ imageUrl, quickLink
Quick Access to Your Services
</div>
<ul className={classnames("flex flex-col pl-10 text-2xl leading-12 list-disc list-inside", font.className)}>
{quickLinks.map((quickLink) => (
<li><InlineLink>{quickLink.display}</InlineLink></li>
{quickLinks.map((quickLink, i) => (
<li key={i} ><InlineLink link={quickLink.url}>{quickLink.display}</InlineLink></li>
))}
</ul>
</div>
-1
View File
@@ -4,7 +4,6 @@ import classnames from 'classnames';
import Button from '@/components/Button';
import { Schibsted_Grotesk } from 'next/font/google'
import { useEffect, useState } from 'react';
import Link from 'next/link';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
+14 -16
View File
@@ -1,34 +1,31 @@
"use client";
import React, { useState, ComponentType } from 'react';
import React from 'react';
import classnames from 'classnames';
import DynamicIcon from './DynamicIcon';
import { NavItems } from '@/lib/dashboard';
import { FaArrowCircleLeft, FaArrowCircleRight } from "react-icons/fa";
import { TbCircleArrowLeft, TbCircleArrowRight } from "react-icons/tb";
import { PiBreadFill } from "react-icons/pi";
import Button from './Button';
import { sideNavItems, NavItems } from '@/lib/dashboard';
import { usePathname } from 'next/navigation';
interface SideNavProps {
maxWidth: string;
bgColor: string;
navItems: NavItems[];
}
const SideNav: React.FC<SideNavProps> = (
{
maxWidth,
bgColor,
navItems
bgColor
}
) => {
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
const isSidebarOpen = true;
const pathname = usePathname();
const pageRole = pathname?.split('/')[2] as string;
let pageNavItems = {} as NavItems[];
if (pageRole) {
pageNavItems = sideNavItems[pageRole]
}
return (
<div className={classnames(
@@ -48,10 +45,10 @@ const SideNav: React.FC<SideNavProps> = (
{isSidebarOpen ? <FaArrowCircleLeft size={30} /> : <FaArrowCircleRight size={30} />}
</Button> */}
<div className="flex flex-col divide-y divide-bluetang">
{navItems.map((navItem, index) => (
{pageNavItems.map((navItem, index) => (
<a href={navItem.url} key={index} className={classnames(
"w-full h-20 flex justify-center items-center px-1 text-xs md:text-sm lg:text-base font-semibold hover:text-platinum",
{"bg-bluetang text-platinum" : usePathname() == navItem.url}
{"bg-bluetang text-platinum" : pathname == navItem.url}
)}>
{isSidebarOpen ? (
<>{navItem.title}</>
@@ -60,6 +57,7 @@ const SideNav: React.FC<SideNavProps> = (
)}
</a>
))}
<div className="w-full h-20 flex justify-center items-center px-1 text-xs md:text-sm lg:text-base font-semibold hover:text-platinum"></div>
</div>
</div>
);
-49
View File
@@ -1,49 +0,0 @@
"use client";
import React, { useState } from 'react';
interface SideNavTempProps {
idk?: string
}
const SideNavTemp: React.FC<SideNavTempProps> = (
{
idk
}
) => {
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
return (
<div className="flex flex-col justify-end">
{/* Sidebar */}
<div
className={`bg-gray-800 text-white h-screen/2 transition-all duration-300 ${
isSidebarOpen ? 'w-64' : 'w-16'
}`}
>
<div className="p-4">
{isSidebarOpen ? (
<h1 className="text-xl font-bold">My App</h1>
) : (
<h1 className="text-xl font-bold">M</h1>
)}
<button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
{isSidebarOpen ? 'Collapse' : 'Expand'} Sidebar
</button>
{/* Navigation Links */}
<ul className="mt-4">
<li className="mb-2">Link 1</li>
<li className="mb-2">Link 2</li>
</ul>
</div>
</div>
</div>
);
}
export default SideNavTemp;
+33
View File
@@ -0,0 +1,33 @@
'use react';
import { useRouter } from 'next/navigation';
import Button from './Button';
export default function SignOutButton() {
const router = useRouter();
const handleSignOut = async () => {
try {
const res = await fetch('/api/signout', {
method: 'POST',
});
if (res.ok) {
// Refresh the router to update layout files / middleware protection
router.refresh();
router.push('/account/signin');
}
} catch (error) {
console.error('Error during sign out:', error);
}
};
return (
<Button
onClick={handleSignOut}
className="block data-focus:text-bluemana hover:text-bluemana"
>
Sign Out
</Button>
);
}
-82
View File
@@ -1,82 +0,0 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { useIdleTimer } from "react-idle-timer";
import { signOut, useSession } from "next-auth/react"; // Or use your custom auth hook
const TIMEOUT_MINUTES = 15; // Log out after 15 minutes of inactivity
const WARNING_SECONDS = 60; // Show warning 1 minute before logout
export default function IdleTimerContainer() {
const { data: session } = useSession();
const [showWarning, setShowWarning] = useState(false);
const [remainingTime, setRemainingTime] = useState(0);
const idleTimerRef = useRef(null);
// Triggered when user becomes idle but before logging out (for a warning modal)
const onPrompt = () => {
// getRemainingTime is provided by react-idle-timer
const time = Math.floor(idleTimerRef.current.getRemainingTime() / 1000);
setRemainingTime(time);
setShowWarning(true);
};
// Triggered when the user is completely idle and timer runs out
const onIdle = () => {
setShowWarning(false);
signOut({ callbackUrl: "/login" }); // Redirects to login after logging out
};
const onActive = () => {
setShowWarning(false);
};
const { getRemainingTime, getLastActiveTime } = useIdleTimer({
timeout: 1000 * 60 * TIMEOUT_MINUTES,
promptTimeout: 1000 * 60 * WARNING_SECONDS,
onPrompt,
onIdle,
onActive,
debounce: 500,
});
useEffect(() => {
let interval;
if (showWarning) {
interval = setInterval(() => {
setRemainingTime((prev) => {
if (prev <= 1) {
clearInterval(interval);
return 0;
}
return prev - 1;
});
}, 1000);
}
return () => clearInterval(interval);
}, [showWarning]);
// Don't render anything if the user isn't logged in or no warning is active
if (!session || !showWarning) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div className="bg-white p-6 rounded-lg shadow-xl text-center max-w-sm w-full mx-4">
<h2 className="text-lg font-semibold text-gray-800">Session Timeout Warning</h2>
<p className="mt-2 text-sm text-gray-600">
You will be automatically logged out in <b>{remainingTime} seconds</b> due to inactivity.
</p>
<p className="mt-1 text-xs text-gray-500">
Click anywhere or press any key to stay logged in.
</p>
<button
onClick={() => idleTimerRef.current?.activate()}
className="mt-4 px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded hover:bg-blue-700 w-full"
>
Stay Logged In
</button>
</div>
</div>
);
}
-2
View File
@@ -9,8 +9,6 @@ interface TaglineProps {
const Tagline: React.FC<TaglineProps> = (
{
bgColor,
textColor,
textTop,
textBottom
}
+1 -1
View File
@@ -6,7 +6,7 @@ interface TextImage50Props {
imageUrl: string,
}
const TextImage50: React.FC<TextImage50Props> = ({ children, className, imageUrl }) => {
const TextImage50: React.FC<TextImage50Props> = ({ children, imageUrl }) => {
return (
<div className="w-full flex justify-between py-5">
+98
View File
@@ -31,6 +31,11 @@ export const sideNavItems: sideNavGroups = {
url: "/dashboard/member/claims",
iconName: "BsClipboardCheck"
},
{
title: "Documents",
url: "/dashboard/member/documents",
iconName: "LuClipboardList"
},
// {
// title: "Providers & Services",
// url: "/",
@@ -46,5 +51,98 @@ export const sideNavItems: sideNavGroups = {
// url: "/",
// iconName: "IoDocumentsOutline"
// }
],
"employer": [
{
title: "Dashboard",
url: "/dashboard/employer",
iconName: "LuLayoutDashboard"
},
{
title: "ID Cards",
url: "/dashboard/employer/id-cards",
iconName: "BsClipboardCheck"
},
{
title: "Eligibility & Benefits",
url: "/dashboard/employer/eligibility",
iconName: "LuClipboardList"
},
{
title: "Members Coverage",
url: "/dashboard/employer/members-coverage",
iconName: "LuClipboardList"
},
{
title: "Documents",
url: "/dashboard/employer/documents",
iconName: "LuClipboardList"
},
],
"broker": [
{
title: "Dashboard",
url: "/dashboard/broker",
iconName: "LuLayoutDashboard"
},
{
title: "ID Cards",
url: "/dashboard/broker/id-cards",
iconName: "BsClipboardCheck"
},
{
title: "Eligibility & Benefits",
url: "/dashboard/broker/eligibility",
iconName: "LuClipboardList"
},
{
title: "Coverage By Employer",
url: "/dashboard/broker/employer-coverage",
iconName: "LuClipboardList"
},
],
"carrier": [
{
title: "Dashboard",
url: "/dashboard/carrier",
iconName: "LuLayoutDashboard"
},
{
title: "ID Cards",
url: "/dashboard/carrier/id-cards",
iconName: "BsClipboardCheck"
},
{
title: "Claims",
url: "/dashboard/carrier/claims",
iconName: "LuClipboardList"
},
{
title: "Eligibility & Benefits",
url: "/dashboard/carrier/eligibility",
iconName: "LuClipboardList"
},
{
title: "Coverage By Employer",
url: "/dashboard/carrier/employer-coverage",
iconName: "LuClipboardList"
},
],
"provider": [
{
title: "Dashboard",
url: "/dashboard/provider",
iconName: "LuLayoutDashboard"
},
{
title: "Claims",
url: "/dashboard/provider/claim-search",
iconName: "LuClipboardList"
},
{
title: "Eligibility & Benefits",
url: "/dashboard/provider/eligibility-lookup",
iconName: "LuClipboardList"
},
]
}
+10 -2
View File
@@ -2,12 +2,20 @@ import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value;
const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');
if (isProtectedRoute && !token) {
return NextResponse.redirect(new URL('/account/signin', request.url));
}
const headers = new Headers(request.headers);
headers.set("x-current-path", request.nextUrl.pathname);
return NextResponse.next({ headers });
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)" // Exclude API routes, static files, etc.
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)",
"/dashboard/:path*"
],
};
};
-29
View File
@@ -1,29 +0,0 @@
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { useRouter } from 'next/router';
import { Fraunces } from 'next/font/google'
import Navbar from '../components/Navbar';
import Footer from '../components/Footer';
import classNames from 'classnames';
const font = Fraunces({ subsets: ['latin'] })
function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
const noNavPath = "/dashboard/"
return (
<main className={classNames("bg-deepcove min-h-screen flex justify-center", font.className)}>
<script src={`https://maps.googleapis.com/maps/api/js?key=${process.env.GOOGLE_PLACES_API_KEY}&libraries=places`} />
<div className="w-full max-w-[1920px]">
{!router.pathname.includes("/dashboard/") && <Navbar />}
{/* <Navbar /> */}
<Component {...pageProps} />
{!router.pathname.includes("/dashboard/") && <Footer />}
</div>
</main>
);
}
export default MyApp;
-13
View File
@@ -1,13 +0,0 @@
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body className="antialiased">
<Main />
<NextScript />
</body>
</Html>
);
}
-77
View File
@@ -1,77 +0,0 @@
import InfoCard from '../components/InfoCard';
import Tagline from "../components/Tagline";
export default function Home() {
return (
<>
<Tagline
bgColor="bg-bluemana"
textColor="text-deepcove"
textTop="Healthcare that's"
textBottom="truly affordable"
/>
<InfoCard
title="Health & Risk Management"
imageUrl="/images/doctor.png"
parentBgColor="bg-deepcove"
cardBGColor="bg-bluetang"
bodyTextColor="text-platinum"
>
Traditional insurance plans require a lot of upfront costs for coverage that you and your employees may never use. Your insurance provider will collect your monthly premium based on the number of enrolled employees, and your premium only changes if you add or remove enrolled employees.
<br />
While there are still fixed costs with self-funded plans, such as administrative fees, stop-loss premiums and any other set fees charged per employee, your employee premiums are billed on a per-claim basis.
</InfoCard>
<div className='bg-atmosphere w-full h-full rounded-3xl'>
<InfoCard
title="Self-Funded Plans"
imageUrl='/images/pwn-webi-square.png'
parentBgColor="bg-atmosphere"
cardBGColor="bg-deepcove"
bodyTextColor="text-platinum"
learnMoreUrl="https://github.com/orgs/Britton-Benefits/repositories"
invert
>
Self-funding gives you the flexibility to design a plan that is best suited to meet your company's needs. And unlike traditional insurance plans that charge a fixed annual rate for claims, self-funded plans are billed monthly, and only for the services used. This saves you from the extra cost of paying for something your company might not use and lets you continuously evaluate your company’s plan, maximizing flexibility and mitigating risk to your company's bottom line.
</InfoCard>
</div>
<InfoCard
title="Benefit Access & Online Support"
imageUrl="/images/doctor.png"
parentBgColor="bg-deepcove"
cardBGColor="bg-bluetang"
bodyTextColor="text-platinum"
>
We offer a wide range of online services to help streamline and automate the benefit process for you and your employees. This means easier access to the benefit information you need and faster action on individual claims. By logging in to any of the services we provide, you can look up in-network providers through our list of PPOs, file insurance claims and review a claim's status, research which type of plan is best for you, and control your health care costs by selecting a PPO that's actively competing for your business.
</InfoCard>
<div className='bg-platinum rounded-3xl'>
<InfoCard
title="Regulatory Compliance"
imageUrl="/images/pwn-webi-square.png"
parentBgColor="bg-platinum"
cardBGColor="bg-deepcove"
bodyTextColor="text-platinum"
invert
>
At Britton Benefit Services, LLC we are leading the third-party benefits industry by working hard to make employee benefits more affordable for you and your employees. As a third-party administrator, we negotiate with leading PPOs and other organizations to help you find the best program for your employees without sacrificing the bottom line. This creates a major cost savings that you can pass on to your employees.
</InfoCard>
</div>
{/* <div className="flex w-full h-70 justify-between items-center pt-30">
<div className="bg-platinum h-full w-3/12 rounded-3xl"></div>
<div className="bg-bluetang h-full w-3/12 rounded-3xl"></div>
<div className="bg-bluemana h-full w-3/12 rounded-3xl"></div>
</div> */}
</>
);
}
-31
View File
@@ -1,31 +0,0 @@
import classNames from "classnames";
import { Playfair_Display, Fraunces } from "next/font/google"
import Tagline from "../components/Tagline";
const playfair = Playfair_Display({ weight: "500",subsets: ["latin"] })
const fraunces = Fraunces({ subsets: ["latin"] })
export default function Home() {
return (
<>
<Tagline
bgColor="bg-bluemana"
textColor="text-deepcove"
textTop="Healthcare that's"
textBottom="truly affordable"
/>
<div className="flex justify-center p-5">
<div className="w-11/12 h-4 bg-bluetang text-center text-deepcove text-7xl rounded-3xl p-4">
</div>
</div>
<div className="flex justify-center p-5">
<div className="w-11/12 h-4 bg-platinum text-center text-deepcove text-7xl rounded-3xl p-4">
</div>
</div>
<div className="flex justify-center p-5">
<div className="w-11/12 h-4 bg-bronze text-center text-deepcove text-7xl rounded-3xl p-4">
</div>
</div>
</>
);
}