Skip to content
This repository was archived by the owner on Nov 29, 2025. It is now read-only.
Merged
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
95 changes: 91 additions & 4 deletions src/routes/(app)/app/users/new/+page.server.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,91 @@
/** @type {import('./$types').PageServerLoad} */
export async function load() {
return {};
};
import { prisma } from '$lib/prisma';
import { fail, redirect } from '@sveltejs/kit';

export const actions = {
addUser: async ({ request, locals }) => {
if (!locals.org || !locals.org.id) {
return fail(500, { error: 'Organization not found' });
}
const organizationId = locals.org.id;

const formData = await request.formData();
const email = formData.get('email')?.toString();
const name = formData.get('name')?.toString();
const role = formData.get('role')?.toString();

// Basic validation
if (!email || !name || !role) {
return fail(400, { error: 'Missing required fields', data: { email, name, role } });
}

if (role !== 'ADMIN' && role !== 'USER') {
return fail(400, { error: 'Invalid role specified', data: { email, name, role } });
}

let userIdToLink;

try {
// Check if user exists globally
let existingUser = await prisma.user.findUnique({
where: { email },
});

if (existingUser) {
userIdToLink = existingUser.id;
// Check if user is already in this organization
const existingUserOrganization = await prisma.userOrganization.findFirst({
where: {
userId: existingUser.id,
organizationId: organizationId,
},
});

if (existingUserOrganization) {
return fail(400, { error: 'User already exists in this organization', data: { email, name, role } });
}
// If user exists globally but not in this org, we'll link them later
} else {
// Create new user if they don't exist globally
// Assuming user_id should be unique, often email is used or a generated cuid/uuid
// For now, let's assume user_id can be the email if it's meant to be a unique string identifier
// and the actual primary key is 'id' (auto-increment or CUID).
// If 'user_id' is meant to be the Clerk/Auth0 ID, this might need adjustment
// based on how that ID is obtained or if it's set post-creation.
// The schema provided has `user_id String @unique`
const newUser = await prisma.user.create({
data: {
email,
name,
user_id: email, // Assuming email can serve as the initial unique user_id
},
});
userIdToLink = newUser.id;
}
} catch (error) {
console.error('Error finding or creating user:', error);
return fail(500, { error: 'Could not process user information', data: { email, name, role } });
}

try {
// Link user to the organization
await prisma.userOrganization.create({
data: {
userId: userIdToLink,
organizationId: organizationId,
role: role, // 'ADMIN' or 'USER'
},
});

// On success, it's often good to redirect to avoid form resubmission issues,
// or return a success object that the page can use to update its state.
// For this task, returning a success object is specified.
return { success: true, message: 'User added successfully!' };

} catch (error) {
console.error('Error linking user to organization:', error);
// This could happen if, for example, a race condition occurred or a DB constraint was violated.
// The earlier check for existingUserOrganization should prevent most common cases.
return fail(500, { error: 'Could not add user to organization', data: { email, name, role } });
}
},
};
141 changes: 93 additions & 48 deletions src/routes/(app)/app/users/new/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,51 +1,96 @@
<script>
/** @type {{ data: import('./$types').PageData }} */
let { data } = $props();
<script lang="ts">
export let form;
</script>

<div class="min-h-screen flex items-center justify-center bg-gray-50 py-8 px-4 sm:px-6 lg:px-8">
<div class="max-w-xl w-full space-y-8">
<div class="bg-white p-8 rounded-2xl shadow-xl">
<h2 class="text-2xl font-bold text-gray-900 mb-6 text-center">Create New User</h2>
<form class="space-y-6">
<!-- Profile Photo Upload -->


<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Name -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Name</label>
<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" />
</div>
<!-- Email -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<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="user@email.com" />
</div>
<!-- Phone -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Phone</label>
<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" />
</div>
<!-- Department -->

<!-- Role -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Role</label>
<select class="mt-1 block w-full rounded-lg border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500">
<option value="USER">User</option>
<option value="ADMIN">Admin</option>
</select>
</div>
</div>


<!-- Actions -->
<div class="flex items-center justify-between mt-6">
<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>
<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>
</div>
</form>
<svelte:head>
<title>New User</title>
</svelte:head>

<div class="container">
<h1>Add New User</h1>

{#if form?.success}
<p class="success">User added successfully!</p>
{/if}

{#if form?.error}
<p class="error">{form.error}</p>
{/if}

<form method="POST" action="?/addUser">
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required value={form?.data?.email ?? ''} />
{#if form?.errors?.email}
<p class="error">{form.errors.email}</p>
{/if}
</div>

<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required value={form?.data?.name ?? ''} />
{#if form?.errors?.name}
<p class="error">{form.errors.name}</p>
{/if}
</div>

<div class="form-group">
<label for="role">Role:</label>
<select id="role" name="role">
<option value="USER" selected={form?.data?.role === 'USER' || !form?.data?.role}>USER</option>
<option value="ADMIN" selected={form?.data?.role === 'ADMIN'}>ADMIN</option>
</select>
{#if form?.errors?.role}
<p class="error">{form.errors.role}</p>
{/if}
</div>
</div>
</div>

<button type="submit">Add User</button>
</form>
</div>

<style>
.container {
max-width: 500px;
margin: 2rem auto;
padding: 1rem;
border: 1px solid #ccc;
border-radius: 8px;
}
.form-group {
margin-bottom: 1rem;
}
label {
display: block;
margin-bottom: 0.25rem;
}
input[type="email"],
input[type="text"],
select {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button[type="submit"] {
background-color: #007bff;
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
button[type="submit"]:hover {
background-color: #0056b3;
}
.error {
color: red;
font-size: 0.875rem;
margin-top: 0.25rem;
}
.success {
color: green;
margin-bottom: 1rem;
}
</style>