realtimex-crm 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/assets/{DealList-B7Q5fWfD.js → DealList-90RCBW_j.js} +2 -2
- package/dist/assets/{DealList-B7Q5fWfD.js.map → DealList-90RCBW_j.js.map} +1 -1
- package/dist/assets/{index-7x4zGeo4.js → index-CTPFYcZP.js} +47 -47
- package/dist/assets/{index-7x4zGeo4.js.map → index-CTPFYcZP.js.map} +1 -1
- package/dist/assets/index-DbCBhMSJ.css +1 -0
- package/dist/edge-functions/README.md +151 -0
- package/dist/edge-functions/updatePassword.ts +84 -0
- package/dist/edge-functions/users.ts +240 -0
- package/dist/index.html +1 -1
- package/dist/stats.html +1 -1
- package/package.json +1 -1
- package/public/edge-functions/README.md +151 -0
- package/public/edge-functions/updatePassword.ts +84 -0
- package/public/edge-functions/users.ts +240 -0
- package/src/components/atomic-crm/login/StartPage.tsx +84 -12
- package/dist/assets/index-C0zU8xwx.css +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "realtimex-crm",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "RealTimeX CRM - A full-featured CRM built with React, shadcn-admin-kit, and Supabase. Fork of Atomic CRM with RealTimeX App SDK integration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Edge Functions for RealTimeX CRM
|
|
2
|
+
|
|
3
|
+
This directory contains Edge Functions that can be deployed directly from Supabase Dashboard.
|
|
4
|
+
|
|
5
|
+
## Required Functions (Deploy via Dashboard)
|
|
6
|
+
|
|
7
|
+
These are critical for team management and can be easily deployed through the GUI:
|
|
8
|
+
|
|
9
|
+
### 1. users.ts ⭐ **REQUIRED**
|
|
10
|
+
**Purpose:** Create and update CRM team members
|
|
11
|
+
**Required for:** Inviting new users, updating user profiles, managing permissions
|
|
12
|
+
**Deploy:** Copy entire `users.ts` file → Supabase Dashboard → Edge Functions → New Function → Name: `users` → Paste → Deploy
|
|
13
|
+
|
|
14
|
+
### 2. updatePassword.ts ⭐ **REQUIRED**
|
|
15
|
+
**Purpose:** Reset user passwords
|
|
16
|
+
**Required for:** Password reset functionality
|
|
17
|
+
**Deploy:** Copy entire `updatePassword.ts` file → Supabase Dashboard → Edge Functions → New Function → Name: `updatePassword` → Paste → Deploy
|
|
18
|
+
|
|
19
|
+
## Optional Functions (Deploy via CLI)
|
|
20
|
+
|
|
21
|
+
These functions have complex dependencies and are easier to deploy using Supabase CLI:
|
|
22
|
+
|
|
23
|
+
### 3. mergeContacts
|
|
24
|
+
**Purpose:** Merge duplicate contact records
|
|
25
|
+
**Required for:** Contact deduplication feature
|
|
26
|
+
**Deploy via CLI:**
|
|
27
|
+
```bash
|
|
28
|
+
cd /path/to/realtimex-crm
|
|
29
|
+
npx supabase functions deploy mergeContacts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 4. postmark
|
|
33
|
+
**Purpose:** Inbound email integration (capture emails as notes)
|
|
34
|
+
**Required for:** Email forwarding feature (optional)
|
|
35
|
+
**Deploy via CLI:**
|
|
36
|
+
```bash
|
|
37
|
+
npx supabase functions deploy postmark
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Additional Setup for postmark:**
|
|
41
|
+
- Requires Postmark account and API key
|
|
42
|
+
- Configure webhook URL in Postmark dashboard
|
|
43
|
+
- Set environment variable `POSTMARK_API_KEY` in Supabase
|
|
44
|
+
|
|
45
|
+
## Deployment Instructions
|
|
46
|
+
|
|
47
|
+
### Method 1: Supabase Dashboard (Recommended for users, updatePassword)
|
|
48
|
+
|
|
49
|
+
1. **Go to Supabase Dashboard**
|
|
50
|
+
- Open your project
|
|
51
|
+
- Click "Edge Functions" in the left sidebar
|
|
52
|
+
|
|
53
|
+
2. **Create New Function**
|
|
54
|
+
- Click "Deploy a new function"
|
|
55
|
+
- Or click "+ New Edge Function"
|
|
56
|
+
|
|
57
|
+
3. **Configure Function**
|
|
58
|
+
- **Function name:** Enter exactly as shown (e.g., `users`)
|
|
59
|
+
- **Code:** Copy entire content from `.ts` file
|
|
60
|
+
- Paste into the code editor
|
|
61
|
+
|
|
62
|
+
4. **Deploy**
|
|
63
|
+
- Click "Deploy" button
|
|
64
|
+
- Wait for deployment to complete (usually 10-20 seconds)
|
|
65
|
+
- Status should show "Active"
|
|
66
|
+
|
|
67
|
+
5. **Verify**
|
|
68
|
+
- Function should appear in the list
|
|
69
|
+
- Status: Active (green)
|
|
70
|
+
- Click function name to view logs
|
|
71
|
+
|
|
72
|
+
### Method 2: Supabase CLI (Required for mergeContacts, postmark)
|
|
73
|
+
|
|
74
|
+
If you have the repository cloned:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Deploy all functions at once
|
|
78
|
+
npx supabase functions deploy
|
|
79
|
+
|
|
80
|
+
# Or deploy specific function
|
|
81
|
+
npx supabase functions deploy mergeContacts
|
|
82
|
+
npx supabase functions deploy postmark
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Verification
|
|
86
|
+
|
|
87
|
+
After deploying, verify functions are active:
|
|
88
|
+
|
|
89
|
+
1. Go to Supabase Dashboard → Edge Functions
|
|
90
|
+
2. You should see:
|
|
91
|
+
- ✅ users (Active)
|
|
92
|
+
- ✅ updatePassword (Active)
|
|
93
|
+
- ✅ mergeContacts (Active) - if deployed
|
|
94
|
+
- ✅ postmark (Active) - if deployed
|
|
95
|
+
|
|
96
|
+
## What if Functions Fail?
|
|
97
|
+
|
|
98
|
+
### Common Issues:
|
|
99
|
+
|
|
100
|
+
**1. "Function not found" errors in CRM**
|
|
101
|
+
- Solution: Ensure function name matches exactly (case-sensitive)
|
|
102
|
+
- users, updatePassword, mergeContacts, postmark (not Users or update_password)
|
|
103
|
+
|
|
104
|
+
**2. "Unauthorized" errors**
|
|
105
|
+
- Solution: Functions automatically use `SUPABASE_SERVICE_ROLE_KEY`
|
|
106
|
+
- This is set automatically by Supabase - no action needed
|
|
107
|
+
|
|
108
|
+
**3. Deploy button grayed out**
|
|
109
|
+
- Solution: Ensure you're the project Owner
|
|
110
|
+
- Only project Owners can deploy Edge Functions
|
|
111
|
+
|
|
112
|
+
**4. Function shows "Inactive"**
|
|
113
|
+
- Solution: Click on the function → Click "Deploy" again
|
|
114
|
+
- Check logs for errors
|
|
115
|
+
|
|
116
|
+
## Testing Edge Functions
|
|
117
|
+
|
|
118
|
+
### Test users function:
|
|
119
|
+
From your CRM app, try to:
|
|
120
|
+
1. Go to Settings → Team
|
|
121
|
+
2. Click "Invite User"
|
|
122
|
+
3. Fill in details and submit
|
|
123
|
+
4. If successful, function is working!
|
|
124
|
+
|
|
125
|
+
### Test updatePassword function:
|
|
126
|
+
1. Go to user profile
|
|
127
|
+
2. Click "Reset Password"
|
|
128
|
+
3. Check if you receive password reset email
|
|
129
|
+
|
|
130
|
+
## CRM Features Without Edge Functions
|
|
131
|
+
|
|
132
|
+
If you skip Edge Function deployment:
|
|
133
|
+
|
|
134
|
+
**✅ Works:**
|
|
135
|
+
- Login/Signup (first user)
|
|
136
|
+
- Contacts, Companies, Deals
|
|
137
|
+
- Tasks & Notes
|
|
138
|
+
- Dashboard & Reports
|
|
139
|
+
- Tags & Filters
|
|
140
|
+
|
|
141
|
+
**❌ Doesn't Work:**
|
|
142
|
+
- Inviting additional team members
|
|
143
|
+
- User password reset
|
|
144
|
+
- Contact merging
|
|
145
|
+
- Email capture
|
|
146
|
+
|
|
147
|
+
## Support
|
|
148
|
+
|
|
149
|
+
- [View full documentation](https://github.com/therealtimex/realtimex-crm/blob/main/SETUP_GUIDE.md)
|
|
150
|
+
- [Report issues](https://github.com/therealtimex/realtimex-crm/issues)
|
|
151
|
+
- [Supabase Edge Functions docs](https://supabase.com/docs/guides/functions)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// RealTimeX CRM - Update Password Edge Function
|
|
2
|
+
// This function handles password reset requests for CRM users
|
|
3
|
+
//
|
|
4
|
+
// To deploy:
|
|
5
|
+
// 1. Go to Supabase Dashboard → Edge Functions
|
|
6
|
+
// 2. Click "Deploy a new function"
|
|
7
|
+
// 3. Name it: updatePassword
|
|
8
|
+
// 4. Copy and paste this entire file
|
|
9
|
+
// 5. Click "Deploy"
|
|
10
|
+
|
|
11
|
+
import { createClient } from "jsr:@supabase/supabase-js@2";
|
|
12
|
+
|
|
13
|
+
// CORS headers for API access
|
|
14
|
+
const corsHeaders = {
|
|
15
|
+
"Access-Control-Allow-Origin": "*",
|
|
16
|
+
"Access-Control-Allow-Headers":
|
|
17
|
+
"authorization, x-client-info, apikey, content-type",
|
|
18
|
+
"Access-Control-Allow-Methods": "POST, PATCH, DELETE",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function createErrorResponse(status: number, message: string) {
|
|
22
|
+
return new Response(JSON.stringify({ status, message }), {
|
|
23
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
24
|
+
status,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Initialize Supabase Admin client (uses service_role key automatically)
|
|
29
|
+
const supabaseAdmin = createClient(
|
|
30
|
+
Deno.env.get("SUPABASE_URL") ?? "",
|
|
31
|
+
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
|
|
32
|
+
{
|
|
33
|
+
auth: {
|
|
34
|
+
autoRefreshToken: false,
|
|
35
|
+
persistSession: false,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
async function updatePassword(user: any) {
|
|
41
|
+
const { data, error } = await supabaseAdmin.auth.resetPasswordForEmail(
|
|
42
|
+
user.email
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (!data || error) {
|
|
46
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return new Response(
|
|
50
|
+
JSON.stringify({
|
|
51
|
+
data,
|
|
52
|
+
}),
|
|
53
|
+
{
|
|
54
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Deno.serve(async (req: Request) => {
|
|
60
|
+
if (req.method === "OPTIONS") {
|
|
61
|
+
return new Response(null, {
|
|
62
|
+
status: 204,
|
|
63
|
+
headers: corsHeaders,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const authHeader = req.headers.get("Authorization")!;
|
|
68
|
+
const localClient = createClient(
|
|
69
|
+
Deno.env.get("SUPABASE_URL") ?? "",
|
|
70
|
+
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
|
|
71
|
+
{ global: { headers: { Authorization: authHeader } } }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const { data } = await localClient.auth.getUser();
|
|
75
|
+
if (!data?.user) {
|
|
76
|
+
return createErrorResponse(401, "Unauthorized");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (req.method === "PATCH") {
|
|
80
|
+
return updatePassword(data.user);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return createErrorResponse(405, "Method Not Allowed");
|
|
84
|
+
});
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// RealTimeX CRM - Users Edge Function
|
|
2
|
+
// This function handles creating and updating CRM users (sales team members)
|
|
3
|
+
//
|
|
4
|
+
// To deploy:
|
|
5
|
+
// 1. Go to Supabase Dashboard → Edge Functions
|
|
6
|
+
// 2. Click "Deploy a new function"
|
|
7
|
+
// 3. Name it: users
|
|
8
|
+
// 4. Copy and paste this entire file
|
|
9
|
+
// 5. Click "Deploy"
|
|
10
|
+
|
|
11
|
+
import { createClient } from "jsr:@supabase/supabase-js@2";
|
|
12
|
+
|
|
13
|
+
// CORS headers for API access
|
|
14
|
+
const corsHeaders = {
|
|
15
|
+
"Access-Control-Allow-Origin": "*",
|
|
16
|
+
"Access-Control-Allow-Headers":
|
|
17
|
+
"authorization, x-client-info, apikey, content-type",
|
|
18
|
+
"Access-Control-Allow-Methods": "POST, PATCH, DELETE",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function createErrorResponse(status: number, message: string) {
|
|
22
|
+
return new Response(JSON.stringify({ status, message }), {
|
|
23
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
24
|
+
status,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Initialize Supabase Admin client (uses service_role key automatically)
|
|
29
|
+
const supabaseAdmin = createClient(
|
|
30
|
+
Deno.env.get("SUPABASE_URL") ?? "",
|
|
31
|
+
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
|
|
32
|
+
{
|
|
33
|
+
auth: {
|
|
34
|
+
autoRefreshToken: false,
|
|
35
|
+
persistSession: false,
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
async function updateSaleDisabled(user_id: string, disabled: boolean) {
|
|
41
|
+
return await supabaseAdmin
|
|
42
|
+
.from("sales")
|
|
43
|
+
.update({ disabled: disabled ?? false })
|
|
44
|
+
.eq("user_id", user_id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function updateSaleAdministrator(
|
|
48
|
+
user_id: string,
|
|
49
|
+
administrator: boolean
|
|
50
|
+
) {
|
|
51
|
+
const { data: sales, error: salesError } = await supabaseAdmin
|
|
52
|
+
.from("sales")
|
|
53
|
+
.update({ administrator })
|
|
54
|
+
.eq("user_id", user_id)
|
|
55
|
+
.select("*");
|
|
56
|
+
|
|
57
|
+
if (!sales?.length || salesError) {
|
|
58
|
+
console.error("Error updating user:", salesError);
|
|
59
|
+
throw salesError ?? new Error("Failed to update sale");
|
|
60
|
+
}
|
|
61
|
+
return sales.at(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function updateSaleAvatar(user_id: string, avatar: string) {
|
|
65
|
+
const { data: sales, error: salesError } = await supabaseAdmin
|
|
66
|
+
.from("sales")
|
|
67
|
+
.update({ avatar })
|
|
68
|
+
.eq("user_id", user_id)
|
|
69
|
+
.select("*");
|
|
70
|
+
|
|
71
|
+
if (!sales?.length || salesError) {
|
|
72
|
+
console.error("Error updating user:", salesError);
|
|
73
|
+
throw salesError ?? new Error("Failed to update sale");
|
|
74
|
+
}
|
|
75
|
+
return sales.at(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function inviteUser(req: Request, currentUserSale: any) {
|
|
79
|
+
const { email, password, first_name, last_name, disabled, administrator } =
|
|
80
|
+
await req.json();
|
|
81
|
+
|
|
82
|
+
if (!currentUserSale.administrator) {
|
|
83
|
+
return createErrorResponse(401, "Not Authorized");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { data, error: userError } = await supabaseAdmin.auth.admin.createUser({
|
|
87
|
+
email,
|
|
88
|
+
password,
|
|
89
|
+
user_metadata: { first_name, last_name },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const { error: emailError } =
|
|
93
|
+
await supabaseAdmin.auth.admin.inviteUserByEmail(email);
|
|
94
|
+
|
|
95
|
+
if (!data?.user || userError) {
|
|
96
|
+
console.error(`Error inviting user: user_error=${userError}`);
|
|
97
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!data?.user || userError || emailError) {
|
|
101
|
+
console.error(`Error inviting user, email_error=${emailError}`);
|
|
102
|
+
return createErrorResponse(500, "Failed to send invitation mail");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await updateSaleDisabled(data.user.id, disabled);
|
|
107
|
+
const sale = await updateSaleAdministrator(data.user.id, administrator);
|
|
108
|
+
|
|
109
|
+
return new Response(
|
|
110
|
+
JSON.stringify({
|
|
111
|
+
data: sale,
|
|
112
|
+
}),
|
|
113
|
+
{
|
|
114
|
+
headers: { "Content-Type": "application/json", ...corsHeaders },
|
|
115
|
+
}
|
|
116
|
+
);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.error("Error patching sale:", e);
|
|
119
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function patchUser(req: Request, currentUserSale: any) {
|
|
124
|
+
const {
|
|
125
|
+
sales_id,
|
|
126
|
+
email,
|
|
127
|
+
first_name,
|
|
128
|
+
last_name,
|
|
129
|
+
avatar,
|
|
130
|
+
administrator,
|
|
131
|
+
disabled,
|
|
132
|
+
} = await req.json();
|
|
133
|
+
const { data: sale } = await supabaseAdmin
|
|
134
|
+
.from("sales")
|
|
135
|
+
.select("*")
|
|
136
|
+
.eq("id", sales_id)
|
|
137
|
+
.single();
|
|
138
|
+
|
|
139
|
+
if (!sale) {
|
|
140
|
+
return createErrorResponse(404, "Not Found");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Users can only update their own profile unless they are an administrator
|
|
144
|
+
if (!currentUserSale.administrator && currentUserSale.id !== sale.id) {
|
|
145
|
+
return createErrorResponse(401, "Not Authorized");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { data, error: userError } =
|
|
149
|
+
await supabaseAdmin.auth.admin.updateUserById(sale.user_id, {
|
|
150
|
+
email,
|
|
151
|
+
ban_duration: disabled ? "87600h" : "none",
|
|
152
|
+
user_metadata: { first_name, last_name },
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (!data?.user || userError) {
|
|
156
|
+
console.error("Error patching user:", userError);
|
|
157
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (avatar) {
|
|
161
|
+
await updateSaleAvatar(data.user.id, avatar);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Only administrators can update the administrator and disabled status
|
|
165
|
+
if (!currentUserSale.administrator) {
|
|
166
|
+
const { data: new_sale } = await supabaseAdmin
|
|
167
|
+
.from("sales")
|
|
168
|
+
.select("*")
|
|
169
|
+
.eq("id", sales_id)
|
|
170
|
+
.single();
|
|
171
|
+
return new Response(
|
|
172
|
+
JSON.stringify({
|
|
173
|
+
data: new_sale,
|
|
174
|
+
}),
|
|
175
|
+
{
|
|
176
|
+
headers: {
|
|
177
|
+
"Content-Type": "application/json",
|
|
178
|
+
...corsHeaders,
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
await updateSaleDisabled(data.user.id, disabled);
|
|
186
|
+
const sale = await updateSaleAdministrator(data.user.id, administrator);
|
|
187
|
+
return new Response(
|
|
188
|
+
JSON.stringify({
|
|
189
|
+
data: sale,
|
|
190
|
+
}),
|
|
191
|
+
{
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "application/json",
|
|
194
|
+
...corsHeaders,
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
} catch (e) {
|
|
199
|
+
console.error("Error patching sale:", e);
|
|
200
|
+
return createErrorResponse(500, "Internal Server Error");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
Deno.serve(async (req: Request) => {
|
|
205
|
+
if (req.method === "OPTIONS") {
|
|
206
|
+
return new Response(null, {
|
|
207
|
+
status: 204,
|
|
208
|
+
headers: corsHeaders,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const authHeader = req.headers.get("Authorization")!;
|
|
213
|
+
const localClient = createClient(
|
|
214
|
+
Deno.env.get("SUPABASE_URL") ?? "",
|
|
215
|
+
Deno.env.get("SUPABASE_ANON_KEY") ?? "",
|
|
216
|
+
{ global: { headers: { Authorization: authHeader } } }
|
|
217
|
+
);
|
|
218
|
+
const { data } = await localClient.auth.getUser();
|
|
219
|
+
if (!data?.user) {
|
|
220
|
+
return createErrorResponse(401, "Unauthorized");
|
|
221
|
+
}
|
|
222
|
+
const currentUserSale = await supabaseAdmin
|
|
223
|
+
.from("sales")
|
|
224
|
+
.select("*")
|
|
225
|
+
.eq("user_id", data.user.id)
|
|
226
|
+
.single();
|
|
227
|
+
|
|
228
|
+
if (!currentUserSale?.data) {
|
|
229
|
+
return createErrorResponse(401, "Unauthorized");
|
|
230
|
+
}
|
|
231
|
+
if (req.method === "POST") {
|
|
232
|
+
return inviteUser(req, currentUserSale.data);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (req.method === "PATCH") {
|
|
236
|
+
return patchUser(req, currentUserSale.data);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return createErrorResponse(405, "Method Not Allowed");
|
|
240
|
+
});
|
|
@@ -3,8 +3,6 @@ import { useDataProvider } from "ra-core";
|
|
|
3
3
|
import { Navigate } from "react-router-dom";
|
|
4
4
|
import { LoginPage } from "@/components/admin/login-page";
|
|
5
5
|
import { checkDatabaseHealth } from "@/lib/database-health-check";
|
|
6
|
-
import { getSupabaseConfig } from "@/lib/supabase-config";
|
|
7
|
-
import { DatabaseSetupGuide } from "../setup/DatabaseSetupGuide";
|
|
8
6
|
|
|
9
7
|
import type { CrmDataProvider } from "../providers/types";
|
|
10
8
|
import { LoginSkeleton } from "./LoginSkeleton";
|
|
@@ -38,17 +36,91 @@ export const StartPage = () => {
|
|
|
38
36
|
// Show loading state
|
|
39
37
|
if (isCheckingHealth || isCheckingInit) return <LoginSkeleton />;
|
|
40
38
|
|
|
41
|
-
// Show
|
|
39
|
+
// Show error if schema is missing
|
|
42
40
|
if (healthStatus && !healthStatus.isHealthy) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
return (
|
|
42
|
+
<div className="flex items-center justify-center min-h-screen p-4">
|
|
43
|
+
<div className="max-w-md w-full space-y-4">
|
|
44
|
+
<div className="text-center space-y-2">
|
|
45
|
+
<div className="flex justify-center">
|
|
46
|
+
<div className="rounded-full bg-destructive/10 p-3">
|
|
47
|
+
<svg
|
|
48
|
+
className="h-6 w-6 text-destructive"
|
|
49
|
+
fill="none"
|
|
50
|
+
viewBox="0 0 24 24"
|
|
51
|
+
stroke="currentColor"
|
|
52
|
+
>
|
|
53
|
+
<path
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
strokeWidth={2}
|
|
57
|
+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
58
|
+
/>
|
|
59
|
+
</svg>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<h1 className="text-2xl font-bold">Database Not Configured</h1>
|
|
63
|
+
<p className="text-muted-foreground">
|
|
64
|
+
Your Supabase database is missing required tables and functions.
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="bg-muted p-4 rounded-lg space-y-2">
|
|
69
|
+
<p className="text-sm font-semibold">Missing tables:</p>
|
|
70
|
+
<div className="flex flex-wrap gap-2">
|
|
71
|
+
{healthStatus.missingTables.map((table) => (
|
|
72
|
+
<span
|
|
73
|
+
key={table}
|
|
74
|
+
className="px-2 py-1 bg-background text-xs rounded border"
|
|
75
|
+
>
|
|
76
|
+
{table}
|
|
77
|
+
</span>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div className="space-y-3 text-sm">
|
|
83
|
+
<p className="font-semibold">To fix this:</p>
|
|
84
|
+
<ol className="list-decimal list-inside space-y-2 text-muted-foreground ml-2">
|
|
85
|
+
<li>
|
|
86
|
+
Download{" "}
|
|
87
|
+
<a
|
|
88
|
+
href="https://raw.githubusercontent.com/therealtimex/realtimex-crm/main/public/setup.sql"
|
|
89
|
+
target="_blank"
|
|
90
|
+
rel="noopener noreferrer"
|
|
91
|
+
className="text-primary hover:underline font-semibold"
|
|
92
|
+
download="setup.sql"
|
|
93
|
+
>
|
|
94
|
+
setup.sql
|
|
95
|
+
</a>
|
|
96
|
+
</li>
|
|
97
|
+
<li>Open your Supabase project's SQL Editor</li>
|
|
98
|
+
<li>Copy all contents from setup.sql and paste into the editor</li>
|
|
99
|
+
<li>Click "Run" to execute the setup</li>
|
|
100
|
+
<li>Return here and reload the page</li>
|
|
101
|
+
</ol>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<button
|
|
105
|
+
onClick={() => window.location.reload()}
|
|
106
|
+
className="w-full px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
|
|
107
|
+
>
|
|
108
|
+
Reload Page
|
|
109
|
+
</button>
|
|
110
|
+
|
|
111
|
+
<div className="pt-4 border-t text-center">
|
|
112
|
+
<a
|
|
113
|
+
href="https://github.com/therealtimex/realtimex-crm#database-setup"
|
|
114
|
+
target="_blank"
|
|
115
|
+
rel="noopener noreferrer"
|
|
116
|
+
className="text-sm text-primary hover:underline"
|
|
117
|
+
>
|
|
118
|
+
View detailed setup guide →
|
|
119
|
+
</a>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
52
124
|
}
|
|
53
125
|
|
|
54
126
|
// Show login page if there's an error or already initialized
|