diff --git a/graphcdn.yml b/graphcdn.yml deleted file mode 100644 index 6b77361af..000000000 --- a/graphcdn.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: boilerplate -originUrl: 'https://nq-boilerplate.herokuapp.com/graphql' -schema: 'packages/api/schema.graphql' -scopes: - AUTHENTICATED: 'cookie:boilerplate.session.token|header:Authorization' -rules: - - description: Cache everything (default) - maxAge: 900 - scope: AUTHENTICATED - swr: 900 - types: - Query: true - - description: Users - maxAge: 900 - scope: AUTHENTICATED - swr: 900 - types: - Query: - users: true diff --git a/packages/api/package.json b/packages/api/package.json index 5d31c2978..ad76799e4 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -13,6 +13,7 @@ "start": "node dist", "generate": "prisma generate", "db:migrate": "prisma migrate dev", + "db:seed": "ts-node src/db/seed.ts", "build": "rm -rf dist && tsc --build", "buildSchema": "ts-node buildSchema.ts", "typecheck": "tsc --noEmit", @@ -64,6 +65,7 @@ "typegraphql-prisma": "0.22.0" }, "devDependencies": { + "@faker-js/faker": "^7.6.0", "@types/bcryptjs": "2.4.2", "@types/express": "4.17.14", "@types/express-jwt": "6.0.4", diff --git a/packages/api/src/db/schema.prisma b/packages/api/src/db/schema.prisma index 9b131bbda..b6d84c0e6 100644 --- a/packages/api/src/db/schema.prisma +++ b/packages/api/src/db/schema.prisma @@ -1,11 +1,13 @@ generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" + previewFeatures = ["orderByNulls"] } generator typegraphql { - provider = "typegraphql-prisma" - output = "../../../../node_modules/@generated" - emitOnly = "inputs,enums,crudResolvers" + provider = "typegraphql-prisma" + output = "../../../../node_modules/@generated" + emitOnly = "inputs,enums,crudResolvers" + useSimpleInputs = true } datasource db { diff --git a/packages/api/src/db/seed.ts b/packages/api/src/db/seed.ts new file mode 100644 index 000000000..a4b55a78f --- /dev/null +++ b/packages/api/src/db/seed.ts @@ -0,0 +1,23 @@ +import "reflect-metadata" +import "dotenv/config" + +import { faker } from "@faker-js/faker" + +import { prisma } from "../lib/prisma" + +const createUserData = () => + Array.from({ length: 40 }).map(() => { + const firstName = faker.name.firstName() + const lastName = faker.name.lastName() + return { + firstName, + lastName, + email: faker.internet.email(firstName, lastName), + password: faker.internet.password(), + } + }) + +export async function main() { + await prisma.user.createMany({ data: createUserData() }) +} +main() diff --git a/packages/api/src/modules/user/inputs/updateUser.input.ts b/packages/api/src/modules/user/inputs/updateUser.input.ts deleted file mode 100644 index 488623c38..000000000 --- a/packages/api/src/modules/user/inputs/updateUser.input.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { IsNotEmpty, MinLength } from "class-validator" -import { Field, InputType } from "type-graphql" - -import { User } from "../user.model" - -@InputType() -export class UpdateUserInput implements Partial { - @IsNotEmpty() - @Field({ nullable: true }) - firstName?: string - - @IsNotEmpty() - @Field({ nullable: true }) - lastName?: string - - @IsNotEmpty() - @Field({ nullable: true }) - email?: string - - @Field({ nullable: true }) - avatar?: string - - @Field({ nullable: true }) - bio?: string - - @MinLength(8) - @IsNotEmpty() - @Field({ nullable: true }) - password?: string -} diff --git a/packages/api/src/modules/user/user.resolver.ts b/packages/api/src/modules/user/user.resolver.ts index 4852ad088..7f177a460 100644 --- a/packages/api/src/modules/user/user.resolver.ts +++ b/packages/api/src/modules/user/user.resolver.ts @@ -1,7 +1,7 @@ import { Arg, Args, Ctx, Mutation, Query, Resolver } from "type-graphql" import { Inject, Service } from "typedi" -import { CreateOneUserArgs, FindFirstUserArgs, FindManyUserArgs, Role } from "@generated" +import { CreateOneUserArgs, FindFirstUserArgs, FindManyUserArgs, Role, UserUpdateInput } from "@generated" import { createToken, decodeRefreshToken, decodeToken } from "../../lib/jwt" import { prisma } from "../../lib/prisma" @@ -12,7 +12,6 @@ import { ResolverContext } from "../shared/resolverContext" import { LoginInput } from "./inputs/login.input" import { RegisterInput } from "./inputs/register.input" import { ResetPasswordInput } from "./inputs/resetPassword.input" -import { UpdateUserInput } from "./inputs/updateUser.input" import { AuthResponse } from "./responses/auth.response" import { RefreshTokenResponse } from "./responses/refreshToken.response" import { UsersResponse } from "./responses/users.response" @@ -58,7 +57,7 @@ export default class UserResolver { // UPDATE ME @UseAuth() @Mutation(() => User) - async updateMe(@CurrentUser() currentUser: User, @Arg("data") data: UpdateUserInput): Promise { + async updateMe(@CurrentUser() currentUser: User, @Arg("data") data: UserUpdateInput): Promise { return await prisma.user.update({ where: { id: currentUser.id }, data }) } diff --git a/packages/app/package.json b/packages/app/package.json index 9a6301b80..00635ff56 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -31,32 +31,32 @@ "@react-navigation/native": "6.0.13", "@react-navigation/native-stack": "6.9.1", "@react-navigation/stack": "6.3.2", - "expo": "45.0.8", - "expo-status-bar": "1.4.0", + "expo": "^46.0.0", + "expo-status-bar": "~1.4.0", "graphql": "15.8.0", "graphql-tag": "2.12.6", "native-base": "3.4.19", "polished": "4.2.2", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-native": "0.68.2", - "react-native-safe-area-context": "4.2.4", - "react-native-screens": "~3.11.1", - "react-native-svg": "12.4.4", - "react-native-web": "0.18.9", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-native": "0.69.6", + "react-native-safe-area-context": "4.3.1", + "react-native-screens": "~3.15.0", + "react-native-svg": "12.3.0", + "react-native-web": "~0.18.7", "styled-components": "5.3.6", "styled-system": "5.1.5" }, "devDependencies": { - "@babel/core": "7.19.6", + "@babel/core": "^7.18.6", "@graphql-codegen/add": "3.2.1", "@graphql-codegen/cli": "2.13.7", "@graphql-codegen/typescript": "2.8.0", "@graphql-codegen/typescript-operations": "2.5.5", "@graphql-codegen/typescript-react-apollo": "3.3.5", - "@types/react": "17.0.51", - "@types/react-dom": "17.0.17", - "@types/react-native": "0.70.6", + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", + "@types/react-native": "~0.69.1", "eslint-plugin-react": "7.31.10", "eslint-plugin-react-hooks": "4.6.0" }, diff --git a/packages/app/src/components/InputError.tsx b/packages/app/src/components/InputError.tsx index 835934574..ebbedad66 100644 --- a/packages/app/src/components/InputError.tsx +++ b/packages/app/src/components/InputError.tsx @@ -6,7 +6,7 @@ interface Props { error?: Merge>> | undefined } -export const InputError: React.FC = (props) => { +export function InputError(props: Props) { if (!props.error) return null return ( diff --git a/packages/web/.env.local b/packages/web/.env.local new file mode 100644 index 000000000..e26e33431 --- /dev/null +++ b/packages/web/.env.local @@ -0,0 +1 @@ +SENTRY_IGNORE_API_RESOLUTION_ERROR=1 \ No newline at end of file diff --git a/packages/web/.gitignore b/packages/web/.gitignore index 0077c3dba..c4415ff3e 100644 --- a/packages/web/.gitignore +++ b/packages/web/.gitignore @@ -5,3 +5,5 @@ node_modules/ # Sentry .sentryclirc + +.vscode diff --git a/packages/web/codegen.yml b/packages/web/codegen.yml index a17139901..b2119fc58 100644 --- a/packages/web/codegen.yml +++ b/packages/web/codegen.yml @@ -3,6 +3,7 @@ documents: - "src/components/**/*.{ts,tsx}" - "src/lib/**/*.{ts,tsx}" - "src/pages/**/*.{ts,tsx}" + - "src/app/**/*.{ts,tsx}" overwrite: true generates: src/lib/graphql.tsx: diff --git a/packages/web/next.config.js b/packages/web/next.config.js index c77eaf768..792d9a750 100644 --- a/packages/web/next.config.js +++ b/packages/web/next.config.js @@ -8,7 +8,10 @@ const sentryWebpackPluginOptions = { */ module.exports = withSentryConfig( { - reactStrictMode: false, + experimental: { appDir: true, esmExternals: false }, + sentry: { + hideSourceMaps: false, + }, env: { NEXT_PUBLIC_PULL_REQUEST_NUMBER: process.env.VERCEL_GIT_PULL_REQUEST_NUMBER, NEXT_PUBLIC_APP_ENV: process.env.NEXT_PUBLIC_APP_ENV, diff --git a/packages/web/package.json b/packages/web/package.json index 3b734816f..50acd18e5 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -29,17 +29,19 @@ "license": "MIT", "dependencies": { "@apollo/client": "3.7.0", - "@chakra-ui/react": "1.8.9", - "@emotion/react": "11.10.4", - "@emotion/styled": "11.10.4", + "@chakra-ui/icons": "^2.0.11", + "@chakra-ui/react": "^2.3.6", + "@emotion/react": "11.10.5", + "@emotion/styled": "11.10.5", "@hookform/resolvers": "2.9.10", + "@next/font": "^13.0.0", "@sentry/nextjs": "7.16.0", "dayjs": "1.11.6", "framer-motion": "6.5.1", "graphql": "15.8.0", - "next": "12.3.1", - "react": "17.0.2", - "react-dom": "17.0.2", + "next": "^13.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-dropzone": "14.2.3", "react-hook-form": "7.38.0", "react-icons": "4.6.0", @@ -53,8 +55,8 @@ "@graphql-codegen/typescript-operations": "2.5.5", "@graphql-codegen/typescript-react-apollo": "3.3.5", "@types/cookie": "0.5.1", - "@types/react": "17.0.51", - "@types/react-dom": "17.0.17", - "eslint-config-next": "12.3.1" + "@types/react": "^18.0.24", + "@types/react-dom": "^18.0.8", + "eslint-config-next": "^13.0.0" } } diff --git a/packages/web/src/pages/forgot-password.tsx b/packages/web/src/app/(auth)/forgot-password/page.tsx similarity index 87% rename from packages/web/src/pages/forgot-password.tsx rename to packages/web/src/app/(auth)/forgot-password/page.tsx index d7fdeee09..07f900265 100644 --- a/packages/web/src/pages/forgot-password.tsx +++ b/packages/web/src/app/(auth)/forgot-password/page.tsx @@ -1,9 +1,9 @@ +"use client" import * as React from "react" import { gql } from "@apollo/client" import { Box, Button, Center, Heading, Stack, Text } from "@chakra-ui/react" -import Head from "next/head" import Link from "next/link" -import { useRouter } from "next/router" +import { useRouter } from "next/navigation" import type { MutationForgotPasswordArgs } from "lib/graphql" import { useForgotPasswordMutation } from "lib/graphql" @@ -11,7 +11,6 @@ import { useForm } from "lib/hooks/useForm" import { useToast } from "lib/hooks/useToast" import Yup from "lib/yup" import { Form } from "components/Form" -import { HomeLayout } from "components/HomeLayout" import { Input } from "components/Input" const _ = gql` @@ -45,9 +44,6 @@ export default function ForgotPassword() { } return (
- - Forgot password -
@@ -65,4 +61,3 @@ export default function ForgotPassword() {
) } -ForgotPassword.getLayout = (page: React.ReactNode) => {page} diff --git a/packages/web/src/app/(auth)/layout.tsx b/packages/web/src/app/(auth)/layout.tsx new file mode 100644 index 000000000..94a9ba090 --- /dev/null +++ b/packages/web/src/app/(auth)/layout.tsx @@ -0,0 +1,63 @@ +"use client" +import type { LinkProps} from "@chakra-ui/react"; +import { Box, HStack, Link, useColorModeValue } from "@chakra-ui/react" +import NextLink from "next/link" +import { usePathname } from "next/navigation" + +import { Limiter } from "components/Limiter" + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> + + + {/* Left link list */} + + + Home + + + + + {children} + + ) +} + +interface HomeLinkProps extends LinkProps { + href: string +} + +function HomeLink({ href, ...props }: HomeLinkProps) { + const pathname = usePathname() + const isActive = pathname === href + + return ( + + {props.children} + + ) +} diff --git a/packages/web/src/app/(auth)/login/head.tsx b/packages/web/src/app/(auth)/login/head.tsx new file mode 100644 index 000000000..ac68566d1 --- /dev/null +++ b/packages/web/src/app/(auth)/login/head.tsx @@ -0,0 +1,7 @@ +export default function Head() { + return ( + <> + Login + + ) +} diff --git a/packages/web/src/pages/login.tsx b/packages/web/src/app/(auth)/login/page.tsx similarity index 86% rename from packages/web/src/pages/login.tsx rename to packages/web/src/app/(auth)/login/page.tsx index aad3ef9f7..08889b0f7 100644 --- a/packages/web/src/pages/login.tsx +++ b/packages/web/src/app/(auth)/login/page.tsx @@ -1,9 +1,9 @@ +"use client" import * as React from "react" import { gql, useApolloClient } from "@apollo/client" import * as c from "@chakra-ui/react" -import Head from "next/head" import Link from "next/link" -import { useRouter } from "next/router" +import { useRouter } from "next/navigation" import { ACCESS_TOKEN, REFRESH_TOKEN_KEY } from "lib/config" import type { LoginInput, MeQuery } from "lib/graphql" @@ -12,8 +12,6 @@ import { useForm } from "lib/hooks/useForm" import Yup from "lib/yup" import { Form } from "components/Form" import { FormError } from "components/FormError" -import { withNoAuth } from "components/hoc/withNoAuth" -import { HomeLayout } from "components/HomeLayout" import { Input } from "components/Input" const _ = gql` @@ -33,7 +31,7 @@ const LoginSchema = Yup.object().shape({ password: Yup.string().min(8, "Must be at least 8 characters"), }) -function Login() { +export default function Login() { const client = useApolloClient() const [login, { loading }] = useLoginMutation() @@ -59,9 +57,6 @@ function Login() { return ( - - Login - @@ -84,6 +79,4 @@ function Login() { ) } -Login.getLayout = (page: React.ReactNode) => {page} - -export default withNoAuth(Login) +// export default withNoAuth(Login) diff --git a/packages/web/src/pages/logout.tsx b/packages/web/src/app/(auth)/logout/page.tsx similarity index 86% rename from packages/web/src/pages/logout.tsx rename to packages/web/src/app/(auth)/logout/page.tsx index d0eba6c21..c55743ede 100644 --- a/packages/web/src/pages/logout.tsx +++ b/packages/web/src/app/(auth)/logout/page.tsx @@ -1,6 +1,7 @@ +"use client" import * as React from "react" import { Center, Spinner } from "@chakra-ui/react" -import { useRouter } from "next/router" +import { useRouter } from "next/navigation" import { useMe } from "lib/hooks/useMe" diff --git a/packages/web/src/pages/register.tsx b/packages/web/src/app/(auth)/register/page.tsx similarity index 88% rename from packages/web/src/pages/register.tsx rename to packages/web/src/app/(auth)/register/page.tsx index d3cc9879e..1f6582ae6 100644 --- a/packages/web/src/pages/register.tsx +++ b/packages/web/src/app/(auth)/register/page.tsx @@ -1,19 +1,18 @@ +"use client" import * as React from "react" import { gql, useApolloClient } from "@apollo/client" import { Box, Button, Center, Heading, Stack } from "@chakra-ui/react" import Head from "next/head" import Link from "next/link" -import { useRouter } from "next/router" +import { useRouter } from "next/navigation" -import { ACCESS_TOKEN,REFRESH_TOKEN_KEY } from "lib/config" +import { ACCESS_TOKEN, REFRESH_TOKEN_KEY } from "lib/config" import type { MeQuery, RegisterInput } from "lib/graphql" import { MeDocument, useRegisterMutation } from "lib/graphql" import { useForm } from "lib/hooks/useForm" import Yup from "lib/yup" import { Form } from "components/Form" import { FormError } from "components/FormError" -import { withNoAuth } from "components/hoc/withNoAuth" -import { HomeLayout } from "components/HomeLayout" import { Input } from "components/Input" const _ = gql` @@ -35,7 +34,7 @@ const RegisterSchema = Yup.object().shape({ lastName: Yup.string().required("Required"), }) -function Register() { +export default function Register() { const client = useApolloClient() const [register, { loading }] = useRegisterMutation() @@ -82,6 +81,3 @@ function Register() { ) } - -Register.getLayout = (page: React.ReactNode) => {page} -export default withNoAuth(Register) diff --git a/packages/web/src/pages/reset-password/[token].tsx b/packages/web/src/app/(auth)/reset-password/[token]/page.tsx similarity index 81% rename from packages/web/src/pages/reset-password/[token].tsx rename to packages/web/src/app/(auth)/reset-password/[token]/page.tsx index cf29244db..6abd45af1 100644 --- a/packages/web/src/pages/reset-password/[token].tsx +++ b/packages/web/src/app/(auth)/reset-password/[token]/page.tsx @@ -1,9 +1,9 @@ +"use client" import * as React from "react" import { gql } from "@apollo/client" import { Box, Button, Center, Heading, Stack, Text } from "@chakra-ui/react" -import Head from "next/head" import Link from "next/link" -import { useRouter } from "next/router" +import { useRouter } from "next/navigation" import type { ResetPasswordInput } from "lib/graphql" import { useResetPasswordMutation } from "lib/graphql" @@ -11,7 +11,6 @@ import { useForm } from "lib/hooks/useForm" import { useToast } from "lib/hooks/useToast" import Yup from "lib/yup" import { Form } from "components/Form" -import { HomeLayout } from "components/HomeLayout" import { Input } from "components/Input" const _ = gql` @@ -24,14 +23,14 @@ const ResetPasswordSchema = Yup.object().shape({ password: Yup.string().min(8, "Must be at least 8 characters"), }) -export default function ResetPassword() { - const { query, push } = useRouter() - const token = query.token as string +export default function ResetPassword({ params }: { params: { token: string } }) { + const { push } = useRouter() + const token = params.token const [reset, { loading }] = useResetPasswordMutation() const form = useForm({ schema: ResetPasswordSchema }) const toast = useToast() const handleSubmit = async (data: ResetPasswordInput) => { - if (!data || !token) return + if (!data) return return form.handler(() => reset({ variables: { data: { ...data, token } } }), { onSuccess: () => { form.reset() @@ -45,9 +44,6 @@ export default function ResetPassword() { } return (
- - Reset password - @@ -66,4 +62,3 @@ export default function ResetPassword() {
) } -ResetPassword.getLayout = (page: React.ReactNode) => {page} diff --git a/packages/web/src/app/(home)/layout.tsx b/packages/web/src/app/(home)/layout.tsx new file mode 100644 index 000000000..4ee147efc --- /dev/null +++ b/packages/web/src/app/(home)/layout.tsx @@ -0,0 +1,14 @@ +"use client" +import { Box } from "@chakra-ui/react" + +import { Limiter } from "components/Limiter" +import { Nav } from "components/Nav" + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + +