Skip to content

Commit

Permalink
Added loading skeletons to RSC and expanded profile page with salary …
Browse files Browse the repository at this point in the history
…and setting sections
  • Loading branch information
tommybarvaag committed Mar 23, 2023
1 parent 83c10be commit d34eaa9
Show file tree
Hide file tree
Showing 48 changed files with 679 additions and 277 deletions.
19 changes: 2 additions & 17 deletions app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { LoginButton } from "@/components/auth/login-button";
import { Icons } from "@/components/icons";
import LoginPageContent from "@/components/auth/login-page-content";
import { Metadata } from "next";

export const metadata: Metadata = {
Expand All @@ -8,19 +7,5 @@ export const metadata: Metadata = {
};

export default function LoginPage() {
return (
<div className="container flex h-screen w-screen flex-col items-center justify-center">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
<div className="flex flex-col space-y-2 text-center">
<Icons.Logo className="mx-auto" />
<h1 className="text-2xl font-semibold tracking-tight">Welcome back</h1>
<p className="mb-8 text-sm text-neutral-500 dark:text-neutral-400">
Click to login with your Knowit AD account.
</p>
</div>
<div className=""></div>
<LoginButton />
</div>
</div>
);
return <LoginPageContent />;
}
7 changes: 6 additions & 1 deletion app/api-v2/user/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { planetscaleEdge } from "@/lib/planetscale-edge";
import { getEdgeFriendlyToken } from "@/lib/token";
import { createUser } from "@/lib/user";
import { NextResponse } from "next/server";

export const runtime = "edge";
Expand All @@ -22,7 +23,11 @@ export async function GET(request: Request) {
try {
const { rows } = await planetscaleEdge.execute("SELECT * FROM user", []);

return NextResponse.json(rows);
const users = rows
.map(row => createUser(row))
.sort((a, b) => new Date(b.updated).getTime() - new Date(a.updated).getTime());

return NextResponse.json(users);
} catch (error) {
return new Response(error, {
status: 422
Expand Down
4 changes: 2 additions & 2 deletions app/api-v2/user/work-day-detail/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export async function PATCH(request: NextRequest) {
"UPDATE user_work_day_detail SET nonCommissionedHours = ?, extraHours = ?, sickDay = ? WHERE id = ?",
[nonCommissionedHours, extraHours, sickDay, +existing.id]
);
// create
} else {
// create if extra hours or non commissioned hours are greater than 0
} else if (extraHours > 0 || nonCommissionedHours > 0) {
await planetscaleEdge.execute(
"INSERT INTO user_work_day_detail (userId, date, nonCommissionedHours, extraHours, sickDay) VALUES (?, ?, ?, ?, ?)",
[+token.id, date, nonCommissionedHours, extraHours, sickDay]
Expand Down
9 changes: 4 additions & 5 deletions app/dashboard/_components/calendar-year.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { buttonVariants } from "@/components/ui/button";
import { getRequestDateNow } from "@/lib/date";
import { cn } from "@/lib/utils";
import { UserWorkDayDetail } from "@/types";
import { getCalendarYear } from "@/utils/calendar-utils";
import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons";
import { getYear } from "date-fns";
import Link from "next/link";
import * as React from "react";

import { buttonVariants } from "@/components/ui/button";
import { getRequestDateNow } from "@/lib/date";
import { cn } from "@/lib/utils";
import { UserWorkDayDetail } from "@/types";
import { CalendarMonth } from "./calendar-month";

const CalendarYear: React.FC<{ date: Date; workDayDetails?: UserWorkDayDetail[] }> = ({
Expand Down
7 changes: 5 additions & 2 deletions app/dashboard/_components/footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import Link from "@/components/ui/link";
export default function Footer() {
return (
<footer className="border-t border-t-neutral-700">
<div className="mx-auto max-w-5xl px-3 pt-4 pb-24 md:px-4 md:pt-8">
<Flex justify="between">
<div className="mx-auto max-w-5xl px-4 pt-4 pb-24 md:px-4 md:pt-8">
<Flex
className="flex-col justify-center space-y-6 md:flex-row md:justify-between md:space-y-0"
justify="between"
>
<Box>
<h2 className="mb-3 text-lg font-bold">Resources</h2>
<ul className="space-y-2">
Expand Down
19 changes: 18 additions & 1 deletion app/dashboard/_components/next-paycheck.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Skeleton } from "@/components/ui/skeleton";
import { getEdgeFriendlyToken } from "@/lib/token";
import { getUserEarnings } from "@/lib/user";

Expand All @@ -6,7 +7,7 @@ export default async function NextPaycheck() {
const userEarnings = await getUserEarnings(token.id);

return (
<div>
<div className="min-w-[120px]">
<div className="text-xs text-neutral-400">Next paycheck</div>
<div className="text-sm">{userEarnings?.nextPayDayStatistics.payDay}</div>
<div className="text-sm font-bold text-emerald-500">
Expand All @@ -15,3 +16,19 @@ export default async function NextPaycheck() {
</div>
);
}

const NextPaycheckSkeleton = () => {
return (
<div className="min-w-[120px]">
<div className="text-xs text-neutral-400">Next paycheck</div>
<div className="text-sm">
<Skeleton className="mb-[2px] h-[19px] w-full"></Skeleton>
</div>
<div className="text-sm font-bold">
<Skeleton className="h-[19px] w-24"></Skeleton>
</div>
</div>
);
};

export { NextPaycheckSkeleton };
4 changes: 2 additions & 2 deletions app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Link from "next/link";
import { Suspense } from "react";
import Avatar, { AvatarSkeleton } from "./_components/avatar";
import Footer from "./_components/footer";
import NextPaycheck from "./_components/next-paycheck";
import NextPaycheck, { NextPaycheckSkeleton } from "./_components/next-paycheck";

export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
Expand All @@ -13,7 +13,7 @@ export default async function DashboardLayout({ children }: { children: React.Re
<Icons.Logo className="w-full max-w-[96px] lg:max-w-[140px]" />
</Link>
<div className="flex gap-8">
<Suspense fallback={<div></div>}>
<Suspense fallback={<NextPaycheckSkeleton />}>
{/* @ts-expect-error Async Server Component */}
<NextPaycheck />
</Suspense>
Expand Down
20 changes: 20 additions & 0 deletions app/dashboard/profile/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { TabNavigation } from "@/components/tab-navigation";
import { Card } from "@/components/ui/card";

export default function ProfileLayout({ children }: { children: React.ReactNode }) {
return (
<div className="space-y-3">
<TabNavigation
path="/dashboard/profile"
items={[
{ text: "Profile", slug: "" },
{ text: "Salary", slug: "salary" },
{ text: "Settings", slug: "settings" }
]}
/>
<Card>
<Card.Content className="p-4">{children}</Card.Content>
</Card>
</div>
);
}
22 changes: 6 additions & 16 deletions app/dashboard/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Suspense } from "react";
import SalaryForm from "../_components/salary-form";
import SettingsForm from "../_components/settings-form";
import { Button } from "@/components/ui/button";

export const runtime = "edge";
export const dynamic = "force-dynamic";
Expand All @@ -12,20 +10,12 @@ export const metadata = {

export default async function ProfilePage() {
return (
<div className="grid grid-cols-2">
<div className="grid grid-cols-1 lg:grid-cols-2">
<div>
<h2>Salary details</h2>
<Suspense fallback={<div>loading...</div>}>
{/* @ts-expect-error Async Server Component */}
<SalaryForm />
</Suspense>
</div>
<div>
<h2>Settings</h2>
<Suspense fallback={<div>loading...</div>}>
{/* @ts-expect-error Async Server Component */}
<SettingsForm />
</Suspense>
<p>coming...</p>
<Button className="mls-auto" variant="destructive">
Delete me
</Button>
</div>
</div>
);
Expand Down
24 changes: 24 additions & 0 deletions app/dashboard/profile/salary/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { UserSalaryDetailsFormSkeleton } from "@/components/user/user-salary-details-form";
import { Suspense } from "react";
import SalaryForm from "../../_components/salary-form";

export const runtime = "edge";
export const dynamic = "force-dynamic";
export const dynamicParams = true;

export const metadata = {
title: "Profile"
};

export default async function ProfilePage() {
return (
<div className="grid grid-cols-1 lg:grid-cols-2">
<div>
<Suspense fallback={<UserSalaryDetailsFormSkeleton />}>
{/* @ts-expect-error Async Server Component */}
<SalaryForm />
</Suspense>
</div>
</div>
);
}
22 changes: 22 additions & 0 deletions app/dashboard/profile/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { UserSettingsFormSkeleton } from "@/components/user/user-settings-form";
import { Suspense } from "react";
import SettingsForm from "../../_components/settings-form";

export const runtime = "edge";
export const dynamic = "force-dynamic";
export const dynamicParams = true;

export const metadata = {
title: "Settings"
};

export default async function SettingsPage() {
return (
<div className="grid grid-cols-1 lg:grid-cols-2">
<Suspense fallback={<UserSettingsFormSkeleton />}>
{/* @ts-expect-error Async Server Component */}
<SettingsForm />
</Suspense>
</div>
);
}
19 changes: 14 additions & 5 deletions components/auth/login-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,28 @@ import { signIn } from "next-auth/react";
import { useSearchParams } from "next/navigation";
import { Button } from "../ui/button";

function LoginButton() {
function LoginButton({
children,
onLoginClicked = () => {},
...other
}: React.ComponentPropsWithoutRef<typeof Button> & {
children?: React.ReactNode;
onLoginClicked?: () => void;
}) {
const searchParams = useSearchParams();

return (
<Button
onClick={async () =>
onClick={async () => {
onLoginClicked?.();
signIn("azure-ad", {
redirect: false,
callbackUrl: searchParams?.get("from") || "/dashboard"
})
}
});
}}
{...other}
>
Login
{children ?? "Login"}
</Button>
);
}
Expand Down
38 changes: 38 additions & 0 deletions components/auth/login-page-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import { LoginButton } from "@/components/auth/login-button";
import { Icons } from "@/components/icons";
import { Show } from "@/components/ui/show";
import { useState } from "react";

export default function LoginPageContent() {
const [isLoggingIn, setIsLoggingIn] = useState(false);

return (
<div className="container flex h-screen w-screen flex-col items-center justify-center">
<div className="mx-auto flex w-full flex-col justify-center space-y-6 md:max-w-[400px]">
<div className="flex flex-col space-y-2 text-center">
<Icons.Logo className="mx-auto" />
<h1 className="text-2xl font-semibold tracking-tight">
{isLoggingIn ? "Logging you in" : "Welcome back"}
</h1>
<p className="mb-8 text-sm text-neutral-500 dark:text-neutral-400">
{isLoggingIn
? "This may take a few seconds, please don't close this page."
: "Click to login with your Knowit AD account."}
</p>
</div>
<LoginButton
className="mx-auto w-full max-w-[300px]"
disabled={isLoggingIn}
onLoginClicked={() => setIsLoggingIn(true)}
>
{isLoggingIn ? "Logging you in..." : "Login with Knowit AD"}
<Show when={isLoggingIn}>
<Icons.Loader className="ml-2 h-4 w-4" />
</Show>
</LoginButton>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const Icons = {
Rocket: RocketIcon,
CheckCircled: CheckCircledIcon,
MinusCircled: MinusCircledIcon,
Loader: ({ className, ...other }) => (
Loader: ({ className, ...other }: Icon) => (
<svg
className={cn("fill-current", className)}
xmlns="http://www.w3.org/2000/svg"
Expand Down
40 changes: 40 additions & 0 deletions components/tab-navigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"use client";

export type Item = {
text: string;
slug?: string;
segment?: string;
};

import Link from "next/link";
import { useSelectedLayoutSegment } from "next/navigation";
import { buttonVariants } from "./ui/button";

const TabNavigationItem = ({ path, item }: { path: string; item: Item }) => {
const segment = useSelectedLayoutSegment();
const href = item.slug ? path + "/" + item.slug : path;
const isActive =
// Example home pages e.g. `/layouts`
(!item.slug && segment === null) ||
segment === item.segment ||
// Nested pages e.g. `/layouts/electronics`
segment === item.slug;

return (
<Link href={href} className={buttonVariants({ variant: isActive ? "default" : "outline" })}>
{item.text}
</Link>
);
};

const TabNavigation = ({ path, items }: { path: string; items: Item[] }) => {
return (
<div className="flex flex-wrap items-center gap-2">
{items.map(item => (
<TabNavigationItem key={path + item.slug} item={item} path={path} />
))}
</div>
);
};

export { TabNavigation };
10 changes: 5 additions & 5 deletions components/theme-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client"
"use client";

import { ThemeProvider as NextThemesProvider } from "next-themes"
import { ThemeProviderProps } from "next-themes/dist/types"
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { ThemeProviderProps } from "next-themes/dist/types";

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
export function ThemeProvider({ children, ...other }: ThemeProviderProps) {
return <NextThemesProvider {...other}>{children}</NextThemesProvider>;
}
Loading

0 comments on commit d34eaa9

Please sign in to comment.