diff --git a/.gitignore b/.gitignore index 8f322f0d..86c26cc1 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts +mongodb-realm +next.config.js \ No newline at end of file diff --git a/next.config.js b/next.config.js index 4fe9b5b1..d3902c28 100644 --- a/next.config.js +++ b/next.config.js @@ -1,7 +1,9 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - env: { - REALM_APP_ID: "sparked-next-vbuim", + env: {}, + webpack: (config) => { + config.externals = [...config.externals, { realm: "realm" }]; // required to make realm + return config; }, }; diff --git a/package.json b/package.json index d998515b..841b719a 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,14 @@ "flowbite-react": "^0.5.0", "mobx": "^6.10.0", "mobx-react-lite": "^4.0.3", - "mongodb": "^6.0.0", + "mongodb": "^6.1.0", "next": "13.4.17", "next-auth": "^4.23.1", "react": "18.2.0", "react-dom": "18.2.0", "realm": "^12.1.0", - "typescript": "5.1.6" + "typescript": "5.1.6", + "zod-form-data": "^2.0.1" }, "devDependencies": { "autoprefixer": "^10.4.15", diff --git a/src/app/api/auth/[slug]/route.ts b/src/app/api/auth/[slug]/route.ts index ec99d2b1..669c6801 100644 --- a/src/app/api/auth/[slug]/route.ts +++ b/src/app/api/auth/[slug]/route.ts @@ -6,12 +6,12 @@ const authApiHandler_ = async function GET( ) { const slug = params.slug; - const authFunctions: { [key: string]: () => {} } = { + const authFunctions: { [key: string]: (request: Request) => {} } = { signup: signup_, }; if (authFunctions[slug]) { - return authFunctions[slug](); + return authFunctions[slug](request); } else { const response = { isError: true }; diff --git a/src/app/api/auth/constants.ts b/src/app/api/auth/constants.ts index 149caa2c..5b7deb9e 100644 --- a/src/app/api/auth/constants.ts +++ b/src/app/api/auth/constants.ts @@ -2,10 +2,10 @@ import { MongoDBAdapter } from "@auth/mongodb-adapter"; import NextAuth, { NextAuthOptions } from "next-auth"; import EmailProvider from "next-auth/providers/email"; import CredentialsProvider from "next-auth/providers/credentials"; -import clientPromise from "../lib/db"; +import mongoClientPromise from "../lib/db"; export const authOptions: NextAuthOptions = { - adapter: MongoDBAdapter(clientPromise), + adapter: MongoDBAdapter(mongoClientPromise), pages: { diff --git a/src/app/api/auth/signup.ts b/src/app/api/auth/signup.ts index 82b084c8..74a2e625 100644 --- a/src/app/api/auth/signup.ts +++ b/src/app/api/auth/signup.ts @@ -1,14 +1,88 @@ -export default async function signup_() { - // NextResponse.json({ - // isError: true, - // msg: "Sorry you are not authenticated", - // }); +import { zfd } from "zod-form-data"; +import { realmApp } from "../lib/db/realm"; +import { translate } from "utils/intl"; +import { WORDS } from "utils/intl/data/constants"; +import { dbClient } from "../lib/db"; +import { dbCollections } from "../lib/db/collections"; - const response = { isError: false,msg:'it worked again' }; +export default async function signup_(request: Request) { + const schema = zfd.formData({ + email: zfd.text(), + password: zfd.text(), + }); + const formBody = await request.json(); + const { email, password } = schema.parse(formBody); - return new Response(JSON.stringify(response), { - status: 200, + try { + const db = await dbClient(); - }); + if (!db) { + console.log("signup_:error", "db error", db); + + const response = { + isError: true, + msg: translate(WORDS.unknown_error), + }; + return new Response(JSON.stringify(response), { + status: 200, + }); + } + + const user = await db.collection(dbCollections.users.name).findOne({ + email, + }); + + if (user) { + const response = { + isError: true, + msg: translate(WORDS.user_exist), + }; + return new Response(JSON.stringify(response), { + status: 200, + }); + } + + const resp = await realmApp.emailPasswordAuth.registerUser({ + email, + password, + }); + + //TODO: verify schema + await db.collection(dbCollections.users.name).insertOne({ + email, + is_verified: false, + created_at: new Date(), + }); + + const response = { + isError: false, + msg: translate(WORDS.user_created), + email, + }; + + return new Response(JSON.stringify(response), { + status: 200, + }); + } catch (error) { + console.log("signup_:error", error); + const errorCodeIndex = `${JSON.stringify(error)}`.lastIndexOf("code"); + + const code = + errorCodeIndex === -1 + ? 0 + : Number(`${error}`.substring(errorCodeIndex).match(/\d+/g)); + + const resp = { + isError: true, + msg: + code === 4348 + ? translate(WORDS.email_error, true) + : translate(WORDS.unknown_error), + }; + + return new Response(JSON.stringify(resp), { + status: 200, + }); + } } diff --git a/src/app/api/lib/db/collections.ts b/src/app/api/lib/db/collections.ts new file mode 100644 index 00000000..56383f8a --- /dev/null +++ b/src/app/api/lib/db/collections.ts @@ -0,0 +1,8 @@ +import { TdbCollection } from "./types"; + +export const dbCollections: TdbCollection = { + users: { + name: "users", + label: "Users", + }, +}; diff --git a/src/app/api/lib/db/index.ts b/src/app/api/lib/db/index.ts index 9cb221ff..b95d75e1 100644 --- a/src/app/api/lib/db/index.ts +++ b/src/app/api/lib/db/index.ts @@ -1,4 +1,3 @@ -// This approach is taken from https://github.com/vercel/next.js/tree/canary/examples/with-mongodb import { MongoClient } from "mongodb"; if (!process.env.MONGODB_URI) { @@ -8,27 +7,31 @@ if (!process.env.MONGODB_URI) { const uri = process.env.MONGODB_URI; const options = {}; -let client; -let clientPromise: Promise; +let client: MongoClient; +let mongoClientPromise: Promise; if (process.env.NODE_ENV === "development") { - // In development mode, use a global variable so that the value - // is preserved across module reloads caused by HMR (Hot Module Replacement). - //@ts-ignore - if (!global._mongoClientPromise) { + if (!global._mongomongoClientPromise) { client = new MongoClient(uri, options); //@ts-ignore - global._mongoClientPromise = client.connect(); + global._mongomongoClientPromise = client.connect(); } //@ts-ignore - clientPromise = global._mongoClientPromise; + mongoClientPromise = global._mongomongoClientPromise; } else { - // In production mode, it's best to not use a global variable. client = new MongoClient(uri, options); - clientPromise = client.connect(); + mongoClientPromise = client.connect(); } -// Export a module-scoped MongoClient promise. By doing this in a -// separate module, the client can be shared across functions. -export default clientPromise; +export const dbClient = async () => { + try { + const mongoClient = new MongoClient(uri, options); + const dbConnection = await mongoClient.connect(); + return dbConnection.db(process.env.MONGODB_DB); + } catch (error) { + return null; + } +}; + +export default mongoClientPromise; diff --git a/src/app/api/lib/db/realm.ts b/src/app/api/lib/db/realm.ts new file mode 100644 index 00000000..c4478991 --- /dev/null +++ b/src/app/api/lib/db/realm.ts @@ -0,0 +1,5 @@ +import Realm from "realm"; + +export const realmApp = new Realm.App({ + id: process.env.REALM_APP_ID as string, +}); diff --git a/src/app/api/lib/db/types.ts b/src/app/api/lib/db/types.ts new file mode 100644 index 00000000..31602d3c --- /dev/null +++ b/src/app/api/lib/db/types.ts @@ -0,0 +1,6 @@ +export type TdbCollection = { + [key: string]: { + name: string; + label: string; + }; +}; diff --git a/src/app/custom.css b/src/app/custom.css index 2a171eb5..c2dc72f1 100644 --- a/src/app/custom.css +++ b/src/app/custom.css @@ -1,54 +1,46 @@ -.logo{ - margin-top:-20px !important; +.logo { + margin-top: -20px !important; } -.landing-page-container{ - background-color:#FAF8F4 !important; - background-image:url('/landing-page-feature-image.png') ; +.landing-page-container { + background-color: #faf8f4 !important; + background-image: url("/landing-page-feature-image.png"); background-position: right; background-repeat: no-repeat; } -.guest-layout{ - +.guest-layout { } -.nav-bar{ - background-image:url('https://media.istockphoto.com/id/1085039192/vector/abstract-background-of-engineering-drawing-technological-wallpaper-made-with-circles-and.webp?b=1&s=612x612&w=0&k=20&c=GopWlnZX4z5n_DG5Sdx2iIouMZ-eSNbTjb462paNlFM='); +.nav-bar { + background-image: url("https://media.istockphoto.com/id/1085039192/vector/abstract-background-of-engineering-drawing-technological-wallpaper-made-with-circles-and.webp?b=1&s=612x612&w=0&k=20&c=GopWlnZX4z5n_DG5Sdx2iIouMZ-eSNbTjb462paNlFM="); background-position: left; background-repeat: no-repeat; - background-size: cover; - - - + background-size: cover; } .nav-bar::before { - content: ''; + content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100 %; - background-color: rgba(0, 0, 0, 0.034); + background-color: rgba(0, 0, 0, 0.034); } - -.landing-page-plain-card{ +.landing-page-plain-card { } -.landing-page-main-card-container{ - margin-top:110px; - - +.landing-page-main-card-container { + margin-top: 110px; } - .project-card { margin: 1em; height: 50px; width: 300px; - background-color:#EC975F; + background-color: #ec975f; position: relative; overflow: hidden; } @@ -58,19 +50,22 @@ width: 300px; padding: 0.3rem 90px; box-sizing: border-box; - background-color: #7471F0; + background-color: #7471f0; transform: rotateZ(-45deg); top: 0; left: 0; transform-origin: 150px 150px; text-align: center; - color:white + color: white; } +.auth-card { + background-color: #f0f0f0; + margin: 30px; + min-height: 80%; + max-width: 80%; +} - -.auth-card{ - background-color:#F0F0F0; - margin:10px - -} \ No newline at end of file +.auth-container { + margin: 0 auto; +} diff --git a/src/components/auth/signup.tsx b/src/components/auth/signup.tsx index 7d330dd5..0afdf150 100644 --- a/src/components/auth/signup.tsx +++ b/src/components/auth/signup.tsx @@ -8,9 +8,7 @@ import { Button } from "flowbite-react"; import { SIGNUP_FORM_FIELDS } from "./constants"; import useAuth from "@hooks/useAuth"; -// const onFinish = (values: TsignupFields) => { -// console.log("Success:", values); -// }; + const onFinishFailed = (errorInfo: any) => { console.log("Failed:", errorInfo); @@ -20,7 +18,7 @@ const Signup: React.FC = () => { const { handleSignup } = useAuth(); return ( - + diff --git a/src/components/layouts/guestLayout/index.tsx b/src/components/layouts/guestLayout/index.tsx index 584c1290..aba83f72 100644 --- a/src/components/layouts/guestLayout/index.tsx +++ b/src/components/layouts/guestLayout/index.tsx @@ -24,7 +24,9 @@ const GuestLayout: FC<{ About Resources - {!isAuthenticated && Login | Sign up} + {!isAuthenticated && ( + Login | Sign up + )} {children} diff --git a/src/hooks/useAuth/index.ts b/src/hooks/useAuth/index.ts index af407959..19a7374d 100644 --- a/src/hooks/useAuth/index.ts +++ b/src/hooks/useAuth/index.ts @@ -2,6 +2,8 @@ import { message } from "antd"; import { API_LINKS } from "app/links"; import { useSession } from "next-auth/react"; import { TsignupFields } from "./types"; +import { translate } from "utils/intl"; +import { WORDS } from "utils/intl/data/constants"; const useAuth = () => { const { data: session, status } = useSession(); @@ -21,15 +23,25 @@ const useAuth = () => { try { const resp = await fetch(url, formData); + if (!resp.ok) { - message.warning(`Sorry something went wrong `); + message.warning(translate(WORDS.unknown_error)); return false; } + const responseData = await resp.json(); + + if (responseData.isError) { + message.warning(responseData.msg); + return false; + } + message.success(responseData.msg); + + console.log(responseData); } catch (err: any) { - message.error(`Sorry an error occured. ${err.msg ? err.msg : ""}`); + message.error(`${translate(WORDS.unknown_error)}. ${err.msg ? err.msg : ""}`); return false; } }; diff --git a/src/utils/intl/data/constants.ts b/src/utils/intl/data/constants.ts index d0f10724..fce3e8ab 100644 --- a/src/utils/intl/data/constants.ts +++ b/src/utils/intl/data/constants.ts @@ -7,4 +7,7 @@ export const WORDS = { form: "form", email_error: "email_error", password_error: "password_error", + unknown_error: "unknown_error", + user_created: "user_created", + user_exist: "user_exist", }; diff --git a/src/utils/intl/data/eng/index.ts b/src/utils/intl/data/eng/index.ts index edbc832c..2ec3e2bd 100644 --- a/src/utils/intl/data/eng/index.ts +++ b/src/utils/intl/data/eng/index.ts @@ -27,10 +27,22 @@ module.exports = { }, [WORDS.email_error]: { word: "Please input your email", - word2: "Please input your email", + word2: "Email address is already taken", }, [WORDS.password_error]: { word: "Please input your password!", word2: "Please input your password!", }, + [WORDS.unknown_error]: { + word: "Sorry something went wrong", + word2: "Sorry something went wrong", + }, + [WORDS.user_created]: { + word: "Account successfully created", + word2: "Account successfully created", + }, + [WORDS.user_exist]: { + word: "Sorry account already exits", + word2: "Sorry account already exits. Please sign in", + }, }; diff --git a/src/utils/intl/index.ts b/src/utils/intl/index.ts index 6c1d80ac..8bf31374 100644 --- a/src/utils/intl/index.ts +++ b/src/utils/intl/index.ts @@ -1,6 +1,6 @@ import { DEFAULT_LANGAUGE } from "./constants"; -export const translate = (word: string, word2?: string) => { +export const translate = (word: string, word2?: boolean ) => { let appLang = DEFAULT_LANGAUGE; const data = require("./data"); const text = word2 ? "word2" : "word"; diff --git a/yarn.lock b/yarn.lock index 6e16a7b4..daa93dc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -406,9 +406,9 @@ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== "@types/node@*": - version "20.6.0" - resolved "https://registry.npmmirror.com/@types/node/-/node-20.6.0.tgz#9d7daa855d33d4efec8aea88cd66db1c2f0ebe16" - integrity sha512-najjVq5KN2vsH2U/xyh2opaSEz6cZMR2SetLIlxlj08nOcmPOemJmUK2o4kUzfLqfrWE0PIrNeE16XhYDd3nqg== + version "20.6.3" + resolved "https://registry.npmmirror.com/@types/node/-/node-20.6.3.tgz#5b763b321cd3b80f6b8dde7a37e1a77ff9358dd9" + integrity sha512-HksnYH4Ljr4VQgEy2lTStbCKv/P590tmPe5HqOnv9Gprffgv5WXAY+Y5Gqniu0GGqeTCUdBnzC3QSrzPkBkAMA== "@types/node@20.5.0": version "20.5.0" @@ -830,10 +830,10 @@ bson@^4.7.2: dependencies: buffer "^5.6.0" -bson@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/bson/-/bson-6.0.0.tgz#685f631a2ce255e2ed3987a7355210e4beace17e" - integrity sha512-FoWvdELfF2wQaUo8S/a1Rh2BDwJEUancDDnzdTpYymJTZjmvRpLWoqRPelKn+XSeh5D4YddWDG66cLtEhGGvcg== +bson@^6.1.0: + version "6.1.0" + resolved "https://registry.npmmirror.com/bson/-/bson-6.1.0.tgz#ea7c98b90540e1632173da6b1f70187827e6ae8c" + integrity sha512-yiQ3KxvpVoRpx1oD1uPz4Jit9tAVTJgjdmjDKtUErkOoL9VNoF8Dd58qtAOL5E40exx2jvAT9sqdRSK/r+SHlA== buffer@^5.5.0, buffer@^5.6.0: version "5.7.1" @@ -2181,13 +2181,13 @@ mongodb-connection-string-url@^2.6.0: "@types/whatwg-url" "^8.2.1" whatwg-url "^11.0.0" -mongodb@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/mongodb/-/mongodb-6.0.0.tgz#a01f989d0c1ceb7b08068551c545e6131557a1e8" - integrity sha512-wUIYesF4DTyDccm0noE5TwGi9ISdXUAi9T2cQ4xPc+EUBZG44bfMVt2ecOG5Ypca7eCz3oRpJm6YI6c7jAnuNw== +mongodb@^6.1.0: + version "6.1.0" + resolved "https://registry.npmmirror.com/mongodb/-/mongodb-6.1.0.tgz#5144bee74d50746f7b0ed68dbb974f31e1b40900" + integrity sha512-AvzNY0zMkpothZ5mJAaIo2bGDjlJQqqAbn9fvtVgwIIUPEfdrqGxqNjjbuKyrgQxg2EvCmfWdjq+4uj96c0YPw== dependencies: "@mongodb-js/saslprep" "^1.1.0" - bson "^6.0.0" + bson "^6.1.0" mongodb-connection-string-url "^2.6.0" ms@2.1.2: @@ -3729,6 +3729,11 @@ yocto-queue@^0.1.0: resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +zod-form-data@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/zod-form-data/-/zod-form-data-2.0.1.tgz#b069b76ad8a47e2ee475f691db87db8afa9dd3f2" + integrity sha512-4jcsj3vFyFGINLLHEmehfOPKdcw+HqV65RwsV2IdyLHp9wpvGJRVXWg1yY8sq0ASEbQfTVBRtI7LcDGv3Qpj8g== + zod@3.21.4: version "3.21.4" resolved "https://registry.npmmirror.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"