Compare commits

..

10 Commits

Author SHA1 Message Date
Jason Jordan 8b3bb209b3 Its been a while 2026-07-23 10:38:20 -04:00
Jason Jordan 77f294c972 Code dump 2025-11-24 08:28:31 -05:00
Jason Jordan 181be7e16c too much time on responsive, on to hooking up cms 2025-08-04 14:16:42 -04:00
Jason Jordan 0e44bd53e6 Responsive done for computer screens 2025-07-30 16:31:41 -04:00
Jason Jordan 3bdc768714 layouts and components, oh my! 2025-07-29 17:41:13 -04:00
Jason Jordan d1ebd100b5 some responsive changes 2025-07-25 10:56:20 -04:00
Jason Jordan 234a02f783 minor clean up for demo 2025-07-24 13:46:38 -04:00
Jason Jordan 32f47d9c62 demo cleaning 2025-07-23 13:57:45 -04:00
Jason Jordan 2bd7ce94fe homepage/landing pages 'done', dashboard started 2025-07-16 17:27:22 -04:00
Jason Jordan f16a4fd49a minor style fixes 2025-07-10 13:29:33 -04:00
84 changed files with 8034 additions and 1377 deletions
+5
View File
@@ -7,9 +7,14 @@ WORKDIR /usr/src/app
# Copy package.json and package-lock.json (or yarn.lock/pnpm-lock.yaml)
COPY package*.json ./
ARG NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
ENV NEXT_SERVER_ACTIONS_ENCRYPTION_KEY=$NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
# Install dependencies
RUN npm install
# RUN build
# Copy the rest of your application code
COPY . .
+47
View File
@@ -0,0 +1,47 @@
<div
class="min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8"
>
<div class="max-w-md w-full space-y-8">
<div>
<div class="flex flex-col text-8xl font-bold text-[#2A4B6F] space-y-30">
<div>
<span class="[text-shadow:0_0_20px_#fff,0_0_50px_#0090D9,0_0_80px_#0ff] text-[#04153E]">B</span>acl<span class="relative inline-block">i<span class="absolute top-0 right-0 h-5 w-5 rounded-full bg-[#CD7F32] translate-y-[12px] -translate-x-[4px]"></span></span>ght
</div>
</div>
<h2 class="mt-10 text-center text-2xl font-extrabold text-[#CD7F32]">
Britton Ancillary Center
</h2>
</div>
<%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { data: { turbo: false} }) do |f| %>
<div class="mt-8 space-y-6">
<input type="hidden" name="remember" value="true" />
<div class="rounded-md shadow-sm -space-y-px">
<div>
<%= f.label :email, class: "sr-only", for: "email-address" %>
<%= f.email_field :email, id: "email-address", autofocus: true, autocomplete: "email", placeholder: "Email address", class: "appearance-none rounded-none relative block w-full px-3 py-2 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" %>
</div>
<div>
<%= f.label :password, for: "password", class: "sr-only" %>
<%= f.password_field :password, id: "password", autocomplete: "current-password", placeholder: "Password", class: "appearance-none rounded-none relative block w-full px-3 py-2 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" %>
</div>
</div>
<% if devise_mapping.rememberable? %>
<div class="flex items-center justify-between">
<div class="field flex items-center">
<%= f.check_box :remember_me, class: "h-4 w-4 text-[#CD7F32] focus:ring-indigo-500 border-[#CD7F32] rounded" %>
<%= f.label :remember_me, class: "ml-2 block font-semibold text-md text-[#CD7F32]" %>
</div>
<div class="text-md">
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "font-semibold text-atmosphere hover:text-bluemana" %>
<% end %>
</div>
</div>
<% end %>
<div>
<%= f.submit "Sign in", { class: "relative w-full flex justify-center py-2 px-4 border-4 font-bold rounded-lg text-platinum" } %>
</div>
</div>
<% end %>
</div>
</div>
+10 -1
View File
@@ -5,6 +5,15 @@ services:
dockerfile: Dockerfile.dev
ports:
- "3001:3001"
environment:
- BE_API_SERVER_HOST=web_api
- BE_API_SERVER_PORT=3005
volumes:
- .:/usr/src/app # Mount the current directory into the container for live reloading
- /usr/src/app/node_modules # Isolate node_modules to prevent host-container conflicts
- /usr/src/app/node_modules # Isolate node_modules to prevent host-container conflicts
networks:
- britton_network
networks:
britton_network:
external: true
+13 -1
View File
@@ -1,7 +1,19 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
// async rewrites() {
// return [
// {
// source: '/api-backend/:path*',
// destination: `http://web_api:3005`, // The external api URL
// },
// ];
// },
outputFileTracingIncludes: {
'/api/**/*': ['./node_modules/jose/**/*'],
},
allowedDevOrigins: [`${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}`]
};
export default nextConfig;
+3177 -940
View File
File diff suppressed because it is too large Load Diff
+17 -7
View File
@@ -9,22 +9,32 @@
"lint": "next lint"
},
"dependencies": {
"@apollo/client": "^3.13.9",
"@apollo/experimental-nextjs-app-support": "^0.12.3",
"graphql": "^16.11.0",
"jose": "^6.2.3",
"next": "15.3.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"next": "15.3.4"
"react-idle-timer": "^5.7.3",
"react-markdown": "^10.1.0",
"react-places-autocomplete": "^7.3.0"
},
"devDependencies": {
"typescript": "^5",
"@eslint/eslintrc": "^3",
"@headlessui/react": "2.2.4",
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4",
"@types/react-places-autocomplete": "^7.2.14",
"classnames": "^2.5",
"eslint": "^9",
"eslint-config-next": "15.3.4",
"@eslint/eslintrc": "^3",
"react-icons": "^5.5",
"classnames": "^2.5",
"tailwind-rounded-out": "2.0.0"
"recharts": "3.1.0",
"tailwind-rounded-out": "2.0.0",
"tailwindcss": "^4",
"typescript": "^5"
}
}
+76
View File
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 184.69333 172.64"
height="172.64"
width="184.69333"
xml:space="preserve"
id="svg2"
version="1.1"
sodipodi:docname="Cigna_deepcove.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><sodipodi:namedview
id="namedview27"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="3.6376274"
inkscape:cx="48.520637"
inkscape:cy="86.32"
inkscape:window-width="1512"
inkscape:window-height="873"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g12" />
<metadata
id="metadata8">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>
image/svg+xml
</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage">
</dc:type>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6">
</defs>
<g
transform="matrix(1.3333333,0,0,-1.3333333,0,172.64)"
id="g10">
<g
transform="scale(0.1)"
id="g12">
<path
id="path14"
style="fill:#04153e;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 973.246,741.891 c 0,-8.696 -11.594,-20.286 -23.183,-37.672 -46.372,-57.961 -150.7,-159.391 -150.7,-414.418 0,-130.41 20.289,-185.469 20.289,-246.3322 C 819.652,14.4883 813.855,0 799.363,0 c -31.875,0 -89.836,139.109 -89.836,304.293 0,350.656 220.246,446.293 255.024,446.293 5.797,0 8.695,-2.898 8.695,-8.695 m -553.519,8.695 c 34.777,0 252.125,-95.637 252.125,-443.395 C 671.852,142 616.793,2.89844 582.016,2.89844 c -14.493,0 -20.286,14.49216 -20.286,43.47266 0,60.8589 20.286,115.9179 20.286,246.3279 0,252.129 -104.328,356.457 -150.696,414.414 -14.492,17.391 -23.183,28.981 -23.183,37.676 2.894,2.899 5.793,5.797 11.59,5.797">
</path>
<path
id="path16"
style="fill:#04153e;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 611.109,681.008 c 0,-46.367 34.774,-81.145 81.145,-81.145 43.469,0 78.242,34.778 78.242,81.145 0,46.367 -34.773,81.144 -81.141,81.144 -40.574,0 -78.246,-34.777 -78.246,-81.144">
</path>
<path
id="path18"
style="fill:#04153e;fill-opacity:1;fill-rule:nonzero;stroke:none"
d="m 692.621,1060.09 c -31.875,14.49 -63.754,43.47 -63.754,101.43 0,55.07 26.082,110.12 63.754,133.31 37.676,-23.19 63.758,-78.24 63.758,-133.31 0,-57.96 -31.879,-84.04 -63.758,-101.43 M 1376.55,164.609 c -113.02,69.551 -286.9,142 -539.027,165.188 0,-2.899 -2.898,-20.289 -2.898,-40.578 0,-8.688 0,-20.278 2.898,-20.278 246.327,-14.492 420.207,-66.66 539.027,-110.132 2.9,0 5.8,-2.887 5.8,-2.887 0,0 2.9,0 2.9,2.887 -2.9,2.902 -5.8,2.902 -8.7,5.8 M 823.031,1100.66 c -20.285,17.39 -34.773,43.47 -23.183,84.05 11.593,37.67 40.574,69.55 72.453,78.24 20.285,-26.08 28.976,-66.65 17.387,-107.22 -14.493,-37.68 -43.469,-49.27 -66.657,-55.07 M 1005.61,947.07 c -5.797,23.184 -5.797,52.164 26.08,81.14 28.98,26.09 69.55,34.78 98.53,26.09 5.8,-31.88 -11.59,-69.558 -40.57,-92.741 -31.88,-28.977 -60.86,-23.184 -84.04,-14.489 m 5.79,-231.84 c 8.7,23.184 23.19,46.368 63.76,52.165 37.67,2.898 75.35,-8.696 95.63,-34.778 -14.49,-28.98 -46.37,-49.265 -84.04,-52.164 -40.57,-5.797 -60.86,14.488 -75.35,34.777 m -66.65,-84.042 c 14.488,17.386 34.773,34.777 69.55,26.082 31.88,-5.797 60.86,-28.981 72.45,-55.063 -17.39,-20.285 -52.16,-28.98 -86.937,-23.184 -34.778,8.696 -49.266,31.879 -55.063,52.165 m -199.965,220.25 c -23.183,20.285 -43.469,52.164 -28.976,101.429 14.488,46.367 49.265,84.043 86.937,92.733 26.082,-28.98 34.777,-81.143 20.285,-127.51 -14.488,-46.367 -49.265,-63.754 -78.246,-66.652 m 86.942,-52.165 c -8.696,26.082 -5.797,60.856 28.98,89.836 31.879,28.981 78.246,40.575 113.023,28.981 5.793,-34.777 -14.492,-78.246 -46.371,-104.328 -37.671,-28.981 -69.55,-26.082 -95.632,-14.489 m 57.961,188.368 c -14.493,26.079 -26.083,63.759 2.898,107.229 26.082,40.57 72.449,69.55 113.024,66.65 17.38,-34.77 11.59,-89.84 -17.391,-130.41 -28.981,-43.469 -66.653,-49.262 -98.531,-43.469 M 1008.5,825.355 c 0,28.981 11.6,63.754 55.07,84.04 43.47,17.39 89.83,14.492 121.71,-5.793 -5.79,-34.778 -34.77,-72.454 -78.24,-92.739 -43.47,-20.285 -75.35,-5.797 -98.54,14.492 M 0,158.809 v 0 c 2.89844,-2.887 5.79688,0 8.69531,0 118.81669,43.472 292.69569,95.64 539.02769,110.132 0,2.899 2.898,14.489 2.898,20.278 0,17.39 -2.898,37.679 -2.898,40.578 C 295.598,306.609 124.613,234.16 8.69531,164.609 2.89844,161.711 0,161.711 0,158.809 m 495.559,999.821 c -11.594,37.67 -5.797,81.14 17.386,107.22 31.879,-8.69 60.86,-40.57 72.45,-78.24 11.593,-40.58 -2.895,-66.66 -23.184,-84.05 -26.082,2.9 -55.063,14.49 -66.652,55.07 M 292.699,958.664 c -28.98,26.078 -46.371,63.756 -40.574,92.736 28.98,8.69 69.555,0 98.531,-26.08 31.879,-26.086 31.879,-55.066 26.082,-81.148 -23.183,-5.797 -52.164,-11.59 -84.039,14.492 m 5.793,-278.211 c -37.672,2.899 -72.449,26.082 -86.937,52.164 20.285,23.184 57.961,37.676 95.633,34.778 40.574,-2.899 55.062,-28.981 63.757,-52.165 -11.593,-20.289 -34.777,-40.574 -72.453,-34.777 m 86.942,-98.531 c -31.879,-5.797 -66.653,2.898 -86.942,23.183 8.696,26.083 37.676,46.368 72.453,52.165 34.774,8.695 55.063,-8.696 66.653,-26.082 -5.797,-20.286 -17.387,-43.469 -52.164,-49.266 M 559.313,918.09 c -14.489,46.367 -5.793,98.53 20.289,127.51 37.671,-11.59 72.449,-49.264 86.941,-92.733 14.488,-49.265 -5.797,-81.144 -26.082,-101.429 -31.879,2.898 -66.656,20.285 -81.148,66.652 M 457.883,813.762 c -31.875,28.98 -52.164,69.551 -46.367,104.328 34.777,11.594 78.246,0 113.023,-28.981 34.774,-28.98 34.774,-63.754 28.981,-89.836 -28.981,-11.593 -60.86,-14.492 -95.637,14.489 m -60.856,217.348 c -26.082,40.57 -31.879,92.74 -17.39,130.41 40.574,0 84.043,-26.08 113.023,-66.65 28.981,-43.47 17.387,-78.25 0,-107.229 -31.879,-5.793 -69.551,0 -95.633,43.469 M 275.309,810.863 c -40.571,20.285 -72.45,57.961 -78.246,92.739 31.878,20.285 81.144,23.183 121.718,2.898 43.469,-20.289 55.063,-52.164 55.063,-84.043 -23.188,-17.391 -55.063,-31.879 -98.535,-11.594">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 474 159"
width="474"
height="159"
version="1.1"
id="svg4971"
sodipodi:docname="Fairosrx.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4975" />
<sodipodi:namedview
id="namedview4973"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="1.4842767"
inkscape:cx="238.16314"
inkscape:cy="79.500002"
inkscape:window-width="1512"
inkscape:window-height="873"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4971" />
<path
d="M444.4 15.25C441.68 23.37 431.19 17.11 426.32 15.4C413.29 10.83 390.72 13.95 390.41 31.25C390.21 42.22 399.98 44.81 408.65 47.16C425.62 51.77 450.1 53.21 450.22 76.25C450.24 79.45 449.92 82.58 448.78 85.58C441.34 105.13 410.99 104.68 394.86 98.85C388.59 96.59 373.77 88.72 381.76 80.25C384.76 80.59 390.94 87.06 394.49 88.73C404.72 93.56 416.25 94.72 427.27 92.09C432.87 90.76 439.09 87.04 439.95 80.75C441.63 68.4 434.34 64.02 423.92 60.78C410.29 56.54 383.56 55.46 381.03 37.25C377.34 10.64 402.77 2.26 424.43 5.76C430.08 6.68 440.84 10.49 444.4 15.25ZM64.75 5.56C65.88 12.91 57.87 14.55 52.25 14.55C42.58 14.55 32.92 14.55 23.25 14.55C19.81 14.55 14.24 13.51 11.27 15.25C11.27 26.37 11.27 37.49 11.27 48.61C25.1 48.61 38.92 48.61 52.75 48.61C53.75 54.08 48.57 57.09 43.75 57.51C36.2 58.16 28.33 57.55 20.75 57.55C17.94 57.55 13.62 56.72 11.26 58.25C11.26 72.69 11.26 87.13 11.26 101.57C8.1 101.57 4.95 101.57 1.79 101.57C1.79 69.57 1.79 37.56 1.79 5.56C22.78 5.56 43.76 5.56 64.75 5.56ZM106.25 5.63C119.66 37.6 133.08 69.57 146.49 101.54C143.24 101.54 140 101.54 136.75 101.54C125.42 74.3 114.08 47.06 102.75 19.81C96.79 33.79 90.83 47.77 84.88 61.75C91.72 63.14 99.76 61.98 106.75 61.97C109.48 61.97 113.38 61.27 115.75 62.59C115.57 67.59 111.49 70.64 106.75 71.02C98.53 71.68 89.42 69.49 81.38 71.25C77 81.35 72.63 91.46 68.25 101.56C65.07 101.56 61.89 101.56 58.71 101.56C72.05 69.58 85.4 37.6 98.75 5.63C101.25 5.63 103.75 5.63 106.25 5.63ZM167.05 5.86C167.05 37.76 167.05 69.66 167.05 101.55C163.93 101.55 160.8 101.55 157.68 101.55C155.91 93.75 157.27 84.27 157.27 76.25C157.27 59.58 157.27 42.92 157.27 26.25C157.27 18.99 155.56 4.43 167.05 5.86ZM236.54 65.25C243.95 76.65 251.35 88.05 258.75 99.44C262.92 93.09 267.08 86.75 271.25 80.4C273.13 83.01 275.01 85.63 276.89 88.25C272.66 95.08 268.43 101.92 264.21 108.75C273.54 124.08 282.88 139.42 292.22 154.75C285.64 158.84 280.93 153.78 277.65 148.6C273.2 141.56 268.71 134.52 264.32 127.43C262.53 124.53 260.84 120.55 258.25 118.39C252.08 127.99 245.92 137.59 239.75 147.18C236.25 147.18 232.75 147.18 229.25 147.18C237.04 134.37 244.84 121.56 252.63 108.75C243.69 94.92 234.76 81.08 225.82 67.25C218.58 66.86 203.83 69.73 204.25 58.14C207.43 56.75 212.28 57.61 215.75 57.61C228.29 57.61 241.7 58.43 246 43.76C246.79 41.05 247.37 38.38 247.25 35.51C246.88 26.95 241.78 17.87 233.12 15.21C227.81 13.58 221.23 14.55 215.75 14.55C209.93 14.55 203.85 14.06 198.08 14.75C198.08 43.69 198.08 72.63 198.08 101.56C194.82 101.56 191.56 101.56 188.3 101.56C188.3 69.66 188.3 37.76 188.3 5.86C197.09 4.21 207.3 5.55 216.25 5.55C223.37 5.55 231.27 4.61 238.02 7.26C256.29 14.45 261.98 38.12 251.68 53.95C249.29 57.62 245.59 60.98 241.67 62.92C239.97 63.76 238.07 64.1 236.54 65.25ZM467.75 101.23C462.71 93.59 457.97 94.1 453.25 101.61C451.72 92.91 469.15 92.93 467.75 101.23ZM457.75 99.12C460.39 98.54 463.26 98.28 464.15 101.25C463.9 102.29 462.85 103.26 462.87 104.25C462.89 105.35 463.8 105.84 463.75 106.97C461.63 106.35 461.64 104.18 459.25 104.14C458.92 105.05 458.58 105.95 458.25 106.85C456.94 105.41 457.43 101.06 457.75 99.12ZM462.77 101.25C461.85 99.77 460.66 99.94 459.07 100.25C459.07 101.09 459.07 101.93 459.07 102.77C460.92 102.91 461.96 103.04 462.77 101.25ZM468.25 101.29C469.96 104.83 466.62 109.62 463.13 110.8C457.15 112.8 452.46 107.74 452.51 102.25C454.09 104.16 454.1 108.24 457.44 109.78C463.97 112.78 467.37 106.36 468.25 101.29Z"
fill="#3469a5"
fill-rule="evenodd"
stroke="#3469a5"
stroke-width="0.25"
stroke-linejoin="round"
id="path4965"
style="fill:#0047ab;fill-opacity:1" />
<path
d="M337.99 5.75C334.75 6.31 327.37 4.96 323.25 5.1C314.9 5.38 306.42 8.73 299.5 13.27C271.82 31.42 270.21 73.63 296.37 93.86C303.08 99.05 311.75 103.27 320.25 104C325.22 104.42 330.6 103.37 335.44 104.25C328.2 108.53 312.04 105.99 304.44 103.31C268.94 90.76 258.61 45.98 284.33 18.59C294.3 7.97 308.35 2.73 322.75 2.84C326.45 2.87 335.35 3.45 337.99 5.75ZM359.48 27.87C359.28 28 359.08 28.12 358.89 28.25C359.02 28.92 359.16 29.58 359.3 30.25C358.78 30.28 358.27 30.31 357.75 30.34C357.45 30.65 357.15 30.95 356.84 31.25C356.83 31.68 356.83 32.1 356.82 32.53C354.88 30.66 353.41 28.22 351.5 26.25C346.08 20.65 338.54 16.88 331 15.26C326.74 14.34 319.72 16.05 316.25 14.68C328.38 8.03 351.75 16.98 359.48 27.87Z"
fill="#c45d3e"
fill-rule="evenodd"
stroke="#c45d3e"
stroke-width="0.25"
stroke-linejoin="round"
id="path4967"
style="fill:#cd7f32;fill-opacity:1" />
<path
d="M359.48 27.87C363.36 31.37 365.79 37.08 367.3 41.95C373.66 62.5 363.46 85.12 343.43 93.23C337.09 95.79 321.06 98.57 315.48 94.25C321.15 93.66 326.66 95.28 332.48 93.72C351.38 88.65 364.29 71.46 363.07 51.75C362.79 47.17 361.62 42.61 359.77 38.43C358.89 36.44 357.44 34.57 356.82 32.53C356.83 32.1 356.83 31.68 356.84 31.25C357.15 30.95 357.45 30.65 357.75 30.34C358.27 30.31 358.78 30.28 359.3 30.25C359.16 29.58 359.02 28.92 358.89 28.25C359.08 28.12 359.28 28 359.48 27.87Z"
fill="#d68551"
fill-rule="evenodd"
stroke="#d68551"
stroke-width="0.25"
stroke-linejoin="round"
id="path4969"
style="fill:#cd7f32;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 474 159"
width="474"
height="159"
version="1.1"
id="svg4971"
sodipodi:docname="fairosrx_inverted.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4975" />
<sodipodi:namedview
id="namedview4973"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="1.4842767"
inkscape:cx="237.48941"
inkscape:cy="79.5"
inkscape:window-width="1512"
inkscape:window-height="873"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg4971" />
<path
d="M444.4 15.25C441.68 23.37 431.19 17.11 426.32 15.4C413.29 10.83 390.72 13.95 390.41 31.25C390.21 42.22 399.98 44.81 408.65 47.16C425.62 51.77 450.1 53.21 450.22 76.25C450.24 79.45 449.92 82.58 448.78 85.58C441.34 105.13 410.99 104.68 394.86 98.85C388.59 96.59 373.77 88.72 381.76 80.25C384.76 80.59 390.94 87.06 394.49 88.73C404.72 93.56 416.25 94.72 427.27 92.09C432.87 90.76 439.09 87.04 439.95 80.75C441.63 68.4 434.34 64.02 423.92 60.78C410.29 56.54 383.56 55.46 381.03 37.25C377.34 10.64 402.77 2.26 424.43 5.76C430.08 6.68 440.84 10.49 444.4 15.25ZM64.75 5.56C65.88 12.91 57.87 14.55 52.25 14.55C42.58 14.55 32.92 14.55 23.25 14.55C19.81 14.55 14.24 13.51 11.27 15.25C11.27 26.37 11.27 37.49 11.27 48.61C25.1 48.61 38.92 48.61 52.75 48.61C53.75 54.08 48.57 57.09 43.75 57.51C36.2 58.16 28.33 57.55 20.75 57.55C17.94 57.55 13.62 56.72 11.26 58.25C11.26 72.69 11.26 87.13 11.26 101.57C8.1 101.57 4.95 101.57 1.79 101.57C1.79 69.57 1.79 37.56 1.79 5.56C22.78 5.56 43.76 5.56 64.75 5.56ZM106.25 5.63C119.66 37.6 133.08 69.57 146.49 101.54C143.24 101.54 140 101.54 136.75 101.54C125.42 74.3 114.08 47.06 102.75 19.81C96.79 33.79 90.83 47.77 84.88 61.75C91.72 63.14 99.76 61.98 106.75 61.97C109.48 61.97 113.38 61.27 115.75 62.59C115.57 67.59 111.49 70.64 106.75 71.02C98.53 71.68 89.42 69.49 81.38 71.25C77 81.35 72.63 91.46 68.25 101.56C65.07 101.56 61.89 101.56 58.71 101.56C72.05 69.58 85.4 37.6 98.75 5.63C101.25 5.63 103.75 5.63 106.25 5.63ZM167.05 5.86C167.05 37.76 167.05 69.66 167.05 101.55C163.93 101.55 160.8 101.55 157.68 101.55C155.91 93.75 157.27 84.27 157.27 76.25C157.27 59.58 157.27 42.92 157.27 26.25C157.27 18.99 155.56 4.43 167.05 5.86ZM236.54 65.25C243.95 76.65 251.35 88.05 258.75 99.44C262.92 93.09 267.08 86.75 271.25 80.4C273.13 83.01 275.01 85.63 276.89 88.25C272.66 95.08 268.43 101.92 264.21 108.75C273.54 124.08 282.88 139.42 292.22 154.75C285.64 158.84 280.93 153.78 277.65 148.6C273.2 141.56 268.71 134.52 264.32 127.43C262.53 124.53 260.84 120.55 258.25 118.39C252.08 127.99 245.92 137.59 239.75 147.18C236.25 147.18 232.75 147.18 229.25 147.18C237.04 134.37 244.84 121.56 252.63 108.75C243.69 94.92 234.76 81.08 225.82 67.25C218.58 66.86 203.83 69.73 204.25 58.14C207.43 56.75 212.28 57.61 215.75 57.61C228.29 57.61 241.7 58.43 246 43.76C246.79 41.05 247.37 38.38 247.25 35.51C246.88 26.95 241.78 17.87 233.12 15.21C227.81 13.58 221.23 14.55 215.75 14.55C209.93 14.55 203.85 14.06 198.08 14.75C198.08 43.69 198.08 72.63 198.08 101.56C194.82 101.56 191.56 101.56 188.3 101.56C188.3 69.66 188.3 37.76 188.3 5.86C197.09 4.21 207.3 5.55 216.25 5.55C223.37 5.55 231.27 4.61 238.02 7.26C256.29 14.45 261.98 38.12 251.68 53.95C249.29 57.62 245.59 60.98 241.67 62.92C239.97 63.76 238.07 64.1 236.54 65.25ZM467.75 101.23C462.71 93.59 457.97 94.1 453.25 101.61C451.72 92.91 469.15 92.93 467.75 101.23ZM457.75 99.12C460.39 98.54 463.26 98.28 464.15 101.25C463.9 102.29 462.85 103.26 462.87 104.25C462.89 105.35 463.8 105.84 463.75 106.97C461.63 106.35 461.64 104.18 459.25 104.14C458.92 105.05 458.58 105.95 458.25 106.85C456.94 105.41 457.43 101.06 457.75 99.12ZM462.77 101.25C461.85 99.77 460.66 99.94 459.07 100.25C459.07 101.09 459.07 101.93 459.07 102.77C460.92 102.91 461.96 103.04 462.77 101.25ZM468.25 101.29C469.96 104.83 466.62 109.62 463.13 110.8C457.15 112.8 452.46 107.74 452.51 102.25C454.09 104.16 454.1 108.24 457.44 109.78C463.97 112.78 467.37 106.36 468.25 101.29Z"
fill="#3469a5"
fill-rule="evenodd"
stroke="#3469a5"
stroke-width="0.25"
stroke-linejoin="round"
id="path4965"
style="fill:#cd7f32;fill-opacity:1" />
<path
d="M337.99 5.75C334.75 6.31 327.37 4.96 323.25 5.1C314.9 5.38 306.42 8.73 299.5 13.27C271.82 31.42 270.21 73.63 296.37 93.86C303.08 99.05 311.75 103.27 320.25 104C325.22 104.42 330.6 103.37 335.44 104.25C328.2 108.53 312.04 105.99 304.44 103.31C268.94 90.76 258.61 45.98 284.33 18.59C294.3 7.97 308.35 2.73 322.75 2.84C326.45 2.87 335.35 3.45 337.99 5.75ZM359.48 27.87C359.28 28 359.08 28.12 358.89 28.25C359.02 28.92 359.16 29.58 359.3 30.25C358.78 30.28 358.27 30.31 357.75 30.34C357.45 30.65 357.15 30.95 356.84 31.25C356.83 31.68 356.83 32.1 356.82 32.53C354.88 30.66 353.41 28.22 351.5 26.25C346.08 20.65 338.54 16.88 331 15.26C326.74 14.34 319.72 16.05 316.25 14.68C328.38 8.03 351.75 16.98 359.48 27.87Z"
fill="#c45d3e"
fill-rule="evenodd"
stroke="#c45d3e"
stroke-width="0.25"
stroke-linejoin="round"
id="path4967"
style="fill:#0047ab;fill-opacity:1" />
<path
d="M359.48 27.87C363.36 31.37 365.79 37.08 367.3 41.95C373.66 62.5 363.46 85.12 343.43 93.23C337.09 95.79 321.06 98.57 315.48 94.25C321.15 93.66 326.66 95.28 332.48 93.72C351.38 88.65 364.29 71.46 363.07 51.75C362.79 47.17 361.62 42.61 359.77 38.43C358.89 36.44 357.44 34.57 356.82 32.53C356.83 32.1 356.83 31.68 356.84 31.25C357.15 30.95 357.45 30.65 357.75 30.34C358.27 30.31 358.78 30.28 359.3 30.25C359.16 29.58 359.02 28.92 358.89 28.25C359.08 28.12 359.28 28 359.48 27.87Z"
fill="#d68551"
fill-rule="evenodd"
stroke="#d68551"
stroke-width="0.25"
stroke-linejoin="round"
id="path4969"
style="fill:#0047ab;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

+119
View File
@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="Layer_1"
x="0px"
y="0px"
viewBox="0 0 43.213475 44.200001"
xml:space="preserve"
sodipodi:docname="MedCost_deepcove.svg"
width="43.213474"
height="44.200001"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs2819" /><sodipodi:namedview
id="namedview2817"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="5.0021322"
inkscape:cx="85.66347"
inkscape:cy="22.590367"
inkscape:window-width="1512"
inkscape:window-height="873"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g2806" />
<style
type="text/css"
id="style2778">
.st0{fill:#235937;}
.st1{fill:#55A51C;}
</style>
<g
id="g2810"
transform="translate(0.1)">
<g
id="g2808"
transform="matrix(1.0026328,0,0,1,2.6327667e-4,0)">
<g
id="g2794">
<path
class="st0"
d="m 73.7,10.7 v 26.9 h -3 V 25.5 c 0,-4.1 0.2,-9.5 0.1,-9.5 v 0 c 0,0 -1.4,2.5 -2.3,4.1 L 62.3,31 H 60.7 L 54.6,20.1 C 53.7,18.5 52.3,16 52.3,16 v 0 c 0,0 0.2,5.3 0.2,9.5 v 12.1 h -3 V 10.7 h 2.9 l 6.5,11.4 c 2.3,3.9 2.7,5 2.7,5 v 0 c 0,0 0.5,-1.1 2.7,-5 l 6.5,-11.4 z"
id="path2780" />
<path
class="st0"
d="m 95.7,34.6 v 3 H 77.9 V 10.7 h 17.5 v 3 H 81 v 9.1 h 11.6 v 2.8 H 81 v 9.1 h 14.7 z"
id="path2782" />
<path
class="st0"
d="m 111,10.7 c 7.5,0 13.6,5.5 13.6,13.6 0,8 -6,13.3 -13.4,13.3 H 99.9 V 10.7 Z m 0.2,23.9 c 5.5,0 10.2,-3.9 10.2,-10.3 0,-6.5 -4.7,-10.6 -10.4,-10.6 h -8 v 21 h 8.2 z"
id="path2784" />
<path
class="st0"
d="m 150.1,18 c -1.8,-3 -5.1,-4.8 -8.8,-4.8 -6,0 -10.5,4.4 -10.5,10.9 0,6.5 4.6,10.9 10.5,10.9 4,0 7.5,-2.2 9.3,-5.6 l 3,0.8 c -2.2,4.8 -6.8,7.8 -12.3,7.8 -7.5,0 -13.6,-5.9 -13.6,-14 0,-8.1 6.1,-14 13.6,-14 5.1,0 9.6,2.8 11.9,7.1 z"
id="path2786" />
<path
class="st0"
d="m 170,10.2 c 7.5,0 13.7,5.9 13.7,14 0,8.1 -6.1,14 -13.7,14 -7.5,0 -13.6,-5.9 -13.6,-14 0,-8.2 6.1,-14 13.6,-14 z m 0,24.9 c 5.9,0 10.5,-4.5 10.5,-10.9 0,-6.5 -4.5,-10.9 -10.5,-10.9 -6,0 -10.5,4.4 -10.5,10.9 0.1,6.4 4.6,10.9 10.5,10.9 z"
id="path2788" />
<path
class="st0"
d="m 202.1,18.1 c -0.6,-3.3 -3.3,-5.2 -6.6,-5.2 -3.5,0 -5.9,1.7 -5.9,4.4 0,3 3.6,3.9 7.1,5 3.7,1.1 8.7,2.8 8.7,8 0,4.5 -3.8,7.8 -9.6,7.8 -5.6,0 -9.2,-3.4 -10,-7.9 l 3,-0.8 c 0.4,3.1 2.9,5.9 7.2,5.9 3.9,0 6.3,-2.1 6.3,-4.9 0,-3.9 -4.5,-4.7 -8.3,-5.9 -3,-1 -7.4,-2.3 -7.4,-7 0,-4.2 3.9,-7.3 9.1,-7.3 4.8,0 8.5,2.9 9.4,7.2 z"
id="path2790" />
<path
class="st0"
d="m 227.7,10.7 v 3 h -8.8 v 23.9 h -3 v -24 h -8.8 v -3 h 20.6 z"
id="path2792" />
</g>
<g
id="g2806">
<path
class="st1"
d="M 29.6,13.7 C 31.4,9.5 32.5,4.9 32.5,0 h -4.2 c 0,7.9 -3.1,15.1 -8.1,20.4 -0.1,0.1 -0.3,0.3 -0.4,0.4 C 14.7,26 7.7,29.2 0,29.2 v 4.3 c 4.8,0 9.3,-1.1 13.3,-3 -1.8,4.2 -2.9,8.8 -2.9,13.7 h 4.2 c 0,-7.9 3.1,-15.1 8.1,-20.4 0.1,-0.1 0.3,-0.3 0.4,-0.4 C 28.2,18.2 35.2,15 42.9,15 v -4.3 c -4.7,0 -9.2,1.1 -13.3,3 z"
id="path2796"
style="fill:#04153e;fill-opacity:1" />
<path
class="st1"
d="m 16.6,44.2 h 4.2 c 0,-12.6 9.9,-22.8 22.2,-23 V 17 C 28.4,17.1 16.6,29.3 16.6,44.2 Z"
id="path2798"
style="fill:#04153e;fill-opacity:1" />
<path
class="st1"
d="m 22.7,44.2 h 4.2 c 0,-9.1 7.2,-16.5 16,-16.7 v -4.3 c -11.1,0.2 -20.2,9.5 -20.2,21 z"
id="path2800"
style="fill:#04153e;fill-opacity:1" />
<path
class="st1"
d="m 26.3,0 h -4.2 c 0,12.6 -9.9,22.8 -22.2,23 v 4.3 C 14.6,27.1 26.3,14.9 26.3,0 Z"
id="path2802"
style="fill:#04153e;fill-opacity:1" />
<path
class="st1"
d="M 20.2,0 H 16 C 16,9.1 8.8,16.5 0,16.7 V 21 C 11.2,20.8 20.2,11.5 20.2,0 Z"
id="path2804"
style="fill:#04153e;fill-opacity:1" />
</g>
</g>
</g>
<g
id="g2814"
transform="translate(0.1)">
<path
class="st0"
d="m 234.5,8.9 c 0,1.7 -1.3,3 -3,3 -1.7,0 -3,-1.3 -3,-3 0,-1.6 1.4,-3 3,-3 1.7,0.1 3,1.4 3,3 z m -5.3,0 c 0,1.3 1,2.4 2.3,2.4 1.3,0 2.3,-1 2.3,-2.3 0,-1.3 -1,-2.4 -2.3,-2.4 -1.3,0 -2.3,1 -2.3,2.3 z m 1.8,1.6 h -0.7 v -3 c 0.3,-0.1 0.6,-0.1 1.1,-0.1 0.6,0 0.8,0.1 1,0.2 0.2,0.1 0.3,0.4 0.3,0.6 0,0.3 -0.3,0.6 -0.6,0.7 v 0 c 0.3,0.1 0.4,0.3 0.5,0.7 0.1,0.4 0.1,0.6 0.2,0.7 h -0.7 C 232,10.2 232,9.9 231.9,9.6 231.8,9.3 231.7,9.1 231.3,9.1 H 231 Z m 0,-1.7 h 0.3 c 0.4,0 0.7,-0.1 0.7,-0.4 0,-0.3 -0.2,-0.5 -0.6,-0.5 -0.2,0 -0.3,0 -0.4,0 z"
id="path2812" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

+169
View File
@@ -0,0 +1,169 @@
belongs_to = member.joins(employer: { broker: :carrier }).select(
'brokers.id AS broker_id',
'carriers.id AS carrier_id'
)
keys = member.attributes.with_indifferent_access.slice(
:name, :family_id, :mb_member_key, :pb_entity_key, :pl_plan_key
)
enum :role, { member: 0, employer: 1, broker: 2, provider: 3, carrier: 4, admin: 5 }
http://localhost:3005/api/v1/claims/member_claims/343872
<div class="w-full flex items-center pt-6">
<h3 class="flex-none font-bold text-2xl text-brightlava">Admin</h3>
<div class="grow h-[1px] mt-2 ml-1 bg-brightlava"></div>
</div>
{
"network_provider": "Cigna",
"plan_benefits": [
{
"id": 1295,
"title": "CLASSIC 3.5K",
"sequence": 1,
"benefit": "$25",
"benefit_desc": "Primary Visit"
},
{
"id": 1296,
"title": "CLASSIC 3.5K",
"sequence": 2,
"benefit": "$50",
"benefit_desc": "Specialist Visit"
},
{
"id": 1297,
"title": "CLASSIC 3.5K",
"sequence": 3,
"benefit": "$50",
"benefit_desc": "Urgent Care"
},
{
"id": 1298,
"title": "CLASSIC 3.5K",
"sequence": 4,
"benefit": "$3,500",
"benefit_desc": "INN-Ind Ded"
},
{
"id": 1299,
"title": "CLASSIC 3.5K",
"sequence": 5,
"benefit": "$7,000",
"benefit_desc": "INN-Family Ded"
},
{
"id": 1300,
"title": "CLASSIC 3.5K",
"sequence": 6,
"benefit": "$7,000",
"benefit_desc": "OON-Ind Ded"
},
{
"id": 1301,
"title": "CLASSIC 3.5K",
"sequence": 7,
"benefit": "$14,000",
"benefit_desc": "OON-Family Ded"
},
{
"id": 1302,
"title": "CLASSIC 3.5K",
"sequence": 8,
"benefit": "70%/30%",
"benefit_desc": "Co-Insurance"
},
{
"id": 1303,
"title": "CLASSIC 3.5K",
"sequence": 9,
"benefit": "$7,000",
"benefit_desc": "INN-Ind OOP"
},
{
"id": 1304,
"title": "CLASSIC 3.5K",
"sequence": 10,
"benefit": "$14,000",
"benefit_desc": "INN-Family OOP"
},
{
"id": 1305,
"title": "CLASSIC 3.5K",
"sequence": 11,
"benefit": "$14,000",
"benefit_desc": "OON-Ind OOP"
},
{
"id": 1306,
"title": "CLASSIC 3.5K",
"sequence": 12,
"benefit": "$28,000",
"benefit_desc": "OON-Family OOP"
},
{
"id": 1307,
"title": "CLASSIC 3.5K",
"sequence": 13,
"benefit": "Sub to Ded",
"benefit_desc": "Emergency Room"
},
{
"id": 1308,
"title": "CLASSIC 3.5K",
"sequence": 14,
"benefit": "100%",
"benefit_desc": "Preventive Care"
}
],
"recent_claims": [
{
"claim_number": 1950638,
"visit_date": "04-28-2026",
"claim_status": "Approved Claim",
"patient_name": "LAWHORN, ALMON",
"provider_name": "COLE, CHRISTOPHER"
},
{
"claim_number": 1949703,
"visit_date": "02-12-2026",
"claim_status": "Approved Claim",
"patient_name": "LAWHORN, ALMON",
"provider_name": "CIGNA HEALTH AND LIFE INSURANCE COMPANY"
},
{
"claim_number": 1942866,
"visit_date": "02-12-2026",
"claim_status": "Denied Claim",
"patient_name": "LAWHORN, ALMON",
"provider_name": "NELSON, JOHN C"
},
{
"claim_number": 1942522,
"visit_date": "02-12-2026",
"claim_status": "Denied Claim",
"patient_name": "LAWHORN, ALMON",
"provider_name": "MALOY, JENNIFER R"
},
{
"claim_number": 1941594,
"visit_date": "02-12-2026",
"claim_status": "Denied Claim",
"patient_name": "LAWHORN, ALMON",
"provider_name": "JON K MARTI, DO"
},
{
"claim_number": 1940479,
"visit_date": "02-12-2026",
"claim_status": "Approved Claim",
"patient_name": "LAWHORN, ALMON",
"provider_name": "GROUNDS, DAVID S"
}
]
}
+63
View File
@@ -0,0 +1,63 @@
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';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const quickLinks: QuickLink[] = [
{
display: "Member Login/Registration",
url: "/"
},
{
display: "Link to Thing 1",
url: "/"
},
{
display: "Link to Thing 2",
url: "/"
},
{
display: "Link to another Thing",
url: "/"
}
]
export default function Brokers() {
return (
<div className="w-full">
<LandingPageHeader headerText="Brokers" />
<div className="w-full flex justify-center pb-30">
<div className="w-11/12 z-1">
<div className={classnames("w-3/5 text-4xl text-bluemana px-10 -mb-14", font.className)}>
Partner with Britton Benefit Services to Grow Your Business and Serve Your Clients
</div>
<QuickAccessLinks
imageUrl="/images/brokers-stock.jpg"
quickLinks={quickLinks}
/>
<div className={classnames("w-full text-2xl px-10", font.className)}>
In the ever-changing health care market, finding cost-effective plans to help your clients save money is a serious challenge. At Britton Benefit Services, LLC we have a proven ourselves as leaders in Third-Party Benefits Administration through our performance in finding employers the right self-funded plans that work for them. Whether you need to review a plan document, manage information reports, review provider directories, or you are looking for access to marketing materials, youve come to the right place.
<br />
<br />
Broker Services from Britton Benefit Services
<ul className="flex flex-col pl-10 leading-4 list-disc list-inside">
<li>
Discuss your clients needs and provide additional insight
</li>
<li>
Learn more about our company and self-funding
</li>
<li>
Request a free, no-obligation quote to see the cost savings we can help you provide to your client
</li>
</ul>
</div>
</div>
</div>
</div>
)
}
@@ -0,0 +1,28 @@
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 claims status<br />
Research which type of plan is best for you<br />
Control your health care costs by selecting a PPO thats 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>
)
}
@@ -0,0 +1,60 @@
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';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const quickLinks: QuickLink[] = [
{
display: "Employer Login/Registration",
url: "/"
},
{
display: "Link to Thing 1",
url: "/"
},
{
display: "Link to Thing 2",
url: "/"
},
{
display: "Link to another Thing",
url: "/"
}
]
export default function Employers() {
return (
<div className="w-full">
<LandingPageHeader headerText="Employers" />
<div className="w-full flex justify-center pb-30">
<div className="w-11/12 z-1">
<div className={classnames("w-3/5 text-4xl text-bluemana px-10 -mb-14", font.className)}>
Making Your Benefits Work for You and Your Employees
</div>
<QuickAccessLinks
imageUrl="/images/employers-stock.jpg"
quickLinks={quickLinks}
/>
<div className={classnames("w-full text-2xl px-10", font.className)}>
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 claims status<br />
Research which type of plan is best for you<br />
Control your health care costs by selecting a PPO thats 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.
</div>
</div>
</div>
</div>
)
}
+56
View File
@@ -0,0 +1,56 @@
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 (
<>
<Navbar collapseHeight={40} />
<div className="w-full relative">
<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 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 text-platinum">
{children}
</div>
<Footer />
</div>
</>
)
}
@@ -0,0 +1,25 @@
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>
)
}
+58
View File
@@ -0,0 +1,58 @@
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';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const quickLinks: QuickLink[] = [
{
display: "Member Login/Registration",
url: "/"
},
{
display: "Link to Thing 1",
url: "/"
},
{
display: "Link to Thing 2",
url: "/"
},
{
display: "Link to another Thing",
url: "/"
}
]
export default function Members() {
return (
<div className="w-full">
<LandingPageHeader headerText="Members" />
<div className="w-full flex justify-center pb-30">
<div className="w-11/12 z-1">
<div className={classnames("w-3/5 text-4xl text-bluemana px-10 -mb-14", font.className)}>
Member Tagline will go here when we come up with the wording
</div>
<QuickAccessLinks
imageUrl="/images/members-stock.jpg"
quickLinks={quickLinks}
/>
<div className={classnames("w-full text-2xl px-10", font.className)}>
At Britton Benefit Services, LLC we're making employee benefits more affordable. In addition to the cost savings you receive when your employer chooses Britton Benefit Services, LLC we also make sure the important information you need is at your fingertips.
<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.
</div>
</div>
</div>
</div>
)
}
@@ -0,0 +1,49 @@
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';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
const quickLinks: QuickLink[] = [
{
display: "Member Login/Registration",
url: "/"
},
{
display: "Link to Thing 1",
url: "/"
},
{
display: "Link to Thing 2",
url: "/"
},
{
display: "Link to another Thing",
url: "/"
}
]
export default function Providers() {
return (
<div className="w-full">
<LandingPageHeader headerText="Providers" />
<div className="w-full flex justify-center pb-6">
<div className="w-11/12 z-1">
<div className={classnames("w-3/5 text-4xl text-bluemana px-10 -mb-14", font.className)}>
Easy Access to HIPAA Compliant Patient Information and Much More
</div>
<QuickAccessLinks
imageUrl="/images/providers-stock.jpg"
quickLinks={quickLinks}
/>
<div className={classnames("w-full text-2xl px-10", font.className)}>
You know that as a provider, you have a responsibility to your patients to respond promptly and accurately. At Britton Benefit Services, LLC its 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>
)
}
+54
View File
@@ -0,0 +1,54 @@
"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';
export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
const headersList = await headers();
const pathname = headersList.get("x-current-path");
const dashboardType = pathname?.split('/').pop() || "";
const pageNavItems = sideNavItems[dashboardType];
return (
<div className="relative w-full h-full">
<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-light-bronze 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-light-bronze border-atmosphere"
)} />
</div>
</div>
</div>
<div className="w-full h-full flex items-start">
<div className="w-full min-w-[87vw] max-w-[94vw] lg:min-w-[89vw] lg:max-w-[95vw] flex flex-col items-end">
<div className="w-[90.15vw] h-20 flex justify-between items-center">
<div className={classnames('flex items-center')}>
<div className="-ml-8.5 z-2">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
</div>
</div>
</div>
<div className="w-full h-full flex justify-center ml-[60px]">
{children}
</div>
</div>
</div>
</div>
)
}
+94
View File
@@ -0,0 +1,94 @@
// /app/login/page.tsx
'use test' // Marks this component as a client component
'use client'
import classnames from 'classnames';
import { useActionState } from 'react'
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() {
// useActionState manages the server state (errors, loading) and form binding
const [state, formAction, isPending] = useActionState(signInAndFetchUser, null)
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">
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 className="text-bluemana">
Not signed up?
<Link
href="/account/signin"
>
<span className="hover:text-light-bronze">Click here.</span>
</Link>
</div> */}
</div>
<div className="text-bluemana mt-4 z-20">
Not signed up?{' '}
<Link
href="/account/signup"
className="underline text-atmosphere hover:text-light-bronze z-10"
>
Click here.
</Link>
</div>
</div>
)
}
+188
View File
@@ -0,0 +1,188 @@
// /app/login/page.tsx
'use test' // Marks this component as a client component
'use client'
import classnames from 'classnames';
import { useActionState, useState } from 'react'
import { registerUser } from '@/app/actions/signup'
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(registerUser, null)
const [role, setRole] = useState("");
const [date, setDate] = useState("");
const handleDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let input = e.target.value.replace(/\D/g, "");
if (input.length > 2 && input.length <= 4) {
input = `${input.slice(0, 2)}/${input.slice(2)}`;
} else if (input.length > 4) {
input = `${input.slice(0, 2)}/${input.slice(2, 4)}/${input.slice(4, 8)}`;
}
setDate(input);
};
return (
<div
className="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-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">
Sign Up
</div>
<div className="mb-4 text-center text-md font-extrabold text-bluemana">
(Employer and Broker users must be invited)
</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>
<label htmlFor="two-item-dropdown" className="block text-md font-bold text-platinum">
I Am A...
</label>
<select
id="role"
name="role"
value={role}
onChange={(e) => setRole(e.target.value)}
required
className="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 option */}
<option value="" disabled>Select User Role</option>
{/* The two dropdown items */}
<option value="member">Member</option>
<option value="provider">Provider</option>
</select>
</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-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="you@example.com"
/>
</div>
</div>
{role === 'member' && (
<>
{/* <div className="mt-2">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Member ID</label>
<input
type="roleId"
name="roleId"
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="######"
/>
</div>
</div> */}
<div className="mt-2">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Last 4 Digits of SSN</label>
<input
type="lastFourSsn"
name="lastFourSsn"
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="####"
/>
</div>
</div>
<div className="mt-2">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Date of Birth</label>
<input
type="dateOfBirth"
name="dateOfBirth"
value={date}
onChange={handleDateChange}
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="MM/DD/YYYY"
/>
</div>
</div>
</>
)}
{role === 'provider' && (
<div className="mt-2">
<div className="rounded-md shadow-sm -space-y-px">
<label className="block text-md font-bold text-platinum">Tax ID</label>
<input
type="roleId"
name="roleId"
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 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="######"
/>
</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-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="••••••••"
/>
</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="confirmPassword"
name="confirmPassword"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 bg-platinum border border-gray-300 placeholder-bluetang text-deepcove rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
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>
)
}
+100
View File
@@ -0,0 +1,100 @@
// /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.' }
}
}
+91
View File
@@ -0,0 +1,91 @@
'use server'
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { SignJWT } from 'jose';
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
function roleMapping(role_number: number) {
const map = [
"member",
"employer",
"broker",
"provider",
"carrier",
"admin"
]
return map[role_number]
}
export async function signInAndFetchUser(prevState: any, formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
if (!email || !password) {
throw new Error('Please fill in all fields.');
}
const authResponse = await fetch(`http://${process.env.BE_API_SERVER_HOST}:${process.env.BE_API_SERVER_PORT}/signin`, {
method: 'POST',
body: JSON.stringify({ user: {email, password} }),
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
});
if (!authResponse.ok) {
throw new Error('Invalid credentials');
}
const { user } = await authResponse.json();
const token = authResponse.headers.get('authorization')?.split(' ')[1];
console.log("token: ", token)
// const token = user.jti
const role = user.role
const sessionPayload = {
token: token,
user: {
id: user.id,
name: user.name,
email: user.email,
role: role,
roleId: user.role_id,
keychain: user.keychain
}
};
console.log("sessionPayload: ", sessionPayload)
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: '/',
});
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.' }
}
}
+98
View File
@@ -0,0 +1,98 @@
// app/login/actions.ts
'use server'
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { SignJWT } from 'jose';
const SECRET_KEY = new TextEncoder().encode(process.env.JWT_SECRET_KEY);
function roleMapping(role_number: number) {
const map = [
"member",
"employer",
"broker",
"provider",
"carrier",
"admin"
]
return map[role_number]
}
export async function registerUser(prevState: any, formData: FormData) {
const email = formData.get('email') as string
const password = formData.get('password') as string
const confirm_password = formData.get('confirmPassword') as string
const role = formData.get('role') as string
const role_id = formData.get('roleId') as string
const last_four_ssn = formData.get('lastFourSsn') as string
const date_of_birth = formData.get('dateOfBirth') as string
if (!email || !password || !confirm_password || !role) {
throw new Error('Please fill in all fields.');
}
if (role === "provider" && !role_id) {
throw new Error('Please fill in all fields.');
}
if (role === "member" && (!date_of_birth || !last_four_ssn)) {
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`, {
method: 'POST',
body: JSON.stringify({ user: {email, password, confirm_password, role, role_id, last_four_ssn, date_of_birth} }),
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
});
if (!authResponse.ok) {
throw new Error('Invalid credentials');
}
const data = await authResponse.json();
const token = data.jti
const sessionPayload = {
token: token,
user: {
id: data.id,
name: data.name,
email: data.email,
role: data.role,
roleId: data.role_id,
keychain: data.keychain
}
};
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: '/',
});
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.' }
}
}
+18
View File
@@ -0,0 +1,18 @@
import { isIPv4 } from 'net';
export async function GET() {
// Read the runtime env variable safely on the server
let apiUrl = "";
const backendApiHost = process.env.BE_API_SERVER_HOST || "localhost";
if (isIPv4(backendApiHost)) {
apiUrl = apiUrl.concat(backendApiHost)
} else {
apiUrl = apiUrl.concat("127.0.0.1")
}
if (process.env.BE_API_SERVER_PORT) {
apiUrl = apiUrl.concat(":").concat(process.env.BE_API_SERVER_PORT)
}
return Response.json({ apiUrl });
}
+141
View File
@@ -0,0 +1,141 @@
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>
)
}
+148
View File
@@ -0,0 +1,148 @@
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');
}
}
}
+149
View File
@@ -0,0 +1,149 @@
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';
// 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;
}
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 Member() {
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 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}/initialize_dashboard`, {
method: 'GET',
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 (
<>
{dashboardData &&
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col xl:flex-row pt-4 z-2 space-x-4 space-y-4 xl:space-y-0")}>
<div className="flex flex-col gap-y-4">
<div className="flex flex-col w-full h-full xl:h-auto lg:flex-row gap-y-4 lg:gap-y-0 lg:gap-x-4">
{/* <DeductableProgressWidget /> */}
<IdCardViewer
pbEntityKey={userData.pb_entity_key}
cardLaout="FullPageCard"
/>
<FindProviderWidget
// pl_plan_key='57'
networkProvider={dashboardData.network_provider}
/>
</div>
<FindRxInformationWidget />
<RecentClaimsWidget
recentClaims={dashboardData.recent_claims}
/>
</div>
<BenefitsAAGWidget
planBenefits={dashboardData.plan_benefits}
/>
</div>
</div>
</div>
}
</>
);
} 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>;
// }
}
+100
View File
@@ -0,0 +1,100 @@
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";
import AccountDropdown from '../../components/AccountDropdown';
import SideNav from "@/components/SideNav";
import Image from 'next/image';
import classnames from 'classnames';
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 DashboardLayout({ children }: { children: React.ReactNode }) {
const cookieStore = await cookies();
const sessionCookie = cookieStore.get("session"); // Replace with your cookie name
if (!sessionCookie) {
return <div className='flex w-full justify-center'>No active session. Please log in.</div>;
}
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 userRole = data.user.role
// const headersList = await headers();
// const pathname = headersList.get("x-current-path");
// const dashboardType = pathname?.split('/').pop() || "";
const pageNavItems = sideNavItems[userRole];
return (
<div className="relative w-full h-full">
<div className="absolute bottom-0 right-0 flex flex-col justify-end items-end w-full h-full">
<div className="w-full -mb-[2px] flex justify-start">
<div className="w-[87vw] sm:w-[91vw] md:w-[93vw] lg:w-[94.5vw] xl:w-[95.5vw] h-20 border-b-2 border-copper z-2" />
</div>
<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="w-full h-full flex items-start">
<SideNav
maxWidth="w-[16vw]"
bgColor="bg-atmosphere"
navItems={pageNavItems}
/>
<div className="w-full min-w-[87vw] max-w-[94vw] lg:min-w-[89vw] lg:max-w-[95vw] flex flex-col items-end">
<div className="w-[90.15vw] h-20 flex justify-between items-center">
<div className={classnames('flex items-center')}>
<div className="-ml-4.5 z-2">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
</div>
</div>
<div className="pr-20 z-2">
<AccountDropdown userRole="member" />
</div>
</div>
<div className="w-full h-full flex justify-start">
{children}
</div>
</div>
</div>
</div>
)
} catch (error) {
// Fails if token is expired, tampered with, or invalid
console.error("JWT verification failed:", error);
return <div className='flex w-full justify-center'>Session invalid or expired.</div>;
}
}
+76
View File
@@ -0,0 +1,76 @@
// /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>
)
}
+148
View File
@@ -0,0 +1,148 @@
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 MemberClaims() {
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');
}
}
}
+308
View File
@@ -0,0 +1,308 @@
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>
)
}
+149
View File
@@ -0,0 +1,149 @@
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';
// 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;
}
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 Member() {
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 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}/initialize_dashboard`, {
method: 'GET',
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 (
<>
{dashboardData &&
<div className="flex flex-col w-full pl-8 pt-5 2xl:pt-7">
<DashboardHeader
userDisplayName={userData.name}
/>
<div className="flex w-full pr-23">
<div className={classnames("w-full flex flex-col xl:flex-row pt-4 z-2 space-x-4 space-y-4 xl:space-y-0")}>
<div className="flex flex-col gap-y-4">
<div className="flex flex-col w-full h-full xl:h-auto lg:flex-row gap-y-4 lg:gap-y-0 lg:gap-x-4">
{/* <DeductableProgressWidget /> */}
<IdCardViewer
pbEntityKey={userData.pb_entity_key}
cardLaout="FullPageCard"
/>
<FindProviderWidget
// pl_plan_key='57'
networkProvider={dashboardData.network_provider}
/>
</div>
<FindRxInformationWidget />
<RecentClaimsWidget
recentClaims={dashboardData.recent_claims}
/>
</div>
<BenefitsAAGWidget
planBenefits={dashboardData.plan_benefits}
/>
</div>
</div>
</div>
}
</>
);
} 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>;
// }
}
+30
View File
@@ -0,0 +1,30 @@
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'] })
// export const metadata: Metadata = {
// title: 'Britton Benifit Services, LLC',
// };
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<main className={classNames("bg-deepcove h-full min-h-screen flex justify-center", font.className)}>
<div className="w-full max-w-[1920px]">
{children}
</div>
</main>
</body>
</html>
);
}
-103
View File
@@ -1,103 +0,0 @@
import Image from "next/image";
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2 tracking-[-.01em]">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
src/app/page.tsx
</code>
.
</li>
<li className="tracking-[-.01em]">
Save and see your changes instantly.
</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
);
}
+63
View File
@@ -0,0 +1,63 @@
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';
export default async function Home() {
// const { data } = await getClient().query({
// query: HOME_GET_DATA_QUERY,
// });
return (
<>
<Navbar collapseHeight={730} />
<Tagline
bgColor="bg-bluemana"
textColor="text-deepcove"
textTop="Healthcare that's"
textBottom="truly affordable"
/>
<div className="pt-10">
<InfoCard
title="Members"
learnMoreUrl="/members"
imageUrl="/images/member-image.jpg"
>
What we do for members
</InfoCard>
</div>
<div className='bg-atmosphere rounded-3xl'>
<InfoCard
title="Employers"
learnMoreUrl="/employers"
imageUrl='/images/employer-image.jpg'
invert
>
What we do for employers
</InfoCard>
</div>
<div className="w-full">
<InfoCard
title="Providers"
learnMoreUrl="/providers"
imageUrl="/images/provider-image.png"
>
What we do for providers
</InfoCard>
</div>
<div className='bg-light-bronze rounded-3xl'>
<InfoCard
title="Brokers"
learnMoreUrl="/brokers"
imageUrl="/images/broker-image.jpg"
invert
>
What we do for brokers
</InfoCard>
</div>
<Footer />
</>
);
}
+11
View File
@@ -0,0 +1,11 @@
export default function Basepage() {
return (
<div className="w-full h-screen flex justify-center items-center">
<div>
</div>
</div>
)
}
+35
View File
@@ -0,0 +1,35 @@
// import SideNav from "@/components/SideNav";
// export default function Sidenav() {
// return (
// <div className="w-full h-screen flex justify-start items-center">
// <div className="h-[calc(screen * 2/3)]">
// <SideNav
// maxWidth="max-w-31"
// bgColor="bg-atmosphere"
// navItems={
// [
// {
// title: "Dashboard",
// url: "/"
// },
// {
// title: "My Plan & Benefits",
// url: "/"
// },
// {
// title: "Plan Utilization",
// url: "/"
// },
// {
// title: "Insurance Card",
// url: "/"
// }
// ]
// }
// />
// </div>
// </div>
// )
// }
+78
View File
@@ -0,0 +1,78 @@
"use client"
import React, { useState } from 'react'
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { CgProfile } from "react-icons/cg";
import classnames from 'classnames';
interface DropdownProps {
userRole: "member" | "employer";
}
const AccountDropdown: React.FC<DropdownProps> = ({userRole}) => {
const [isHovered, setIsHovered] = useState(false);
return (
<div className="">
<Menu>
<MenuButton as="div" className={classnames(
"inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm/6 font-semibold text-platinum focus:outline-hidden data-open:text-bronze data-hover:text-bronze",
{
'cursor-pointer': isHovered,
}
)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<CgProfile size={32} />
</MenuButton>
<MenuItems anchor="bottom end" className={"py-1 bg-deepcove border-2 border-copper focus:outline-hidden text-platinum text-md text-right font-semibold rounded-lg z-2"}>
<MenuItem as="div" className={"px-3 py-1.5"}>
<a className="block data-focus:text-bluemana hover:text-bluemana" href="/dashboard/member">
Dashboard
</a>
</MenuItem>
<MenuItem as="div" className={"px-3 py-1.5"}>
<a className="block data-focus:text-bluemana hover:text-bluemana" href="/">
Sign Out
</a>
</MenuItem>
</MenuItems>
{/* <MenuItems
transition
anchor="bottom end"
className="w-52 origin-top-right rounded-xl border border-white/5 bg-white/5 p-1 text-sm/6 text-white transition duration-100 ease-out [--anchor-gap:--spacing(1)] focus:outline-none data-closed:scale-95 data-closed:opacity-0"
>
<MenuItem>
<button className="group flex w-full items-center gap-2 rounded-lg px-3 py-1.5 data-focus:bg-white/10">
Edit
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-focus:inline">⌘E</kbd>
</button>
</MenuItem>
<MenuItem>
<button className="group flex w-full items-center gap-2 rounded-lg px-3 py-1.5 data-focus:bg-white/10">
Duplicate
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-focus:inline">⌘D</kbd>
</button>
</MenuItem>
<div className="my-1 h-px bg-white/5" />
<MenuItem>
<button className="group flex w-full items-center gap-2 rounded-lg px-3 py-1.5 data-focus:bg-white/10">
Archive
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-focus:inline">⌘A</kbd>
</button>
</MenuItem>
<MenuItem>
<button className="group flex w-full items-center gap-2 rounded-lg px-3 py-1.5 data-focus:bg-white/10">
Delete
<kbd className="ml-auto hidden font-sans text-xs text-white/50 group-data-focus:inline">⌘D</kbd>
</button>
</MenuItem>
</MenuItems> */}
</Menu>
</div>
);
};
export default AccountDropdown;
+47
View File
@@ -0,0 +1,47 @@
"use client";
import classnames from 'classnames';
import { Schibsted_Grotesk } from 'next/font/google'
import { useEffect, useState } from 'react';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface BenefitsAAGWidgetProps {
planBenefits: PlanBenefitType[];
}
interface PlanBenefitType {
id: number;
title: string;
sequence: number;
benefit: string;
benefit_desc: string;
}
const BenefitsAAGWidget: React.FC<BenefitsAAGWidgetProps> = ({ planBenefits }) => {
const planTitle = planBenefits[0]?.title || "Unknown Plan Benefits"
return (
<div className="2xl:min-w-[32vw] xl:min-w-[25vw] w-full flex flex-col justify-start items-center px-4 py-3 text-xl text-platinum border border-6 border-platinum rounded-4xl">
<p className="text-3xl underline py-2">Benefits At A Glance</p>
<p className={classnames("text-2xl font-semibold py-2", font.className)}>{planTitle}</p>
<div className={classnames("w-full flex-col items-between px-4", font.className)}>
{(planBenefits).map((mb) => (
<div key={mb.sequence} className="flex items-center w-full">
<div className="flex-none">
{mb.benefit_desc}
</div>
<div className="flex-grow h-[1px] mx-1 mt-1 bg-verdigris"></div>
<div className="flex-none justify-end">
{mb.benefit}
</div>
</div>
))}
</div>
</div>
);
};
export default BenefitsAAGWidget;
+3 -1
View File
@@ -5,16 +5,18 @@ import classnames from 'classnames';
interface ButtonProps {
onClick?: () => void;
disabled?: boolean;
children: React.ReactNode;
className?: string;
}
const Button: React.FC<ButtonProps> = ({ onClick, children, className }) => {
const Button: React.FC<ButtonProps> = ({ onClick, disabled, children, className }) => {
const [isHovered, setIsHovered] = useState(false);
return (
<button
onClick={onClick}
disabled={disabled}
className={classnames(
className, {
'cursor-pointer': isHovered,
+40
View File
@@ -0,0 +1,40 @@
import classnames from 'classnames';
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface DashboardHeaderProps {
userDisplayName: string;
}
const DashboardHeader: React.FC<DashboardHeaderProps> = ({ userDisplayName }) => {
return (
<div className="w-full flex flex-col justify-start">
<div className={classnames(
"-mb-[34px] ml-1 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",
font.className
)}>
{userDisplayName}
</div>
<div className="w-full flex items-center justify-end">
<div className={classnames(
"w-full h-[6px] rounded-s bg-atmosphere z-1"
)}>
</div>
<div className="w-[60px] h-full flex justify-start flex-shrink-0">
<div className="w-[33px] 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>
);
};
export default DashboardHeader;
+92
View File
@@ -0,0 +1,92 @@
"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;
@@ -0,0 +1,36 @@
"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;
+42 -74
View File
@@ -1,84 +1,52 @@
import React, { useState } from 'react'
import Link from 'next/link';
import classnames from 'classnames';
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'
import { FaAngleDown } from "react-icons/fa";
import Button from './Button';
import { MenuItem } from './Navbar'
const userRoles: string[] = ["Member", "Employer"]
interface Props {
item: MenuItem;
interface DropdownProps {
currentRole: "Member" | "Employer";
}
export default function Dropdown(props: Props) {
const { item } = props;
const [isOpen, setIsOpen] = useState<boolean>(false);
const menuItems = item?.children ? item.children : [];
const lastIndex = menuItems.length - 1;
const itemId = item.id;
const Dropdown: React.FC<DropdownProps> = ({currentRole}) => {
const [isHovered, setIsHovered] = useState(false);
const toggle = () => {
setIsOpen(old => !old);
}
const transClass = isOpen
?
"flex"
:
"hidden";
function isLastItem (currentItem: MenuItem) {
return lastIndex == menuItems.indexOf(currentItem)
}
function isSecondItem (currentItem: MenuItem) {
return 1 == menuItems.indexOf(currentItem)
}
let avaliableUserRoles: string[] = userRoles.filter(role => role !== currentRole);
return (
<>
<div className="relative">
<Button
className="hover:text-britton-light font-semibold text-lg"
onClick={toggle}
>{item.title}</Button>
<div className={`absolute top-8 z-11 w-[150px] flex flex-col bg-stone-300 text-sm rounded-md ${transClass}`}>
{
menuItems.map(item =>
<>
<Link
key={item.id}
className={classnames(
"hover:text-britton-neutral text-center px-2 py-1",
{
"hover:bg-britton-newblue" : itemId == 'employers',
"hover:bg-britton-medium" : itemId != 'employers'
}
)}
href={item?.route || ''}
onClick={toggle}
>
{item.title}
</Link>
<div className={classnames(
"h-px bg-britton-accent mx-2",
{"hidden" : isLastItem(item)}
)}
/>
</>
)
}
</div>
</div>
{
isOpen
?
<div
className="fixed top-0 right-0 bottom-0 left-0 z-11 bg-britton-dark/40"
onClick={toggle}
></div>
:
<></>
}
</>
)
}
<div className="">
<Menu>
<MenuButton as="a" className={classnames(
"text-bluemana decoration-bluemana underline hover:text-atmosphere hover:decoration-atmosphere flex items-end",
{
'cursor-pointer': isHovered,
'cursor-default': !isHovered,
}
)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{currentRole}
</MenuButton>
<MenuItems anchor="bottom end" className={"py-2 bg-bluetang border border-copper focus:outline-hidden text-platinum text-md text-left font-semibold rounded-md divide-y divide-[#B8B8B8]"}>
{avaliableUserRoles.map((role) => (
<MenuItem as="div" className={"px-1"}>
<a className="block data-focus:text-atmosphere hover:text-atmosphere" href={`/dashboard/${role.toLowerCase()}`}>
Switch To {role} Role
</a>
</MenuItem>
))}
{/* <MenuItem as="div" className={"px-1"}>
<a className="block data-focus:text-atmosphere hover:text-atmosphere" href="/">
Reset Password
</a>
</MenuItem> */}
</MenuItems>
</Menu>
</div>
);
};
export default Dropdown;
+45
View File
@@ -0,0 +1,45 @@
"use client";
import React, { type SVGProps } from 'react'
import { IconBaseProps, IconType } from 'react-icons';
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";
interface iconMapType {
[key: string]: IconType
}
const iconMap: iconMapType = {
FaRegIdCard: FaRegIdCard,
FaStethoscope: FaStethoscope,
LuLayoutDashboard: LuLayoutDashboard,
LuClipboardList: LuClipboardList,
IoDocumentsOutline: IoDocumentsOutline,
BsClipboardCheck: BsClipboardCheck,
TbProgress: TbProgress
};
type DynamicIconProps = IconBaseProps & {
iconName: string;
};
// interface DynamicIconProps {
// iconName: string;
// };
const DynamicIcon: React.FC<DynamicIconProps> = ({ iconName, ...props }) => {
const IconComponent = iconMap[iconName];
if (!IconComponent) {
console.warn(`Icon "${iconName}" not found in iconMap.`);
return null; // Or render a fallback icon
}
return React.createElement(IconComponent, props);
};
export default DynamicIcon;
+177
View File
@@ -0,0 +1,177 @@
'use client';
import { useState, useEffect, ChangeEvent } from 'react';
import Button from './Button';
interface SelectOption {
entity_key: string;
name: string;
}
interface EmployerMemberSelectProps {
onSelectionCompleteEmployer: (employerValue: string) => void;
onSelectionCompleteMember: (memberValue: string) => void;
userRole: 'employer' | 'broker' | 'carrier';
employerAllButtonText: string;
plPlanKey?: string;
employerName?: string;
brokerId?: number;
carrierId?: number;
}
const EmployerMemberSelect: React.FC<EmployerMemberSelectProps> = ({ onSelectionCompleteEmployer, onSelectionCompleteMember, userRole, employerAllButtonText, plPlanKey, employerName, brokerId, carrierId }) => {
const [optionsEmployer, setOptionsEmployer] = useState<SelectOption[]>([]);
const [selectedEmployer, setSelectedEmployer] = useState<string>('');
const [loadingEmployer, setLoadingEmployer] = useState<boolean>(false);
const [optionsMember, setOptionsMember] = useState<SelectOption[]>([]);
const [selectedMember, setSelectedMember] = useState<string>('');
const [loadingMember, setLoadingMember] = useState<boolean>(false);
const [apiUrl, setapiUrl] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => setapiUrl(data.apiUrl));
}, []);
useEffect(() => {
async function fetchOptionsEmployer(role: string, roleId: number) {
setLoadingEmployer(true);
try {
const response = await fetch(`http://${apiUrl}/api/v1/${role}/${roleId}/employers_list`, {
method: "GET",
});
const data = await response.json();
setOptionsEmployer(data);
} catch (error) {
console.error('Error fetching employer options:', error);
} finally {
setLoadingEmployer(false);
}
}
if (plPlanKey && employerName) {
setSelectedEmployer(plPlanKey)
setOptionsEmployer([{entity_key: plPlanKey, name: employerName}]);
} else if (brokerId) {
fetchOptionsEmployer("brokers", brokerId);
} else if (carrierId) {
fetchOptionsEmployer("carriers", carrierId);
}
}, [apiUrl]);
useEffect(() => {
if (!selectedEmployer) {
setOptionsMember([]);
setSelectedMember('');
return;
}
async function fetchOptionsMember() {
setLoadingMember(true);
try {
const response = await fetch(`http://${apiUrl}/api/v1/employers/${selectedEmployer}/members_list`, {
method: "GET",
});
const data = await response.json();
setOptionsMember(data);
setSelectedMember('');
} catch (error) {
console.error('Error fetching second options:', error);
} finally {
setLoadingMember(false);
}
}
fetchOptionsMember();
}, [selectedEmployer]);
const handleFirstChange = (e: ChangeEvent<HTMLSelectElement>) => {
const val = e.target.value;
setSelectedEmployer(val);
};
const handleAllEmployerSelect = () => {
if (selectedEmployer) {
onSelectionCompleteEmployer(selectedEmployer);
}
};
const handleSecondChange = (e: ChangeEvent<HTMLSelectElement>) => {
const val = e.target.value;
setSelectedMember(val);
// if (val) {
// onSelectionCompleteMember(val);
// }
};
const handleMemberSelect = () => {
if (selectedMember) {
onSelectionCompleteMember(selectedMember);
}
};
return (
<div className="flex flex-col gap-4 max-w-xs">
{/* First Select Dropdown */}
{!plPlanKey &&
<div className="flex flex-col gap-1">
<label className="text-sm font-medium">Employer</label>
<div className="flex">
<select
value={selectedEmployer}
onChange={handleFirstChange}
disabled={loadingEmployer}
className="border p-2 rounded"
>
<option value="">{loadingEmployer ? 'Loading...' : '-- Select Employer --'}</option>
{optionsEmployer.map((opt) => (
<option key={opt.entity_key} value={opt.entity_key}>
{opt.name}
</option>
))}
</select>
<Button
onClick={handleAllEmployerSelect}
>
<div className="flex justify-center items-center rounded-r-2">
{employerAllButtonText}
</div>
</Button>
</div>
</div>
}
{/* Second Select Dropdown */}
<div className="flex flex-col gap-1">
<label className="text-sm font-medium">Member</label>
<div className="flex">
<select
value={selectedMember}
onChange={handleSecondChange}
disabled={!selectedEmployer || loadingMember}
className="border p-2 rounded"
>
<option value="">
{!selectedEmployer
? '-- Select a category first --'
: loadingMember ? 'Loading...' : '-- Select Subcategory --'}
</option>
{optionsMember.map((opt) => (
<option key={opt.entity_key} value={opt.entity_key}>
{opt.name}
</option>
))}
</select>
<Button
onClick={handleAllEmployerSelect}
>
<div className="flex justify-center items-center rounded-r-2">
Select Member
</div>
</Button>
</div>
</div>
</div>
);
}
+60
View File
@@ -0,0 +1,60 @@
"use client";
import Button from '@/components/Button';
import Image from 'next/image';
import { Schibsted_Grotesk } from 'next/font/google'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
import Link from 'next/link';
interface FindProviderWidgetProps {
networkProvider: string;
}
interface NetworkProvider {
network_provider: string;
}
const FindProviderWidget: React.FC<FindProviderWidgetProps> = ({ networkProvider }) => {
return (
<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">
{networkProvider &&
<div className="flex items-end mb-3">
<div className="text-platinum font-semibold">Find A {networkProvider} Provider</div>
</div>
}
{networkProvider == "Cigna" &&
<Link href="http://cigna.com" target="_blank" rel="noopener noreferrer">
<Button className="flex items-center justify-center bg-cobalt rounded hover:bg-copper rounded-xl">
<Image
src="/images/Cigna_deepcove.svg"
alt="Cigna Logo"
width={150}
height={150}
priority
/>
</Button>
</Link>
}
{networkProvider == "MedCost" &&
<Link href="http://medcost.com" target="_blank" rel="noopener noreferrer">
<Button className="flex items-center justify-center bg-cobalt rounded hover:bg-copper rounded-xl">
<Image
src="/images/MedCost_deepcove.svg"
alt="MedCost Logo"
width={150}
height={150}
priority
/>
</Button>
</Link>
}
</div>
</div>
);
};
export default FindProviderWidget;
+57
View File
@@ -0,0 +1,57 @@
"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;
+48
View File
@@ -0,0 +1,48 @@
"use client";
import classnames from 'classnames';
import Button from '@/components/Button';
import { FaRegIdCard } from "react-icons/fa";
import Image from 'next/image';
import { Schibsted_Grotesk } from 'next/font/google'
import { useEffect, useState } from 'react';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
import Link from 'next/link';
const FindRxInformationWidget = () => {
return (
<div className="2xl:min-w-[32vw] xl:min-w-30vw] 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">
<div className="flex items-end mb-3">
<div className="text-platinum font-semibold">Find Rx Information</div>
</div>
<Link href="https://www.fairosrx.com/" target="_blank" rel="noopener noreferrer" className='group mb-3'>
<Button className="block group-hover:hidden items-center justify-center bg-platinum rounded rounded-xl p-1">
<Image
src="/images/Fairosrx.svg"
alt="FairosRx Logo"
width={300}
height={150}
priority
/>
</Button>
<Button className="hidden group-hover:block items-center justify-center bg-platinum rounded rounded-xl p-1">
<Image
src="/images/Fairosrx_inverted.svg"
alt="Inverted FairosRx Logo"
width={300}
height={150}
priority
/>
</Button>
</Link>
</div>
</div>
);
};
export default FindRxInformationWidget;
+29 -30
View File
@@ -1,4 +1,3 @@
"use client";
import Image from 'next/image';
import Link from 'next/link';
import { BsLinkedin } from "react-icons/bs";
@@ -11,8 +10,8 @@ const headerFont = Fraunces({ subsets: ['latin'] })
const Footer = () => {
return (
<>
<footer className="m-h-[135vh] bg-britton-dark flex justify-center py-30">
<div className={classnames("w-11/12 md:flex md:items-center md:justify-evenly text-britton-neutral", font.className)}>
<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='w-1/2 h-full flex flex-col justify-between'>
<div className='w-full flex justify-start'>
<Image
@@ -25,15 +24,15 @@ const Footer = () => {
<div className='w-full flex flex-col justify-start ml-[22px]'>
<div className='flex'>
<Link href="https://www.linkedin.com/company/discovery-insurance">
<BsLinkedin size={32} className='hover:text-britton-newblue' />
<BsLinkedin size={32} className='hover:text-atmosphere' />
</Link>
</div>
<ul className="flex flex-col pt-2 text-md font-semibold">
<li className='pt-1'>
<a href="#" className="hover:text-britton-newblue">Terms of Service</a>
<a href="#" className="hover:text-atmosphere">Terms of Service</a>
</li>
<li className='pt-1'>
<a href="#" className="hover:text-britton-newblue">Website Privacy Statement</a>
<a href="#" className="hover:text-atmosphere">Website Privacy Statement</a>
</li>
<li className='pt-3 font-normal text-xs'>
© Copyright 2025 Britton Benefit Services, LLC All Rights Reserved
@@ -44,82 +43,82 @@ const Footer = () => {
</div>
</div>
<div className='w-1/4 h-full flex flex-col justify-start pt-5'>
<div className={classnames("text-xl text-britton-light pb-12", headerFont.className)}>
<div className={classnames("text-xl text-bluemana pb-12", headerFont.className)}>
<div className="pb-6">
<a href="#" className="hover:text-britton-accent">Log In?</a>
<a href="#" className="hover:text-bronze">Log In?</a>
</div>
<div className="text-xl border-b border-britton-accent 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>
<ul className="pt-3 text-md font-medium">
<li className="text-britton-accent pb-4">
<li className="text-bronze pb-4">
Members
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Members Landing</a>
<a href="#" className='hover:text-bluemana'>Members Landing</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Find a Medical Provider</a>
<a href="#" className='hover:text-bluemana'>Find a Medical Provider</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Obtain Rx Information</a>
<a href="#" className='hover:text-bluemana'>Obtain Rx Information</a>
</li>
</ul>
<ul className="pt-7 text-md font-medium">
<li className="text-britton-accent pb-4">
<li className="text-bronze pb-4">
Employers
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Employers Landing</a>
<a href="#" className='hover:text-bluemana'>Employers Landing</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Self-Funded Plans</a>
<a href="#" className='hover:text-bluemana'>Self-Funded Plans</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Employer Services</a>
<a href="#" className='hover:text-bluemana'>Employer Services</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Control Health Care Costs</a>
<a href="#" className='hover:text-bluemana'>Control Health Care Costs</a>
</li>
</ul>
<ul className="pt-7 text-md font-medium">
<li className="text-britton-accent mb-4">
<li className="text-bronze mb-4">
Providers
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Providers Landing</a>
<a href="#" className='hover:text-bluemana'>Providers Landing</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Eligibility Lookup</a>
<a href="#" className='hover:text-bluemana'>Eligibility Lookup</a>
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Contact BBS</a>
<a href="#" className='hover:text-bluemana'>Contact BBS</a>
</li>
</ul>
<ul className="pt-7 text-md font-medium">
<li className="text-britton-accent mb-4">
<li className="text-bronze mb-4">
Brokers
</li>
<li className='pt-2'>
<a href="#" className='hover:text-britton-light'>Brokers Landing</a>
<a href="#" className='hover:text-bluemana'>Brokers Landing</a>
</li>
</ul>
</div>
<div className='w-1/4 h-full flex flex-col justify-start pt-5'>
<div className={classnames("text-xl text-britton-light pb-12", headerFont.className)}>
<div className={classnames("text-xl text-bluemana pb-12", headerFont.className)}>
<div className="pb-6">
<a href="#" className="hover:text-britton-accent">Get a Quote</a>
<a href="#" className="hover:text-bronze">Get a Quote</a>
</div>
<div className="text-xl border-b border-britton-accent 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>
<ul className="pt-3 text-md font-medium">
<li className='pt-3'>
<a href="#" className='hover:text-britton-light' >About</a>
<a href="#" className='hover:text-bluemana' >About</a>
</li>
<li className='pt-6'>
<a href="#" className='hover:text-britton-light' >Careers</a>
<a href="#" className='hover:text-bluemana' >Careers</a>
</li>
<li className='pt-6'>
<a href="#" className='hover:text-britton-light' >Contact Us</a>
<a href="#" className='hover:text-bluemana' >Contact Us</a>
</li>
</ul>
</div>
+125
View File
@@ -0,0 +1,125 @@
"use client";
import { useState, useEffect } from "react";
interface IdCardDownloaderProps {
plPlanKey: number;
}
const IdCardDownloader: React.FC<IdCardDownloaderProps> = ({ plPlanKey }) => {
const [loading, setLoading] = useState(false);
const [apiUrl, setapiUrl] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => setapiUrl(data.apiUrl));
}, []);
const handleDownload = async () => {
setLoading(true);
try {
const response = await fetch(`http://${apiUrl}/api/v1/employers/${plPlanKey}/id_cards`, {
method: 'GET',
headers: {
'Accept': 'application/zip',
},
});
if (!response.ok) throw new Error('Failed to download');
const contentDisposition = response.headers.get('Content-Disposition');
console.log("contentDisposition: " + contentDisposition);
let fileName = 'employer_id_cards.zip'; // Default fallback name
// 2. Parse the filename using a regular expression
if (contentDisposition) {
const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
if (filenameMatch && filenameMatch[1]) {
// Remove surrounding quotes if they exist
fileName = filenameMatch[1].replace(/['"]/g, '');
}
}
console.log(fileName)
// 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);
} finally {
setLoading(false);
}
};
return (
<div className="flex flex-col items-center justify-center">
<button
onClick={handleDownload}
disabled={loading}
className="text-platinum underline hover:text-bluemana focus:outline-none"
>
Download Employer Cards
</button>
{(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">
{loading && (
<div className="flex flex-col items-center">
{/* Spinner */}
<svg
className="animate-spin h-20 w-20 text-deepcove mb-2"
xmlns="http://w3.org"
fill="none"
viewBox="0 0 24 24"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-45" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<p className="text-deepcove text-lg font-medium">Generating ID Cards...</p>
</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>
);
}
export default IdCardDownloader;
+150
View File
@@ -0,0 +1,150 @@
"use client";
import { useState, useEffect } from "react";
import Button from "./Button";
import { FaRegIdCard } from "react-icons/fa";
interface IdCardViewerProps {
cardLaout: 'FullPageCard' | 'MobileDisplayCard';
pbEntityKey: number;
}
const IdCardViewer: React.FC<IdCardViewerProps> = ({ cardLaout, pbEntityKey }) => {
const [loading, setLoading] = useState(false);
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [apiUrl, setapiUrl] = useState("");
useEffect(() => {
fetch("/api/backendApiUrl")
.then((res) => res.json())
.then((data) => setapiUrl(data.apiUrl));
}, []);
const handleFetchPdf = async () => {
setLoading(true);
setPdfUrl(null);
try {
const response = await fetch(`http://${apiUrl}/api/v1/members/${pbEntityKey}/id_card/${cardLaout}`, {
method: "GET",
});
if (!response.ok) throw new Error("Failed to fetch PDF");
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
setPdfUrl(url);
} catch (error) {
console.error("Error fetching PDF:", error);
} finally {
setLoading(false);
}
};
const handleClose = () => {
if (pdfUrl) {
URL.revokeObjectURL(pdfUrl);
}
setPdfUrl(null);
};
return (
<div className="2xl:min-w-[16vw] xl:min-w-[15vw] w-full h-full flex lg:flex-col justify-center items-center text-xl space-x-10 lg:space-x-0 border border-6 border-atmosphere rounded-4xl">
<div className="flex flex-col justify-end items-center mb-4">
<div className="flex items-end mb-3">
<div className="text-platinum font-semibold">View Temporary ID Card</div>
</div>
<div className="flex items-end">
<div className="text-deepcove -mb-11 font-semibold z-1">My Card</div>
</div>
<Button
onClick={handleFetchPdf}
disabled={loading}
className="focus:outline-none"
>
<FaRegIdCard size={150} className="text-bluemana hover:text-light-bronze" />
</Button>
</div>
{/* <div className="flex flex-col items-center justify-center">
<Button
onClick={handleFetchPdf}
disabled={loading}
className="font-semibold text-platinum underline hover:text-light-bronze focus:outline-none"
>
View ID Card
</Button> */}
{(loading || pdfUrl) && (
<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">
{loading && (
<div className="flex flex-col items-center">
{/* Spinner */}
<svg
className="animate-spin h-20 w-20 text-deepcove mb-2"
xmlns="http://w3.org"
fill="none"
viewBox="0 0 24 24"
>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-45" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<p className="text-deepcove text-lg font-medium">Generating ID Card...</p>
</div>
)}
{pdfUrl && (
<div className="flex flex-col w-full h-full bg-platinum rounded-lg shadow-xl overflow-hidden flex flex-col">
{/* Top Bar with Close Button */}
<div className="w-full h-14 bg-atmosphere text-deepcove flex justify-between items-center px-6">
<span className="font-bold text-sm truncate">ID Card Preview</span>
<Button className="px-3 py-2 bg-bluetang text-platinum text-sm font-semibold rounded hover:bg-atmosphere hover:text-bluetang border-2 border-bluetang" onClick={handleClose}>
Close
</Button>
{/* <button
onClick={handleClose}
className="px-3 py-2 bg-bluetang text-platinum text-sm font-semibold rounded hover:bg-atmosphere hover:text-bluetang border-2 border-bluetang"
>
Close
</button> */}
</div>
<iframe
src={pdfUrl}
className="w-full h-full"
title="ID Card"
/>
</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>
);
}
export default IdCardViewer;
+19 -18
View File
@@ -1,9 +1,10 @@
"use client";
import Link from 'next/link';
import { Schibsted_Grotesk } from 'next/font/google'
import classnames from 'classnames';
import React from 'react'
import ReactMarkdown from 'react-markdown'
const font = Schibsted_Grotesk({ subsets: ['latin'] })
@@ -12,7 +13,7 @@ interface InfoCardProps {
imageUrl: string;
learnMoreUrl?: string;
invert?: boolean;
children: React.ReactNode;
children: string;
}
const InfoCard: React.FC<InfoCardProps> = (
@@ -29,10 +30,10 @@ const InfoCard: React.FC<InfoCardProps> = (
let imageShadowColor;
if (invert) {
cardBGColor = "bg-britton-dark";
imageShadowColor = "shadow-britton-neutral/70";
cardBGColor = "bg-deepcove";
imageShadowColor = "shadow-platinum/70";
} else {
cardBGColor = "bg-britton-medium";
cardBGColor = "bg-bluetang";
imageShadowColor = "shadow-black/50";
}
@@ -40,14 +41,14 @@ const InfoCard: React.FC<InfoCardProps> = (
<div className='flex m-h-150 justify-center py-10'>
<div className='flex flex-col w-11/12'>
<div className='flex flex-col w-full items-center'>
<div className={classnames('flex rounded-2xl shadow-britton-accent2/90',
<div className={classnames('flex w-full rounded-2xl shadow-platinum/50',
{
"flex-row-reverse relative" : invert,
"shadow-[8px_8px_6px,2px_2px_2px_-1px]" : !invert
})}>
<div className={classnames({
"hidden" : !invert,
"after:absolute after:bottom-0 after:right-0 after:w-full after:h-114 after:rounded-2xl after:shadow-[8px_8px_6px,2px_2px_2px_-1px] after:shadow-britton-accent2/90" : invert,
"after:absolute after:bottom-0 after:right-0 after:w-full after:h-[calc(100%-(var(--spacing)*23.5))] after:rounded-2xl after:shadow-[8px_8px_6px,2px_2px_2px_-1px] after:shadow-platinum/50" : invert,
})}/>
<div className={classnames("flex flex-col w-7/12")}>
<div className={classnames(
@@ -57,17 +58,17 @@ const InfoCard: React.FC<InfoCardProps> = (
}
)}>
<div className={classnames(
"relative w-full pt-8 mb-2 ml-8 border-b-3",
"relative w-full pt-8 mb-2 ml-8 mr-100 border-b-3",
{
"border-britton-newblue rounded-br-2xl" : !invert,
"border-britton-accent mr-100" : invert
"border-atmosphere" : !invert,
"border-platinum" : invert
}
)}>
<div className={classnames(
'absolute bottom-0 left-0 mb-[2px] text-5xl text-left',
{
"text-britton-accent" : !invert,
"text-britton-dark font-semibold" : invert
"text-bronze" : !invert,
"text-deepcove font-semibold" : invert
}
)}>
{title}
@@ -75,14 +76,14 @@ const InfoCard: React.FC<InfoCardProps> = (
</div>
</div>
<div className={classnames(
"relative text-2xl text-left text-britton-neutral font-medium p-8 h-full",
"relative xl:text-xl 2xl:text-2xl text-left text-platinum font-medium p-8 h-full",
cardBGColor, font.className,
{
"rounded-l-2xl rounded-out-rt-2xl" : !invert,
"rounded-r-2xl rounded-out-lt-2xl" : invert,
}
)}>
{children}
<ReactMarkdown>{children}</ReactMarkdown>
</div>
</div>
<div className={classnames(
@@ -98,13 +99,13 @@ const InfoCard: React.FC<InfoCardProps> = (
imageShadowColor
)}>
<img src={imageUrl} alt="Description" className="grayscale rounded-xl bg-cover bg-center" />
<div className="after:absolute after:inset-0 after:bg-britton-medium after:mix-blend-color after:rounded-xl" />
<div className="after:absolute after:inset-0 after:bg-bluetang after:mix-blend-color after:rounded-xl" />
</div>
</div>
</div>
</div>
<div className={classnames(
"w-2/9 relative flex justify-center pb-4 text-center rounded-b-3xl rounded-out-t-2xl shadow-[8px_8px_6px,2px_2px_2px_-1px] shadow-britton-accent2/90",
"w-2/9 relative flex justify-center pb-4 text-center rounded-b-3xl rounded-out-t-2xl shadow-[8px_8px_6px,2px_2px_2px_-1px] shadow-platinum/50",
cardBGColor,
{
"ml-[calc(2/9*100%)]" : !invert,
@@ -113,7 +114,7 @@ const InfoCard: React.FC<InfoCardProps> = (
)}>
<Link href={learnMoreUrl ? learnMoreUrl : '/'}
className={classnames(
"py-1 px-4 rounded text-xl text-semibold border-2 text-britton-accent hover:text-britton-accent2 border-britton-accent hover:border-britton-accent2",
"py-1 px-4 rounded text-xl text-semibold border-2 hover:text-platinum text-bluemana hover:border-platinum border-bluemana",
)}
>
Learn More
+11 -3
View File
@@ -1,12 +1,20 @@
"use client";
import classnames from 'classnames';
interface InlineLinkProps {
children: React.ReactNode;
alt?: boolean;
}
const InlineLink: React.FC<InlineLinkProps> = ({ children }) => {
const InlineLink: React.FC<InlineLinkProps> = ({ children, alt }) => {
return (
<a href="#" className='text-britton-light hover:text-britton-newblue' >{children}</a>
<a href="#" className={classnames(
{
"text-bluemana hover:text-bronze" : !alt,
"text-bluemana hover:text-atmosphere" : alt,
}
)} >
{children}
</a>
);
};
+71
View File
@@ -0,0 +1,71 @@
"use client";
import classnames from 'classnames';
import Button from '@/components/Button';
import { FaRegIdCard } from "react-icons/fa";
import IdCardViewer from '@/components/IdCardViewer';
import { Schibsted_Grotesk } from 'next/font/google'
import IdCardDownloader from './IdCardDownloader';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface InsuranceCardWidgettProps {
pbEntityKey?: number;
plPlanKey?: number;
}
const InsuranceCardWidget: React.FC<InsuranceCardWidgettProps> = ({ pbEntityKey, plPlanKey }) => {
return (
<div className="2xl:min-w-[16vw] xl:min-w-[15vw] w-full h-full flex lg:flex-col justify-center items-center text-md space-x-10 lg:space-x-0 border border-6 border-atmosphere rounded-4xl">
<div className="flex flex-col justify-end items-center">
<div className="flex items-end">
<div className="text-deepcove -mb-11 font-semibold z-1">My Card</div>
</div>
<FaRegIdCard size={150} className="text-bluemana" />
</div>
<div className={classnames("flex w-full items-center space-x-1", font.className)}>
{/* <Button className="hover:text-bronze">
View
</Button> */}
{/* <div className={classnames("flex w-[45%] justify-end")}>
<IdCardViewer
pbEntityKey={pbEntityKey}
cardLaout="FullPageCard"
/>
</div> */}
{/* <div className={classnames("flex w-[10%] justify-center")}>
|
</div> */}
{/* <p>|</p> */}
{/* <div className={classnames("flex w-[45%] justify-start")}>
<IdCardViewer
pbEntityKey={pbEntityKey}
cardLaout="MobileDisplayCard"
/>
</div> */}
{/* <Button className="hover:text-bronze">
Download
</Button> */}
</div>
{pbEntityKey &&
<div className={classnames("flex w-[45%] justify-center")}>
<IdCardViewer
pbEntityKey={pbEntityKey}
cardLaout="FullPageCard"
/>
</div>
}
{plPlanKey &&
<div className={classnames("flex w-full justify-center mb-4")}>
<IdCardDownloader
plPlanKey={plPlanKey}
/>
</div>
}
</div>
);
};
export default InsuranceCardWidget;
+88
View File
@@ -0,0 +1,88 @@
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;
+36
View File
@@ -0,0 +1,36 @@
import classnames from 'classnames';
interface LandingPageHeaderProps {
headerText: string;
}
const LandingPageHeader: React.FC<LandingPageHeaderProps> = ({ headerText }) => {
return (
<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>
);
};
export default LandingPageHeader;
+86
View File
@@ -0,0 +1,86 @@
"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;
+45 -30
View File
@@ -5,7 +5,6 @@ import classnames from 'classnames';
import Link from 'next/link';
import Button from './Button';
import Image from 'next/image';
import Dropdown from "./Dropdown";
export interface MenuItem {
id: string;
@@ -75,14 +74,18 @@ export interface MenuItem {
// },
// ];
const Navbar = () => {
interface NavbarProps {
collapseHeight: number;
}
const Navbar: React.FC<NavbarProps> = ({ collapseHeight }) => {
const [isScrolled, setIsScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => {
const subtractedHeight = window.innerHeight - 80;
if (window.scrollY > subtractedHeight) {
// const subtractedHeight = window.innerHeight - 80;
const subtractedHeight = window.innerHeight - collapseHeight;
if (window.scrollY > collapseHeight) {
setIsScrolled(true);
} else {
setIsScrolled(false);
@@ -97,54 +100,56 @@ const Navbar = () => {
}, []);
return (
<nav className="sticky top-0 left-0 z-10 hidden lg:flex h-[85px] justify-center bg-transparent">
<nav className="fixed top-0 left-0 z-10 hidden md:flex w-full h-[85px] justify-center bg-transparent">
<div className={classnames(
"w-11/12 bg-britton-neutral px-1 text-britton-dark rounded-b-3xl", {
"w-11/12 bg-platinum px-1 text-deepcove rounded-b-3xl", {
"h-13 pb-1 border-b-2 border-x-2 border-black" : isScrolled,
"h-full pb-4" : !isScrolled
}
)}
>
<div className="container mx-auto justify-between items-center flex">
<div className={classnames('rounded-b-2xl bg-britton-dark w-72', {"hidden" : isScrolled})}>
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
<div className="w-full px-3 justify-between items-center flex">
<div className={classnames('rounded-b-2xl bg-deepcove w-72', {"hidden" : isScrolled})}>
<Link href="/">
<Image
src="/images/bbstpa-forsite.png"
alt="Britton Logo"
width={205}
height={60}
/>
</Link>
</div>
<div className={classnames('rounded-b-2xl py-2 mb-1 bg-britton-dark font-semibold text-britton-neutral text-center text-lg uppercase w-72', {"hidden" : !isScrolled})}>
<div className={classnames('rounded-b-2xl py-2 mb-1 bg-deepcove font-semibold text-platinum text-center text-lg uppercase w-72', {"hidden" : !isScrolled})}>
<Link href="/">
Britton Benefit Services
</Link>
</div>
<ul className={classnames("flex space-x-4 font-semibold text-xl", {"pl-2" : isScrolled})}>
<li>
<Link href="/" className="hover:text-britton-light">
Employers
</Link>
</li>
<li>
<Link href="/" className="hover:text-britton-light">
<Link href="/members" className="hover:text-bluemana">
Members
</Link>
</li>
<li>
<Link href="/" className="hover:text-britton-light">
<Link href="/employers" className="hover:text-bluemana">
Employers
</Link>
</li>
<li>
<Link href="/providers" className="hover:text-bluemana">
Providers
</Link>
</li>
<li>
<Link href="/" className="hover:text-britton-light">
<Link href="/brokers" className="hover:text-bluemana">
Brokers
</Link>
</li>
<li>
<Link href="/" className="hover:text-britton-light">
{/* <li>
<Link href="/" className="hover:text-bluemana">
Services
</Link>
</li>
</li> */}
</ul>
{/* <div className={classnames("flex space-x-4 font-semibold text-lg", {"pl-2" : isScrolled})}>
{menuItems.map((item) => {
@@ -152,17 +157,27 @@ const Navbar = () => {
(
<Dropdown item={item} />
) : (
<Link className="hover:text-britton-light" href={item?.route || ""}>
<Link className="hover:text-bluemana" href={item?.route || ""}>
{item.title}
</Link>
);
})}
</div> */}
<div className={classnames("flex space-x-2 font-semibold", {"pt-1" : isScrolled})}>
<Button className="bg-britton-neutral text-britton-accent2 hover:text-britton-accent py-1 px-4 rounded border-1 border-britton-accent2 hover:border-britton-accent">
{/* <Link href="/account/signup">
<Button className="bg-platinum text-copper hover:text-bronze py-1 px-4 rounded border-1 border-copper hover:border-bronze">
Sign Up
</Button>
</Link> */}
<Link href="/account/signin">
<Button className="bg-platinum text-copper hover:text-bronze py-1 px-4 rounded border-1 border-copper hover:border-bronze">
Sign In
</Button>
</Link>
{/* <Button href="" className="bg-platinum text-copper hover:text-bronze py-1 px-4 rounded border-1 border-copper hover:border-bronze">
Sign In
</Button>
<Button className="bg-britton-dark hover:bg-britton-medium text-britton-light py-1 px-4 rounded">
</Button> */}
<Button className="bg-deepcove hover:bg-bluetang text-bluemana py-1 px-4 rounded">
Get a Quote
</Button>
</div>
+48
View File
@@ -0,0 +1,48 @@
import { Schibsted_Grotesk } from 'next/font/google'
import classnames from 'classnames';
import InlineLink from "@/components/InlineLink";
const font = Schibsted_Grotesk({ subsets: ['latin'] })
export interface QuickLink {
display: string;
url: string;
}
interface QuickAccessLinksProps {
imageUrl: string;
quickLinks: QuickLink[];
}
const QuickAccessLinks: React.FC<QuickAccessLinksProps> = ({ imageUrl, quickLinks }) => {
return (
<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>
<ul className={classnames("flex flex-col pl-10 text-2xl leading-12 list-disc list-inside", font.className)}>
{quickLinks.map((quickLink) => (
<li><InlineLink>{quickLink.display}</InlineLink></li>
))}
</ul>
</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>
);
};
export default QuickAccessLinks;
+83
View File
@@ -0,0 +1,83 @@
"use client";
import classnames from 'classnames';
import Button from '@/components/Button';
import { Schibsted_Grotesk } from 'next/font/google'
import { useEffect, useState } from 'react';
import Link from 'next/link';
const font = Schibsted_Grotesk({ subsets: ['latin'] })
interface RecentClaimsWidgetProps {
recentClaims: RecentClaimType[];
}
interface RecentClaimType {
claim_number: number;
visit_date: string;
claim_status: string;
patient_name: string;
provider_name: string;
}
const RecentClaimsWidget: React.FC<RecentClaimsWidgetProps> = ({ recentClaims }) => {
return (
<div className="2xl:min-w-[48vw] xl:min-w-[50vw] flex z-1">
<div className="w-full flex flex-col px-4 py-3 text-platinum 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-sm", 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>
{(recentClaims ?? []).map((mc) => (
<div key={mc.claim_number} className="flex w-full">
<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 justify-end w-4/20">
{mc.claim_status}
</div>
</div>
))}
</div>
<div className="w-full flex justify-end text-platinum font-semibold">
<Button className="mr-2 underline hover:text-light-bronze">
<Link
href="/dashboard/member/claims"
>
View All Claims
</Link>
</Button>
</div>
</div>
</div>
);
};
export default RecentClaimsWidget;
+68
View File
@@ -0,0 +1,68 @@
"use client";
import React, { useState, ComponentType } from 'react';
import classnames from 'classnames';
import DynamicIcon from './DynamicIcon';
import { NavItems } from '@/lib/dashboard';
import { FaArrowCircleLeft, FaArrowCircleRight } from "react-icons/fa";
import { TbCircleArrowLeft, TbCircleArrowRight } from "react-icons/tb";
import { PiBreadFill } from "react-icons/pi";
import Button from './Button';
import { usePathname } from 'next/navigation';
interface SideNavProps {
maxWidth: string;
bgColor: string;
navItems: NavItems[];
}
const SideNav: React.FC<SideNavProps> = (
{
maxWidth,
bgColor,
navItems
}
) => {
const [isSidebarOpen, setIsSidebarOpen] = useState(true);
return (
<div className={classnames(
"relative h-full flex flex-col text-deepcove text-center transition-all duration-500",
bgColor,
{
"w-[6vw] lg:w-[5vw]" : !isSidebarOpen,
"w-[13vw] lg:w-[11vw]" : isSidebarOpen
}
)}>
<div className="h-20" />
{/* <Button
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
className="h-10 flex justify-end items-center pr-2"
>
{isSidebarOpen ? <FaArrowCircleLeft size={30} /> : <FaArrowCircleRight size={30} />}
</Button> */}
<div className="flex flex-col divide-y divide-bluetang">
{navItems.map((navItem, index) => (
<a href={navItem.url} key={index} className={classnames(
"w-full h-20 flex justify-center items-center px-1 text-xs md:text-sm lg:text-base font-semibold hover:text-platinum",
{"bg-bluetang text-platinum" : usePathname() == navItem.url}
)}>
{isSidebarOpen ? (
<>{navItem.title}</>
) : (
<DynamicIcon iconName={navItem.iconName} size={30} />
)}
</a>
))}
</div>
</div>
);
}
export default SideNav;
+49
View File
@@ -0,0 +1,49 @@
"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;
+82
View File
@@ -0,0 +1,82 @@
"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>
);
}
+4 -23
View File
@@ -1,5 +1,3 @@
"use client";
import { textColor, bgColor } from '@/interfaces/common'
interface TaglineProps {
@@ -20,34 +18,17 @@ const Tagline: React.FC<TaglineProps> = (
return (
<>
{/* <div className='h-[calc(100vh-85px)]'>
<div className="w-full h-screen aspect-16/9 absolute top-0 left-0 bg-britton-medium bg-blend-soft-light bg-[url('/images/bg.jpg')] bg-cover bg-position-[center_-4.5rem] rounded-b-3xl">
<div className="flex flex-col justify-center items-center py-20">
<div className="w-11/12 flex justify-start mt-58">
<div className="w-1/2 relative bg-britton-light text-center text-britton-dark text-7xl rounded-l-3xl rounded-t-3xl rounded-out-br-3xl py-3 px-4 ml-110">
{textTop}
</div>
</div>
<div className="w-11/12 flex justify-end">
<div className="w-1/2 relative bg-britton-light text-center text-britton-dark text-7xl rounded-r-3xl rounded-b-3xl rounded-out-tl-3xl py-3 px-4 mr-20">
{textBottom}
</div>
</div>
</div>
</div>
</div> */}
<div className='h-[calc(100vh-85px)] lg:min-h-[640px] xl:min-h-[786px] 2xl:min-h-[1080px] flex'>
<div className="absolute top-0 left-0 w-full aspect-16/9 bg-cover bg-center bg-[url('/images/bg.jpg')] bg-britton-medium bg-blend-soft-light bg-position-[center_-4.5rem] rounded-b-3xl">
<div className="absolute top-0 left-0 w-full aspect-16/9 bg-cover bg-center bg-[url('/images/bg.jpg')] bg-[#223C59] bg-blend-soft-light bg-position-[center_-4.5rem] rounded-b-3xl">
<div className="relative w-full h-full overflow-hidden">
<div className="absolute flex flex-col w-3/7 top-1/2 right-9/40">
<div className="absolute flex flex-col w-100 lg:w-150 xl:w-190 bottom-2/5 left-7/20 text-center text-deepcove text-4xl lg:text-5xl xl:text-7xl">
<div className="flex justify-start">
<div className="w-4/5 relative bg-britton-light text-center text-britton-dark text-7xl rounded-l-3xl rounded-t-3xl rounded-out-br-3xl py-3 px-4">
<div className="w-5/6 lg:w-4/5 relative bg-bluemana rounded-t-3xl rounded-l-3xl rounded-out-br-3xl py-2 lg:py-3 px-4">
{textTop}
</div>
</div>
<div className="flex justify-end">
<div className="w-4/5 relative bg-britton-light text-center text-britton-dark text-7xl rounded-r-3xl rounded-b-3xl rounded-out-tl-3xl py-3 px-4">
<div className="w-5/6 lg:w-4/5 relative bg-bluemana rounded-b-3xl rounded-r-3xl rounded-out-tl-3xl py-2 lg:py-3 px-4">
{textBottom}
</div>
</div>
+30
View File
@@ -0,0 +1,30 @@
import classnames from 'classnames';
interface TextImage50Props {
children: React.ReactNode;
className?: string;
imageUrl: string,
}
const TextImage50: React.FC<TextImage50Props> = ({ children, className, imageUrl }) => {
return (
<div className="w-full flex justify-between py-5">
<div className="w-4/7 flex flex-col justify-start">
{children}
</div>
<div className="w-2/5 flex flex-col justify-start">
<div className="flex justify-center bg-atmosphere rounded-2xl">
<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>
);
};
export default TextImage50;
+2 -2
View File
@@ -1,6 +1,6 @@
export type textColor = "text-britton-dark" | "text-britton-medium" | "text-britton-newblue" | "text-britton-newblue2" | "text-britton-light" | "text-britton-neutral" | "text-britton-accent" | "text-britton-accent2" | "text-britton-electric2";
export type textColor = "text-deepcove" | "text-bluetang" | "text-atmosphere" | "text-bluemana" | "text-platinum" | "text-bronze" | "text-copper";
export type bgColor = "bg-britton-medium-rgb" | "bg-britton-dark" | "bg-britton-medium" | "bg-britton-newblue" | "bg-britton-light" | "bg-britton-neutral" | "bg-britton-accent" | "bg-britton-accent2";
export type bgColor = "bg-deepcove" | "bg-bluetang" | "bg-atmosphere" | "bg-bluemana" | "bg-platinum" | "bg-bronze" | "bg-copper";
export interface brittonRGB {
[key: string]: string;
+2 -2
View File
@@ -1,6 +1,6 @@
declare global {
type textColors = "text-britton-dark" | "text-britton-medium" | "text-britton-light" | "text-britton-neutral" | "text-britton-accent" | "text-britton-accent2";
type bgColors = "bg-britton-dark" | "bg-britton-medium" | "bg-britton-light" | "bg-britton-neutral" | "bg-britton-accent" | "bg-britton-accent2";
type textColors = "text-deepcove" | "text-bluetang" | "text-bluemana" | "text-platinum" | "text-bronze" | "text-copper";
type bgColors = "bg-deepcove" | "bg-bluetang" | "bg-bluemana" | "bg-platinum" | "bg-bronze" | "bg-copper";
}
export {};
+50
View File
@@ -0,0 +1,50 @@
export interface NavItems {
title: string;
url: string;
iconName: string;
}
interface sideNavGroups {
[key: string]: NavItems[]
}
export const sideNavItems: sideNavGroups = {
"member": [
{
title: "Dashboard",
url: "/dashboard/member",
iconName: "LuLayoutDashboard"
},
// {
// title: "My Plan & Benefits",
// url: "/",
// iconName: "LuClipboardList"
// },
// {
// title: "Plan Utilization",
// url: "/",
// iconName: "TbProgress"
// },
{
title: "Claims",
url: "/dashboard/member/claims",
iconName: "BsClipboardCheck"
},
// {
// title: "Providers & Services",
// url: "/",
// iconName: "FaStethoscope"
// },
// {
// title: "Insurance Card",
// url: "/",
// iconName: "FaRegIdCard"
// },
// {
// title: "Benefits Documents",
// url: "/",
// iconName: "IoDocumentsOutline"
// }
]
}
+11
View File
@@ -0,0 +1,11 @@
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
import { registerApolloClient } from "@apollo/client-integration-nextjs";
export const { getClient } = registerApolloClient(() => {
return new ApolloClient({
cache: new InMemoryCache(),
link: new HttpLink({
uri: "http://api:3000/graphql",
}),
});
});
+13
View File
@@ -0,0 +1,13 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const headers = new Headers(request.headers);
headers.set("x-current-path", request.nextUrl.pathname);
return NextResponse.next({ headers });
}
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)" // Exclude API routes, static files, etc.
],
};
-22
View File
@@ -1,22 +0,0 @@
import '../styles/globals.css';
import type { AppProps } from 'next/app';
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) {
return (
<main className={classNames("bg-britton-dark min-h-screen flex justify-center", font.className)}>
<div className="max-w-[1920px]">
<Navbar />
<Component {...pageProps} />
<Footer />
</div>
</main>
);
}
export default MyApp;
+29
View File
@@ -0,0 +1,29 @@
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;
+19 -19
View File
@@ -5,29 +5,29 @@ export default function Home() {
return (
<>
<Tagline
bgColor="bg-britton-light"
textColor="text-britton-dark"
bgColor="bg-bluemana"
textColor="text-deepcove"
textTop="Healthcare that's"
textBottom="truly affordable"
/>
<InfoCard
title="Health & Risk Management"
imageUrl="/images/doctor.png"
parentBgColor="bg-britton-dark"
cardBGColor="bg-britton-medium"
bodyTextColor="text-britton-neutral"
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-britton-newblue w-full h-full rounded-3xl'>
<div className='bg-atmosphere w-full h-full rounded-3xl'>
<InfoCard
title="Self-Funded Plans"
imageUrl='/images/pwn-webi-square.png'
parentBgColor="bg-britton-newblue"
cardBGColor="bg-britton-dark"
bodyTextColor="text-britton-neutral"
parentBgColor="bg-atmosphere"
cardBGColor="bg-deepcove"
bodyTextColor="text-platinum"
learnMoreUrl="https://github.com/orgs/Britton-Benefits/repositories"
invert
>
@@ -37,19 +37,19 @@ export default function Home() {
<InfoCard
title="Benefit Access & Online Support"
imageUrl="/images/doctor.png"
parentBgColor="bg-britton-dark"
cardBGColor="bg-britton-medium"
bodyTextColor="text-britton-neutral"
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-britton-neutral rounded-3xl'>
<div className='bg-platinum rounded-3xl'>
<InfoCard
title="Regulatory Compliance"
imageUrl="/images/pwn-webi-square.png"
parentBgColor="bg-britton-neutral"
cardBGColor="bg-britton-dark"
bodyTextColor="text-britton-neutral"
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.
@@ -68,9 +68,9 @@ export default function Home() {
{/* <div className="flex w-full h-70 justify-between items-center pt-30">
<div className="bg-britton-neutral h-full w-3/12 rounded-3xl"></div>
<div className="bg-britton-medium h-full w-3/12 rounded-3xl"></div>
<div className="bg-britton-light h-full w-3/12 rounded-3xl"></div>
<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> */}
</>
);
+5 -5
View File
@@ -9,21 +9,21 @@ export default function Home() {
return (
<>
<Tagline
bgColor="bg-britton-light"
textColor="text-britton-dark"
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-britton-medium text-center text-britton-dark text-7xl rounded-3xl p-4">
<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-britton-neutral text-center text-britton-dark text-7xl rounded-3xl p-4">
<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-britton-accent text-center text-britton-dark text-7xl rounded-3xl p-4">
<div className="w-11/12 h-4 bg-bronze text-center text-deepcove text-7xl rounded-3xl p-4">
</div>
</div>
</>
-49
View File
@@ -1,49 +0,0 @@
import InfoCard from '../components/InfoCard';
import Tagline from "../components/Tagline";
import InlineLink from "../components/InlineLink";
export default function Home() {
return (
<>
<Tagline
bgColor="bg-britton-light"
textColor="text-britton-dark"
textTop="Healthcare that's"
textBottom="truly affordable"
/>
<InfoCard
title="Members"
imageUrl="/images/members-stock.jpg"
>
Traditional insurance plans require a lot of upfront costs for coverage that you and your employees may never use. Your <InlineLink>insurance provider</InlineLink> 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 <InlineLink>self-funded plans</InlineLink>, 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-britton-newblue rounded-3xl'>
<InfoCard
title="Employers"
imageUrl='/images/employers-stock.jpg'
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 <InlineLink>traditional insurance plans</InlineLink> 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 companys plan, maximizing flexibility and mitigating risk to your company's bottom line.
</InfoCard>
</div>
<InfoCard
title="Providers"
imageUrl="/images/providers-stock.jpg"
>
We offer a wide range of <InlineLink>online services</InlineLink> 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 <InlineLink>in-network providers</InlineLink> 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-britton-neutral rounded-3xl'>
<InfoCard
title="Brokers"
imageUrl="/images/brokers-stock.jpg"
invert
>
At Britton Benefit Services, LLC we are leading the third-party benefits industry by working hard to make <InlineLink>employee benefits</InlineLink> 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>
</>
);
}
+20 -24
View File
@@ -7,30 +7,26 @@
}
@theme {
--color-britton-dark: #04153E;
--color-britton-medium: #2A4B6F;
--color-britton-newblue: #0096E0;
--color-britton-newblue2: #0094de;
--color-britton-light: #6AC8F1;
--color-britton-electric: #00F0FF;
--color-britton-electric2: #7DF9FF;
--color-britton-neutral: #E0E0E0;
--color-britton-accent2: #B06E30;
--color-britton-accent: #D38F4A;
/* for testing */
/* --color-britton-light: #7DF9FF; */
/* --color-britton-neutral: #EEF2E3; */
/* name colors like below AFTER we settle on them for sure */
/* --color-britton-deepcove: #04153E;
--color-britton-bluetang: #2A4B6F;
--color-britton-bluemana: #6AC8F1; */
/* --color-britton-deepcove-background: #04153E;
--color-britton-bluetang-background: #2A4B6F;
--color-britton-bluemana-background: #6AC8F1; */
/* Add more custom colors as needed */
/* dark blue */
--color-deepcove: #04153E;
/* medium dark blue */
--color-bluetang: #2A4B6F;
/* bright blue */
--color-atmosphere: #0096E0;
/* light blue */
--color-bluemana: #6AC8F1;
/* platinum */
--color-platinum: #E0E0E0;
/* copper */
--color-copper: #B06E30;
/* bronze */
--color-bronze: #D38F4A;
/* bronze */
--color-light-bronze: #CD7F32;
/* cobalt blue */
--color-cobalt: #0047AB;
/* oxidized copper/bronze green-blue */
--color-verdigris: #618D94;
}
@theme inline {
+20 -19
View File
@@ -1,19 +1,20 @@
module.exports = {
theme: {
extend: {
colors: {
'britton-dark-rgb': 'rgb(var(--color-britton-dark))',
'britton-medium-rgb': 'rgb(var(--color-britton-medium))',
'britton-light-rgb': 'rgb(var(--color-britton-light))',
'britton-electric-rgb': 'rgb(var(--color-britton-electric))',
'britton-electric2-rgb': 'rgb(var(--color-britton-electric2))',
'britton-neutral-rgb': 'rgb(var(--color-britton-neutral))',
'britton-accent2-rgb': 'rgb(var(--color-britton-accent2))',
'britton-accent-rgb': 'rgb(var(--color-britton-accent))',
},
boxShadow: {
'britton': '10px 10px 5px 0px'
}
},
},
};
// import fluid, { extract, screens, fontSize } from 'fluid-tailwind'
// module.exports = {
// // Define content paths for Tailwind CSS to scan for classes
// content: {
// files: [
// './src/components/**/*.{tsx}',
// './src/app/**/*.{tsx}',
// ],
// extract, // Use the extract function from fluid-tailwind
// },
// theme: {
// extend: {},
// screens, // Integrate fluid-tailwind's screens for responsive breakpoints
// fontSize, // Integrate fluid-tailwind's font sizes for fluid typography
// },
// plugins: [
// fluid, // Include the fluid-tailwind plugin
// ],
// };
+17 -4
View File
@@ -1,7 +1,11 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -19,9 +23,18 @@
}
],
"paths": {
"@/*": ["./src/*"]
"@/*": [
"./src/*"
]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}