├── .gitignore
├── LICENSE
├── README.md
├── jsvectormap.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── prettier.config.js
├── protein bind.drawio
├── protein bind.drawio.zip
├── public
├── drug_research.jpg
└── images
│ ├── favicon.ico
│ ├── logo
│ ├── dna.svg
│ └── logo.svg
│ └── user
│ └── user-01.png
├── src
├── app
│ ├── api
│ │ ├── auth
│ │ │ └── [...nextauth]
│ │ │ │ └── route.ts
│ │ ├── send-reset-password-email
│ │ │ └── route.ts
│ │ └── send-verification-email
│ │ │ └── route.ts
│ ├── auth-page
│ │ ├── signin
│ │ │ └── page.tsx
│ │ └── signup
│ │ │ └── page.tsx
│ ├── context
│ │ └── UserContext.tsx
│ ├── favicon.ico
│ ├── forget-password
│ │ └── page.tsx
│ ├── layout.tsx
│ ├── message
│ │ ├── chat-box.jsx
│ │ ├── chat.jsx
│ │ └── page.jsx
│ ├── model
│ │ └── page.tsx
│ ├── molecule-bank
│ │ └── page.tsx
│ ├── page.tsx
│ ├── profile
│ │ └── page.tsx
│ ├── research
│ │ └── page.tsx
│ ├── reset-password
│ │ └── page.tsx
│ ├── settings
│ │ └── page.tsx
│ ├── ui
│ │ └── modals
│ │ │ └── page.tsx
│ └── verify-email
│ │ └── page.tsx
├── components
│ ├── ClickOutside.tsx
│ ├── ComponentHeader
│ │ └── ComponentHeader.tsx
│ ├── EmailTemplates
│ │ ├── reset-email.tsx
│ │ └── verify-email.tsx
│ ├── Header
│ │ ├── DarkModeSwitcher.tsx
│ │ ├── DropdownMessage.tsx
│ │ ├── DropdownUser.tsx
│ │ └── index.tsx
│ ├── Layouts
│ │ └── DefaultLayout.tsx
│ ├── MoleculeBank
│ │ └── MoleculeBankTable.tsx
│ ├── MoleculeStructure
│ │ └── index.jsx
│ ├── Sidebar
│ │ ├── SidebarDropdown.tsx
│ │ ├── SidebarItem.tsx
│ │ └── index.tsx
│ ├── common
│ │ └── Loader
│ │ │ └── index.tsx
│ └── dashboard
│ │ ├── components
│ │ ├── CTACard.tsx
│ │ ├── DashboardCardChat.tsx
│ │ └── DashboardCardMap.tsx
│ │ └── index.tsx
├── css
│ └── style.css
├── hooks
│ ├── useColorMode.tsx
│ └── useLocalStorage.tsx
├── js
│ ├── us-aea-en.js
│ └── world.js
├── lib
│ ├── actions
│ │ ├── email.actions.ts
│ │ ├── group.actions.ts
│ │ ├── molecule-generation.action.ts
│ │ └── user.actions.ts
│ ├── database
│ │ ├── models
│ │ │ ├── group.model.ts
│ │ │ ├── molecule-generation.model.ts
│ │ │ └── user.model.ts
│ │ └── mongoose.ts
│ └── utils.ts
└── types
│ └── index.d.ts
├── tailadmin-nextjs.jpg
├── tailwind.config.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 | /.pnp
4 | .pnp.js
5 | .yarn/install-state.gz
6 | descriptions.txt
7 | # testing
8 | /coverage
9 | oldpage
10 | # next.js
11 | /.next/
12 | /out/
13 | .env
14 | /.env
15 | # production
16 | /build
17 | outline.md
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 |
30 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 TailAdmin
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
ProteinBind
17 |
18 |
19 | Build this project step by step with our detailed tutorial on
Your YouTube Channel. Join the community!
20 |
21 |
22 |
23 | ## 📋 Table of Contents
24 |
25 | 1. 🤖 [Introduction](#introduction)
26 | 2. ⚙️ [Tech Stack](#tech-stack)
27 | 3. 🔋 [Features](#features)
28 | 4. 🤸 [Quick Start](#quick-start)
29 | 5. 🧬 [Protein Data Processing](#protein-data)
30 | 6. 🚀 [More](#more)
31 |
32 | ## 🚨 Tutorial
33 |
34 | This repository contains the code corresponding to an in-depth tutorial available on our YouTube channel, Code with Albert.
35 |
36 | If you prefer visual learning, this is the perfect resource for you. Follow our tutorial to learn how to build projects like these step-by-step in a beginner-friendly manner!
37 |
38 | ## 🤖 Introduction
39 |
40 | **ProteinBind** is a drug discovery and protein-binding prediction tool built with the latest in machine learning and natural language processing (NLP) technology. Powered by NVIDIA NIM and protein structure prediction models, this project enables users to simulate molecular interactions and predict protein structures.
41 |
42 | The platform is designed to help researchers accelerate drug discovery by leveraging cutting-edge AI models for protein folding, docking, and molecular dynamics.
43 |
44 | If you're getting started and need assistance or face any bugs, join our active Discord community. It's a place where people help each other out.
45 |
46 |
47 |
48 | ## ⚙️ Tech Stack
49 |
50 | - **Next.js**
51 | - **TypeScript**
52 | - **NVIDIA** (for protein structure prediction)
53 | - **Tailwind CSS**
54 | - **React Chart.js** (for visualizing protein data)
55 |
56 | ## 🔋 Features
57 |
58 | 👉 **Protein Structure Prediction**: Predicts 2D protein structures using NVIDIA models.
59 |
60 | 👉 **Collaborative Research**: Researches can create groups and colloborate with other research online
61 |
62 | 👉 **Responsive Design**: Ensures seamless experience across all devices, from desktops to mobile.
63 |
64 | ## 🤸 Quick Start
65 |
66 | Follow these steps to set up the project locally on your machine.
67 |
68 | ### **Prerequisites**
69 |
70 | Make sure you have the following installed on your machine:
71 |
72 | - [Git](https://git-scm.com/)
73 | - [Node.js](https://nodejs.org/en)
74 | - [npm](https://www.npmjs.com/) (Node Package Manager)
75 |
76 | ### **Cloning the Repository**
77 |
78 | ```bash
79 | git clone https://github.com/mendsalbert/ProteinBind.git
80 | cd proteinbind
81 | ```
82 |
83 | ### **Installation**
84 |
85 | Install the project dependencies using npm:
86 |
87 | ```bash
88 | npm install
89 | ```
90 |
91 | ### **Set Up Environment Variables**
92 |
93 | Create a new file named `.env` in the root of your project and add the following content:
94 |
95 | ```env
96 | NEXT_PUBLIC_NVIDIA_API_KEY=your-nvidia-api-key
97 |
98 | ABLY_API_KEY='your-ably-api-key'
99 |
100 | MONGODB_URL='your-mongodb-url'
101 |
102 | NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
103 |
104 | RESEND_KEY='your-resend-api-key'
105 | ```
106 |
107 | ### **Running the Project**
108 |
109 | ```bash
110 | npm run dev
111 | ```
112 |
113 | Open [http://localhost:3000](http://localhost:3000) in your browser to view the project.
114 |
115 | ## 🧬 Protein Data Processing
116 |
117 | This section covers the protein data processing pipeline, including loading protein structure files (e.g., PDB format), performing molecular docking simulations, and visualizing the results.
118 |
119 | ### **Protein Structure Input**
120 |
121 | Users can upload PDB files for protein structures, which will then be processed by NVIDIA NeMo's protein-folding models.
122 |
123 | ### **Docking Simulation**
124 |
125 | Using molecular docking algorithms, the system predicts how small molecules (such as drug candidates) bind to protein targets.
126 |
127 | ## 🚀 More
128 |
129 | Stay tuned for more updates and features! Join our community, contribute to the repository, and follow along with our detailed tutorials.
130 |
131 | ## 📞 **Contact & Community**
132 |
133 | If you have any questions or need support, feel free to join our Discord server or contact us through GitHub Discussions.
134 |
135 | Join Discord | GitHub Discussions
136 |
--------------------------------------------------------------------------------
/jsvectormap.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'jsvectormap' {
2 | const jsVectorMap: any;
3 | export default jsVectorMap;
4 | }
5 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const CopyPlugin = require("copy-webpack-plugin");
2 |
3 | /** @type {import('next').NextConfig} */
4 | const nextConfig = {
5 | webpack(config, { isServer }) {
6 | config.plugins.push(
7 | new CopyPlugin({
8 | patterns: [
9 | {
10 | from: "node_modules/@rdkit/rdkit/dist/RDKit_minimal.wasm",
11 | to: "static/chunks",
12 | },
13 | ],
14 | }),
15 | );
16 |
17 | if (!isServer) {
18 | config.resolve.fallback = {
19 | fs: false,
20 | };
21 | }
22 |
23 | return config;
24 | },
25 | };
26 |
27 | module.exports = nextConfig;
28 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "free-nextjs-admin-dashboard",
3 | "version": "1.3.4",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@next-auth/prisma-adapter": "^1.0.7",
13 | "@rdkit/rdkit": "^2023.9.2-1.0.0",
14 | "@xmtp/content-type-reaction": "^1.1.9",
15 | "@xmtp/content-type-remote-attachment": "^1.1.9",
16 | "@xmtp/content-type-reply": "^1.1.11",
17 | "@xmtp/message-kit": "^0.0.9",
18 | "@xmtp/react-sdk": "^8.0.1",
19 | "ably": "^2.3.1",
20 | "apexcharts": "^3.45.2",
21 | "bcrypt": "^5.1.1",
22 | "copy-webpack-plugin": "^12.0.2",
23 | "dotenv": "^16.4.5",
24 | "ethers": "^5.7.0",
25 | "flatpickr": "^4.6.13",
26 | "fs": "^0.0.1-security",
27 | "hex-rgb": "^5.0.0",
28 | "install": "^0.13.0",
29 | "jsvectormap": "^1.6.0",
30 | "lodash": "^4.17.21",
31 | "lucide-react": "^0.427.0",
32 | "mongodb": "^6.3.0",
33 | "mongoose": "^8.1.1",
34 | "next": "^14.2.4",
35 | "next-auth": "^4.24.7",
36 | "prop-types": "^15.8.1",
37 | "react": "^18.3.1",
38 | "react-apexcharts": "^1.4.1",
39 | "react-dom": "^18",
40 | "resend": "^3.5.0"
41 | },
42 | "devDependencies": {
43 | "@types/bcrypt": "^5.0.2",
44 | "@types/node": "^20",
45 | "@types/react": "^18",
46 | "@types/react-dom": "^18",
47 | "autoprefixer": "^10.0.1",
48 | "daisyui": "^4.12.10",
49 | "eslint": "^8",
50 | "eslint-config-next": "14.1.0",
51 | "node-loader": "^2.0.0",
52 | "postcss": "^8",
53 | "prettier": "^3.2.5",
54 | "prettier-plugin-tailwindcss": "^0.5.11",
55 | "prisma": "^5.18.0",
56 | "tailwindcss": "^3.4.1",
57 | "typescript": "^5"
58 | },
59 | "browser": {
60 | "fs": false,
61 | "path": false,
62 | "os": false
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['prettier-plugin-tailwindcss'],
3 | }
--------------------------------------------------------------------------------
/protein bind.drawio.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mendsalbert/ProteinBind/654560d8d17542170ae40170254650993859736e/protein bind.drawio.zip
--------------------------------------------------------------------------------
/public/drug_research.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mendsalbert/ProteinBind/654560d8d17542170ae40170254650993859736e/public/drug_research.jpg
--------------------------------------------------------------------------------
/public/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mendsalbert/ProteinBind/654560d8d17542170ae40170254650993859736e/public/images/favicon.ico
--------------------------------------------------------------------------------
/public/images/logo/dna.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/logo/logo.svg:
--------------------------------------------------------------------------------
1 |
54 |
--------------------------------------------------------------------------------
/public/images/user/user-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mendsalbert/ProteinBind/654560d8d17542170ae40170254650993859736e/public/images/user/user-01.png
--------------------------------------------------------------------------------
/src/app/api/auth/[...nextauth]/route.ts:
--------------------------------------------------------------------------------
1 | import NextAuth from "next-auth";
2 | import CredentialsProvider from "next-auth/providers/credentials";
3 | import { loginUser } from "@/lib/actions/user.actions";
4 |
5 | export const handler = NextAuth({
6 | providers: [
7 | CredentialsProvider({
8 | name: "Credentials",
9 | credentials: {
10 | email: { label: "Email", type: "email" },
11 | password: { label: "Password", type: "password" },
12 | } as any,
13 | async authorize(credentials) {
14 | if (credentials?.email && credentials?.password) {
15 | const user = await loginUser(credentials.email, credentials.password);
16 | if (user) {
17 | return user;
18 | } else {
19 | return null;
20 | }
21 | }
22 |
23 | return null;
24 | },
25 | }),
26 | ],
27 | session: {
28 | strategy: "jwt",
29 | maxAge: 24 * 60 * 60,
30 | },
31 | callbacks: {
32 | async jwt({ token, user }) {
33 | if (user) {
34 | token.id = user.id;
35 | }
36 | return token;
37 | },
38 | async session({ session, token }: any) {
39 | if (token) {
40 | session.id = token.id;
41 | }
42 | return session;
43 | },
44 | },
45 | secret: process.env.NEXTAUTH_SECRET,
46 | });
47 |
48 | export { handler as GET, handler as POST };
49 |
--------------------------------------------------------------------------------
/src/app/api/send-reset-password-email/route.ts:
--------------------------------------------------------------------------------
1 | import { ResetPasswordTemplate } from "@/components/EmailTemplates/reset-email";
2 | import { Resend } from "resend";
3 | const resend = new Resend(process.env.RESEND_KEY);
4 |
5 | export async function POST(request: Request) {
6 | const { firstName, email, resetUrl } = await request.json();
7 |
8 | try {
9 | const { data, error } = await resend.emails.send({
10 | from: "ProteinBind ",
11 | to: [email],
12 | subject: "Reset your password",
13 | react: ResetPasswordTemplate({ firstName, resetUrl }),
14 | });
15 |
16 | if (error) {
17 | return new Response(JSON.stringify({ error }), { status: 500 });
18 | }
19 |
20 | return new Response(JSON.stringify(data), { status: 200 });
21 | } catch (error: any) {
22 | return new Response(JSON.stringify({ error: error.message }), {
23 | status: 500,
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/api/send-verification-email/route.ts:
--------------------------------------------------------------------------------
1 | // app/api/send-verification-email/route.ts
2 |
3 | import { VerifyEmailTemplate } from "@/components/EmailTemplates/verify-email";
4 | import { Resend } from "resend";
5 | const resend = new Resend(process.env.RESEND_KEY);
6 |
7 | export async function POST(request: Request) {
8 | const { firstName, email, verificationUrl } = await request.json();
9 |
10 | try {
11 | const { data, error } = await resend.emails.send({
12 | from: "ProteinBind ",
13 | to: [email],
14 | subject: "Verify your email",
15 | react: VerifyEmailTemplate({ firstName, verificationUrl }),
16 | });
17 |
18 | if (error) {
19 | console.log(error);
20 | return new Response(JSON.stringify({ error }), { status: 500 });
21 | }
22 |
23 | return new Response(JSON.stringify(data), { status: 200 });
24 | } catch (error: any) {
25 | console.log(error);
26 |
27 | return new Response(JSON.stringify({ error: error.message }), {
28 | status: 500,
29 | });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/auth-page/signin/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import Link from "next/link";
4 | import { LoaderCircle, LockIcon, MailIcon } from "lucide-react";
5 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
6 | import ComponentHeader from "@/components/ComponentHeader/ComponentHeader";
7 | import { useRouter } from "next/navigation";
8 | import { signIn } from "next-auth/react";
9 |
10 | const SignIn: React.FC = () => {
11 | const [email, setEmail] = useState("");
12 | const [password, setPassword] = useState("");
13 | const [error, setError] = useState(null);
14 | const [isLoading, setIsLoading] = useState(false);
15 | const router = useRouter();
16 |
17 | const handleSubmit = async (e: React.FormEvent) => {
18 | e.preventDefault();
19 | setError(null);
20 | setIsLoading(true);
21 |
22 | // Basic form validation
23 | if (!email || !password) {
24 | setError("Please fill in all fields.");
25 | setIsLoading(false);
26 | return;
27 | }
28 |
29 | try {
30 | // Use NextAuth's signIn function
31 | const result = await signIn("credentials", {
32 | redirect: false, // Prevent auto-redirect
33 | email,
34 | password,
35 | });
36 |
37 | if (result?.error) {
38 | setError(result.error); // Handle login error
39 | } else {
40 | // Navigate to the dashboard or another page on successful login
41 | router.push("/");
42 | }
43 | } catch (err: any) {
44 | // Handle unexpected error
45 | setError("Something went wrong. Please try again.");
46 | console.error(err);
47 | } finally {
48 | setIsLoading(false);
49 | }
50 | };
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
Start for free
61 |
62 | Sign In to ProteinBind
63 |
64 |
65 | {error &&
{error}
}
66 |
67 |
140 |
141 |
142 |
143 |
144 |
145 | );
146 | };
147 |
148 | export default SignIn;
149 |
--------------------------------------------------------------------------------
/src/app/auth-page/signup/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useCallback } from "react";
3 | import Link from "next/link";
4 | import Image from "next/image";
5 | import ComponentHeader from "@/components/ComponentHeader/ComponentHeader";
6 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
7 | import { createUser } from "@/lib/actions/user.actions";
8 | import {
9 | CameraIcon,
10 | LoaderCircle,
11 | LockIcon,
12 | MailIcon,
13 | UserIcon,
14 | } from "lucide-react";
15 |
16 | const SignUp: React.FC = () => {
17 | const [user, setUser] = useState({
18 | email: "",
19 | firstName: "",
20 | lastName: "",
21 | password: "",
22 | confirmPassword: "",
23 | photo: "",
24 | userBio: "",
25 | });
26 |
27 | const [imageFile, setImageFile] = useState(null);
28 | const [errors, setErrors] = useState(null);
29 | const [isLoading, setIsLoading] = useState(false);
30 |
31 | const handleInputChange = (
32 | e: React.ChangeEvent,
33 | ) => {
34 | const { name, value } = e.target;
35 | setUser((prev) => ({
36 | ...prev,
37 | [name]: value, // Ensure the value is treated as a string
38 | }));
39 | };
40 |
41 | const handleFileChange = (e: React.ChangeEvent) => {
42 | const file = e.target.files ? e.target.files[0] : null;
43 | setImageFile(file);
44 | };
45 |
46 | const convertImageToBase64 = async (file: File): Promise => {
47 | return new Promise((resolve, reject) => {
48 | const reader = new FileReader();
49 | reader.readAsDataURL(file);
50 | reader.onloadend = () => {
51 | resolve(reader.result as string);
52 | };
53 | reader.onerror = (error) => {
54 | reject(error);
55 | };
56 | });
57 | };
58 |
59 | // Validate form fields
60 | const validateForm = useCallback(() => {
61 | if (
62 | !user.email ||
63 | !user.firstName ||
64 | !user.lastName ||
65 | !user.password ||
66 | !user.confirmPassword
67 | ) {
68 | return "Please fill in all the fields.";
69 | }
70 | if (user.password !== user.confirmPassword) {
71 | return "Passwords do not match.";
72 | }
73 | if (!imageFile) {
74 | return "Please upload a profile picture.";
75 | }
76 | return null;
77 | }, [user, imageFile]);
78 |
79 | let isSubmitting = false;
80 | const handleSubmit = async (e: React.FormEvent) => {
81 | e.preventDefault();
82 |
83 | if (isSubmitting) return;
84 | isSubmitting = true;
85 |
86 | setIsLoading(true);
87 |
88 | const formError = validateForm();
89 | if (formError) {
90 | setErrors(formError);
91 | setIsLoading(false);
92 | isSubmitting = false;
93 | return;
94 | }
95 |
96 | setErrors(null);
97 |
98 | try {
99 | let base64Image = "";
100 | if (imageFile) {
101 | base64Image = await convertImageToBase64(imageFile);
102 | }
103 |
104 | const newUser = {
105 | ...user,
106 | photo: base64Image,
107 | };
108 |
109 | console.log(newUser);
110 | const createdUser = await createUser(newUser);
111 | console.log(createdUser);
112 |
113 | setUser({
114 | email: "",
115 | firstName: "",
116 | lastName: "",
117 | password: "",
118 | confirmPassword: "",
119 | photo: "",
120 | userBio: "",
121 | });
122 | setImageFile(null);
123 | setIsLoading(false);
124 | } catch (error) {
125 | console.error("Error registering user:", error);
126 | setErrors("Registration failed.");
127 | } finally {
128 | isSubmitting = false;
129 | setIsLoading(false);
130 | }
131 | };
132 |
133 | return (
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
151 |
152 |
153 | ProteinBind
154 |
155 |
156 |
157 |
158 |
159 |
279 |
280 |
281 |
282 |
283 |
466 |
467 |
468 |
469 | );
470 | };
471 |
472 | export default SignUp;
473 |
--------------------------------------------------------------------------------
/src/app/context/UserContext.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | createContext,
3 | useContext,
4 | useState,
5 | useEffect,
6 | ReactNode,
7 | } from "react";
8 | import { useSession } from "next-auth/react";
9 | import { getUserByEmail } from "@/lib/actions/user.actions";
10 |
11 | const UserContext = createContext(null);
12 | export const UserProvider = ({ children }: { children: ReactNode }) => {
13 | const { data: session } = useSession();
14 | const [user, setUser] = useState({
15 | firstName: "John",
16 | lastName: "Doe",
17 | photo: "/images/user/user-01.png",
18 | jobTitle: "Drug Researcher",
19 | userBio: "",
20 | });
21 |
22 | useEffect(() => {
23 | const fetchUser = async () => {
24 | if (session?.user?.email) {
25 | const fetchedUser = await getUserByEmail(session.user.email);
26 | setUser({
27 | firstName: fetchedUser?.firstName || "John",
28 | lastName: fetchedUser?.lastName || "Doe",
29 | photo: fetchedUser?.photo || "/images/user/user-01.png",
30 | jobTitle: fetchedUser?.jobTitle || "Researcher",
31 | userBio: fetchedUser?.userBio || "",
32 | });
33 | }
34 | };
35 | fetchUser();
36 | }, [session?.user?.email]);
37 |
38 | return {children};
39 | };
40 |
41 | export const useUser = () => useContext(UserContext);
42 |
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mendsalbert/ProteinBind/654560d8d17542170ae40170254650993859736e/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/forget-password/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useEffect } from "react";
3 | import {
4 | getUserByEmail,
5 | requestPasswordReset,
6 | } from "@/lib/actions/user.actions";
7 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
8 | import { useRouter } from "next/navigation";
9 | import { MailIcon } from "lucide-react";
10 | import { useSession } from "next-auth/react";
11 |
12 | const ForgetPasswordPage: React.FC = () => {
13 | const [email, setEmail] = useState("");
14 | const [status, setStatus] = useState<
15 | "idle" | "loading" | "success" | "error"
16 | >("idle");
17 | const [errorMessage, setErrorMessage] = useState(null);
18 | const router = useRouter();
19 | const { data: session } = useSession();
20 | const [user, setUser] = useState(null);
21 |
22 | useEffect(() => {
23 | const fetchUser = async () => {
24 | if (session?.user?.email) {
25 | try {
26 | const fetchedUser = await getUserByEmail(session.user.email);
27 | setUser(fetchedUser);
28 | } catch (error) {
29 | console.error("Failed to fetch user:", error);
30 | }
31 | }
32 | };
33 |
34 | fetchUser();
35 | }, [session?.user?.email]);
36 | console.log(user);
37 |
38 | const handleSubmit = async (e: React.FormEvent) => {
39 | e.preventDefault();
40 | setStatus("loading");
41 | try {
42 | await requestPasswordReset(email);
43 | setStatus("success");
44 | } catch (error: any) {
45 | setStatus("error");
46 | setErrorMessage(error.message || "Something went wrong.");
47 | }
48 | };
49 |
50 | return (
51 |
52 |
53 | {status === "idle" && (
54 | <>
55 |
56 | Forget Password
57 |
58 |
59 | Enter your email to receive a password reset link.
60 |
61 |
93 | >
94 | )}
95 |
96 | {status === "loading" &&
Sending reset link, please wait...
}
97 |
98 | {status === "success" && (
99 | <>
100 |
Check your email
101 |
102 | We have sent a password reset link to {email}. Please check your
103 | inbox.
104 |
105 |
111 | >
112 | )}
113 |
114 | {status === "error" && (
115 |
There was an error sending the reset link. Please try again.
116 | )}
117 |
118 |
119 | );
120 | };
121 |
122 | export default ForgetPasswordPage;
123 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import "jsvectormap/dist/jsvectormap.css";
3 | import "flatpickr/dist/flatpickr.min.css";
4 | import "@/css/style.css";
5 | import React, { useEffect, useState } from "react";
6 | import Loader from "@/components/common/Loader";
7 |
8 | import * as Ably from "ably";
9 | import { AblyProvider, ChannelProvider } from "ably/react";
10 | import { SessionProvider } from "next-auth/react";
11 | import { UserProvider } from "@/app/context/UserContext";
12 |
13 | export default function RootLayout({
14 | children,
15 | }: Readonly<{
16 | children: React.ReactNode;
17 | }>) {
18 | const [sidebarOpen, setSidebarOpen] = useState(false);
19 | const [loading, setLoading] = useState(true);
20 |
21 | useEffect(() => {
22 | setTimeout(() => setLoading(false), 1000);
23 | }, []);
24 |
25 | const client = new Ably.Realtime({
26 | key: "xiEQTw.SBJKWA:Kv7RDv6PngxN8y8ttHsOWHDQqchaEYtU9rgKefhsl7o",
27 | });
28 | return (
29 |
30 | {/* */}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {loading ? : children}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/message/chat-box.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from "react";
2 | import { useChannel } from "ably/react";
3 | import { useAbly } from "ably/react";
4 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
5 | import { SendIcon } from "lucide-react";
6 | import { useUser } from "../context/UserContext";
7 | import { resizeBase64Img } from "@/lib/utils";
8 | import {
9 | createGroup,
10 | getAllGroups,
11 | addMessageToGroup,
12 | getGroupMessages,
13 | } from "@/lib/actions/group.actions";
14 | import { getUserByEmail } from "@/lib/actions/user.actions";
15 | import { useSession } from "next-auth/react";
16 |
17 | function ChatBox() {
18 | const ably = useAbly();
19 | const [groups, setGroups] = useState([]);
20 | const [currentGroup, setCurrentGroup] = useState(null);
21 | const [messageText, setMessageText] = useState("");
22 | const [receivedMessages, setMessages] = useState([]);
23 | const [user_, setUser_] = useState("");
24 | const channelName = "chat-demo1";
25 | const { data: session } = useSession();
26 | const user = useUser();
27 | const { channel } = useChannel(channelName, (message) => {
28 | if (message.data.group === currentGroup._id) {
29 | setMessages((prevMessages) => [...prevMessages, message.data]);
30 | }
31 | });
32 |
33 | const bottomRef = useRef(null);
34 |
35 | useEffect(() => {
36 | if (bottomRef.current) {
37 | bottomRef.current.scrollIntoView({ behavior: "smooth" });
38 | }
39 | }, [receivedMessages]);
40 |
41 | useEffect(() => {
42 | const fetchGroups = async () => {
43 | const groupsFromServer = await getAllGroups();
44 | const user_ = await getUserByEmail(session?.user?.email);
45 | setUser_(user_);
46 | setGroups(groupsFromServer);
47 | };
48 | fetchGroups();
49 | }, [session?.user?.email]);
50 |
51 | const handleCreateGroup = async (groupName) => {
52 | if (groupName && !groups.some((group) => group.name === groupName)) {
53 | const newGroup = await createGroup(groupName, user_._id);
54 | setGroups([...groups, newGroup]);
55 | setCurrentGroup(newGroup);
56 | setMessages([]);
57 | }
58 | };
59 |
60 | const handleJoinGroup = async (groupId) => {
61 | const selectedGroup = groups.find((group) => group._id === groupId);
62 | setCurrentGroup(selectedGroup);
63 |
64 | const groupMessages = await getGroupMessages(groupId);
65 | const formattedMessages = groupMessages.map((msg) => ({
66 | ...msg,
67 | connectionId: msg.sender._id,
68 | name: `${msg.sender.firstName} ${msg.sender.lastName}`,
69 | image: msg.sender.photo || "/default-avatar.png",
70 | data: msg.text,
71 | timestamp: msg.timestamp,
72 | }));
73 |
74 | setMessages(formattedMessages);
75 | };
76 |
77 | const sendChatMessage = async (messageText) => {
78 | try {
79 | if (currentGroup && channel) {
80 | const resizedImage = await resizeBase64Img(user.photo, 100, 100);
81 |
82 | const message = {
83 | group: currentGroup._id,
84 | name: `${user.firstName} ${user.lastName}`,
85 | image: resizedImage,
86 | data: messageText,
87 | timestamp: new Date().toISOString(),
88 | connectionId: ably.connection.id,
89 | };
90 |
91 | await channel.publish("chat-message", message);
92 | await addMessageToGroup(currentGroup._id, user_._id, messageText);
93 |
94 | setMessageText("");
95 | }
96 | } catch (error) {
97 | console.log(error);
98 | }
99 | };
100 |
101 | const handleFormSubmission = (event) => {
102 | event.preventDefault();
103 | sendChatMessage(messageText);
104 | };
105 |
106 | const renderedMessages = receivedMessages.map((message, index) => {
107 | const isMe = message.connectionId === ably.connection.id;
108 | return (
109 |
113 |
120 |
121 |

126 |
{message.name}
127 |
128 |
{message.data}
129 |
130 | {new Date(message.timestamp).toLocaleTimeString()}
131 |
132 |
133 |
134 | );
135 | });
136 |
137 | return (
138 |
139 |
140 |
141 | Drug Discovery Chat
142 |
143 |
144 |
145 |
{
150 | if (e.key === "Enter") handleCreateGroup(e.target.value);
151 | }}
152 | />
153 |
154 |
165 |
166 |
167 |
168 | {currentGroup && (
169 |
170 |
171 | Current Group: {currentGroup.name}
172 |
173 |
174 | {renderedMessages.length > 0 ? (
175 | <>
176 | {renderedMessages}
177 |
178 | >
179 | ) : (
180 |
181 | No messages yet. Start chatting!
182 |
183 | )}
184 |
185 |
202 |
203 | )}
204 |
205 |
206 | );
207 | }
208 |
209 | export default ChatBox;
210 |
--------------------------------------------------------------------------------
/src/app/message/chat.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as Ably from "ably";
4 | import ChatBox from "./chat-box.jsx";
5 |
6 | export default function Chat() {
7 | const client = new Ably.Realtime({
8 | key: "VpPesA.e058xw:HltRuWqP4MBNSxON5IERYcR4ODsj96dUmpcwC84keGk",
9 | });
10 | return ;
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/message/page.jsx:
--------------------------------------------------------------------------------
1 | import dynamic from "next/dynamic";
2 |
3 | const Chat = dynamic(() => import("./chat"), {
4 | ssr: false,
5 | });
6 |
7 | function page() {
8 | return (
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | export default page;
16 |
--------------------------------------------------------------------------------
/src/app/model/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import Breadcrumb from "@/components/ComponentHeader/ComponentHeader";
3 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
4 | import MoleculeStructure from "../../components/MoleculeStructure/index";
5 | import React, { useState, useEffect } from "react";
6 | import { useSession } from "next-auth/react";
7 | import {
8 | createMoleculeGenerationHistory,
9 | getMoleculeGenerationHistoryByUser,
10 | } from "@/lib/actions/molecule-generation.action";
11 | import { getUserByEmail } from "@/lib/actions/user.actions";
12 |
13 | const ModalLayout = () => {
14 | const { data: session } = useSession();
15 | const [smiles, setSmiles] = useState(
16 | "CCN(CC)C(=O)[C@@]1(C)Nc2c(ccc3ccccc23)C[C@H]1N(C)C",
17 | );
18 | const [numMolecules, setNumMolecules] = useState("10");
19 | const [minSimilarity, setMinSimilarity] = useState("0.3");
20 | const [particles, setParticles] = useState("30");
21 | const [iterations, setIterations] = useState("10");
22 | const [molecules, setMolecules] = useState([]);
23 | const [loading, setLoading] = useState(false);
24 | const [history, setHistory] = useState([]);
25 | const [userId, setUserId] = useState(null);
26 |
27 | useEffect(() => {
28 | const fetchUserData = async () => {
29 | if (session?.user?.email) {
30 | try {
31 | const user = await getUserByEmail(session.user.email);
32 | setUserId(user._id);
33 | const historyFromServer = await getMoleculeGenerationHistoryByUser(
34 | user._id,
35 | );
36 | setHistory(historyFromServer);
37 | } catch (error) {
38 | console.error("Error fetching user or history:", error);
39 | }
40 | }
41 | };
42 |
43 | fetchUserData();
44 | }, [session?.user?.email]);
45 |
46 | const handleSubmit = async (e: any) => {
47 | e.preventDefault();
48 | setLoading(true);
49 |
50 | const API_KEY =
51 | "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";
52 |
53 | const invokeUrl =
54 | "https://health.api.nvidia.com/v1/biology/nvidia/molmim/generate";
55 |
56 | const payload = {
57 | algorithm: "CMA-ES",
58 | num_molecules: parseInt(numMolecules),
59 | property_name: "QED",
60 | minimize: false,
61 | min_similarity: parseFloat(minSimilarity),
62 | particles: parseInt(particles),
63 | iterations: parseInt(iterations),
64 | smi: smiles,
65 | };
66 |
67 | try {
68 | const response = await fetch(invokeUrl, {
69 | method: "POST",
70 | headers: {
71 | Authorization: `Bearer ${API_KEY}`,
72 | Accept: "application/json",
73 | "Content-Type": "application/json",
74 | },
75 | body: JSON.stringify(payload),
76 | });
77 |
78 | const data = await response.json();
79 | const generatedMolecules = JSON.parse(data.molecules).map((mol: any) => ({
80 | structure: mol.sample,
81 | score: mol.score,
82 | }));
83 |
84 | setMolecules(generatedMolecules);
85 |
86 | if (userId) {
87 | await createMoleculeGenerationHistory(
88 | {
89 | smiles,
90 | numMolecules: parseInt(numMolecules),
91 | minSimilarity: parseFloat(minSimilarity),
92 | particles: parseInt(particles),
93 | iterations: parseInt(iterations),
94 | generatedMolecules,
95 | },
96 | userId,
97 | );
98 |
99 | const updatedHistory = await getMoleculeGenerationHistoryByUser(userId);
100 | setHistory(updatedHistory);
101 | } else {
102 | console.error("User ID is not available.");
103 | }
104 |
105 | console.log(generatedMolecules);
106 | } catch (error) {
107 | console.error("Error fetching data:", error);
108 | } finally {
109 | setLoading(false);
110 | }
111 | };
112 |
113 | return (
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | SMILES to Molecule Generator
123 |
124 |
125 |
203 |
204 |
205 |
206 |
207 |
208 |
209 | Molecule Generation History
210 |
211 |
212 | {history.map((entry: any, index) => (
213 |
214 |
215 | SMILES: {entry.smiles}
216 |
217 |
218 | Molecules:{" "}
219 | {entry.numMolecules}
220 |
221 |
222 | Date:{" "}
223 | {new Date(entry.createdAt).toLocaleDateString()}
224 |
225 |
226 |
232 |
233 |
234 | ))}
235 |
236 |
237 |
238 |
239 |
240 | {molecules.length > 0 && (
241 |
242 |
243 |
244 | {molecules.map((mol: any, index) => (
245 |
251 | ))}
252 |
253 |
254 |
255 | )}
256 |
257 | );
258 | };
259 |
260 | export default ModalLayout;
261 |
--------------------------------------------------------------------------------
/src/app/molecule-bank/page.tsx:
--------------------------------------------------------------------------------
1 | import ComponetHeader from "@/components/ComponentHeader/ComponentHeader";
2 | import MoleculeBankTable from "@/components/MoleculeBank/MoleculeBankTable";
3 |
4 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
5 |
6 | const Page = () => {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 |
14 | );
15 | };
16 |
17 | export default Page;
18 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Index from "@/components/Dashboard";
2 | import { Metadata } from "next";
3 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
4 |
5 | export const metadata: Metadata = {
6 | title:
7 | "Next.js E-commerce Dashboard | TailAdmin - Next.js Dashboard Template",
8 | description: "This is Next.js Home for TailAdmin Dashboard Template",
9 | };
10 |
11 | export default function Home() {
12 | return (
13 | <>
14 |
15 |
16 |
17 | >
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/profile/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import Breadcrumb from "@/components/ComponentHeader/ComponentHeader";
3 | import Image from "next/image";
4 | import { Metadata } from "next";
5 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
6 | import { CameraIcon } from "lucide-react";
7 | import { useUser } from "../context/UserContext";
8 |
9 | const Profile = () => {
10 | const user = useUser();
11 |
12 | console.log(user);
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
32 |
33 |
34 |
35 |
36 |
47 |
59 |
60 |
61 |
62 |
63 | {user.firstName} {user.lastName}
64 |
65 |
Drug Researcher
66 |
67 |
68 |
69 | 259
70 |
71 | Contributions
72 |
73 |
74 |
75 |
76 |
77 | About Me
78 |
79 |
{user.userBio}
80 |
81 |
82 |
83 |
84 |
85 |
86 | );
87 | };
88 |
89 | export default Profile;
90 |
--------------------------------------------------------------------------------
/src/app/research/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
3 | import MoleculeStructure from "@/components/MoleculeStructure";
4 | import { useState } from "react";
5 | import { Search } from "lucide-react";
6 |
7 | export default function PubChem() {
8 | const [compoundName, setCompoundName] = useState("");
9 | const [compoundData, setCompoundData] = useState(null);
10 | const [loading, setLoading] = useState(false);
11 | const [error, setError] = useState("");
12 |
13 | const fetchCompoundData = async () => {
14 | setLoading(true);
15 | setError("");
16 | setCompoundData(null);
17 |
18 | try {
19 | const response = await fetch(
20 | `https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/name/${encodeURIComponent(
21 | compoundName,
22 | )}/property/MolecularFormula,MolecularWeight,InChIKey,CanonicalSMILES,IsomericSMILES,IUPACName,XLogP,ExactMass,MonoisotopicMass,TPSA,Complexity,Charge,HBondDonorCount,HBondAcceptorCount,RotatableBondCount,HeavyAtomCount/JSON`,
23 | );
24 |
25 | if (!response.ok) {
26 | throw new Error("Compound not found");
27 | }
28 |
29 | const data = await response.json();
30 |
31 | if (
32 | data &&
33 | data.PropertyTable &&
34 | data.PropertyTable.Properties &&
35 | data.PropertyTable.Properties.length > 0
36 | ) {
37 | const compoundInfo = data.PropertyTable.Properties[0];
38 | setCompoundData({
39 | MolecularFormula: compoundInfo.MolecularFormula,
40 | MolecularWeight: compoundInfo.MolecularWeight,
41 | InChIKey: compoundInfo.InChIKey,
42 | CanonicalSMILES: compoundInfo.CanonicalSMILES,
43 | IsomericSMILES: compoundInfo.IsomericSMILES,
44 | IUPACName: compoundInfo.IUPACName,
45 | XLogP: compoundInfo.XLogP,
46 | ExactMass: compoundInfo.ExactMass,
47 | MonoisotopicMass: compoundInfo.MonoisotopicMass,
48 | TPSA: compoundInfo.TPSA,
49 | Complexity: compoundInfo.Complexity,
50 | Charge: compoundInfo.Charge,
51 | HBondDonorCount: compoundInfo.HBondDonorCount,
52 | HBondAcceptorCount: compoundInfo.HBondAcceptorCount,
53 | RotatableBondCount: compoundInfo.RotatableBondCount,
54 | HeavyAtomCount: compoundInfo.HeavyAtomCount,
55 | });
56 | } else {
57 | throw new Error("Compound data is not available");
58 | }
59 | } catch (err) {
60 | setError((err as Error).message);
61 | } finally {
62 | setLoading(false);
63 | }
64 | };
65 |
66 | const handleKeyDown = (e: React.KeyboardEvent) => {
67 | if (e.key === "Enter") {
68 | fetchCompoundData();
69 | }
70 | };
71 |
72 | return (
73 |
74 |
75 |
76 |
77 | Compound Search{" "}
78 |
79 |
80 | setCompoundName(e.target.value)}
84 | onKeyDown={handleKeyDown}
85 | className="border-gray-300 w-full rounded-lg border bg-white p-3 pl-10 text-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 md:w-96"
86 | placeholder="Enter a compound name"
87 | />
88 |
89 |
90 |
91 |
92 |
93 |
94 | {error &&
{error}
}
95 |
96 | {compoundData && (
97 |
98 |
99 |
100 | Basic Information
101 |
102 |
103 |
104 | Molecular Formula:
105 | {" "}
106 | {compoundData.MolecularFormula}
107 |
108 |
109 |
110 | Molecular Weight:
111 | {" "}
112 | {compoundData.MolecularWeight} g/mol
113 |
114 |
115 |
116 | InChIKey:
117 | {" "}
118 | {compoundData.InChIKey}
119 |
120 |
121 |
122 | Canonical SMILES:
123 | {" "}
124 |
128 |
129 |
130 |
131 | Isomeric SMILES:
132 | {" "}
133 | {compoundData.IsomericSMILES}
134 |
135 |
136 |
137 | IUPAC Name:
138 | {" "}
139 | {compoundData.IUPACName}
140 |
141 |
142 |
143 |
144 |
145 | Physical Properties
146 |
147 |
148 |
149 | XLogP:
150 | {" "}
151 | {compoundData.XLogP}
152 |
153 |
154 |
155 | Exact Mass:
156 | {" "}
157 | {compoundData.ExactMass} g/mol
158 |
159 |
160 |
161 | Monoisotopic Mass:
162 | {" "}
163 | {compoundData.MonoisotopicMass} g/mol
164 |
165 |
166 |
167 | Topological Polar Surface Area (TPSA):
168 | {" "}
169 | {compoundData.TPSA} Ų
170 |
171 |
172 |
173 | Complexity:
174 | {" "}
175 | {compoundData.Complexity}
176 |
177 |
178 |
179 | Charge:
180 | {" "}
181 | {compoundData.Charge}
182 |
183 |
184 |
185 |
186 |
187 | Additional Information
188 |
189 |
190 |
191 |
192 | Hydrogen Bond Donors:
193 | {" "}
194 | {compoundData.HBondDonorCount}
195 |
196 |
197 |
198 | Hydrogen Bond Acceptors:
199 | {" "}
200 | {compoundData.HBondAcceptorCount}
201 |
202 |
203 |
204 | Rotatable Bonds:
205 | {" "}
206 | {compoundData.RotatableBondCount}
207 |
208 |
209 |
210 | Heavy Atom Count:
211 | {" "}
212 | {compoundData.HeavyAtomCount}
213 |
214 |
215 |
216 |
217 | )}
218 |
219 |
220 | );
221 | }
222 |
--------------------------------------------------------------------------------
/src/app/reset-password/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState } from "react";
3 | import Link from "next/link";
4 | import { LoaderCircle, LockIcon } from "lucide-react";
5 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
6 | import Breadcrumb from "@/components/ComponentHeader/ComponentHeader";
7 | import { useRouter, useSearchParams } from "next/navigation";
8 | import { resetPassword } from "@/lib/actions/user.actions";
9 |
10 | const ResetPasswordPage: React.FC = () => {
11 | const [password, setPassword] = useState("");
12 | const [confirmPassword, setConfirmPassword] = useState("");
13 | const [error, setError] = useState(null);
14 | const [success, setSuccess] = useState(false);
15 | const [isLoading, setIsLoading] = useState(false);
16 | const router = useRouter();
17 | const searchParams = useSearchParams();
18 | const token = searchParams.get("token");
19 |
20 | const handleSubmit = async (e: React.FormEvent) => {
21 | e.preventDefault();
22 | setError(null);
23 | setIsLoading(true);
24 |
25 | if (!password || !confirmPassword) {
26 | setError("Please fill in all fields.");
27 | setIsLoading(false);
28 | return;
29 | }
30 |
31 | if (password !== confirmPassword) {
32 | setError("Passwords do not match.");
33 | setIsLoading(false);
34 | return;
35 | }
36 |
37 | try {
38 | await resetPassword(token as string, password);
39 |
40 | setSuccess(true);
41 | setTimeout(() => {
42 | router.push("/auth-page/signin");
43 | }, 3000);
44 | } catch (err: any) {
45 | setError("Something went wrong. Please try again.");
46 | console.error(err);
47 | } finally {
48 | setIsLoading(false);
49 | }
50 | };
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | Reset Your Password
62 |
63 |
64 | {error &&
{error}
}
65 | {success && (
66 |
67 | Password reset successfully! Redirecting to sign in...
68 |
69 | )}
70 |
71 |
136 |
137 |
138 |
139 |
140 |
141 | );
142 | };
143 |
144 | export default ResetPasswordPage;
145 |
--------------------------------------------------------------------------------
/src/app/settings/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import Breadcrumb from "@/components/ComponentHeader/ComponentHeader";
3 | import Image from "next/image";
4 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
5 | import DarkModeSwitcher from "@/components/Header/DarkModeSwitcher";
6 | import { Edit, MailIcon, CameraIcon, User } from "lucide-react";
7 | import { useSession } from "next-auth/react";
8 | import React, { useState, useEffect } from "react";
9 | import { getUserByEmail, updateUser } from "@/lib/actions/user.actions";
10 |
11 | const Settings = () => {
12 | const { data: session } = useSession();
13 | const [userData, setUserData] = useState({
14 | firstName: "",
15 | lastName: "",
16 | email: "",
17 | userBio: "",
18 | photo: "",
19 | id: "",
20 | });
21 | const [imageFile, setImageFile] = useState(null);
22 | const [errors, setErrors] = useState(null);
23 | const [isLoading, setIsLoading] = useState(false);
24 |
25 | useEffect(() => {
26 | const fetchUserData = async () => {
27 | if (session?.user?.email) {
28 | const user = await getUserByEmail(session.user.email);
29 | setUserData({
30 | firstName: user.firstName,
31 | lastName: user.lastName,
32 | email: user.email,
33 | userBio: user.userBio || "",
34 | photo: user.photo || "/images/user/user-03.png",
35 | id: user._id,
36 | });
37 | }
38 | };
39 |
40 | fetchUserData();
41 | }, [session?.user?.email]);
42 |
43 | const handlePersonalInfoSubmit = async (
44 | e: React.FormEvent,
45 | ) => {
46 | e.preventDefault();
47 | setIsLoading(true);
48 |
49 | try {
50 | const updatedUser = {
51 | firstName: userData.firstName,
52 | lastName: userData.lastName,
53 | userBio: userData.userBio,
54 | photo: userData.photo,
55 | email: userData.email,
56 | };
57 |
58 | if (userData.id) {
59 | const updated = await updateUser(userData.id, updatedUser);
60 | setUserData(updated);
61 | }
62 |
63 | setIsLoading(false);
64 | } catch (error) {
65 | setIsLoading(false);
66 | setErrors("Failed to update profile.");
67 | console.error("Error updating user:", error);
68 | }
69 | };
70 |
71 | console.log(userData);
72 |
73 | const handleImageUploadSubmit = async (
74 | e: React.FormEvent,
75 | ) => {
76 | e.preventDefault();
77 | setIsLoading(true);
78 |
79 | try {
80 | let base64Image = userData.photo;
81 | if (imageFile) {
82 | base64Image = await convertImageToBase64(imageFile);
83 | }
84 |
85 | if (userData.id) {
86 | const updatedUser = {
87 | ...userData,
88 | photo: base64Image,
89 | };
90 | const updated = await updateUser(userData.id, updatedUser);
91 | setUserData(updated);
92 | }
93 |
94 | setIsLoading(false);
95 | } catch (error) {
96 | setIsLoading(false);
97 | setErrors("Failed to upload image.");
98 | console.error("Error uploading image:", error);
99 | }
100 | };
101 |
102 | const convertImageToBase64 = async (file: File): Promise => {
103 | return new Promise((resolve, reject) => {
104 | const reader = new FileReader();
105 | reader.readAsDataURL(file);
106 | reader.onloadend = () => resolve(reader.result as string);
107 | reader.onerror = (error) => reject(error);
108 | });
109 | };
110 |
111 | const handleChange = (
112 | e: React.ChangeEvent,
113 | ) => {
114 | const { name, value } = e.target;
115 | setUserData((prevData) => ({
116 | ...prevData,
117 | [name]: value,
118 | }));
119 | };
120 |
121 | const handleFileChange = (e: any) => {
122 | if (e.target.files && e.target.files[0]) {
123 | setImageFile(e.target.files[0]);
124 | setUserData((prevData) => ({
125 | ...prevData,
126 | photo: URL.createObjectURL(e.target.files[0]),
127 | }));
128 | }
129 | };
130 |
131 | return (
132 |
133 |
134 |
135 |
136 | Toggle Theme
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | Personal Information
145 |
146 |
147 |
148 |
240 |
241 |
242 |
243 |
244 | {/* Image Upload Form */}
245 |
246 |
247 |
248 |
249 | Your Photo
250 |
251 |
252 |
253 |
329 |
330 |
331 |
332 |
333 |
334 |
335 | );
336 | };
337 |
338 | export default Settings;
339 |
--------------------------------------------------------------------------------
/src/app/ui/modals/page.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Modal: React.FC = ({ id, title, content, onCloseText }) => {
4 | return (
5 |
14 | );
15 | };
16 |
17 | export default Modal;
18 |
--------------------------------------------------------------------------------
/src/app/verify-email/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useEffect, useState } from "react";
4 | import { useRouter, useSearchParams } from "next/navigation";
5 | import { verifyEmail } from "@/lib/actions/user.actions";
6 | import DefaultLayout from "@/components/Layouts/DefaultLayout";
7 | import { CircleCheckBig } from "lucide-react";
8 |
9 | const VerifyEmailPage: React.FC = () => {
10 | const [status, setStatus] = useState<"loading" | "success" | "error">(
11 | "loading",
12 | );
13 | const searchParams = useSearchParams();
14 | const token = searchParams.get("token");
15 | const router = useRouter();
16 |
17 | useEffect(() => {
18 | const verifyUserEmail = async () => {
19 | if (token) {
20 | try {
21 | await verifyEmail(token);
22 | setStatus("success");
23 | } catch (error) {
24 | console.error("Error verifying email:", error);
25 | setStatus("error");
26 | }
27 | } else {
28 | setStatus("error");
29 | }
30 | };
31 |
32 | verifyUserEmail();
33 | }, [token]);
34 |
35 | return (
36 |
37 |
38 |
39 |
40 |
41 | {status === "loading" &&
Verifying your email, please wait...
}
42 | {status === "success" && (
43 |
Your email has been successfully verified!
44 | )}
45 | {status === "error" && (
46 |
There was an error verifying your email. Please try again.
47 | )}
48 | {status === "success" && (
49 |
55 | )}
56 |
57 |
58 | );
59 | };
60 |
61 | export default VerifyEmailPage;
62 |
--------------------------------------------------------------------------------
/src/components/ClickOutside.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from "react";
2 |
3 | interface Props {
4 | children: React.ReactNode;
5 | exceptionRef?: React.RefObject;
6 | onClick: () => void;
7 | className?: string;
8 | }
9 |
10 | const ClickOutside: React.FC = ({
11 | children,
12 | exceptionRef,
13 | onClick,
14 | className,
15 | }) => {
16 | const wrapperRef = useRef(null);
17 |
18 | useEffect(() => {
19 | const handleClickListener = (event: MouseEvent) => {
20 | let clickedInside: null | boolean = false;
21 | if (exceptionRef) {
22 | clickedInside =
23 | (wrapperRef.current &&
24 | wrapperRef.current.contains(event.target as Node)) ||
25 | (exceptionRef.current && exceptionRef.current === event.target) ||
26 | (exceptionRef.current &&
27 | exceptionRef.current.contains(event.target as Node));
28 | } else {
29 | clickedInside =
30 | wrapperRef.current &&
31 | wrapperRef.current.contains(event.target as Node);
32 | }
33 |
34 | if (!clickedInside) onClick();
35 | };
36 |
37 | document.addEventListener("mousedown", handleClickListener);
38 |
39 | return () => {
40 | document.removeEventListener("mousedown", handleClickListener);
41 | };
42 | }, [exceptionRef, onClick]);
43 |
44 | return (
45 |
46 | {children}
47 |
48 | );
49 | };
50 |
51 | export default ClickOutside;
52 |
--------------------------------------------------------------------------------
/src/components/ComponentHeader/ComponentHeader.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import Modal from "@/app/ui/modals/page";
3 | import React from "react";
4 |
5 | interface ComponentHeaderProps {
6 | pageName: string;
7 | containActionButton?: boolean;
8 | }
9 |
10 | const ComponentHeader: React.FC = ({
11 | pageName,
12 | containActionButton,
13 | }) => {
14 | const openModal = (modalId: string) => {
15 | const modal = document.getElementById(modalId) as HTMLDialogElement;
16 | if (modal) {
17 | modal.showModal();
18 | }
19 | };
20 |
21 | return (
22 |
23 |
24 | {pageName}
25 |
26 |
27 | {containActionButton && (
28 |
38 | )}
39 |
40 |
45 |
98 | >
99 | }
100 | onCloseText="Close"
101 | />
102 |
103 | );
104 | };
105 |
106 | export default ComponentHeader;
107 |
--------------------------------------------------------------------------------
/src/components/EmailTemplates/reset-email.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | interface ResetPasswordTemplateProps {
4 | firstName: string;
5 | resetUrl: string;
6 | }
7 |
8 | export const ResetPasswordTemplate: React.FC<
9 | Readonly
10 | > = ({ firstName, resetUrl }) => (
11 |
12 |
Hello, {firstName}!
13 |
14 | It looks like you requested a password reset. Click the link below to
15 | reset your password:
16 |
17 |
18 | Reset your password
19 |
20 |
If you did not request a password reset, please ignore this email.
21 |
Thank you!
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/src/components/EmailTemplates/verify-email.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | interface VerifyEmailTemplateProps {
4 | firstName: string;
5 | verificationUrl: string;
6 | }
7 |
8 | export const VerifyEmailTemplate: React.FC<
9 | Readonly
10 | > = ({ firstName, verificationUrl }) => (
11 |
12 |
Welcome, {firstName}!
13 |
14 | Thanks for signing up! Please verify your email address by clicking the
15 | link below:
16 |
17 |
21 | Verify your email
22 |
23 |
If you did not sign up for this account, please ignore this email.
24 |
Thank you!
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/src/components/Header/DarkModeSwitcher.tsx:
--------------------------------------------------------------------------------
1 | import useColorMode from "@/hooks/useColorMode";
2 |
3 | const DarkModeSwitcher = () => {
4 | const [colorMode, setColorMode] = useColorMode();
5 |
6 | return (
7 |
60 | );
61 | };
62 |
63 | export default DarkModeSwitcher;
64 |
--------------------------------------------------------------------------------
/src/components/Header/DropdownMessage.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from "react";
2 | import Link from "next/link";
3 | import Image from "next/image";
4 | import ClickOutside from "@/components/ClickOutside";
5 | import { MessageCircleMore } from "lucide-react";
6 |
7 | const DropdownMessage = () => {
8 | const [dropdownOpen, setDropdownOpen] = useState(false);
9 | const [notifying, setNotifying] = useState(true);
10 |
11 | return (
12 | setDropdownOpen(false)} className="relative">
13 |
14 | {
16 | setNotifying(false);
17 | setDropdownOpen(!dropdownOpen);
18 | }}
19 | className="relative flex h-8.5 w-8.5 items-center justify-center rounded-full border-[0.5px] border-stroke bg-gray hover:text-primary dark:border-strokedark dark:bg-meta-4 dark:text-white"
20 | href="/message"
21 | >
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | };
35 |
36 | export default DropdownMessage;
37 |
--------------------------------------------------------------------------------
/src/components/Header/DropdownUser.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useState, useEffect } from "react";
3 | import { useUser } from "@/app/context/UserContext";
4 | import { signOut } from "next-auth/react";
5 | import { useRouter } from "next/navigation";
6 | import { ChevronDown, LogOut, Settings, User2 } from "lucide-react";
7 | import Image from "next/image";
8 | import Link from "next/link";
9 | import ClickOutside from "@/components/ClickOutside";
10 |
11 | const DropdownUser = () => {
12 | const [dropdownOpen, setDropdownOpen] = useState(false);
13 | const user = useUser();
14 | const router = useRouter();
15 |
16 | const handleLogout = async () => {
17 | await signOut({ redirect: false });
18 | router.push("/auth-page/signin");
19 | };
20 |
21 | return (
22 | setDropdownOpen(false)} className="relative">
23 | setDropdownOpen(!dropdownOpen)}
25 | className="flex items-center gap-4"
26 | href="#"
27 | >
28 |
29 |
30 | {user.firstName} {user.lastName}
31 |
32 | {user.jobTitle}
33 |
34 |
35 |
36 |
47 |
48 |
49 |
50 |
51 |
52 | {dropdownOpen && (
53 |
54 |
55 | -
56 |
60 |
61 | My Profile
62 |
63 |
64 | -
65 |
69 |
70 | Account Settings
71 |
72 |
73 |
74 |
81 |
82 | )}
83 |
84 | );
85 | };
86 |
87 | export default DropdownUser;
88 |
--------------------------------------------------------------------------------
/src/components/Header/index.tsx:
--------------------------------------------------------------------------------
1 | import Link from "next/link";
2 | import DarkModeSwitcher from "./DarkModeSwitcher";
3 | import DropdownMessage from "./DropdownMessage";
4 | import DropdownUser from "./DropdownUser";
5 | import Image from "next/image";
6 | import { SearchIcon } from "lucide-react";
7 |
8 | const Header = (props: {
9 | sidebarOpen: string | boolean | undefined;
10 | setSidebarOpen: (arg0: boolean) => void;
11 | }) => {
12 | return (
13 |
83 | );
84 | };
85 |
86 | export default Header;
87 |
--------------------------------------------------------------------------------
/src/components/Layouts/DefaultLayout.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useLayoutEffect } from "react";
3 | import Sidebar from "@/components/Sidebar";
4 | import Header from "@/components/Header";
5 | import { useSession } from "next-auth/react";
6 | import { useRouter, usePathname } from "next/navigation";
7 |
8 | export default function DefaultLayout({
9 | children,
10 | }: {
11 | children: React.ReactNode;
12 | }) {
13 | const [sidebarOpen, setSidebarOpen] = useState(false);
14 |
15 | const { data: session, status } = useSession();
16 | const router = useRouter();
17 | const pathname = usePathname();
18 |
19 | const publicRoutes = [
20 | "/auth-page/signin",
21 | "/auth-page/signup",
22 | "/verify-email",
23 | "/reset-password",
24 | ];
25 |
26 | useLayoutEffect(() => {
27 | if (status === "unauthenticated" && !publicRoutes.includes(pathname)) {
28 | router.push("/auth-page/signin");
29 | }
30 | }, [status, router, pathname]);
31 |
32 | return (
33 | <>
34 |
35 |
36 |
37 |
38 |
39 |
40 | {children}
41 |
42 |
43 |
44 |
45 | >
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/MoleculeBank/MoleculeBankTable.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { useState, useEffect } from "react";
3 | import MoleculeStructure from "../MoleculeStructure/index";
4 |
5 | const moleculeBank = [
6 | {
7 | moleculeName: "Aspirin",
8 | smilesStructure: "CC(=O)OC1=CC=CC=C1C(O)=O",
9 | molecularWeight: 180.16,
10 | categoryUsage: "Pain reliever/NSAID",
11 | },
12 | {
13 | moleculeName: "Caffeine",
14 | smilesStructure: "CN1C=NC2=C1C(=O)N(C(=O)N2C)C",
15 | molecularWeight: 194.19,
16 | categoryUsage: "Stimulant",
17 | },
18 | {
19 | moleculeName: "Benzene",
20 | smilesStructure: "C1=CC=CC=C1",
21 | molecularWeight: 78.11,
22 | categoryUsage: "Industrial solvent",
23 | },
24 | {
25 | moleculeName: "Glucose",
26 | smilesStructure: "C(C1C(C(C(C(O1)O)O)O)O)O",
27 | molecularWeight: 180.16,
28 | categoryUsage: "Energy source/sugar",
29 | },
30 | {
31 | moleculeName: "Penicillin",
32 | smilesStructure: "CC1(C2C(C(C(O2)N1C(=O)COC(=O)C)C)S)C=O",
33 | molecularWeight: 334.39,
34 | categoryUsage: "Antibiotic",
35 | },
36 | {
37 | moleculeName: "Ibuprofen",
38 | smilesStructure: "CC(C)CC1=CC=C(C=C1)C(C)C(=O)O",
39 | molecularWeight: 206.28,
40 | categoryUsage: "Pain reliever/NSAID",
41 | },
42 | {
43 | moleculeName: "Acetaminophen",
44 | smilesStructure: "CC(=O)NC1=CC=C(O)C=C1",
45 | molecularWeight: 151.16,
46 | categoryUsage: "Pain reliever/Antipyretic",
47 | },
48 | {
49 | moleculeName: "Morphine",
50 | smilesStructure: "CN1CCC23C4C1CC(C2C3O)OC5=CC=CC=C45",
51 | molecularWeight: 285.34,
52 | categoryUsage: "Pain reliever/Opiate",
53 | },
54 | {
55 | moleculeName: "Nicotine",
56 | smilesStructure: "CN1CCCC1C2=CN=CC=C2",
57 | molecularWeight: 162.23,
58 | categoryUsage: "Stimulant",
59 | },
60 | {
61 | moleculeName: "Ethanol",
62 | smilesStructure: "CCO",
63 | molecularWeight: 46.07,
64 | categoryUsage: "Alcohol/Disinfectant",
65 | },
66 | ];
67 |
68 | const TableOne = () => {
69 | const [searchQuery, setSearchQuery] = useState("");
70 | const [filteredMolecules, setFilteredMolecules] = useState(moleculeBank);
71 |
72 | useEffect(() => {
73 | const filteredData = moleculeBank.filter((molecule) =>
74 | molecule.moleculeName.toLowerCase().includes(searchQuery.toLowerCase()),
75 | );
76 | setFilteredMolecules(filteredData);
77 | }, [searchQuery]);
78 |
79 | return (
80 |
81 |
82 | Molecules
83 |
84 |
85 |
setSearchQuery(e.target.value)}
90 | className="border-gray-300 text-gray-700 placeholder-gray-400 dark:border-gray-600 dark:placeholder-gray-500 text-md mb-4 w-full rounded-lg border bg-white px-4 py-3 shadow-sm outline-none focus:border-primary focus:ring-primary dark:bg-[#181818] dark:text-white"
91 | />
92 |
93 |
94 |
95 |
96 | Molecule name
97 |
98 |
99 |
100 |
101 | Smile Structure Image
102 |
103 |
104 |
105 |
106 | Molecular Weights (g/mol)
107 |
108 |
109 |
110 |
111 | Category Usage
112 |
113 |
114 |
115 |
116 | {filteredMolecules.map((molecule, key) => (
117 |
125 |
126 |
127 | {molecule.moleculeName}
128 |
129 |
130 |
131 |
139 |
140 |
141 |
142 | {molecule.molecularWeight}
143 |
144 |
145 |
146 |
147 |
148 | {molecule.categoryUsage}
149 |
150 |
151 |
152 | ))}
153 |
154 |
155 | );
156 | };
157 |
158 | export default TableOne;
159 |
--------------------------------------------------------------------------------
/src/components/MoleculeStructure/index.jsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import React, { Component } from "react";
3 | import _ from "lodash";
4 | import PropTypes from "prop-types";
5 | import initRDKitModule from "@rdkit/rdkit";
6 |
7 | const initRDKit = (() => {
8 | let rdkitLoadingPromise;
9 | return () => {
10 | if (!rdkitLoadingPromise) {
11 | rdkitLoadingPromise = new Promise((resolve, reject) => {
12 | initRDKitModule()
13 | .then((RDKit) => {
14 | resolve(RDKit);
15 | })
16 | .catch((e) => {
17 | reject(e);
18 | });
19 | });
20 | }
21 | return rdkitLoadingPromise;
22 | };
23 | })();
24 |
25 | class MoleculeStructure extends Component {
26 | static propTypes = {
27 | id: PropTypes.string.isRequired,
28 | className: PropTypes.string,
29 | svgMode: PropTypes.bool,
30 | width: PropTypes.number,
31 | height: PropTypes.number,
32 | structure: PropTypes.string.isRequired,
33 | subStructure: PropTypes.string,
34 | extraDetails: PropTypes.object,
35 | drawingDelay: PropTypes.number,
36 | scores: PropTypes.number,
37 | };
38 |
39 | static defaultProps = {
40 | subStructure: "",
41 | className: "",
42 | width: 250,
43 | height: 200,
44 | svgMode: false,
45 | extraDetails: {},
46 | drawingDelay: undefined,
47 | scores: 0,
48 | };
49 |
50 | constructor(props) {
51 | super(props);
52 |
53 | this.MOL_DETAILS = {
54 | width: this.props.width,
55 | height: this.props.height,
56 | bondLineWidth: 1,
57 | addStereoAnnotation: true,
58 | ...this.props.extraDetails,
59 | };
60 |
61 | this.state = {
62 | svg: undefined,
63 | rdKitLoaded: false,
64 | rdKitError: false,
65 | };
66 | }
67 |
68 | drawOnce = (() => {
69 | let wasCalled = false;
70 |
71 | return () => {
72 | if (!wasCalled) {
73 | wasCalled = true;
74 | this.draw();
75 | }
76 | };
77 | })();
78 |
79 | draw() {
80 | if (this.props.drawingDelay) {
81 | setTimeout(() => {
82 | this.drawSVGorCanvas();
83 | }, this.props.drawingDelay);
84 | } else {
85 | this.drawSVGorCanvas();
86 | }
87 | }
88 |
89 | drawSVGorCanvas() {
90 | const mol = this.RDKit.get_mol(this.props.structure || "invalid");
91 | const qmol = this.RDKit.get_qmol(this.props.subStructure || "invalid");
92 | const isValidMol = this.isValidMol(mol);
93 |
94 | if (this.props.svgMode && isValidMol) {
95 | const svg = mol.get_svg_with_highlights(this.getMolDetails(mol, qmol));
96 | this.setState({ svg });
97 | } else if (isValidMol) {
98 | const canvas = document.getElementById(this.props.id);
99 | mol.draw_to_canvas_with_highlights(canvas, this.getMolDetails(mol, qmol));
100 | }
101 |
102 | mol?.delete();
103 | qmol?.delete();
104 | }
105 |
106 | isValidMol(mol) {
107 | return !!mol;
108 | }
109 |
110 | getMolDetails(mol, qmol) {
111 | if (this.isValidMol(mol) && this.isValidMol(qmol)) {
112 | const subStructHighlightDetails = JSON.parse(
113 | mol.get_substruct_matches(qmol),
114 | );
115 | const subStructHighlightDetailsMerged = !_.isEmpty(
116 | subStructHighlightDetails,
117 | )
118 | ? subStructHighlightDetails.reduce(
119 | (acc, { atoms, bonds }) => ({
120 | atoms: [...acc.atoms, ...atoms],
121 | bonds: [...acc.bonds, ...bonds],
122 | }),
123 | { bonds: [], atoms: [] },
124 | )
125 | : subStructHighlightDetails;
126 | return JSON.stringify({
127 | ...this.MOL_DETAILS,
128 | ...(this.props.extraDetails || {}),
129 | ...subStructHighlightDetailsMerged,
130 | });
131 | } else {
132 | return JSON.stringify({
133 | ...this.MOL_DETAILS,
134 | ...(this.props.extraDetails || {}),
135 | });
136 | }
137 | }
138 |
139 | componentDidMount() {
140 | initRDKit()
141 | .then((RDKit) => {
142 | this.RDKit = RDKit;
143 | this.setState({ rdKitLoaded: true });
144 | try {
145 | this.draw();
146 | } catch (err) {
147 | console.log(err);
148 | }
149 | })
150 | .catch((err) => {
151 | console.log(err);
152 | this.setState({ rdKitError: true });
153 | });
154 | }
155 |
156 | componentDidUpdate(prevProps) {
157 | if (
158 | !this.state.rdKitError &&
159 | this.state.rdKitLoaded &&
160 | !this.props.svgMode
161 | ) {
162 | this.drawOnce();
163 | }
164 |
165 | if (this.state.rdKitLoaded) {
166 | const shouldUpdateDrawing =
167 | prevProps.structure !== this.props.structure ||
168 | prevProps.svgMode !== this.props.svgMode ||
169 | prevProps.subStructure !== this.props.subStructure ||
170 | prevProps.width !== this.props.width ||
171 | prevProps.height !== this.props.height ||
172 | !_.isEqual(prevProps.extraDetails, this.props.extraDetails);
173 |
174 | if (shouldUpdateDrawing) {
175 | this.draw();
176 | }
177 | }
178 | }
179 |
180 | render() {
181 | console.log("props score number:", this.props.scores);
182 | if (this.state.rdKitError) {
183 | return "Error loading renderer.";
184 | }
185 | if (!this.state.rdKitLoaded) {
186 | return "Loading renderer...";
187 | }
188 |
189 | const mol = this.RDKit.get_mol(this.props.structure || "invalid");
190 | const isValidMol = this.isValidMol(mol);
191 | mol?.delete();
192 |
193 | if (!isValidMol) {
194 | return (
195 |
196 | Render Error.
197 |
198 | );
199 | } else if (this.props.svgMode) {
200 | return (
201 |
207 | );
208 | } else {
209 | return (
210 |
215 |
221 | {this.props.scores ? (
222 |
223 | Score: {this.props.scores.toFixed(2)}
224 |
225 | ) : (
226 | ""
227 | )}
228 |
229 | );
230 | }
231 | }
232 | }
233 |
234 | export default MoleculeStructure;
235 |
--------------------------------------------------------------------------------
/src/components/Sidebar/SidebarDropdown.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 | import { usePathname } from "next/navigation";
4 |
5 | const SidebarDropdown = ({ item }: any) => {
6 | const pathname = usePathname();
7 |
8 | return (
9 | <>
10 |
11 | {item.map((item: any, index: number) => (
12 | -
13 |
19 | {item.label}
20 |
21 |
22 | ))}
23 |
24 | >
25 | );
26 | };
27 |
28 | export default SidebarDropdown;
29 |
--------------------------------------------------------------------------------
/src/components/Sidebar/SidebarItem.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Link from "next/link";
3 | import SidebarDropdown from "@/components/Sidebar/SidebarDropdown";
4 | import { usePathname } from "next/navigation";
5 |
6 | const SidebarItem = ({ item, pageName, setPageName }: any) => {
7 | const handleClick = () => {
8 | const updatedPageName =
9 | pageName !== item.label.toLowerCase() ? item.label.toLowerCase() : "";
10 | return setPageName(updatedPageName);
11 | };
12 |
13 | const pathname = usePathname();
14 |
15 | const isActive = (item: any) => {
16 | if (item.route === pathname) return true;
17 | if (item.children) {
18 | return item.children.some((child: any) => isActive(child));
19 | }
20 | return false;
21 | };
22 |
23 | const isItemActive = isActive(item);
24 |
25 | return (
26 | <>
27 |
28 |
33 | {item.icon}
34 | {item.label}
35 |
36 |
37 | {item.children && (
38 |
43 |
44 |
45 | )}
46 |
47 | >
48 | );
49 | };
50 |
51 | export default SidebarItem;
52 |
--------------------------------------------------------------------------------
/src/components/Sidebar/index.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import React, { useEffect, useRef, useState } from "react";
4 | import { usePathname } from "next/navigation";
5 | import Link from "next/link";
6 | import Image from "next/image";
7 | import SidebarItem from "@/components/Sidebar/SidebarItem";
8 | import ClickOutside from "@/components/ClickOutside";
9 | import useLocalStorage from "@/hooks/useLocalStorage";
10 | import {
11 | LayoutGrid,
12 | Atom,
13 | Network,
14 | Microscope,
15 | Settings,
16 | MessageSquareText,
17 | ChevronLeft,
18 | } from "lucide-react";
19 | interface SidebarProps {
20 | sidebarOpen: boolean;
21 | setSidebarOpen: (arg: boolean) => void;
22 | }
23 |
24 | const menuGroups = [
25 | {
26 | name: "",
27 | menuItems: [
28 | {
29 | icon: ,
30 | label: "Dashboard",
31 | route: "/",
32 | },
33 | {
34 | icon: ,
35 | label: "Molecules Bank",
36 | route: "/molecule-bank",
37 | },
38 | {
39 | icon: ,
40 | label: "Model",
41 | route: "/model",
42 | },
43 | {
44 | icon: ,
45 | label: "Research",
46 | route: "/research",
47 | },
48 | {
49 | icon: ,
50 | label: "Messages",
51 | route: "/message",
52 | },
53 | ],
54 | },
55 | {
56 | name: "OTHERS",
57 | menuItems: [
58 | {
59 | icon: ,
60 | label: "Settings",
61 | route: "/settings",
62 | },
63 | ],
64 | },
65 | ];
66 |
67 | const Sidebar = ({ sidebarOpen, setSidebarOpen }: SidebarProps) => {
68 | const pathname = usePathname();
69 | const [pageName, setPageName] = useLocalStorage("selectedMenu", "dashboard");
70 |
71 | return (
72 | setSidebarOpen(false)}>
73 |
126 |
127 | );
128 | };
129 |
130 | export default Sidebar;
131 |
--------------------------------------------------------------------------------
/src/components/common/Loader/index.tsx:
--------------------------------------------------------------------------------
1 | const Loader = () => {
2 | return (
3 |
6 | );
7 | };
8 |
9 | export default Loader;
10 |
--------------------------------------------------------------------------------
/src/components/dashboard/components/CTACard.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowRight } from "lucide-react";
2 | import React, { ReactNode } from "react";
3 |
4 | interface CTACardProps {
5 | title: string;
6 | subtitle: string;
7 | children: ReactNode;
8 | }
9 |
10 | const CTACard: React.FC = ({ title, subtitle, children }) => {
11 | return (
12 |
13 |
14 | {children}
15 |
16 |
17 |
18 |
19 |
20 | {title}
21 |
22 | {subtitle}
23 |
24 |
25 |
30 |
31 | );
32 | };
33 |
34 | export default CTACard;
35 |
--------------------------------------------------------------------------------
/src/components/dashboard/components/DashboardCardChat.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useUser } from "@/app/context/UserContext";
3 | import { ApexOptions } from "apexcharts";
4 | import Image from "next/image";
5 | import React from "react";
6 | import ReactApexChart from "react-apexcharts";
7 | const ChartThree: React.FC = () => {
8 | const user = useUser();
9 |
10 | return (
11 |
12 |
13 |
14 |
15 | Collaborate with team
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |

28 |
29 |
30 |
31 |
32 | {user.firstName}{" "}
33 |
34 |
35 | 11:46
36 |
37 |
38 |
39 | That's awesome. I think we are making a pretty good progress
40 |
41 |
42 | Delivered
43 |
44 |
45 |
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default ChartThree;
53 |
--------------------------------------------------------------------------------
/src/components/dashboard/components/DashboardCardMap.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import jsVectorMap from "jsvectormap";
3 | import "jsvectormap/dist/jsvectormap.css";
4 | import React, { useEffect } from "react";
5 | import "../../../js/us-aea-en";
6 | import "../../../js/world";
7 |
8 | const MapOne: React.FC = () => {
9 | useEffect(() => {
10 | const mapOne = new jsVectorMap({
11 | selector: "#mapOne",
12 | map: "world",
13 | zoomButtons: true,
14 |
15 | regionStyle: {
16 | initial: {
17 | fill: "#C8D0D8",
18 | },
19 | hover: {
20 | fillOpacity: 1,
21 | fill: "#3056D3",
22 | },
23 | },
24 | regionLabelStyle: {
25 | initial: {
26 | fontFamily: "Satoshi",
27 | fontWeight: "semibold",
28 | fill: "#fff",
29 | },
30 | hover: {
31 | cursor: "pointer",
32 | },
33 | },
34 |
35 | labels: {
36 | regions: {
37 | render(code: string) {
38 | return code.split("-")[1];
39 | },
40 | },
41 | },
42 | });
43 |
44 | return () => {
45 | const map = document.getElementById("mapOne");
46 | if (map) {
47 | map.innerHTML = "";
48 | }
49 | // mapOne.destroy();
50 | };
51 | }, []);
52 |
53 | return (
54 |
55 |
56 | All over the world
57 |
58 |
61 |
62 | );
63 | };
64 |
65 | export default MapOne;
66 |
--------------------------------------------------------------------------------
/src/components/dashboard/index.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import dynamic from "next/dynamic";
3 | import React from "react";
4 |
5 | import CTACard from "./components/CTACard";
6 | import { AtomIcon, MessageCircle, Network, SearchIcon } from "lucide-react";
7 |
8 | const DashboardCardMap = dynamic(
9 | () => import("@/components/Dashboard/components/DashboardCardMap"),
10 | {
11 | ssr: false,
12 | },
13 | );
14 |
15 | const DashboardCardChat = dynamic(
16 | () => import("@/components/Dashboard/components/DashboardCardChat"),
17 | {
18 | ssr: false,
19 | },
20 | );
21 |
22 | const Index: React.FC = () => {
23 | return (
24 | <>
25 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
39 |
40 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | >
54 | );
55 | };
56 |
57 | export default Index;
58 |
--------------------------------------------------------------------------------
/src/css/style.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
2 | @import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap");
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
7 | @layer base {
8 | body {
9 | @apply relative z-1 bg-whiten font-satoshi text-base font-normal text-body;
10 | }
11 | }
12 |
13 | @layer components {
14 | }
15 |
16 | @layer utilities {
17 | /* Chrome, Safari and Opera */
18 | .no-scrollbar::-webkit-scrollbar {
19 | display: none;
20 | }
21 |
22 | .no-scrollbar {
23 | -ms-overflow-style: none; /* IE and Edge */
24 | scrollbar-width: none; /* Firefox */
25 | }
26 |
27 | .chat-height {
28 | @apply h-[calc(100vh_-_8.125rem)] lg:h-[calc(100vh_-_5.625rem)];
29 | }
30 |
31 | .inbox-height {
32 | @apply h-[calc(100vh_-_8.125rem)] lg:h-[calc(100vh_-_5.625rem)];
33 | }
34 | }
35 |
36 | /* third-party libraries CSS */
37 |
38 | .tableCheckbox:checked ~ div span {
39 | @apply opacity-100;
40 | }
41 |
42 | .tableCheckbox:checked ~ div {
43 | @apply border-primary bg-primary;
44 | }
45 |
46 | .apexcharts-legend-text {
47 | @apply !text-body dark:!text-bodydark;
48 | }
49 |
50 | .apexcharts-text {
51 | @apply !fill-body dark:!fill-bodydark;
52 | }
53 |
54 | .apexcharts-xcrosshairs {
55 | @apply !fill-stroke dark:!fill-strokedark;
56 | }
57 |
58 | .apexcharts-gridline {
59 | @apply !stroke-stroke dark:!stroke-strokedark;
60 | }
61 |
62 | .apexcharts-series.apexcharts-pie-series path {
63 | @apply dark:!stroke-transparent;
64 | }
65 |
66 | .apexcharts-legend-series {
67 | @apply !inline-flex gap-1.5;
68 | }
69 |
70 | .apexcharts-tooltip.apexcharts-theme-light {
71 | @apply dark:!border-strokedark dark:!bg-boxdark;
72 | }
73 |
74 | .apexcharts-tooltip.apexcharts-theme-light .apexcharts-tooltip-title {
75 | @apply dark:!border-strokedark dark:!bg-meta-4;
76 | }
77 |
78 | .apexcharts-xaxistooltip,
79 | .apexcharts-yaxistooltip {
80 | @apply dark:!border-meta-4 dark:!bg-meta-4 dark:!text-bodydark1;
81 | }
82 |
83 | .apexcharts-xaxistooltip-bottom:after {
84 | @apply !border-b-gray dark:!border-b-meta-4;
85 | }
86 |
87 | .apexcharts-xaxistooltip-bottom:before {
88 | @apply !border-b-gray dark:!border-b-meta-4;
89 | }
90 |
91 | .apexcharts-xaxistooltip-bottom {
92 | @apply !rounded !border-none !bg-gray !text-xs !font-medium !text-black dark:!text-white;
93 | }
94 |
95 | .apexcharts-tooltip-series-group {
96 | @apply !pl-1.5;
97 | }
98 |
99 | .flatpickr-wrapper {
100 | @apply w-full;
101 | }
102 |
103 | .flatpickr-months .flatpickr-prev-month:hover svg,
104 | .flatpickr-months .flatpickr-next-month:hover svg {
105 | @apply !fill-primary;
106 | }
107 |
108 | .flatpickr-calendar.arrowTop:before {
109 | @apply dark:!border-b-boxdark;
110 | }
111 |
112 | .flatpickr-calendar.arrowTop:after {
113 | @apply dark:!border-b-boxdark;
114 | }
115 |
116 | .flatpickr-calendar {
117 | @apply !p-6 dark:!bg-boxdark dark:!text-bodydark dark:!shadow-8 2xsm:!w-auto;
118 | }
119 |
120 | .flatpickr-day {
121 | @apply dark:!text-bodydark dark:hover:!border-meta-4 dark:hover:!bg-meta-4;
122 | }
123 |
124 | .flatpickr-months .flatpickr-prev-month,
125 | .flatpickr-months .flatpickr-next-month {
126 | @apply !top-7 dark:!fill-white dark:!text-white;
127 | }
128 |
129 | .flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,
130 | .flatpickr-months .flatpickr-next-month.flatpickr-prev-month {
131 | @apply !left-7;
132 | }
133 |
134 | .flatpickr-months .flatpickr-prev-month.flatpickr-next-month,
135 | .flatpickr-months .flatpickr-next-month.flatpickr-next-month {
136 | @apply !right-7;
137 | }
138 |
139 | span.flatpickr-weekday,
140 | .flatpickr-months .flatpickr-month {
141 | @apply dark:!fill-white dark:!text-white;
142 | }
143 |
144 | .flatpickr-day.inRange {
145 | box-shadow:
146 | -5px 0 0 #f3f4f6,
147 | 5px 0 0 #f3f4f6 !important;
148 | @apply dark:!shadow-7;
149 | }
150 |
151 | .flatpickr-day.inRange,
152 | .flatpickr-day.prevMonthDay.inRange,
153 | .flatpickr-day.nextMonthDay.inRange,
154 | .flatpickr-day.today.inRange,
155 | .flatpickr-day.prevMonthDay.today.inRange,
156 | .flatpickr-day.nextMonthDay.today.inRange,
157 | .flatpickr-day:hover,
158 | .flatpickr-day.prevMonthDay:hover,
159 | .flatpickr-day.nextMonthDay:hover,
160 | .flatpickr-day:focus,
161 | .flatpickr-day.prevMonthDay:focus,
162 | .flatpickr-day.nextMonthDay:focus {
163 | @apply !border-[#F3F4F6] !bg-[#F3F4F6] dark:!border-meta-4 dark:!bg-meta-4;
164 | }
165 |
166 | .flatpickr-day.selected,
167 | .flatpickr-day.startRange,
168 | .flatpickr-day.selected,
169 | .flatpickr-day.endRange {
170 | @apply dark:!text-white;
171 | }
172 |
173 | .flatpickr-day.selected,
174 | .flatpickr-day.startRange,
175 | .flatpickr-day.endRange,
176 | .flatpickr-day.selected.inRange,
177 | .flatpickr-day.startRange.inRange,
178 | .flatpickr-day.endRange.inRange,
179 | .flatpickr-day.selected:focus,
180 | .flatpickr-day.startRange:focus,
181 | .flatpickr-day.endRange:focus,
182 | .flatpickr-day.selected:hover,
183 | .flatpickr-day.startRange:hover,
184 | .flatpickr-day.endRange:hover,
185 | .flatpickr-day.selected.prevMonthDay,
186 | .flatpickr-day.startRange.prevMonthDay,
187 | .flatpickr-day.endRange.prevMonthDay,
188 | .flatpickr-day.selected.nextMonthDay,
189 | .flatpickr-day.startRange.nextMonthDay,
190 | .flatpickr-day.endRange.nextMonthDay {
191 | background: #3c50e0;
192 | @apply !border-primary !bg-primary hover:!border-primary hover:!bg-primary;
193 | }
194 |
195 | .flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n + 1)),
196 | .flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n + 1)),
197 | .flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
198 | box-shadow: -10px 0 0 #3c50e0;
199 | }
200 |
201 | .map-btn .jvm-zoom-btn {
202 | @apply flex h-7.5 w-7.5 items-center justify-center rounded border border-stroke bg-white px-0 pb-0.5 pt-0 text-2xl leading-none text-body hover:border-primary hover:bg-primary hover:text-white dark:border-strokedark dark:bg-meta-4 dark:text-bodydark dark:hover:border-primary dark:hover:bg-primary dark:hover:text-white;
203 | }
204 |
205 | .mapOne .jvm-zoom-btn {
206 | @apply !bottom-0 !left-auto !top-auto;
207 | }
208 |
209 | .mapOne .jvm-zoom-btn.jvm-zoomin {
210 | @apply !right-10;
211 | }
212 |
213 | .mapOne .jvm-zoom-btn.jvm-zoomout {
214 | @apply !right-0;
215 | }
216 |
217 | .taskCheckbox:checked ~ .box span {
218 | @apply opacity-100;
219 | }
220 |
221 | .taskCheckbox:checked ~ p {
222 | @apply line-through;
223 | }
224 |
225 | .taskCheckbox:checked ~ .box {
226 | @apply border-primary bg-primary dark:border-primary;
227 | }
228 |
229 | .custom-input-date::-webkit-calendar-picker-indicator {
230 | background: transparent;
231 | }
232 |
233 | input[type="search"]::-webkit-search-cancel-button {
234 | @apply appearance-none;
235 | }
236 |
237 | .custom-input-date::-webkit-calendar-picker-indicator {
238 | background-position: center;
239 | background-repeat: no-repeat;
240 | background-size: 20px;
241 | }
242 |
243 | [x-cloak] {
244 | display: none !important;
245 | }
246 |
--------------------------------------------------------------------------------
/src/hooks/useColorMode.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import useLocalStorage from "./useLocalStorage";
3 |
4 | const useColorMode = () => {
5 | const [colorMode, setColorMode] = useLocalStorage("color-theme", "light");
6 |
7 | useEffect(() => {
8 | const className = "dark";
9 | const bodyClass = window.document.body.classList;
10 |
11 | colorMode === "dark"
12 | ? bodyClass.add(className)
13 | : bodyClass.remove(className);
14 | }, [colorMode]);
15 |
16 | return [colorMode, setColorMode];
17 | };
18 |
19 | export default useColorMode;
20 |
--------------------------------------------------------------------------------
/src/hooks/useLocalStorage.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useEffect, useState } from "react";
3 |
4 | type SetValue = T | ((val: T) => T);
5 |
6 | function useLocalStorage(
7 | key: string,
8 | initialValue: T,
9 | ): [T, (value: SetValue) => void] {
10 | // State to store our value
11 | // Pass initial state function to useState so logic is only executed once
12 | const [storedValue, setStoredValue] = useState(() => {
13 | try {
14 | // Get from local storage by key
15 | if (typeof window !== "undefined") {
16 | // browser code
17 | const item = window.localStorage.getItem(key);
18 | // Parse stored json or if none return initialValue
19 | return item ? JSON.parse(item) : initialValue;
20 | }
21 | } catch (error) {
22 | // If error also return initialValue
23 | console.log(error);
24 | return initialValue;
25 | }
26 | });
27 |
28 | // useEffect to update local storage when the state changes
29 | useEffect(() => {
30 | try {
31 | // Allow value to be a function so we have same API as useState
32 | const valueToStore =
33 | typeof storedValue === "function"
34 | ? storedValue(storedValue)
35 | : storedValue;
36 | // Save state
37 | if (typeof window !== "undefined") {
38 | // browser code
39 | window.localStorage.setItem(key, JSON.stringify(valueToStore));
40 | }
41 | } catch (error) {
42 | // A more advanced implementation would handle the error case
43 | console.log(error);
44 | }
45 | }, [key, storedValue]);
46 |
47 | return [storedValue, setStoredValue];
48 | }
49 |
50 | export default useLocalStorage;
51 |
--------------------------------------------------------------------------------
/src/lib/actions/email.actions.ts:
--------------------------------------------------------------------------------
1 | export async function sendVerificationEmail(
2 | email: string,
3 | firstName: string,
4 | verificationUrl: string,
5 | ) {
6 | const response = await fetch(
7 | `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/send-verification-email`,
8 | {
9 | method: "POST",
10 | headers: {
11 | "Content-Type": "application/json",
12 | },
13 | body: JSON.stringify({ email, firstName, verificationUrl }),
14 | },
15 | );
16 |
17 | const data = await response.json();
18 |
19 | if (!response.ok) {
20 | throw new Error(data.error || "Failed to send verification email");
21 | }
22 |
23 | return data;
24 | }
25 |
26 | export async function sendResetPasswordEmail(
27 | email: string,
28 | firstName: string,
29 | resetUrl: string,
30 | ) {
31 | const response = await fetch(
32 | `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/send-reset-password-email`,
33 | {
34 | method: "POST",
35 | headers: {
36 | "Content-Type": "application/json",
37 | },
38 | body: JSON.stringify({ email, firstName, resetUrl }),
39 | },
40 | );
41 |
42 | const data = await response.json();
43 |
44 | if (!response.ok) {
45 | throw new Error(data.error || "Failed to send reset password email");
46 | }
47 |
48 | return data;
49 | }
50 |
--------------------------------------------------------------------------------
/src/lib/actions/group.actions.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 | import mongoose from "mongoose";
3 | import Group from "../database/models/group.model";
4 | import { connectToDatabase } from "../database/mongoose";
5 | import { handleError } from "../utils";
6 |
7 | export async function createGroup(
8 | groupName: string,
9 | creatorId: string,
10 | memberIds: string[] = [],
11 | ) {
12 | try {
13 | await connectToDatabase();
14 |
15 | const members = Array.from(
16 | new Set([
17 | creatorId,
18 | ...memberIds.map((id) => new mongoose.Types.ObjectId(id)),
19 | ]),
20 | );
21 |
22 | const newGroup = await Group.create({
23 | name: groupName,
24 | createdBy: creatorId,
25 | members,
26 | });
27 |
28 | return JSON.parse(JSON.stringify(newGroup));
29 | } catch (error) {
30 | console.error("Error creating group:", error);
31 | handleError(error);
32 | }
33 | }
34 |
35 | export async function addMemberToGroup(groupId: string, userId: string) {
36 | try {
37 | await connectToDatabase();
38 |
39 | const group = await Group.findById(groupId);
40 | if (!group) throw new Error("Group not found");
41 |
42 | if (!group.members.includes(userId)) {
43 | group.members.push(userId);
44 | await group.save();
45 | }
46 |
47 | return JSON.parse(JSON.stringify(group));
48 | } catch (error) {
49 | console.error("Error adding member to group:", error);
50 | handleError(error);
51 | }
52 | }
53 |
54 | export async function addMessageToGroup(
55 | groupId: string,
56 | userId: string,
57 | text: string,
58 | ) {
59 | try {
60 | await connectToDatabase();
61 | const group = await Group.findById(groupId);
62 | if (!group) throw new Error("Group not found");
63 |
64 | const message = {
65 | sender: userId,
66 | text,
67 | };
68 |
69 | group.messages.push(message);
70 | await group.save();
71 |
72 | return JSON.parse(JSON.stringify(group));
73 | } catch (error) {
74 | console.error("Error adding message to group:", error);
75 | handleError(error);
76 | }
77 | }
78 |
79 | export async function getGroupById(groupId: string) {
80 | try {
81 | await connectToDatabase();
82 |
83 | const group = await Group.findById(groupId).populate(
84 | "members",
85 | "firstName lastName photo",
86 | );
87 | if (!group) throw new Error("Group not found");
88 |
89 | return JSON.parse(JSON.stringify(group));
90 | } catch (error) {
91 | console.error("Error retrieving group:", error);
92 | handleError(error);
93 | }
94 | }
95 |
96 | export async function getAllGroups() {
97 | try {
98 | await connectToDatabase();
99 |
100 | const groups = await Group.find().populate(
101 | "members",
102 | "firstName lastName photo",
103 | );
104 |
105 | return JSON.parse(JSON.stringify(groups));
106 | } catch (error) {
107 | console.error("Error retrieving groups:", error);
108 | handleError(error);
109 | }
110 | }
111 |
112 | export async function getGroupMessages(groupId: string) {
113 | try {
114 | await connectToDatabase();
115 |
116 | const group = await Group.findById(groupId).populate(
117 | "messages.sender",
118 | "firstName lastName photo",
119 | );
120 | if (!group) throw new Error("Group not found");
121 |
122 | return JSON.parse(JSON.stringify(group.messages));
123 | } catch (error) {
124 | console.error("Error retrieving group messages:", error);
125 | handleError(error);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/lib/actions/molecule-generation.action.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import { revalidatePath } from "next/cache";
4 | import MoleculeGenerationHistory from "../database/models/molecule-generation.model";
5 | import { connectToDatabase } from "../database/mongoose";
6 | import { handleError } from "../utils";
7 | import mongoose from "mongoose";
8 |
9 | export async function createMoleculeGenerationHistory(
10 | payload: MoleculeGenerationHistoryType,
11 | userId: string,
12 | ) {
13 | try {
14 | await connectToDatabase();
15 |
16 | const newHistoryEntry = await MoleculeGenerationHistory.create({
17 | ...payload,
18 | user: new mongoose.Types.ObjectId(userId),
19 | });
20 |
21 | return JSON.parse(JSON.stringify(newHistoryEntry));
22 | } catch (error) {
23 | console.error("Error creating history entry:", error);
24 | handleError(error);
25 | }
26 | }
27 |
28 | export async function getMoleculeGenerationHistoryByUser(userId: string) {
29 | try {
30 | await connectToDatabase();
31 |
32 | const historyEntries = await MoleculeGenerationHistory.find({
33 | user: userId,
34 | }).sort({ createdAt: -1 });
35 |
36 | return JSON.parse(JSON.stringify(historyEntries));
37 | } catch (error) {
38 | console.error("Error retrieving history entries:", error);
39 | handleError(error);
40 | }
41 | }
42 |
43 | export async function getMoleculeGenerationHistoryById(historyId: string) {
44 | try {
45 | await connectToDatabase();
46 |
47 | const historyEntry = await MoleculeGenerationHistory.findById(historyId);
48 | if (!historyEntry) throw new Error("History entry not found");
49 |
50 | return JSON.parse(JSON.stringify(historyEntry));
51 | } catch (error) {
52 | console.error("Error retrieving history entry by ID:", error);
53 | handleError(error);
54 | }
55 | }
56 |
57 | export async function deleteMoleculeGenerationHistory(entryId: string) {
58 | try {
59 | await connectToDatabase();
60 |
61 | const deletedEntry =
62 | await MoleculeGenerationHistory.findByIdAndDelete(entryId);
63 |
64 | return JSON.parse(JSON.stringify(deletedEntry));
65 | } catch (error) {
66 | console.error("Error deleting history entry:", error);
67 | handleError(error);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/lib/actions/user.actions.ts:
--------------------------------------------------------------------------------
1 | "use server";
2 |
3 | import { revalidatePath } from "next/cache";
4 | import bcrypt from "bcrypt";
5 | import User from "../database/models/user.model";
6 | import { connectToDatabase } from "../database/mongoose";
7 | import { handleError } from "../utils";
8 | import { sendVerificationEmail, sendResetPasswordEmail } from "./email.actions";
9 |
10 | export async function createUser(user: CreateUserParams) {
11 | try {
12 | await connectToDatabase();
13 |
14 | const existingUser = await User.findOne({ email: user.email });
15 | if (existingUser) {
16 | throw new Error("User already exists with this email");
17 | }
18 |
19 | const salt = await bcrypt.genSalt(10);
20 | const hashedPassword = await bcrypt.hash(user.password, salt);
21 |
22 | const newUser = await User.create({
23 | ...user,
24 | password: hashedPassword,
25 | userBio: user.userBio || "",
26 | });
27 |
28 | const verificationUrl = `${process.env.NEXT_PUBLIC_API_BASE_URL}/verify-email?token=${newUser._id}`;
29 | await sendVerificationEmail(
30 | newUser.email,
31 | newUser.firstName || "User",
32 | verificationUrl,
33 | );
34 |
35 | return JSON.parse(JSON.stringify(newUser));
36 | } catch (error: any) {
37 | console.log(error);
38 | handleError(error);
39 | throw new Error(
40 | error.message || "An error occurred during user registration",
41 | );
42 | }
43 | }
44 |
45 | export async function loginUser(email: string, password: string) {
46 | try {
47 | await connectToDatabase();
48 |
49 | const user = await User.findOne({ email });
50 | if (!user) throw new Error("Invalid credentials");
51 |
52 | const isMatch = await bcrypt.compare(password, user.password);
53 | if (!isMatch) throw new Error("Invalid credentials");
54 |
55 | return JSON.parse(JSON.stringify(user));
56 | } catch (error) {
57 | handleError(error);
58 | }
59 | }
60 |
61 | export async function verifyEmail(token: string) {
62 | try {
63 | await connectToDatabase();
64 |
65 | const user = await User.findById(token);
66 | if (!user) throw new Error("Invalid token or user not found");
67 |
68 | user.isEmailVerified = true;
69 | await user.save();
70 |
71 | return JSON.parse(JSON.stringify(user));
72 | } catch (error) {
73 | handleError(error);
74 | }
75 | }
76 |
77 | export async function requestPasswordReset(email: string) {
78 | try {
79 | await connectToDatabase();
80 |
81 | const user = await User.findOne({ email });
82 | if (!user) throw new Error("User not found");
83 | const resetUrl = `${process.env.NEXT_PUBLIC_API_BASE_URL}/reset-password?token=${user._id}`;
84 | await sendResetPasswordEmail(
85 | user.email,
86 | user.firstName || "User",
87 | resetUrl,
88 | );
89 |
90 | return true;
91 | } catch (error) {
92 | console.error(error);
93 | throw error;
94 | }
95 | }
96 |
97 | export async function resetPassword(token: string, newPassword: string) {
98 | try {
99 | await connectToDatabase();
100 |
101 | const user = await User.findById(token);
102 | if (!user) throw new Error("Invalid token or user not found");
103 |
104 | const salt = await bcrypt.genSalt(10);
105 | const hashedPassword = await bcrypt.hash(newPassword, salt);
106 |
107 | user.password = hashedPassword;
108 | await user.save();
109 |
110 | return JSON.parse(JSON.stringify(user));
111 | } catch (error) {
112 | handleError(error);
113 | }
114 | }
115 |
116 | export async function getUserById(userId: string) {
117 | try {
118 | await connectToDatabase();
119 | const user = await User.findOne({ Id: userId });
120 | if (!user) throw new Error("User not found");
121 | return JSON.parse(JSON.stringify(user));
122 | } catch (error) {
123 | handleError(error);
124 | }
125 | }
126 |
127 | export async function updateUser(Id: string, user: UpdateUserParams) {
128 | try {
129 | await connectToDatabase();
130 | const updatedUser = await User.findOneAndUpdate({ _id: Id }, user, {
131 | new: true,
132 | });
133 | if (!updatedUser) throw new Error("User update failed");
134 | return JSON.parse(JSON.stringify(updatedUser));
135 | } catch (error) {
136 | handleError(error);
137 | }
138 | }
139 |
140 | export async function deleteUser(Id: string) {
141 | try {
142 | await connectToDatabase();
143 |
144 | const userToDelete = await User.findOne({ Id });
145 | if (!userToDelete) {
146 | throw new Error("User not found");
147 | }
148 |
149 | const deletedUser = await User.findByIdAndDelete(userToDelete._id);
150 | revalidatePath("/");
151 |
152 | return deletedUser ? JSON.parse(JSON.stringify(deletedUser)) : null;
153 | } catch (error) {
154 | handleError(error);
155 | }
156 | }
157 |
158 | export async function updateCredits(userId: string, creditFee: number) {
159 | try {
160 | await connectToDatabase();
161 |
162 | const updatedUserCredits = await User.findOneAndUpdate(
163 | { _id: userId },
164 | { $inc: { creditBalance: creditFee } },
165 | { new: true },
166 | );
167 |
168 | if (!updatedUserCredits) throw new Error("User credits update failed");
169 |
170 | return JSON.parse(JSON.stringify(updatedUserCredits));
171 | } catch (error) {
172 | handleError(error);
173 | }
174 | }
175 |
176 | export async function getUserByEmail(email: string) {
177 | try {
178 | await connectToDatabase();
179 | const user = await User.findOne({ email });
180 | if (!user) throw new Error("User not found");
181 |
182 | return JSON.parse(JSON.stringify(user));
183 | } catch (error) {
184 | handleError(error);
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/lib/database/models/group.model.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const MessageSchema = new Schema({
4 | sender: {
5 | type: Schema.Types.ObjectId,
6 | ref: "User",
7 | required: true,
8 | },
9 | text: {
10 | type: String,
11 | required: true,
12 | },
13 | timestamp: {
14 | type: Date,
15 | default: Date.now,
16 | },
17 | });
18 |
19 | const GroupSchema = new Schema({
20 | createdAt: {
21 | type: Date,
22 | default: Date.now,
23 | },
24 | name: {
25 | type: String,
26 | required: true,
27 | unique: true,
28 | },
29 | createdBy: {
30 | type: Schema.Types.ObjectId,
31 | ref: "User",
32 | required: true,
33 | },
34 | members: [
35 | {
36 | type: Schema.Types.ObjectId,
37 | ref: "User",
38 | },
39 | ],
40 | messages: [MessageSchema],
41 | });
42 |
43 | const Group = models?.Group || model("Group", GroupSchema);
44 |
45 | export default Group;
46 |
--------------------------------------------------------------------------------
/src/lib/database/models/molecule-generation.model.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const MoleculeGenerationHistorySchema = new Schema(
4 | {
5 | smiles: {
6 | type: String,
7 | required: true,
8 | },
9 | numMolecules: {
10 | type: Number,
11 | required: true,
12 | },
13 | minSimilarity: {
14 | type: Number,
15 | required: true,
16 | },
17 | particles: {
18 | type: Number,
19 | required: true,
20 | },
21 | iterations: {
22 | type: Number,
23 | required: true,
24 | },
25 | generatedMolecules: [
26 | {
27 | structure: { type: String, required: true },
28 | score: { type: Number, required: true },
29 | },
30 | ],
31 | user: {
32 | type: Schema.Types.ObjectId,
33 | ref: "User", // Reference to the user model
34 | required: true,
35 | },
36 | createdAt: {
37 | type: Date,
38 | default: Date.now,
39 | },
40 | },
41 | { timestamps: true },
42 | );
43 |
44 | const MoleculeGenerationHistory =
45 | models.MoleculeGenerationHistory ||
46 | model("MoleculeGenerationHistory", MoleculeGenerationHistorySchema);
47 |
48 | export default MoleculeGenerationHistory;
49 |
--------------------------------------------------------------------------------
/src/lib/database/models/user.model.ts:
--------------------------------------------------------------------------------
1 | import { Schema, model, models } from "mongoose";
2 |
3 | const UserSchema = new Schema(
4 | {
5 | email: {
6 | type: String,
7 | required: true,
8 | unique: true,
9 | },
10 | fullname: {
11 | type: String,
12 | unique: false,
13 | required: false,
14 | },
15 | photo: {
16 | type: String,
17 | required: true,
18 | },
19 | firstName: { type: String },
20 | lastName: {
21 | type: String,
22 | },
23 |
24 | password: {
25 | type: String,
26 | required: true,
27 | },
28 | isEmailVerified: {
29 | type: Boolean,
30 | default: false,
31 | },
32 | userBio: {
33 | type: String,
34 | default: "",
35 | },
36 | verificationToken: String,
37 | verificationExpires: Date,
38 | resetPasswordToken: String,
39 | resetPasswordExpires: Date,
40 | },
41 | {
42 | timestamps: true,
43 | },
44 | );
45 |
46 | const User = models?.User || model("User", UserSchema);
47 |
48 | export default User;
49 |
--------------------------------------------------------------------------------
/src/lib/database/mongoose.ts:
--------------------------------------------------------------------------------
1 | import mongoose, { Mongoose } from "mongoose";
2 |
3 | const MONGODB_URL = process.env.MONGODB_URL;
4 |
5 | console.log(MONGODB_URL);
6 |
7 | interface MongooseConnection {
8 | conn: Mongoose | null;
9 | promise: Promise | null;
10 | }
11 |
12 | let cached: MongooseConnection = (global as any).mongoose;
13 |
14 | if (!cached) {
15 | cached = (global as any).mongoose = {
16 | conn: null,
17 | promise: null,
18 | };
19 | }
20 |
21 | export const connectToDatabase = async () => {
22 | if (cached.conn) return cached.conn;
23 |
24 | if (!MONGODB_URL) throw new Error("Missing MONGODB_URL");
25 |
26 | cached.promise =
27 | cached.promise ||
28 | mongoose.connect(MONGODB_URL, {
29 | dbName: "proteinBind",
30 | bufferCommands: false,
31 | });
32 |
33 | cached.conn = await cached.promise;
34 |
35 | return cached.conn;
36 | };
37 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | export const handleError = (error: unknown) => {
2 | if (error instanceof Error) {
3 | console.error(error.message);
4 | throw new Error(`Error: ${error.message}`);
5 | } else if (typeof error === "string") {
6 | console.error(error);
7 | throw new Error(`Error: ${error}`);
8 | } else {
9 | console.error(error);
10 | throw new Error(`Unknown error: ${JSON.stringify(error)}`);
11 | }
12 | };
13 |
14 | export const resizeBase64Img = (
15 | base64Str: any,
16 | maxWidth = 100,
17 | maxHeight = 100,
18 | ) => {
19 | return new Promise((resolve) => {
20 | const img = new Image();
21 | img.src = base64Str;
22 |
23 | img.onload = () => {
24 | const canvas = document.createElement("canvas");
25 | let width = img.width;
26 | let height = img.height;
27 |
28 | if (width > height) {
29 | if (width > maxWidth) {
30 | height = Math.round((height * maxWidth) / width);
31 | width = maxWidth;
32 | }
33 | } else {
34 | if (height > maxHeight) {
35 | width = Math.round((width * maxHeight) / height);
36 | height = maxHeight;
37 | }
38 | }
39 |
40 | canvas.width = width;
41 | canvas.height = height;
42 |
43 | const ctx = canvas.getContext("2d") as any;
44 | ctx.drawImage(img, 0, 0, width, height);
45 |
46 | const newBase64Str = canvas.toDataURL("image/jpeg", 0.7);
47 | resolve(newBase64Str);
48 | };
49 | });
50 | };
51 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare type MoleculeStructure = {
2 | structure: string;
3 | score: number;
4 | };
5 |
6 | declare type MoleculeGenerationHistoryType = {
7 | _id?: string;
8 | smiles: string;
9 | numMolecules: number;
10 | minSimilarity: number;
11 | particles: number;
12 | iterations: number;
13 | generatedMolecules: MoleculeStructure[];
14 | createdAt?: Date;
15 | };
16 |
17 | declare type CreateUserParams = {
18 | email: string;
19 | fullname?: string;
20 | password: string;
21 | photo: string;
22 | firstName?: string;
23 | lastName?: string;
24 | userBio?: string;
25 | isEmailVerified?: boolean;
26 | };
27 |
28 | declare type UpdateUserParams = {
29 | firstName: string;
30 | lastName: string;
31 | photo: string;
32 | userBio?: string;
33 | email: string;
34 | };
35 |
36 | declare type CompoundData = {
37 | MolecularFormula: string;
38 | MolecularWeight: string;
39 | InChIKey: string;
40 | CanonicalSMILES: string;
41 | IsomericSMILES: string;
42 | IUPACName: string;
43 | XLogP: string;
44 | ExactMass: string;
45 | MonoisotopicMass: string;
46 | TPSA: string;
47 | Complexity: string;
48 | Charge: string;
49 | HBondDonorCount: string;
50 | HBondAcceptorCount: string;
51 | RotatableBondCount: string;
52 | HeavyAtomCount: string;
53 | };
54 |
55 | declare type ModalProps = {
56 | id: string;
57 | title: string;
58 | content: React.ReactNode;
59 | onCloseText: string;
60 | };
61 |
--------------------------------------------------------------------------------
/tailadmin-nextjs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mendsalbert/ProteinBind/654560d8d17542170ae40170254650993859736e/tailadmin-nextjs.jpg
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 | import defaultTheme from "tailwindcss/defaultTheme";
3 |
4 | const config: Config = {
5 | content: [
6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
9 | ],
10 | darkMode: "class",
11 | theme: {
12 | fontFamily: {
13 | satoshi: ["Satoshi", "sans-serif"],
14 | inter: ["Inter", "sans-serif"],
15 | poppins: ["Poppins"],
16 | },
17 | screens: {
18 | "2xsm": "375px",
19 | xsm: "425px",
20 | "3xl": "2000px",
21 | ...defaultTheme.screens,
22 | },
23 | extend: {
24 | colors: {
25 | current: "currentColor",
26 | transparent: "transparent",
27 | white: "#FFFFFF",
28 | black: "#1C2434",
29 | red: "#FB5454",
30 | "black-2": "#010101",
31 | body: "#64748B",
32 | bodydark: "#AEB7C0",
33 | bodydark1: "#DEE4EE",
34 | bodydark2: "#8A99AF",
35 | primary: "#3C50E0",
36 | secondary: "#80CAEE",
37 | stroke: "#E2E8F0",
38 | gray: "#EFF4FB",
39 | graydark: "#333A48",
40 | "gray-2": "#F7F9FC",
41 | "gray-3": "#FAFAFA",
42 | whiten: "#F1F5F9",
43 | whiter: "#F5F7FD",
44 | boxdark: "#24303F",
45 | "boxdark-2": "#1A222C",
46 | strokedark: "#2E3A47",
47 | "form-strokedark": "#3d4d60",
48 | "form-input": "#1d2a39",
49 | "meta-1": "#DC3545",
50 | "meta-2": "#EFF2F7",
51 | "meta-3": "#10B981",
52 | "meta-4": "#313D4A",
53 | "meta-5": "#259AE6",
54 | "meta-6": "#FFBA00",
55 | "meta-7": "#FF6766",
56 | "meta-8": "#F0950C",
57 | "meta-9": "#E5E7EB",
58 | "meta-10": "#0FADCF",
59 | success: "#219653",
60 | danger: "#D34053",
61 | warning: "#FFA70B",
62 | },
63 | fontSize: {
64 | "title-xxl": ["44px", "55px"],
65 | "title-xxl2": ["42px", "58px"],
66 | "title-xl": ["36px", "45px"],
67 | "title-xl2": ["33px", "45px"],
68 | "title-lg": ["28px", "35px"],
69 | "title-md": ["24px", "30px"],
70 | "title-md2": ["26px", "30px"],
71 | "title-sm": ["20px", "26px"],
72 | "title-sm2": ["22px", "28px"],
73 | "title-xsm": ["18px", "24px"],
74 | },
75 | spacing: {
76 | 4.5: "1.125rem",
77 | 5.5: "1.375rem",
78 | 6.5: "1.625rem",
79 | 7.5: "1.875rem",
80 | 8.5: "2.125rem",
81 | 9.5: "2.375rem",
82 | 10.5: "2.625rem",
83 | 11: "2.75rem",
84 | 11.5: "2.875rem",
85 | 12.5: "3.125rem",
86 | 13: "3.25rem",
87 | 13.5: "3.375rem",
88 | 14: "3.5rem",
89 | 14.5: "3.625rem",
90 | 15: "3.75rem",
91 | 15.5: "3.875rem",
92 | 16: "4rem",
93 | 16.5: "4.125rem",
94 | 17: "4.25rem",
95 | 17.5: "4.375rem",
96 | 18: "4.5rem",
97 | 18.5: "4.625rem",
98 | 19: "4.75rem",
99 | 19.5: "4.875rem",
100 | 21: "5.25rem",
101 | 21.5: "5.375rem",
102 | 22: "5.5rem",
103 | 22.5: "5.625rem",
104 | 24.5: "6.125rem",
105 | 25: "6.25rem",
106 | 25.5: "6.375rem",
107 | 26: "6.5rem",
108 | 27: "6.75rem",
109 | 27.5: "6.875rem",
110 | 29: "7.25rem",
111 | 29.5: "7.375rem",
112 | 30: "7.5rem",
113 | 31: "7.75rem",
114 | 32.5: "8.125rem",
115 | 33: "8.25rem",
116 | 34: "8.5rem",
117 | 34.5: "8.625rem",
118 | 35: "8.75rem",
119 | 36.5: "9.125rem",
120 | 37.5: "9.375rem",
121 | 39: "9.75rem",
122 | 39.5: "9.875rem",
123 | 40: "10rem",
124 | 42.5: "10.625rem",
125 | 44: "11rem",
126 | 45: "11.25rem",
127 | 46: "11.5rem",
128 | 47.5: "11.875rem",
129 | 49: "12.25rem",
130 | 50: "12.5rem",
131 | 52: "13rem",
132 | 52.5: "13.125rem",
133 | 54: "13.5rem",
134 | 54.5: "13.625rem",
135 | 55: "13.75rem",
136 | 55.5: "13.875rem",
137 | 59: "14.75rem",
138 | 60: "15rem",
139 | 62.5: "15.625rem",
140 | 65: "16.25rem",
141 | 67: "16.75rem",
142 | 67.5: "16.875rem",
143 | 70: "17.5rem",
144 | 72.5: "18.125rem",
145 | 73: "18.25rem",
146 | 75: "18.75rem",
147 | 90: "22.5rem",
148 | 94: "23.5rem",
149 | 95: "23.75rem",
150 | 100: "25rem",
151 | 115: "28.75rem",
152 | 125: "31.25rem",
153 | 132.5: "33.125rem",
154 | 150: "37.5rem",
155 | 171.5: "42.875rem",
156 | 180: "45rem",
157 | 187.5: "46.875rem",
158 | 203: "50.75rem",
159 | 230: "57.5rem",
160 | 242.5: "60.625rem",
161 | },
162 | maxWidth: {
163 | 2.5: "0.625rem",
164 | 3: "0.75rem",
165 | 4: "1rem",
166 | 7: "1.75rem",
167 | 9: "2.25rem",
168 | 10: "2.5rem",
169 | 10.5: "2.625rem",
170 | 11: "2.75rem",
171 | 13: "3.25rem",
172 | 14: "3.5rem",
173 | 15: "3.75rem",
174 | 16: "4rem",
175 | 22.5: "5.625rem",
176 | 25: "6.25rem",
177 | 30: "7.5rem",
178 | 34: "8.5rem",
179 | 35: "8.75rem",
180 | 40: "10rem",
181 | 42.5: "10.625rem",
182 | 44: "11rem",
183 | 45: "11.25rem",
184 | 60: "15rem",
185 | 70: "17.5rem",
186 | 90: "22.5rem",
187 | 94: "23.5rem",
188 | 125: "31.25rem",
189 | 132.5: "33.125rem",
190 | 142.5: "35.625rem",
191 | 150: "37.5rem",
192 | 180: "45rem",
193 | 203: "50.75rem",
194 | 230: "57.5rem",
195 | 242.5: "60.625rem",
196 | 270: "67.5rem",
197 | 280: "70rem",
198 | 292.5: "73.125rem",
199 | },
200 | maxHeight: {
201 | 35: "8.75rem",
202 | 70: "17.5rem",
203 | 90: "22.5rem",
204 | 550: "34.375rem",
205 | 300: "18.75rem",
206 | },
207 | minWidth: {
208 | 22.5: "5.625rem",
209 | 42.5: "10.625rem",
210 | 47.5: "11.875rem",
211 | 75: "18.75rem",
212 | },
213 | zIndex: {
214 | 999999: "999999",
215 | 99999: "99999",
216 | 9999: "9999",
217 | 999: "999",
218 | 99: "99",
219 | 9: "9",
220 | 1: "1",
221 | },
222 | opacity: {
223 | 65: ".65",
224 | },
225 | aspectRatio: {
226 | "4/3": "4 / 3",
227 | "21/9": "21 / 9",
228 | },
229 | backgroundImage: {
230 | video: "url('../images/video/video.png')",
231 | },
232 | content: {
233 | "icon-copy": 'url("../images/icon/icon-copy-alt.svg")',
234 | },
235 | transitionProperty: { width: "width", stroke: "stroke" },
236 | borderWidth: {
237 | 6: "6px",
238 | 10: "10px",
239 | 12: "12px",
240 | },
241 | boxShadow: {
242 | default: "0px 8px 13px -3px rgba(0, 0, 0, 0.07)",
243 | card: "0px 1px 3px rgba(0, 0, 0, 0.12)",
244 | "card-2": "0px 1px 2px rgba(0, 0, 0, 0.05)",
245 | switcher:
246 | "0px 2px 4px rgba(0, 0, 0, 0.2), inset 0px 2px 2px #FFFFFF, inset 0px -1px 1px rgba(0, 0, 0, 0.1)",
247 | "switch-1": "0px 0px 5px rgba(0, 0, 0, 0.15)",
248 | 1: "0px 1px 3px rgba(0, 0, 0, 0.08)",
249 | 2: "0px 1px 4px rgba(0, 0, 0, 0.12)",
250 | 3: "0px 1px 5px rgba(0, 0, 0, 0.14)",
251 | 4: "0px 4px 10px rgba(0, 0, 0, 0.12)",
252 | 5: "0px 1px 1px rgba(0, 0, 0, 0.15)",
253 | 6: "0px 3px 15px rgba(0, 0, 0, 0.1)",
254 | 7: "-5px 0 0 #313D4A, 5px 0 0 #313D4A",
255 | 8: "1px 0 0 #313D4A, -1px 0 0 #313D4A, 0 1px 0 #313D4A, 0 -1px 0 #313D4A, 0 3px 13px rgb(0 0 0 / 8%)",
256 | 9: "0px 2px 3px rgba(183, 183, 183, 0.5)",
257 | 10: "0px 1px 2px 0px rgba(0, 0, 0, 0.10)",
258 | 11: "0px 1px 3px 0px rgba(166, 175, 195, 0.40)",
259 | 12: "0px 0.5px 3px 0px rgba(0, 0, 0, 0.18)",
260 | 13: "0px 1px 3px 0px rgba(0, 0, 0, 0.08)",
261 | 14: "0px 2px 3px 0px rgba(0, 0, 0, 0.10)",
262 | },
263 | dropShadow: {
264 | 1: "0px 1px 0px #E2E8F0",
265 | 2: "0px 1px 4px rgba(0, 0, 0, 0.12)",
266 | 3: "0px 0px 4px rgba(0, 0, 0, 0.15)",
267 | 4: "0px 0px 2px rgba(0, 0, 0, 0.2)",
268 | 5: "0px 1px 5px rgba(0, 0, 0, 0.2)",
269 | },
270 | keyframes: {
271 | linspin: {
272 | "100%": { transform: "rotate(360deg)" },
273 | },
274 | easespin: {
275 | "12.5%": { transform: "rotate(135deg)" },
276 | "25%": { transform: "rotate(270deg)" },
277 | "37.5%": { transform: "rotate(405deg)" },
278 | "50%": { transform: "rotate(540deg)" },
279 | "62.5%": { transform: "rotate(675deg)" },
280 | "75%": { transform: "rotate(810deg)" },
281 | "87.5%": { transform: "rotate(945deg)" },
282 | "100%": { transform: "rotate(1080deg)" },
283 | },
284 | "left-spin": {
285 | "0%": { transform: "rotate(130deg)" },
286 | "50%": { transform: "rotate(-5deg)" },
287 | "100%": { transform: "rotate(130deg)" },
288 | },
289 | "right-spin": {
290 | "0%": { transform: "rotate(-130deg)" },
291 | "50%": { transform: "rotate(5deg)" },
292 | "100%": { transform: "rotate(-130deg)" },
293 | },
294 | rotating: {
295 | "0%, 100%": { transform: "rotate(360deg)" },
296 | "50%": { transform: "rotate(0deg)" },
297 | },
298 | topbottom: {
299 | "0%, 100%": { transform: "translate3d(0, -100%, 0)" },
300 | "50%": { transform: "translate3d(0, 0, 0)" },
301 | },
302 | bottomtop: {
303 | "0%, 100%": { transform: "translate3d(0, 0, 0)" },
304 | "50%": { transform: "translate3d(0, -100%, 0)" },
305 | },
306 | line: {
307 | "0%, 100%": { transform: "translateY(0)" },
308 | "50%": { transform: "translateY(100%)" },
309 | },
310 | "line-revert": {
311 | "0%, 100%": { transform: "translateY(100%)" },
312 | "50%": { transform: "translateY(0)" },
313 | },
314 | },
315 | animation: {
316 | linspin: "linspin 1568.2353ms linear infinite",
317 | easespin: "easespin 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both",
318 | "left-spin":
319 | "left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both",
320 | "right-spin":
321 | "right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both",
322 | "ping-once": "ping 5s cubic-bezier(0, 0, 0.2, 1)",
323 | rotating: "rotating 30s linear infinite",
324 | topbottom: "topbottom 60s infinite alternate linear",
325 | bottomtop: "bottomtop 60s infinite alternate linear",
326 | "spin-1.5": "spin 1.5s linear infinite",
327 | "spin-2": "spin 2s linear infinite",
328 | "spin-3": "spin 3s linear infinite",
329 | line1: "line 10s infinite linear",
330 | line2: "line-revert 8s infinite linear",
331 | line3: "line 7s infinite linear",
332 | },
333 | },
334 | },
335 | plugins: [require("daisyui")],
336 | };
337 | export default config;
338 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------