Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Build and Deploy

on:
push:
branches: ["develop"]
pull_request:
branches: ["develop"]

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22.x'
cache: 'npm' # Enable caching for faster builds

- name: Install dependencies
run: npm ci # Use 'ci' instead of 'install' for reproducible builds

- name: Build
run: npm run build

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist' # Change to your build output directory

deploy:
# Only deploy on push to develop, not on PRs
if: github.event_name == 'push' && github.ref == 'refs/heads/develop'

environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}

runs-on: ubuntu-latest
needs: build

steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
11 changes: 7 additions & 4 deletions src/apis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import {
ConsentRequest,
} from "@/types/consent";
import { queryString, request } from "./request";
import { PartialPatient } from "@/types/patient";

import { AbhaNumber } from "@/types/abhaNumber";
import { GovtOrganization } from "@/types/govtOrganization";
import { HealthFacility } from "@/types/healthFacility";
import { HealthInformation } from "@/types/healthInformation";
import { PaginatedResponse } from "./types";
import { Patient } from "@/types/patient";
import { User } from "@/types/user";

// FIXME: Move all the api specific types to a ./types.ts file
Expand Down Expand Up @@ -417,9 +417,12 @@ export const apis = {
},

hip: {
getPatientByToken: async (token: string) => {
return await request<PartialPatient>(
`/api/abdm/v3/hip/patient/fetch-by-token/${token}/`
getPatientByToken: async (query: {
token: number;
facility_id: string;
}) => {
return await request<Patient>(
`/api/abdm/v3/hip/patient/fetch-by-token/` + queryString(query)
);
},
},
Expand Down
5 changes: 3 additions & 2 deletions src/components/LinkAbhaNumber/CreateWithAadhaar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { AbhaNumber } from "@/types/abhaNumber";
import { Checkbox } from "@/components/ui/checkbox";
import { Input } from "@/components/ui/input";
import { QRCodeSVG } from "qrcode.react";
import { Textarea } from "@/components/ui/textarea";
import { apis } from "@/apis";
import { cn } from "@/lib/utils";
import { faceAuthUrl } from "@/config";
import { toast } from "@/lib/utils";
import { useForm } from "react-hook-form";
import { useLinkAbhaNumberContext } from ".";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
import { QRCodeSVG } from "qrcode.react";

type CreateWithAadhaarProps = {
onSuccess: (abhaNumber: AbhaNumber) => void;
Expand Down Expand Up @@ -1045,7 +1046,7 @@ const VerifyAadhaarWithFace: FC<VerifyAadhaarWithFaceProps> = ({
{authInitViaFaceMutation.isSuccess && (
<div className="flex flex-col items-center justify-center border-2 border-dashed border-secondary-600 rounded-lg p-4">
<QRCodeSVG
value={`https://phrsbx.abdm.gov.in/face-auth?txnId=${authInitViaFaceMutation.data?.transaction_id}`}
value={`${faceAuthUrl}?txnId=${authInitViaFaceMutation.data?.transaction_id}`}
className="size-80 text-secondary-500 rounded-lg"
/>
</div>
Expand Down
55 changes: 40 additions & 15 deletions src/components/LinkAbhaNumber/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Button, ButtonProps } from "@/components/ui/button";
import { Button, ButtonProps, buttonVariants } from "@/components/ui/button";
import { ChevronLeftIcon, IdCardIcon } from "lucide-react";
import {
Drawer,
DrawerContent,
Expand All @@ -7,24 +8,25 @@ import {
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { FC, createContext, useContext, useState } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { createContext, FC, useState, useContext } from "react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";

import { AbhaNumber } from "@/types/abhaNumber";
import { CreateWithAadhaar } from "./CreateWithAadhaar";
import { IdCardIcon } from "lucide-react";
import { HealthFacility } from "@/types/healthFacility";
import { Link } from "raviger";
import { LinkWithOtp } from "./LinkWithOtp";
import { ScrollArea } from "@/components/ui/scroll-area";
import { User } from "@/types/user";
import { apis } from "@/apis";
import { cn } from "@/lib/utils";
import { useQuery } from "@tanstack/react-query";
import { HealthFacility } from "@/types/healthFacility";
import { User } from "@/types/user";

type LinkAbhaNumberContextValue = {
healthFacility?: HealthFacility;
Expand All @@ -34,6 +36,8 @@ type LinkAbhaNumberContextValue = {
const LinkAbhaNumberContext = createContext<LinkAbhaNumberContextValue>({});

type LinkAbhaNumberProps = ButtonProps & {
enforceLinking?: boolean;
backUrl?: string;
facilityId?: string;
onSuccess: (abhaNumber: AbhaNumber) => void;
defaultMode?: "new" | "existing";
Expand All @@ -42,10 +46,12 @@ type LinkAbhaNumberProps = ButtonProps & {
export const LinkAbhaNumber: FC<LinkAbhaNumberProps> = ({
facilityId,
onSuccess,
defaultMode = "new",
backUrl,
enforceLinking = false,
defaultMode = "existing",
...props
}) => {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [isDrawerOpen, setIsDrawerOpen] = useState(enforceLinking);

const handleOnSuccess = (abhaNumber: AbhaNumber) => {
setIsDrawerOpen(false);
Expand All @@ -70,7 +76,11 @@ export const LinkAbhaNumber: FC<LinkAbhaNumberProps> = ({
currentUser,
}}
>
<Drawer open={isDrawerOpen} onOpenChange={setIsDrawerOpen}>
<Drawer
dismissible={!enforceLinking}
open={isDrawerOpen}
onOpenChange={setIsDrawerOpen}
>
<DrawerTrigger disabled={!healthFacility} className="abdm-container">
<TooltipProvider>
<Tooltip>
Expand Down Expand Up @@ -105,12 +115,27 @@ export const LinkAbhaNumber: FC<LinkAbhaNumberProps> = ({
<DrawerContent className="abdm-container">
<ScrollArea className="h-[90vh]">
<div className="md:mx-auto max-w-screen md:max-w-md max-md:p-4">
<DrawerHeader>
<DrawerTitle>Generate/Link ABHA Number</DrawerTitle>
<DrawerDescription>
Generate/link patient's ABHA details for easy access to
healthcare services.
</DrawerDescription>
<DrawerHeader className="flex justify-between items-center max-sm:flex-col">
<div className="flex-1">
<DrawerTitle>Generate/Link ABHA Number</DrawerTitle>
<DrawerDescription>
Generate/link patient's ABHA details for easy access to
healthcare services.
</DrawerDescription>
</div>

{backUrl && (
<Link
href={backUrl}
className={cn(
buttonVariants({ variant: "outline" }),
"max-sm:w-full"
)}
>
<ChevronLeftIcon className="h-4 w-4" />
Go back
</Link>
)}
</DrawerHeader>
<Tabs defaultValue={defaultMode} orientation="vertical">
<TabsList className="w-full sticky top-0 bg-gray-200 z-10 py-6">
Expand Down
93 changes: 44 additions & 49 deletions src/components/TokenSearchDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,40 @@
import { FC, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { apis } from "@/apis";
import { PartialPatient } from "@/types/patient";
import { useDebouncedState } from "@/hooks/useDebouncedState";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
DialogDescription,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { FC, useMemo, useState } from "react";

import { ArrowRightIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Patient } from "@/types/patient";
import { Skeleton } from "@/components/ui/skeleton";
import { Link } from "raviger";
import { ArrowRightIcon } from "lucide-react";
import { apis } from "@/apis";
import { navigate } from "raviger";
import { useDebouncedState } from "@/hooks/useDebouncedState";
import { useQuery } from "@tanstack/react-query";

type TokenSearchDialogProps = {
facilityId: string;
trigger?: React.ReactNode;
};

const TokenSearchDialog: FC<TokenSearchDialogProps> = ({ trigger }) => {
const TokenSearchDialog: FC<TokenSearchDialogProps> = ({
trigger,
facilityId,
}) => {
const [open, setOpen] = useState(false);
const [token, setToken] = useState("");
const [token, setToken] = useState<number | undefined>();
const debouncedToken = useDebouncedState(token, 300);

const isEnabled = useMemo(
() => debouncedToken.trim().length > 0,
() =>
debouncedToken !== undefined &&
debouncedToken.toString().trim().length > 0,
[debouncedToken]
);

Expand All @@ -39,8 +45,12 @@ const TokenSearchDialog: FC<TokenSearchDialogProps> = ({ trigger }) => {
error,
} = useQuery({
queryKey: ["patient-by-token", debouncedToken],
queryFn: () => apis.hip.getPatientByToken(debouncedToken.trim()),
enabled: isEnabled && open,
queryFn: () =>
apis.hip.getPatientByToken({
token: debouncedToken as number,
facility_id: facilityId,
}),
enabled: isEnabled && open && !!facilityId,
retry: false,
});

Expand All @@ -62,7 +72,9 @@ const TokenSearchDialog: FC<TokenSearchDialogProps> = ({ trigger }) => {
<div className="flex items-center justify-center">
<Input
value={token}
onChange={(e) => setToken(e.target.value.replace(/\D/g, ""))}
onChange={(e) =>
setToken(Number(e.target.value.replace(/\D/g, "")) || undefined)
}
inputMode="numeric"
pattern="[0-9]*"
maxLength={6}
Expand Down Expand Up @@ -95,7 +107,7 @@ const TokenSearchDialog: FC<TokenSearchDialogProps> = ({ trigger }) => {
)}

{!isFetching && isEnabled && patient && (
<PartialPatientCard patient={patient} />
<PatientCard patient={patient} />
)}
</div>
</div>
Expand All @@ -104,11 +116,24 @@ const TokenSearchDialog: FC<TokenSearchDialogProps> = ({ trigger }) => {
);
};

const PartialPatientCard: FC<{ patient: PartialPatient }> = ({ patient }) => {
const [yearOfBirth, setYearOfBirth] = useState("");
const PatientCard: FC<{ patient: Patient }> = ({ patient }) => {
const yearOfBirth = useMemo(() => {
return patient.year_of_birth ?? patient.date_of_birth?.split("-")[0];
}, [patient]);

return (
<div className="border rounded-md p-4">
<div
onClick={() => {
navigate("patients/verify", {
query: {
phone_number: patient.phone_number,
year_of_birth: yearOfBirth,
partial_id: patient.partial_id || patient.id.slice(0, 5),
},
});
}}
className="border rounded-md p-4 cursor-pointer"
>
<div className="flex items-center justify-between">
<div className="space-y-1">
<div className="font-medium text-base">{patient.name}</div>
Expand All @@ -120,36 +145,6 @@ const PartialPatientCard: FC<{ patient: PartialPatient }> = ({ patient }) => {
#{patient.id.slice(0, 8)}
</div>
</div>

<div className="mt-3 flex items-center gap-3">
<Input
placeholder="Enter the year of birth to verify"
inputMode="numeric"
pattern="[0-9]*"
value={yearOfBirth}
onChange={(e) => setYearOfBirth(e.target.value)}
minLength={4}
maxLength={4}
className="flex-1"
/>
<Button
size="icon"
variant="outline"
aria-label="Open patient page"
disabled={yearOfBirth.length !== 4}
onClick={() => {
navigate("patients/verify", {
query: {
phone_number: patient.phone_number,
year_of_birth: yearOfBirth,
partial_id: patient.partial_id || patient.id.slice(0, 5),
},
});
}}
>
<ArrowRightIcon />
</Button>
</div>
</div>
);
};
Expand Down
Loading