Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

# testing
/coverage

.vscode
# next.js
/.next/
/out/
Expand All @@ -35,10 +35,13 @@ yarn-error.log*

# vercel
.vercel

.swc
# typescript
*.tsbuildinfo
next-env.d.ts

*.xlsx
*.csv
*.csv

jest.config.ts
*.test.tsx
192 changes: 192 additions & 0 deletions ALERT_SYSTEM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Alert System Documentation

This documentation explains how to use the reusable alert system in your components.

## Overview

The alert system consists of:
- `AlertProvider` - Context provider that manages alert state
- `useAlert()` - Hook to access alert context
- `useAlertActions()` - Convenient hook with pre-defined alert functions

## Setup

The `AlertProvider` is already set up in the dashboard layout, so any component within the dashboard can use alerts.

## Usage in Components

### Method 1: Using useAlertActions (Recommended)

```typescript
import { useAlertActions } from "@/lib/use-alert";

export default function MyComponent() {
const { showError, showSuccess, showWarning, showInfo } = useAlertActions();

const handleSubmit = async () => {
try {
// Your logic here
const response = await fetch('/api/some-endpoint');

if (response.ok) {
showSuccess("Operation completed successfully!");
} else {
showError("Something went wrong!");
}
} catch (error) {
showError("Network error occurred");
}
};

return (
// Your component JSX
);
}
```

### Method 2: Using useAlert directly

```typescript
import { useAlert } from "@/lib/alert-context";

export default function MyComponent() {
const { showAlert } = useAlert();

const handleAction = () => {
showAlert("Custom message", "warning");
};

return (
// Your component JSX
);
}
```

## Available Alert Types

### 1. Error Alerts
```typescript
showError("This is an error message");
// or
showAlert("This is an error message", "destructive");
```

### 2. Success Alerts
```typescript
showSuccess("Operation completed successfully!");
// or
showAlert("Operation completed successfully!", "success");
```

### 3. Warning Alerts
```typescript
showWarning("Please check your input");
// or
showAlert("Please check your input", "warning");
```

### 4. Info Alerts
```typescript
showInfo("Here's some information");
// or
showAlert("Here's some information", "default");
```

## Alert Features

- **Auto-dismiss**: All alerts automatically disappear after 3 seconds
- **Fixed positioning**: Alerts appear at the top center of the screen
- **Responsive design**: Alerts adapt to different screen sizes
- **Custom styling**: Each alert type has its own color scheme and icon
- **Z-index management**: Alerts appear above all other content

## Alert Variants

| Variant | Description | Color Scheme | Icon |
|---------|-------------|--------------|------|
| `destructive` | Error messages | Red | AlertCircle |
| `success` | Success messages | Green | CheckCircle |
| `warning` | Warning messages | Yellow | AlertTriangle |
| `default` | Info messages | Blue | Info |

## Examples

### Form Validation
```typescript
const { showError, showSuccess } = useAlertActions();

const validateForm = () => {
if (!email) {
showError("Email is required");
return false;
}
if (!password) {
showError("Password is required");
return false;
}
return true;
};

const handleSubmit = async () => {
if (!validateForm()) return;

try {
await submitForm();
showSuccess("Form submitted successfully!");
} catch (error) {
showError("Failed to submit form");
}
};
```

### API Operations
```typescript
const { showError, showSuccess, showWarning } = useAlertActions();

const deleteItem = async (id: string) => {
try {
const response = await fetch(\`/api/items/\${id}\`, {
method: 'DELETE'
});

if (response.ok) {
showSuccess("Item deleted successfully!");
} else if (response.status === 404) {
showWarning("Item not found");
} else {
showError("Failed to delete item");
}
} catch (error) {
showError("Network error occurred");
}
};
```

### User Feedback
```typescript
const { showInfo, showSuccess } = useAlertActions();

const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
showSuccess("Copied to clipboard!");
};

const showHelpInfo = () => {
showInfo("Click the button to copy the GST number");
};
```

## Best Practices

1. **Use appropriate variants**: Choose the right alert type for your message
2. **Keep messages concise**: Short, clear messages work best
3. **Don't overuse**: Avoid showing multiple alerts in quick succession
4. **Provide context**: Include relevant information in error messages
5. **Test auto-dismiss**: Ensure 3 seconds is enough time to read the message

## Implementation Details

- Alerts are managed by React Context
- State is automatically cleaned up when alerts hide
- The system prevents multiple alerts from stacking
- Custom CSS classes provide consistent styling across variants
77 changes: 51 additions & 26 deletions app/api/auth/register/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import bcrypt from "bcryptjs";

export async function POST(request: NextRequest) {
try {
const { gstin, user_id, password } = await request.json();
const { gstin, user_id, password, keyAttributes } = await request.json();

if (!gstin || !password) {
return NextResponse.json(
Expand All @@ -13,15 +13,29 @@ export async function POST(request: NextRequest) {
);
}

// Hash the password on the server side
const hashedPassword = await bcrypt.hash(password, 10);
if (!user_id) {
return NextResponse.json(
{ error: "User ID is required" },
{ status: 400 }
);
}
if (!keyAttributes) {
return NextResponse.json(
{ error: "Key attributes not generated, please try again later." },
{ status: 400 }
);
}
// Generate salt and hash the password
const salt = await bcrypt.genSalt(10);
// Hash the password
const hashedPassword = await bcrypt.hash(password, salt);

// Check if the user already exists
const { data: existingUser, error: userError } = await supabaseAdmin
.from("users")
.select("*")
.eq("gstin", gstin)
.single();
.from("users")
.select("*")
.eq("gstin", gstin)
.single();
if (userError && userError.code !== "PGRST116") {
return NextResponse.json(
{ error: "Failed to check existing user" },
Expand All @@ -34,33 +48,44 @@ export async function POST(request: NextRequest) {
{ status: 400 }
);
}
const {data: business, error: businessError} = await supabaseAdmin
.from("businesses")
.select("*")
.eq("gstin", gstin);
const { data: business, error: businessError } = await supabaseAdmin
.from("businesses")
.select("*")
.eq("gstin", gstin);

if (businessError) {
if (!business || businessError) {
console.error("Error fetching business:", businessError);
return NextResponse.json(
{ error: "Failed to fetch business. The GSTIN could be incorrect. Check the details." },
{
error:
"Failed to fetch business. The GSTIN could be incorrect or the service might be temporarily unavailable.",
},
{ status: 404 }
);
}
const {data : newUser, error: newUserError} = await supabaseAdmin
.from("users")
.insert([{
user_id,
gstin,
business_name: business?.[0]?.business_name,
mobile_number: business?.[0]?.mobile_number,
profile_url : null,
password: hashedPassword, // Store the server-side hashed password
}]);
// Use a transaction to ensure atomicity
const { data, error } = await supabaseAdmin.rpc('register_user_with_keys', {
p_user_id: user_id,
p_gstin: gstin,
p_business_name: business?.[0]?.business_name,
p_mobile_number: business?.[0]?.mobile_number,
p_password: hashedPassword,
p_business_address: business?.[0]?.business_address,
p_business_description: business?.[0]?.business_description,
p_encrypted_key: keyAttributes.encryptedKey,
p_key_decryption_nonce: keyAttributes.keyDecryptionNonce,
p_kek_salt: keyAttributes.kekSalt,
p_ops_limit: keyAttributes.opsLimit,
p_mem_limit: keyAttributes.memLimit,
p_public_key: keyAttributes.publicKey,
p_encrypted_secret_key: keyAttributes.encryptedSecretKey,
p_secret_key_decryption_nonce: keyAttributes.secretKeyDecryptionNonce
});

if (newUserError) {
console.error("Error inserting user:", newUserError);
if (error) {
console.error("Error in atomic registration:", error);
return NextResponse.json(
{ error: "Failed to register user" },
{ error: "Failed to register user and key attributes" },
{ status: 400 }
);
}
Expand Down
49 changes: 49 additions & 0 deletions app/api/auth/verify/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NextRequest, NextResponse } from "next/server";
import { supabaseAdmin } from "@/db/connect";

export async function POST(request: NextRequest) {
try {
const { gstin } = await request.json();
if (!gstin) {
return NextResponse.json({ error: "GSTIN is required" }, { status: 400 });
}

// Fetch user by GSTIN
const { data: keyAttributes, error } = await supabaseAdmin
.from("key-attributes")
.select("*")
.eq("gstin", gstin)
.single();
if (!keyAttributes) {
return NextResponse.json(
{ error: "User not found. Register first to use InvoSafe" },
{ status: 404 }
);
}
if (error) {
console.error("Error fetching key attributes:", error);
return NextResponse.json(
{ error: "Error while fetching user details. Try later." },
{ status: 404 }
);
}
// Only send necessary user details
const userResponse = {
encryptedKey: keyAttributes.encrypted_key,
keyDecryptionNonce: keyAttributes.key_decryption_nonce,
kekSalt: keyAttributes.kek_salt,
opsLimit: keyAttributes.ops_limit,
memLimit: keyAttributes.mem_limit,
publicKey: keyAttributes.public_key,
encryptedSecretKey: keyAttributes.encrypted_secret_key,
secretKeyDecryptionNonce: keyAttributes.secret_key_decryption_nonce,
};

return NextResponse.json({ keyAttributes: userResponse }, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: "An error occurred while processing your request." },
{ status: 500 }
);
}
}
Loading