I’m working on a project using the Full Stack FastAPI Template. The technology stack includes:
- FastAPI for the backend with SQLModel for ORM, Pydantic for data
validation, and PostgreSQL as the database. - React for the frontend using TypeScript, Vite, and Chakra UI.
- Docker Compose for development and production.
- Other features like JWT authentication, email-based password
recovery, CI/CD with GitHub Actions, and more.
Problem:
When I click on the “New Signature Request” link in the sidebar, I get a 404 Oops! Page not found.
error with no sidebar or other elements displayed. However, other sidebar links work correctly and load their respective pages.
Here is the relevant code:
Route Tree Configuration (frontend/src/routeTree.gen.ts
):
import { Route as rootRoute } from './routes/__root'
import { Route as ResetPasswordImport } from './routes/reset-password'
import { Route as RecoverPasswordImport } from './routes/recover-password'
import { Route as LoginImport } from './routes/login'
import { Route as LayoutImport } from './routes/_layout'
import { Route as LayoutIndexImport } from './routes/_layout/index'
import { Route as LayoutSettingsImport } from './routes/_layout/settings'
import { Route as LayoutItemsImport } from './routes/_layout/items'
import { Route as LayoutDocumentsImport } from './routes/_layout/documents'
import { Route as LayoutSignatureRequestsImport } from './routes/_layout/signature_requests'
import { Route as LayoutCreateRouteImport } from './routes/_layout/create_signature_request'
import { Route as LayoutAdminImport } from './routes/_layout/admin'
const ResetPasswordRoute = ResetPasswordImport.update({ path: '/reset-password', getParentRoute: () => rootRoute })
const RecoverPasswordRoute = RecoverPasswordImport.update({ path: '/recover-password', getParentRoute: () => rootRoute })
const LoginRoute = LoginImport.update({ path: '/login', getParentRoute: () => rootRoute })
const LayoutRoute = LayoutImport.update({ id: '/_layout', getParentRoute: () => rootRoute })
const LayoutIndexRoute = LayoutIndexImport.update({ path: '/', getParentRoute: () => LayoutRoute })
const LayoutSettingsRoute = LayoutSettingsImport.update({ path: '/settings', getParentRoute: () => LayoutRoute })
const LayoutItemsRoute = LayoutItemsImport.update({ path: '/items', getParentRoute: () => LayoutRoute })
const LayoutDocumentsRoute = LayoutDocumentsImport.update({ path: '/documents', getParentRoute: () => LayoutRoute })
const LayoutSignatureRequestsRoute = LayoutSignatureRequestsImport.update({ path: '/signature_requests', getParentRoute: () => LayoutRoute })
const LayoutCreateSignatureRequestRoute = LayoutCreateRouteImport.update({ path: '/new_signature_requests', getParentRoute: () => LayoutRoute })
const LayoutAdminRoute = LayoutAdminImport.update({ path: '/admin', getParentRoute: () => LayoutRoute })
export const routeTree = rootRoute.addChildren([
LayoutRoute.addChildren([
LayoutAdminRoute,
LayoutDocumentsRoute,
LayoutSignatureRequestsRoute,
LayoutCreateSignatureRequestRoute,
LayoutItemsRoute,
LayoutSettingsRoute,
LayoutIndexRoute,
]),
LoginRoute,
RecoverPasswordRoute,
ResetPasswordRoute,
])
Sidebar Configuration (frontend/src/components/Common/SidebarItems.tsx
):
import { Box, Flex, Icon, Text, useColorModeValue } from "@chakra-ui/react";
import { Link } from "@tanstack/react-router";
import React from "react";
import { FiBriefcase, FiFileText, FiHome, FiPlus, FiSettings, FiUsers } from "react-icons/fi";
import { useQueryClient } from "react-query";
import type { UserOut } from "../../client";
const items = [
{ icon: FiHome, title: "Dashboard", path: "/" },
{ icon: FiBriefcase, title: "Items", path: "/items" },
{ icon: FiFileText, title: "Documents", path: "/documents" },
{ icon: FiFileText, title: "Signature Requests", path: "/signature_requests" },
{ icon: FiPlus, title: "New Signature Request", path: "/new_signature_requests" },
{ icon: FiSettings, title: "User Settings", path: "/settings" },
];
interface SidebarItemsProps {
onClose?: () => void;
}
const SidebarItems: React.FC<SidebarItemsProps> = ({ onClose }) => {
const queryClient = useQueryClient();
const textColor = useColorModeValue("ui.main", "ui.white");
const bgActive = useColorModeValue("#E2E8F0", "#4A5568");
const currentUser = queryClient.getQueryData<UserOut>("currentUser");
const finalItems = currentUser?.is_superuser ? [...items, { icon: FiUsers, title: "Admin", path: "/admin" }] : items;
const listItems = finalItems.map((item) => (
<Flex
as={Link}
to={item.path}
w="100%"
p={2}
key={item.title}
activeProps={{ style: { background: bgActive, borderRadius: "12px" } }}
color={textColor}
onClick={onClose}
>
<Icon as={item.icon} alignSelf="center" />
<Text ml={2}>{item.title}</Text>
</Flex>
));
return <Box>{listItems}</Box>;
};
export default SidebarItems;
Create Signature Request Route (frontend/src/routes/_layout/create_signature_request.tsx
):
import { createRoute } from "@tanstack/react-router";
import CreateSignatureRequestWizard from "../../components/SignatureRequests/CreateSignatureRequestWizard";
import { Route as LayoutRoute } from "../_layout";
export const Route = createRoute({
getParentRoute: () => LayoutRoute,
path: "/new_signature_requests",
component: CreateSignatureRequestWizard,
});
Create Signature Request Wizard Component (frontend/src/components/SignatureRequests/CreateSignatureRequestWizard.tsx
):
import React, { useState } from "react";
import { Box, Button, Container, Flex, Heading, Input, Select, VStack, Checkbox, Textarea, useToast } from "@chakra-ui/react";
import { SignatureRequestCreate } from "../../client/models/SignatureRequestCreate";
import { initiateSignatureRequest } from "../../client/services/SignatureRequestsService";
import StepIndicator from "../Common/StepIndicator";
import DocumentSelect from "../Common/DocumentSelect";
import SignatoryForm from "../Common/SignatoryForm";
import FieldForm from "../Common/FieldForm";
const initialRequest: SignatureRequestCreate = {
name: "",
delivery_mode: "",
ordered_signers: false,
reminder_settings: { interval_in_days: 0, max_occurrences: 0, timezone: "" },
expiration_date: "",
message: "",
expiry_date: "",
signatories: [],
documents: [],
};
const CreateSignatureRequestWizard = () => {
const [step, setStep] = useState(0);
const [signatureRequest, setSignatureRequest] = useState<SignatureRequestCreate>(initialRequest);
const toast = useToast();
const handleNext = () => setStep((prev) => prev + 1);
const handlePrev = () => setStep((prev) => prev - 1);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
const { name, value, type } = e.target;
if (type === "checkbox") {
setSignatureRequest((prev) => ({ ...prev, [name]: (e.target as HTMLInputElement).checked }));
} else {
setSignatureRequest((prev) => ({ ...prev, [name]: value }));
}
};
const handleDocumentSelect = (documents: number[]) => {
setSignatureRequest((prev) => ({ ...prev, documents }));
};
const handleSignatoriesChange = (signatories: SignatureRequestCreate["signatories"]) => {
setSignatureRequest((prev) => ({ ...prev, signatories }));
};
const handleFieldsChange = (fields: SignatureRequestCreate["signatories"][0]["fields"]) => {
const updatedSignatories = [...signatureRequest.signatories];
if (updatedSignatories.length > 0) {
updatedSignatories[0].fields = fields;
setSignatureRequest((prev) => ({ ...prev, signatories: updatedSignatories }));
}
};
const handleSubmit = async () => {
try {
await initiateSignatureRequest(signatureRequest);
toast({
title: "Signature request created.",
description: "Your signature request has been created successfully.",
status: "success",
duration: 5000,
isClosable: true,
});
} catch (error) {
toast({
title: "Error creating signature request.",
description: "An error occurred while creating the signature request.",
status: "error",
duration: 5000,
isClosable: true,
});
}
};
return (
<Container maxW="container.md">
<Heading as="h1" mb={4}>Create Signature Request</Heading>
<StepIndicator currentStep={step} steps={["Document", "Signatories", "Fields", "Review"]} />
<Box my={4}>
{step === 0 && <DocumentSelect selectedDocuments={signatureRequest.documents} onSelect={handleDocumentSelect} />}
{step === 1 && <SignatoryForm signatories={signatureRequest.signatories} onChange={handleSignatoriesChange} />}
{step === 2 && <FieldForm fields={signatureRequest.signatories[0]?.fields || []} onChange={handleFieldsChange} />}
{step === 3 && (
<VStack spacing={4} align="stretch">
<Input type="text" name="name" value={signatureRequest.name} onChange={handleInputChange} placeholder="Request Name" />
<Select name="delivery_mode" value={signatureRequest.delivery_mode} onChange={handleInputChange}>
<option value="">Select Delivery Mode</option>
<option value="email">Email</option>
<option value="direct">Direct</option>
<option value="sms">SMS</option>
</Select>
<Checkbox name="ordered_signers" isChecked={signatureRequest.ordered_signers} onChange={handleInputChange}>Ordered Signers</Checkbox>
<Input type="datetime-local" name="expiration_date" value={signatureRequest.expiration_date} onChange={handleInputChange} />
<Textarea name="message" value={signatureRequest.message} onChange={handleInputChange} placeholder="Message" />
</VStack>
)}
</Box>
<Flex justify="space-between" mt={4}>
{step > 0 && <Button onClick={handlePrev}>Previous</Button>}
{step < 3 && <Button onClick={handleNext}>Next</Button>}
{step === 3 && <Button onClick={handleSubmit}>Submit</Button>}
</Flex>
</Container>
);
};
export default CreateSignatureRequestWizard;
Service for Signature Requests (frontend/src/client/services/SignatureRequestsService.ts
):
import { OpenAPI } from "../core/OpenAPI";
import { request } from "../core/request";
import { SignatureRequestCreate } from "../models/SignatureRequestCreate";
import { SignatureRequestRead } from "../models/SignatureRequestRead";
export const initiateSignatureRequest = (data: SignatureRequestCreate): Promise<SignatureRequestRead> => {
return request<SignatureRequestRead>(OpenAPI, {
method: "POST",
url: "/api/v1/signature_requests/",
body: data,
mediaType: "application/json",
});
};
export const fetchSignatureRequests = (): Promise<SignatureRequestRead[]> => {
return request<SignatureRequestRead[]>(OpenAPI, {
method: "GET",
url: "/api/v1/signature_requests/",
});
};
Problem Explanation:
- Clicking on “New Signature Request” in the sidebar navigates to a 404
page with no sidebar or other elements. - All other sidebar links work correctly.
- The path defined for “New Signature Request” in the route is
/new_signature_requests
.
Troubleshooting Steps Taken:
- Verified that the route path /new_signature_requests is consistent
across the route configuration and sidebar items. - Ensured the
CreateSignatureRequestWizard
component is correctly
imported and used in the create route.
Request:
- Could anyone provide insights into what might be causing the 404
error specifically for the “New Signature Request” page? - Any suggestions on additional debugging steps or corrections in the
route configuration would be appreciated.
Thank you!