From dcb6d63539ed4cef37f21f57229eedfbd50f8d80 Mon Sep 17 00:00:00 2001 From: CodeWithInferno2618 Date: Sat, 9 Nov 2024 23:33:16 -0500 Subject: [PATCH 01/27] feat: Implement sidebar navigation and user profile section; add skeleton and tooltip components --- package-lock.json | 79 +++ package.json | 2 + src/app/api/Rules/get-rules/route.js | 30 + src/app/api/Rules/save-rule/route.js | 120 ++++ src/app/api/Rules/update-field/route.js | 36 + src/app/api/Rules/update-status/route.js | 82 +++ src/app/globals.css | 16 + src/app/rules/analytics/page.js | 16 + src/app/rules/bulk-unsubscribe/page.js | 16 + src/app/rules/cold-email-blocker/page.js | 16 + src/app/rules/components/ExamplesPanel.js | 33 + src/app/rules/components/PromptSection.js | 52 ++ src/app/rules/components/RulesTable.js | 511 +++++++++++++++ src/app/rules/components/TopNavigationTabs.js | 23 + .../rules/components/UserProfileSection.js | 22 + src/app/rules/components/sidebar.js | 66 ++ src/app/rules/page.js | 113 ++++ src/components/ui/input.jsx | 19 + src/components/ui/separator.jsx | 25 + src/components/ui/sheet.jsx | 108 +++ src/components/ui/sidebar.jsx | 619 ++++++++++++++++++ src/components/ui/skeleton.jsx | 14 + src/components/ui/tooltip.jsx | 28 + src/hooks/use-mobile.jsx | 19 + tailwind.config.js | 10 + 25 files changed, 2075 insertions(+) create mode 100644 src/app/api/Rules/get-rules/route.js create mode 100644 src/app/api/Rules/save-rule/route.js create mode 100644 src/app/api/Rules/update-field/route.js create mode 100644 src/app/api/Rules/update-status/route.js create mode 100644 src/app/rules/analytics/page.js create mode 100644 src/app/rules/bulk-unsubscribe/page.js create mode 100644 src/app/rules/cold-email-blocker/page.js create mode 100644 src/app/rules/components/ExamplesPanel.js create mode 100644 src/app/rules/components/PromptSection.js create mode 100644 src/app/rules/components/RulesTable.js create mode 100644 src/app/rules/components/TopNavigationTabs.js create mode 100644 src/app/rules/components/UserProfileSection.js create mode 100644 src/app/rules/components/sidebar.js create mode 100644 src/app/rules/page.js create mode 100644 src/components/ui/input.jsx create mode 100644 src/components/ui/separator.jsx create mode 100644 src/components/ui/sheet.jsx create mode 100644 src/components/ui/sidebar.jsx create mode 100644 src/components/ui/skeleton.jsx create mode 100644 src/components/ui/tooltip.jsx create mode 100644 src/hooks/use-mobile.jsx diff --git a/package-lock.json b/package-lock.json index 99b865d..5160e1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,9 @@ "@radix-ui/react-context-menu": "^2.2.2", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.3", "@sanity/image-url": "^1.0.2", "@sanity/vision": "^3.61.0", "@shadcn/ui": "^0.0.4", @@ -4889,6 +4891,28 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.0.tgz", + "integrity": "sha512-3uBAs+egzvJBDZAzvb/n4NxxOYpnspmWxO2u5NbZ8Y6FM/NdrGSF9bop3Cf6F6C71z1rTSn8KV0Fo2ZVd79lGA==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", @@ -4906,6 +4930,39 @@ } } }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz", + "integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", @@ -5002,6 +5059,28 @@ } } }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", + "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/rect": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", diff --git a/package.json b/package.json index 870ce87..728a277 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,9 @@ "@radix-ui/react-context-menu": "^2.2.2", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.3", "@sanity/image-url": "^1.0.2", "@sanity/vision": "^3.61.0", "@shadcn/ui": "^0.0.4", diff --git a/src/app/api/Rules/get-rules/route.js b/src/app/api/Rules/get-rules/route.js new file mode 100644 index 0000000..7c8e045 --- /dev/null +++ b/src/app/api/Rules/get-rules/route.js @@ -0,0 +1,30 @@ +import { connectToDatabase } from '@/lib/mongodb'; +import { getSession } from '@auth0/nextjs-auth0'; + +export async function GET(req) { + try { + const session = await getSession(req); + const user = session?.user; + + if (!user) { + return new Response(JSON.stringify({ message: 'User not authenticated' }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }); + } + + const db = await connectToDatabase(); + const rules = await db.collection('Rules').find({ userId: user.sub }).toArray(); + + return new Response(JSON.stringify({ rules }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.error('Error fetching rules:', error); + return new Response(JSON.stringify({ message: 'Internal server error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} diff --git a/src/app/api/Rules/save-rule/route.js b/src/app/api/Rules/save-rule/route.js new file mode 100644 index 0000000..0081442 --- /dev/null +++ b/src/app/api/Rules/save-rule/route.js @@ -0,0 +1,120 @@ +// import { connectToDatabase } from '@/lib/mongodb'; +// import { getSession } from '@auth0/nextjs-auth0'; + +// export async function POST(req) { +// try { +// // Get user session +// const session = await getSession(req); +// const user = session?.user; + +// if (!user) { +// return new Response(JSON.stringify({ message: 'User not authenticated' }), { +// status: 401, +// headers: { 'Content-Type': 'application/json' }, +// }); +// } + +// // Parse the request body +// const { promptText } = await req.json(); + +// // Validate input +// if (!promptText) { +// return new Response(JSON.stringify({ message: 'Prompt text is required' }), { +// status: 400, +// headers: { 'Content-Type': 'application/json' }, +// }); +// } + +// // Connect to the database +// const db = await connectToDatabase(); + +// // Rule object to insert +// const rule = { +// userId: user.sub, +// promptText, +// createdAt: new Date(), +// }; + +// // Insert rule into the 'Rules' collection +// const result = await db.collection('Rules').insertOne(rule); + +// return new Response(JSON.stringify({ message: 'Rule saved successfully', result }), { +// status: 200, +// headers: { 'Content-Type': 'application/json' }, +// }); +// } catch (error) { +// console.error('Error saving rule:', error); +// return new Response(JSON.stringify({ message: 'Internal server error' }), { +// status: 500, +// headers: { 'Content-Type': 'application/json' }, +// }); +// } +// } + + + + + + + + + + + + + + + + + +import { connectToDatabase } from '@/lib/mongodb'; +import { getSession } from '@auth0/nextjs-auth0'; + +export async function POST(req) { + try { + const session = await getSession(req); + const user = session?.user; + + if (!user) { + return new Response(JSON.stringify({ message: 'User not authenticated' }), { + status: 401, + headers: { 'Content-Type': 'application/json' }, + }); + } + + // Parse the request body + const { promptText, action, group, name, status } = await req.json(); + + // Split the promptText into individual lines + const prompts = promptText.trim().split('\n').filter(Boolean); + + // Connect to the database + const db = await connectToDatabase(); + + // Create a list of rule documents to insert + const rules = prompts.map((description) => ({ + userId: user.sub, + promptText: description, + description, // Store each line as its own description + action, + group, + name, + status: status || false, + createdAt: new Date(), + })); + + // Insert each prompt as a separate document + const result = await db.collection('Rules').insertMany(rules); + + return new Response(JSON.stringify({ message: 'Rules added successfully', result }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.error('Error adding rules:', error); + return new Response(JSON.stringify({ message: 'Internal server error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} diff --git a/src/app/api/Rules/update-field/route.js b/src/app/api/Rules/update-field/route.js new file mode 100644 index 0000000..d88b644 --- /dev/null +++ b/src/app/api/Rules/update-field/route.js @@ -0,0 +1,36 @@ +// /src/app/api/Rules/update-field.js +import { connectToDatabase } from '@/lib/mongodb'; +import { ObjectId } from 'mongodb'; + +export async function POST(req) { + try { + const { ruleId, field, value } = await req.json(); + + // Connect to the database + const db = await connectToDatabase(); + + // Perform the update + const result = await db.collection('Rules').updateOne( + { _id: new ObjectId(ruleId) }, + { $set: { [field]: value } } // Dynamically set the field + ); + + if (result.modifiedCount === 1) { + return new Response(JSON.stringify({ message: 'Field updated successfully' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } else { + return new Response(JSON.stringify({ message: 'Failed to update field' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } + } catch (error) { + console.error('Error updating field:', error); + return new Response(JSON.stringify({ message: 'Internal server error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} diff --git a/src/app/api/Rules/update-status/route.js b/src/app/api/Rules/update-status/route.js new file mode 100644 index 0000000..867858e --- /dev/null +++ b/src/app/api/Rules/update-status/route.js @@ -0,0 +1,82 @@ +// import { connectToDatabase } from '@/lib/mongodb'; +// import { getSession } from '@auth0/nextjs-auth0'; + +// export async function POST(req) { +// try { +// const session = await getSession(req); +// const user = session?.user; + +// if (!user) { +// return new Response(JSON.stringify({ message: 'User not authenticated' }), { +// status: 401, +// headers: { 'Content-Type': 'application/json' }, +// }); +// } + +// const { ruleId, enabled, threading } = await req.json(); + +// const db = await connectToDatabase(); +// const result = await db.collection('Rules').updateOne( +// { _id: ruleId, userId: user.sub }, +// { $set: { enabled, threading } } +// ); + +// return new Response(JSON.stringify({ message: 'Rule updated', result }), { +// status: 200, +// headers: { 'Content-Type': 'application/json' }, +// }); +// } catch (error) { +// console.error('Error updating rule:', error); +// return new Response(JSON.stringify({ message: 'Internal server error' }), { +// status: 500, +// headers: { 'Content-Type': 'application/json' }, +// }); +// } +// } + + + + + + + + + + + + + + +import { connectToDatabase } from '@/lib/mongodb'; +import { ObjectId } from 'mongodb'; + +export async function POST(req) { + try { + const { ruleId, status } = await req.json(); + + const db = await connectToDatabase(); + + const result = await db.collection('Rules').updateOne( + { _id: new ObjectId(ruleId) }, + { $set: { status } } + ); + + if (result.modifiedCount === 1) { + return new Response(JSON.stringify({ message: 'Rule status updated successfully' }), { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }); + } else { + return new Response(JSON.stringify({ message: 'Failed to update rule status' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } + } catch (error) { + console.error('Error updating rule status:', error); + return new Response(JSON.stringify({ message: 'Internal server error' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } +} diff --git a/src/app/globals.css b/src/app/globals.css index 0886c12..355e0ea 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -40,6 +40,14 @@ body { --chart-4: 43 74% 66%; --chart-5: 27 87% 67%; --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; } .dark { --background: 0 0% 3.9%; @@ -66,6 +74,14 @@ body { --chart-3: 30 80% 55%; --chart-4: 280 65% 60%; --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; } } diff --git a/src/app/rules/analytics/page.js b/src/app/rules/analytics/page.js new file mode 100644 index 0000000..8de1b1f --- /dev/null +++ b/src/app/rules/analytics/page.js @@ -0,0 +1,16 @@ +import React from 'react'; +import Sidebar from '../components/sidebar'; + +const RulesPage = () => { + return ( +
+ +
+

Rules & Features

+ {/* Your main content goes here */} +
+
+ ); +}; + +export default RulesPage; diff --git a/src/app/rules/bulk-unsubscribe/page.js b/src/app/rules/bulk-unsubscribe/page.js new file mode 100644 index 0000000..8de1b1f --- /dev/null +++ b/src/app/rules/bulk-unsubscribe/page.js @@ -0,0 +1,16 @@ +import React from 'react'; +import Sidebar from '../components/sidebar'; + +const RulesPage = () => { + return ( +
+ +
+

Rules & Features

+ {/* Your main content goes here */} +
+
+ ); +}; + +export default RulesPage; diff --git a/src/app/rules/cold-email-blocker/page.js b/src/app/rules/cold-email-blocker/page.js new file mode 100644 index 0000000..8de1b1f --- /dev/null +++ b/src/app/rules/cold-email-blocker/page.js @@ -0,0 +1,16 @@ +import React from 'react'; +import Sidebar from '../components/sidebar'; + +const RulesPage = () => { + return ( +
+ +
+

Rules & Features

+ {/* Your main content goes here */} +
+
+ ); +}; + +export default RulesPage; diff --git a/src/app/rules/components/ExamplesPanel.js b/src/app/rules/components/ExamplesPanel.js new file mode 100644 index 0000000..ec38207 --- /dev/null +++ b/src/app/rules/components/ExamplesPanel.js @@ -0,0 +1,33 @@ +import React from 'react'; + +const examples = [ + "Label newsletters as 'Newsletter' and archive them", + "Label marketing emails as 'Marketing' and archive them", + "Label emails that require a reply as 'Reply Required'", + "Label urgent emails as 'Urgent'", + "Label receipts as 'Receipt' and forward them to jane@accounting.com", + "Label pitch decks as 'Pitch Deck' and forward them to john@investing.com", + "Reply to cold emails by telling them to check out Inbox Zero. Then mark them as spam", + "Label high priority emails as 'High Priority'", +]; + +const ExamplesPanel = ({ onExampleClick }) => { + return ( +
+

Examples

+
+ {examples.map((example, index) => ( + + ))} +
+
+ ); +}; + +export default ExamplesPanel; diff --git a/src/app/rules/components/PromptSection.js b/src/app/rules/components/PromptSection.js new file mode 100644 index 0000000..ff8e6e7 --- /dev/null +++ b/src/app/rules/components/PromptSection.js @@ -0,0 +1,52 @@ +'use client'; +import React, { useState } from 'react'; + +const PromptSection = ({ promptText, setPromptText }) => { + const handleTextChange = (e) => { + setPromptText(e.target.value); + }; + + const handleSave = async () => { + try { + const response = await fetch('/api/Rules/save-rule', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ promptText }), + }); + + const data = await response.json(); + if (response.ok) { + alert('Rule saved successfully!'); + } else { + alert(`Failed to save rule: ${data.message}`); + } + } catch (error) { + console.error('Error saving rule:', error); + alert('An error occurred while saving the rule.'); + } + }; + + return ( +
+

How your AI personal assistant should handle your emails

+

Write a prompt for your assistant to follow.

+