From ead18a470905a065b14c4a44bcb2a961831db918 Mon Sep 17 00:00:00 2001 From: Jason Jordan Date: Wed, 16 Sep 2026 12:39:54 -0400 Subject: [PATCH] Production Build Working --- .dockerignore | 12 +- Dockerfile | 43 +++ docker-compose.yml | 5 + next.config.ts | 9 +- src/app/(informational)/brokers/page.tsx | 13 +- src/app/(informational)/employers/index.tsx | 28 -- src/app/(informational)/employers/page.tsx | 11 +- src/app/(informational)/layout.tsx | 30 +- src/app/(informational)/members/indexold.tsx | 25 -- src/app/(informational)/members/page.tsx | 25 +- src/app/(informational)/providers/page.tsx | 13 +- src/app/account/forgot-password/page.tsx | 81 +++++ src/app/account/layout.tsx | 35 +- src/app/account/password-reset/page.tsx | 117 +++++++ src/app/account/signin/page.tsx | 29 +- src/app/account/signup/page.tsx | 4 +- src/app/actions/auth.ts | 100 ------ src/app/actions/signin.ts | 49 ++- src/app/actions/signup.ts | 62 ++-- src/app/api/backendApiUrl/route.ts | 17 +- src/app/api/signout/route.ts | 45 +++ src/app/dashboard/broker/eligibility/page.tsx | 90 +++++ .../broker/employer-coverage/page.tsx | 85 +++++ src/app/dashboard/broker/id-cards/page.tsx | 86 +++++ src/app/dashboard/broker/index.tsx | 141 -------- src/app/dashboard/broker/page.tsx | 87 +++++ src/app/dashboard/carrier/claims/page.tsx | 90 +++++ .../dashboard/carrier/eligibility/page.tsx | 90 +++++ .../carrier/employer-coverage/page.tsx | 89 +++++ src/app/dashboard/carrier/id-cards/page.tsx | 90 +++++ src/app/dashboard/carrier/page.tsx | 94 ++++++ src/app/dashboard/employer/documents/page.tsx | 117 +++++++ .../dashboard/employer/eligibility/page.tsx | 91 ++++++ src/app/dashboard/employer/id-cards/page.tsx | 91 ++++++ src/app/dashboard/employer/idCards/page.tsx | 148 --------- .../employer/members-coverage/page.tsx | 86 +++++ src/app/dashboard/employer/page.tsx | 120 ++----- src/app/dashboard/layout.tsx | 40 +-- src/app/dashboard/login/page.tsx | 76 ----- src/app/dashboard/member/claims/page.tsx | 18 +- src/app/dashboard/member/documents/page.tsx | 114 +++++++ src/app/dashboard/member/indexold.tsx | 308 ------------------ src/app/dashboard/member/page.tsx | 61 ++-- .../dashboard/provider/claim-search/page.tsx | 86 +++++ .../provider/eligibility-lookup/page.tsx | 85 +++++ src/app/dashboard/provider/page.tsx | 83 +++++ src/app/layout.tsx | 3 - src/app/page.tsx | 9 +- src/app/public/eligibility-lookup/page.tsx | 27 ++ src/app/public/layout.tsx | 49 +++ src/components/AccountDropdown.tsx | 9 +- src/components/ArrowText.tsx | 26 ++ src/components/BenefitsAAGWidget.tsx | 28 +- src/components/ClaimsSelectorNew.tsx | 51 +++ src/components/ClaimsViewer.tsx | 131 ++++++++ src/components/CoverageSelector.tsx | 44 +++ src/components/CoverageViewer.tsx | 117 +++++++ src/components/DashboardPieChart.tsx | 92 ------ src/components/DeductableProgressWidget.tsx | 36 -- src/components/DocumentDownloader.tsx | 76 +++++ src/components/Dropdown.tsx | 8 +- src/components/DynamicIcon.tsx | 7 +- src/components/EligibilitySearch.tsx | 51 +++ src/components/EligibilitySelector.tsx | 51 +++ src/components/EligibilityViewer.tsx | 121 +++++++ src/components/EmployerMemberSelect.tsx | 111 ++++--- src/components/EmployerSelect.tsx | 107 ++++++ src/components/FindProvider.tsx | 7 - src/components/FindProviderOld.tsx | 57 ---- src/components/FindRxInformation.tsx | 7 - src/components/Footer.tsx | 60 ++-- src/components/IdCardDownloader.tsx | 74 ++--- src/components/IdCardSelector.tsx | 58 ++++ src/components/IdCardViewer.tsx | 57 +++- src/components/InfoCard.tsx | 2 +- src/components/InlineLink.tsx | 5 +- src/components/InsuranceCard.tsx | 71 ---- src/components/LandingPage.tsx | 88 ----- src/components/LocationSearchInput.tsx | 86 ----- src/components/MemberNumberDoB.tsx | 96 ++++++ src/components/Navbar.tsx | 69 ---- src/components/ProviderClaimSearch.tsx | 44 +++ src/components/QuickAccessLinks.tsx | 4 +- src/components/RecentClaimsWidget.tsx | 1 - src/components/SideNav.tsx | 30 +- src/components/SideNavTemp.tsx | 49 --- src/components/SignOut.tsx | 33 ++ src/components/SignOutTimer.tsx | 82 ----- src/components/Tagline.tsx | 2 - src/components/TextImage50.tsx | 2 +- src/lib/dashboard.tsx | 98 ++++++ src/middleware.ts | 12 +- src/pages/_appold.tsx | 29 -- src/pages/_document.tsx | 13 - src/pages/index-services.tsx | 77 ----- src/pages/index-testing.tsx | 31 -- 96 files changed, 3473 insertions(+), 2144 deletions(-) create mode 100644 Dockerfile delete mode 100644 src/app/(informational)/employers/index.tsx delete mode 100644 src/app/(informational)/members/indexold.tsx create mode 100644 src/app/account/forgot-password/page.tsx create mode 100644 src/app/account/password-reset/page.tsx delete mode 100644 src/app/actions/auth.ts create mode 100644 src/app/api/signout/route.ts create mode 100644 src/app/dashboard/broker/eligibility/page.tsx create mode 100644 src/app/dashboard/broker/employer-coverage/page.tsx create mode 100644 src/app/dashboard/broker/id-cards/page.tsx delete mode 100644 src/app/dashboard/broker/index.tsx create mode 100644 src/app/dashboard/broker/page.tsx create mode 100644 src/app/dashboard/carrier/claims/page.tsx create mode 100644 src/app/dashboard/carrier/eligibility/page.tsx create mode 100644 src/app/dashboard/carrier/employer-coverage/page.tsx create mode 100644 src/app/dashboard/carrier/id-cards/page.tsx create mode 100644 src/app/dashboard/carrier/page.tsx create mode 100644 src/app/dashboard/employer/documents/page.tsx create mode 100644 src/app/dashboard/employer/eligibility/page.tsx create mode 100644 src/app/dashboard/employer/id-cards/page.tsx delete mode 100644 src/app/dashboard/employer/idCards/page.tsx create mode 100644 src/app/dashboard/employer/members-coverage/page.tsx delete mode 100644 src/app/dashboard/login/page.tsx create mode 100644 src/app/dashboard/member/documents/page.tsx delete mode 100644 src/app/dashboard/member/indexold.tsx create mode 100644 src/app/dashboard/provider/claim-search/page.tsx create mode 100644 src/app/dashboard/provider/eligibility-lookup/page.tsx create mode 100644 src/app/dashboard/provider/page.tsx create mode 100644 src/app/public/eligibility-lookup/page.tsx create mode 100644 src/app/public/layout.tsx create mode 100644 src/components/ArrowText.tsx create mode 100644 src/components/ClaimsSelectorNew.tsx create mode 100644 src/components/ClaimsViewer.tsx create mode 100644 src/components/CoverageSelector.tsx create mode 100644 src/components/CoverageViewer.tsx delete mode 100644 src/components/DashboardPieChart.tsx delete mode 100644 src/components/DeductableProgressWidget.tsx create mode 100644 src/components/DocumentDownloader.tsx create mode 100644 src/components/EligibilitySearch.tsx create mode 100644 src/components/EligibilitySelector.tsx create mode 100644 src/components/EligibilityViewer.tsx create mode 100644 src/components/EmployerSelect.tsx delete mode 100644 src/components/FindProviderOld.tsx create mode 100644 src/components/IdCardSelector.tsx delete mode 100644 src/components/InsuranceCard.tsx delete mode 100644 src/components/LandingPage.tsx delete mode 100644 src/components/LocationSearchInput.tsx create mode 100644 src/components/MemberNumberDoB.tsx create mode 100644 src/components/ProviderClaimSearch.tsx delete mode 100644 src/components/SideNavTemp.tsx create mode 100644 src/components/SignOut.tsx delete mode 100644 src/pages/_appold.tsx delete mode 100644 src/pages/_document.tsx delete mode 100644 src/pages/index-services.tsx delete mode 100644 src/pages/index-testing.tsx diff --git a/.dockerignore b/.dockerignore index d07ece8..9fd447f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,9 @@ - node_modules/ - .env - .git \ No newline at end of file +node_modules +.next +out +build +.git +.gitignore +.env* +.local +README.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1a8b547 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/docker-compose.yml b/docker-compose.yml index 2be35d1..df1f331 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,9 +3,14 @@ services: build: context: . dockerfile: Dockerfile.dev + # dockerfile: Dockerfile ports: - "3001:3001" + env_file: + - .env environment: + - NODE_ENV=development + # - NODE_ENV=production - BE_API_SERVER_HOST=web_api - BE_API_SERVER_PORT=3005 volumes: diff --git a/next.config.ts b/next.config.ts index b339dfc..b4213ed 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,14 +1,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - // async rewrites() { - // return [ - // { - // source: '/api-backend/:path*', - // destination: `http://web_api:3005`, // The external api URL - // }, - // ]; - // }, + output: 'standalone', outputFileTracingIncludes: { '/api/**/*': ['./node_modules/jose/**/*'], }, diff --git a/src/app/(informational)/brokers/page.tsx b/src/app/(informational)/brokers/page.tsx index 19371da..8b40835 100644 --- a/src/app/(informational)/brokers/page.tsx +++ b/src/app/(informational)/brokers/page.tsx @@ -1,6 +1,5 @@ import { Schibsted_Grotesk } from 'next/font/google' import classnames from 'classnames'; -import InlineLink from "@/components/InlineLink"; import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks'; import LandingPageHeader from '@/components/LandingPageHeader'; @@ -8,8 +7,8 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] }) const quickLinks: QuickLink[] = [ { - display: "Member Login/Registration", - url: "/" + display: "Broker SignIn", + url: "/account/signin" }, { display: "Link to Thing 1", @@ -36,17 +35,17 @@ export default function Brokers() { Partner with Britton Benefit Services to Grow Your Business and Serve Your Clients
- 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.

Broker Services from Britton Benefit Services -
    +
    • - Discuss your client’s needs and provide additional insight + Discuss your client's needs and provide additional insight
    • Learn more about our company and self-funding diff --git a/src/app/(informational)/employers/index.tsx b/src/app/(informational)/employers/index.tsx deleted file mode 100644 index 8d2b34f..0000000 --- a/src/app/(informational)/employers/index.tsx +++ /dev/null @@ -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 ( -
      - - 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. -
      -
      - 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: -
      -
      - 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
      - • Control your health care costs by selecting a PPO that’s actively competing for your business. -
      -
      - 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. -
      -
      - ) -} \ No newline at end of file diff --git a/src/app/(informational)/employers/page.tsx b/src/app/(informational)/employers/page.tsx index 387630e..1e1fc48 100644 --- a/src/app/(informational)/employers/page.tsx +++ b/src/app/(informational)/employers/page.tsx @@ -1,6 +1,5 @@ import { Schibsted_Grotesk } from 'next/font/google' import classnames from 'classnames'; -import InlineLink from "@/components/InlineLink"; import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks'; import LandingPageHeader from '@/components/LandingPageHeader'; @@ -8,8 +7,8 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] }) const quickLinks: QuickLink[] = [ { - display: "Employer Login/Registration", - url: "/" + display: "Employer SignIn", + url: "/account/signin" }, { display: "Link to Thing 1", @@ -35,7 +34,7 @@ export default function Employers() { Making Your Benefits Work for You and Your Employees
@@ -46,9 +45,9 @@ export default function Employers() {

Look up in-network providers through our list of PPOs
- • File insurance claims and review a claim’s status
+ • File insurance claims and review a claim's status
• Research which type of plan is best for you
- • 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.

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. diff --git a/src/app/(informational)/layout.tsx b/src/app/(informational)/layout.tsx index 0bce36c..40755f4 100644 --- a/src/app/(informational)/layout.tsx +++ b/src/app/(informational)/layout.tsx @@ -1,14 +1,9 @@ -import { headers } from "next/headers"; import classnames from 'classnames'; import Navbar from '@/components/Navbar'; import Footer from '@/components/Footer'; export default async function LandingPageLayout({ children }: { children: React.ReactNode }) { - const headersList = await headers(); - const pathname = headersList.get("x-current-path"); - const landingPageType = pathname?.split('/').pop() || ""; - const headerText = landingPageType.charAt(0).toUpperCase() + landingPageType.slice(1); return ( <> @@ -23,30 +18,7 @@ export default async function LandingPageLayout({ children }: { children: React.
- {/*
-
-
-
-
-
-
-
-
- {headerText} -
-
-
-
-
*/} -
+
{children}
diff --git a/src/app/(informational)/members/indexold.tsx b/src/app/(informational)/members/indexold.tsx deleted file mode 100644 index 31d6ffc..0000000 --- a/src/app/(informational)/members/indexold.tsx +++ /dev/null @@ -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 ( -
- - 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. -
-
- 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. -
-
- 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. -
-
- 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. -
-
- ) -} \ No newline at end of file diff --git a/src/app/(informational)/members/page.tsx b/src/app/(informational)/members/page.tsx index c767c93..a325b6e 100644 --- a/src/app/(informational)/members/page.tsx +++ b/src/app/(informational)/members/page.tsx @@ -1,6 +1,5 @@ import { Schibsted_Grotesk } from 'next/font/google' import classnames from 'classnames'; -import InlineLink from "@/components/InlineLink"; import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks'; import LandingPageHeader from '@/components/LandingPageHeader'; @@ -9,19 +8,19 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] }) const quickLinks: QuickLink[] = [ { display: "Member Login/Registration", - url: "/" + url: "/account/signin" }, { - display: "Link to Thing 1", - url: "/" + display: "Find a MedCost Medical Provider", + url: "http://medcost.com/" }, { - display: "Link to Thing 2", - url: "/" + display: "Find a Cigna Medical Provider", + url: "http://cigna.com/" }, { - display: "Link to another Thing", - url: "/" + display: "Obtain Rx Information", + url: "https://www.fairosrx.com/" } ] @@ -36,20 +35,20 @@ export default function Members() { Member Tagline will go here when we come up with the wording
- 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.

- 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.

- 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.

- 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.
diff --git a/src/app/(informational)/providers/page.tsx b/src/app/(informational)/providers/page.tsx index 1f1f696..8523104 100644 --- a/src/app/(informational)/providers/page.tsx +++ b/src/app/(informational)/providers/page.tsx @@ -1,6 +1,5 @@ import { Schibsted_Grotesk } from 'next/font/google' import classnames from 'classnames'; -import InlineLink from "@/components/InlineLink"; import QuickAccessLinks, { QuickLink } from '@/components/QuickAccessLinks'; import LandingPageHeader from '@/components/LandingPageHeader'; @@ -8,12 +7,12 @@ const font = Schibsted_Grotesk({ subsets: ['latin'] }) const quickLinks: QuickLink[] = [ { - display: "Member Login/Registration", - url: "/" + display: "Provider SignIn/Registration", + url: "/account/signin" }, { - display: "Link to Thing 1", - url: "/" + display: "Eligibility & Benefits Lookup", + url: "/public/eligibility-lookup" }, { display: "Link to Thing 2", @@ -36,11 +35,11 @@ export default function Providers() { Easy Access to HIPAA Compliant Patient Information and Much More
- 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.
diff --git a/src/app/account/forgot-password/page.tsx b/src/app/account/forgot-password/page.tsx new file mode 100644 index 0000000..cef7b42 --- /dev/null +++ b/src/app/account/forgot-password/page.tsx @@ -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) => { + 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 ( +
+
+ Britton Benefits +
+
+
+ Forgot Password +
+
+
+
+ + 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 + /> +
+
+ + +
+ {message &&

{message}

} +
+
+ ) +} diff --git a/src/app/account/layout.tsx b/src/app/account/layout.tsx index 211bf53..58080c7 100644 --- a/src/app/account/layout.tsx +++ b/src/app/account/layout.tsx @@ -1,20 +1,12 @@ "user client" -import { sideNavItems } from '@/lib/dashboard'; -import { headers } from "next/headers"; -import Script from 'next/script'; - -import AccountDropdown from '../../components/AccountDropdown'; -import SideNav from "@/components/SideNav"; import Image from 'next/image'; import classnames from 'classnames'; +import Footer from '@/components/Footer'; +import Link from 'next/link'; export default async function DashboardLayout({ children }: { children: React.ReactNode }) { - const headersList = await headers(); - const pathname = headersList.get("x-current-path"); - const dashboardType = pathname?.split('/').pop() || ""; - const pageNavItems = sideNavItems[dashboardType]; return (
@@ -30,25 +22,28 @@ export default async function DashboardLayout({ children }: { children: React.Re
-
+
-
+
-
- Britton Logo +
+ + Britton Logo +
-
+
{children}
+
) } \ No newline at end of file diff --git a/src/app/account/password-reset/page.tsx b/src/app/account/password-reset/page.tsx new file mode 100644 index 0000000..85b42dc --- /dev/null +++ b/src/app/account/password-reset/page.tsx @@ -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) => { + 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 ( +
+
+ Britton Benefits + {/*

Britton

+

Benefits

*/} +
+
+
+ Password Reset +
+ +
+
+
+ + 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 + /> +
+
+ +
+
+ + 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 + /> +
+
+ + +
+ {message &&

{message}

} +
+
+ ) +} + +export default function PasswordResetPage() { + return ( + Loading form...
}> + + + ); +} \ No newline at end of file diff --git a/src/app/account/signin/page.tsx b/src/app/account/signin/page.tsx index 176e460..09da680 100644 --- a/src/app/account/signin/page.tsx +++ b/src/app/account/signin/page.tsx @@ -9,7 +9,6 @@ import { signInAndFetchUser } from '@/app/actions/signin' import { Schibsted_Grotesk } from 'next/font/google' import Link from 'next/link'; -import Button from '@/components/Button'; const font = Schibsted_Grotesk({ subsets: ['latin'] }) export default function SignInPage() { @@ -71,23 +70,23 @@ export default function SignInPage() { {isPending ? 'Signing In...' : 'Submit'} - {/*
- Not signed up? - - Click here. - -
*/}
-
- Not signed up?{' '} +
- Click here. + href="/account/forgot-password" + className={classnames("font-bold underline text-light-bronze hover:text-bluemana z-10", font.className)} + > + Forgot Password? +
+ Not signed up?{' '} + + Register Here + +
) diff --git a/src/app/account/signup/page.tsx b/src/app/account/signup/page.tsx index a5186a9..491f0a6 100644 --- a/src/app/account/signup/page.tsx +++ b/src/app/account/signup/page.tsx @@ -30,7 +30,7 @@ export default function LoginPage() { return (
Britton Benefits @@ -165,7 +165,7 @@ export default function LoginPage() {
+
+ +
+
+
+

Eligibility Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/broker/employer-coverage/page.tsx b/src/app/dashboard/broker/employer-coverage/page.tsx new file mode 100644 index 0000000..a1362cc --- /dev/null +++ b/src/app/dashboard/broker/employer-coverage/page.tsx @@ -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 ( + <> +
+ +
+
+
+

Employer Coverage Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/broker/id-cards/page.tsx b/src/app/dashboard/broker/id-cards/page.tsx new file mode 100644 index 0000000..6f51d45 --- /dev/null +++ b/src/app/dashboard/broker/id-cards/page.tsx @@ -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 ( + <> +
+ +
+
+
+

ID Card Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/broker/index.tsx b/src/app/dashboard/broker/index.tsx deleted file mode 100644 index b711dee..0000000 --- a/src/app/dashboard/broker/index.tsx +++ /dev/null @@ -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 ( -
-
- {/*
-
-
-
-
-
*/} -
-
-
-
-
- {/*
- Dashboard | Items | Go | Here | More | Dashboard | Items | Go | Here | Too -
-
- - -
*/} - {/*
*/} -
-
- Tom McSwain's Dashboard -
-
- - - - - {/* */} -
-
-
-
-
-
- - {/*
- Dashboard Home - Thing 1 - Thing 2 - Thing 3 - Thing 4 - Thing 5 - Thing 6 - Thing 7 -
*/} -
-
- Thing 1 -
-
- 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. -
-
- 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. -
-
- Thing 2 -
-
- 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. -
-
- 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. -
-
- Thing 3 -
-
- 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. -
-
- 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. -
-
-
-
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/src/app/dashboard/broker/page.tsx b/src/app/dashboard/broker/page.tsx new file mode 100644 index 0000000..2d93861 --- /dev/null +++ b/src/app/dashboard/broker/page.tsx @@ -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 ( +
+ +
+
+ + Generate Temporary Member or Employer ID Cards. + + + Search Eligibility and Benefits by Member. + + + Search Coverage by Employer. + +
+
+
+ ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/carrier/claims/page.tsx b/src/app/dashboard/carrier/claims/page.tsx new file mode 100644 index 0000000..bd63b94 --- /dev/null +++ b/src/app/dashboard/carrier/claims/page.tsx @@ -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 ( + <> +
+ +
+
+
+

Claims Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/carrier/eligibility/page.tsx b/src/app/dashboard/carrier/eligibility/page.tsx new file mode 100644 index 0000000..c0eeadb --- /dev/null +++ b/src/app/dashboard/carrier/eligibility/page.tsx @@ -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 ( + <> +
+ +
+
+
+

Eligibility Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/carrier/employer-coverage/page.tsx b/src/app/dashboard/carrier/employer-coverage/page.tsx new file mode 100644 index 0000000..6ebe404 --- /dev/null +++ b/src/app/dashboard/carrier/employer-coverage/page.tsx @@ -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 ( + <> +
+ +
+
+
+

Employer Coverage Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/carrier/id-cards/page.tsx b/src/app/dashboard/carrier/id-cards/page.tsx new file mode 100644 index 0000000..85164a9 --- /dev/null +++ b/src/app/dashboard/carrier/id-cards/page.tsx @@ -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 ( + <> +
+ +
+
+
+

ID Card Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/carrier/page.tsx b/src/app/dashboard/carrier/page.tsx new file mode 100644 index 0000000..212a09f --- /dev/null +++ b/src/app/dashboard/carrier/page.tsx @@ -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 ( +
+ +
+
+ + Generate Temporary Member or Employer ID Cards. + + + Search Claims by Member. + + + Search Eligibility and Benefits by Member. + + + Search Coverage by Employer. + +
+
+
+ ); + // 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
Session invalid or expired.
; +// } +} \ No newline at end of file diff --git a/src/app/dashboard/employer/documents/page.tsx b/src/app/dashboard/employer/documents/page.tsx new file mode 100644 index 0000000..ecc9fe1 --- /dev/null +++ b/src/app/dashboard/employer/documents/page.tsx @@ -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 ( + <> +
+ +
+
+
+

Documents

+
+
+ {avaliableDocuments[1] && + + } + {avaliableDocuments[2] && + + } +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/employer/eligibility/page.tsx b/src/app/dashboard/employer/eligibility/page.tsx new file mode 100644 index 0000000..60455af --- /dev/null +++ b/src/app/dashboard/employer/eligibility/page.tsx @@ -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 ( + <> +
+ +
+
+
+

Eligibility Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/employer/id-cards/page.tsx b/src/app/dashboard/employer/id-cards/page.tsx new file mode 100644 index 0000000..32b71fd --- /dev/null +++ b/src/app/dashboard/employer/id-cards/page.tsx @@ -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 ( + <> +
+ +
+
+
+

ID Card Search

+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/employer/idCards/page.tsx b/src/app/dashboard/employer/idCards/page.tsx deleted file mode 100644 index 8c56c5c..0000000 --- a/src/app/dashboard/employer/idCards/page.tsx +++ /dev/null @@ -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 && -
- -
-
-
-

Claims

-
-
-
-
- Claim # -
-
- Patient Name -
-
- Provider -
-
- Visit Date -
-
- Claim Status -
-
- {(claimsData).map((mc) => ( -
-
- {mc.claim_number} -
-
- {mc.patient_name} -
-
- {mc.provider_name} -
-
- {mc.visit_date} -
-
- {mc.claim_status} -
-
- ))} -
-
-
-
- } - - ); - } catch (error: any) { - if (error.message === 'NEXT_REDIRECT') throw error; - - if (error.message === 'Unauthenticated') { - redirect('/account/signin'); - } - } -} \ No newline at end of file diff --git a/src/app/dashboard/employer/members-coverage/page.tsx b/src/app/dashboard/employer/members-coverage/page.tsx new file mode 100644 index 0000000..6f1bec8 --- /dev/null +++ b/src/app/dashboard/employer/members-coverage/page.tsx @@ -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 ( + <> +
+ +
+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/employer/page.tsx b/src/app/dashboard/employer/page.tsx index 2bd75d1..dffed7a 100644 --- a/src/app/dashboard/employer/page.tsx +++ b/src/app/dashboard/employer/page.tsx @@ -1,41 +1,14 @@ import classnames from 'classnames'; import DashboardHeader from "@/components/DashboardHeader"; -import FindProviderWidget from '@/components/FindProvider'; -import InsuranceCardWidget from '@/components/InsuranceCard'; -import FindRxInformationWidget from '@/components/FindRxInformation'; -import RecentClaimsWidget from '@/components/RecentClaimsWidget'; -import BenefitsAAGWidget from '@/components/BenefitsAAGWidget'; import { cookies } from "next/headers"; import { JWTPayload, jwtVerify } from "jose"; -import { redirect } from 'next/navigation'; -import IdCardViewer from '@/components/IdCardViewer'; +import { notFound, redirect } from 'next/navigation'; +import ArrowText from '@/components/ArrowText'; // Ensure your secret key matches exactly what you used to sign the token const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY); -interface PlanBenefitType { - id: number; - title: string; - sequence: number; - benefit: string; - benefit_desc: string; -} - -interface RecentClaimType { - claim_number: number; - visit_date: string; - claim_status: string; - patient_name: string; - provider_name: string; -} - -interface DashboardDataType { - network_provider: string; - plan_benefits: PlanBenefitType[]; - recent_claims: RecentClaimType[]; -} - interface UserJwtPayload extends JWTPayload { user: UserData; } @@ -46,17 +19,9 @@ interface DashboardDataType { name: string; role: string; roleId: string; - keychain: UserKeychain; } - interface UserKeychain { - family_id: string; - mb_member_key: number; - pb_entity_key: number; - pl_plan_key: number; - }; - -export default async function Member() { +export default async function Employer() { const cookieStore = await cookies(); const sessionCookie = cookieStore.get("session"); // Replace with your cookie name @@ -74,66 +39,45 @@ export default async function Member() { // 3. Cast the payload to your custom interface const data = payload as UserJwtPayload; - // const userDataTest = { - // pb_entity_key: 377527, - // pl_plan_key: '61', - // name: "John McMember" + // const userData = { + // plPlanKey: '61', + // name: "Bryan Pest Control", + // role: 'employer', + // roleId: '61' // } const userData = { - pb_entity_key: data.user.keychain.pb_entity_key, - pl_plan_key: data.user.keychain.pl_plan_key, - name: data.user.name + name: data.user.name, + role: data.user.role, + roleId: data.user.roleId } - const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.pb_entity_key}/initialize_dashboard`, { - method: 'GET', - credentials: 'include', - headers: { - 'Content-Type': 'application/json' , - 'Authorization': `Bearer ${data.token}` - }, - }); + if (!data || userData.role !== 'employer') { + notFound(); + } - if (!response.ok) throw new Error("Unauthenticated"); - - const dashboardData: DashboardDataType = await response.json() return ( - <> - {dashboardData &&
- -
-
-
-
- {/* */} - - -
- - -
- -
-
-
- } - + +
+
+ + Generate Temporary Member or Employer ID Cards. + + + Search Eligibility and Benefits by Member. + + + View Members Coverage. + +
+
+
); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { if (error.message === 'NEXT_REDIRECT') throw error; diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index c2a45c4..abf6d6a 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,6 +1,3 @@ -import { sideNavItems } from '@/lib/dashboard'; -import { headers } from "next/headers"; -import Script from 'next/script'; import { cookies } from "next/headers"; import { JWTPayload, jwtVerify } from "jose"; @@ -8,6 +5,8 @@ import AccountDropdown from '../../components/AccountDropdown'; import SideNav from "@/components/SideNav"; import Image from 'next/image'; import classnames from 'classnames'; +import Link from 'next/link'; +import InlineLink from '@/components/InlineLink'; const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY); @@ -29,7 +28,7 @@ export default async function DashboardLayout({ children }: { children: React.Re const sessionCookie = cookieStore.get("session"); // Replace with your cookie name if (!sessionCookie) { - return
No active session. Please log in.
; + return
No active session. Please Sign In.
; } try { @@ -44,16 +43,11 @@ export default async function DashboardLayout({ children }: { children: React.Re const userRole = data.user.role - // const headersList = await headers(); - // const pathname = headersList.get("x-current-path"); - // const dashboardType = pathname?.split('/').pop() || ""; - const pageNavItems = sideNavItems[userRole]; - return (
-
+
@@ -65,27 +59,27 @@ export default async function DashboardLayout({ children }: { children: React.Re
-
+
-
- Britton Logo +
+ + Britton Logo +
-
- +
+
-
+
{children}
diff --git a/src/app/dashboard/login/page.tsx b/src/app/dashboard/login/page.tsx deleted file mode 100644 index 6e20031..0000000 --- a/src/app/dashboard/login/page.tsx +++ /dev/null @@ -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 ( -
-
- Britton Benefits - {/*

Britton

-

Benefits

*/} -
-
-
- Sign In -
- -
- {/* Error Message Display */} - {state?.error && ( -
- {state.error} -
- )} - -
-
- - -
-
- -
-
- - -
-
- - -
-
-
- ) -} diff --git a/src/app/dashboard/member/claims/page.tsx b/src/app/dashboard/member/claims/page.tsx index 002c5e5..c07a24d 100644 --- a/src/app/dashboard/member/claims/page.tsx +++ b/src/app/dashboard/member/claims/page.tsx @@ -5,7 +5,6 @@ import { JWTPayload, jwtVerify } from "jose"; import DashboardHeader from "@/components/DashboardHeader"; import { Schibsted_Grotesk } from 'next/font/google' import { redirect } from 'next/navigation'; -import { isRedirectError } from 'next/dist/client/components/redirect-error'; const font = Schibsted_Grotesk({ subsets: ['latin'] }) const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY); @@ -28,16 +27,8 @@ interface ClaimType { name: string; role: string; roleId: string; - keychain: UserKeychain; } - interface UserKeychain { - family_id: string; - mb_member_key: number; - pb_entity_key: number; - pl_plan_key: number; - }; - export default async function MemberClaims() { const cookieStore = await cookies(); const sessionCookie = cookieStore.get("session"); @@ -63,12 +54,12 @@ export default async function MemberClaims() { // } const userData = { - pb_entity_key: data.user.keychain.pb_entity_key, - pl_plan_key: data.user.keychain.pl_plan_key, - name: data.user.name + name: data.user.name, + role: data.user.role, + roleId: data.user.roleId } - const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.pb_entity_key}/claims`, { + const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.roleId}/claims`, { method: 'GET', credentials: 'include', headers: { @@ -138,6 +129,7 @@ export default async function MemberClaims() { } ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { if (error.message === 'NEXT_REDIRECT') throw error; diff --git a/src/app/dashboard/member/documents/page.tsx b/src/app/dashboard/member/documents/page.tsx new file mode 100644 index 0000000..4eee838 --- /dev/null +++ b/src/app/dashboard/member/documents/page.tsx @@ -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 ( + <> +
+ +
+
+
+

Documents

+
+
+ {avaliableDocuments[1] && + + } +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/member/indexold.tsx b/src/app/dashboard/member/indexold.tsx deleted file mode 100644 index 240f819..0000000 --- a/src/app/dashboard/member/indexold.tsx +++ /dev/null @@ -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 ( -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Britton Logo -
-
-
- -
-
-
-
- -
-
-
- Robert La'Blah -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

Paid

-
- -

Deductable Progress

-
-

Remaining

-
-
-
-
My Card
- -
- -

|

- -
-
-
-
-
-
-

Find A Provider By...

-
- -
- {/*

*/} - - - -
-
- -
-
-
-
-
-
-

Recent Claims

-
-
-
-
- -
-
- LA'BLAH, ROBERT -
-
- HOUSE, GREGORY -
-
- 07/21/2025 -
-
-
-
- -
-
- LA'BLAH, ROBERT -
-
- QUINN, MICHAELA -
-
- 07/02/2025 -
-
-
-
- -
-
- LA'BLAH, JENNY -
-
- HOWSER, DOGGIE -
-
- 05/15/2025 -
-
-
-
- -
-
- LA'BLAH, ROBERT -
-
- HOUSE, GREGORY -
-
- 04/29/2025 -
-
-
-
- -
-
-
-
-
-

Benefits At A Glance

-

CLASSIC 5K 100/5000

-
-
-

Physician Visit

-

Specialist Visit

-

Urgent Care

-

INN-Ind Ded

-

INN-Family Ded

-

OON-Ind Ded

-

OON-Family Ded

-

Co-Insurance

-

INN-Ind OOP

-

INN-Family OOP

-

OON-Ind OOP

-

OON-Family OOP

-

Emergency Room

-

Preventive Care

-
-
-

$25.00

-

$50.00

-

$50.00

-

$5,000

-

$10,000

-

$10,000

-

$20,000

-

100%/0%

-

$5,000

-

$10,000

-

$10,000

-

$20,000

-

Ded & Coin

-

100%

-
-
- -
-
-
-
-
-
-
-
- ) -} \ No newline at end of file diff --git a/src/app/dashboard/member/page.tsx b/src/app/dashboard/member/page.tsx index 2bd75d1..bbe4a2f 100644 --- a/src/app/dashboard/member/page.tsx +++ b/src/app/dashboard/member/page.tsx @@ -2,19 +2,18 @@ import classnames from 'classnames'; import DashboardHeader from "@/components/DashboardHeader"; import FindProviderWidget from '@/components/FindProvider'; -import InsuranceCardWidget from '@/components/InsuranceCard'; import FindRxInformationWidget from '@/components/FindRxInformation'; import RecentClaimsWidget from '@/components/RecentClaimsWidget'; import BenefitsAAGWidget from '@/components/BenefitsAAGWidget'; import { cookies } from "next/headers"; import { JWTPayload, jwtVerify } from "jose"; -import { redirect } from 'next/navigation'; +import { notFound, redirect } from 'next/navigation'; import IdCardViewer from '@/components/IdCardViewer'; // Ensure your secret key matches exactly what you used to sign the token const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY); -interface PlanBenefitType { +interface PlanDetailtType { id: number; title: string; sequence: number; @@ -32,10 +31,16 @@ interface RecentClaimType { interface DashboardDataType { network_provider: string; - plan_benefits: PlanBenefitType[]; + plan_benefits: PlanBenefitType; recent_claims: RecentClaimType[]; } +interface PlanBenefitType { + plan_details: PlanDetailtType[]; + effective_date: string; + dependents: string[]; +} + interface UserJwtPayload extends JWTPayload { user: UserData; } @@ -46,16 +51,8 @@ interface DashboardDataType { name: string; role: string; roleId: string; - keychain: UserKeychain; } - interface UserKeychain { - family_id: string; - mb_member_key: number; - pb_entity_key: number; - pl_plan_key: number; - }; - export default async function Member() { const cookieStore = await cookies(); const sessionCookie = cookieStore.get("session"); // Replace with your cookie name @@ -65,15 +62,15 @@ export default async function Member() { } try { - // 2. Extract the raw string token from the cookie object - const token = sessionCookie.value; - - // 3. Verify and decode the JWT using jose - const { payload } = await jwtVerify(token, SECRET_KEY) - - // 3. Cast the payload to your custom interface - const data = payload as UserJwtPayload; - + // 2. Extract the raw string token from the cookie object + const token = sessionCookie.value; + + // 3. Verify and decode the JWT using jose + const { payload } = await jwtVerify(token, SECRET_KEY) + + // 3. Cast the payload to your custom interface + const data = payload as UserJwtPayload; + // const userDataTest = { // pb_entity_key: 377527, // pl_plan_key: '61', @@ -81,12 +78,16 @@ export default async function Member() { // } const userData = { - pb_entity_key: data.user.keychain.pb_entity_key, - pl_plan_key: data.user.keychain.pl_plan_key, + role: data.user.role, + roleId: data.user.roleId, name: data.user.name } - const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.pb_entity_key}/initialize_dashboard`, { + if (!data || userData.role !== 'member') { + notFound(); + } + + const response = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/api/v1/members/${userData.roleId}/initialize_dashboard`, { method: 'GET', credentials: 'include', headers: { @@ -112,7 +113,7 @@ export default async function Member() {
{/* */}
@@ -134,6 +137,7 @@ export default async function Member() { } ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { if (error.message === 'NEXT_REDIRECT') throw error; @@ -141,9 +145,4 @@ export default async function Member() { redirect('/account/signin'); } } -// } catch (error) { -// // Fails if token is expired, tampered with, or invalid -// console.error("JWT verification failed:", error); -// return
Session invalid or expired.
; -// } } \ No newline at end of file diff --git a/src/app/dashboard/provider/claim-search/page.tsx b/src/app/dashboard/provider/claim-search/page.tsx new file mode 100644 index 0000000..f478285 --- /dev/null +++ b/src/app/dashboard/provider/claim-search/page.tsx @@ -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 ( + <> +
+ +
+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/provider/eligibility-lookup/page.tsx b/src/app/dashboard/provider/eligibility-lookup/page.tsx new file mode 100644 index 0000000..f904bff --- /dev/null +++ b/src/app/dashboard/provider/eligibility-lookup/page.tsx @@ -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 ( + <> +
+ +
+
+
+ +
+
+
+
+ + ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/dashboard/provider/page.tsx b/src/app/dashboard/provider/page.tsx new file mode 100644 index 0000000..f9024cc --- /dev/null +++ b/src/app/dashboard/provider/page.tsx @@ -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 ( +
+ +
+
+ + Search Claims by Member Number. + + + Search Eligibility and Benefits by Member Number. + +
+
+
+ ); + // 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'); + } + } +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fe52d38..cdf4adc 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,5 @@ import '../styles/globals.css'; -import type { Metadata } from 'next'; import { Fraunces } from 'next/font/google' -import Navbar from '../components/Navbar'; -import Footer from '../components/Footer'; import classNames from 'classnames'; const font = Fraunces({ subsets: ['latin'] }) diff --git a/src/app/page.tsx b/src/app/page.tsx index a081cca..bdbe2c4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,5 @@ import InfoCard from '../components/InfoCard'; import Tagline from "../components/Tagline"; -import InlineLink from "../components/InlineLink"; import Navbar from '@/components/Navbar'; import Footer from '@/components/Footer'; @@ -25,7 +24,7 @@ export default async function Home() { learnMoreUrl="/members" imageUrl="/images/member-image.jpg" > - What we do for members + At Britton Benefit Services, LLC we'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.
@@ -35,7 +34,7 @@ export default async function Home() { imageUrl='/images/employer-image.jpg' invert > - What we do for employers + At Britton Benefit Services, LLC we are leading the third-party benefits industry by working hard to make employee benefits more affordable for you and your employees. As a third-party administrator, we negotiate with leading PPOs and other organizations to help you find the best program for your employees without sacrificing the bottom line. This creates a major cost savings that you can pass on to your employees.
@@ -44,7 +43,7 @@ export default async function Home() { learnMoreUrl="/providers" imageUrl="/images/provider-image.png" > - What we do for providers + At Britton Benefit Services, LLC it’s our goal to provide you with the prompt response to your inquiry and easy access to the information you need to care for your patients. In addition to 24/7 online support and access to HIPAA compliant patient information, you can find the information you need to verify eligibility, confirm benefits and check claim statuses. Simply select the navigation above to access the forms and information you need.
@@ -54,7 +53,7 @@ export default async function Home() { imageUrl="/images/broker-image.jpg" invert > - What we do for brokers + At Britton Benefit Services, LLC we have a proven ourselves as leaders in Third-Party Benefits Administration through our performance in finding employers the right self-funded plans that work for them. Whether you need to review a plan document, manage information reports, review provider directories, or you are looking for access to marketing materials, you’ve come to the right place.