- Currently, your day is set to start at {convertMinsToHrsMins(props.user.startTime)} and end
- at {convertMinsToHrsMins(props.user.endTime)}.
+ Currently, your day is set to start at {convertMinsToHrsMins(user.startTime)} and end at{" "}
+ {convertMinsToHrsMins(user.endTime)}.
@@ -199,7 +204,7 @@ export default function Availability(props) {
id="hours"
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 block w-full sm:text-sm border-gray-300 rounded-sm"
placeholder="9"
- defaultValue={convertMinsToHrsMins(props.user.startTime).split(":")[0]}
+ defaultValue={convertMinsToHrsMins(user.startTime).split(":")[0]}
/>
-
-
-
-
- {isEmpty ? (
+
+
+
+
+ {query.status === "error" && (
+
+ )}
+ {query.status === "loading" &&
}
+ {bookings &&
+ (bookings.length === 0 ? (
- )}
-
+ ))}
-
-
+
+
);
}
-
-export async function getServerSideProps(context) {
- const session = await getSession(context);
-
- if (!session) {
- return { redirect: { permanent: false, destination: "/auth/login" } };
- }
-
- const user: User = await prisma.user.findUnique({
- where: {
- id: session.user.id,
- },
- select: {
- email: true,
- },
- });
-
- const b = await prisma.booking.findMany({
- where: {
- OR: [
- {
- userId: session.user.id,
- },
- {
- attendees: {
- some: {
- email: user.email,
- },
- },
- },
- ],
- },
- select: {
- uid: true,
- title: true,
- description: true,
- attendees: true,
- confirmed: true,
- rejected: true,
- id: true,
- startTime: true,
- endTime: true,
- eventType: {
- select: {
- team: {
- select: {
- name: true,
- },
- },
- },
- },
- status: true,
- },
- orderBy: {
- startTime: "asc",
- },
- });
-
- const bookings = b.reverse().map((booking) => {
- return { ...booking, startTime: booking.startTime.toISOString(), endTime: booking.endTime.toISOString() };
- });
-
- return { props: { session, bookings } };
-}
diff --git a/server/createContext.ts b/server/createContext.ts
new file mode 100644
index 0000000000..44c526d65c
--- /dev/null
+++ b/server/createContext.ts
@@ -0,0 +1,68 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import * as trpc from "@trpc/server";
+import { Maybe } from "@trpc/server";
+import * as trpcNext from "@trpc/server/adapters/next";
+
+import { getSession, Session } from "@lib/auth";
+import prisma from "@lib/prisma";
+import { defaultAvatarSrc } from "@lib/profile";
+
+async function getUserFromSession(session: Maybe
) {
+ if (!session?.user?.id) {
+ return null;
+ }
+ const user = await prisma.user.findUnique({
+ where: {
+ id: session.user.id,
+ },
+ select: {
+ id: true,
+ username: true,
+ name: true,
+ email: true,
+ bio: true,
+ timeZone: true,
+ weekStart: true,
+ startTime: true,
+ endTime: true,
+ bufferTime: true,
+ theme: true,
+ createdDate: true,
+ hideBranding: true,
+ avatar: true,
+ },
+ });
+
+ // some hacks to make sure `username` and `email` are never inferred as `null`
+ if (!user) {
+ return null;
+ }
+ const { email, username } = user;
+ if (!username || !email) {
+ return null;
+ }
+ const avatar = user.avatar || defaultAvatarSrc({ email });
+ return {
+ ...user,
+ avatar,
+ email,
+ username,
+ };
+}
+
+/**
+ * Creates context for an incoming request
+ * @link https://trpc.io/docs/context
+ */
+export const createContext = async ({ req, res }: trpcNext.CreateNextContextOptions) => {
+ // for API-response caching see https://trpc.io/docs/caching
+ const session = await getSession({ req });
+
+ return {
+ prisma,
+ session,
+ user: await getUserFromSession(session),
+ };
+};
+
+export type Context = trpc.inferAsyncReturnType;
diff --git a/server/createRouter.ts b/server/createRouter.ts
new file mode 100644
index 0000000000..26fc7303e9
--- /dev/null
+++ b/server/createRouter.ts
@@ -0,0 +1,10 @@
+import * as trpc from "@trpc/server";
+
+import { Context } from "./createContext";
+
+/**
+ * Helper function to create a router with context
+ */
+export function createRouter() {
+ return trpc.router();
+}
diff --git a/server/routers/_app.ts b/server/routers/_app.ts
new file mode 100644
index 0000000000..5809af5270
--- /dev/null
+++ b/server/routers/_app.ts
@@ -0,0 +1,26 @@
+/**
+ * This file contains the root router of your tRPC-backend
+ */
+import { createRouter } from "../createRouter";
+import { viewerRouter } from "./viewer";
+
+/**
+ * Create your application's root router
+ * If you want to use SSG, you need export this
+ * @link https://trpc.io/docs/ssg
+ * @link https://trpc.io/docs/router
+ */
+export const appRouter = createRouter()
+ /**
+ * Add data transformers
+ * @link https://trpc.io/docs/data-transformers
+ */
+ // .transformer(superjson)
+ /**
+ * Optionally do custom error (type safe!) formatting
+ * @link https://trpc.io/docs/error-formatting
+ */
+ // .formatError(({ shape, error }) => { })
+ .merge("viewer.", viewerRouter);
+
+export type AppRouter = typeof appRouter;
diff --git a/server/routers/viewer.tsx b/server/routers/viewer.tsx
new file mode 100644
index 0000000000..d980ddb0df
--- /dev/null
+++ b/server/routers/viewer.tsx
@@ -0,0 +1,81 @@
+import { TRPCError } from "@trpc/server";
+
+import { createRouter } from "../createRouter";
+
+// routes only available to authenticated users
+export const viewerRouter = createRouter()
+ // check that user is authenticated
+ .middleware(({ ctx, next }) => {
+ const { user } = ctx;
+ if (!user) {
+ throw new TRPCError({ code: "UNAUTHORIZED" });
+ }
+
+ return next({
+ ctx: {
+ ...ctx,
+ // session value is known to be non-null now
+ user,
+ },
+ });
+ })
+ .query("me", {
+ resolve({ ctx }) {
+ return ctx.user;
+ },
+ })
+ .query("bookings", {
+ async resolve({ ctx }) {
+ const { prisma, user } = ctx;
+ const bookingsQuery = await prisma.booking.findMany({
+ where: {
+ OR: [
+ {
+ userId: user.id,
+ },
+ {
+ attendees: {
+ some: {
+ email: user.email,
+ },
+ },
+ },
+ ],
+ },
+ select: {
+ uid: true,
+ title: true,
+ description: true,
+ attendees: true,
+ confirmed: true,
+ rejected: true,
+ id: true,
+ startTime: true,
+ endTime: true,
+ eventType: {
+ select: {
+ team: {
+ select: {
+ name: true,
+ },
+ },
+ },
+ },
+ status: true,
+ },
+ orderBy: {
+ startTime: "asc",
+ },
+ });
+
+ const bookings = bookingsQuery.reverse().map((booking) => {
+ return {
+ ...booking,
+ startTime: booking.startTime.toISOString(),
+ endTime: booking.endTime.toISOString(),
+ };
+ });
+
+ return bookings;
+ },
+ });
diff --git a/server/ssg.ts b/server/ssg.ts
new file mode 100644
index 0000000000..3c1025d260
--- /dev/null
+++ b/server/ssg.ts
@@ -0,0 +1,14 @@
+import { createSSGHelpers } from "@trpc/react/ssg";
+
+import prisma from "@lib/prisma";
+
+import { appRouter } from "./routers/_app";
+
+export const ssg = createSSGHelpers({
+ router: appRouter,
+ ctx: {
+ prisma,
+ session: null,
+ user: null,
+ },
+});
diff --git a/tsconfig.json b/tsconfig.json
index 45f10e64a7..6fda3124ca 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,11 +6,13 @@
"paths": {
"@components/*": ["components/*"],
"@lib/*": ["lib/*"],
+ "@server/*": ["server/*"],
"@ee/*": ["ee/*"]
},
"allowJs": true,
"skipLibCheck": true,
"strict": true,
+ "strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
diff --git a/yarn.lock b/yarn.lock
index 833a9d3c9d..32678776a4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -282,7 +282,7 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.17", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7":
+"@babel/runtime@^7.10.5", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.13.17", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.5", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.0":
version "7.15.4"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz"
dependencies:
@@ -1006,19 +1006,12 @@
"@radix-ui/react-primitive" "0.1.0"
"@radix-ui/react-use-callback-ref" "0.1.0"
-"@radix-ui/react-id@0.1.0":
+"@radix-ui/react-id@0.1.0", "@radix-ui/react-id@^0.1.0":
version "0.1.0"
resolved "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-0.1.0.tgz"
dependencies:
"@babel/runtime" "^7.13.10"
-"@radix-ui/react-id@^0.1.0":
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.1.0.tgz#d01067520fb8f4b09da3f914bfe6cb0f88c26721"
- integrity sha512-SubMSz7rAtl6w8qZ9YBRbDe9GjW36JugBsc6aYqng8tFydvNtkuBMj86zN/x5QiomMo+r8ylBVvuWzRkS0WbBA==
- dependencies:
- "@babel/runtime" "^7.13.10"
-
"@radix-ui/react-label@0.1.0":
version "0.1.0"
resolved "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-0.1.0.tgz"
@@ -1281,6 +1274,41 @@
javascript-natural-sort "0.7.1"
lodash "4.17.21"
+"@trpc/client@^9.8.0":
+ version "9.8.0"
+ resolved "https://registry.npmjs.org/@trpc/client/-/client-9.8.0.tgz#e60f4ff1fff7c34b1f36e240441022192c463d6e"
+ integrity sha512-YGUJI8EvAykXKciDe62aLNwk4TNh4bX+qHSWtgbF5d6qRQOwViINpB6vR8YSugPvmC4F+hvKm61wSd01cCWN4g==
+ dependencies:
+ "@babel/runtime" "^7.9.0"
+ "@trpc/server" "^9.8.0"
+
+"@trpc/next@^9.8.0":
+ version "9.8.0"
+ resolved "https://registry.npmjs.org/@trpc/next/-/next-9.8.0.tgz#ab76cf56de604551565f13509f9163fb98dc8b1a"
+ integrity sha512-VfpTPtFt8E2lgHVolFtE90DkP2mV8CqdvLQKQ8gY4OfsOoAlitnJPAVeAxvHPVruUyDUjuggFocc1TZnEWRdxg==
+ dependencies:
+ "@babel/runtime" "^7.9.0"
+ "@trpc/client" "^9.8.0"
+ "@trpc/react" "^9.8.0"
+ "@trpc/server" "^9.8.0"
+ react-ssr-prepass "^1.4.0"
+
+"@trpc/react@^9.8.0":
+ version "9.8.0"
+ resolved "https://registry.npmjs.org/@trpc/react/-/react-9.8.0.tgz#1ae46b84da9fb4e257335e6bdb2a489d70a1a9b2"
+ integrity sha512-vErvC98QBQh0XzfPm9LA/dmGHBm6N9/m+B3XdIkimGOD055bsxgKMLW25BeeDV3gLWlzQJ7nwOrsxKRd6fLi3w==
+ dependencies:
+ "@babel/runtime" "^7.9.0"
+ "@trpc/client" "^9.8.0"
+ "@trpc/server" "^9.8.0"
+
+"@trpc/server@^9.8.0":
+ version "9.8.0"
+ resolved "https://registry.npmjs.org/@trpc/server/-/server-9.8.0.tgz#f7e8a0ab46cc41179dc06722cb3dbe33901eddb6"
+ integrity sha512-YFmS+5SwDQ9NRO9JvNyl1oLprWE2AwS2huXYcGo9e3Fl5ju2Q2MN0JYvVh7XYNyp1rI2EKqVVRzXEuCNk+3vVQ==
+ dependencies:
+ tslib "^2.1.0"
+
"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz"
@@ -6004,10 +6032,10 @@ react-portal@^4.2.0:
dependencies:
prop-types "^15.5.8"
-react-query@^3.21.0:
- version "3.24.4"
- resolved "https://registry.npmjs.org/react-query/-/react-query-3.24.4.tgz"
- integrity sha512-p/t18+FN5P//bk/xR39r4JRWEigYzia2+J3lmKWSZHYbcivQlygJixY+81NiTNxT1P+/P6cl173b1lEbh1R8yQ==
+react-query@^3.23.1:
+ version "3.23.1"
+ resolved "https://registry.npmjs.org/react-query/-/react-query-3.23.1.tgz#cde2d268958716d34a23e62aabba668752ba8f95"
+ integrity sha512-pq0vEwB5PNGvkWJNUk0qPpsxcDmhzY80ZLNPLIVQJ3k2UyXoGccPTrgOIj4Kz2TrMfgvRBTNwiSxHdaW7Sl0WQ==
dependencies:
"@babel/runtime" "^7.5.5"
broadcast-channel "^3.4.1"
@@ -6046,6 +6074,11 @@ react-select@^4.3.1:
react-input-autosize "^3.0.0"
react-transition-group "^4.3.0"
+react-ssr-prepass@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmjs.org/react-ssr-prepass/-/react-ssr-prepass-1.4.0.tgz#33a3db19414f0f8f9f3f781c88f760ae366b4f51"
+ integrity sha512-0SzdmiQUtHvhxCabHg9BI/pkJfijGkQ0jQL6fC4YFy7idaDOuaiQLsajIkkNxffFXtJFHIWFITlve2WB88e0Jw==
+
react-style-singleton@^2.1.0:
version "2.1.1"
resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.1.1.tgz"
@@ -7421,3 +7454,8 @@ zen-observable-ts@^1.0.0:
zen-observable@0.8.15:
version "0.8.15"
resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz"
+
+zod@^3.8.2:
+ version "3.8.2"
+ resolved "https://registry.npmjs.org/zod/-/zod-3.8.2.tgz#f25b78bc76e64f31318d242e301c23d3d610b7a1"
+ integrity sha512-kpwVRACazsOhELVt5h4R2pC2OndrqaBK4+z134TWOsnzn7n2uOYnSyvx0QAn410pl28CgVtkSi5ew7e/AgO0oA==