Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code Cleanup and Query Abstraction #1

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

feliciachang
Copy link
Collaborator

No description provided.

Copy link

netlify bot commented Oct 25, 2024

Deploy Preview for y-sweet-supabase-demo ready!

Name Link
🔨 Latest commit ad34ae9
🔍 Latest deploy log https://app.netlify.com/sites/y-sweet-supabase-demo/deploys/671fce520f15f800089acada
😎 Deploy Preview https://deploy-preview-1--y-sweet-supabase-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

coderabbitai bot commented Oct 25, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces multiple changes across various components and utility functions in the codebase. Notably, the README.md file has been updated to include a new command for cloning the Y-Sweet Supabase Starter template, enhancing local setup instructions without altering existing content. In the DocumentPage component, the import of createClient has been removed, replaced with getDocMetadata, and the state management has been simplified by introducing an error state while eliminating the hasAccess state. The document fetching logic has been streamlined to prioritize error handling.

The DocumentHome component has had its document fetching logic removed entirely, simplifying its control flow. The CreateDoc component has been adjusted for clarity in inserting document permissions, while the DisplayDocs component underwent a minor formatting change. The EditableDocTitle component now utilizes a centralized function for updating titles, and the InviteByEmail component has shifted to a new user invitation process. The PermissionsToggle component has updated its props and internal logic to manage document visibility more effectively. Lastly, a new queries.tsx file has been introduced, containing several asynchronous functions for managing document permissions and metadata.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 720b1d2 and ad34ae9.

📒 Files selected for processing (1)
  • components/document/editable-doc-title.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/document/editable-doc-title.tsx

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (5)
components/document/editable-doc-title.tsx (1)

Line range hint 33-39: Consider adding aria-label for accessibility.

The Input component could benefit from an aria-label to improve accessibility for screen readers.

 <Input
   value={docMetadata?.name ?? ""}
   onChange={handleInputChange}
   className="text-2xl font-bold mb-4 w-fit"
   placeholder="Untitled Document"
+  aria-label="Document title"
 />
components/document/permissions-toggle.tsx (2)

7-11: Consider adding type safety for the metadata setter.

The interface changes look good, moving from a simple boolean to a more comprehensive metadata object improves component flexibility.

Consider making the setter type more specific:

-  setDocumentMetadata: (metadata: DocumentMetadata) => void;
+  setDocumentMetadata: React.Dispatch<React.SetStateAction<DocumentMetadata>>;

Line range hint 35-50: Improve accessibility of the toggle component.

The current implementation lacks proper accessibility support:

  1. Missing keyboard navigation
  2. Missing ARIA attributes
  3. Using div instead of proper button element

Consider these accessibility improvements:

-    <div className="flex items-center gap-4">
+    <div className="flex items-center gap-4" role="group" aria-labelledby="visibility-label">
-      <div>Make this document public</div>
+      <span id="visibility-label">Make this document public</span>
       <div className="flex items-center justify-center">
-        <div
-          onClick={() => handleToggle()}
+        <button
+          type="button"
+          onClick={handleToggle}
+          onKeyPress={(e) => e.key === 'Enter' && handleToggle()}
+          disabled={isLoading}
+          aria-pressed={isToggle}
+          aria-label={`Make document ${isToggle ? 'private' : 'public'}`}
           className={`w-16 h-8 flex items-center...`}
         >
           <div
             className={`bg-white w-6 h-6 rounded-full...`}
           ></div>
-        </div>
+        </button>
       </div>
     </div>
app/document/[id]/page.tsx (1)

28-28: Consider using a more specific error type.

While the error state is a good addition, consider using a more specific type than string to handle different error scenarios (e.g., not found, permission denied, etc.) in a type-safe manner.

-const [error, setError] = React.useState("");
+type DocError = {
+  type: 'not_found' | 'permission_denied' | 'fetch_error';
+  message: string;
+};
+const [error, setError] = React.useState<DocError | null>(null);
README.md (1)

60-60: LGTM! Consider clarifying the alternative nature of the commands.

The addition of an SSH-based git clone command improves accessibility for users who prefer SSH or don't have GitHub CLI installed.

Consider adding "or" between the two commands to make it clear they are alternatives:

   ```bash
   gh repo clone jamsocket/y-sweet-supabase-demo
  • or
  • git clone git@github.com:jamsocket/y-sweet-supabase-demo.git

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**

<details>
<summary>📥 Commits</summary>

Files that changed from the base of the PR and between ad5cb1f4ad56ed2da2b9254a081f15dcea62e169 and 6682e4c91a0567fb44b1fc7d8d5cc1214ed454c1.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* README.md (1 hunks)
* app/document/[id]/page.tsx (2 hunks)
* app/document/page.tsx (0 hunks)
* components/create-doc.tsx (1 hunks)
* components/display-docs.tsx (1 hunks)
* components/document/editable-doc-title.tsx (2 hunks)
* components/document/invite-by-email.tsx (3 hunks)
* components/document/permissions-toggle.tsx (1 hunks)
* utils/supabase/queries.tsx (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* app/document/page.tsx

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (2)</summary>

* components/create-doc.tsx
* components/display-docs.tsx

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>components/document/editable-doc-title.tsx (1)</summary><blockquote>

`5-5`: **LGTM! Good abstraction of database operations.**

The change to import `editDocTitle` from a centralized queries module improves code organization and maintainability.

</blockquote></details>
<details>
<summary>components/document/invite-by-email.tsx (2)</summary><blockquote>

`4-4`: **LGTM! Clean import replacement.**

The replacement of `createClient` with `addUserToDoc` improves code organization by abstracting database operations.

---

`4-4`: **Verify the implementation of addUserToDoc function.**

Let's ensure the imported function is properly implemented with correct error handling.




Also applies to: 22-22

<details>
<summary>✅ Verification successful</summary>

**Function implementation looks correct with proper error handling**

The `addUserToDoc` function is well-implemented with:
- Proper type checking for input parameters (newEmail: string, id: string)
- Error handling for user lookup failures
- Error handling for permission insertion failures
- Appropriate error messages and return values
- Database operations using Supabase client

The function follows a good pattern:
1. Looks up user by email
2. Handles user lookup errors
3. Inserts permission record
4. Handles permission insertion errors



</details>

<details>
<summary>🏁 Scripts executed</summary>

The following scripts were executed for the analysis:

Script:

```shell
#!/bin/bash
# Description: Verify the implementation of addUserToDoc function

# Check the implementation
echo "Checking addUserToDoc implementation..."
ast-grep --pattern 'export async function addUserToDoc($_: string, $_: string) {
  $$$
}'

# Check error handling
echo "Checking error handling..."
rg -A 5 "addUserToDoc.*catch"

Length of output: 1634

components/document/permissions-toggle.tsx (1)

4-5: LGTM! Clean abstraction of database queries.

The separation of database queries into a dedicated utility file improves code organization and maintainability.

utils/supabase/queries.tsx (1)

1-4: LGTM: Server setup is correct

The server-side setup with a single reusable Supabase client instance is appropriate.

app/document/[id]/page.tsx (2)

11-11: LGTM! Good abstraction of Supabase queries.

The change improves separation of concerns by moving database queries to a dedicated utility file.


81-82: Verify PermissionsToggle component implementation.

The prop changes look good, providing more context to the PermissionsToggle component. However, let's verify that the PermissionsToggle component correctly handles these new props.

✅ Verification successful

PermissionsToggle implementation correctly handles the new props

The component implementation in components/document/permissions-toggle.tsx properly uses the new documentMetadata and setDocumentMetadata props:

  • Correctly defines prop types in the PermissionsToggleProps interface
  • Initializes toggle state using documentMetadata.is_public
  • Updates document metadata via setDocumentMetadata when visibility changes
  • Maintains proper type safety with the DocumentMetadata type

The prop changes are well-implemented and working as intended.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify PermissionsToggle component implementation
# Test: Check if PermissionsToggle component correctly uses the new props

# Look for the PermissionsToggle component definition
ast-grep --pattern 'export default function PermissionsToggle({ documentMetadata, setDocumentMetadata }: { documentMetadata: $_; setDocumentMetadata: $_; }) {
  $$$
}'

Length of output: 169


Script:

#!/bin/bash
# Let's try a broader search for PermissionsToggle component
# First, find the file containing PermissionsToggle
fd "PermissionsToggle" --type f

# Then let's look at its implementation with ripgrep for better context
rg "PermissionsToggle" -A 20

Length of output: 4785

.from("docs") // Assuming your table name is "docs"
.update({ name: e.target.value }) // Update the name field
.eq("doc_id", docId); // Match the document by its id
await editDocTitle(docId, e.target.value);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix throttling implementation and add error handling.

The current implementation has several issues:

  1. The throttling logic is ineffective as inThrottle is recreated on every call
  2. Missing error handling for failed title updates

Consider this implementation:

 export default function EditableDocTitle(props: EditableDocTitleProps) {
   const { docId, setDocMetadata, docMetadata } = props;
+  const inThrottle = useRef(false);
+  const [error, setError] = useState<string | null>(null);
+
   const handleInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
     if (docMetadata) {
       setDocMetadata({
         ...docMetadata,
         name: e.target.value,
       });
     }
-    let inThrottle = false;
-    if (!inThrottle) {
-      inThrottle = true;
-      await editDocTitle(docId, e.target.value);
+    if (!inThrottle.current) {
+      inThrottle.current = true;
+      try {
+        await editDocTitle(docId, e.target.value);
+        setError(null);
+      } catch (err) {
+        setError('Failed to update title. Please try again.');
+        // Optionally revert to last known good state
+        if (docMetadata) {
+          setDocMetadata({
+            ...docMetadata,
+            name: docMetadata.name,
+          });
+        }
+      }
       setTimeout(() => {
-        inThrottle = false;
+        inThrottle.current = false;
       }, 1000);
     }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await editDocTitle(docId, e.target.value);
export default function EditableDocTitle(props: EditableDocTitleProps) {
const { docId, setDocMetadata, docMetadata } = props;
const inThrottle = useRef(false);
const [error, setError] = useState<string | null>(null);
const handleInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (docMetadata) {
setDocMetadata({
...docMetadata,
name: e.target.value,
});
}
if (!inThrottle.current) {
inThrottle.current = true;
try {
await editDocTitle(docId, e.target.value);
setError(null);
} catch (err) {
setError('Failed to update title. Please try again.');
// Optionally revert to last known good state
if (docMetadata) {
setDocMetadata({
...docMetadata,
name: docMetadata.name,
});
}
}
setTimeout(() => {
inThrottle.current = false;
}, 1000);
}
};

@@ -56,7 +44,7 @@ export default function InviteByEmail(props: InviteByEmailProps) {
className="w-full px-4 py-2 border border-gray-300 bg-white rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onClick={() => addPerson()}
onClick={() => inviteUser()}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance accessibility and form handling.

Consider wrapping the input and button in a form element to handle Enter key submissions and improve accessibility.

Here's how to improve it:

-<div>
+<form onSubmit={(e) => {
+  e.preventDefault();
+  inviteUser();
+}}>
   Add people by email
   <div className="flex items-center py-4">
     <input
       type="email"
+      aria-label="Email address for invitation"
       placeholder="invite-email@gmail.com"
       value={newEmail}
       onChange={(e) => setNewEmail(e.target.value)}
       className="w-full px-4 py-2 border border-gray-300 bg-white rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500"
     />
     <button
-      onClick={() => inviteUser()}
+      type="submit"
+      aria-label="Send invitation"
       className="px-4 py-2 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 focus:outline-none"
     >
       Add
     </button>
   </div>
   <span className="text-xs text-red-700" role="alert">{error}</span>
-</div>
+</form>

Committable suggestion was skipped due to low confidence.

Comment on lines +16 to 32
async function inviteUser() {
const email = newEmail.trim();

async function addPerson() {
const { data: user, error: userError } = await supabase
.from("users")
.select("id")
.eq("email", newEmail)
.single();
if (!email) {
setError("No email provided");
} else {
const error = await addUserToDoc(email, id);

if (!user || userError) {
console.error("User not found", userError);
setError("User not found");
return;
if (error) {
setError(error);
} else {
setToolTipMessage("User added");
setTimeout(() => setToolTipMessage(""), 2000);
}
}

// In a secure production environment, this operation should be handled within a server-side action, with proper authorization checks to verify the user's permission to share the specified doc_id.
const { error } = await supabase
.from("permissions")
.insert([{ doc_id: id, user_id: user.id }])
.select();

if (error) {
console.error("Failed to insert permission", error);
setError("Failed to provide permission to user");
return;
}
setToolTipMessage("User added");
setTimeout(() => setToolTipMessage(""), 2000);
setNewEmail("");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider enhancing error handling and user feedback.

While the basic flow is correct, there are several improvements that could be made:

Consider implementing these enhancements:

 const [newEmail, setNewEmail] = React.useState("");
 const [error, setError] = React.useState("");
+const [isLoading, setIsLoading] = React.useState(false);
+let timeoutId: NodeJS.Timeout;

 async function inviteUser() {
+  clearTimeout(timeoutId);
+  setError("");
+  setIsLoading(true);
   const email = newEmail.trim();
+  
+  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+  if (!emailRegex.test(email)) {
+    setError("Please enter a valid email address");
+    setIsLoading(false);
+    return;
+  }

   if (!email) {
     setError("No email provided");
+    setIsLoading(false);
   } else {
     const error = await addUserToDoc(email, id);

     if (error) {
       setError(error);
     } else {
       setToolTipMessage("User added");
-      setTimeout(() => setToolTipMessage(""), 2000);
+      timeoutId = setTimeout(() => setToolTipMessage(""), 2000);
     }
   }

   setNewEmail("");
+  setIsLoading(false);
 }

Then update the button to show loading state:

 <button
   onClick={() => inviteUser()}
+  disabled={isLoading}
   className="px-4 py-2 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 focus:outline-none"
 >
-  Add
+  {isLoading ? "Adding..." : "Add"}
 </button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function inviteUser() {
const email = newEmail.trim();
async function addPerson() {
const { data: user, error: userError } = await supabase
.from("users")
.select("id")
.eq("email", newEmail)
.single();
if (!email) {
setError("No email provided");
} else {
const error = await addUserToDoc(email, id);
if (!user || userError) {
console.error("User not found", userError);
setError("User not found");
return;
if (error) {
setError(error);
} else {
setToolTipMessage("User added");
setTimeout(() => setToolTipMessage(""), 2000);
}
}
// In a secure production environment, this operation should be handled within a server-side action, with proper authorization checks to verify the user's permission to share the specified doc_id.
const { error } = await supabase
.from("permissions")
.insert([{ doc_id: id, user_id: user.id }])
.select();
if (error) {
console.error("Failed to insert permission", error);
setError("Failed to provide permission to user");
return;
}
setToolTipMessage("User added");
setTimeout(() => setToolTipMessage(""), 2000);
setNewEmail("");
const [newEmail, setNewEmail] = React.useState("");
const [error, setError] = React.useState("");
const [isLoading, setIsLoading] = React.useState(false);
let timeoutId: NodeJS.Timeout;
async function inviteUser() {
clearTimeout(timeoutId);
setError("");
setIsLoading(true);
const email = newEmail.trim();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
setError("Please enter a valid email address");
setIsLoading(false);
return;
}
if (!email) {
setError("No email provided");
setIsLoading(false);
} else {
const error = await addUserToDoc(email, id);
if (error) {
setError(error);
} else {
setToolTipMessage("User added");
timeoutId = setTimeout(() => setToolTipMessage(""), 2000);
}
}
setNewEmail("");
setIsLoading(false);
}
<button
onClick={() => inviteUser()}
disabled={isLoading}
className="px-4 py-2 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 focus:outline-none"
>
{isLoading ? "Adding..." : "Add"}
</button>

Comment on lines +15 to 28
const { docId, documentMetadata, setDocumentMetadata, setToolTipMessage } =
props;
const [isToggle, setIsToggle] = React.useState(documentMetadata.is_public); // Track toggle state

const handleToggle = () => {
const handleToggle = async () => {
setIsToggle(!isToggle); // Toggle the state between true and false
setToolTipMessage(
!isToggle ? "Document made public" : "Document made private",
);

async function updateDocMetadata() {
// Update the document's public status in Supabase
const { data: docsData, error } = await supabase
.from("docs")
.update({ is_public: !isToggle }) // Toggle the is_public field
.eq("doc_id", docId); // Match the document by its id
let error = await changeDocVisibility(!isToggle, docId);
if (!error) {
setDocumentMetadata({ ...documentMetadata, is_public: !isToggle });
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error handling and state management.

While the async implementation is good, there are a few concerns:

  1. The toggle state is updated before the API call completes, which could lead to UI/state mismatches if the operation fails
  2. There's no handling of loading state during the API call
  3. The error case doesn't revert the toggle state

Consider this improved implementation:

-  const [isToggle, setIsToggle] = React.useState(documentMetadata.is_public);
+  const [isLoading, setIsLoading] = React.useState(false);
+  const isToggle = documentMetadata.is_public;

   const handleToggle = async () => {
-    setIsToggle(!isToggle);
+    if (isLoading) return;
+    setIsLoading(true);
     setToolTipMessage(
       !isToggle ? "Document made public" : "Document made private"
     );

     let error = await changeDocVisibility(!isToggle, docId);
     if (!error) {
       setDocumentMetadata({ ...documentMetadata, is_public: !isToggle });
+    } else {
+      setToolTipMessage("Failed to update document visibility");
     }
+    setIsLoading(false);

     setTimeout(() => {
       setToolTipMessage("");
     }, 2000);
   };

Also, consider disabling the toggle during loading:

   <div
     onClick={() => handleToggle()}
-    className={`w-16 h-8 flex items-center bg-gray-300 rounded-full p-1 cursor-pointer...`}
+    className={`w-16 h-8 flex items-center bg-gray-300 rounded-full p-1 
+      ${isLoading ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}...`}
   >

Committable suggestion was skipped due to low confidence.

Comment on lines +6 to +28
export async function addUserToDoc(newEmail: string, id: string) {
// In a production environment, you should verify the user's permission to share the specified doc_id.
const { data: user, error: userError } = await supabase
.from("users")
.select("id")
.eq("email", newEmail)
.single();

if (!user || userError) {
console.error("User not found", userError);
return "User not found";
}

const { error } = await supabase
.from("permissions")
.insert([{ doc_id: id, user_id: user.id }])
.select();

if (error) {
console.error("Failed to insert permission", error);
return "Failed to provide permission to user";
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add transaction and duplicate checks for data integrity

The function has several potential issues that should be addressed:

  1. The permission insert uses id (line 21) but the query parameter is doc_id. This mismatch could cause errors.
  2. There's no check for duplicate permissions.
  3. The user check and permission insert should be wrapped in a transaction to prevent race conditions.

Here's a suggested improvement:

 export async function addUserToDoc(newEmail: string, id: string) {
-  const { data: user, error: userError } = await supabase
-    .from("users")
-    .select("id")
-    .eq("email", newEmail)
-    .single();
+  const { data, error } = await supabase.rpc('add_user_to_doc', {
+    p_email: newEmail,
+    p_doc_id: id
+  });

-  if (!user || userError) {
-    console.error("User not found", userError);
-    return "User not found";
+  if (error) {
+    console.error("Failed to add user to doc", error);
+    return error.message;
   }

-  const { error } = await supabase
-    .from("permissions")
-    .insert([{ doc_id: id, user_id: user.id }])
-    .select();
-
-  if (error) {
-    console.error("Failed to insert permission", error);
-    return "Failed to provide permission to user";
-  }
+  return "User successfully added to document";
 }

Create a Postgres function to handle this atomically:

CREATE OR REPLACE FUNCTION add_user_to_doc(p_email TEXT, p_doc_id UUID)
RETURNS void AS $$
BEGIN
  INSERT INTO permissions (doc_id, user_id)
  SELECT p_doc_id, users.id
  FROM users
  WHERE users.email = p_email
  AND NOT EXISTS (
    SELECT 1 FROM permissions
    WHERE doc_id = p_doc_id
    AND user_id = users.id
  );
  
  IF NOT FOUND THEN
    RAISE EXCEPTION 'User not found or already has permission';
  END IF;
END;
$$ LANGUAGE plpgsql;

Comment on lines +30 to +33
export async function editDocTitle(docId: string, newName: string) {
// In a production environment, you should check that the user has permission to edit the doc and add error handling.
await supabase.from("docs").update({ name: newName }).eq("doc_id", docId);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling and input validation

The function needs several improvements for production readiness:

  1. Missing error handling
  2. No return value to indicate success/failure
  3. No input validation for the new title

Here's a suggested improvement:

-export async function editDocTitle(docId: string, newName: string) {
+export async function editDocTitle(docId: string, newName: string): Promise<{ error: string | null }> {
+  if (!newName?.trim()) {
+    return { error: "Document title cannot be empty" };
+  }
+
+  if (newName.length > 255) {
+    return { error: "Document title is too long" };
+  }
+
   // In a production environment, you should check that the user has permission to edit the doc and add error handling.
-  await supabase.from("docs").update({ name: newName }).eq("doc_id", docId);
+  const { error } = await supabase
+    .from("docs")
+    .update({ name: newName.trim() })
+    .eq("doc_id", docId);
+
+  if (error) {
+    console.error("Failed to update document title:", error);
+    return { error: "Failed to update document title" };
+  }
+
+  return { error: null };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function editDocTitle(docId: string, newName: string) {
// In a production environment, you should check that the user has permission to edit the doc and add error handling.
await supabase.from("docs").update({ name: newName }).eq("doc_id", docId);
}
export async function editDocTitle(docId: string, newName: string): Promise<{ error: string | null }> {
if (!newName?.trim()) {
return { error: "Document title cannot be empty" };
}
if (newName.length > 255) {
return { error: "Document title is too long" };
}
// In a production environment, you should check that the user has permission to edit the doc and add error handling.
const { error } = await supabase
.from("docs")
.update({ name: newName.trim() })
.eq("doc_id", docId);
if (error) {
console.error("Failed to update document title:", error);
return { error: "Failed to update document title" };
}
return { error: null };
}

Comment on lines +35 to +46
export async function changeDocVisibility(isPublic: boolean, docId: string) {
// In a production environment, you should check that the user has permission to edit the doc and add error handling.
const { data, error } = await supabase
.from("docs")
.update({ is_public: isPublic })
.eq("doc_id", docId);

if (error) {
console.error("Failed to update doc visibility", error);
return "Failed to update doc visibility";
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve type safety and return value handling

The function could benefit from better type safety and return value handling.

Here's a suggested improvement:

-export async function changeDocVisibility(isPublic: boolean, docId: string) {
+export async function changeDocVisibility(
+  isPublic: boolean,
+  docId: string
+): Promise<{ success: boolean; error: string | null }> {
   // In a production environment, you should check that the user has permission to edit the doc and add error handling.
-  const { data, error } = await supabase
+  const { error } = await supabase
     .from("docs")
     .update({ is_public: isPublic })
     .eq("doc_id", docId);

   if (error) {
     console.error("Failed to update doc visibility", error);
-    return "Failed to update doc visibility";
+    return { success: false, error: "Failed to update doc visibility" };
   }
+
+  return { success: true, error: null };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function changeDocVisibility(isPublic: boolean, docId: string) {
// In a production environment, you should check that the user has permission to edit the doc and add error handling.
const { data, error } = await supabase
.from("docs")
.update({ is_public: isPublic })
.eq("doc_id", docId);
if (error) {
console.error("Failed to update doc visibility", error);
return "Failed to update doc visibility";
}
}
export async function changeDocVisibility(
isPublic: boolean,
docId: string
): Promise<{ success: boolean; error: string | null }> {
// In a production environment, you should check that the user has permission to edit the doc and add error handling.
const { error } = await supabase
.from("docs")
.update({ is_public: isPublic })
.eq("doc_id", docId);
if (error) {
console.error("Failed to update doc visibility", error);
return { success: false, error: "Failed to update doc visibility" };
}
return { success: true, error: null };
}

Comment on lines +48 to +91
export async function getDocMetadata(docId: string) {
const {
data: { user },
} = await supabase.auth.getUser();

const { data: docsData, error } = await supabase
.from("docs")
.select("*")
.eq("doc_id", docId);

if (!docsData || docsData.length === 0 || error) {
console.error("Document not found", error);
return {
data: null,
error: "Document not found",
};
}

if (docsData[0]?.is_public) {
return {
data: docsData[0],
error: null,
};
} else {
const { data: permissionsData, error: permError } = await supabase
.from("permissions")
.select("id")
.eq("doc_id", docsData[0].id)
.eq("user_id", user?.id);

if (permError || !permissionsData) {
console.error("User does not have access to this document", permError);
return {
data: null,
error: "User does not have access to this document",
};
} else {
return {
data: docsData[0],
error: null,
};
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize queries and add type safety

The function has several areas for improvement:

  1. N+1 query issue: Separate queries for docs and permissions
  2. Inconsistent use of doc_id vs id in queries
  3. Type safety could be improved

Here's a suggested improvement:

+interface DocMetadata {
+  id: string;
+  doc_id: string;
+  name: string;
+  is_public: boolean;
+  created_at: string;
+}
+
+interface DocMetadataResponse {
+  data: DocMetadata | null;
+  error: string | null;
+}
+
-export async function getDocMetadata(docId: string) {
+export async function getDocMetadata(docId: string): Promise<DocMetadataResponse> {
   const {
     data: { user },
   } = await supabase.auth.getUser();

   const { data: docsData, error } = await supabase
     .from("docs")
-    .select("*")
+    .select(`
+      *,
+      permissions!inner(user_id)
+    `)
     .eq("doc_id", docId)
+    .eq("permissions.user_id", user?.id)
+    .maybeSingle();

   if (!docsData || error) {
     console.error("Document not found", error);
     return {
       data: null,
       error: "Document not found",
     };
   }

-  if (docsData[0]?.is_public) {
+  if (docsData.is_public) {
     return {
-      data: docsData[0],
+      data: docsData,
       error: null,
     };
   } else {
-    const { data: permissionsData, error: permError } = await supabase
-      .from("permissions")
-      .select("id")
-      .eq("doc_id", docsData[0].id)
-      .eq("user_id", user?.id);
-
-    if (permError || !permissionsData) {
-      console.error("User does not have access to this document", permError);
-      return {
-        data: null,
-        error: "User does not have access to this document",
-      };
-    } else {
-      return {
-        data: docsData[0],
-        error: null,
-      };
-    }
+    return {
+      data: docsData,
+      error: null,
+    };
   }
 }

This improvement:

  1. Uses a single query with a join to check permissions
  2. Adds TypeScript interfaces for better type safety
  3. Uses maybeSingle() to simplify the response handling
  4. Removes redundant permission check for private documents since it's handled by the join
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function getDocMetadata(docId: string) {
const {
data: { user },
} = await supabase.auth.getUser();
const { data: docsData, error } = await supabase
.from("docs")
.select("*")
.eq("doc_id", docId);
if (!docsData || docsData.length === 0 || error) {
console.error("Document not found", error);
return {
data: null,
error: "Document not found",
};
}
if (docsData[0]?.is_public) {
return {
data: docsData[0],
error: null,
};
} else {
const { data: permissionsData, error: permError } = await supabase
.from("permissions")
.select("id")
.eq("doc_id", docsData[0].id)
.eq("user_id", user?.id);
if (permError || !permissionsData) {
console.error("User does not have access to this document", permError);
return {
data: null,
error: "User does not have access to this document",
};
} else {
return {
data: docsData[0],
error: null,
};
}
}
}
interface DocMetadata {
id: string;
doc_id: string;
name: string;
is_public: boolean;
created_at: string;
}
interface DocMetadataResponse {
data: DocMetadata | null;
error: string | null;
}
export async function getDocMetadata(docId: string): Promise<DocMetadataResponse> {
const {
data: { user },
} = await supabase.auth.getUser();
const { data: docsData, error } = await supabase
.from("docs")
.select(`
*,
permissions!inner(user_id)
`)
.eq("doc_id", docId)
.eq("permissions.user_id", user?.id)
.maybeSingle();
if (!docsData || error) {
console.error("Document not found", error);
return {
data: null,
error: "Document not found",
};
}
if (docsData.is_public) {
return {
data: docsData,
error: null,
};
} else {
return {
data: docsData,
error: null,
};
}
}

app/document/[id]/page.tsx Show resolved Hide resolved
Comment on lines 32 to 47
if (!docId) return;

const { data: docsData } = await supabase
.from("docs")
.select("*")
.eq("doc_id", docId);
let { data: docsData, error } = await getDocMetadata(docId);

if (!docsData || docsData.length === 0) {
console.error("Document not found");
if (error || !docsData) {
setError(error ?? "Document not found");
return;
}

setDocMetadata({
name: docsData[0].name ?? "Untitled Document",
id: docsData[0].id,
doc_id: docsData[0].doc_id,
is_public: docsData[0].is_public ?? false,
});

if (docsData[0]?.is_public) {
setHasAccess(true);
} else {
const { data: permissionsData, error: permError } = await supabase
.from("permissions")
.select("id")
.eq("doc_id", docsData[0].id)
.eq("user_id", user?.id);

if (permError || !permissionsData) {
console.error(
"User does not have access to this document",
permError,
);
} else {
setHasAccess(true);
}
if (docsData) {
setDocMetadata({
name: docsData.name ?? "Untitled Document",
id: docsData.id,
doc_id: docsData.doc_id,
is_public: docsData.is_public ?? false,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling specificity.

While the error handling is functional, it could be more specific about different error cases. Consider distinguishing between:

  • Missing document ID
  • Document not found
  • Permission errors
 if (!docId) {
-  return;
+  setError({
+    type: 'not_found',
+    message: 'Invalid document ID'
+  });
+  return;
 }

 let { data: docsData, error } = await getDocMetadata(docId);

 if (error || !docsData) {
-  setError(error ?? "Document not found");
+  setError({
+    type: error ? 'fetch_error' : 'not_found',
+    message: error ?? "Document not found"
+  });
   return;
 }

Committable suggestion was skipped due to low confidence.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Outside diff range and nitpick comments (5)
components/document/create-doc.tsx (2)

11-11: Add TypeScript types for better type safety

The destructured response from createDoc() lacks type information.

Consider adding proper typing:

-    let {data, error} = await createDoc();
+    let {data, error}: {data: string | null; error: Error | null} = await createDoc();

1-6: Consider adding proper imports for error handling

You'll need to import additional dependencies for the suggested improvements.

Add these imports:

 "use client";
 import React from "react";
+import { useState } from "react";
 import { Button } from "../ui/button";
 import { useRouter } from "next/navigation";
 import { createDoc } from "@/utils/supabase/queries";
+import { toast } from "@/components/ui/toast";
components/document/display-docs.tsx (1)

13-25: Consider using SWR or React Query for better data management.

The current implementation could benefit from a more robust data fetching strategy.

Consider these improvements:

  1. Use SWR or React Query for automatic:
    • Caching
    • Revalidation
    • Error retry
    • Loading states
  2. Implement error boundaries for better error handling

Example with SWR:

import useSWR from 'swr'

export default function DisplayDocs() {
  const { data: docs, error, isLoading } = useSWR<Docs[]>('docs', getDocs)
  // ... rest of the component
}
app/document/[id]/page.tsx (1)

32-47: Consider adding specific error message for missing docId.

The early return for missing docId should set an error message to provide better user feedback.

-if (!docId) return;
+if (!docId) {
+  setError("Invalid document ID");
+  return;
+}
utils/supabase/queries.tsx (1)

144-147: Improve type safety by defining an interface for doc

Currently, you're using (doc: any) in your mapping function, which reduces type safety. Consider defining an interface for your document objects to enhance type checking and maintainability.

Apply this diff to define an interface and use it:

+interface Doc {
+    doc_id: string;
+    name: string | null;
+}

 const transformedDocs = docsData?.map((doc: Doc) => ({
     doc_id: doc.doc_id,
     name: doc.name ?? "Untitled Document",
 }));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 6682e4c and 0ff994c.

📒 Files selected for processing (8)
  • app/api/docs/route.ts (0 hunks)
  • app/document/[id]/page.tsx (2 hunks)
  • app/document/page.tsx (1 hunks)
  • components/create-doc.tsx (0 hunks)
  • components/display-docs.tsx (0 hunks)
  • components/document/create-doc.tsx (1 hunks)
  • components/document/display-docs.tsx (1 hunks)
  • utils/supabase/queries.tsx (1 hunks)
💤 Files with no reviewable changes (3)
  • app/api/docs/route.ts
  • components/create-doc.tsx
  • components/display-docs.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/document/page.tsx
🔇 Additional comments (4)
app/document/[id]/page.tsx (4)

11-11: LGTM! Good abstraction of database queries.

Moving the database query logic to a dedicated utility file improves code organization and maintainability.


28-28: LGTM! State management simplified.

Good removal of unnecessary state and addition of focused error handling state.


54-55: Previous suggestion for enhanced error presentation still applies.


81-82: LGTM! Verify PermissionsToggle component interface.

The change from primitive props to passing the complete metadata object is a good practice.

✅ Verification successful

Let's search for the component file and then analyze its props interface.


Props interface matches the usage - all good!

The PermissionsToggle component correctly defines the documentMetadata and setDocumentMetadata props in its interface, matching exactly how they are being used in the page component.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify PermissionsToggle component accepts the new props
ast-grep --pattern 'type $Props = {
  $$$
  documentMetadata: $_
  setDocumentMetadata: $_
  $$$
}'

Length of output: 101


Script:

#!/bin/bash
# Let's try to find the PermissionsToggle component definition first
fd "PermissionsToggle" --type f

# Then we can examine its content
rg -l "PermissionsToggle" --type tsx

Length of output: 96


Script:

#!/bin/bash
# Let's try without file type restriction first
rg -l "PermissionsToggle"

# Also search for the component definition pattern
rg "interface.*PermissionsToggle|type.*PermissionsToggle"

Length of output: 233


Script:

#!/bin/bash
# Let's examine the PermissionsToggleProps interface
rg -A 5 "interface PermissionsToggleProps" components/document/permissions-toggle.tsx

Length of output: 288

router.push(`/document/${data}`);
};

return <Button onClick={() => onCreateDoc()}>+ Create new doc</Button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize event handler binding

The current onClick handler creates a new function on every render.

Directly pass the function reference:

-  return <Button onClick={() => onCreateDoc()}>+ Create new doc</Button>
+  return <Button onClick={onCreateDoc}>+ Create new doc</Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return <Button onClick={() => onCreateDoc()}>+ Create new doc</Button>
return <Button onClick={onCreateDoc}>+ Create new doc</Button>

Comment on lines 10 to 19
const onCreateDoc = async () => {
let {data, error} = await createDoc();

if (error) {
console.error(error);
return;
}

router.push(`/document/${data}`);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enhance error handling and user feedback

The current error handling only logs to console, which doesn't provide feedback to users when document creation fails.

Consider implementing proper error handling:

 const onCreateDoc = async () => {
+  try {
     let {data, error} = await createDoc();
 
     if (error) {
-      console.error(error);
-      return;
+      throw error;
     }
 
     router.push(`/document/${data}`);
+  } catch (error) {
+    console.error(error);
+    // Add toast or alert for user feedback
+    toast.error("Failed to create document. Please try again.");
   }
 };

Committable suggestion was skipped due to low confidence.

Comment on lines 7 to 22
export default function CreateDoc() {
const router = useRouter();

const onCreateDoc = async () => {
let {data, error} = await createDoc();

if (error) {
console.error(error);
return;
}

router.push(`/document/${data}`);
};

return <Button onClick={() => onCreateDoc()}>+ Create new doc</Button>
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add loading state and prevent duplicate submissions

The button remains clickable during document creation, which could lead to duplicate submissions.

Implement loading state and disable the button during submission:

 export default function CreateDoc() {
   const router = useRouter();
+  const [isLoading, setIsLoading] = useState(false);
 
   const onCreateDoc = async () => {
+    if (isLoading) return;
+    setIsLoading(true);
     try {
       let {data, error} = await createDoc();
       if (error) throw error;
       router.push(`/document/${data}`);
     } catch (error) {
       console.error(error);
       toast.error("Failed to create document");
+    } finally {
+      setIsLoading(false);
     }
   };
 
-  return <Button onClick={() => onCreateDoc()}>+ Create new doc</Button>
+  return (
+    <Button 
+      onClick={onCreateDoc} 
+      disabled={isLoading}
+    >
+      {isLoading ? "Creating..." : "+ Create new doc"}
+    </Button>
+  );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default function CreateDoc() {
const router = useRouter();
const onCreateDoc = async () => {
let {data, error} = await createDoc();
if (error) {
console.error(error);
return;
}
router.push(`/document/${data}`);
};
return <Button onClick={() => onCreateDoc()}>+ Create new doc</Button>
}
export default function CreateDoc() {
const router = useRouter();
const [isLoading, setIsLoading] = useState(false);
const onCreateDoc = async () => {
if (isLoading) return;
setIsLoading(true);
try {
let {data, error} = await createDoc();
if (error) throw error;
router.push(`/document/${data}`);
} catch (error) {
console.error(error);
toast.error("Failed to create document");
} finally {
setIsLoading(false);
}
};
return (
<Button
onClick={onCreateDoc}
disabled={isLoading}
>
{isLoading ? "Creating..." : "+ Create new doc"}
</Button>
);
}

name: string;
};
export default function DisplayDocs() {
const [docs, setDocs] = useState<Docs[]>([]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enhance state management and error handling.

The current implementation has several areas for improvement:

  1. Remove debug console.log
  2. Add loading state for better UX
  3. Add error state for user feedback

Here's the suggested implementation:

-  const [docs, setDocs] = useState<Docs[]>([]);
+  const [docs, setDocs] = useState<Docs[]>([]);
+  const [isLoading, setIsLoading] = useState(true);
+  const [error, setError] = useState<string | null>(null);

   useEffect(() => {
     async function fetchDocs() {
+      setIsLoading(true);
+      setError(null);
       let {data, error} = await getDocs();
-      console.log(data, error)
       if(error || !data) {
-        console.error(error);
+        setError(error?.message ?? 'Failed to fetch documents');
+        setIsLoading(false);
         return;
       }

       setDocs(data);
+      setIsLoading(false);
     }
     fetchDocs()
   }, []);

Also applies to: 13-25

Comment on lines +6 to +9
type Docs = {
doc_id: string;
name: string;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider enhancing the Docs type definition.

The current type definition could be more comprehensive to support future features and improve data display.

Consider adding useful metadata fields:

 type Docs = {
   doc_id: string;
   name: string;
+  created_at?: string;
+  updated_at?: string;
+  owner_id?: string;
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type Docs = {
doc_id: string;
name: string;
};
type Docs = {
doc_id: string;
name: string;
created_at?: string;
updated_at?: string;
owner_id?: string;
};

Comment on lines +27 to +49
return (
<div className="w-full">
<h2 className="text-2xl font-bold mb-4 w-fit">Recent Documents</h2>
<div className="flex flex-col gap-4">
{docs.length === 0 ? (
<p className="text-gray-500">
No documents found. Start by creating a new document!
</p>
) : (
docs.map((doc) => (
<Link
href={`/document/${doc.doc_id}`}
key={doc.doc_id}
className="w-full flex justify-between items-center gap-3 py-1 border-b border-neutral-800 text-neutral-300 hover:text-white hover:border-neutral-200 transition-all"
>
<span>{doc.name ?? "Untitled Document"}</span>
<span>{`->`}</span>
</Link>
))
)}
</div>
</div>
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance UI feedback and accessibility.

The current UI could be improved with loading states, error feedback, and better accessibility.

Consider these improvements:

   return (
     <div className="w-full">
       <h2 className="text-2xl font-bold mb-4 w-fit">Recent Documents</h2>
       <div className="flex flex-col gap-4">
+        {isLoading && (
+          <p className="text-gray-500">Loading documents...</p>
+        )}
+        {error && (
+          <p className="text-red-500" role="alert">{error}</p>
+        )}
-        {docs.length === 0 ? (
+        {!isLoading && !error && docs.length === 0 ? (
           <p className="text-gray-500">
             No documents found. Start by creating a new document!
           </p>
         ) : (
           docs.map((doc) => (
             <Link
               href={`/document/${doc.doc_id}`}
               key={doc.doc_id}
+              aria-label={`Open ${doc.name ?? "Untitled Document"}`}
               className="w-full flex justify-between items-center gap-3 py-1 border-b border-neutral-800 text-neutral-300 hover:text-white hover:border-neutral-200 transition-all"
             >
               <span>{doc.name ?? "Untitled Document"}</span>
-              <span>{`->`}</span>
+              <span aria-hidden="true">→</span>
             </Link>
           ))
         )}
       </div>
     </div>
   );

Committable suggestion was skipped due to low confidence.

Comment on lines 131 to 134
const { data: docsData, error: docsError } = await supabase
.from("docs")
.select("id, doc_id, is_public, name")
.in("id", docIds);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential mismatch between doc_ids and ids in document query

In your getDocs function, the docIds array contains doc_ids from the permissions table. However, when querying the docs table, you are using .in("id", docIds). If docs.id does not correspond to the doc_ids from permissions, this could lead to incorrect documents being retrieved. Consider verifying whether you should be using .in("doc_id", docIds) instead.

Apply this diff to correct the query:

     .from("docs")
     .select("id, doc_id, is_public, name")
-    .in("id", docIds);
+    .in("doc_id", docIds);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: docsData, error: docsError } = await supabase
.from("docs")
.select("id, doc_id, is_public, name")
.in("id", docIds);
const { data: docsData, error: docsError } = await supabase
.from("docs")
.select("id, doc_id, is_public, name")
.in("doc_id", docIds);

Comment on lines 155 to 159
export async function createDoc() {
const ysweetDoc = await createYSweetDoc(
process.env.Y_SWEET_CONNECTION_STRING ?? "",
);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for createYSweetDoc call

Currently, the call to createYSweetDoc does not handle potential errors. If the function fails (e.g., due to an invalid connection string), it could cause an unhandled exception. Consider wrapping the call in a try-catch block to handle any errors gracefully.

Apply this diff to add error handling:

 export async function createDoc() {
-    const ysweetDoc = await createYSweetDoc(
-        process.env.Y_SWEET_CONNECTION_STRING ?? "",
-    );
+    let ysweetDoc;
+    try {
+        ysweetDoc = await createYSweetDoc(
+            process.env.Y_SWEET_CONNECTION_STRING ?? "",
+        );
+    } catch (error) {
+        console.error("Failed to create Y-Sweet document:", error);
+        return {
+            data: null,
+            error: "Failed to create Y-Sweet document"
+        };
+    }

Committable suggestion was skipped due to low confidence.

Comment on lines 171 to 180
const { data: docData, error: docError } = await supabase
.from("docs")
.insert([{ doc_id: ysweetDoc.docId }])
.select();

if (docError) {
return {
data: null,
error: `Failed to insert doc: ${docError.message}`}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check for docData before accessing docData[0]

In the createDoc function, after inserting into the docs table, you access docData[0].id without ensuring that docData contains data. If the insert operation does not return data, accessing docData[0] could result in an error. Consider adding a check to ensure docData is not null or empty before accessing it.

Apply this diff to add the check:

     const { data: docData, error: docError } = await supabase
         .from("docs")
         .insert([{ doc_id: ysweetDoc.docId }])
         .select();

-    if (docError) {
+    if (docError || !docData || docData.length === 0) {
         return {
             data: null,
-            error: `Failed to insert doc: ${docError.message}`}
+            error: `Failed to insert doc: ${docError ? docError.message : 'No data returned from insert'}`
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { data: docData, error: docError } = await supabase
.from("docs")
.insert([{ doc_id: ysweetDoc.docId }])
.select();
if (docError) {
return {
data: null,
error: `Failed to insert doc: ${docError.message}`}
}
const { data: docData, error: docError } = await supabase
.from("docs")
.insert([{ doc_id: ysweetDoc.docId }])
.select();
if (docError || !docData || docData.length === 0) {
return {
data: null,
error: `Failed to insert doc: ${docError ? docError.message : 'No data returned from insert'}`
}
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (1)
utils/supabase/queries.tsx (1)

1-5: Add type safety to Supabase client initialization

Consider adding type definitions for your database schema to improve type safety.

import { Database } from './types'
const supabase = createClient<Database>();
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 0ff994c and 720b1d2.

📒 Files selected for processing (3)
  • components/document/create-doc.tsx (1 hunks)
  • components/document/display-docs.tsx (1 hunks)
  • utils/supabase/queries.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/document/create-doc.tsx
  • components/document/display-docs.tsx
🔇 Additional comments (6)
utils/supabase/queries.tsx (6)

7-29: Previous review comment is still applicable

The concerns about transactions and duplicate checks raised in the previous review remain valid.


31-34: Previous review comment is still applicable

The concerns about error handling and input validation raised in the previous review remain valid.


36-47: Previous review comment is still applicable

The concerns about type safety and return value handling raised in the previous review remain valid.


49-92: Previous review comment is still applicable

The concerns about query optimization and type safety raised in the previous review remain valid.


129-133: Previous review comment is still applicable

The concern about potential mismatch between doc_id and id in the query remains valid.


153-198: Previous review comments are still applicable

The concerns about error handling for createYSweetDoc and null checks for docData raised in the previous reviews remain valid.

Comment on lines +142 to +146
const transformedDocs = docsData?.map((doc: any) => ({
doc_id: doc.doc_id,
name: doc.name ?? "Untitled Document",
}));

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add type safety to document transformation

The current implementation uses any type which bypasses TypeScript's type checking. Consider defining proper interfaces for your document types.

+ interface Doc {
+   id: string;
+   doc_id: string;
+   is_public: boolean;
+   name: string | null;
+ }
+ 
+ interface TransformedDoc {
+   doc_id: string;
+   name: string;
+ }
- const transformedDocs = docsData?.map((doc: any) => ({
+ const transformedDocs = docsData?.map((doc: Doc): TransformedDoc => ({
    doc_id: doc.doc_id,
    name: doc.name ?? "Untitled Document",
  }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const transformedDocs = docsData?.map((doc: any) => ({
doc_id: doc.doc_id,
name: doc.name ?? "Untitled Document",
}));
interface Doc {
id: string;
doc_id: string;
is_public: boolean;
name: string | null;
}
interface TransformedDoc {
doc_id: string;
name: string;
}
const transformedDocs = docsData?.map((doc: Doc): TransformedDoc => ({
doc_id: doc.doc_id,
name: doc.name ?? "Untitled Document",
}));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant