diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..d44b827 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -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 diff --git a/src/apis/index.ts b/src/apis/index.ts index edf5dee..132dfca 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -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 { PartialPatient } from "@/types/patient"; import { User } from "@/types/user"; // FIXME: Move all the api specific types to a ./types.ts file @@ -417,9 +417,12 @@ export const apis = { }, hip: { - getPatientByToken: async (token: string) => { + getPatientByToken: async (query: { + token: number; + facility_id: string; + }) => { return await request( - `/api/abdm/v3/hip/patient/fetch-by-token/${token}/` + `/api/abdm/v3/hip/patient/fetch-by-token/` + queryString(query) ); }, }, diff --git a/src/components/LinkAbhaNumber/CreateWithAadhaar.tsx b/src/components/LinkAbhaNumber/CreateWithAadhaar.tsx index c892a0a..17bf907 100644 --- a/src/components/LinkAbhaNumber/CreateWithAadhaar.tsx +++ b/src/components/LinkAbhaNumber/CreateWithAadhaar.tsx @@ -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; @@ -1045,7 +1046,7 @@ const VerifyAadhaarWithFace: FC = ({ {authInitViaFaceMutation.isSuccess && (
diff --git a/src/components/LinkAbhaNumber/index.tsx b/src/components/LinkAbhaNumber/index.tsx index e6038f5..f6cccb3 100644 --- a/src/components/LinkAbhaNumber/index.tsx +++ b/src/components/LinkAbhaNumber/index.tsx @@ -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, @@ -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; @@ -34,6 +36,8 @@ type LinkAbhaNumberContextValue = { const LinkAbhaNumberContext = createContext({}); type LinkAbhaNumberProps = ButtonProps & { + enforceLinking?: boolean; + backUrl?: string; facilityId?: string; onSuccess: (abhaNumber: AbhaNumber) => void; defaultMode?: "new" | "existing"; @@ -42,10 +46,12 @@ type LinkAbhaNumberProps = ButtonProps & { export const LinkAbhaNumber: FC = ({ facilityId, onSuccess, + backUrl, + enforceLinking = false, defaultMode = "new", ...props }) => { - const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [isDrawerOpen, setIsDrawerOpen] = useState(enforceLinking); const handleOnSuccess = (abhaNumber: AbhaNumber) => { setIsDrawerOpen(false); @@ -70,7 +76,11 @@ export const LinkAbhaNumber: FC = ({ currentUser, }} > - + @@ -105,12 +115,27 @@ export const LinkAbhaNumber: FC = ({
- - Generate/Link ABHA Number - - Generate/link patient's ABHA details for easy access to - healthcare services. - + +
+ Generate/Link ABHA Number + + Generate/link patient's ABHA details for easy access to + healthcare services. + +
+ + {backUrl && ( + + + Go back + + )}
diff --git a/src/components/TokenSearchDialog.tsx b/src/components/TokenSearchDialog.tsx index 7c6fec4..714da72 100644 --- a/src/components/TokenSearchDialog.tsx +++ b/src/components/TokenSearchDialog.tsx @@ -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 { PartialPatient } 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 = ({ trigger }) => { +const TokenSearchDialog: FC = ({ + trigger, + facilityId, +}) => { const [open, setOpen] = useState(false); - const [token, setToken] = useState(""); + const [token, setToken] = useState(); const debouncedToken = useDebouncedState(token, 300); const isEnabled = useMemo( - () => debouncedToken.trim().length > 0, + () => + debouncedToken !== undefined && + debouncedToken.toString().trim().length > 0, [debouncedToken] ); @@ -39,8 +45,12 @@ const TokenSearchDialog: FC = ({ 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, }); @@ -62,7 +72,9 @@ const TokenSearchDialog: FC = ({ trigger }) => {
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} diff --git a/src/components/pluggables/PatientRegistrationForm.tsx b/src/components/pluggables/PatientRegistrationForm.tsx index 49374b4..8bb707c 100644 --- a/src/components/pluggables/PatientRegistrationForm.tsx +++ b/src/components/pluggables/PatientRegistrationForm.tsx @@ -7,6 +7,7 @@ import { LinkAbhaNumber } from "../LinkAbhaNumber"; import { ShowAbhaProfile } from "../LinkAbhaNumber/ShowAbhaProfile"; import { UseFormReturn } from "react-hook-form"; import { apis } from "@/apis"; +import { enforceAbhaNumberLinking } from "@/config"; import { toast } from "@/lib/utils"; type PatientRegistrationFormProps = { @@ -56,8 +57,11 @@ const PatientRegistrationForm: FC = ({ if ( isFirstSuccess && mutation?.state.status === "success" && - mutation?.options.mutationKey?.includes("create_patient") && - mutation?.state.data?.id + (mutation?.options.mutationKey?.includes("create_patient") || + mutation?.options.mutationKey?.includes("update_patient")) && + mutation?.state.data?.id && + form.watch("abha_id") && + !abhaNumber?.patient ) { isFirstSuccess = false; @@ -85,6 +89,12 @@ const PatientRegistrationForm: FC = ({ return (
= ({ return (
diff --git a/src/config.ts b/src/config.ts index db8efed..f0405c2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1 +1,7 @@ export const scanAndShareUrl = import.meta.env.REACT_SCAN_AND_SHARE_URL; + +export const faceAuthUrl = + import.meta.env.REACT_FACE_AUTH_URL || "https://phrsbx.abdm.gov.in/face-auth"; + +export const enforceAbhaNumberLinking = + import.meta.env.REACT_ENFORCE_ABHA_NUMBER_LINKING === "true"; \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index ce24dfe..5af4fa2 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -3,6 +3,8 @@ interface ImportMetaEnv { readonly REACT_SCAN_AND_SHARE_URL: string; + readonly REACT_FACE_AUTH_URL: string; + readonly REACT_ENFORCE_ABHA_NUMBER_LINKING: string; } declare global { diff --git a/vite.config.mjs b/vite.config.mjs index 2a49bfe..f98f958 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -18,6 +18,7 @@ export default defineConfig({ "react-i18next", "@tanstack/react-query", "raviger", + "sonner", ], }), tailwindcss(),