diff --git a/.changeset/poor-seahorses-wonder.md b/.changeset/poor-seahorses-wonder.md new file mode 100644 index 0000000..7a3ca22 --- /dev/null +++ b/.changeset/poor-seahorses-wonder.md @@ -0,0 +1,5 @@ +--- +"namesake": minor +--- + +Redesign app layout for optimal line length and taller scroll area diff --git a/src/components/AppHeader/AppHeader.tsx b/src/components/AppHeader/AppHeader.tsx deleted file mode 100644 index c4a72c0..0000000 --- a/src/components/AppHeader/AppHeader.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useAuthActions } from "@convex-dev/auth/react"; -import { api } from "@convex/_generated/api"; -import { RiAccountCircleFill } from "@remixicon/react"; -import { Authenticated, useQuery } from "convex/react"; -import { Button } from "../Button"; -import { Link } from "../Link"; -import { Logo } from "../Logo"; -import { Menu, MenuItem, MenuTrigger } from "../Menu"; - -export const AppHeader = () => { - const { signOut } = useAuthActions(); - const role = useQuery(api.users.getCurrentUserRole); - const isAdmin = role === "admin"; - - const handleSignOut = async () => { - await signOut(); - }; - - return ( -
- - - - - Quests - {isAdmin && Admin} -
- -
-
-
- ); -}; diff --git a/src/components/AppHeader/index.ts b/src/components/AppHeader/index.ts deleted file mode 100644 index 5ab21b4..0000000 --- a/src/components/AppHeader/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./AppHeader"; diff --git a/src/components/AppSidebar/AppSidebar.tsx b/src/components/AppSidebar/AppSidebar.tsx new file mode 100644 index 0000000..7d41602 --- /dev/null +++ b/src/components/AppSidebar/AppSidebar.tsx @@ -0,0 +1,103 @@ +import { useAuthActions } from "@convex-dev/auth/react"; +import { api } from "@convex/_generated/api"; +import { + RiAccountCircleFill, + RiAddLine, + RiAdminLine, + RiSettings3Line, +} from "@remixicon/react"; +import { useQuery } from "convex/react"; +import { Badge } from "../Badge"; +import { Button } from "../Button"; +import { Link } from "../Link"; +import { Logo } from "../Logo"; +import { Menu, MenuItem, MenuTrigger } from "../Menu"; +import { Tooltip } from "../Tooltip"; +import { TooltipTrigger } from "../Tooltip"; + +type AppSidebarProps = { + children: React.ReactNode; +}; + +export const AppSidebar = ({ children }: AppSidebarProps) => { + const { signOut } = useAuthActions(); + const user = useQuery(api.users.getCurrentUser); + const isAdmin = user?.role === "admin"; + + const handleSignOut = async () => { + await signOut(); + }; + + return ( + + ); +}; diff --git a/src/components/AppSidebar/index.ts b/src/components/AppSidebar/index.ts new file mode 100644 index 0000000..2b27d49 --- /dev/null +++ b/src/components/AppSidebar/index.ts @@ -0,0 +1 @@ +export * from "./AppSidebar"; diff --git a/src/components/Badge/Badge.tsx b/src/components/Badge/Badge.tsx index 57b335b..fdad6e0 100644 --- a/src/components/Badge/Badge.tsx +++ b/src/components/Badge/Badge.tsx @@ -42,11 +42,11 @@ const icon = tv({ }, }); -export function Badge({ icon: Icon, ...props }: BadgeProps) { +export function Badge({ icon: Icon, className, ...props }: BadgeProps) { return (
{Icon && } {props.children} diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index abbfdd4..d4c80ac 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -16,7 +16,7 @@ export interface ButtonProps extends AriaButtonProps { export const buttonStyles = tv({ extend: focusRing, - base: "py-2 text-sm font-medium whitespace-nowrap rounded-lg flex gap-1 items-center justify-center border border-black/10 dark:border-white/10 cursor-pointer", + base: "py-2 text-sm font-medium whitespace-nowrap rounded-lg flex gap-1.5 items-center justify-center border border-black/10 dark:border-white/10 cursor-pointer", variants: { variant: { primary: "bg-purple-solid text-white", diff --git a/src/components/Container/Container.tsx b/src/components/Container/Container.tsx index aca0cc3..7a4cde3 100644 --- a/src/components/Container/Container.tsx +++ b/src/components/Container/Container.tsx @@ -1,14 +1,14 @@ -import { twMerge } from "tailwind-merge"; +import { tv } from "tailwind-variants"; export interface ContainerProps { className?: string; children: React.ReactNode; } +const containerStyles = tv({ + base: "max-w-full w-[1200px] px-4 lg:px-6 xl:px-8 mx-auto", +}); + export function Container({ className, children }: ContainerProps) { - return ( -
- {children} -
- ); + return
{children}
; } diff --git a/src/components/Nav/Nav.tsx b/src/components/Nav/Nav.tsx index fec0721..1ab43fd 100644 --- a/src/components/Nav/Nav.tsx +++ b/src/components/Nav/Nav.tsx @@ -21,7 +21,7 @@ export const Nav = ({ routes }: NavProps) => { const matchRoute = useMatchRoute(); return ( - +
); }; diff --git a/src/components/PageHeader/PageHeader.stories.tsx b/src/components/PageHeader/PageHeader.stories.tsx index 3804e14..6682d4f 100644 --- a/src/components/PageHeader/PageHeader.stories.tsx +++ b/src/components/PageHeader/PageHeader.stories.tsx @@ -28,19 +28,10 @@ export const Badged: Story = { }, }; -export const Subtitle: Story = { - args: { - title: "Court Order", - badge: MA, - subtitle: "Case #123456", - }, -}; - export const Actions: Story = { args: { title: "Court Order", badge: MA, - subtitle: "Case #123456", children: , }, }; diff --git a/src/components/PageHeader/PageHeader.tsx b/src/components/PageHeader/PageHeader.tsx index 88ae348..e69f431 100644 --- a/src/components/PageHeader/PageHeader.tsx +++ b/src/components/PageHeader/PageHeader.tsx @@ -5,7 +5,6 @@ export interface PageHeaderProps { title: string; icon?: RemixiconComponentType; badge?: React.ReactNode; - subtitle?: string; children?: React.ReactNode; className?: string; } @@ -14,14 +13,13 @@ export const PageHeader = ({ title, icon: Icon, badge, - subtitle, children, className, }: PageHeaderProps) => { return (
@@ -31,7 +29,6 @@ export const PageHeader = ({

{title}

{badge} - {subtitle &&

{subtitle}

}
{children}
diff --git a/src/components/SearchField/SearchField.tsx b/src/components/SearchField/SearchField.tsx index afad43a..f9b718a 100644 --- a/src/components/SearchField/SearchField.tsx +++ b/src/components/SearchField/SearchField.tsx @@ -18,12 +18,14 @@ export interface SearchFieldProps extends AriaSearchFieldProps { label?: string; description?: string; errorMessage?: string | ((validation: ValidationResult) => string); + placeholder?: string; } export function SearchField({ label, description, errorMessage, + placeholder, ...props }: SearchFieldProps) { return ( @@ -40,7 +42,10 @@ export function SearchField({ aria-hidden className="w-4 h-4 ml-3 text-gray-dim forced-colors:text-[ButtonText] group-disabled:opacity-50 forced-colors:group-disabled:text-[GrayText]" /> - + null // Render nothing in production - : React.lazy(() => - // Lazy load in development - import("@tanstack/router-devtools").then((res) => ({ - default: res.TanStackRouterDevtools, - })), - ); - return ( // TODO: Improve this API // https://github.com/adobe/react-spectrum/issues/6587 @@ -64,13 +53,11 @@ function RootRoute() { } > - - - ; return ( -
- +
); diff --git a/src/routes/_authenticated/_home.tsx b/src/routes/_authenticated/_home.tsx index 7c6e185..5a439d4 100644 --- a/src/routes/_authenticated/_home.tsx +++ b/src/routes/_authenticated/_home.tsx @@ -1,13 +1,13 @@ import { Badge, Button, + Container, Disclosure, DisclosureGroup, DisclosurePanel, Empty, GridList, GridListItem, - Link, Menu, MenuItem, MenuSection, @@ -16,6 +16,7 @@ import { Tooltip, TooltipTrigger, } from "@/components"; +import { AppSidebar } from "@/components/AppSidebar/AppSidebar"; import { api } from "@convex/_generated/api"; import { CATEGORIES, @@ -33,7 +34,7 @@ import { TIME_UNITS_ORDER, type TimeUnit, } from "@convex/constants"; -import { RiAddLine, RiListCheck2, RiSignpostLine } from "@remixicon/react"; +import { RiListCheck2, RiSignpostLine } from "@remixicon/react"; import { Outlet, createFileRoute } from "@tanstack/react-router"; import { Authenticated, Unauthenticated, useQuery } from "convex/react"; import { useEffect, useState } from "react"; @@ -130,8 +131,8 @@ function IndexRoute() { ]; return ( -
-
+ +
- Group by - - - - - - Add quests + Group by -
- - {Object.entries(groupedQuests) - .sort(([groupA], [groupB]) => - sortGroupedQuests(groupA, groupB, groupByValue), - ) - .map(([group, quests]) => { - if (quests.length === 0) return null; - let groupDetails: GroupDetails; - switch (groupByValue) { - case "category": - groupDetails = CATEGORIES[group as keyof typeof CATEGORIES]; - break; - case "status": - groupDetails = STATUS[group as keyof typeof STATUS]; - break; - case "timeRequired": - groupDetails = TIME_UNITS[group as keyof typeof TIME_UNITS]; - break; - case "dateAdded": - groupDetails = DATE_ADDED[group as keyof typeof DATE_ADDED]; - break; - } - const { label, icon: Icon } = groupDetails; + + {Object.entries(groupedQuests) + .sort(([groupA], [groupB]) => + sortGroupedQuests(groupA, groupB, groupByValue), + ) + .map(([group, quests]) => { + if (quests.length === 0) return null; + let groupDetails: GroupDetails; + switch (groupByValue) { + case "category": + groupDetails = CATEGORIES[group as keyof typeof CATEGORIES]; + break; + case "status": + groupDetails = STATUS[group as keyof typeof STATUS]; + break; + case "timeRequired": + groupDetails = TIME_UNITS[group as keyof typeof TIME_UNITS]; + break; + case "dateAdded": + groupDetails = DATE_ADDED[group as keyof typeof DATE_ADDED]; + break; + } + const { label, icon: Icon } = groupDetails; - return ( - - - {label} - {quests.length} -
- } - > - - - {quests.map((quest) => ( - -
-
- {quest.title} -
+ return ( + + + {label} + {quests.length} +
+ } + > + + + {quests.map((quest) => ( + +
+
+ {quest.title}
- - ))} - - - - ); - })} - -
- + +
+ ))} +
+
+ + ); + })} + + ); }; return ( <> -
+ -
+

Please log in

diff --git a/src/routes/_authenticated/_home/quests.$questId.tsx b/src/routes/_authenticated/_home/quests.$questId.tsx index ede43a2..268db34 100644 --- a/src/routes/_authenticated/_home/quests.$questId.tsx +++ b/src/routes/_authenticated/_home/quests.$questId.tsx @@ -1,7 +1,6 @@ import { Badge, Button, - Container, DialogTrigger, Empty, Link, @@ -38,9 +37,9 @@ const StatGroup = ({ value, children, }: { label: string; value: string; children?: React.ReactNode }) => ( -
-
{label}
-
+
+
{label}
+
{value} {children}
@@ -114,6 +113,21 @@ const QuestTimeRequired = ({ return ; }; +const QuestUrls = ({ urls }: { urls?: string[] }) => { + if (!urls || urls.length === 0) return null; + + return ( +
+ {urls.map((url) => ( + + + {url} + + ))} +
+ ); +}; + function QuestDetailRoute() { const { questId } = Route.useParams(); const navigate = useNavigate(); @@ -145,11 +159,10 @@ function QuestDetailRoute() { return ; return ( -
+
{quest.jurisdiction}} - className="px-6 border-b border-gray-dim h-16" > - -
- - -
- {quest.urls && ( -
- {quest.urls.map((url) => ( - - - {url} - - ))} -
- )} - {quest.content ? ( - - {quest.content} - - ) : ( - "No content" - )} -
+
+ + +
+ + {quest.content ? ( + + {quest.content} + + ) : ( + "No content" + )}
); } diff --git a/src/routes/_authenticated/admin/forms/$formId.tsx b/src/routes/_authenticated/admin/forms/$formId.tsx index f56ae5b..5830afc 100644 --- a/src/routes/_authenticated/admin/forms/$formId.tsx +++ b/src/routes/_authenticated/admin/forms/$formId.tsx @@ -24,8 +24,12 @@ function AdminFormDetailRoute() {
{form.jurisdiction}} - subtitle={form.formCode} + badge={ + <> + {form.jurisdiction} + {form.formCode} + + } /> {fileUrl && ( - - setTitle(value)} - /> - - - -
- {urls.map((url, index) => ( - { - const newUrls = [...urls]; - newUrls[index] = value; - setUrls(newUrls); - }} - onRemove={() => { - setUrls(urls.filter((_, i) => i !== index)); - }} - hideLabel={index > 0} - /> - ))} - +
+ +
+ +
+ + ); } diff --git a/src/routes/_authenticated/admin/quests/index.tsx b/src/routes/_authenticated/admin/quests/index.tsx index 8692625..856c7b2 100644 --- a/src/routes/_authenticated/admin/quests/index.tsx +++ b/src/routes/_authenticated/admin/quests/index.tsx @@ -224,7 +224,7 @@ function QuestsRoute() { const quests = useQuery(api.quests.getAllQuests); return ( -
+ <>
+ ); } diff --git a/src/routes/_authenticated/admin/route.tsx b/src/routes/_authenticated/admin/route.tsx index 0d3d2c1..6e1488d 100644 --- a/src/routes/_authenticated/admin/route.tsx +++ b/src/routes/_authenticated/admin/route.tsx @@ -1,4 +1,4 @@ -import { Container, Nav } from "@/components"; +import { AppSidebar, Container, Nav } from "@/components"; import { RiFileTextLine, RiInputField, RiSignpostLine } from "@remixicon/react"; import { Outlet, createFileRoute, redirect } from "@tanstack/react-router"; @@ -19,29 +19,31 @@ export const Route = createFileRoute("/_authenticated/admin")({ function AdminRoute() { return ( -
-
+ ); } diff --git a/src/routes/_authenticated/browse/index.tsx b/src/routes/_authenticated/browse/index.tsx index 28be573..f947001 100644 --- a/src/routes/_authenticated/browse/index.tsx +++ b/src/routes/_authenticated/browse/index.tsx @@ -1,7 +1,9 @@ import { + AppSidebar, Badge, Button, Card, + Container, Link, PageHeader, SearchField, @@ -102,11 +104,11 @@ const QuestCategoryRow = ({ category }: { category: Category }) => { return (
-

+

{label}

-
+
{quests?.map((quest) => { const userQuest = userQuests?.find((uq) => uq.questId === quest._id); const questCount = questCounts?.find( @@ -145,7 +147,7 @@ const SearchResultsGrid = ({ if (!quests) return; return ( -
+
{quests.map((quest) => ( - - - - {filteredQuests && search !== "" ? ( - - ) : ( - - )} -
+ + + + +
+ + {filteredQuests && search !== "" ? ( + + ) : ( + + )} +
+
); } diff --git a/src/routes/_authenticated/settings/data.tsx b/src/routes/_authenticated/settings/data.tsx index ad5f474..f2cd985 100644 --- a/src/routes/_authenticated/settings/data.tsx +++ b/src/routes/_authenticated/settings/data.tsx @@ -8,14 +8,11 @@ export const Route = createFileRoute("/_authenticated/settings/data")({ function DataRoute() { return ( -
- + <> + Data shown here is end-to-end encrypted. Only you can access it. -
+ ); } diff --git a/src/routes/_authenticated/settings/route.tsx b/src/routes/_authenticated/settings/route.tsx index 8e70b31..c9a0d10 100644 --- a/src/routes/_authenticated/settings/route.tsx +++ b/src/routes/_authenticated/settings/route.tsx @@ -1,4 +1,4 @@ -import { Container, Nav } from "@/components"; +import { AppSidebar, Container, Nav } from "@/components"; import { RiLock2Line, RiSettings3Line } from "@remixicon/react"; import { Outlet, createFileRoute } from "@tanstack/react-router"; @@ -8,8 +8,8 @@ export const Route = createFileRoute("/_authenticated/settings")({ function SettingsRoute() { return ( - <> -
+ +