Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎮 WOR-7 Playground: account & workspace actions #86

Merged
merged 2 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
"use client";

import Image from "next/image";

import { cn } from "@swy/ui/lib";

export interface CardProps {
id: number;
checked: boolean;
image: string;
title: string;
description: string;
onClick?: () => void;
}

export const Card = ({
id,
checked,
image,
title,
description,
onClick,
}: CardProps) => {
return (
<div
id={`${id}`}
role="button"
tabIndex={0}
className={cn(
"relative m-3 h-[280px] w-[230px] cursor-pointer select-none items-center justify-center whitespace-normal rounded-md border border-primary/20 bg-white py-10 text-center text-sm/[1.2] opacity-70 hover:opacity-100",
checked && "border-2 border-blue opacity-100",
)}
onClick={onClick}
>
<Image
src="https://www.notion.so/images/onboarding/unchecked.svg"
alt=""
className={cn("absolute right-3 top-3", checked && "hidden")}
height={24}
width={24}
/>
<Image
src="https://www.notion.so/images/onboarding/checked.svg"
alt=""
className={cn("absolute right-3 top-3", !checked && "hidden")}
height={24}
width={24}
/>
<div className="flex h-[90px] justify-center">
<Image src={image} alt="" className="h-full" />
</div>
<div className="m-4 flex-grow basis-full">
<header className="mt-[30px] text-lg font-semibold text-black">
{title}
</header>
<p className="mb-2 mt-3 text-sm/snug text-black/70">{description}</p>
</div>
</div>
);
};
140 changes: 140 additions & 0 deletions apps/playground/src/app/(platform)/(auth)/onboarding/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
"use client";

import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { v4 } from "uuid";

import { usePlatformStore } from "@swy/notion";
import { useTransition } from "@swy/ui/hooks";
import { Button } from "@swy/ui/shadcn";
import { Spinner } from "@swy/ui/shared";
import { Plan, Role } from "@swy/validators";

import { WorkspaceModel } from "~/db";
import { useAppActions, useAppState, useMockDB } from "~/hooks";
import { Card, type CardProps } from "./_components/card";

const data: Omit<CardProps, "checked">[] = [
{
id: 0,
image:
"https://www.notion.so/images/onboarding/team-features-illustration.png",
title: "For my team",
description: "Collaborate on your docs, projects, and wikis.",
},
{
id: 1,
image: "https://www.notion.so/images/onboarding/use-case-note.png",
title: "For personal use",
description: "Write better. Think more clearly. Stay organized.",
},
{
id: 2,
image: "https://www.notion.so/images/onboarding/use-case-wiki.png",
title: "For school",
description: "Keep your notes, research, and tasks all in one place.",
},
];

export default function Page() {
const [checked, setChecked] = useState(-1);
const router = useRouter();
const { addToDB } = useMockDB();
const [isSignedIn, accountId] = useAppState((state) => [
state.isSignedIn,
state.userId,
]);
const { selectWorkspace } = useAppActions();
const addWorkspace = usePlatformStore((state) => state.addWorkspace);

const [createWorkspace, loading] = useTransition(async () => {
if (!accountId) {
router.push("/sign-in");
return;
}
const wid = v4();
const w: WorkspaceModel = {
id: wid,
name: "New Workspace",
createdBy: accountId,
domain: `workspace-${accountId.slice(0, 5)}`,
inviteToken: wid,
plan: Plan.FREE,
icon: { type: "text", text: "N" },
lastEditedAt: Date.now(),
};
await addToDB("workspaces", { [wid]: w });
const mem = {
id: v4(),
accountId,
workspaceId: w.id,
joinedAt: Date.now(),
role: Role.OWNER,
};
await addToDB("memberships", [mem]);
addWorkspace({
id: wid,
name: w.name,
icon: w.icon ?? { type: "text", text: w.name },
members: 1,
plan: w.plan,
role: mem.role,
});
console.log(`create success, redirecting to ${wid}`);
await selectWorkspace(accountId, w.id);
});

useEffect(() => {
if (!isSignedIn) router.push("/sign-in");
}, [isSignedIn, router]);

return (
<>
<div className="flex min-h-screen flex-col justify-center">
<div className="relative mx-auto max-w-[520px] py-8 text-center">
<div>
<div className="font-sans text-3xl/tight font-semibold">
How are you planning to use Notion?
</div>
<div className="pt-0.5 text-lg/tight font-normal text-secondary dark:text-secondary-dark">
We’ll streamline your setup experience accordingly.
</div>
</div>
</div>
<div className="flex flex-col items-center pb-8">
<div className="flex flex-col items-center justify-center">
<div className="mb-8 mt-2.5 inline-flex w-full justify-center">
{data.map((props) => (
<Card
key={props.id}
{...props}
checked={checked === props.id}
onClick={() => setChecked(props.id)}
/>
))}
</div>
<Button
variant="blue"
size="sm"
className="w-[280px]"
disabled={!(checked in data) || loading}
onClick={createWorkspace}
>
Continue
{loading && <Spinner className="ml-2 text-white" />}
</Button>
</div>
</div>
</div>
<Button
variant="hint"
size="sm"
tabIndex={0}
className="absolute right-[18px] top-[18px] text-primary dark:text-primary/80"
onClick={() => router.back()}
>
Cancel
</Button>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,62 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
import React from "react";
import Image from "next/image";
import { SquareArrowOutUpRight } from "lucide-react";

import { Button } from "@swy/ui/shadcn";
import { Hint } from "@swy/ui/shared";

interface SignInButtonProps {
name: string;
githubAccount: string;
avatarUrl: string;
onClick: () => void;
}

export const SignInButton: React.FC<SignInButtonProps> = ({
name,
githubAccount,
avatarUrl,
onClick,
}) => {
return (
<Button variant="hint" className="flex h-fit w-full justify-between">
<div className="flex w-full items-center gap-2.5">
<div className="relative flex-shrink-0">
<Image
src={avatarUrl}
alt="I"
className="size-7 rounded-full border border-border"
/>
<Button
variant="hint"
className="flex h-fit w-full justify-between gap-2.5"
onClick={onClick}
>
<div className="relative flex-shrink-0">
<Image
src={avatarUrl}
alt="I"
className="size-7 rounded-full border border-border"
width={28}
height={28}
/>
</div>
<div className="flex-1 text-left">
<div className="truncate text-sm text-primary dark:text-primary/80">
{name}
</div>
<div className="flex-1 text-left">
<div className="truncate text-sm text-primary dark:text-primary/80">
{name}
</div>
<div className="truncate text-xs text-secondary dark:text-secondary-dark">
Fake user
</div>
<div className="truncate text-xs text-secondary dark:text-secondary-dark">
{githubAccount}
</div>
</div>
<div className="flex-shrink-0 grow-0">
<Hint description="Support on Github">
<div
role="button"
tabIndex={0}
className="flex size-7 items-center justify-center rounded-sm text-icon hover:bg-primary/5 dark:text-icon-dark"
onClick={(e) => {
e.stopPropagation();
window.open(`https://github.com/${githubAccount}`);
}}
>
<SquareArrowOutUpRight className="size-4" />
</div>
</Hint>
</div>
</Button>
);
};
44 changes: 41 additions & 3 deletions apps/playground/src/app/(platform)/(auth)/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,42 @@
"use client";

import { useRouter } from "next/navigation";

import { usePlatformStore, useSettingsStore2 } from "@swy/notion";
import { IconBlock } from "@swy/ui/shared";

import { accounts } from "~/db/accounts";
import { accounts, githubAccounts } from "~/db";
import { useAppActions, useAppState, useMockDB } from "~/hooks";
import { SignInButton } from "./_components/sign-in-button";

export default function Page() {
const router = useRouter();
const { signIn } = useAppState();
const { goToOnboarding, selectWorkspace } = useAppActions();
const { findAccount, findAccountMemberships } = useMockDB();
/** Store */
const setUser = usePlatformStore((state) => state.setUser);
const setWorkspaces = usePlatformStore((state) => state.setWorkspaces);
const updateSettings = useSettingsStore2((state) => state.update);
const login = async (userId: string) => {
const u = await findAccount(userId);
if (!u) return router.push("/sign-in");

console.log(`set app state: ${userId}`);
setUser({
id: userId,
name: u.name,
email: u.email,
avatarUrl: u.avatarUrl,
});
updateSettings({ account: u });
signIn(userId);
const workspaces = await findAccountMemberships(userId);
if (workspaces.length === 0) return goToOnboarding();
setWorkspaces(workspaces);
await selectWorkspace(userId, workspaces[0]!.id);
};

return (
<div className="flex flex-col items-center gap-12 p-10">
<div className="flex w-[320px] flex-col items-center gap-4">
Expand All @@ -15,8 +48,13 @@ export default function Page() {
Sign in with ...
</h1>
<div className="flex w-[240px] flex-col items-center gap-3">
{Object.values(accounts).map(({ name, avatarUrl }) => (
<SignInButton name={name} avatarUrl={avatarUrl} />
{Object.values(accounts).map(({ id, name, avatarUrl }) => (
<SignInButton
name={name}
avatarUrl={avatarUrl}
githubAccount={githubAccounts[id]!}
onClick={() => login(id)}
/>
))}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"use client";

import Image from "next/image";
import { PlusCircle } from "lucide-react";

import { usePlatformStore } from "@swy/notion";
import { Button } from "@swy/ui/shadcn";

interface Params {
params: { workspaceId: string };
}

const HomePage = ({ params: _ }: Params) => {
const activeWorkspace = usePlatformStore(
(state) => state.workspaces[state.activeWorkspace ?? ""],
);
/** Action */
const onSubmit = () => {
// TODO create doc
};

return (
<div className="flex h-full flex-col items-center justify-center space-y-4">
<Image
src="/empty.png"
height="300"
width="300"
alt="Empty"
className="dark:hidden"
/>
<Image
src="/empty-dark.png"
height="300"
width="300"
alt="Empty"
className="hidden dark:block"
/>
<h2 className="text-lg font-medium">
Welcome to {activeWorkspace?.name ?? "WorXpace"}
</h2>
<form action={onSubmit}>
<Button type="submit">
<PlusCircle className="mr-2 size-4" />
Create a note
</Button>
</form>
</div>
);
};

export default HomePage;
Loading
Loading