Skip to content

Commit

Permalink
🐛 Handle invalid team or appId URLs using notFound utility (#523)
Browse files Browse the repository at this point in the history
  • Loading branch information
alejsdev authored Nov 21, 2024
1 parent fda633b commit 711cdcc
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 47 deletions.
14 changes: 14 additions & 0 deletions frontend/src/assets/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,20 @@ export const Moon = createIcon({
),
})

export const NotFound = createIcon({
displayName: "NotFound",
path: (
<>
<path
d="m16.707,13.207l-.543.543.543.543c.391.391.391,1.023,0,1.414-.195.195-.451.293-.707.293s-.512-.098-.707-.293l-.543-.543-.543.543c-.195.195-.451.293-.707.293s-.512-.098-.707-.293c-.391-.391-.391-1.023,0-1.414l.543-.543-.543-.543c-.391-.391-.391-1.023,0-1.414s1.023-.391,1.414,0l.543.543.543-.543c.391-.391,1.023-.391,1.414,0s.391,1.023,0,1.414Zm-7.457,1.957l.543.543c.195.195.451.293.707.293s.512-.098.707-.293c.391-.391.391-1.023,0-1.414l-.543-.543.543-.543c.391-.391.391-1.023,0-1.414s-1.023-.391-1.414,0l-.543.543-.543-.543c-.391-.391-1.023-.391-1.414,0s-.391,1.023,0,1.414l.543.543-.543.543c-.391.391-.391,1.023,0,1.414.195.195.451.293.707.293s.512-.098.707-.293l.543-.543ZM4.5,7c.828,0,1.5-.672,1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5,1.5.672,1.5,1.5,1.5Zm4,0c.828,0,1.5-.672,1.5-1.5s-.672-1.5-1.5-1.5-1.5.672-1.5,1.5.672,1.5,1.5,1.5Zm15.5-1v12c0,2.757-2.243,5-5,5H5c-2.757,0-5-2.243-5-5V6C0,3.243,2.243,1,5,1h14c2.757,0,5,2.243,5,5Zm-22,0v2h20v-2c0-1.654-1.346-3-3-3H5c-1.654,0-3,1.346-3,3Zm20,12v-8H2v8c0,1.654,1.346,3,3,3h14c1.654,0,3-1.346,3-3Zm-7.143-.434c-1.994-.762-3.72-.762-5.714,0-.516.197-.774.775-.577,1.291.197.517.776.776,1.291.577,1.53-.584,2.756-.584,4.286,0,.118.045.238.066.357.066.402,0,.782-.245.934-.644.197-.516-.062-1.094-.577-1.291Z"
fill="currentColor"
opacity="1"
data-original="#000000"
/>
</>
),
})

export const Plus = createIcon({
displayName: "Plus",
path: (
Expand Down
72 changes: 49 additions & 23 deletions frontend/src/components/Common/NotFound.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,62 @@
import { Container, Text } from "@chakra-ui/react"
import { Link } from "@tanstack/react-router"

import { NotFound as NotFoundIcon } from "@/assets/icons"
import { Button } from "@/components/ui/button"
import { Center, Flex, Text } from "@chakra-ui/react"
import { Link } from "@tanstack/react-router"

const NotFound = () => {
return (
<>
<Container
h="100vh"
alignItems="stretch"
justifyContent="center"
textAlign="center"
maxW="sm"
centerContent
<Flex
height="100vh"
align="center"
justify="center"
flexDir="column"
data-testid="not-found"
p={4}
>
<Flex alignItems="center" zIndex={1}>
<NotFoundIcon
boxSize={{ base: "100px", md: "150px" }}
color="gray.600"
mb={4}
/>
<Flex flexDir="column" ml={4} align="center" justify="center" p={4}>
<Text
fontSize={{ base: "6xl", md: "8xl" }}
fontWeight="bold"
lineHeight="1"
mb={4}
>
404
</Text>
<Text fontSize="2xl" fontWeight="bold" mb={2}>
Oops!
</Text>
</Flex>
</Flex>

<Text
fontSize="8xl"
color="main.dark"
fontWeight="bold"
lineHeight="1"
fontSize="lg"
color="gray.600"
mb={4}
textAlign="center"
zIndex={1}
>
404
The page you are looking for was not found.
</Text>
<Text fontSize="md">Oops!</Text>
<Text fontSize="md">Page not found.</Text>
<Link to="/">
<Button variant="outline" mt={4}>
Go back
</Button>
</Link>
</Container>
<Center zIndex={1}>
<Link to="/">
<Button
variant="solid"
colorScheme="teal"
mt={4}
alignSelf="center"
>
Go Back
</Button>
</Link>
</Center>
</Flex>
</>
)
}
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/TeamSettings/DeleteConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ const DeleteConfirmation = ({ teamId }: DeleteProps) => {
},
onSuccess: () => {
showToast("Success", "The team was deleted successfully", "success")
navigate({ to: "/teams/all" })
localStorage.removeItem("current_team")
navigate({ to: "/" })
},
onError: handleError.bind(showToast),
onSettled: () => {
Expand Down
48 changes: 30 additions & 18 deletions frontend/src/routes/_layout/$team/apps/$appSlug/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Box, Container, Heading, Link, Text } from "@chakra-ui/react"
import { useQuery } from "@tanstack/react-query"
import { createFileRoute } from "@tanstack/react-router"
import { createFileRoute, notFound } from "@tanstack/react-router"

import { ExternalLink } from "@/assets/icons"
import { AppsService, DeploymentsService } from "@/client"
Expand All @@ -14,28 +14,40 @@ import { fetchTeamBySlug } from "@/utils"
export const Route = createFileRoute("/_layout/$team/apps/$appSlug/")({
component: AppDetail,
loader: async ({ context, params }) => {
const team = await fetchTeamBySlug(params.team)
try {
const team = await fetchTeamBySlug(params.team)

const apps = await AppsService.readApps({
teamId: team.id,
slug: params.appSlug,
})
const apps = await AppsService.readApps({
teamId: team.id,
slug: params.appSlug,
})

const deployments = await DeploymentsService.readDeployments({
appId: apps.data[0].id,
orderBy: "created_at",
order: "desc",
limit: 5,
})
if (apps.data.length === 0) {
throw notFound({
data: { appSlug: params.appSlug },
})
}

const app = apps.data[0]
const deployments = await DeploymentsService.readDeployments({
appId: apps.data[0].id,
orderBy: "created_at",
order: "desc",
limit: 5,
})

const app = apps.data[0]

await context.queryClient.ensureQueryData({
queryKey: ["apps", app.id, "environmentVariables"],
queryFn: () => AppsService.readEnvironmentVariables({ appId: app.id }),
})
await context.queryClient.ensureQueryData({
queryKey: ["apps", app.id, "environmentVariables"],
queryFn: () => AppsService.readEnvironmentVariables({ appId: app.id }),
})

return { app, deployments }
return { app, deployments }
} catch (error) {
throw notFound({
data: { appSlug: params.appSlug },
})
}
},
pendingComponent: () => (
<Box>
Expand Down
15 changes: 13 additions & 2 deletions frontend/src/routes/_layout/$team/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Box, Button, Container, Flex, Text } from "@chakra-ui/react"
import { createFileRoute } from "@tanstack/react-router"
import { Suspense } from "react"
import { createFileRoute, notFound } from "@tanstack/react-router"

import CustomCard from "@/components/Common/CustomCard"
import { SkeletonText } from "@/components/ui/skeleton"
Expand All @@ -11,8 +10,20 @@ import {
StatValueText,
} from "@/components/ui/stat"
import { useCurrentUser } from "@/hooks/useAuth"
import { fetchTeamBySlug } from "@/utils"
import { Suspense } from "react"

export const Route = createFileRoute("/_layout/$team/")({
loader: async ({ params: { team } }) => {
try {
const teamData = await fetchTeamBySlug(team)
return { teamData }
} catch (error) {
throw notFound({
data: { team },
})
}
},
component: Dashboard,
})

Expand Down
46 changes: 46 additions & 0 deletions frontend/tests/not-found.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, test } from "@playwright/test"
import { createTeam, createUser } from "./utils/privateApi"
import {
randomAppName,
randomEmail,
randomTeamName,
slugify,
} from "./utils/random"
import { createApp, logInUser } from "./utils/userUtils"

test.describe("404 Not Found when invalid URL", () => {
test.use({ storageState: { cookies: [], origins: [] } })
const password = "password"
let email: string
let user: any

test.beforeEach(async ({ page }) => {
email = randomEmail()
user = await createUser({ email, password })
await logInUser(page, email, password)
})

test("Display 404 for invalid team URL", async ({ page }) => {
const teamName = randomTeamName()
const team = await createTeam({ name: teamName, ownerId: user.id })

await page.goto(`/teams/${team.slug}-not-found`)
await expect(page.getByTestId("not-found")).toBeVisible()
})

test("Display 404 for invalid app URL", async ({ page }) => {
const teamName = randomTeamName()
const appName = randomAppName()
const appSlug = slugify(appName)

const team = await createTeam({
name: teamName,
ownerId: user.id,
isPersonalTeam: true,
})
await createApp(page, team.slug, appName)

await page.goto(`/${team.slug}/apps/${appSlug}-not-found`)
await expect(page.getByTestId("not-found")).toBeVisible()
})
})
6 changes: 3 additions & 3 deletions frontend/tests/user-settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,14 @@ test.describe("Delete account successfully", () => {
// Appearance

test("Appearance tab is visible", async ({ page }) => {
await page.goto("/admin")
await page.goto("/")
await expect(page.getByLabel("Toggle dark mode")).toBeVisible()
})

test("User can switch from light mode to dark mode and vice versa", async ({
page,
}) => {
await page.goto("/admin")
await page.goto("/")

// Ensure the initial state is light mode
if (
Expand Down Expand Up @@ -260,7 +260,7 @@ test("User can switch from light mode to dark mode and vice versa", async ({
})

test("Selected mode is preserved across sessions", async ({ page }) => {
await page.goto("/admin")
await page.goto("/")

// Ensure the initial state is light mode
if (
Expand Down

0 comments on commit 711cdcc

Please sign in to comment.