diff --git a/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDay.tsx b/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDay.tsx
index 6586c1c47..721e7346d 100644
--- a/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDay.tsx
+++ b/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDay.tsx
@@ -1,32 +1,13 @@
+import ProblemOfTheDaySkeleton from "@/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDaySkeleton";
import { useFetchPotdQuery } from "@/lib/api/queries/potd";
-import {
- Badge,
- Button,
- Card,
- Center,
- Flex,
- Loader,
- Title,
-} from "@mantine/core";
+import { Badge, Button, Card, Center, Flex, Title } from "@mantine/core";
import { Link } from "react-router-dom";
export default function ProblemOfTheDay() {
const { data, status } = useFetchPotdQuery();
if (status === "pending") {
- return (
-
-
-
-
-
- );
+ return ;
}
if (status === "error") {
diff --git a/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDaySkeleton.test.tsx b/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDaySkeleton.test.tsx
new file mode 100644
index 000000000..355b1bcd8
--- /dev/null
+++ b/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDaySkeleton.test.tsx
@@ -0,0 +1,46 @@
+import ProblemOfTheDaySkeleton from "@/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDaySkeleton";
+import { TestUtils, TestUtilTypes } from "@/lib/test";
+import { screen } from "@testing-library/react";
+
+describe("ProblemOfTheDaySkeleton", () => {
+ let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null;
+
+ beforeEach(() => {
+ renderProviderFn = TestUtils.getRenderWithAllProvidersFn();
+ });
+
+ it("should render POTD title skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("potd-skeleton-title");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render POTD reset time skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("potd-skeleton-reset-time");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render problem title skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("potd-skeleton-problem-title");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render problem multiplier skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("potd-skeleton-problem-multiplier");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render problem link button skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("potd-skeleton-problem-link-button");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+});
diff --git a/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDaySkeleton.tsx b/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDaySkeleton.tsx
new file mode 100644
index 000000000..f686ceae9
--- /dev/null
+++ b/js/src/app/dashboard/_components/ProblemOfTheDay/ProblemOfTheDaySkeleton.tsx
@@ -0,0 +1,44 @@
+import { Card, Center, Flex, Skeleton } from "@mantine/core";
+
+export default function ProblemOfTheDaySkeleton() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/js/src/app/settings/Settings.page.tsx b/js/src/app/settings/Settings.page.tsx
index 84d3c4aa8..aa0879dcb 100644
--- a/js/src/app/settings/Settings.page.tsx
+++ b/js/src/app/settings/Settings.page.tsx
@@ -1,13 +1,14 @@
import ChangeImageSettingsCard from "@/app/settings/_components/ChangeImageSettingsCard";
import LogoutAllSessionsCard from "@/app/settings/_components/LogoutAllSessionsCard";
import SchoolVerifySettingsCard from "@/app/settings/_components/SchoolVerifySettingsCard";
+import SettingsSkeleton from "@/app/settings/_components/SettingsSkeleton";
import DocumentDescription from "@/components/ui/title/DocumentDescription";
import DocumentTitle from "@/components/ui/title/DocumentTitle";
import Toast from "@/components/ui/toast/Toast";
import ToastWithRedirect from "@/components/ui/toast/ToastWithRedirect";
import { useAuthQuery } from "@/lib/api/queries/auth";
import { useBackendCallbackParams } from "@/lib/hooks/useBackendCallbackParams";
-import { Box, Center, Loader, Stack, Title } from "@mantine/core";
+import { Box, Center, Stack, Title } from "@mantine/core";
import { notifications } from "@mantine/notifications";
import { useEffect } from "react";
@@ -25,11 +26,7 @@ export default function SettingsPage() {
}, [message, success]);
if (status === "pending") {
- return (
-
-
-
- );
+ return ;
}
if (status === "error") {
diff --git a/js/src/app/settings/_components/SettingsSkeleton.test.tsx b/js/src/app/settings/_components/SettingsSkeleton.test.tsx
new file mode 100644
index 000000000..d4ec29bc6
--- /dev/null
+++ b/js/src/app/settings/_components/SettingsSkeleton.test.tsx
@@ -0,0 +1,134 @@
+import SettingsSkeleton from "@/app/settings/_components/SettingsSkeleton";
+import { TestUtils, TestUtilTypes } from "@/lib/test";
+import { screen } from "@testing-library/react";
+
+describe("SettingsSkeleton", () => {
+ let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null;
+
+ beforeEach(() => {
+ renderProviderFn = TestUtils.getRenderWithAllProvidersFn();
+ });
+
+ it("should render title skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("settings-skeleton-title");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render verify school card skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("settings-skeleton-verify-school-card");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render verify school title skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("settings-skeleton-verify-school-title");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render verify school description 1 skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId(
+ "settings-skeleton-verify-school-description-1",
+ );
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render verify school description 2 skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId(
+ "settings-skeleton-verify-school-description-2",
+ );
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render verify school list skeletons", () => {
+ renderProviderFn?.();
+ const elements = screen.queryAllByTestId(
+ "settings-skeleton-verify-school-list",
+ );
+ expect(elements).toHaveLength(9);
+ for (const element of elements) {
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ }
+ });
+
+ it("should render verify now button skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("settings-skeleton-verify-now-button");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render change profile card skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("settings-skeleton-change-profile-card");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render change profile title skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId(
+ "settings-skeleton-change-profile-title",
+ );
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render change profile description skeletons", () => {
+ renderProviderFn?.();
+ const description1 = screen.getByTestId(
+ "settings-skeleton-change-profile-description-1",
+ );
+ const description2 = screen.getByTestId(
+ "settings-skeleton-change-profile-description-2",
+ );
+ const description3 = screen.getByTestId(
+ "settings-skeleton-change-profile-description-3",
+ );
+ expect(description1).toBeInTheDocument();
+ expect(description1).toBeVisible();
+ expect(description2).toBeInTheDocument();
+ expect(description2).toBeVisible();
+ expect(description3).toBeInTheDocument();
+ expect(description3).toBeVisible();
+ });
+
+ it("should render log out card skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("settings-skeleton-log-out-card");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render log out title skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("settings-skeleton-log-out-title");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render log out description skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("settings-skeleton-log-out-description");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render log out all sessions button skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId(
+ "settings-skeleton-log-out-all-sessions-button",
+ );
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+});
diff --git a/js/src/app/settings/_components/SettingsSkeleton.tsx b/js/src/app/settings/_components/SettingsSkeleton.tsx
new file mode 100644
index 000000000..a58e79b09
--- /dev/null
+++ b/js/src/app/settings/_components/SettingsSkeleton.tsx
@@ -0,0 +1,135 @@
+import { Box, Card, Center, Flex, Skeleton, Stack } from "@mantine/core";
+
+export default function SettingsSkeleton() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ {Array(9)
+ .fill(0)
+ .map((_, index) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContent.tsx b/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContent.tsx
index 76a166f2f..2310366d5 100644
--- a/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContent.tsx
+++ b/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContent.tsx
@@ -1,4 +1,5 @@
import classes from "@/app/submission/[submissionId]/_components/SubmissionDetailsContent.module.css";
+import SubmissionDetailsContentSkeleton from "@/app/submission/[submissionId]/_components/SubmissionDetailsContentSkeleton";
import DocumentDescription from "@/components/ui/title/DocumentDescription";
import DocumentTitle from "@/components/ui/title/DocumentTitle";
import Toast from "@/components/ui/toast/Toast";
@@ -12,7 +13,6 @@ import {
Card,
Center,
Flex,
- Loader,
Text,
Title,
} from "@mantine/core";
@@ -32,11 +32,7 @@ export default function SubmissionDetailsContent({
const { data, status } = useSubmissionDetailsQuery({ submissionId });
if (status === "pending") {
- return (
-
-
-
- );
+ return ;
}
if (status === "error") {
diff --git a/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContentSkeleton.test.tsx b/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContentSkeleton.test.tsx
new file mode 100644
index 000000000..30f906e7c
--- /dev/null
+++ b/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContentSkeleton.test.tsx
@@ -0,0 +1,75 @@
+import SubmissionDetailsContentSkeleton from "@/app/submission/[submissionId]/_components/SubmissionDetailsContentSkeleton";
+import { TestUtils, TestUtilTypes } from "@/lib/test";
+import { screen } from "@testing-library/react";
+
+describe("SubmissionDetailsContentSkeleton", () => {
+ let renderProviderFn: TestUtilTypes.RenderWithAllProvidersFn | null = null;
+
+ beforeEach(() => {
+ renderProviderFn = TestUtils.getRenderWithAllProvidersFn();
+ });
+
+ it("should render question title skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("submission-skeleton-question-title");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render question link button skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId(
+ "submission-skeleton-question-link-button",
+ );
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render question solved by skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId(
+ "submission-skeleton-question-solved-by",
+ );
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render question information skeletons", () => {
+ renderProviderFn?.();
+ const info1 = screen.getByTestId("submission-skeleton-question-points");
+ const info2 = screen.getByTestId("submission-skeleton-question-difficulty");
+ const info3 = screen.getByTestId(
+ "submission-skeleton-question-acceptance-rate",
+ );
+
+ expect(info1).toBeInTheDocument();
+ expect(info1).toBeVisible();
+ expect(info2).toBeInTheDocument();
+ expect(info2).toBeVisible();
+ expect(info3).toBeInTheDocument();
+ expect(info3).toBeVisible();
+ });
+
+ it("should render user profile button skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId(
+ "submission-skeleton-user-profile-button",
+ );
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render question details skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("submission-skeleton-question-details");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+
+ it("should render user solution skeleton", () => {
+ renderProviderFn?.();
+ const element = screen.getByTestId("submission-skeleton-user-solution");
+ expect(element).toBeInTheDocument();
+ expect(element).toBeVisible();
+ });
+});
diff --git a/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContentSkeleton.tsx b/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContentSkeleton.tsx
new file mode 100644
index 000000000..d891933e4
--- /dev/null
+++ b/js/src/app/submission/[submissionId]/_components/SubmissionDetailsContentSkeleton.tsx
@@ -0,0 +1,94 @@
+import { Box, Card, Center, Flex, Skeleton } from "@mantine/core";
+
+export default function SubmissionDetailsContentSkeleton() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}