├── .env.sample
├── .eslintrc.json
├── .github
└── workflows
│ ├── build.yml
│ └── update.yml
├── .gitignore
├── .prettierrc
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── app
├── api
│ ├── auth
│ │ └── [...nextauth]
│ │ │ └── route.ts
│ └── model
│ │ └── [...path]
│ │ └── route.ts
├── create-space
│ └── page.tsx
├── layout.tsx
├── page.tsx
├── providers.tsx
├── signin
│ └── page.tsx
├── signup
│ └── page.tsx
└── space
│ └── [slug]
│ ├── [listId]
│ └── page.tsx
│ └── page.tsx
├── assets
└── styles
│ └── globals.css
├── components
├── Avatar.tsx
├── BreadCrumb.tsx
├── ManageMembers.tsx
├── NavBar.tsx
├── SpaceMembers.tsx
├── Spaces.tsx
├── TimeInfo.tsx
├── Todo.tsx
├── TodoList.tsx
└── WithNavBar.tsx
├── lib
├── context.ts
└── hooks
│ ├── __model_meta.ts
│ ├── account.ts
│ ├── index.ts
│ ├── list.ts
│ ├── space-user.ts
│ ├── space.ts
│ ├── todo.ts
│ └── user.ts
├── middleware.ts
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── prisma
├── migrations
│ ├── 20221014084317_init
│ │ └── migration.sql
│ ├── 20221020094651_upate_cli
│ │ └── migration.sql
│ ├── 20221103144245_drop_account_session
│ │ └── migration.sql
│ ├── 20221126150023_add_account
│ │ └── migration.sql
│ ├── 20221126151212_email_password_optional
│ │ └── migration.sql
│ ├── 20221126151510_refresh_token_expires
│ │ └── migration.sql
│ ├── 20221127033222_email_required
│ │ └── migration.sql
│ ├── 20230306121228_update
│ │ └── migration.sql
│ ├── 20230905040400_drop_aux_fields
│ │ └── migration.sql
│ ├── 20241222133651_add_space_owner
│ │ └── migration.sql
│ └── migration_lock.toml
└── schema.prisma
├── public
├── auth-bg.jpg
├── avatar.jpg
└── logo.png
├── schema.zmodel
├── server
├── auth.ts
└── db.ts
├── tailwind.config.js
├── tsconfig.json
└── types
├── next-auth.d.ts
└── next.d.ts
/.env.sample:
--------------------------------------------------------------------------------
1 | POSTGRES_URL=
2 | POSTGRES_URL_NON_POOLING=
3 | AUTH_GITHUB_ID=
4 | AUTH_GITHUB_SECRET=
5 | AUTH_SECRET=
6 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "next/core-web-vitals",
4 | "plugin:@typescript-eslint/recommended",
5 | "plugin:@typescript-eslint/recommended-type-checked"
6 | ],
7 | "parser": "@typescript-eslint/parser",
8 | "plugins": ["@typescript-eslint"],
9 | "parserOptions": {
10 | "ecmaVersion": 2020,
11 | "project": ["tsconfig.json"]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | env:
4 | DO_NOT_TRACK: '1'
5 |
6 | on:
7 | push:
8 | branches: ['main']
9 | pull_request:
10 | branches: ['main']
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Use Node.js 20.x
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: 20.x
22 | cache: 'npm'
23 | - run: npm ci
24 | - run: npm run build
25 |
--------------------------------------------------------------------------------
/.github/workflows/update.yml:
--------------------------------------------------------------------------------
1 | name: Update ZenStack
2 |
3 | env:
4 | DO_NOT_TRACK: '1'
5 |
6 | on:
7 | workflow_dispatch:
8 | repository_dispatch:
9 | types: [zenstack-release]
10 |
11 | jobs:
12 | update:
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Update to latest ZenStack
18 | run: |
19 | git config --global user.name ymc9
20 | git config --global user.email yiming@whimslab.io
21 | npm ci
22 | npm run up
23 |
24 | - name: Build
25 | run: |
26 | npm run build
27 |
28 | - name: Commit and push
29 | run: |
30 | git add .
31 | git commit -m "chore: update to latest ZenStack" || true
32 | git push || true
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | *.log
3 | node_modules/
4 | .env.local
5 | .next
6 | .zenstack_repl*
7 | .env
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "useTabs": false,
4 | "printWidth": 120,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Debug server-side",
9 | "type": "node-terminal",
10 | "request": "launch",
11 | "command": "npm run dev"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 ZenStack Repositories
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A Collaborative Todo Sample - ZenStack + Next.js
2 |
3 | This project is a collaborative todo app built with [Next.js](https://nextjs.org) (app router), [Auth.js](https://authjs.dev/), and [ZenStack](https://zenstack.dev).
4 |
5 | > See the [pages-route](https://github.com/zenstackhq/sample-todo-nextjs-tanstack/tree/pages-route) branch for an implementation using Next.js's old pages router.
6 |
7 | In this fictitious app, users can be invited to workspaces where they can collaborate on todos. Public todo lists are visible to all members in the workspace.
8 |
9 | See a live deployment at: https://zenstack-todo.vercel.app/.
10 |
11 | ## Features:
12 |
13 | - User signup/signin
14 | - Creating workspaces and inviting members
15 | - Data segregation and permission control
16 |
17 | ## Running the sample:
18 |
19 | 1. Setup a new PostgreSQL database
20 |
21 | You can launch a PostgreSQL instance locally, or create one from a hoster like [Supabase](https://supabase.com). Create a new database for this app, and set the connection string in .env file.
22 |
23 | 1. Install dependencies
24 |
25 | ```bash
26 | npm install
27 | ```
28 |
29 | 1. Configure environment variables
30 |
31 | Copy the `.env.example` file to `.env` and set the values for your environment. Github related variables can be left empty if you don't need GitHub OAuth login.
32 |
33 | 1. Generate server and client-side code from model
34 |
35 | ```bash
36 | npm run generate
37 | ```
38 |
39 | 1. Synchronize database schema
40 |
41 | ```bash
42 | npm run db:push
43 | ```
44 |
45 | 1. Start dev server
46 |
47 | ```bash
48 | npm run dev
49 | ```
50 |
51 | For more information on using ZenStack, visit [https://zenstack.dev](https://zenstack.dev).
52 |
--------------------------------------------------------------------------------
/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import { handlers } from 'server/auth';
2 | export const { GET, POST } = handlers;
3 |
--------------------------------------------------------------------------------
/app/api/model/[...path]/route.ts:
--------------------------------------------------------------------------------
1 | import { enhance } from '@zenstackhq/runtime';
2 | import { NextRequestHandler } from '@zenstackhq/server/next';
3 | import { auth } from 'server/auth';
4 | import { prisma } from 'server/db';
5 |
6 | // create an enhanced Prisma client with user context
7 | async function getPrisma() {
8 | const authObj = await auth();
9 | if (authObj?.user) {
10 | const user = await prisma.user.findUniqueOrThrow({
11 | where: { id: authObj.user.id },
12 | include: { memberships: true },
13 | });
14 | return enhance(prisma, { user });
15 | } else {
16 | // anonymous user
17 | return enhance(prisma);
18 | }
19 | }
20 |
21 | const handler = NextRequestHandler({ getPrisma, useAppDir: true });
22 |
23 | export { handler as DELETE, handler as GET, handler as PATCH, handler as POST, handler as PUT };
24 |
--------------------------------------------------------------------------------
/app/create-space/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4 | /* eslint-disable @typescript-eslint/no-explicit-any */
5 | import { SpaceUserRole } from '@prisma/client';
6 | import WithNavBar from 'components/WithNavBar';
7 | import { useCreateSpace } from 'lib/hooks';
8 | import { NextPage } from 'next';
9 | import { useSession } from 'next-auth/react';
10 | import { useRouter } from 'next/navigation';
11 | import { FormEvent, useState } from 'react';
12 | import { toast } from 'react-toastify';
13 |
14 | const CreateSpace: NextPage = () => {
15 | const { data: session } = useSession();
16 | const [name, setName] = useState('');
17 | const [slug, setSlug] = useState('');
18 |
19 | const { mutateAsync: createAsync } = useCreateSpace();
20 | const router = useRouter();
21 |
22 | const onSubmit = async (event: FormEvent) => {
23 | event.preventDefault();
24 | try {
25 | const space = await createAsync({
26 | data: {
27 | name,
28 | slug,
29 | members: {
30 | create: [
31 | {
32 | userId: session!.user.id,
33 | role: SpaceUserRole.ADMIN,
34 | },
35 | ],
36 | },
37 | },
38 | });
39 | console.log('Space created:', space);
40 | toast.success("Space created successfully! You'll be redirected.");
41 |
42 | setTimeout(() => {
43 | if (space) {
44 | void router.push(`/space/${space.slug}`);
45 | }
46 | }, 2000);
47 | } catch (err: any) {
48 | console.error(err);
49 | if (err.info?.prisma === true) {
50 | if (err.info.code === 'P2002') {
51 | toast.error('Space slug already in use');
52 | } else {
53 | toast.error(`Unexpected Prisma error: ${err.info.code}`);
54 | }
55 | } else {
56 | toast.error(JSON.stringify(err));
57 | }
58 | }
59 | };
60 |
61 | return (
62 |
63 |
109 |
110 | );
111 | };
112 |
113 | export default CreateSpace;
114 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import 'assets/styles/globals.css';
4 | import { ToastContainer } from 'react-toastify';
5 | import 'react-toastify/dist/ReactToastify.css';
6 | import Providers from './providers';
7 |
8 | export default function RootLayout({ children }: { children: React.ReactNode }) {
9 | return (
10 |
11 |
12 |
13 | {children}
14 |
15 | {' '}
16 |
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Spaces from 'components/Spaces';
4 | import WithNavBar from 'components/WithNavBar';
5 | import { useCurrentUser } from 'lib/context';
6 | import { useFindManySpace } from 'lib/hooks';
7 | import Link from 'next/link';
8 |
9 | const Home = () => {
10 | const user = useCurrentUser();
11 | const { data: spaces } = useFindManySpace();
12 |
13 | if (!spaces || !user) {
14 | return Loading...
;
15 | }
16 |
17 | return (
18 |
19 |
20 |
Welcome {user.name || user.email}!
21 |
22 |
23 |
24 | Choose a space to start, or{' '}
25 |
26 | create a new one.
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default Home;
37 |
--------------------------------------------------------------------------------
/app/providers.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4 | import { SpaceContext, useCurrentSpace, useCurrentUser, UserContext } from 'lib/context';
5 | import { SessionProvider } from 'next-auth/react';
6 | import type { ReactNode } from 'react';
7 |
8 | const queryClient = new QueryClient();
9 |
10 | function UserSpaceProvider({ children }: { children: ReactNode }) {
11 | const user = useCurrentUser();
12 | const space = useCurrentSpace();
13 | return (
14 |
15 | {children}
16 |
17 | );
18 | }
19 |
20 | export default function Providers({ children }: { children: ReactNode }) {
21 | return (
22 |
23 |
24 | {children}
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/app/signin/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { signIn } from 'next-auth/react';
4 | import Image from 'next/image';
5 | import Link from 'next/link';
6 | import { useSearchParams } from 'next/navigation';
7 | import { FormEvent, Suspense, useEffect, useState } from 'react';
8 | import { toast } from 'react-toastify';
9 |
10 | function OAuthError() {
11 | const params = useSearchParams();
12 |
13 | useEffect(() => {
14 | const error = params.get('error');
15 | if (error) {
16 | if (error === 'OAuthAccountNotLinked') {
17 | toast.error('Unable to signin. The user email may be already in use.');
18 | } else {
19 | toast.error(`Authentication error: ${error.toString()}`);
20 | }
21 | }
22 | }, [params]);
23 | return null;
24 | }
25 |
26 | export default function Signin() {
27 | const [email, setEmail] = useState('');
28 | const [password, setPassword] = useState('');
29 |
30 | async function onSignin(e: FormEvent) {
31 | e.preventDefault();
32 | const signInResult = await signIn('credentials', {
33 | redirect: false,
34 | email,
35 | password,
36 | });
37 | if (!signInResult?.error) {
38 | window.location.href = '/';
39 | } else {
40 | toast.error(`Signin failed. Please check your email and password.`);
41 | }
42 | }
43 |
44 | return (
45 |
46 |
47 |
48 |
49 |
Welcome to Todo
50 |
51 |
52 |
53 |
54 |
Sign in to your account
55 |
56 |
122 |
123 |
124 |
125 | {/* Suspense is needed because 🤔: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout */}
126 |
127 |
128 |
129 | );
130 | }
131 |
--------------------------------------------------------------------------------
/app/signup/page.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
2 | /* eslint-disable @typescript-eslint/no-explicit-any */
3 | 'use client';
4 |
5 | import { useCreateUser } from 'lib/hooks';
6 | import { signIn } from 'next-auth/react';
7 | import Image from 'next/image';
8 | import Link from 'next/link';
9 | import { FormEvent, useState } from 'react';
10 | import { toast } from 'react-toastify';
11 |
12 | export default function Signup() {
13 | const [email, setEmail] = useState('');
14 | const [password, setPassword] = useState('');
15 | const signup = useCreateUser();
16 |
17 | async function onSignup(e: FormEvent) {
18 | e.preventDefault();
19 | try {
20 | await signup.mutateAsync({ data: { email, password } });
21 | } catch (err: any) {
22 | console.error(err);
23 | if (err.info?.prisma === true) {
24 | if (err.info.code === 'P2002') {
25 | toast.error('User already exists');
26 | } else {
27 | toast.error(`Unexpected Prisma error: ${err.info.code}`);
28 | }
29 | } else {
30 | toast.error(`Error occurred: ${JSON.stringify(err)}`);
31 | }
32 | return;
33 | }
34 |
35 | const signInResult = await signIn('credentials', {
36 | redirect: false,
37 | email,
38 | password,
39 | });
40 | if (signInResult?.ok) {
41 | window.location.href = '/';
42 | } else {
43 | console.error('Signin failed:', signInResult?.error);
44 | }
45 | }
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
Welcome to Todo
53 |
54 |
55 |
56 |
57 |
Create a Free Account
58 |
117 |
118 |
119 |
120 | );
121 | }
122 |
--------------------------------------------------------------------------------
/app/space/[slug]/[listId]/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { PlusIcon } from '@heroicons/react/24/outline';
4 | import BreadCrumb from 'components/BreadCrumb';
5 | import TodoComponent from 'components/Todo';
6 | import WithNavBar from 'components/WithNavBar';
7 | import { useCurrentSpace } from 'lib/context';
8 | import { useCreateTodo, useFindManyTodo, useFindUniqueList } from 'lib/hooks';
9 | import { useParams } from 'next/navigation';
10 | import { ChangeEvent, KeyboardEvent, useState } from 'react';
11 |
12 | export default function TodoList() {
13 | const [title, setTitle] = useState('');
14 | const { mutate: createTodo } = useCreateTodo({ optimisticUpdate: true });
15 | const params = useParams<{ listId: string }>();
16 | const space = useCurrentSpace();
17 |
18 | const { data: list } = useFindUniqueList({ where: { id: params.listId } });
19 |
20 | const { data } = useFindManyTodo({
21 | where: { listId: params.listId },
22 | include: {
23 | owner: true,
24 | },
25 | orderBy: {
26 | createdAt: 'desc' as const,
27 | },
28 | });
29 |
30 | const onCreateTodo = () => {
31 | if (!title) {
32 | return;
33 | }
34 | setTitle('');
35 | createTodo({
36 | data: {
37 | title,
38 | list: { connect: { id: params.listId } },
39 | },
40 | });
41 | };
42 |
43 | if (!space || !list) {
44 | return <>>;
45 | }
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 |
{list.title}
54 |
73 |
74 | {data?.map((todo) => (
75 |
76 | ))}
77 |
78 |
79 |
80 | );
81 | }
82 |
--------------------------------------------------------------------------------
/app/space/[slug]/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4 | /* eslint-disable @typescript-eslint/no-explicit-any */
5 | import BreadCrumb from 'components/BreadCrumb';
6 | import SpaceMembers from 'components/SpaceMembers';
7 | import TodoList from 'components/TodoList';
8 | import WithNavBar from 'components/WithNavBar';
9 | import { useCurrentSpace } from 'lib/context';
10 | import { useCreateList, useFindManyList } from 'lib/hooks';
11 | import { useParams } from 'next/navigation';
12 | import { ChangeEvent, FormEvent, useRef, useState } from 'react';
13 | import { toast } from 'react-toastify';
14 |
15 | function CreateDialog() {
16 | const space = useCurrentSpace();
17 |
18 | const [modalOpen, setModalOpen] = useState(false);
19 | const [title, setTitle] = useState('');
20 | const [_private, setPrivate] = useState(false);
21 |
22 | const create = useCreateList();
23 |
24 | const inputRef = useRef(null);
25 |
26 | const onSubmit = async (event: FormEvent) => {
27 | event.preventDefault();
28 |
29 | try {
30 | await create.mutateAsync({
31 | data: {
32 | title,
33 | private: _private,
34 | space: { connect: { id: space!.id } },
35 | },
36 | });
37 | } catch (err: any) {
38 | toast.error(`Failed to create list: ${err.info?.message || err.message}`);
39 | return;
40 | }
41 |
42 | toast.success('List created successfully!');
43 |
44 | // reset states
45 | setTitle('');
46 | setPrivate(false);
47 |
48 | // close modal
49 | setModalOpen(false);
50 | };
51 |
52 | return (
53 | <>
54 | ) => {
60 | setModalOpen(e.currentTarget.checked);
61 | }}
62 | />
63 |
64 |
65 |
Create a Todo list
66 |
102 |
103 |
104 | >
105 | );
106 | }
107 |
108 | export default function SpaceHome() {
109 | const params = useParams<{ slug: string }>();
110 | const space = useCurrentSpace();
111 |
112 | const { data: lists } = useFindManyList({
113 | where: {
114 | space: { slug: params.slug },
115 | },
116 | include: {
117 | owner: true,
118 | },
119 | orderBy: {
120 | updatedAt: 'desc',
121 | },
122 | });
123 |
124 | if (!space) {
125 | return Loading...
;
126 | }
127 |
128 | return (
129 |
130 |
131 |
132 |
133 |
134 |
135 |
138 |
139 |
140 |
141 |
142 | {lists?.map((list) => (
143 | -
144 |
145 |
146 | ))}
147 |
148 |
149 |
150 |
151 |
152 | );
153 | }
154 |
--------------------------------------------------------------------------------
/assets/styles/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | body {
6 | @apply text-gray-800;
7 | }
8 |
--------------------------------------------------------------------------------
/components/Avatar.tsx:
--------------------------------------------------------------------------------
1 | import { User } from 'next-auth';
2 | import Image from 'next/image';
3 |
4 | type Props = {
5 | user: User;
6 | size?: number;
7 | };
8 |
9 | export default function Avatar({ user, size }: Props) {
10 | if (!user) {
11 | return <>>;
12 | }
13 | return (
14 |
15 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/components/BreadCrumb.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { List, Space } from '@prisma/client';
4 | import Link from 'next/link';
5 | import { usePathname } from 'next/navigation';
6 |
7 | type Props = {
8 | space: Space;
9 | list?: List;
10 | };
11 |
12 | export default function BreadCrumb({ space, list }: Props) {
13 | const path = usePathname();
14 | const parts = path.split('/').filter((p) => p);
15 | const [base] = parts;
16 | if (base !== 'space') {
17 | return <>>;
18 | }
19 |
20 | const items: Array<{ text: string; link: string }> = [];
21 |
22 | items.push({ text: 'Home', link: '/' });
23 | items.push({ text: space.name || '', link: `/space/${space.slug}` });
24 |
25 | if (list) {
26 | items.push({
27 | text: list?.title || '',
28 | link: `/space/${space.slug}/${list.id}`,
29 | });
30 | }
31 |
32 | return (
33 |
34 |
35 | {items.map((item, i) => (
36 | -
37 | {item.text}
38 |
39 | ))}
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/components/ManageMembers.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4 | /* eslint-disable @typescript-eslint/no-explicit-any */
5 | import { PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
6 | import { Space, SpaceUserRole } from '@prisma/client';
7 | import { useCurrentUser } from 'lib/context';
8 | import { useCreateSpaceUser, useDeleteSpaceUser, useFindManySpaceUser } from 'lib/hooks';
9 | import { ChangeEvent, KeyboardEvent, useState } from 'react';
10 | import { toast } from 'react-toastify';
11 | import Avatar from './Avatar';
12 |
13 | type Props = {
14 | space: Space;
15 | };
16 |
17 | export default function ManageMembers({ space }: Props) {
18 | const [email, setEmail] = useState('');
19 | const [role, setRole] = useState(SpaceUserRole.USER);
20 | const user = useCurrentUser();
21 | const { mutateAsync: createMember } = useCreateSpaceUser();
22 | const { mutate: deleteMember } = useDeleteSpaceUser();
23 |
24 | const { data: members } = useFindManySpaceUser({
25 | where: {
26 | spaceId: space.id,
27 | },
28 | include: {
29 | user: true,
30 | },
31 | orderBy: {
32 | role: 'desc',
33 | },
34 | });
35 |
36 | const inviteUser = async () => {
37 | try {
38 | const r = await createMember({
39 | data: {
40 | user: {
41 | connect: {
42 | email,
43 | },
44 | },
45 | space: {
46 | connect: {
47 | id: space.id,
48 | },
49 | },
50 | role,
51 | },
52 | });
53 | console.log('SpaceUser created:', r);
54 | } catch (err: any) {
55 | console.error(err);
56 | if (err.info?.prisma === true) {
57 | if (err.info.code === 'P2002') {
58 | toast.error('User is already a member of the space');
59 | } else if (err.info.code === 'P2025') {
60 | toast.error('User is not found for this email');
61 | } else {
62 | toast.error(`Unexpected Prisma error: ${err.info.code}`);
63 | }
64 | } else {
65 | toast.error(`Error occurred: ${JSON.stringify(err)}`);
66 | }
67 | }
68 | };
69 |
70 | const removeMember = (id: string) => {
71 | if (confirm(`Are you sure to remove this member from space?`)) {
72 | void deleteMember({ where: { id } });
73 | }
74 | };
75 |
76 | return (
77 |
134 | );
135 | }
136 |
--------------------------------------------------------------------------------
/components/NavBar.tsx:
--------------------------------------------------------------------------------
1 | import { Space } from '@prisma/client';
2 | import { User } from 'next-auth';
3 | import { signOut } from 'next-auth/react';
4 | import Image from 'next/image';
5 | import Link from 'next/link';
6 | import Avatar from './Avatar';
7 |
8 | type Props = {
9 | space: Space | undefined;
10 | user: User | undefined;
11 | };
12 |
13 | export default function NavBar({ user, space }: Props) {
14 | const onSignout = () => {
15 | void signOut({ callbackUrl: '/signin' });
16 | };
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 | {space?.name || 'Welcome Todo App'}
25 |
26 |
Powered by ZenStack
27 |
28 |
29 |
30 |
31 |
34 |
43 |
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/components/SpaceMembers.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { PlusIcon } from '@heroicons/react/24/outline';
4 | import { Space } from '@prisma/client';
5 | import { useCurrentSpace } from 'lib/context';
6 | import { useFindManySpaceUser } from 'lib/hooks';
7 | import Avatar from './Avatar';
8 | import ManageMembers from './ManageMembers';
9 |
10 | function ManagementDialog(space?: Space) {
11 | if (!space) return undefined;
12 | return (
13 | <>
14 |
17 |
18 |
19 |
20 |
21 |
Manage Members of {space.name}
22 |
23 |
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 | >
35 | );
36 | }
37 |
38 | export default function SpaceMembers() {
39 | const space = useCurrentSpace();
40 |
41 | const { data: members } = useFindManySpaceUser(
42 | {
43 | where: {
44 | spaceId: space?.id,
45 | },
46 | include: {
47 | user: true,
48 | },
49 | orderBy: {
50 | role: 'desc',
51 | },
52 | },
53 | { enabled: !!space }
54 | );
55 |
56 | return (
57 |
58 | {ManagementDialog(space)}
59 | {members && (
60 |
65 | )}
66 |
67 | );
68 | }
69 |
--------------------------------------------------------------------------------
/components/Spaces.tsx:
--------------------------------------------------------------------------------
1 | import { Space } from '@prisma/client';
2 | import { useCountList } from 'lib/hooks';
3 | import Link from 'next/link';
4 |
5 | type Props = {
6 | spaces: Space[];
7 | };
8 |
9 | function SpaceItem({ space }: { space: Space }) {
10 | const { data: listCount } = useCountList({
11 | where: { spaceId: space.id },
12 | });
13 | return (
14 |
15 |
{listCount}
16 |
17 |
18 |
{space.name}
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default function Spaces({ spaces }: Props) {
26 | return (
27 |
28 | {spaces?.map((space) => (
29 | -
33 |
34 |
35 | ))}
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/components/TimeInfo.tsx:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 |
3 | type Props = {
4 | value: { createdAt: Date; updatedAt: Date; completedAt?: Date | null };
5 | };
6 |
7 | export default function TimeInfo({ value }: Props) {
8 | return (
9 |
10 | {value.completedAt
11 | ? `Completed ${moment(value.completedAt).fromNow()}`
12 | : value.createdAt === value.updatedAt
13 | ? `Created ${moment(value.createdAt).fromNow()}`
14 | : `Updated ${moment(value.updatedAt).fromNow()}`}
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/components/Todo.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { TrashIcon } from '@heroicons/react/24/outline';
4 | import { Todo, User } from '@prisma/client';
5 | import { useDeleteTodo, useUpdateTodo } from 'lib/hooks';
6 | import { ChangeEvent } from 'react';
7 | import Avatar from './Avatar';
8 | import TimeInfo from './TimeInfo';
9 |
10 | type Props = {
11 | value: Todo & { owner: User };
12 | optimistic?: boolean;
13 | };
14 |
15 | export default function TodoComponent({ value, optimistic }: Props) {
16 | const { mutate: updateTodo } = useUpdateTodo({ optimisticUpdate: true });
17 | const { mutate: deleteTodo } = useDeleteTodo({ optimisticUpdate: true });
18 |
19 | const onDelete = () => {
20 | deleteTodo({ where: { id: value.id } });
21 | };
22 |
23 | const onToggleCompleted = (completed: boolean) => {
24 | if (completed === !!value.completedAt) {
25 | return;
26 | }
27 | updateTodo({
28 | where: { id: value.id },
29 | data: { completedAt: completed ? new Date() : null },
30 | });
31 | };
32 |
33 | return (
34 |
35 |
36 |
41 | {value.title}
42 | {optimistic && }
43 |
44 |
45 | ) => onToggleCompleted(e.currentTarget.checked)}
50 | />
51 |
52 |
53 |
54 |
58 |
59 | );
60 | }
61 |
--------------------------------------------------------------------------------
/components/TodoList.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { LockClosedIcon, TrashIcon } from '@heroicons/react/24/outline';
4 | import { List } from '@prisma/client';
5 | import { useCheckList, useDeleteList } from 'lib/hooks';
6 | import { customAlphabet } from 'nanoid';
7 | import { User } from 'next-auth';
8 | import Image from 'next/image';
9 | import Link from 'next/link';
10 | import { usePathname } from 'next/navigation';
11 | import Avatar from './Avatar';
12 | import TimeInfo from './TimeInfo';
13 |
14 | type Props = {
15 | value: List & { owner: User };
16 | };
17 |
18 | export default function TodoList({ value }: Props) {
19 | const path = usePathname();
20 | // check if the current user can delete the list (based on its owner)
21 | const { data: canDelete } = useCheckList({ operation: 'delete', where: { ownerId: value.ownerId } });
22 |
23 | const { mutate: deleteList } = useDeleteList();
24 |
25 | const onDelete = () => {
26 | if (confirm('Are you sure to delete this list?')) {
27 | deleteList({ where: { id: value.id } });
28 | }
29 | };
30 |
31 | return (
32 |
33 |
34 |
35 |
42 |
43 |
44 |
45 |
46 |
{value.title || 'Missing Title'}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {value.private && (
55 |
56 |
57 |
58 | )}
59 | {canDelete &&
}
60 |
61 |
62 |
63 |
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/components/WithNavBar.tsx:
--------------------------------------------------------------------------------
1 | import { useCurrentSpace, useCurrentUser } from 'lib/context';
2 | import NavBar from './NavBar';
3 |
4 | type Props = {
5 | children: JSX.Element | JSX.Element[] | undefined;
6 | };
7 |
8 | export default function WithNavBar({ children }: Props) {
9 | const user = useCurrentUser();
10 | const space = useCurrentSpace();
11 |
12 | return (
13 | <>
14 |
15 | {children}
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/lib/context.ts:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { Space } from '@prisma/client';
4 | import { User } from 'next-auth';
5 | import { useSession } from 'next-auth/react';
6 | import { useParams } from 'next/navigation';
7 | import { createContext } from 'react';
8 | import { useFindManySpace } from './hooks';
9 |
10 | export const UserContext = createContext(undefined);
11 |
12 | export function useCurrentUser() {
13 | const { data: session } = useSession();
14 | return session?.user;
15 | }
16 |
17 | export const SpaceContext = createContext(undefined);
18 |
19 | export function useCurrentSpace() {
20 | const params = useParams<{ slug: string }>();
21 | const { data: spaces } = useFindManySpace(
22 | {
23 | where: { slug: params.slug },
24 | },
25 | {
26 | enabled: !!params.slug,
27 | }
28 | );
29 |
30 | return spaces?.[0];
31 | }
32 |
--------------------------------------------------------------------------------
/lib/hooks/__model_meta.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 | // @ts-nocheck
7 |
8 | const metadata = {
9 | models: {
10 | space: {
11 | name: 'Space', fields: {
12 | id: {
13 | name: "id",
14 | type: "String",
15 | isId: true,
16 | attributes: [{ "name": "@default", "args": [] }],
17 | }, createdAt: {
18 | name: "createdAt",
19 | type: "DateTime",
20 | attributes: [{ "name": "@default", "args": [] }],
21 | }, updatedAt: {
22 | name: "updatedAt",
23 | type: "DateTime",
24 | attributes: [{ "name": "@updatedAt", "args": [] }],
25 | }, owner: {
26 | name: "owner",
27 | type: "User",
28 | isDataModel: true,
29 | backLink: 'ownedSpaces',
30 | isRelationOwner: true,
31 | onDeleteAction: 'Cascade',
32 | foreignKeyMapping: { "id": "ownerId" },
33 | }, ownerId: {
34 | name: "ownerId",
35 | type: "String",
36 | attributes: [{ "name": "@default", "args": [] }],
37 | defaultValueProvider: $default$Space$ownerId,
38 | isForeignKey: true,
39 | relationField: 'owner',
40 | }, name: {
41 | name: "name",
42 | type: "String",
43 | }, slug: {
44 | name: "slug",
45 | type: "String",
46 | }, members: {
47 | name: "members",
48 | type: "SpaceUser",
49 | isDataModel: true,
50 | isArray: true,
51 | backLink: 'space',
52 | }, lists: {
53 | name: "lists",
54 | type: "List",
55 | isDataModel: true,
56 | isArray: true,
57 | backLink: 'space',
58 | },
59 | }, uniqueConstraints: {
60 | id: {
61 | name: "id",
62 | fields: ["id"]
63 | }, slug: {
64 | name: "slug",
65 | fields: ["slug"]
66 | },
67 | },
68 | },
69 | spaceUser: {
70 | name: 'SpaceUser', fields: {
71 | id: {
72 | name: "id",
73 | type: "String",
74 | isId: true,
75 | attributes: [{ "name": "@default", "args": [] }],
76 | }, createdAt: {
77 | name: "createdAt",
78 | type: "DateTime",
79 | attributes: [{ "name": "@default", "args": [] }],
80 | }, updatedAt: {
81 | name: "updatedAt",
82 | type: "DateTime",
83 | attributes: [{ "name": "@updatedAt", "args": [] }],
84 | }, space: {
85 | name: "space",
86 | type: "Space",
87 | isDataModel: true,
88 | backLink: 'members',
89 | isRelationOwner: true,
90 | onDeleteAction: 'Cascade',
91 | foreignKeyMapping: { "id": "spaceId" },
92 | }, spaceId: {
93 | name: "spaceId",
94 | type: "String",
95 | isForeignKey: true,
96 | relationField: 'space',
97 | }, user: {
98 | name: "user",
99 | type: "User",
100 | isDataModel: true,
101 | backLink: 'memberships',
102 | isRelationOwner: true,
103 | onDeleteAction: 'Cascade',
104 | foreignKeyMapping: { "id": "userId" },
105 | }, userId: {
106 | name: "userId",
107 | type: "String",
108 | isForeignKey: true,
109 | relationField: 'user',
110 | }, role: {
111 | name: "role",
112 | type: "SpaceUserRole",
113 | },
114 | }, uniqueConstraints: {
115 | id: {
116 | name: "id",
117 | fields: ["id"]
118 | }, userId_spaceId: {
119 | name: "userId_spaceId",
120 | fields: ["userId", "spaceId"]
121 | },
122 | },
123 | },
124 | user: {
125 | name: 'User', fields: {
126 | id: {
127 | name: "id",
128 | type: "String",
129 | isId: true,
130 | attributes: [{ "name": "@default", "args": [] }],
131 | }, createdAt: {
132 | name: "createdAt",
133 | type: "DateTime",
134 | attributes: [{ "name": "@default", "args": [] }],
135 | }, updatedAt: {
136 | name: "updatedAt",
137 | type: "DateTime",
138 | attributes: [{ "name": "@updatedAt", "args": [] }],
139 | }, email: {
140 | name: "email",
141 | type: "String",
142 | }, emailVerified: {
143 | name: "emailVerified",
144 | type: "DateTime",
145 | isOptional: true,
146 | }, password: {
147 | name: "password",
148 | type: "String",
149 | isOptional: true,
150 | }, name: {
151 | name: "name",
152 | type: "String",
153 | isOptional: true,
154 | }, ownedSpaces: {
155 | name: "ownedSpaces",
156 | type: "Space",
157 | isDataModel: true,
158 | isArray: true,
159 | backLink: 'owner',
160 | }, memberships: {
161 | name: "memberships",
162 | type: "SpaceUser",
163 | isDataModel: true,
164 | isArray: true,
165 | backLink: 'user',
166 | }, image: {
167 | name: "image",
168 | type: "String",
169 | isOptional: true,
170 | }, lists: {
171 | name: "lists",
172 | type: "List",
173 | isDataModel: true,
174 | isArray: true,
175 | backLink: 'owner',
176 | }, todos: {
177 | name: "todos",
178 | type: "Todo",
179 | isDataModel: true,
180 | isArray: true,
181 | backLink: 'owner',
182 | }, accounts: {
183 | name: "accounts",
184 | type: "Account",
185 | isDataModel: true,
186 | isArray: true,
187 | backLink: 'user',
188 | },
189 | }, uniqueConstraints: {
190 | id: {
191 | name: "id",
192 | fields: ["id"]
193 | }, email: {
194 | name: "email",
195 | fields: ["email"]
196 | },
197 | },
198 | },
199 | list: {
200 | name: 'List', fields: {
201 | id: {
202 | name: "id",
203 | type: "String",
204 | isId: true,
205 | attributes: [{ "name": "@default", "args": [] }],
206 | }, createdAt: {
207 | name: "createdAt",
208 | type: "DateTime",
209 | attributes: [{ "name": "@default", "args": [] }],
210 | }, updatedAt: {
211 | name: "updatedAt",
212 | type: "DateTime",
213 | attributes: [{ "name": "@updatedAt", "args": [] }],
214 | }, space: {
215 | name: "space",
216 | type: "Space",
217 | isDataModel: true,
218 | backLink: 'lists',
219 | isRelationOwner: true,
220 | onDeleteAction: 'Cascade',
221 | foreignKeyMapping: { "id": "spaceId" },
222 | }, spaceId: {
223 | name: "spaceId",
224 | type: "String",
225 | isForeignKey: true,
226 | relationField: 'space',
227 | }, owner: {
228 | name: "owner",
229 | type: "User",
230 | isDataModel: true,
231 | backLink: 'lists',
232 | isRelationOwner: true,
233 | onDeleteAction: 'Cascade',
234 | foreignKeyMapping: { "id": "ownerId" },
235 | }, ownerId: {
236 | name: "ownerId",
237 | type: "String",
238 | attributes: [{ "name": "@default", "args": [] }],
239 | defaultValueProvider: $default$List$ownerId,
240 | isForeignKey: true,
241 | relationField: 'owner',
242 | }, title: {
243 | name: "title",
244 | type: "String",
245 | }, private: {
246 | name: "private",
247 | type: "Boolean",
248 | attributes: [{ "name": "@default", "args": [{ "value": false }] }],
249 | }, todos: {
250 | name: "todos",
251 | type: "Todo",
252 | isDataModel: true,
253 | isArray: true,
254 | backLink: 'list',
255 | },
256 | }, uniqueConstraints: {
257 | id: {
258 | name: "id",
259 | fields: ["id"]
260 | },
261 | },
262 | },
263 | todo: {
264 | name: 'Todo', fields: {
265 | id: {
266 | name: "id",
267 | type: "String",
268 | isId: true,
269 | attributes: [{ "name": "@default", "args": [] }],
270 | }, createdAt: {
271 | name: "createdAt",
272 | type: "DateTime",
273 | attributes: [{ "name": "@default", "args": [] }],
274 | }, updatedAt: {
275 | name: "updatedAt",
276 | type: "DateTime",
277 | attributes: [{ "name": "@updatedAt", "args": [] }],
278 | }, owner: {
279 | name: "owner",
280 | type: "User",
281 | isDataModel: true,
282 | backLink: 'todos',
283 | isRelationOwner: true,
284 | onDeleteAction: 'Cascade',
285 | foreignKeyMapping: { "id": "ownerId" },
286 | }, ownerId: {
287 | name: "ownerId",
288 | type: "String",
289 | attributes: [{ "name": "@default", "args": [] }],
290 | defaultValueProvider: $default$Todo$ownerId,
291 | isForeignKey: true,
292 | relationField: 'owner',
293 | }, list: {
294 | name: "list",
295 | type: "List",
296 | isDataModel: true,
297 | backLink: 'todos',
298 | isRelationOwner: true,
299 | onDeleteAction: 'Cascade',
300 | foreignKeyMapping: { "id": "listId" },
301 | }, listId: {
302 | name: "listId",
303 | type: "String",
304 | isForeignKey: true,
305 | relationField: 'list',
306 | }, title: {
307 | name: "title",
308 | type: "String",
309 | }, completedAt: {
310 | name: "completedAt",
311 | type: "DateTime",
312 | isOptional: true,
313 | },
314 | }, uniqueConstraints: {
315 | id: {
316 | name: "id",
317 | fields: ["id"]
318 | },
319 | },
320 | },
321 | account: {
322 | name: 'Account', fields: {
323 | id: {
324 | name: "id",
325 | type: "String",
326 | isId: true,
327 | attributes: [{ "name": "@default", "args": [] }],
328 | }, userId: {
329 | name: "userId",
330 | type: "String",
331 | isForeignKey: true,
332 | relationField: 'user',
333 | }, type: {
334 | name: "type",
335 | type: "String",
336 | }, provider: {
337 | name: "provider",
338 | type: "String",
339 | }, providerAccountId: {
340 | name: "providerAccountId",
341 | type: "String",
342 | }, refresh_token: {
343 | name: "refresh_token",
344 | type: "String",
345 | isOptional: true,
346 | }, refresh_token_expires_in: {
347 | name: "refresh_token_expires_in",
348 | type: "Int",
349 | isOptional: true,
350 | }, access_token: {
351 | name: "access_token",
352 | type: "String",
353 | isOptional: true,
354 | }, expires_at: {
355 | name: "expires_at",
356 | type: "Int",
357 | isOptional: true,
358 | }, token_type: {
359 | name: "token_type",
360 | type: "String",
361 | isOptional: true,
362 | }, scope: {
363 | name: "scope",
364 | type: "String",
365 | isOptional: true,
366 | }, id_token: {
367 | name: "id_token",
368 | type: "String",
369 | isOptional: true,
370 | }, session_state: {
371 | name: "session_state",
372 | type: "String",
373 | isOptional: true,
374 | }, user: {
375 | name: "user",
376 | type: "User",
377 | isDataModel: true,
378 | backLink: 'accounts',
379 | isRelationOwner: true,
380 | onDeleteAction: 'Cascade',
381 | foreignKeyMapping: { "id": "userId" },
382 | },
383 | }, uniqueConstraints: {
384 | id: {
385 | name: "id",
386 | fields: ["id"]
387 | }, provider_providerAccountId: {
388 | name: "provider_providerAccountId",
389 | fields: ["provider", "providerAccountId"]
390 | },
391 | },
392 | },
393 |
394 | },
395 | deleteCascade: {
396 | space: ['SpaceUser', 'List'],
397 | user: ['Space', 'SpaceUser', 'List', 'Todo', 'Account'],
398 | list: ['Todo'],
399 |
400 | },
401 | authModel: 'User'
402 |
403 | };
404 |
405 | function $default$Space$ownerId(user: any): unknown {
406 | return user?.id;
407 | }
408 |
409 | function $default$List$ownerId(user: any): unknown {
410 | return user?.id;
411 | }
412 |
413 | function $default$Todo$ownerId(user: any): unknown {
414 | return user?.id;
415 | }
416 | export default metadata;
417 |
--------------------------------------------------------------------------------
/lib/hooks/account.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 | // @ts-nocheck
7 |
8 | import type { Prisma, Account } from "@zenstackhq/runtime/models";
9 | import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/react-query';
10 | import { getHooksContext } from '@zenstackhq/tanstack-query/runtime-v5/react';
11 | import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '@zenstackhq/tanstack-query/runtime-v5/react';
12 | import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '@zenstackhq/tanstack-query/runtime-v5';
13 | import type { PolicyCrudKind } from '@zenstackhq/runtime'
14 | import metadata from './__model_meta';
15 | type DefaultError = QueryError;
16 | import { useSuspenseModelQuery, useSuspenseInfiniteModelQuery } from '@zenstackhq/tanstack-query/runtime-v5/react';
17 | import type { UseSuspenseQueryOptions, UseSuspenseInfiniteQueryOptions } from '@tanstack/react-query';
18 |
19 | export function useCreateAccount(options?: Omit<(UseMutationOptions<(Account | undefined), DefaultError, Prisma.AccountCreateArgs> & ExtraMutationOptions), 'mutationFn'>) {
20 | const { endpoint, fetch } = getHooksContext();
21 | const _mutation =
22 | useModelMutation('Account', 'POST', `${endpoint}/account/create`, metadata, options, fetch, true)
23 | ;
24 | const mutation = {
25 | ..._mutation,
26 | mutateAsync: async (
27 | args: Prisma.SelectSubset,
28 | options?: Omit<(UseMutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
29 | ) => {
30 | return (await _mutation.mutateAsync(
31 | args,
32 | options as any
33 | )) as (CheckSelect> | undefined);
34 | },
35 | };
36 | return mutation;
37 | }
38 |
39 | export function useCreateManyAccount(options?: Omit<(UseMutationOptions & ExtraMutationOptions), 'mutationFn'>) {
40 | const { endpoint, fetch } = getHooksContext();
41 | const _mutation =
42 | useModelMutation('Account', 'POST', `${endpoint}/account/createMany`, metadata, options, fetch, false)
43 | ;
44 | const mutation = {
45 | ..._mutation,
46 | mutateAsync: async (
47 | args: Prisma.SelectSubset,
48 | options?: Omit<(UseMutationOptions> & ExtraMutationOptions), 'mutationFn'>
49 | ) => {
50 | return (await _mutation.mutateAsync(
51 | args,
52 | options as any
53 | )) as Prisma.BatchPayload;
54 | },
55 | };
56 | return mutation;
57 | }
58 |
59 | export function useFindManyAccount & { $optimistic?: boolean }>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
60 | const { endpoint, fetch } = getHooksContext();
61 | return useModelQuery('Account', `${endpoint}/account/findMany`, args, options, fetch);
62 | }
63 |
64 | export function useInfiniteFindManyAccount>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: Omit>, 'queryKey' | 'initialPageParam'>) {
65 | options = options ?? { getNextPageParam: () => null };
66 | const { endpoint, fetch } = getHooksContext();
67 | return useInfiniteModelQuery('Account', `${endpoint}/account/findMany`, args, options, fetch);
68 | }
69 |
70 | export function useSuspenseFindManyAccount & { $optimistic?: boolean }>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
71 | const { endpoint, fetch } = getHooksContext();
72 | return useSuspenseModelQuery('Account', `${endpoint}/account/findMany`, args, options, fetch);
73 | }
74 |
75 | export function useSuspenseInfiniteFindManyAccount>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: Omit>, 'queryKey' | 'initialPageParam'>) {
76 | options = options ?? { getNextPageParam: () => null };
77 | const { endpoint, fetch } = getHooksContext();
78 | return useSuspenseInfiniteModelQuery('Account', `${endpoint}/account/findMany`, args, options, fetch);
79 | }
80 |
81 | export function useFindUniqueAccount & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
82 | const { endpoint, fetch } = getHooksContext();
83 | return useModelQuery('Account', `${endpoint}/account/findUnique`, args, options, fetch);
84 | }
85 |
86 | export function useSuspenseFindUniqueAccount & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
87 | const { endpoint, fetch } = getHooksContext();
88 | return useSuspenseModelQuery('Account', `${endpoint}/account/findUnique`, args, options, fetch);
89 | }
90 |
91 | export function useFindFirstAccount & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
92 | const { endpoint, fetch } = getHooksContext();
93 | return useModelQuery('Account', `${endpoint}/account/findFirst`, args, options, fetch);
94 | }
95 |
96 | export function useSuspenseFindFirstAccount & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
97 | const { endpoint, fetch } = getHooksContext();
98 | return useSuspenseModelQuery('Account', `${endpoint}/account/findFirst`, args, options, fetch);
99 | }
100 |
101 | export function useUpdateAccount(options?: Omit<(UseMutationOptions<(Account | undefined), DefaultError, Prisma.AccountUpdateArgs> & ExtraMutationOptions), 'mutationFn'>) {
102 | const { endpoint, fetch } = getHooksContext();
103 | const _mutation =
104 | useModelMutation('Account', 'PUT', `${endpoint}/account/update`, metadata, options, fetch, true)
105 | ;
106 | const mutation = {
107 | ..._mutation,
108 | mutateAsync: async (
109 | args: Prisma.SelectSubset,
110 | options?: Omit<(UseMutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
111 | ) => {
112 | return (await _mutation.mutateAsync(
113 | args,
114 | options as any
115 | )) as (CheckSelect> | undefined);
116 | },
117 | };
118 | return mutation;
119 | }
120 |
121 | export function useUpdateManyAccount(options?: Omit<(UseMutationOptions & ExtraMutationOptions), 'mutationFn'>) {
122 | const { endpoint, fetch } = getHooksContext();
123 | const _mutation =
124 | useModelMutation('Account', 'PUT', `${endpoint}/account/updateMany`, metadata, options, fetch, false)
125 | ;
126 | const mutation = {
127 | ..._mutation,
128 | mutateAsync: async (
129 | args: Prisma.SelectSubset,
130 | options?: Omit<(UseMutationOptions> & ExtraMutationOptions), 'mutationFn'>
131 | ) => {
132 | return (await _mutation.mutateAsync(
133 | args,
134 | options as any
135 | )) as Prisma.BatchPayload;
136 | },
137 | };
138 | return mutation;
139 | }
140 |
141 | export function useUpsertAccount(options?: Omit<(UseMutationOptions<(Account | undefined), DefaultError, Prisma.AccountUpsertArgs> & ExtraMutationOptions), 'mutationFn'>) {
142 | const { endpoint, fetch } = getHooksContext();
143 | const _mutation =
144 | useModelMutation('Account', 'POST', `${endpoint}/account/upsert`, metadata, options, fetch, true)
145 | ;
146 | const mutation = {
147 | ..._mutation,
148 | mutateAsync: async (
149 | args: Prisma.SelectSubset,
150 | options?: Omit<(UseMutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
151 | ) => {
152 | return (await _mutation.mutateAsync(
153 | args,
154 | options as any
155 | )) as (CheckSelect> | undefined);
156 | },
157 | };
158 | return mutation;
159 | }
160 |
161 | export function useDeleteAccount(options?: Omit<(UseMutationOptions<(Account | undefined), DefaultError, Prisma.AccountDeleteArgs> & ExtraMutationOptions), 'mutationFn'>) {
162 | const { endpoint, fetch } = getHooksContext();
163 | const _mutation =
164 | useModelMutation('Account', 'DELETE', `${endpoint}/account/delete`, metadata, options, fetch, true)
165 | ;
166 | const mutation = {
167 | ..._mutation,
168 | mutateAsync: async (
169 | args: Prisma.SelectSubset,
170 | options?: Omit<(UseMutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
171 | ) => {
172 | return (await _mutation.mutateAsync(
173 | args,
174 | options as any
175 | )) as (CheckSelect> | undefined);
176 | },
177 | };
178 | return mutation;
179 | }
180 |
181 | export function useDeleteManyAccount(options?: Omit<(UseMutationOptions & ExtraMutationOptions), 'mutationFn'>) {
182 | const { endpoint, fetch } = getHooksContext();
183 | const _mutation =
184 | useModelMutation('Account', 'DELETE', `${endpoint}/account/deleteMany`, metadata, options, fetch, false)
185 | ;
186 | const mutation = {
187 | ..._mutation,
188 | mutateAsync: async (
189 | args: Prisma.SelectSubset,
190 | options?: Omit<(UseMutationOptions> & ExtraMutationOptions), 'mutationFn'>
191 | ) => {
192 | return (await _mutation.mutateAsync(
193 | args,
194 | options as any
195 | )) as Prisma.BatchPayload;
196 | },
197 | };
198 | return mutation;
199 | }
200 |
201 | export function useAggregateAccount, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
202 | const { endpoint, fetch } = getHooksContext();
203 | return useModelQuery('Account', `${endpoint}/account/aggregate`, args, options, fetch);
204 | }
205 |
206 | export function useSuspenseAggregateAccount, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
207 | const { endpoint, fetch } = getHooksContext();
208 | return useSuspenseModelQuery('Account', `${endpoint}/account/aggregate`, args, options, fetch);
209 | }
210 |
211 | export function useGroupByAccount>, Prisma.Extends<'take', Prisma.Keys>>, OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.AccountGroupByArgs['orderBy'] } : { orderBy?: Prisma.AccountGroupByArgs['orderBy'] }, OrderFields extends Prisma.ExcludeUnderscoreKeys>>, ByFields extends Prisma.MaybeTupleToUnion, ByValid extends Prisma.Has, HavingFields extends Prisma.GetHavingFields, HavingValid extends Prisma.Has, ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False, InputErrors extends ByEmpty extends Prisma.True
212 | ? `Error: "by" must not be empty.`
213 | : HavingValid extends Prisma.False
214 | ? {
215 | [P in HavingFields]: P extends ByFields
216 | ? never
217 | : P extends string
218 | ? `Error: Field "${P}" used in "having" needs to be provided in "by".`
219 | : [
220 | Error,
221 | 'Field ',
222 | P,
223 | ` in "having" needs to be provided in "by"`,
224 | ]
225 | }[HavingFields]
226 | : 'take' extends Prisma.Keys
227 | ? 'orderBy' extends Prisma.Keys
228 | ? ByValid extends Prisma.True
229 | ? {}
230 | : {
231 | [P in OrderFields]: P extends ByFields
232 | ? never
233 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
234 | }[OrderFields]
235 | : 'Error: If you provide "take", you also need to provide "orderBy"'
236 | : 'skip' extends Prisma.Keys
237 | ? 'orderBy' extends Prisma.Keys
238 | ? ByValid extends Prisma.True
239 | ? {}
240 | : {
241 | [P in OrderFields]: P extends ByFields
242 | ? never
243 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
244 | }[OrderFields]
245 | : 'Error: If you provide "skip", you also need to provide "orderBy"'
246 | : ByValid extends Prisma.True
247 | ? {}
248 | : {
249 | [P in OrderFields]: P extends ByFields
250 | ? never
251 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
252 | }[OrderFields], TQueryFnData = {} extends InputErrors ?
253 | Array &
254 | {
255 | [P in ((keyof TArgs) & (keyof Prisma.AccountGroupByOutputType))]: P extends '_count'
256 | ? TArgs[P] extends boolean
257 | ? number
258 | : Prisma.GetScalarType
259 | : Prisma.GetScalarType
260 | }
261 | > : InputErrors, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset & InputErrors>, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
262 | const { endpoint, fetch } = getHooksContext();
263 | return useModelQuery('Account', `${endpoint}/account/groupBy`, args, options, fetch);
264 | }
265 |
266 | export function useSuspenseGroupByAccount>, Prisma.Extends<'take', Prisma.Keys>>, OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.AccountGroupByArgs['orderBy'] } : { orderBy?: Prisma.AccountGroupByArgs['orderBy'] }, OrderFields extends Prisma.ExcludeUnderscoreKeys>>, ByFields extends Prisma.MaybeTupleToUnion, ByValid extends Prisma.Has, HavingFields extends Prisma.GetHavingFields, HavingValid extends Prisma.Has, ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False, InputErrors extends ByEmpty extends Prisma.True
267 | ? `Error: "by" must not be empty.`
268 | : HavingValid extends Prisma.False
269 | ? {
270 | [P in HavingFields]: P extends ByFields
271 | ? never
272 | : P extends string
273 | ? `Error: Field "${P}" used in "having" needs to be provided in "by".`
274 | : [
275 | Error,
276 | 'Field ',
277 | P,
278 | ` in "having" needs to be provided in "by"`,
279 | ]
280 | }[HavingFields]
281 | : 'take' extends Prisma.Keys
282 | ? 'orderBy' extends Prisma.Keys
283 | ? ByValid extends Prisma.True
284 | ? {}
285 | : {
286 | [P in OrderFields]: P extends ByFields
287 | ? never
288 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
289 | }[OrderFields]
290 | : 'Error: If you provide "take", you also need to provide "orderBy"'
291 | : 'skip' extends Prisma.Keys
292 | ? 'orderBy' extends Prisma.Keys
293 | ? ByValid extends Prisma.True
294 | ? {}
295 | : {
296 | [P in OrderFields]: P extends ByFields
297 | ? never
298 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
299 | }[OrderFields]
300 | : 'Error: If you provide "skip", you also need to provide "orderBy"'
301 | : ByValid extends Prisma.True
302 | ? {}
303 | : {
304 | [P in OrderFields]: P extends ByFields
305 | ? never
306 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
307 | }[OrderFields], TQueryFnData = {} extends InputErrors ?
308 | Array &
309 | {
310 | [P in ((keyof TArgs) & (keyof Prisma.AccountGroupByOutputType))]: P extends '_count'
311 | ? TArgs[P] extends boolean
312 | ? number
313 | : Prisma.GetScalarType
314 | : Prisma.GetScalarType
315 | }
316 | > : InputErrors, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset & InputErrors>, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
317 | const { endpoint, fetch } = getHooksContext();
318 | return useSuspenseModelQuery('Account', `${endpoint}/account/groupBy`, args, options, fetch);
319 | }
320 |
321 | export function useCountAccount : number, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
322 | const { endpoint, fetch } = getHooksContext();
323 | return useModelQuery('Account', `${endpoint}/account/count`, args, options, fetch);
324 | }
325 |
326 | export function useSuspenseCountAccount : number, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
327 | const { endpoint, fetch } = getHooksContext();
328 | return useSuspenseModelQuery('Account', `${endpoint}/account/count`, args, options, fetch);
329 | }
330 |
331 | export function useCheckAccount(args: { operation: PolicyCrudKind; where?: { id?: string; userId?: string; type?: string; provider?: string; providerAccountId?: string; refresh_token?: string; refresh_token_expires_in?: number; access_token?: string; expires_at?: number; token_type?: string; scope?: string; id_token?: string; session_state?: string }; }, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
332 | const { endpoint, fetch } = getHooksContext();
333 | return useModelQuery('Account', `${endpoint}/account/check`, args, options, fetch);
334 | }
335 |
--------------------------------------------------------------------------------
/lib/hooks/index.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 | // @ts-nocheck
7 |
8 | export * from './space';
9 | export * from './space-user';
10 | export * from './user';
11 | export * from './list';
12 | export * from './todo';
13 | export * from './account';
14 | export { getQueryKey } from '@zenstackhq/tanstack-query/runtime-v5';
15 | export { Provider } from '@zenstackhq/tanstack-query/runtime-v5/react';
16 | export { default as metadata } from './__model_meta';
17 |
--------------------------------------------------------------------------------
/lib/hooks/list.ts:
--------------------------------------------------------------------------------
1 | /******************************************************************************
2 | * This file was generated by ZenStack CLI.
3 | ******************************************************************************/
4 |
5 | /* eslint-disable */
6 | // @ts-nocheck
7 |
8 | import type { Prisma, List } from "@zenstackhq/runtime/models";
9 | import type { UseMutationOptions, UseQueryOptions, UseInfiniteQueryOptions, InfiniteData } from '@tanstack/react-query';
10 | import { getHooksContext } from '@zenstackhq/tanstack-query/runtime-v5/react';
11 | import { useModelQuery, useInfiniteModelQuery, useModelMutation } from '@zenstackhq/tanstack-query/runtime-v5/react';
12 | import type { PickEnumerable, CheckSelect, QueryError, ExtraQueryOptions, ExtraMutationOptions } from '@zenstackhq/tanstack-query/runtime-v5';
13 | import type { PolicyCrudKind } from '@zenstackhq/runtime'
14 | import metadata from './__model_meta';
15 | type DefaultError = QueryError;
16 | import { useSuspenseModelQuery, useSuspenseInfiniteModelQuery } from '@zenstackhq/tanstack-query/runtime-v5/react';
17 | import type { UseSuspenseQueryOptions, UseSuspenseInfiniteQueryOptions } from '@tanstack/react-query';
18 |
19 | export function useCreateList(options?: Omit<(UseMutationOptions<(List | undefined), DefaultError, Prisma.ListCreateArgs> & ExtraMutationOptions), 'mutationFn'>) {
20 | const { endpoint, fetch } = getHooksContext();
21 | const _mutation =
22 | useModelMutation('List', 'POST', `${endpoint}/list/create`, metadata, options, fetch, true)
23 | ;
24 | const mutation = {
25 | ..._mutation,
26 | mutateAsync: async (
27 | args: Prisma.SelectSubset,
28 | options?: Omit<(UseMutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
29 | ) => {
30 | return (await _mutation.mutateAsync(
31 | args,
32 | options as any
33 | )) as (CheckSelect> | undefined);
34 | },
35 | };
36 | return mutation;
37 | }
38 |
39 | export function useCreateManyList(options?: Omit<(UseMutationOptions & ExtraMutationOptions), 'mutationFn'>) {
40 | const { endpoint, fetch } = getHooksContext();
41 | const _mutation =
42 | useModelMutation('List', 'POST', `${endpoint}/list/createMany`, metadata, options, fetch, false)
43 | ;
44 | const mutation = {
45 | ..._mutation,
46 | mutateAsync: async (
47 | args: Prisma.SelectSubset,
48 | options?: Omit<(UseMutationOptions> & ExtraMutationOptions), 'mutationFn'>
49 | ) => {
50 | return (await _mutation.mutateAsync(
51 | args,
52 | options as any
53 | )) as Prisma.BatchPayload;
54 | },
55 | };
56 | return mutation;
57 | }
58 |
59 | export function useFindManyList & { $optimistic?: boolean }>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
60 | const { endpoint, fetch } = getHooksContext();
61 | return useModelQuery('List', `${endpoint}/list/findMany`, args, options, fetch);
62 | }
63 |
64 | export function useInfiniteFindManyList>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: Omit>, 'queryKey' | 'initialPageParam'>) {
65 | options = options ?? { getNextPageParam: () => null };
66 | const { endpoint, fetch } = getHooksContext();
67 | return useInfiniteModelQuery('List', `${endpoint}/list/findMany`, args, options, fetch);
68 | }
69 |
70 | export function useSuspenseFindManyList & { $optimistic?: boolean }>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
71 | const { endpoint, fetch } = getHooksContext();
72 | return useSuspenseModelQuery('List', `${endpoint}/list/findMany`, args, options, fetch);
73 | }
74 |
75 | export function useSuspenseInfiniteFindManyList>, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: Omit>, 'queryKey' | 'initialPageParam'>) {
76 | options = options ?? { getNextPageParam: () => null };
77 | const { endpoint, fetch } = getHooksContext();
78 | return useSuspenseInfiniteModelQuery('List', `${endpoint}/list/findMany`, args, options, fetch);
79 | }
80 |
81 | export function useFindUniqueList & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
82 | const { endpoint, fetch } = getHooksContext();
83 | return useModelQuery('List', `${endpoint}/list/findUnique`, args, options, fetch);
84 | }
85 |
86 | export function useSuspenseFindUniqueList & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
87 | const { endpoint, fetch } = getHooksContext();
88 | return useSuspenseModelQuery('List', `${endpoint}/list/findUnique`, args, options, fetch);
89 | }
90 |
91 | export function useFindFirstList & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
92 | const { endpoint, fetch } = getHooksContext();
93 | return useModelQuery('List', `${endpoint}/list/findFirst`, args, options, fetch);
94 | }
95 |
96 | export function useSuspenseFindFirstList & { $optimistic?: boolean }, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
97 | const { endpoint, fetch } = getHooksContext();
98 | return useSuspenseModelQuery('List', `${endpoint}/list/findFirst`, args, options, fetch);
99 | }
100 |
101 | export function useUpdateList(options?: Omit<(UseMutationOptions<(List | undefined), DefaultError, Prisma.ListUpdateArgs> & ExtraMutationOptions), 'mutationFn'>) {
102 | const { endpoint, fetch } = getHooksContext();
103 | const _mutation =
104 | useModelMutation('List', 'PUT', `${endpoint}/list/update`, metadata, options, fetch, true)
105 | ;
106 | const mutation = {
107 | ..._mutation,
108 | mutateAsync: async (
109 | args: Prisma.SelectSubset,
110 | options?: Omit<(UseMutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
111 | ) => {
112 | return (await _mutation.mutateAsync(
113 | args,
114 | options as any
115 | )) as (CheckSelect> | undefined);
116 | },
117 | };
118 | return mutation;
119 | }
120 |
121 | export function useUpdateManyList(options?: Omit<(UseMutationOptions & ExtraMutationOptions), 'mutationFn'>) {
122 | const { endpoint, fetch } = getHooksContext();
123 | const _mutation =
124 | useModelMutation('List', 'PUT', `${endpoint}/list/updateMany`, metadata, options, fetch, false)
125 | ;
126 | const mutation = {
127 | ..._mutation,
128 | mutateAsync: async (
129 | args: Prisma.SelectSubset,
130 | options?: Omit<(UseMutationOptions> & ExtraMutationOptions), 'mutationFn'>
131 | ) => {
132 | return (await _mutation.mutateAsync(
133 | args,
134 | options as any
135 | )) as Prisma.BatchPayload;
136 | },
137 | };
138 | return mutation;
139 | }
140 |
141 | export function useUpsertList(options?: Omit<(UseMutationOptions<(List | undefined), DefaultError, Prisma.ListUpsertArgs> & ExtraMutationOptions), 'mutationFn'>) {
142 | const { endpoint, fetch } = getHooksContext();
143 | const _mutation =
144 | useModelMutation('List', 'POST', `${endpoint}/list/upsert`, metadata, options, fetch, true)
145 | ;
146 | const mutation = {
147 | ..._mutation,
148 | mutateAsync: async (
149 | args: Prisma.SelectSubset,
150 | options?: Omit<(UseMutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
151 | ) => {
152 | return (await _mutation.mutateAsync(
153 | args,
154 | options as any
155 | )) as (CheckSelect> | undefined);
156 | },
157 | };
158 | return mutation;
159 | }
160 |
161 | export function useDeleteList(options?: Omit<(UseMutationOptions<(List | undefined), DefaultError, Prisma.ListDeleteArgs> & ExtraMutationOptions), 'mutationFn'>) {
162 | const { endpoint, fetch } = getHooksContext();
163 | const _mutation =
164 | useModelMutation('List', 'DELETE', `${endpoint}/list/delete`, metadata, options, fetch, true)
165 | ;
166 | const mutation = {
167 | ..._mutation,
168 | mutateAsync: async (
169 | args: Prisma.SelectSubset,
170 | options?: Omit<(UseMutationOptions<(CheckSelect> | undefined), DefaultError, Prisma.SelectSubset> & ExtraMutationOptions), 'mutationFn'>
171 | ) => {
172 | return (await _mutation.mutateAsync(
173 | args,
174 | options as any
175 | )) as (CheckSelect> | undefined);
176 | },
177 | };
178 | return mutation;
179 | }
180 |
181 | export function useDeleteManyList(options?: Omit<(UseMutationOptions & ExtraMutationOptions), 'mutationFn'>) {
182 | const { endpoint, fetch } = getHooksContext();
183 | const _mutation =
184 | useModelMutation('List', 'DELETE', `${endpoint}/list/deleteMany`, metadata, options, fetch, false)
185 | ;
186 | const mutation = {
187 | ..._mutation,
188 | mutateAsync: async (
189 | args: Prisma.SelectSubset,
190 | options?: Omit<(UseMutationOptions> & ExtraMutationOptions), 'mutationFn'>
191 | ) => {
192 | return (await _mutation.mutateAsync(
193 | args,
194 | options as any
195 | )) as Prisma.BatchPayload;
196 | },
197 | };
198 | return mutation;
199 | }
200 |
201 | export function useAggregateList, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
202 | const { endpoint, fetch } = getHooksContext();
203 | return useModelQuery('List', `${endpoint}/list/aggregate`, args, options, fetch);
204 | }
205 |
206 | export function useSuspenseAggregateList, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
207 | const { endpoint, fetch } = getHooksContext();
208 | return useSuspenseModelQuery('List', `${endpoint}/list/aggregate`, args, options, fetch);
209 | }
210 |
211 | export function useGroupByList>, Prisma.Extends<'take', Prisma.Keys>>, OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.ListGroupByArgs['orderBy'] } : { orderBy?: Prisma.ListGroupByArgs['orderBy'] }, OrderFields extends Prisma.ExcludeUnderscoreKeys>>, ByFields extends Prisma.MaybeTupleToUnion, ByValid extends Prisma.Has, HavingFields extends Prisma.GetHavingFields, HavingValid extends Prisma.Has, ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False, InputErrors extends ByEmpty extends Prisma.True
212 | ? `Error: "by" must not be empty.`
213 | : HavingValid extends Prisma.False
214 | ? {
215 | [P in HavingFields]: P extends ByFields
216 | ? never
217 | : P extends string
218 | ? `Error: Field "${P}" used in "having" needs to be provided in "by".`
219 | : [
220 | Error,
221 | 'Field ',
222 | P,
223 | ` in "having" needs to be provided in "by"`,
224 | ]
225 | }[HavingFields]
226 | : 'take' extends Prisma.Keys
227 | ? 'orderBy' extends Prisma.Keys
228 | ? ByValid extends Prisma.True
229 | ? {}
230 | : {
231 | [P in OrderFields]: P extends ByFields
232 | ? never
233 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
234 | }[OrderFields]
235 | : 'Error: If you provide "take", you also need to provide "orderBy"'
236 | : 'skip' extends Prisma.Keys
237 | ? 'orderBy' extends Prisma.Keys
238 | ? ByValid extends Prisma.True
239 | ? {}
240 | : {
241 | [P in OrderFields]: P extends ByFields
242 | ? never
243 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
244 | }[OrderFields]
245 | : 'Error: If you provide "skip", you also need to provide "orderBy"'
246 | : ByValid extends Prisma.True
247 | ? {}
248 | : {
249 | [P in OrderFields]: P extends ByFields
250 | ? never
251 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
252 | }[OrderFields], TQueryFnData = {} extends InputErrors ?
253 | Array &
254 | {
255 | [P in ((keyof TArgs) & (keyof Prisma.ListGroupByOutputType))]: P extends '_count'
256 | ? TArgs[P] extends boolean
257 | ? number
258 | : Prisma.GetScalarType
259 | : Prisma.GetScalarType
260 | }
261 | > : InputErrors, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset & InputErrors>, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
262 | const { endpoint, fetch } = getHooksContext();
263 | return useModelQuery('List', `${endpoint}/list/groupBy`, args, options, fetch);
264 | }
265 |
266 | export function useSuspenseGroupByList>, Prisma.Extends<'take', Prisma.Keys>>, OrderByArg extends Prisma.True extends HasSelectOrTake ? { orderBy: Prisma.ListGroupByArgs['orderBy'] } : { orderBy?: Prisma.ListGroupByArgs['orderBy'] }, OrderFields extends Prisma.ExcludeUnderscoreKeys>>, ByFields extends Prisma.MaybeTupleToUnion, ByValid extends Prisma.Has, HavingFields extends Prisma.GetHavingFields, HavingValid extends Prisma.Has, ByEmpty extends TArgs['by'] extends never[] ? Prisma.True : Prisma.False, InputErrors extends ByEmpty extends Prisma.True
267 | ? `Error: "by" must not be empty.`
268 | : HavingValid extends Prisma.False
269 | ? {
270 | [P in HavingFields]: P extends ByFields
271 | ? never
272 | : P extends string
273 | ? `Error: Field "${P}" used in "having" needs to be provided in "by".`
274 | : [
275 | Error,
276 | 'Field ',
277 | P,
278 | ` in "having" needs to be provided in "by"`,
279 | ]
280 | }[HavingFields]
281 | : 'take' extends Prisma.Keys
282 | ? 'orderBy' extends Prisma.Keys
283 | ? ByValid extends Prisma.True
284 | ? {}
285 | : {
286 | [P in OrderFields]: P extends ByFields
287 | ? never
288 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
289 | }[OrderFields]
290 | : 'Error: If you provide "take", you also need to provide "orderBy"'
291 | : 'skip' extends Prisma.Keys
292 | ? 'orderBy' extends Prisma.Keys
293 | ? ByValid extends Prisma.True
294 | ? {}
295 | : {
296 | [P in OrderFields]: P extends ByFields
297 | ? never
298 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
299 | }[OrderFields]
300 | : 'Error: If you provide "skip", you also need to provide "orderBy"'
301 | : ByValid extends Prisma.True
302 | ? {}
303 | : {
304 | [P in OrderFields]: P extends ByFields
305 | ? never
306 | : `Error: Field "${P}" in "orderBy" needs to be provided in "by"`
307 | }[OrderFields], TQueryFnData = {} extends InputErrors ?
308 | Array &
309 | {
310 | [P in ((keyof TArgs) & (keyof Prisma.ListGroupByOutputType))]: P extends '_count'
311 | ? TArgs[P] extends boolean
312 | ? number
313 | : Prisma.GetScalarType
314 | : Prisma.GetScalarType
315 | }
316 | > : InputErrors, TData = TQueryFnData, TError = DefaultError>(args: Prisma.SelectSubset & InputErrors>, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
317 | const { endpoint, fetch } = getHooksContext();
318 | return useSuspenseModelQuery('List', `${endpoint}/list/groupBy`, args, options, fetch);
319 | }
320 |
321 | export function useCountList : number, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
322 | const { endpoint, fetch } = getHooksContext();
323 | return useModelQuery('List', `${endpoint}/list/count`, args, options, fetch);
324 | }
325 |
326 | export function useSuspenseCountList : number, TData = TQueryFnData, TError = DefaultError>(args?: Prisma.SelectSubset, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
327 | const { endpoint, fetch } = getHooksContext();
328 | return useSuspenseModelQuery('List', `${endpoint}/list/count`, args, options, fetch);
329 | }
330 |
331 | export function useCheckList(args: { operation: PolicyCrudKind; where?: { id?: string; spaceId?: string; ownerId?: string; title?: string; private?: boolean }; }, options?: (Omit, 'queryKey'> & ExtraQueryOptions)) {
332 | const { endpoint, fetch } = getHooksContext();
333 | return useModelQuery