Skip to content
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
7 changes: 4 additions & 3 deletions samples/todo/components/AuthGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ export default function AuthGuard({ children }: Props) {
const { status } = useSession();
if (status === 'loading') {
return <p>Loading...</p>;
}
if (status === 'unauthenticated') {
} else if (status === 'unauthenticated') {
signIn();
return <></>;
} else {
return <>{children}</>;
}
return <>{children}</>;
}
2 changes: 1 addition & 1 deletion samples/todo/components/Spaces.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default function Spaces() {
const spaces = find();

return (
<ul className="flex flex-wrap space-x-4">
<ul className="flex flex-wrap gap-4">
{spaces.data?.map((space) => (
<li
className="card w-80 h-32 shadow-xl text-gray-600 cursor-pointer hover:bg-gray-50 border"
Expand Down
17 changes: 17 additions & 0 deletions samples/todo/components/TimeInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import moment from 'moment';

type Props = {
value: { createdAt: Date; updatedAt: Date; completedAt?: Date | null };
};

export default function TimeInfo({ value }: Props) {
return (
<p className="text-sm text-gray-500">
{value.completedAt
? `Completed ${moment(value.completedAt).fromNow()}`
: value.createdAt === value.updatedAt
? `Created ${moment(value.createdAt).fromNow()}`
: `Updated ${moment(value.updatedAt).fromNow()}`}
</p>
);
}
10 changes: 2 additions & 8 deletions samples/todo/components/Todo.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { TrashIcon } from '@heroicons/react/24/outline';
import { useTodo } from '@zenstackhq/runtime/hooks';
import { Todo, User } from '@zenstackhq/runtime/types';
import moment from 'moment';
import { ChangeEvent, useEffect, useState } from 'react';
import Avatar from './Avatar';
import TimeInfo from './TimeInfo';

type Props = {
value: Todo & { owner: User };
Expand Down Expand Up @@ -64,13 +64,7 @@ export default function Component({ value, updated, deleted }: Props) {
</div>
</div>
<div className="flex justify-end w-full space-x-2">
<p className="text-sm text-gray-500">
{value.completedAt
? `Completed ${moment(value.completedAt).fromNow()}`
: value.createdAt === value.updatedAt
? `Created ${moment(value.createdAt).fromNow()}`
: `Updated ${moment(value.updatedAt).fromNow()}`}
</p>
<TimeInfo value={value} />
<Avatar user={value.owner} size={18} />
</div>
</div>
Expand Down
74 changes: 53 additions & 21 deletions samples/todo/components/TodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,77 @@
import Image from 'next/image';
import { List } from '@zenstackhq/runtime/types';
import { customAlphabet } from 'nanoid';
import { LockClosedIcon } from '@heroicons/react/24/outline';
import { LockClosedIcon, TrashIcon } from '@heroicons/react/24/outline';
import { User } from 'next-auth';
import Avatar from './Avatar';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useList } from '@zenstackhq/runtime/hooks';
import TimeInfo from './TimeInfo';

type Props = {
value: List & { owner: User };
deleted?: (value: List) => void;
};

export default function TodoList({ value }: Props) {
export default function TodoList({ value, deleted }: Props) {
const router = useRouter();

const { del } = useList();

const deleteList = async () => {
if (confirm('Are you sure to delete this list?')) {
await del(value.id);
if (deleted) {
deleted(value);
}
}
};

return (
<Link href={`${router.asPath}/${value.id}`}>
<a className="card w-80 bg-base-100 shadow-xl cursor-pointer hover:bg-gray-50">
<figure>
<Image
src={`https://picsum.photos/300/200?r=${customAlphabet(
'0123456789'
)(4)}`}
width={320}
height={200}
alt="Cover"
/>
</figure>
<div className="card-body">
<h2 className="card-title line-clamp-1">
{value.title || 'Missing Title'}
</h2>
<div className="card-actions justify-end">
<div className="card w-80 bg-base-100 shadow-xl cursor-pointer hover:bg-gray-50">
<Link href={`${router.asPath}/${value.id}`}>
<a>
<figure>
<Image
src={`https://picsum.photos/300/200?r=${customAlphabet(
'0123456789'
)(4)}`}
width={320}
height={200}
alt="Cover"
/>
</figure>
</a>
</Link>
<div className="card-body">
<Link href={`${router.asPath}/${value.id}`}>
<a>
<h2 className="card-title line-clamp-1">
{value.title || 'Missing Title'}
</h2>
</a>
</Link>
<div className="card-actions flex w-full justify-between">
<div>
<TimeInfo value={value} />
</div>
<div className="flex space-x-2">
<Avatar user={value.owner} size={18} />
{value.private && (
<div className="tooltip" data-tip="Private">
<LockClosedIcon className="w-4 h-4 text-gray-500" />
</div>
)}
<TrashIcon
className="w-4 h-4 text-gray-500 cursor-pointer"
onClick={() => {
deleteList();
}}
/>
</div>
</div>
</a>
</Link>
</div>
</div>
);
}
46 changes: 19 additions & 27 deletions samples/todo/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,30 @@
import type { NextPage } from 'next';
import { useSession, signIn } from 'next-auth/react';
import Spaces from 'components/Spaces';
import Link from 'next/link';
import { useCurrentUser } from '@lib/context';

const Home: NextPage = () => {
const { data: session, status: sessionStatus } = useSession();

if (sessionStatus === 'unauthenticated') {
// kick back to signin
signIn();
}

if (!session) {
return <div>Loading ...</div>;
}

const user = useCurrentUser();
return (
<>
<div className="mt-8 text-center flex flex-col items-center w-full">
<h1 className="text-2xl text-gray-800">
Welcome {session.user.name || session.user.email}!
</h1>
<div className="w-full p-8">
<h2 className="text-lg md:text-xl text-left mb-8 text-gray-700">
Choose a space to start, or{' '}
<Link href="/create-space">
<a className="link link-primary">
create a new one.
</a>
</Link>
</h2>
<Spaces />
{user && (
<div className="mt-8 text-center flex flex-col items-center w-full">
<h1 className="text-2xl text-gray-800">
Welcome {user.name || user.email}!
</h1>
<div className="w-full p-8">
<h2 className="text-lg md:text-xl text-left mb-8 text-gray-700">
Choose a space to start, or{' '}
<Link href="/create-space">
<a className="link link-primary">
create a new one.
</a>
</Link>
</h2>
<Spaces />
</div>
</div>
</div>
)}
</>
);
};
Expand Down
2 changes: 1 addition & 1 deletion samples/todo/pages/space/[slug]/[listId]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default function TodoList() {
<PlusIcon className="w-6 h-6 text-gray-500" />
</button>
</div>
<ul className="flex flex-col space-y-4 py-8">
<ul className="flex flex-col space-y-4 py-8 w-11/12 md:w-auto">
{todos?.map((todo) => (
<TodoComponent
key={todo.id}
Expand Down
10 changes: 7 additions & 3 deletions samples/todo/pages/space/[slug]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function CreateDialog() {
required
placeholder="Title of your list"
className="input input-bordered w-full max-w-xs mt-2"
value={title}
onChange={(
e: FormEvent<HTMLInputElement>
) => setTitle(e.currentTarget.value)}
Expand Down Expand Up @@ -120,7 +121,7 @@ export default function SpaceHome() {
const space = useContext(SpaceContext);
const { find } = useList();

const lists = find({
const { data: lists, mutate: invalidateLists } = find({
where: {
space: {
id: space?.id,
Expand Down Expand Up @@ -151,9 +152,12 @@ export default function SpaceHome() {
</div>

<ul className="flex flex-wrap gap-6">
{lists.data?.map((list) => (
{lists?.map((list) => (
<li key={list.id}>
<TodoList value={list} />
<TodoList
value={list}
deleted={() => invalidateLists()}
/>
</li>
))}
</ul>
Expand Down