Production Build Working
This commit is contained in:
+8
-2
@@ -1,3 +1,9 @@
|
|||||||
node_modules/
|
node_modules
|
||||||
.env
|
.next
|
||||||
|
out
|
||||||
|
build
|
||||||
.git
|
.git
|
||||||
|
.gitignore
|
||||||
|
.env*
|
||||||
|
.local
|
||||||
|
README.md
|
||||||
+43
@@ -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"]
|
||||||
@@ -3,9 +3,14 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.dev
|
dockerfile: Dockerfile.dev
|
||||||
|
# dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- "3001:3001"
|
- "3001:3001"
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
# - NODE_ENV=production
|
||||||
- BE_API_SERVER_HOST=web_api
|
- BE_API_SERVER_HOST=web_api
|
||||||
- BE_API_SERVER_PORT=3005
|
- BE_API_SERVER_PORT=3005
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
+1
-8
@@ -1,14 +1,7 @@
|
|||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
// async rewrites() {
|
output: 'standalone',
|
||||||
// return [
|
|
||||||
// {
|
|
||||||
// source: '/api-backend/:path*',
|
|
||||||
// destination: `http://web_api:3005`, // The external api URL
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
// },
|
|
||||||
outputFileTracingIncludes: {
|
outputFileTracingIncludes: {
|
||||||
'/api/**/*': ['./node_modules/jose/**/*'],
|
'/api/**/*': ['./node_modules/jose/**/*'],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
import { Schibsted_Grotesk } from 'next/font/google'
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import InlineLink from "@/components/InlineLink";
|
|
||||||
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
|
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
|
||||||
import LandingPageHeader from '@/components/LandingPageHeader';
|
import LandingPageHeader from '@/components/LandingPageHeader';
|
||||||
|
|
||||||
@@ -8,8 +7,8 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
|||||||
|
|
||||||
const quickLinks: QuickLink[] = [
|
const quickLinks: QuickLink[] = [
|
||||||
{
|
{
|
||||||
display: "Member Login/Registration",
|
display: "Broker SignIn",
|
||||||
url: "/"
|
url: "/account/signin"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "Link to Thing 1",
|
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
|
Partner with Britton Benefit Services to Grow Your Business and Serve Your Clients
|
||||||
</div>
|
</div>
|
||||||
<QuickAccessLinks
|
<QuickAccessLinks
|
||||||
imageUrl="/images/brokers-stock.jpg"
|
imageUrl="/images/broker-image.jpg"
|
||||||
quickLinks={quickLinks}
|
quickLinks={quickLinks}
|
||||||
/>
|
/>
|
||||||
<div className={classnames("w-full text-2xl px-10", font.className)}>
|
<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've come to the right place.
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Broker Services from Britton Benefit Services
|
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>
|
<li>
|
||||||
Discuss your client’s needs and provide additional insight
|
Discuss your client's needs and provide additional insight
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
Learn more about our company and self-funding
|
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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
import { Schibsted_Grotesk } from 'next/font/google'
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import InlineLink from "@/components/InlineLink";
|
|
||||||
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
|
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
|
||||||
import LandingPageHeader from '@/components/LandingPageHeader';
|
import LandingPageHeader from '@/components/LandingPageHeader';
|
||||||
|
|
||||||
@@ -8,8 +7,8 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
|||||||
|
|
||||||
const quickLinks: QuickLink[] = [
|
const quickLinks: QuickLink[] = [
|
||||||
{
|
{
|
||||||
display: "Employer Login/Registration",
|
display: "Employer SignIn",
|
||||||
url: "/"
|
url: "/account/signin"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "Link to Thing 1",
|
display: "Link to Thing 1",
|
||||||
@@ -35,7 +34,7 @@ export default function Employers() {
|
|||||||
Making Your Benefits Work for You and Your Employees
|
Making Your Benefits Work for You and Your Employees
|
||||||
</div>
|
</div>
|
||||||
<QuickAccessLinks
|
<QuickAccessLinks
|
||||||
imageUrl="/images/employers-stock.jpg"
|
imageUrl="/images/employer-image.jpg"
|
||||||
quickLinks={quickLinks}
|
quickLinks={quickLinks}
|
||||||
/>
|
/>
|
||||||
<div className={classnames("w-full text-2xl px-10", font.className)}>
|
<div className={classnames("w-full text-2xl px-10", font.className)}>
|
||||||
@@ -46,9 +45,9 @@ export default function Employers() {
|
|||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
Look up in-network providers through our list of PPOs<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's status<br />
|
||||||
• Research which type of plan is best for you<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's actively competing for your business.
|
||||||
<br />
|
<br />
|
||||||
<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.
|
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,14 +1,9 @@
|
|||||||
import { headers } from "next/headers";
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
|
|
||||||
|
|
||||||
export default async function LandingPageLayout({ children }: { children: React.ReactNode }) {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -23,30 +18,7 @@ export default async function LandingPageLayout({ children }: { children: React.
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="w-full flex justify-start items-end pt-30">
|
<div className="w-full min-h-[75vh] text-platinum">
|
||||||
<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">
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
import { Schibsted_Grotesk } from 'next/font/google'
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import InlineLink from "@/components/InlineLink";
|
|
||||||
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
|
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
|
||||||
import LandingPageHeader from '@/components/LandingPageHeader';
|
import LandingPageHeader from '@/components/LandingPageHeader';
|
||||||
|
|
||||||
@@ -9,19 +8,19 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
|||||||
const quickLinks: QuickLink[] = [
|
const quickLinks: QuickLink[] = [
|
||||||
{
|
{
|
||||||
display: "Member Login/Registration",
|
display: "Member Login/Registration",
|
||||||
url: "/"
|
url: "/account/signin"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "Link to Thing 1",
|
display: "Find a MedCost Medical Provider",
|
||||||
url: "/"
|
url: "http://medcost.com/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "Link to Thing 2",
|
display: "Find a Cigna Medical Provider",
|
||||||
url: "/"
|
url: "http://cigna.com/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "Link to another Thing",
|
display: "Obtain Rx Information",
|
||||||
url: "/"
|
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
|
Member Tagline will go here when we come up with the wording
|
||||||
</div>
|
</div>
|
||||||
<QuickAccessLinks
|
<QuickAccessLinks
|
||||||
imageUrl="/images/members-stock.jpg"
|
imageUrl="/images/member-image.jpg"
|
||||||
quickLinks={quickLinks}
|
quickLinks={quickLinks}
|
||||||
/>
|
/>
|
||||||
<div className={classnames("w-full text-2xl px-10", font.className)}>
|
<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'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 />
|
||||||
<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'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 />
|
||||||
<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'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 />
|
||||||
<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'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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
import { Schibsted_Grotesk } from 'next/font/google'
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import InlineLink from "@/components/InlineLink";
|
|
||||||
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
|
import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks';
|
||||||
import LandingPageHeader from '@/components/LandingPageHeader';
|
import LandingPageHeader from '@/components/LandingPageHeader';
|
||||||
|
|
||||||
@@ -8,12 +7,12 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
|||||||
|
|
||||||
const quickLinks: QuickLink[] = [
|
const quickLinks: QuickLink[] = [
|
||||||
{
|
{
|
||||||
display: "Member Login/Registration",
|
display: "Provider SignIn/Registration",
|
||||||
url: "/"
|
url: "/account/signin"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "Link to Thing 1",
|
display: "Eligibility & Benefits Lookup",
|
||||||
url: "/"
|
url: "/public/eligibility-lookup"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
display: "Link to Thing 2",
|
display: "Link to Thing 2",
|
||||||
@@ -36,11 +35,11 @@ export default function Providers() {
|
|||||||
Easy Access to HIPAA Compliant Patient Information and Much More
|
Easy Access to HIPAA Compliant Patient Information and Much More
|
||||||
</div>
|
</div>
|
||||||
<QuickAccessLinks
|
<QuickAccessLinks
|
||||||
imageUrl="/images/providers-stock.jpg"
|
imageUrl="/images/provider-image.png"
|
||||||
quickLinks={quickLinks}
|
quickLinks={quickLinks}
|
||||||
/>
|
/>
|
||||||
<div className={classnames("w-full text-2xl px-10", font.className)}>
|
<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'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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,20 +1,12 @@
|
|||||||
"user client"
|
"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 Image from 'next/image';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import Footer from '@/components/Footer';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
|
||||||
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
|
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 (
|
return (
|
||||||
<div className="relative w-full h-full">
|
<div className="relative w-full h-full">
|
||||||
@@ -30,25 +22,28 @@ export default async function DashboardLayout({ children }: { children: React.Re
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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-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={classnames('flex items-center')}>
|
||||||
<div className="-ml-8.5 z-2">
|
<div className="ml-[8.15vw] z-2">
|
||||||
|
<Link href="/" className="inline-block overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
src="/images/bbstpa-forsite.png"
|
src="/images/bbstpa-forsite.png"
|
||||||
alt="Britton Logo"
|
alt="Britton Logo"
|
||||||
width={205}
|
width={205}
|
||||||
height={60}
|
height={60}
|
||||||
/>
|
/>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import { signInAndFetchUser } from '@/app/actions/signin'
|
|||||||
|
|
||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
import { Schibsted_Grotesk } from 'next/font/google'
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Button from '@/components/Button';
|
|
||||||
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
||||||
|
|
||||||
export default function SignInPage() {
|
export default function SignInPage() {
|
||||||
@@ -71,24 +70,24 @@ export default function SignInPage() {
|
|||||||
{isPending ? 'Signing In...' : 'Submit'}
|
{isPending ? 'Signing In...' : 'Submit'}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</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>
|
||||||
<div className="text-bluemana mt-4 z-20">
|
<div className="flex w-md justify-between text-platinum mt-4 z-20">
|
||||||
|
<Link
|
||||||
|
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?{' '}
|
Not signed up?{' '}
|
||||||
<Link
|
<Link
|
||||||
href="/account/signup"
|
href="/account/signup"
|
||||||
className="underline text-atmosphere hover:text-light-bronze z-10"
|
className={classnames("font-bold underline text-atmosphere hover:text-bluemana z-10", font.className)}
|
||||||
>
|
>
|
||||||
Click here.
|
Register Here
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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">
|
<div className="flex w-full items-center justify-center text-7xl font-bold text-atmosphere">
|
||||||
Britton Benefits
|
Britton Benefits
|
||||||
@@ -165,7 +165,7 @@ export default function LoginPage() {
|
|||||||
<div className="rounded-md shadow-sm -space-y-px">
|
<div className="rounded-md shadow-sm -space-y-px">
|
||||||
<label className="block text-md font-bold text-platinum">Confirm Password</label>
|
<label className="block text-md font-bold text-platinum">Confirm Password</label>
|
||||||
<input
|
<input
|
||||||
type="confirmPassword"
|
type="password"
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
required
|
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"
|
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"
|
||||||
|
|||||||
@@ -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
@@ -6,19 +6,7 @@ import { SignJWT } from 'jose';
|
|||||||
|
|
||||||
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
||||||
|
|
||||||
function roleMapping(role_number: number) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const map = [
|
|
||||||
"member",
|
|
||||||
"employer",
|
|
||||||
"broker",
|
|
||||||
"provider",
|
|
||||||
"carrier",
|
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
|
|
||||||
return map[role_number]
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function signInAndFetchUser(prevState: any, formData: FormData) {
|
export async function signInAndFetchUser(prevState: any, formData: FormData) {
|
||||||
const email = formData.get('email') as string
|
const email = formData.get('email') as string
|
||||||
const password = formData.get('password') 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 { user } = await authResponse.json();
|
||||||
const token = authResponse.headers.get('authorization')?.split(' ')[1];
|
const token = authResponse.headers.get('authorization')?.split(' ')[1];
|
||||||
console.log("token: ", token)
|
|
||||||
// const token = user.jti
|
|
||||||
const role = user.role
|
const role = user.role
|
||||||
|
|
||||||
const sessionPayload = {
|
const sessionPayload = {
|
||||||
@@ -56,8 +43,6 @@ export async function signInAndFetchUser(prevState: any, formData: FormData) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log("sessionPayload: ", sessionPayload)
|
|
||||||
|
|
||||||
const encryptedSession = await new SignJWT(sessionPayload)
|
const encryptedSession = await new SignJWT(sessionPayload)
|
||||||
.setProtectedHeader({ alg: 'HS256' })
|
.setProtectedHeader({ alg: 'HS256' })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
@@ -73,19 +58,25 @@ export async function signInAndFetchUser(prevState: any, formData: FormData) {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (role == "member") {
|
if (role) {
|
||||||
redirect('/dashboard/member')
|
redirect(`/dashboard/${role}`)
|
||||||
} 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 {
|
} else {
|
||||||
return { error: 'Invalid email or password.' }
|
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
@@ -7,19 +7,7 @@ import { SignJWT } from 'jose';
|
|||||||
|
|
||||||
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
||||||
|
|
||||||
function roleMapping(role_number: number) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const map = [
|
|
||||||
"member",
|
|
||||||
"employer",
|
|
||||||
"broker",
|
|
||||||
"provider",
|
|
||||||
"carrier",
|
|
||||||
"admin"
|
|
||||||
]
|
|
||||||
|
|
||||||
return map[role_number]
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function registerUser(prevState: any, formData: FormData) {
|
export async function registerUser(prevState: any, formData: FormData) {
|
||||||
const email = formData.get('email') as string
|
const email = formData.get('email') as string
|
||||||
const password = formData.get('password') 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.');
|
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',
|
method: 'POST',
|
||||||
body: JSON.stringify({ user: {email, password, confirm_password, role, role_id, last_four_ssn, date_of_birth} }),
|
body: JSON.stringify({ user: {email, password, confirm_password, role, role_id, last_four_ssn, date_of_birth} }),
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
@@ -50,18 +38,18 @@ export async function registerUser(prevState: any, formData: FormData) {
|
|||||||
throw new Error('Invalid credentials');
|
throw new Error('Invalid credentials');
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await authResponse.json();
|
const { user } = await authResponse.json();
|
||||||
const token = data.jti
|
const token = authResponse.headers.get('authorization')?.split(' ')[1];
|
||||||
|
|
||||||
const sessionPayload = {
|
const sessionPayload = {
|
||||||
token: token,
|
token: token,
|
||||||
user: {
|
user: {
|
||||||
id: data.id,
|
id: user.id,
|
||||||
name: data.name,
|
name: user.name,
|
||||||
email: data.email,
|
email: user.email,
|
||||||
role: data.role,
|
role: user.role,
|
||||||
roleId: data.role_id,
|
roleId: user.role_id,
|
||||||
keychain: data.keychain
|
keychain: user.keychain
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,19 +68,25 @@ export async function registerUser(prevState: any, formData: FormData) {
|
|||||||
path: '/',
|
path: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (role == "member") {
|
if (role) {
|
||||||
redirect('/dashboard/member')
|
redirect(`/dashboard/${role}`)
|
||||||
} 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 {
|
} else {
|
||||||
return { error: 'Invalid email or password.' }
|
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.' }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
|
import { JWTPayload, jwtVerify } from 'jose';
|
||||||
import { isIPv4 } from 'net';
|
import { isIPv4 } from 'net';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
|
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
||||||
|
|
||||||
export async function GET() {
|
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
|
// Read the runtime env variable safely on the server
|
||||||
let apiUrl = "";
|
let apiUrl = "";
|
||||||
const backendApiHost = process.env.BE_API_SERVER_HOST || "localhost";
|
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)
|
apiUrl = apiUrl.concat(":").concat(process.env.BE_API_SERVER_PORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ apiUrl });
|
return Response.json({ apiUrl: apiUrl, token: bearerToken });
|
||||||
}
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,41 +1,14 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import DashboardHeader from "@/components/DashboardHeader";
|
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 { cookies } from "next/headers";
|
||||||
import { JWTPayload, jwtVerify } from "jose";
|
import { JWTPayload, jwtVerify } from "jose";
|
||||||
import { redirect } from 'next/navigation';
|
import { notFound, redirect } from 'next/navigation';
|
||||||
import IdCardViewer from '@/components/IdCardViewer';
|
import ArrowText from '@/components/ArrowText';
|
||||||
|
|
||||||
// Ensure your secret key matches exactly what you used to sign the token
|
// Ensure your secret key matches exactly what you used to sign the token
|
||||||
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
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 {
|
interface UserJwtPayload extends JWTPayload {
|
||||||
user: UserData;
|
user: UserData;
|
||||||
}
|
}
|
||||||
@@ -46,17 +19,9 @@ interface DashboardDataType {
|
|||||||
name: string;
|
name: string;
|
||||||
role: string;
|
role: string;
|
||||||
roleId: string;
|
roleId: string;
|
||||||
keychain: UserKeychain;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserKeychain {
|
export default async function Employer() {
|
||||||
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 cookieStore = await cookies();
|
||||||
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
|
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
|
// 3. Cast the payload to your custom interface
|
||||||
const data = payload as UserJwtPayload;
|
const data = payload as UserJwtPayload;
|
||||||
|
|
||||||
// const userDataTest = {
|
// const userData = {
|
||||||
// pb_entity_key: 377527,
|
// plPlanKey: '61',
|
||||||
// pl_plan_key: '61',
|
// name: "Bryan Pest Control",
|
||||||
// name: "John McMember"
|
// role: 'employer',
|
||||||
|
// roleId: '61'
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const userData = {
|
const userData = {
|
||||||
pb_entity_key: data.user.keychain.pb_entity_key,
|
name: data.user.name,
|
||||||
pl_plan_key: data.user.keychain.pl_plan_key,
|
role: data.user.role,
|
||||||
name: data.user.name
|
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`, {
|
if (!data || userData.role !== 'employer') {
|
||||||
method: 'GET',
|
notFound();
|
||||||
credentials: 'include',
|
}
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json' ,
|
|
||||||
'Authorization': `Bearer ${data.token}`
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Unauthenticated");
|
|
||||||
|
|
||||||
const dashboardData: DashboardDataType = await response.json()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
|
||||||
{dashboardData &&
|
|
||||||
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
|
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
|
||||||
<DashboardHeader
|
<DashboardHeader
|
||||||
userDisplayName={userData.name}
|
userDisplayName={userData.name}
|
||||||
/>
|
/>
|
||||||
<div className="flex w-full pr-23">
|
<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={classnames("w-full flex flex-col pt-4 z-2 space-x-4 space-y-4 xl:space-y-0 text-bluemana")}>
|
||||||
<div className="flex flex-col gap-y-4">
|
<ArrowText>
|
||||||
<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">
|
Generate Temporary Member or Employer ID Cards.
|
||||||
{/* <DeductableProgressWidget /> */}
|
</ArrowText>
|
||||||
<IdCardViewer
|
<ArrowText>
|
||||||
pbEntityKey={userData.pb_entity_key}
|
Search Eligibility and Benefits by Member.
|
||||||
cardLaout="FullPageCard"
|
</ArrowText>
|
||||||
/>
|
<ArrowText>
|
||||||
<FindProviderWidget
|
View Members Coverage.
|
||||||
// pl_plan_key='57'
|
</ArrowText>
|
||||||
networkProvider={dashboardData.network_provider}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<FindRxInformationWidget />
|
|
||||||
<RecentClaimsWidget
|
|
||||||
recentClaims={dashboardData.recent_claims}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<BenefitsAAGWidget
|
|
||||||
planBenefits={dashboardData.plan_benefits}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.message === 'NEXT_REDIRECT') throw error;
|
if (error.message === 'NEXT_REDIRECT') throw error;
|
||||||
|
|
||||||
|
|||||||
@@ -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 { cookies } from "next/headers";
|
||||||
import { JWTPayload, jwtVerify } from "jose";
|
import { JWTPayload, jwtVerify } from "jose";
|
||||||
|
|
||||||
@@ -8,6 +5,8 @@ import AccountDropdown from '../../components/AccountDropdown';
|
|||||||
import SideNav from "@/components/SideNav";
|
import SideNav from "@/components/SideNav";
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import classnames from 'classnames';
|
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);
|
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
|
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
|
||||||
|
|
||||||
if (!sessionCookie) {
|
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 {
|
try {
|
||||||
@@ -44,16 +43,11 @@ export default async function DashboardLayout({ children }: { children: React.Re
|
|||||||
|
|
||||||
const userRole = data.user.role
|
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 (
|
return (
|
||||||
<div className="relative w-full h-full">
|
<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="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-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>
|
||||||
<div className="w-[60px] h-full flex justify-start">
|
<div className="w-[60px] h-full flex justify-start">
|
||||||
<div className="w-[32.5px] flex flex-col">
|
<div className="w-[32.5px] flex flex-col">
|
||||||
@@ -65,27 +59,27 @@ export default async function DashboardLayout({ children }: { children: React.Re
|
|||||||
</div>
|
</div>
|
||||||
<div className="w-full h-full flex items-start">
|
<div className="w-full h-full flex items-start">
|
||||||
<SideNav
|
<SideNav
|
||||||
maxWidth="w-[16vw]"
|
|
||||||
bgColor="bg-atmosphere"
|
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-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={classnames('flex items-center')}>
|
||||||
<div className="-ml-4.5 z-2">
|
<div className="ml-[8.15vw] z-2">
|
||||||
|
<Link href="/" className="inline-block overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
src="/images/bbstpa-forsite.png"
|
src="/images/bbstpa-forsite.png"
|
||||||
alt="Britton Logo"
|
alt="Britton Logo"
|
||||||
width={205}
|
width={205}
|
||||||
height={60}
|
height={60}
|
||||||
/>
|
/>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="pr-20 z-2">
|
<div className="mr-[2vw] z-2">
|
||||||
<AccountDropdown userRole="member" />
|
<AccountDropdown userRole={userRole} />
|
||||||
</div>
|
</div>
|
||||||
</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}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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,7 +5,6 @@ import { JWTPayload, jwtVerify } from "jose";
|
|||||||
import DashboardHeader from "@/components/DashboardHeader";
|
import DashboardHeader from "@/components/DashboardHeader";
|
||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
import { Schibsted_Grotesk } from 'next/font/google'
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import { isRedirectError } from 'next/dist/client/components/redirect-error';
|
|
||||||
|
|
||||||
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
||||||
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
||||||
@@ -28,16 +27,8 @@ interface ClaimType {
|
|||||||
name: string;
|
name: string;
|
||||||
role: string;
|
role: string;
|
||||||
roleId: 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() {
|
export default async function MemberClaims() {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const sessionCookie = cookieStore.get("session");
|
const sessionCookie = cookieStore.get("session");
|
||||||
@@ -63,12 +54,12 @@ export default async function MemberClaims() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
const userData = {
|
const userData = {
|
||||||
pb_entity_key: data.user.keychain.pb_entity_key,
|
name: data.user.name,
|
||||||
pl_plan_key: data.user.keychain.pl_plan_key,
|
role: data.user.role,
|
||||||
name: data.user.name
|
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',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -138,6 +129,7 @@ export default async function MemberClaims() {
|
|||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.message === 'NEXT_REDIRECT') throw error;
|
if (error.message === 'NEXT_REDIRECT') throw error;
|
||||||
|
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,19 +2,18 @@ import classnames from 'classnames';
|
|||||||
|
|
||||||
import DashboardHeader from "@/components/DashboardHeader";
|
import DashboardHeader from "@/components/DashboardHeader";
|
||||||
import FindProviderWidget from '@/components/FindProvider';
|
import FindProviderWidget from '@/components/FindProvider';
|
||||||
import InsuranceCardWidget from '@/components/InsuranceCard';
|
|
||||||
import FindRxInformationWidget from '@/components/FindRxInformation';
|
import FindRxInformationWidget from '@/components/FindRxInformation';
|
||||||
import RecentClaimsWidget from '@/components/RecentClaimsWidget';
|
import RecentClaimsWidget from '@/components/RecentClaimsWidget';
|
||||||
import BenefitsAAGWidget from '@/components/BenefitsAAGWidget';
|
import BenefitsAAGWidget from '@/components/BenefitsAAGWidget';
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { JWTPayload, jwtVerify } from "jose";
|
import { JWTPayload, jwtVerify } from "jose";
|
||||||
import { redirect } from 'next/navigation';
|
import { notFound, redirect } from 'next/navigation';
|
||||||
import IdCardViewer from '@/components/IdCardViewer';
|
import IdCardViewer from '@/components/IdCardViewer';
|
||||||
|
|
||||||
// Ensure your secret key matches exactly what you used to sign the token
|
// Ensure your secret key matches exactly what you used to sign the token
|
||||||
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
|
||||||
|
|
||||||
interface PlanBenefitType {
|
interface PlanDetailtType {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
sequence: number;
|
sequence: number;
|
||||||
@@ -32,10 +31,16 @@ interface RecentClaimType {
|
|||||||
|
|
||||||
interface DashboardDataType {
|
interface DashboardDataType {
|
||||||
network_provider: string;
|
network_provider: string;
|
||||||
plan_benefits: PlanBenefitType[];
|
plan_benefits: PlanBenefitType;
|
||||||
recent_claims: RecentClaimType[];
|
recent_claims: RecentClaimType[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PlanBenefitType {
|
||||||
|
plan_details: PlanDetailtType[];
|
||||||
|
effective_date: string;
|
||||||
|
dependents: string[];
|
||||||
|
}
|
||||||
|
|
||||||
interface UserJwtPayload extends JWTPayload {
|
interface UserJwtPayload extends JWTPayload {
|
||||||
user: UserData;
|
user: UserData;
|
||||||
}
|
}
|
||||||
@@ -46,16 +51,8 @@ interface DashboardDataType {
|
|||||||
name: string;
|
name: string;
|
||||||
role: string;
|
role: string;
|
||||||
roleId: 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 Member() {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
|
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
|
||||||
@@ -81,12 +78,16 @@ export default async function Member() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
const userData = {
|
const userData = {
|
||||||
pb_entity_key: data.user.keychain.pb_entity_key,
|
role: data.user.role,
|
||||||
pl_plan_key: data.user.keychain.pl_plan_key,
|
roleId: data.user.roleId,
|
||||||
name: data.user.name
|
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',
|
method: 'GET',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
headers: {
|
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">
|
<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 /> */}
|
{/* <DeductableProgressWidget /> */}
|
||||||
<IdCardViewer
|
<IdCardViewer
|
||||||
pbEntityKey={userData.pb_entity_key}
|
pbEntityKey={userData.roleId}
|
||||||
cardLaout="FullPageCard"
|
cardLaout="FullPageCard"
|
||||||
/>
|
/>
|
||||||
<FindProviderWidget
|
<FindProviderWidget
|
||||||
@@ -126,7 +127,9 @@ export default async function Member() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<BenefitsAAGWidget
|
<BenefitsAAGWidget
|
||||||
planBenefits={dashboardData.plan_benefits}
|
planDetails={dashboardData.plan_benefits.plan_details}
|
||||||
|
effectiveDate={dashboardData.plan_benefits.effective_date}
|
||||||
|
dependents={dashboardData.plan_benefits.dependents}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,6 +137,7 @@ export default async function Member() {
|
|||||||
}
|
}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.message === 'NEXT_REDIRECT') throw error;
|
if (error.message === 'NEXT_REDIRECT') throw error;
|
||||||
|
|
||||||
@@ -141,9 +145,4 @@ export default async function Member() {
|
|||||||
redirect('/account/signin');
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
import '../styles/globals.css';
|
import '../styles/globals.css';
|
||||||
import type { Metadata } from 'next';
|
|
||||||
import { Fraunces } from 'next/font/google'
|
import { Fraunces } from 'next/font/google'
|
||||||
import Navbar from '../components/Navbar';
|
|
||||||
import Footer from '../components/Footer';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const font = Fraunces({ subsets: ['latin'] })
|
const font = Fraunces({ subsets: ['latin'] })
|
||||||
|
|||||||
+4
-5
@@ -1,6 +1,5 @@
|
|||||||
import InfoCard from '../components/InfoCard';
|
import InfoCard from '../components/InfoCard';
|
||||||
import Tagline from "../components/Tagline";
|
import Tagline from "../components/Tagline";
|
||||||
import InlineLink from "../components/InlineLink";
|
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ export default async function Home() {
|
|||||||
learnMoreUrl="/members"
|
learnMoreUrl="/members"
|
||||||
imageUrl="/images/member-image.jpg"
|
imageUrl="/images/member-image.jpg"
|
||||||
>
|
>
|
||||||
What we do for members
|
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.
|
||||||
</InfoCard>
|
</InfoCard>
|
||||||
</div>
|
</div>
|
||||||
<div className='bg-atmosphere rounded-3xl'>
|
<div className='bg-atmosphere rounded-3xl'>
|
||||||
@@ -35,7 +34,7 @@ export default async function Home() {
|
|||||||
imageUrl='/images/employer-image.jpg'
|
imageUrl='/images/employer-image.jpg'
|
||||||
invert
|
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>
|
</InfoCard>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -44,7 +43,7 @@ export default async function Home() {
|
|||||||
learnMoreUrl="/providers"
|
learnMoreUrl="/providers"
|
||||||
imageUrl="/images/provider-image.png"
|
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>
|
</InfoCard>
|
||||||
</div>
|
</div>
|
||||||
<div className='bg-light-bronze rounded-3xl'>
|
<div className='bg-light-bronze rounded-3xl'>
|
||||||
@@ -54,7 +53,7 @@ export default async function Home() {
|
|||||||
imageUrl="/images/broker-image.jpg"
|
imageUrl="/images/broker-image.jpg"
|
||||||
invert
|
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>
|
</InfoCard>
|
||||||
</div>
|
</div>
|
||||||
<Footer />
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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,9 +4,10 @@ import React, { useState } from 'react'
|
|||||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
|
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
|
||||||
import { CgProfile } from "react-icons/cg";
|
import { CgProfile } from "react-icons/cg";
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import SignOutButton from './SignOut';
|
||||||
|
|
||||||
interface DropdownProps {
|
interface DropdownProps {
|
||||||
userRole: "member" | "employer";
|
userRole: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountDropdown: React.FC<DropdownProps> = ({userRole}) => {
|
const AccountDropdown: React.FC<DropdownProps> = ({userRole}) => {
|
||||||
@@ -28,14 +29,12 @@ const AccountDropdown: React.FC<DropdownProps> = ({userRole}) => {
|
|||||||
</MenuButton>
|
</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"}>
|
<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"}>
|
<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
|
Dashboard
|
||||||
</a>
|
</a>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem as="div" className={"px-3 py-1.5"}>
|
<MenuItem as="div" className={"px-3 py-1.5"}>
|
||||||
<a className="block data-focus:text-bluemana hover:text-bluemana" href="/">
|
<SignOutButton />
|
||||||
Sign Out
|
|
||||||
</a>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuItems>
|
</MenuItems>
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -3,14 +3,16 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
import { Schibsted_Grotesk } from 'next/font/google'
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
||||||
|
|
||||||
interface BenefitsAAGWidgetProps {
|
interface BenefitsAAGWidgetProps {
|
||||||
planBenefits: PlanBenefitType[];
|
planDetails: PlanDetailsType[];
|
||||||
|
effectiveDate: string;
|
||||||
|
dependents: string[];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PlanBenefitType {
|
interface PlanDetailsType {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
sequence: number;
|
sequence: number;
|
||||||
@@ -18,28 +20,34 @@ interface PlanBenefitType {
|
|||||||
benefit_desc: string;
|
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 (
|
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">
|
<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>
|
<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)}>
|
<div className={classnames("w-full flex-col items-between px-4", font.className)}>
|
||||||
{(planBenefits).map((mb) => (
|
{(planDetails).map((pd) => (
|
||||||
<div key={mb.sequence} className="flex items-center w-full">
|
<div key={pd.sequence} className="flex items-center w-full">
|
||||||
<div className="flex-none">
|
<div className="flex-none">
|
||||||
{mb.benefit_desc}
|
{pd.benefit_desc}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow h-[1px] mx-1 mt-1 bg-verdigris"></div>
|
<div className="flex-grow h-[1px] mx-1 mt-1 bg-verdigris"></div>
|
||||||
<div className="flex-none justify-end">
|
<div className="flex-none justify-end">
|
||||||
{mb.benefit}
|
{pd.benefit}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import Link from 'next/link';
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
|
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
|
||||||
import { FaAngleDown } from "react-icons/fa";
|
|
||||||
|
|
||||||
const userRoles: string[] = ["Member", "Employer"]
|
const userRoles: string[] = ["Member", "Employer"]
|
||||||
|
|
||||||
@@ -13,7 +11,7 @@ interface DropdownProps {
|
|||||||
const Dropdown: React.FC<DropdownProps> = ({currentRole}) => {
|
const Dropdown: React.FC<DropdownProps> = ({currentRole}) => {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
let avaliableUserRoles: string[] = userRoles.filter(role => role !== currentRole);
|
const avaliableUserRoles: string[] = userRoles.filter(role => role !== currentRole);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
@@ -31,8 +29,8 @@ const Dropdown: React.FC<DropdownProps> = ({currentRole}) => {
|
|||||||
{currentRole}
|
{currentRole}
|
||||||
</MenuButton>
|
</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]"}>
|
<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) => (
|
{avaliableUserRoles.map((role, i) => (
|
||||||
<MenuItem as="div" className={"px-1"}>
|
<MenuItem as="div" className={"px-1"} key={i}>
|
||||||
<a className="block data-focus:text-atmosphere hover:text-atmosphere" href={`/dashboard/${role.toLowerCase()}`}>
|
<a className="block data-focus:text-atmosphere hover:text-atmosphere" href={`/dashboard/${role.toLowerCase()}`}>
|
||||||
Switch To {role} Role
|
Switch To {role} Role
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { type SVGProps } from 'react'
|
import React from 'react'
|
||||||
import { IconBaseProps, IconType } from 'react-icons';
|
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 { LuLayoutDashboard, LuClipboardList } from "react-icons/lu";
|
||||||
import { IoDocumentsOutline } from "react-icons/io5";
|
import { IoDocumentsOutline } from "react-icons/io5";
|
||||||
import { BsClipboardCheck } from "react-icons/bs";
|
import { BsClipboardCheck } from "react-icons/bs";
|
||||||
@@ -20,7 +20,8 @@ const iconMap: iconMapType = {
|
|||||||
LuClipboardList: LuClipboardList,
|
LuClipboardList: LuClipboardList,
|
||||||
IoDocumentsOutline: IoDocumentsOutline,
|
IoDocumentsOutline: IoDocumentsOutline,
|
||||||
BsClipboardCheck: BsClipboardCheck,
|
BsClipboardCheck: BsClipboardCheck,
|
||||||
TbProgress: TbProgress
|
TbProgress: TbProgress,
|
||||||
|
FaArrowLeft: FaArrowLeft
|
||||||
};
|
};
|
||||||
|
|
||||||
type DynamicIconProps = IconBaseProps & {
|
type DynamicIconProps = IconBaseProps & {
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -9,17 +9,15 @@ interface SelectOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EmployerMemberSelectProps {
|
interface EmployerMemberSelectProps {
|
||||||
onSelectionCompleteEmployer: (employerValue: string) => void;
|
onSelectionCompleteEmployer: (plPlanKey: string) => void;
|
||||||
onSelectionCompleteMember: (memberValue: string) => void;
|
onSelectionCompleteMember: (pbEntityKey: string) => void;
|
||||||
userRole: 'employer' | 'broker' | 'carrier';
|
userRole: string;
|
||||||
employerAllButtonText: string;
|
roleId: string;
|
||||||
plPlanKey?: string;
|
employerAllButtonText?: string;
|
||||||
employerName?: 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 [optionsEmployer, setOptionsEmployer] = useState<SelectOption[]>([]);
|
||||||
const [selectedEmployer, setSelectedEmployer] = useState<string>('');
|
const [selectedEmployer, setSelectedEmployer] = useState<string>('');
|
||||||
const [loadingEmployer, setLoadingEmployer] = useState<boolean>(false);
|
const [loadingEmployer, setLoadingEmployer] = useState<boolean>(false);
|
||||||
@@ -28,20 +26,30 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
|
|||||||
const [selectedMember, setSelectedMember] = useState<string>('');
|
const [selectedMember, setSelectedMember] = useState<string>('');
|
||||||
const [loadingMember, setLoadingMember] = useState<boolean>(false);
|
const [loadingMember, setLoadingMember] = useState<boolean>(false);
|
||||||
|
|
||||||
const [apiUrl, setapiUrl] = useState("");
|
const [apiUrl, setApiUrl] = useState("");
|
||||||
|
const [token, setToken] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("/api/backendApiUrl")
|
fetch("/api/backendApiUrl")
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => setapiUrl(data.apiUrl));
|
.then((data) => {
|
||||||
|
setApiUrl(data.apiUrl)
|
||||||
|
setToken(data.token)
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchOptionsEmployer(role: string, roleId: number) {
|
if (apiUrl) {
|
||||||
|
async function fetchOptionsEmployer(role: string, roleId: string) {
|
||||||
setLoadingEmployer(true);
|
setLoadingEmployer(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://${apiUrl}/api/v1/${role}/${roleId}/employers_list`, {
|
const response = await fetch(`http://${apiUrl}/api/v1/${role}/${roleId}/employers_list`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json' ,
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setOptionsEmployer(data);
|
setOptionsEmployer(data);
|
||||||
@@ -51,13 +59,15 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
|
|||||||
setLoadingEmployer(false);
|
setLoadingEmployer(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (plPlanKey && employerName) {
|
|
||||||
setSelectedEmployer(plPlanKey)
|
if (userRole == "employer" && employerName) {
|
||||||
setOptionsEmployer([{entity_key: plPlanKey, name: employerName}]);
|
setSelectedEmployer(roleId)
|
||||||
} else if (brokerId) {
|
setOptionsEmployer([{entity_key: roleId, name: employerName}]);
|
||||||
fetchOptionsEmployer("brokers", brokerId);
|
} else if (userRole == "broker") {
|
||||||
} else if (carrierId) {
|
fetchOptionsEmployer("brokers", roleId);
|
||||||
fetchOptionsEmployer("carriers", carrierId);
|
} else if (userRole == "carrier") {
|
||||||
|
fetchOptionsEmployer("carriers", roleId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [apiUrl]);
|
}, [apiUrl]);
|
||||||
|
|
||||||
@@ -73,6 +83,11 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`http://${apiUrl}/api/v1/employers/${selectedEmployer}/members_list`, {
|
const response = await fetch(`http://${apiUrl}/api/v1/employers/${selectedEmployer}/members_list`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json' ,
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setOptionsMember(data);
|
setOptionsMember(data);
|
||||||
@@ -112,17 +127,16 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 max-w-xs">
|
<div className="flex flex-col w-full gap-4">
|
||||||
{/* First Select Dropdown */}
|
{/* First Select Dropdown */}
|
||||||
{!plPlanKey &&
|
<div className="flex flex-col w-full gap-1">
|
||||||
<div className="flex flex-col gap-1">
|
<label className="block text-md font-bold text-platinum">Employer</label>
|
||||||
<label className="text-sm font-medium">Employer</label>
|
<div className="flex w-full">
|
||||||
<div className="flex">
|
|
||||||
<select
|
<select
|
||||||
value={selectedEmployer}
|
value={selectedEmployer}
|
||||||
onChange={handleFirstChange}
|
onChange={handleFirstChange}
|
||||||
disabled={loadingEmployer}
|
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>
|
<option value="">{loadingEmployer ? 'Loading...' : '-- Select Employer --'}</option>
|
||||||
{optionsEmployer.map((opt) => (
|
{optionsEmployer.map((opt) => (
|
||||||
@@ -131,31 +145,34 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
{employerAllButtonText &&
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAllEmployerSelect}
|
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}
|
{employerAllButtonText}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Second Select Dropdown */}
|
{/* Second Select Dropdown */}
|
||||||
|
{selectedEmployer &&
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<label className="text-sm font-medium">Member</label>
|
<label className="block text-md font-bold text-platinum">Member</label>
|
||||||
<div className="flex">
|
<div className="flex w-full">
|
||||||
<select
|
<select
|
||||||
value={selectedMember}
|
value={selectedMember}
|
||||||
onChange={handleSecondChange}
|
onChange={handleSecondChange}
|
||||||
disabled={!selectedEmployer || loadingMember}
|
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="">
|
<option value="">
|
||||||
{!selectedEmployer
|
{!selectedEmployer
|
||||||
? '-- Select a category first --'
|
? '-- Select a category first --'
|
||||||
: loadingMember ? 'Loading...' : '-- Select Subcategory --'}
|
: loadingMember ? 'Loading...' : '-- Select Member --'}
|
||||||
</option>
|
</option>
|
||||||
{optionsMember.map((opt) => (
|
{optionsMember.map((opt) => (
|
||||||
<option key={opt.entity_key} value={opt.entity_key}>
|
<option key={opt.entity_key} value={opt.entity_key}>
|
||||||
@@ -164,14 +181,18 @@ const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelection
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleAllEmployerSelect}
|
onClick={handleMemberSelect}
|
||||||
|
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-br-md">
|
||||||
Select Member
|
Generate Member
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default EmployerMemberSelect;
|
||||||
@@ -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;
|
||||||
@@ -3,19 +3,12 @@
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
|
||||||
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
|
||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
interface FindProviderWidgetProps {
|
interface FindProviderWidgetProps {
|
||||||
networkProvider: string;
|
networkProvider: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NetworkProvider {
|
|
||||||
network_provider: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FindProviderWidget: React.FC<FindProviderWidgetProps> = ({ networkProvider }) => {
|
const FindProviderWidget: React.FC<FindProviderWidgetProps> = ({ networkProvider }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import classnames from 'classnames';
|
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import { FaRegIdCard } from "react-icons/fa";
|
|
||||||
import Image from 'next/image';
|
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';
|
import Link from 'next/link';
|
||||||
|
|
||||||
const FindRxInformationWidget = () => {
|
const FindRxInformationWidget = () => {
|
||||||
|
|||||||
+26
-22
@@ -1,6 +1,5 @@
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { BsLinkedin } from "react-icons/bs";
|
|
||||||
import { Schibsted_Grotesk, Fraunces } from 'next/font/google'
|
import { Schibsted_Grotesk, Fraunces } from 'next/font/google'
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
@@ -11,29 +10,31 @@ const Footer = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<footer className="m-h-[135vh] bg-deepcove flex justify-center py-30">
|
<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-1/2 h-full flex flex-col justify-between'>
|
||||||
<div className='w-full flex justify-start'>
|
<div className='w-full flex justify-start'>
|
||||||
|
<Link href="/" className="inline-block overflow-hidden rounded-lg">
|
||||||
<Image
|
<Image
|
||||||
src="/images/bbstpa-forsite.png"
|
src="/images/bbstpa-forsite.png"
|
||||||
alt="Britton Logo"
|
alt="Britton Logo"
|
||||||
width={410}
|
width={410}
|
||||||
height={120}
|
height={120}
|
||||||
/>
|
/>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full flex flex-col justify-start ml-[22px]'>
|
<div className='w-full flex flex-col justify-start ml-[22px]'>
|
||||||
<div className='flex'>
|
<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' />
|
<BsLinkedin size={32} className='hover:text-atmosphere' />
|
||||||
</Link>
|
</Link> */}
|
||||||
</div>
|
</div>
|
||||||
<ul className="flex flex-col pt-2 text-md font-semibold">
|
<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>
|
<a href="#" className="hover:text-atmosphere">Terms of Service</a>
|
||||||
</li>
|
</li>
|
||||||
<li className='pt-1'>
|
<li className='pt-1'>
|
||||||
<a href="#" className="hover:text-atmosphere">Website Privacy Statement</a>
|
<a href="#" className="hover:text-atmosphere">Website Privacy Statement</a>
|
||||||
</li>
|
</li> */}
|
||||||
<li className='pt-3 font-normal text-xs'>
|
<li className='pt-3 font-normal text-xs'>
|
||||||
© Copyright 2025 Britton Benefit Services, LLC All Rights Reserved
|
© Copyright 2025 Britton Benefit Services, LLC All Rights Reserved
|
||||||
<br />
|
<br />
|
||||||
@@ -45,7 +46,7 @@ const Footer = () => {
|
|||||||
<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-5'>
|
||||||
<div className={classnames("text-xl text-bluemana pb-12", headerFont.className)}>
|
<div className={classnames("text-xl text-bluemana pb-12", headerFont.className)}>
|
||||||
<div className="pb-6">
|
<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>
|
||||||
<div className="text-xl border-b border-bronze mt-4 pb-2 mr-20">Resources</div>
|
<div className="text-xl border-b border-bronze mt-4 pb-2 mr-20">Resources</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,13 +55,16 @@ const Footer = () => {
|
|||||||
Members
|
Members
|
||||||
</li>
|
</li>
|
||||||
<li className='pt-2'>
|
<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>
|
||||||
<li className='pt-2'>
|
<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>
|
||||||
<li className='pt-2'>
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="pt-7 text-md font-medium">
|
<ul className="pt-7 text-md font-medium">
|
||||||
@@ -68,9 +72,9 @@ const Footer = () => {
|
|||||||
Employers
|
Employers
|
||||||
</li>
|
</li>
|
||||||
<li className='pt-2'>
|
<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>
|
||||||
<li className='pt-2'>
|
{/* <li className='pt-2'>
|
||||||
<a href="#" className='hover:text-bluemana'>Self-Funded Plans</a>
|
<a href="#" className='hover:text-bluemana'>Self-Funded Plans</a>
|
||||||
</li>
|
</li>
|
||||||
<li className='pt-2'>
|
<li className='pt-2'>
|
||||||
@@ -78,45 +82,45 @@ const Footer = () => {
|
|||||||
</li>
|
</li>
|
||||||
<li className='pt-2'>
|
<li className='pt-2'>
|
||||||
<a href="#" className='hover:text-bluemana'>Control Health Care Costs</a>
|
<a href="#" className='hover:text-bluemana'>Control Health Care Costs</a>
|
||||||
</li>
|
</li> */}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="pt-7 text-md font-medium">
|
<ul className="pt-7 text-md font-medium">
|
||||||
<li className="text-bronze mb-4">
|
<li className="text-bronze mb-4">
|
||||||
Providers
|
Providers
|
||||||
</li>
|
</li>
|
||||||
<li className='pt-2'>
|
<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>
|
||||||
<li className='pt-2'>
|
<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>
|
||||||
<li className='pt-2'>
|
{/* <li className='pt-2'>
|
||||||
<a href="#" className='hover:text-bluemana'>Contact BBS</a>
|
<a href="#" className='hover:text-bluemana'>Contact BBS</a>
|
||||||
</li>
|
</li> */}
|
||||||
</ul>
|
</ul>
|
||||||
<ul className="pt-7 text-md font-medium">
|
<ul className="pt-7 text-md font-medium">
|
||||||
<li className="text-bronze mb-4">
|
<li className="text-bronze mb-4">
|
||||||
Brokers
|
Brokers
|
||||||
</li>
|
</li>
|
||||||
<li className='pt-2'>
|
<li className='pt-2'>
|
||||||
<a href="#" className='hover:text-bluemana'>Brokers Landing</a>
|
<a href="/brokers" className='hover:text-bluemana'>Brokers Landing</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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={classnames("text-xl text-bluemana pb-12", headerFont.className)}>
|
||||||
<div className="pb-6">
|
<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>
|
||||||
<div className="text-xl border-b border-bronze mt-4 pb-2 mr-20">Company</div>
|
<div className="text-xl border-b border-bronze mt-4 pb-2 mr-20">Company</div>
|
||||||
</div>
|
</div>
|
||||||
<ul className="pt-3 text-md font-medium">
|
<ul className="pt-3 text-md font-medium">
|
||||||
<li className='pt-3'>
|
{/* <li className='pt-3'>
|
||||||
<a href="#" className='hover:text-bluemana' >About</a>
|
<a href="#" className='hover:text-bluemana' >About</a>
|
||||||
</li>
|
</li>
|
||||||
<li className='pt-6'>
|
<li className='pt-6'>
|
||||||
<a href="#" className='hover:text-bluemana' >Careers</a>
|
<a href="#" className='hover:text-bluemana' >Careers</a>
|
||||||
</li>
|
</li> */}
|
||||||
<li className='pt-6'>
|
<li className='pt-6'>
|
||||||
<a href="#" className='hover:text-bluemana' >Contact Us</a>
|
<a href="#" className='hover:text-bluemana' >Contact Us</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -3,46 +3,48 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
interface IdCardDownloaderProps {
|
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 [loading, setLoading] = useState(false);
|
||||||
const [apiUrl, setapiUrl] = useState("");
|
const [apiUrl, setapiUrl] = useState("");
|
||||||
|
const [token, setToken] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("/api/backendApiUrl")
|
fetch("/api/backendApiUrl")
|
||||||
.then((res) => res.json())
|
.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 () => {
|
const handleDownload = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://${apiUrl}/api/v1/employers/${plPlanKey}/id_cards`, {
|
const response = await fetch(`http://${apiUrl}/api/v1/employers/${plPlanKey}/id_cards`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
credentials: 'include',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/zip',
|
'Accept': 'application/zip',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) throw new Error('Failed to download');
|
if (!response.ok) throw new Error('Failed to download');
|
||||||
|
|
||||||
const contentDisposition = response.headers.get('Content-Disposition');
|
const disposition = response.headers.get('content-disposition') || '';
|
||||||
console.log("contentDisposition: " + contentDisposition);
|
const filenameMatch = disposition.match(/filename="(.+)"/);
|
||||||
let fileName = 'employer_id_cards.zip'; // Default fallback name
|
const fileName = filenameMatch ? filenameMatch[1] : 'employer_id_cards.zip';
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// Convert the response to a Blob
|
// Convert the response to a Blob
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
@@ -69,6 +71,7 @@ const IdCardDownloader: React.FC<IdCardDownloaderProps> = ({ plPlanKey }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center">
|
<div className="flex flex-col items-center justify-center">
|
||||||
|
{!autoFetchCard && (
|
||||||
<button
|
<button
|
||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
@@ -76,6 +79,7 @@ const IdCardDownloader: React.FC<IdCardDownloaderProps> = ({ plPlanKey }) => {
|
|||||||
>
|
>
|
||||||
Download Employer Cards
|
Download Employer Cards
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{(loading) && (
|
{(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">
|
<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>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -6,27 +6,44 @@ import { FaRegIdCard } from "react-icons/fa";
|
|||||||
|
|
||||||
interface IdCardViewerProps {
|
interface IdCardViewerProps {
|
||||||
cardLaout: 'FullPageCard' | 'MobileDisplayCard';
|
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 [loading, setLoading] = useState(false);
|
||||||
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
||||||
const [apiUrl, setapiUrl] = useState("");
|
const [apiUrl, setapiUrl] = useState("");
|
||||||
|
const [token, setToken] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("/api/backendApiUrl")
|
fetch("/api/backendApiUrl")
|
||||||
.then((res) => res.json())
|
.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 () => {
|
const handleFetchPdf = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setPdfUrl(null);
|
setPdfUrl(null);
|
||||||
|
console.log(token)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`http://${apiUrl}/api/v1/members/${pbEntityKey}/id_card/${cardLaout}`, {
|
const response = await fetch(`http://${apiUrl}/api/v1/members/${pbEntityKey}/id_card/${cardLaout}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/pdf',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) throw new Error("Failed to fetch PDF");
|
if (!response.ok) throw new Error("Failed to fetch PDF");
|
||||||
@@ -51,6 +68,8 @@ const IdCardViewer: React.FC<IdCardViewerProps> = ({ cardLaout, pbEntityKey }) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{!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="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 flex-col justify-end items-center mb-4">
|
||||||
<div className="flex items-end mb-3">
|
<div className="flex items-end mb-3">
|
||||||
@@ -67,6 +86,8 @@ const IdCardViewer: React.FC<IdCardViewerProps> = ({ cardLaout, pbEntityKey }) =
|
|||||||
<FaRegIdCard size={150} className="text-bluemana hover:text-light-bronze" />
|
<FaRegIdCard size={150} className="text-bluemana hover:text-light-bronze" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* <div className="flex flex-col items-center justify-center">
|
{/* <div className="flex flex-col items-center justify-center">
|
||||||
<Button
|
<Button
|
||||||
@@ -143,7 +164,7 @@ const IdCardViewer: React.FC<IdCardViewerProps> = ({ cardLaout, pbEntityKey }) =
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)} */}
|
)} */}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const InfoCard: React.FC<InfoCardProps> = (
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={classnames(
|
<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,
|
cardBGColor, font.className,
|
||||||
{
|
{
|
||||||
"rounded-l-2xl rounded-out-rt-2xl" : !invert,
|
"rounded-l-2xl rounded-out-rt-2xl" : !invert,
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ import classnames from 'classnames';
|
|||||||
|
|
||||||
interface InlineLinkProps {
|
interface InlineLinkProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
link: string;
|
||||||
alt?: boolean;
|
alt?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InlineLink: React.FC<InlineLinkProps> = ({ children, alt }) => {
|
const InlineLink: React.FC<InlineLinkProps> = ({ children, alt, link }) => {
|
||||||
return (
|
return (
|
||||||
<a href="#" className={classnames(
|
<a href={link} className={classnames(
|
||||||
{
|
{
|
||||||
"text-bluemana hover:text-bronze" : !alt,
|
"text-bluemana hover:text-bronze" : !alt,
|
||||||
"text-bluemana hover:text-atmosphere" : alt,
|
"text-bluemana hover:text-atmosphere" : alt,
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
@@ -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;
|
||||||
@@ -6,73 +6,6 @@ import Link from 'next/link';
|
|||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import Image from 'next/image';
|
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 {
|
interface NavbarProps {
|
||||||
collapseHeight: number;
|
collapseHeight: number;
|
||||||
@@ -83,8 +16,6 @@ const Navbar: React.FC<NavbarProps> = ({ collapseHeight }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
// const subtractedHeight = window.innerHeight - 80;
|
|
||||||
const subtractedHeight = window.innerHeight - collapseHeight;
|
|
||||||
if (window.scrollY > collapseHeight) {
|
if (window.scrollY > collapseHeight) {
|
||||||
setIsScrolled(true);
|
setIsScrolled(true);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -28,8 +28,8 @@ const QuickAccessLinks: React.FC<QuickAccessLinksProps> = ({ imageUrl, quickLink
|
|||||||
Quick Access to Your Services
|
Quick Access to Your Services
|
||||||
</div>
|
</div>
|
||||||
<ul className={classnames("flex flex-col pl-10 text-2xl leading-12 list-disc list-inside", font.className)}>
|
<ul className={classnames("flex flex-col pl-10 text-2xl leading-12 list-disc list-inside", font.className)}>
|
||||||
{quickLinks.map((quickLink) => (
|
{quickLinks.map((quickLink, i) => (
|
||||||
<li><InlineLink>{quickLink.display}</InlineLink></li>
|
<li key={i} ><InlineLink link={quickLink.url}>{quickLink.display}</InlineLink></li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import classnames from 'classnames';
|
|||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
|
|
||||||
import { Schibsted_Grotesk } from 'next/font/google'
|
import { Schibsted_Grotesk } from 'next/font/google'
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
const font = Schibsted_Grotesk({ subsets: ['latin'] })
|
||||||
|
|
||||||
|
|||||||
+14
-16
@@ -1,34 +1,31 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, ComponentType } from 'react';
|
import React from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import DynamicIcon from './DynamicIcon';
|
import DynamicIcon from './DynamicIcon';
|
||||||
import { NavItems } from '@/lib/dashboard';
|
import { sideNavItems, 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 { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
|
||||||
interface SideNavProps {
|
interface SideNavProps {
|
||||||
maxWidth: string;
|
|
||||||
bgColor: string;
|
bgColor: string;
|
||||||
navItems: NavItems[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SideNav: React.FC<SideNavProps> = (
|
const SideNav: React.FC<SideNavProps> = (
|
||||||
{
|
{
|
||||||
maxWidth,
|
bgColor
|
||||||
bgColor,
|
|
||||||
navItems
|
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<div className={classnames(
|
<div className={classnames(
|
||||||
@@ -48,10 +45,10 @@ const SideNav: React.FC<SideNavProps> = (
|
|||||||
{isSidebarOpen ? <FaArrowCircleLeft size={30} /> : <FaArrowCircleRight size={30} />}
|
{isSidebarOpen ? <FaArrowCircleLeft size={30} /> : <FaArrowCircleRight size={30} />}
|
||||||
</Button> */}
|
</Button> */}
|
||||||
<div className="flex flex-col divide-y divide-bluetang">
|
<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(
|
<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",
|
"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 ? (
|
{isSidebarOpen ? (
|
||||||
<>{navItem.title}</>
|
<>{navItem.title}</>
|
||||||
@@ -60,6 +57,7 @@ const SideNav: React.FC<SideNavProps> = (
|
|||||||
)}
|
)}
|
||||||
</a>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ interface TaglineProps {
|
|||||||
|
|
||||||
const Tagline: React.FC<TaglineProps> = (
|
const Tagline: React.FC<TaglineProps> = (
|
||||||
{
|
{
|
||||||
bgColor,
|
|
||||||
textColor,
|
|
||||||
textTop,
|
textTop,
|
||||||
textBottom
|
textBottom
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ interface TextImage50Props {
|
|||||||
imageUrl: string,
|
imageUrl: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextImage50: React.FC<TextImage50Props> = ({ children, className, imageUrl }) => {
|
const TextImage50: React.FC<TextImage50Props> = ({ children, imageUrl }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex justify-between py-5">
|
<div className="w-full flex justify-between py-5">
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ export const sideNavItems: sideNavGroups = {
|
|||||||
url: "/dashboard/member/claims",
|
url: "/dashboard/member/claims",
|
||||||
iconName: "BsClipboardCheck"
|
iconName: "BsClipboardCheck"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Documents",
|
||||||
|
url: "/dashboard/member/documents",
|
||||||
|
iconName: "LuClipboardList"
|
||||||
|
},
|
||||||
// {
|
// {
|
||||||
// title: "Providers & Services",
|
// title: "Providers & Services",
|
||||||
// url: "/",
|
// url: "/",
|
||||||
@@ -46,5 +51,98 @@ export const sideNavItems: sideNavGroups = {
|
|||||||
// url: "/",
|
// url: "/",
|
||||||
// iconName: "IoDocumentsOutline"
|
// 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"
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+9
-1
@@ -2,12 +2,20 @@ import { NextResponse } from "next/server";
|
|||||||
import type { NextRequest } from "next/server";
|
import type { NextRequest } from "next/server";
|
||||||
|
|
||||||
export function middleware(request: NextRequest) {
|
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);
|
const headers = new Headers(request.headers);
|
||||||
headers.set("x-current-path", request.nextUrl.pathname);
|
headers.set("x-current-path", request.nextUrl.pathname);
|
||||||
return NextResponse.next({ headers });
|
return NextResponse.next({ headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
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*"
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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> */}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user