Skip to content
This repository was archived by the owner on Nov 29, 2025. It is now read-only.

Commit 7ac4c99

Browse files
authored
Merge pull request #14 from MicroPyramid/feat/add-user-to-org
Feat/add user to org
2 parents f39ac60 + 1bf0e0e commit 7ac4c99

File tree

2 files changed

+184
-52
lines changed

2 files changed

+184
-52
lines changed
Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,91 @@
1-
/** @type {import('./$types').PageServerLoad} */
2-
export async function load() {
3-
return {};
4-
};
1+
import { prisma } from '$lib/prisma';
2+
import { fail, redirect } from '@sveltejs/kit';
3+
4+
export const actions = {
5+
addUser: async ({ request, locals }) => {
6+
if (!locals.org || !locals.org.id) {
7+
return fail(500, { error: 'Organization not found' });
8+
}
9+
const organizationId = locals.org.id;
10+
11+
const formData = await request.formData();
12+
const email = formData.get('email')?.toString();
13+
const name = formData.get('name')?.toString();
14+
const role = formData.get('role')?.toString();
15+
16+
// Basic validation
17+
if (!email || !name || !role) {
18+
return fail(400, { error: 'Missing required fields', data: { email, name, role } });
19+
}
20+
21+
if (role !== 'ADMIN' && role !== 'USER') {
22+
return fail(400, { error: 'Invalid role specified', data: { email, name, role } });
23+
}
24+
25+
let userIdToLink;
26+
27+
try {
28+
// Check if user exists globally
29+
let existingUser = await prisma.user.findUnique({
30+
where: { email },
31+
});
32+
33+
if (existingUser) {
34+
userIdToLink = existingUser.id;
35+
// Check if user is already in this organization
36+
const existingUserOrganization = await prisma.userOrganization.findFirst({
37+
where: {
38+
userId: existingUser.id,
39+
organizationId: organizationId,
40+
},
41+
});
42+
43+
if (existingUserOrganization) {
44+
return fail(400, { error: 'User already exists in this organization', data: { email, name, role } });
45+
}
46+
// If user exists globally but not in this org, we'll link them later
47+
} else {
48+
// Create new user if they don't exist globally
49+
// Assuming user_id should be unique, often email is used or a generated cuid/uuid
50+
// For now, let's assume user_id can be the email if it's meant to be a unique string identifier
51+
// and the actual primary key is 'id' (auto-increment or CUID).
52+
// If 'user_id' is meant to be the Clerk/Auth0 ID, this might need adjustment
53+
// based on how that ID is obtained or if it's set post-creation.
54+
// The schema provided has `user_id String @unique`
55+
const newUser = await prisma.user.create({
56+
data: {
57+
email,
58+
name,
59+
user_id: email, // Assuming email can serve as the initial unique user_id
60+
},
61+
});
62+
userIdToLink = newUser.id;
63+
}
64+
} catch (error) {
65+
console.error('Error finding or creating user:', error);
66+
return fail(500, { error: 'Could not process user information', data: { email, name, role } });
67+
}
68+
69+
try {
70+
// Link user to the organization
71+
await prisma.userOrganization.create({
72+
data: {
73+
userId: userIdToLink,
74+
organizationId: organizationId,
75+
role: role, // 'ADMIN' or 'USER'
76+
},
77+
});
78+
79+
// On success, it's often good to redirect to avoid form resubmission issues,
80+
// or return a success object that the page can use to update its state.
81+
// For this task, returning a success object is specified.
82+
return { success: true, message: 'User added successfully!' };
83+
84+
} catch (error) {
85+
console.error('Error linking user to organization:', error);
86+
// This could happen if, for example, a race condition occurred or a DB constraint was violated.
87+
// The earlier check for existingUserOrganization should prevent most common cases.
88+
return fail(500, { error: 'Could not add user to organization', data: { email, name, role } });
89+
}
90+
},
91+
};
Lines changed: 93 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,96 @@
1-
<script>
2-
/** @type {{ data: import('./$types').PageData }} */
3-
let { data } = $props();
1+
<script lang="ts">
2+
export let form;
43
</script>
54

6-
<div class="min-h-screen flex items-center justify-center bg-gray-50 py-8 px-4 sm:px-6 lg:px-8">
7-
<div class="max-w-xl w-full space-y-8">
8-
<div class="bg-white p-8 rounded-2xl shadow-xl">
9-
<h2 class="text-2xl font-bold text-gray-900 mb-6 text-center">Create New User</h2>
10-
<form class="space-y-6">
11-
<!-- Profile Photo Upload -->
12-
13-
14-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
15-
<!-- Name -->
16-
<div>
17-
<label class="block text-sm font-medium text-gray-700 mb-1">Name</label>
18-
<input type="text" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" placeholder="Full Name" />
19-
</div>
20-
<!-- Email -->
21-
<div>
22-
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
23-
<input type="email" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" placeholder="[email protected]" />
24-
</div>
25-
<!-- Phone -->
26-
<div>
27-
<label class="block text-sm font-medium text-gray-700 mb-1">Phone</label>
28-
<input type="tel" class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" placeholder="+1 234 567 8901" />
29-
</div>
30-
<!-- Department -->
31-
32-
<!-- Role -->
33-
<div>
34-
<label class="block text-sm font-medium text-gray-700 mb-1">Role</label>
35-
<select class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
36-
<option value="USER">User</option>
37-
<option value="ADMIN">Admin</option>
38-
</select>
39-
</div>
40-
</div>
41-
42-
43-
<!-- Actions -->
44-
<div class="flex items-center justify-between mt-6">
45-
<button type="submit" class="w-full md:w-auto px-6 py-3 bg-blue-600 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition">Create User</button>
46-
<a href="/app/users" class="w-full md:w-auto mt-2 md:mt-0 md:ml-4 px-6 py-3 bg-white text-gray-700 font-semibold rounded-lg border border-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition text-center">Cancel</a>
47-
</div>
48-
</form>
5+
<svelte:head>
6+
<title>New User</title>
7+
</svelte:head>
8+
9+
<div class="container">
10+
<h1>Add New User</h1>
11+
12+
{#if form?.success}
13+
<p class="success">User added successfully!</p>
14+
{/if}
15+
16+
{#if form?.error}
17+
<p class="error">{form.error}</p>
18+
{/if}
19+
20+
<form method="POST" action="?/addUser">
21+
<div class="form-group">
22+
<label for="email">Email:</label>
23+
<input type="email" id="email" name="email" required value={form?.data?.email ?? ''} />
24+
{#if form?.errors?.email}
25+
<p class="error">{form.errors.email}</p>
26+
{/if}
27+
</div>
28+
29+
<div class="form-group">
30+
<label for="name">Name:</label>
31+
<input type="text" id="name" name="name" required value={form?.data?.name ?? ''} />
32+
{#if form?.errors?.name}
33+
<p class="error">{form.errors.name}</p>
34+
{/if}
35+
</div>
36+
37+
<div class="form-group">
38+
<label for="role">Role:</label>
39+
<select id="role" name="role">
40+
<option value="USER" selected={form?.data?.role === 'USER' || !form?.data?.role}>USER</option>
41+
<option value="ADMIN" selected={form?.data?.role === 'ADMIN'}>ADMIN</option>
42+
</select>
43+
{#if form?.errors?.role}
44+
<p class="error">{form.errors.role}</p>
45+
{/if}
4946
</div>
50-
</div>
51-
</div>
47+
48+
<button type="submit">Add User</button>
49+
</form>
50+
</div>
51+
52+
<style>
53+
.container {
54+
max-width: 500px;
55+
margin: 2rem auto;
56+
padding: 1rem;
57+
border: 1px solid #ccc;
58+
border-radius: 8px;
59+
}
60+
.form-group {
61+
margin-bottom: 1rem;
62+
}
63+
label {
64+
display: block;
65+
margin-bottom: 0.25rem;
66+
}
67+
input[type="email"],
68+
input[type="text"],
69+
select {
70+
width: 100%;
71+
padding: 0.5rem;
72+
border: 1px solid #ddd;
73+
border-radius: 4px;
74+
box-sizing: border-box;
75+
}
76+
button[type="submit"] {
77+
background-color: #007bff;
78+
color: white;
79+
padding: 0.75rem 1.5rem;
80+
border: none;
81+
border-radius: 4px;
82+
cursor: pointer;
83+
}
84+
button[type="submit"]:hover {
85+
background-color: #0056b3;
86+
}
87+
.error {
88+
color: red;
89+
font-size: 0.875rem;
90+
margin-top: 0.25rem;
91+
}
92+
.success {
93+
color: green;
94+
margin-bottom: 1rem;
95+
}
96+
</style>

0 commit comments

Comments
 (0)