zenstack 0.1.41 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/.vscode/extensions.json +7 -0
  2. package/.vscode/launch.json +49 -0
  3. package/.vscode/settings.json +4 -0
  4. package/README.md +1 -0
  5. package/package.json +8 -90
  6. package/packages/internal/jest.config.ts +32 -0
  7. package/packages/internal/package.json +42 -0
  8. package/packages/internal/src/constants.ts +1 -0
  9. package/packages/internal/src/handler/data/guard-utils.ts +7 -0
  10. package/packages/internal/src/handler/data/handler.ts +415 -0
  11. package/packages/internal/src/handler/data/query-processor.ts +504 -0
  12. package/packages/internal/src/handler/index.ts +1 -0
  13. package/packages/internal/src/handler/types.ts +20 -0
  14. package/packages/internal/src/index.ts +3 -0
  15. package/packages/internal/src/request-handler.ts +27 -0
  16. package/packages/internal/src/request.ts +101 -0
  17. package/packages/internal/src/types.ts +40 -0
  18. package/packages/internal/tests/query-processor.test.ts +172 -0
  19. package/{out/cli/tsconfig.template.json → packages/internal/tsconfig.json} +7 -3
  20. package/packages/runtime/auth.d.ts +1 -0
  21. package/packages/runtime/auth.js +3 -0
  22. package/packages/runtime/hooks.d.ts +10 -0
  23. package/packages/runtime/hooks.js +3 -0
  24. package/packages/runtime/index.d.ts +3 -0
  25. package/packages/runtime/index.js +1 -0
  26. package/packages/runtime/package-lock.json +512 -0
  27. package/packages/runtime/package.json +16 -0
  28. package/packages/runtime/server.d.ts +1 -0
  29. package/packages/runtime/server.js +3 -0
  30. package/packages/runtime/types.d.ts +1 -0
  31. package/packages/runtime/types.js +3 -0
  32. package/packages/schema/.eslintrc.json +13 -0
  33. package/packages/schema/.vscodeignore +4 -0
  34. package/packages/schema/asset/logo-dark.png +0 -0
  35. package/packages/schema/asset/logo-light.png +0 -0
  36. package/{bin → packages/schema/bin}/cli +0 -0
  37. package/packages/schema/jest.config.ts +32 -0
  38. package/packages/schema/langium-config.json +14 -0
  39. package/packages/schema/langium-quickstart.md +41 -0
  40. package/packages/schema/language-configuration.json +30 -0
  41. package/packages/schema/package.json +96 -0
  42. package/packages/schema/src/cli/cli-util.ts +80 -0
  43. package/packages/schema/src/cli/index.ts +64 -0
  44. package/packages/schema/src/extension.ts +76 -0
  45. package/packages/schema/src/generator/constants.ts +5 -0
  46. package/packages/schema/src/generator/index.ts +92 -0
  47. package/{out/generator/next-auth/index.js → packages/schema/src/generator/next-auth/index.ts} +46 -58
  48. package/{out → packages/schema/src}/generator/package.template.json +0 -0
  49. package/packages/schema/src/generator/prisma/expression-writer.ts +352 -0
  50. package/packages/schema/src/generator/prisma/index.ts +32 -0
  51. package/packages/schema/src/generator/prisma/plain-expression-builder.ts +91 -0
  52. package/packages/schema/src/generator/prisma/prisma-builder.ts +366 -0
  53. package/packages/schema/src/generator/prisma/query-gard-generator.ts +208 -0
  54. package/packages/schema/src/generator/prisma/schema-generator.ts +300 -0
  55. package/packages/schema/src/generator/react-hooks/index.ts +181 -0
  56. package/packages/schema/src/generator/service/index.ts +107 -0
  57. package/{out → packages/schema/src}/generator/tsconfig.template.json +0 -0
  58. package/packages/schema/src/generator/types.ts +17 -0
  59. package/packages/schema/src/generator/utils.ts +9 -0
  60. package/packages/schema/src/language-server/generated/ast.ts +603 -0
  61. package/{out/language-server/generated/grammar.js → packages/schema/src/language-server/generated/grammar.ts} +5 -8
  62. package/packages/schema/src/language-server/generated/module.ts +24 -0
  63. package/packages/schema/src/language-server/main.ts +12 -0
  64. package/{out → packages/schema/src}/language-server/stdlib.zmodel +0 -0
  65. package/packages/schema/src/language-server/types.ts +9 -0
  66. package/packages/schema/src/language-server/zmodel-index.ts +33 -0
  67. package/packages/schema/src/language-server/zmodel-linker.ts +409 -0
  68. package/packages/schema/src/language-server/zmodel-module.ts +90 -0
  69. package/packages/schema/src/language-server/zmodel-scope.ts +21 -0
  70. package/packages/schema/src/language-server/zmodel-validator.ts +35 -0
  71. package/packages/schema/src/language-server/zmodel.langium +186 -0
  72. package/packages/schema/src/utils/exec-utils.ts +5 -0
  73. package/packages/schema/src/utils/indent-string.ts +6 -0
  74. package/packages/schema/syntaxes/zmodel.json +57 -0
  75. package/packages/schema/syntaxes/zmodel.tmLanguage.json +57 -0
  76. package/packages/schema/tests/generator/expression-writer.test.ts +676 -0
  77. package/packages/schema/tests/generator/prisma-builder.test.ts +138 -0
  78. package/packages/schema/tests/schema/parser.test.ts +423 -0
  79. package/packages/schema/tests/schema/sample-todo.test.ts +14 -0
  80. package/packages/schema/tests/utils.ts +38 -0
  81. package/packages/schema/tsconfig.json +23 -0
  82. package/pnpm-workspace.yaml +3 -0
  83. package/samples/todo/.env +2 -0
  84. package/samples/todo/.eslintrc.json +3 -0
  85. package/samples/todo/.vscode/launch.json +11 -0
  86. package/samples/todo/README.md +34 -0
  87. package/samples/todo/components/AuthGuard.tsx +17 -0
  88. package/samples/todo/components/Avatar.tsx +22 -0
  89. package/samples/todo/components/BreadCrumb.tsx +44 -0
  90. package/samples/todo/components/ManageMembers.tsx +134 -0
  91. package/samples/todo/components/NavBar.tsx +57 -0
  92. package/samples/todo/components/SpaceMembers.tsx +76 -0
  93. package/samples/todo/components/Spaces.tsx +28 -0
  94. package/samples/todo/components/TimeInfo.tsx +17 -0
  95. package/samples/todo/components/Todo.tsx +72 -0
  96. package/samples/todo/components/TodoList.tsx +77 -0
  97. package/samples/todo/lib/context.ts +31 -0
  98. package/samples/todo/next.config.js +10 -0
  99. package/samples/todo/package-lock.json +7527 -0
  100. package/samples/todo/package.json +45 -0
  101. package/samples/todo/pages/_app.tsx +50 -0
  102. package/samples/todo/pages/api/auth/[...nextauth].ts +83 -0
  103. package/samples/todo/pages/api/zenstack/[...path].ts +16 -0
  104. package/samples/todo/pages/create-space.tsx +114 -0
  105. package/samples/todo/pages/index.tsx +32 -0
  106. package/samples/todo/pages/space/[slug]/[listId]/index.tsx +88 -0
  107. package/samples/todo/pages/space/[slug]/index.tsx +169 -0
  108. package/samples/todo/postcss.config.js +6 -0
  109. package/samples/todo/public/avatar.jpg +0 -0
  110. package/samples/todo/public/favicon.ico +0 -0
  111. package/samples/todo/public/logo.png +0 -0
  112. package/samples/todo/public/vercel.svg +4 -0
  113. package/samples/todo/styles/globals.css +7 -0
  114. package/samples/todo/tailwind.config.js +11 -0
  115. package/samples/todo/tsconfig.json +28 -0
  116. package/samples/todo/types/next-auth.d.ts +14 -0
  117. package/samples/todo/types/next.d.ts +16 -0
  118. package/samples/todo/zenstack/migrations/20221014084317_init/migration.sql +153 -0
  119. package/samples/todo/zenstack/migrations/20221020094651_upate_cli/migration.sql +23 -0
  120. package/samples/todo/zenstack/migrations/migration_lock.toml +3 -0
  121. package/samples/todo/zenstack/schema.prisma +126 -0
  122. package/samples/todo/zenstack/schema.zmodel +161 -0
  123. package/tests/integration/jest.config.ts +16 -0
  124. package/tests/integration/package-lock.json +1081 -0
  125. package/tests/integration/package.json +27 -0
  126. package/tests/integration/tests/operation-coverate.test.ts +563 -0
  127. package/tests/integration/tests/operations.zmodel +69 -0
  128. package/tests/integration/tests/todo-e2e.test.ts +577 -0
  129. package/tests/integration/tests/todo.zmodel +123 -0
  130. package/tests/integration/tests/tsconfig.template.json +10 -0
  131. package/tests/integration/tests/utils.ts +133 -0
  132. package/tests/integration/tsconfig.json +10 -0
  133. package/out/cli/cli-util.js +0 -64
  134. package/out/cli/cli-util.js.map +0 -1
  135. package/out/cli/generator.js +0 -1
  136. package/out/cli/generator.js.map +0 -1
  137. package/out/cli/index.js +0 -46
  138. package/out/cli/index.js.map +0 -1
  139. package/out/cli/package.template.json +0 -10
  140. package/out/extension.js +0 -81
  141. package/out/extension.js.map +0 -1
  142. package/out/generator/constants.js +0 -9
  143. package/out/generator/constants.js.map +0 -1
  144. package/out/generator/data-server/index.js +0 -1
  145. package/out/generator/data-server/index.js.map +0 -1
  146. package/out/generator/index.js +0 -98
  147. package/out/generator/index.js.map +0 -1
  148. package/out/generator/next-auth/index.js.map +0 -1
  149. package/out/generator/prisma/expression-writer.js +0 -287
  150. package/out/generator/prisma/expression-writer.js.map +0 -1
  151. package/out/generator/prisma/index.js +0 -38
  152. package/out/generator/prisma/index.js.map +0 -1
  153. package/out/generator/prisma/plain-expression-builder.js +0 -69
  154. package/out/generator/prisma/plain-expression-builder.js.map +0 -1
  155. package/out/generator/prisma/prisma-builder.js +0 -307
  156. package/out/generator/prisma/prisma-builder.js.map +0 -1
  157. package/out/generator/prisma/query-gard-generator.js +0 -159
  158. package/out/generator/prisma/query-gard-generator.js.map +0 -1
  159. package/out/generator/prisma/schema-generator.js +0 -201
  160. package/out/generator/prisma/schema-generator.js.map +0 -1
  161. package/out/generator/query-guard/index.js +0 -2
  162. package/out/generator/query-guard/index.js.map +0 -1
  163. package/out/generator/react-hooks/index.js +0 -179
  164. package/out/generator/react-hooks/index.js.map +0 -1
  165. package/out/generator/server/data/data-generator.js +0 -376
  166. package/out/generator/server/data/data-generator.js.map +0 -1
  167. package/out/generator/server/data/expression-writer.js +0 -287
  168. package/out/generator/server/data/expression-writer.js.map +0 -1
  169. package/out/generator/server/data/plain-expression-builder.js +0 -69
  170. package/out/generator/server/data/plain-expression-builder.js.map +0 -1
  171. package/out/generator/server/data-generator.js +0 -82
  172. package/out/generator/server/data-generator.js.map +0 -1
  173. package/out/generator/server/expression-writer.js +0 -1
  174. package/out/generator/server/expression-writer.js.map +0 -1
  175. package/out/generator/server/function/function-generator.js +0 -50
  176. package/out/generator/server/function/function-generator.js.map +0 -1
  177. package/out/generator/server/function-generator.js +0 -13
  178. package/out/generator/server/function-generator.js.map +0 -1
  179. package/out/generator/server/index.js +0 -88
  180. package/out/generator/server/index.js.map +0 -1
  181. package/out/generator/server/js-expression-builder.js +0 -1
  182. package/out/generator/server/js-expression-builder.js.map +0 -1
  183. package/out/generator/server/plain-expression-builder.js +0 -1
  184. package/out/generator/server/plain-expression-builder.js.map +0 -1
  185. package/out/generator/server/server-code-generator.js +0 -3
  186. package/out/generator/server/server-code-generator.js.map +0 -1
  187. package/out/generator/server/server-code-writer.js +0 -1
  188. package/out/generator/server/server-code-writer.js.map +0 -1
  189. package/out/generator/service/index.js +0 -133
  190. package/out/generator/service/index.js.map +0 -1
  191. package/out/generator/types.js +0 -10
  192. package/out/generator/types.js.map +0 -1
  193. package/out/generator/utils.js +0 -10
  194. package/out/generator/utils.js.map +0 -1
  195. package/out/language-server/generated/ast.js +0 -386
  196. package/out/language-server/generated/ast.js.map +0 -1
  197. package/out/language-server/generated/grammar.js.map +0 -1
  198. package/out/language-server/generated/module.js +0 -23
  199. package/out/language-server/generated/module.js.map +0 -1
  200. package/out/language-server/main.js +0 -12
  201. package/out/language-server/main.js.map +0 -1
  202. package/out/language-server/types.js +0 -3
  203. package/out/language-server/types.js.map +0 -1
  204. package/out/language-server/zmodel-index.js +0 -38
  205. package/out/language-server/zmodel-index.js.map +0 -1
  206. package/out/language-server/zmodel-linker.js +0 -241
  207. package/out/language-server/zmodel-linker.js.map +0 -1
  208. package/out/language-server/zmodel-module.js +0 -51
  209. package/out/language-server/zmodel-module.js.map +0 -1
  210. package/out/language-server/zmodel-scope.js +0 -30
  211. package/out/language-server/zmodel-scope.js.map +0 -1
  212. package/out/language-server/zmodel-validator.js +0 -25
  213. package/out/language-server/zmodel-validator.js.map +0 -1
  214. package/out/utils/indent-string.js +0 -9
  215. package/out/utils/indent-string.js.map +0 -1
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "todo",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint",
10
+ "db-gen": "prisma generate --schema zenstack/schema.prisma",
11
+ "db-push": "prisma db push --schema zenstack/schema.prisma",
12
+ "db-migrate": "prisma migrate dev --schema zenstack/schema.prisma",
13
+ "db-deploy": "prisma migrate deploy --schema zenstack/schema.prisma",
14
+ "db-reset": "prisma migrate reset --schema zenstack/schema.prisma",
15
+ "generate": "zenstack generate ./zenstack/schema.zmodel",
16
+ "vercel-build": "zenstack generate ./zenstack/schema.zmodel && npm run build && npm run db-deploy"
17
+ },
18
+ "dependencies": {
19
+ "@heroicons/react": "^2.0.12",
20
+ "@prisma/client": "^4.4.0",
21
+ "@zenstackhq/runtime": "latest",
22
+ "daisyui": "^2.31.0",
23
+ "moment": "^2.29.4",
24
+ "nanoid": "^4.0.0",
25
+ "next": "12.3.1",
26
+ "next-auth": "^4.10.3",
27
+ "react": "18.2.0",
28
+ "react-dom": "18.2.0",
29
+ "react-toastify": "^9.0.8",
30
+ "swr": "^1.3.0"
31
+ },
32
+ "devDependencies": {
33
+ "@tailwindcss/line-clamp": "^0.4.2",
34
+ "@types/node": "^14.17.3",
35
+ "@types/react": "18.0.21",
36
+ "@types/react-dom": "18.0.6",
37
+ "autoprefixer": "^10.4.12",
38
+ "eslint": "^7.19.0",
39
+ "eslint-config-next": "12.3.1",
40
+ "postcss": "^8.4.16",
41
+ "tailwindcss": "^3.1.8",
42
+ "typescript": "^4.6.2",
43
+ "zenstack": "latest"
44
+ }
45
+ }
@@ -0,0 +1,50 @@
1
+ import '../styles/globals.css';
2
+ import type { AppProps } from 'next/app';
3
+ import { SessionProvider } from 'next-auth/react';
4
+ import 'react-toastify/dist/ReactToastify.css';
5
+ import { ToastContainer } from 'react-toastify';
6
+ import NavBar from 'components/NavBar';
7
+ import {
8
+ SpaceContext,
9
+ useCurrentSpace,
10
+ useCurrentUser,
11
+ UserContext,
12
+ } from '@lib/context';
13
+ import AuthGuard from 'components/AuthGuard';
14
+
15
+ function AppContent(props: { children: JSX.Element | JSX.Element[] }) {
16
+ const user = useCurrentUser();
17
+ const space = useCurrentSpace();
18
+
19
+ return (
20
+ <AuthGuard>
21
+ <UserContext.Provider value={user}>
22
+ <SpaceContext.Provider value={space}>
23
+ <div className="h-screen flex flex-col">
24
+ <NavBar user={user} space={space} />
25
+ {props.children}
26
+ </div>
27
+ </SpaceContext.Provider>
28
+ </UserContext.Provider>
29
+ </AuthGuard>
30
+ );
31
+ }
32
+
33
+ function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) {
34
+ return (
35
+ <SessionProvider session={session}>
36
+ <AppContent>
37
+ <div className="flex-grow h-100">
38
+ <Component {...pageProps} />
39
+ <ToastContainer
40
+ position="top-center"
41
+ autoClose={2000}
42
+ hideProgressBar={true}
43
+ />
44
+ </div>
45
+ </AppContent>
46
+ </SessionProvider>
47
+ );
48
+ }
49
+
50
+ export default MyApp;
@@ -0,0 +1,83 @@
1
+ import NextAuth, { NextAuthOptions, User } from 'next-auth';
2
+ import CredentialsProvider from 'next-auth/providers/credentials';
3
+ import {
4
+ authorize,
5
+ NextAuthAdapter as Adapter,
6
+ } from '@zenstackhq/runtime/auth';
7
+ import service from '@zenstackhq/runtime';
8
+ import { nanoid } from 'nanoid';
9
+ import { SpaceUserRole } from '@zenstackhq/runtime/types';
10
+
11
+ export const authOptions: NextAuthOptions = {
12
+ // Configure one or more authentication providers
13
+
14
+ adapter: Adapter(service),
15
+
16
+ session: {
17
+ strategy: 'jwt',
18
+ },
19
+
20
+ providers: [
21
+ CredentialsProvider({
22
+ credentials: {
23
+ email: {
24
+ label: 'Email Address',
25
+ type: 'email',
26
+ placeholder: 'john.doe@example.com',
27
+ },
28
+ password: {
29
+ label: 'Password',
30
+ type: 'password',
31
+ placeholder: 'Your super secure password',
32
+ },
33
+ },
34
+ authorize: authorize(service),
35
+ }),
36
+ ],
37
+
38
+ callbacks: {
39
+ async session({ session, token }) {
40
+ return {
41
+ ...session,
42
+ user: {
43
+ ...session.user,
44
+ id: token.sub!,
45
+ },
46
+ };
47
+ },
48
+ },
49
+
50
+ events: {
51
+ async signIn({ user }: { user: User }) {
52
+ const spaceCount = await service.db.spaceUser.count({
53
+ where: {
54
+ userId: user.id,
55
+ },
56
+ });
57
+ if (spaceCount > 0) {
58
+ return;
59
+ }
60
+
61
+ console.log(
62
+ `User ${user.id} doesn't belong to any space. Creating one.`
63
+ );
64
+ const space = await service.db.space.create({
65
+ data: {
66
+ name: `${user.name || user.email}'s space`,
67
+ slug: nanoid(8),
68
+ members: {
69
+ create: [
70
+ {
71
+ userId: user.id,
72
+ role: SpaceUserRole.ADMIN,
73
+ },
74
+ ],
75
+ },
76
+ },
77
+ });
78
+ console.log(`Space created:`, space);
79
+ },
80
+ },
81
+ };
82
+
83
+ export default NextAuth(authOptions);
@@ -0,0 +1,16 @@
1
+ import { NextApiRequest, NextApiResponse } from 'next';
2
+ import {
3
+ type RequestHandlerOptions,
4
+ requestHandler,
5
+ } from '@zenstackhq/runtime/server';
6
+ import { authOptions } from '@api/auth/[...nextauth]';
7
+ import { unstable_getServerSession } from 'next-auth';
8
+ import service from '@zenstackhq/runtime';
9
+
10
+ const options: RequestHandlerOptions = {
11
+ async getServerUser(req: NextApiRequest, res: NextApiResponse) {
12
+ const session = await unstable_getServerSession(req, res, authOptions);
13
+ return session?.user;
14
+ },
15
+ };
16
+ export default requestHandler(service, options);
@@ -0,0 +1,114 @@
1
+ import { NextPage } from 'next';
2
+ import { FormEvent, useState } from 'react';
3
+ import { useSpace, type HooksError } from '@zenstackhq/runtime/hooks';
4
+ import { ServerErrorCode } from '@zenstackhq/runtime/server';
5
+ import { toast } from 'react-toastify';
6
+ import { useRouter } from 'next/router';
7
+ import { useSession } from 'next-auth/react';
8
+ import { SpaceUserRole } from '@zenstackhq/runtime/types';
9
+
10
+ const CreateSpace: NextPage = () => {
11
+ const { data: session } = useSession();
12
+ const [name, setName] = useState('');
13
+ const [slug, setSlug] = useState('');
14
+
15
+ const { create } = useSpace();
16
+ const router = useRouter();
17
+
18
+ const onSubmit = async (event: FormEvent) => {
19
+ event.preventDefault();
20
+ try {
21
+ const space = await create({
22
+ data: {
23
+ name,
24
+ slug,
25
+ members: {
26
+ create: [
27
+ {
28
+ userId: session!.user.id,
29
+ role: SpaceUserRole.ADMIN,
30
+ },
31
+ ],
32
+ },
33
+ },
34
+ });
35
+ console.log('Space created:', space);
36
+ toast.success("Space created successfull! You'll be redirected.");
37
+
38
+ setTimeout(() => {
39
+ router.push(`/space/${space.slug}`);
40
+ }, 2000);
41
+ } catch (err) {
42
+ console.error(err);
43
+ if (
44
+ (err as HooksError).info?.code ===
45
+ ServerErrorCode.UNIQUE_CONSTRAINT_VIOLATION
46
+ ) {
47
+ toast.error('Space slug alread in use');
48
+ } else {
49
+ toast.error(`Error occurred: ${err}`);
50
+ }
51
+ }
52
+ };
53
+
54
+ return (
55
+ <div className="flex items-center justify-center h-full">
56
+ <form onSubmit={onSubmit}>
57
+ <h1 className="text-3xl mb-8">Create a space</h1>
58
+ <div className="flex-col space-y-4">
59
+ <div>
60
+ <label htmlFor="name" className="text-lg">
61
+ Space name
62
+ </label>
63
+ <input
64
+ id="name"
65
+ type="text"
66
+ required
67
+ placeholder="Name of your space"
68
+ className="input input-bordered w-full max-w-xs mt-2"
69
+ onChange={(e: FormEvent<HTMLInputElement>) =>
70
+ setName(e.currentTarget.value)
71
+ }
72
+ />
73
+ </div>
74
+ <div>
75
+ <label htmlFor="slug" className="text-lg">
76
+ Space slug
77
+ </label>
78
+ <input
79
+ id="slug"
80
+ type="text"
81
+ required
82
+ placeholder="Slug of your space"
83
+ className="input input-bordered w-full max-w-xs mt-2"
84
+ onChange={(e: FormEvent<HTMLInputElement>) =>
85
+ setSlug(e.currentTarget.value)
86
+ }
87
+ />
88
+ </div>
89
+ </div>
90
+
91
+ <div className="flex space-x-4 mt-6">
92
+ <input
93
+ type="submit"
94
+ disabled={
95
+ name.length < 4 ||
96
+ name.length > 20 ||
97
+ !slug.match(/^[0-9a-zA-Z]{4,16}$/)
98
+ }
99
+ value="Create"
100
+ className="btn btn-primary px-8"
101
+ />
102
+ <button
103
+ className="btn btn-outline"
104
+ onClick={() => router.push('/')}
105
+ >
106
+ Cancel
107
+ </button>
108
+ </div>
109
+ </form>
110
+ </div>
111
+ );
112
+ };
113
+
114
+ export default CreateSpace;
@@ -0,0 +1,32 @@
1
+ import type { NextPage } from 'next';
2
+ import Spaces from 'components/Spaces';
3
+ import Link from 'next/link';
4
+ import { useCurrentUser } from '@lib/context';
5
+
6
+ const Home: NextPage = () => {
7
+ const user = useCurrentUser();
8
+ return (
9
+ <>
10
+ {user && (
11
+ <div className="mt-8 text-center flex flex-col items-center w-full">
12
+ <h1 className="text-2xl text-gray-800">
13
+ Welcome {user.name || user.email}!
14
+ </h1>
15
+ <div className="w-full p-8">
16
+ <h2 className="text-lg md:text-xl text-left mb-8 text-gray-700">
17
+ Choose a space to start, or{' '}
18
+ <Link href="/create-space">
19
+ <a className="link link-primary">
20
+ create a new one.
21
+ </a>
22
+ </Link>
23
+ </h2>
24
+ <Spaces />
25
+ </div>
26
+ </div>
27
+ )}
28
+ </>
29
+ );
30
+ };
31
+
32
+ export default Home;
@@ -0,0 +1,88 @@
1
+ import { useList, useTodo } from '@zenstackhq/runtime/hooks';
2
+ import { useRouter } from 'next/router';
3
+ import { PlusIcon } from '@heroicons/react/24/outline';
4
+ import { ChangeEvent, KeyboardEvent, useState } from 'react';
5
+ import { useCurrentUser } from '@lib/context';
6
+ import TodoComponent from 'components/Todo';
7
+ import BreadCrumb from 'components/BreadCrumb';
8
+
9
+ export default function TodoList() {
10
+ const user = useCurrentUser();
11
+ const router = useRouter();
12
+ const { get: getList } = useList();
13
+ const { create: createTodo, find: findTodos } = useTodo();
14
+ const [title, setTitle] = useState('');
15
+
16
+ const { data: list } = getList(router.query.listId as string);
17
+ const { data: todos, mutate: invalidateTodos } = findTodos({
18
+ where: {
19
+ listId: list?.id,
20
+ },
21
+ include: {
22
+ owner: true,
23
+ },
24
+ orderBy: {
25
+ updatedAt: 'desc',
26
+ },
27
+ });
28
+
29
+ if (!list) {
30
+ return <p>Loading ...</p>;
31
+ }
32
+
33
+ const _createTodo = async () => {
34
+ const todo = await createTodo({
35
+ data: {
36
+ title,
37
+ ownerId: user!.id,
38
+ listId: list!.id,
39
+ },
40
+ });
41
+ console.log(`Todo created: ${todo}`);
42
+ setTitle('');
43
+ };
44
+
45
+ return (
46
+ <>
47
+ <div className="px-8 py-2">
48
+ <BreadCrumb />
49
+ </div>
50
+ <div className="container w-full flex flex-col items-center pt-12">
51
+ <h1 className="text-2xl font-semibold mb-4">{list?.title}</h1>
52
+ <div className="flex space-x-2">
53
+ <input
54
+ type="text"
55
+ placeholder="Type a title and press enter"
56
+ className="input input-bordered w-72 max-w-xs mt-2"
57
+ value={title}
58
+ onKeyUp={(e: KeyboardEvent<HTMLInputElement>) => {
59
+ if (e.key === 'Enter') {
60
+ _createTodo();
61
+ }
62
+ }}
63
+ onChange={(e: ChangeEvent<HTMLInputElement>) => {
64
+ setTitle(e.currentTarget.value);
65
+ }}
66
+ />
67
+ <button onClick={() => _createTodo()}>
68
+ <PlusIcon className="w-6 h-6 text-gray-500" />
69
+ </button>
70
+ </div>
71
+ <ul className="flex flex-col space-y-4 py-8 w-11/12 md:w-auto">
72
+ {todos?.map((todo) => (
73
+ <TodoComponent
74
+ key={todo.id}
75
+ value={todo}
76
+ updated={() => {
77
+ invalidateTodos();
78
+ }}
79
+ deleted={() => {
80
+ invalidateTodos();
81
+ }}
82
+ />
83
+ ))}
84
+ </ul>
85
+ </div>
86
+ </>
87
+ );
88
+ }
@@ -0,0 +1,169 @@
1
+ import { SpaceContext, UserContext } from '@lib/context';
2
+ import { ChangeEvent, FormEvent, useContext, useState } from 'react';
3
+ import { useList } from '@zenstackhq/runtime/hooks';
4
+ import { toast } from 'react-toastify';
5
+ import TodoList from 'components/TodoList';
6
+ import BreadCrumb from 'components/BreadCrumb';
7
+ import SpaceMembers from 'components/SpaceMembers';
8
+
9
+ function CreateDialog() {
10
+ const user = useContext(UserContext);
11
+ const space = useContext(SpaceContext);
12
+
13
+ const [modalOpen, setModalOpen] = useState(false);
14
+ const [title, setTitle] = useState('');
15
+ const [_private, setPrivate] = useState(false);
16
+
17
+ const { create } = useList();
18
+
19
+ const onSubmit = async (event: FormEvent) => {
20
+ event.preventDefault();
21
+
22
+ try {
23
+ await create({
24
+ data: {
25
+ title,
26
+ private: _private,
27
+ spaceId: space!.id,
28
+ ownerId: user!.id,
29
+ },
30
+ });
31
+ } catch (err) {
32
+ toast.error(`Failed to create list: ${err}`);
33
+ return;
34
+ }
35
+
36
+ toast.success('List created successfully!');
37
+
38
+ // reset states
39
+ setTitle('');
40
+ setPrivate(false);
41
+
42
+ // close modal
43
+ setModalOpen(false);
44
+ };
45
+
46
+ return (
47
+ <>
48
+ <input
49
+ type="checkbox"
50
+ id="create-list-modal"
51
+ className="modal-toggle"
52
+ checked={modalOpen}
53
+ onChange={(e: ChangeEvent<HTMLInputElement>) => {
54
+ setModalOpen(e.currentTarget.checked);
55
+ }}
56
+ />
57
+ <div className="modal">
58
+ <div className="modal-box">
59
+ <h3 className="font-bold text-xl mb-8">
60
+ Create a Todo list
61
+ </h3>
62
+ <form onSubmit={onSubmit}>
63
+ <div className="flex flex-col space-y-4">
64
+ <div className="flex items-center">
65
+ <label
66
+ htmlFor="title"
67
+ className="text-lg inline-block w-20"
68
+ >
69
+ Title
70
+ </label>
71
+ <input
72
+ id="title"
73
+ type="text"
74
+ required
75
+ placeholder="Title of your list"
76
+ className="input input-bordered w-full max-w-xs mt-2"
77
+ value={title}
78
+ onChange={(
79
+ e: FormEvent<HTMLInputElement>
80
+ ) => setTitle(e.currentTarget.value)}
81
+ />
82
+ </div>
83
+ <div className="flex items-center">
84
+ <label
85
+ htmlFor="private"
86
+ className="text-lg inline-block w-20"
87
+ >
88
+ Private
89
+ </label>
90
+ <input
91
+ id="private"
92
+ type="checkbox"
93
+ className="checkbox"
94
+ onChange={(
95
+ e: FormEvent<HTMLInputElement>
96
+ ) => setPrivate(e.currentTarget.checked)}
97
+ />
98
+ </div>
99
+ </div>
100
+ <div className="modal-action">
101
+ <input
102
+ className="btn btn-primary"
103
+ type="submit"
104
+ value="Create"
105
+ />
106
+ <label
107
+ htmlFor="create-list-modal"
108
+ className="btn btn-outline"
109
+ >
110
+ Cancel
111
+ </label>
112
+ </div>
113
+ </form>
114
+ </div>
115
+ </div>
116
+ </>
117
+ );
118
+ }
119
+
120
+ export default function SpaceHome() {
121
+ const space = useContext(SpaceContext);
122
+ const { find } = useList();
123
+
124
+ const { data: lists, mutate: invalidateLists } = find({
125
+ where: {
126
+ space: {
127
+ id: space?.id,
128
+ },
129
+ },
130
+ include: {
131
+ owner: true,
132
+ },
133
+ orderBy: {
134
+ updatedAt: 'desc',
135
+ },
136
+ });
137
+
138
+ return (
139
+ <>
140
+ <div className="px-8 py-2">
141
+ <BreadCrumb />
142
+ </div>
143
+ <div className="p-8">
144
+ <div className="w-full flex flex-col md:flex-row mb-8 space-y-4 md:space-y-0 md:space-x-4">
145
+ <label
146
+ htmlFor="create-list-modal"
147
+ className="btn btn-primary btn-wide modal-button"
148
+ >
149
+ Create a list
150
+ </label>
151
+ <SpaceMembers />
152
+ </div>
153
+
154
+ <ul className="flex flex-wrap gap-6">
155
+ {lists?.map((list) => (
156
+ <li key={list.id}>
157
+ <TodoList
158
+ value={list}
159
+ deleted={() => invalidateLists()}
160
+ />
161
+ </li>
162
+ ))}
163
+ </ul>
164
+
165
+ <CreateDialog />
166
+ </div>
167
+ </>
168
+ );
169
+ }
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,4 @@
1
+ <svg width="283" height="64" viewBox="0 0 283 64" fill="none"
2
+ xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
4
+ </svg>
@@ -0,0 +1,7 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ body {
6
+ @apply text-gray-800;
7
+ }
@@ -0,0 +1,11 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ './pages/**/*.{js,ts,jsx,tsx}',
5
+ './components/**/*.{js,ts,jsx,tsx}',
6
+ ],
7
+ theme: {
8
+ extend: {},
9
+ },
10
+ plugins: [require('daisyui'), require('@tailwindcss/line-clamp')],
11
+ };
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "incremental": true,
17
+ "baseUrl": ".",
18
+ "paths": {
19
+ "@api/*": ["pages/api/*"],
20
+ "@lib/*": ["lib/*"],
21
+ "@components/*": ["lib/components/*"],
22
+ "@components": ["lib/components/index"],
23
+ "@zenstack/*": [".zenstack/*"]
24
+ }
25
+ },
26
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
27
+ "exclude": ["node_modules"]
28
+ }