Dev Partner
-Your Development Team When You Need One
+Dev+1
+Your Extra Development Help When The Project Needs Momentum
Need help building, fixing, launching, or improving software? Runlevel Systems can work alongside your team or handle development for you.
DESIGN โข DEBUG โข DEPLOY
Who Dev Partner Is For
+Who Dev+1 Is For
- Customers who need help finishing a project
- Customers who need a complete application built diff --git a/estimate.php b/estimate.php index 234b081..0d41886 100644 --- a/estimate.php +++ b/estimate.php @@ -1,124 +1,162 @@ (string)($fullUser['display_name'] ?? ''), + 'email' => (string)($fullUser['email'] ?? ''), + 'phone' => (string)($fullUser['phone'] ?? ''), + 'discord_username' => (string)($fullUser['discord_username'] ?? ''), + 'contact_method' => 'Email', + 'project_type' => '', + 'repo_link' => '', + 'description' => '', + 'desired_username' => '', +]; + +$form = $defaults; +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + foreach ($form as $key => $value) { + $form[$key] = trim((string)($_POST[$key] ?? '')); + } } if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_estimate'])) { - $name = trim($_POST['name'] ?? ''); - $email = trim($_POST['email'] ?? ''); - $phone = trim($_POST['phone'] ?? ''); - $contactMethod = trim($_POST['contact_method'] ?? ''); - $projectType = trim($_POST['project_type'] ?? ''); - $whatNeeded = trim($_POST['what_needed'] ?? ''); - $projectStage = trim($_POST['project_stage'] ?? ''); - $approxSize = trim($_POST['approx_size'] ?? ''); - $hasFiles = trim($_POST['has_files'] ?? ''); - $repoLink = trim($_POST['repo_link'] ?? ''); - $description = trim($_POST['description'] ?? ''); - $timeline = trim($_POST['timeline'] ?? ''); - $budget = trim($_POST['budget'] ?? ''); - $agreed = isset($_POST['agreement']); - - $isGameType = in_array($projectType, $gameTypes, true); + $name = $form['name']; + $email = $form['email']; + $phone = $form['phone']; + $discordUsername = $form['discord_username']; + $contactMethod = $form['contact_method']; + $projectType = $form['project_type']; + $repoLink = $form['repo_link']; + $description = $form['description']; + $desiredUsername = strtolower($form['desired_username']); if ($name === '') { $error = 'Please enter your name.'; - } elseif ($contactMethod === '') { - $error = 'Please select a preferred contact method.'; - } elseif ($projectType === '') { + } elseif ($email === '') { + $error = 'Email is required.'; + } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $error = 'Please enter a valid email address.'; + } elseif (!in_array($projectType, $projectTypes, true)) { $error = 'Please select a project type.'; - } elseif ($whatNeeded === '') { - $error = 'Please select what you need.'; } elseif ($description === '') { $error = 'Please describe your request.'; - } elseif (!$agreed) { - $error = 'Please check the acknowledgement checkbox before submitting.'; + } elseif (!in_array($contactMethod, $contactOptions, true)) { + $error = 'Please select a preferred contact method.'; } elseif ($repoLink !== '' && !filter_var($repoLink, FILTER_VALIDATE_URL)) { $error = 'The repository or project link does not appear to be a valid URL.'; - } elseif ($email === '' && $phone === '') { - // Soft validation: warn but allow submit if Discord is the preferred contact for a game project - if ($isGameType && $contactMethod === 'Discord') { - $warning = 'Please join our Discord and mention your Estimate ID so we can connect your message to this request.'; - } else { - $error = 'Please provide at least one way for us to contact you, such as email or phone.'; - } - } elseif ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) { - $error = 'The email address provided does not appear to be valid.'; + } elseif (!$isLoggedIn && $desiredUsername === '') { + $error = 'Please enter a desired username.'; + } elseif (!$isLoggedIn && !portalIsValidEstimateUsername($desiredUsername)) { + $error = 'Username must be at least 3 characters and use only letters, numbers, dash, or underscore.'; } + $createdAccount = null; + $clientUsername = ''; if ($error === '') { - // Determine rough category for confirmation display - $category = 'Software Project'; - if (in_array($projectType, ['Training / Simulation'], true)) { - $category = 'Simulation / Training Project'; - } elseif (in_array($whatNeeded, ['Fix something broken', 'Review or rescue a project'], true) || $projectStage === 'It worked before but broke') { - $category = 'Project Rescue'; - } elseif (in_array($approxSize, ['Very small task'], true) || $whatNeeded === 'Add a feature') { - $category = 'Quick Fix / Small Task'; - } elseif (in_array($approxSize, ['Ongoing work'], true) || $whatNeeded === 'Ongoing help') { - $category = 'Ongoing Development Support'; - } elseif (in_array($approxSize, ['Small project'], true) && $projectStage === 'Just an idea') { - $category = 'Starter Project'; - } elseif (in_array($projectType, ['Business application', 'Infrastructure / Backend'], true)) { - $category = 'Business / Infrastructure Project'; - } elseif ($isGameType) { - $category = 'Game / Interactive Project'; + if ($isLoggedIn && !empty($sessionUser['username'])) { + $clientUsername = (string)$sessionUser['username']; + } else { + $createError = ''; + $createdAccount = portalCreateClientUserFromEstimate( + $desiredUsername, + $name, + $email, + $phone, + $discordUsername, + $createError + ); + if ($createdAccount === false) { + $error = $createError !== '' ? $createError : 'Unable to create your client account right now.'; + } else { + $clientUsername = (string)$createdAccount['username']; + } } + } - $estimateId = generateEstimateId(); - + if ($error === '') { + $estimateId = portalGenerateUniqueEstimateId(); $request = [ - 'estimate_id' => $estimateId, - 'id' => bin2hex(random_bytes(8)), - 'name' => $name, - 'email' => $email, - 'phone' => $phone, + 'id' => bin2hex(random_bytes(8)), + 'estimate_id' => $estimateId, + 'client_username' => $clientUsername, + 'name' => $name, + 'email' => $email, + 'phone' => $phone, + 'discord_username' => $discordUsername, 'contact_method' => $contactMethod, - 'project_type' => $projectType, - 'what_needed' => $whatNeeded, - 'project_stage' => $projectStage, - 'approx_size' => $approxSize, - 'has_files' => $hasFiles, - 'repo_link' => $repoLink, - 'description' => $description, - 'timeline' => $timeline, - 'budget' => $budget, - 'category' => $category, - 'status' => 'new', - 'notes' => '', - 'created_at' => date('c'), + 'project_type' => $projectType, + 'repo_link' => $repoLink, + 'description' => $description, + 'status' => 'new', + 'notes' => '', + 'created_at' => date('c'), ]; - if (portalAppendEstimateRequest($request)) { - $success = true; - $submitted = $request; - } else { + if (!portalAppendEstimateRequest($request)) { $error = 'There was an error saving your request. Please try again or contact us directly.'; + } else { + $success = true; + $submitted = [ + 'request' => $request, + 'created_account' => $createdAccount, + 'needs_verification' => !$isLoggedIn, + 'show_discord' => in_array($projectType, $gameTypes, true), + ]; + + if ($createdAccount !== null) { + $verifyLink = 'https://' . ($_SERVER['HTTP_HOST'] ?? 'runlevel.systems') + . '/verify-email.php?token=' . urlencode((string)$createdAccount['verification_token']); + $subject = 'Verify your Runlevel Systems account'; + $body = "Hello {$name},\n\n" + . "We received your project estimate request.\n\n" + . "Estimate ID:\n{$estimateId}\n\n" + . "A client dashboard account was created for you:\n\n" + . "Username:\n{$clientUsername}\n\n" + . "Temporary password:\n{$createdAccount['temporary_password']}\n\n" + . "Please verify your email before logging in:\n\n" + . "{$verifyLink}\n\n" + . "After verification, you can log in to view your dashboard and track project information.\n\n" + . "Runlevel Systems\n" + . "DESIGN โข DEBUG โข DEPLOY\n"; + send_email($email, $subject, $body); + } } } } -// Determine if previously selected project type is game-related (for JS init) -$selectedType = $_POST['project_type'] ?? ''; -$isGameSelected = in_array($selectedType, $gameTypes, true); +$showDiscordHint = in_array($form['project_type'], $gameTypes, true) || $form['contact_method'] === 'Discord'; ?> @@ -128,42 +166,37 @@ function generateEstimateId() {
Project Requirements & Estimate
-Tell us what you need. We will help figure out the next step.
+Please complete and submit this estimate request so we have the information needed to review your project. After submission, we will give you an Estimate ID that you can reference when contacting us.
โ Request Received
-Thank you, . We received your project information.
- -- Please save this ID. If you contact us later or submit another request, this helps us find your project quickly. -
- -
- Likely project category:
-
-
-
- Next step:
- Runlevel Systems will review your request and contact you using the contact information provided.
- You may also contact us directly and reference your Estimate ID.
-
- Preferred contact method: -
- - -- For game, server, or mod-related projects, you can also join our Discord and mention your Estimate ID. -
- - Join Discord -โ Request Received
+We received your estimate request.
+Your client account:
+ - - + +Please check your email to verify your account before logging in.
+Temporary password:
+Please save this. You can change it later once account settings are added.
+TODO: Add password reset flow and remove temporary-password delivery.
- -We will review your request and contact you using the information provided.
+ + +