492 lines
22 KiB
Text
492 lines
22 KiB
Text
---
|
|
// Import the majors list and FilePreviewModal
|
|
import allMajors from "../../data/allUCSDMajors.txt?raw";
|
|
import FilePreviewModal from "../modals/FilePreviewModal.astro";
|
|
|
|
const majorsList: string[] = allMajors
|
|
.split("\n")
|
|
.filter((major: string) => major.trim())
|
|
.sort((a, b) => a.localeCompare(b)); // Sort alphabetically
|
|
---
|
|
|
|
<div class="card bg-base-100 shadow-xl">
|
|
<div class="card-body p-6">
|
|
<!-- Header -->
|
|
<div class="flex items-center gap-4 mb-8">
|
|
<div class="flex-1">
|
|
<h2 class="card-title text-2xl">Profile Settings</h2>
|
|
<p class="text-base-content/70 mt-1">
|
|
Manage your academic information and preferences
|
|
</p>
|
|
</div>
|
|
<div class="flex-none">
|
|
<button class="btn btn-primary" id="saveSettings">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 mr-2"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
clip-rule="evenodd"></path>
|
|
</svg>
|
|
Save Changes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Sections -->
|
|
<div class="space-y-8">
|
|
<!-- Academic Information Section -->
|
|
<div class="bg-base-200 rounded-box p-6">
|
|
<div class="flex items-center gap-2 mb-6">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z"
|
|
></path>
|
|
</svg>
|
|
<h3 class="text-xl font-medium">Academic Information</h3>
|
|
</div>
|
|
|
|
<div class="grid gap-6">
|
|
<!-- Major Selection -->
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium">Major</span>
|
|
<span class="label-text-alt text-base-content/70"
|
|
>Select your current major</span
|
|
>
|
|
</label>
|
|
<select
|
|
id="majorSelect"
|
|
class="select select-bordered w-full"
|
|
>
|
|
<option value="">Select your major</option>
|
|
{
|
|
majorsList.map((major: string) => (
|
|
<option value={major}>{major}</option>
|
|
))
|
|
}
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Graduation Year -->
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium"
|
|
>Expected Graduation</span
|
|
>
|
|
<span class="label-text-alt text-base-content/70"
|
|
>When do you plan to graduate?</span
|
|
>
|
|
</label>
|
|
<select
|
|
id="gradYearSelect"
|
|
class="select select-bordered w-full"
|
|
>
|
|
<option value="">Select graduation year</option>
|
|
{
|
|
Array.from({ length: 6 }, (_, i) => {
|
|
const year = new Date().getFullYear() + i;
|
|
return <option value={year}>{year}</option>;
|
|
})
|
|
}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- IEEE Information Section -->
|
|
<div class="bg-base-200 rounded-box p-6">
|
|
<div class="flex items-center gap-2 mb-6">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M10 2a1 1 0 00-1 1v1a1 1 0 002 0V3a1 1 0 00-1-1zM4 4h3a3 3 0 006 0h3a2 2 0 012 2v9a2 2 0 01-2 2H4a2 2 0 01-2-2V6a2 2 0 012-2zm2.5 7a1.5 1.5 0 100-3 1.5 1.5 0 000 3zm2.45 4a2.5 2.5 0 10-4.9 0h4.9zM12 9a1 1 0 100 2h3a1 1 0 100-2h-3zm-1 4a1 1 0 011-1h2a1 1 0 110 2h-2a1 1 0 01-1-1z"
|
|
clip-rule="evenodd"></path>
|
|
</svg>
|
|
<h3 class="text-xl font-medium">IEEE Membership</h3>
|
|
</div>
|
|
|
|
<div class="grid gap-6">
|
|
<!-- IEEE Member ID -->
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium"
|
|
>IEEE Member ID</span
|
|
>
|
|
<span class="label-text-alt text-base-content/70"
|
|
>Your IEEE membership number</span
|
|
>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="memberIdInput"
|
|
placeholder="Enter your IEEE Member ID"
|
|
class="input input-bordered w-full"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Resume Upload -->
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-medium">Resume</span>
|
|
<span class="label-text-alt text-base-content/70"
|
|
>Upload your latest resume</span
|
|
>
|
|
</label>
|
|
<div class="space-y-4">
|
|
<!-- Current Resume Display -->
|
|
<div id="resumeDisplay" class="hidden">
|
|
<div
|
|
class="flex items-center gap-2 p-3 bg-base-100 rounded-lg border border-base-300"
|
|
>
|
|
<div class="flex-1 flex items-center gap-3">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-8 w-8 text-primary"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z"
|
|
clip-rule="evenodd"></path>
|
|
</svg>
|
|
<div class="flex-1 min-w-0">
|
|
<p
|
|
id="currentResume"
|
|
class="text-sm font-medium truncate"
|
|
>
|
|
No resume uploaded
|
|
</p>
|
|
<p
|
|
class="text-xs text-base-content/70"
|
|
>
|
|
PDF, DOC, or DOCX
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex-none flex gap-2">
|
|
<button
|
|
id="previewResume"
|
|
class="btn btn-sm btn-ghost"
|
|
onclick="resumeViewer.showModal()"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 20 20"
|
|
fill="currentColor"
|
|
>
|
|
<path
|
|
d="M10 12a2 2 0 100-4 2 2 0 000 4z"
|
|
></path>
|
|
<path
|
|
fill-rule="evenodd"
|
|
d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z"
|
|
clip-rule="evenodd"></path>
|
|
</svg>
|
|
Preview
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Upload Input -->
|
|
<div class="flex items-center gap-2">
|
|
<input
|
|
type="file"
|
|
id="resumeUpload"
|
|
accept=".pdf,.doc,.docx"
|
|
class="file-input file-input-bordered file-input-primary w-full"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Upload Status -->
|
|
<div class="text-sm">
|
|
<span id="uploadStatus" class="label-text-alt"
|
|
></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Replace the old resume viewer modal with the new FilePreviewModal -->
|
|
<FilePreviewModal id="resumeViewer" title="Resume Preview" />
|
|
|
|
<script>
|
|
import { Authentication } from "../pocketbase/Authentication";
|
|
import { Update } from "../pocketbase/Update";
|
|
import { SendLog } from "../pocketbase/SendLog";
|
|
import { FileManager } from "../pocketbase/FileManager";
|
|
|
|
// Add type declaration for the global filePreviewModal
|
|
declare global {
|
|
interface Window {
|
|
filePreviewModal: {
|
|
show: (file: { url: string; name: string }) => void;
|
|
};
|
|
}
|
|
}
|
|
|
|
const auth = Authentication.getInstance();
|
|
const update = Update.getInstance();
|
|
const logger = SendLog.getInstance();
|
|
const fileManager = FileManager.getInstance();
|
|
|
|
// Get form elements
|
|
const memberIdInput = document.getElementById(
|
|
"memberIdInput"
|
|
) as HTMLInputElement;
|
|
const majorSelect = document.getElementById(
|
|
"majorSelect"
|
|
) as HTMLSelectElement;
|
|
const gradYearSelect = document.getElementById(
|
|
"gradYearSelect"
|
|
) as HTMLSelectElement;
|
|
const resumeUpload = document.getElementById(
|
|
"resumeUpload"
|
|
) as HTMLInputElement;
|
|
const currentResume = document.getElementById("currentResume");
|
|
const uploadStatus = document.getElementById("uploadStatus");
|
|
const saveSettings = document.getElementById("saveSettings");
|
|
|
|
// Add resume preview functionality
|
|
const resumeDisplay = document.getElementById("resumeDisplay");
|
|
const previewResume = document.getElementById("previewResume");
|
|
|
|
// Load current user data
|
|
const loadUserData = () => {
|
|
if (auth.isAuthenticated()) {
|
|
const user = auth.getCurrentUser();
|
|
if (user) {
|
|
if (memberIdInput) memberIdInput.value = user.member_id || "";
|
|
if (majorSelect) majorSelect.value = user.major || "";
|
|
if (gradYearSelect)
|
|
gradYearSelect.value = user.graduation_year || "";
|
|
|
|
// Update resume display
|
|
if (currentResume && resumeDisplay) {
|
|
if (user.resume) {
|
|
const fileName = user.resume.toString();
|
|
currentResume.textContent =
|
|
fileName || "Resume uploaded";
|
|
resumeDisplay.classList.remove("hidden");
|
|
|
|
// Get the file URL from PocketBase
|
|
const resumeUrl = fileManager.getFileUrl(
|
|
"users",
|
|
user.id,
|
|
fileName
|
|
);
|
|
|
|
// Update preview button to use new modal
|
|
if (previewResume) {
|
|
previewResume.onclick = () => {
|
|
window.filePreviewModal.show({
|
|
url: resumeUrl,
|
|
name: fileName,
|
|
});
|
|
};
|
|
}
|
|
} else {
|
|
currentResume.textContent = "No resume uploaded";
|
|
resumeDisplay.classList.add("hidden");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Handle resume upload
|
|
if (resumeUpload && uploadStatus) {
|
|
resumeUpload.addEventListener("change", async (e) => {
|
|
const file = (e.target as HTMLInputElement).files?.[0];
|
|
if (file) {
|
|
uploadStatus.textContent = "Uploading...";
|
|
try {
|
|
const user = auth.getCurrentUser();
|
|
if (!user) throw new Error("User not authenticated");
|
|
|
|
await fileManager.uploadFile(
|
|
"users",
|
|
user.id,
|
|
"resume",
|
|
file
|
|
);
|
|
uploadStatus.textContent = "Resume uploaded successfully";
|
|
|
|
// Refresh the user data to show the new resume
|
|
loadUserData();
|
|
|
|
// Log successful resume upload
|
|
await logger.send(
|
|
"update",
|
|
"resume upload",
|
|
`Successfully uploaded resume: ${file.name}`
|
|
);
|
|
} catch (err) {
|
|
console.error("Resume upload error:", err);
|
|
uploadStatus.textContent = "Failed to upload resume";
|
|
|
|
// Log resume upload error
|
|
await logger.send(
|
|
"error",
|
|
"resume upload",
|
|
`Failed to upload resume: ${file.name}. Error: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Handle save settings
|
|
if (saveSettings) {
|
|
saveSettings.addEventListener("click", async () => {
|
|
const button = saveSettings as HTMLButtonElement;
|
|
button.classList.add("loading");
|
|
|
|
try {
|
|
const user = auth.getCurrentUser();
|
|
if (!user) throw new Error("User not authenticated");
|
|
|
|
const oldData = {
|
|
major: user.major || null,
|
|
graduation_year: user.graduation_year || null,
|
|
member_id: user.member_id || null,
|
|
};
|
|
|
|
const newData = {
|
|
major: majorSelect?.value || null,
|
|
graduation_year: gradYearSelect?.value
|
|
? parseInt(gradYearSelect.value)
|
|
: null,
|
|
member_id: memberIdInput?.value || null,
|
|
} as const;
|
|
|
|
await update.updateFields("users", user.id, newData);
|
|
|
|
// Create detailed log message of changes
|
|
const changes = [];
|
|
if (oldData.major !== newData.major) {
|
|
changes.push(
|
|
`Major: "${oldData.major || "none"}" → "${newData.major || "none"}"`
|
|
);
|
|
}
|
|
if (oldData.graduation_year !== newData.graduation_year) {
|
|
changes.push(
|
|
`Graduation Year: "${oldData.graduation_year || "none"}" → "${newData.graduation_year || "none"}"`
|
|
);
|
|
}
|
|
if (oldData.member_id !== newData.member_id) {
|
|
changes.push(
|
|
`IEEE Member ID: "${oldData.member_id || "none"}" → "${newData.member_id || "none"}"`
|
|
);
|
|
}
|
|
|
|
if (changes.length > 0) {
|
|
await logger.send(
|
|
"update",
|
|
"profile settings",
|
|
`Updated profile settings:\n${changes.join("\n")}`
|
|
);
|
|
}
|
|
|
|
// Show success toast
|
|
const toast = document.createElement("div");
|
|
toast.className =
|
|
"alert alert-success fixed bottom-4 right-4 w-auto z-50";
|
|
toast.innerHTML = `
|
|
<div class="flex items-center gap-2">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
|
|
</svg>
|
|
<span>Settings saved successfully!</span>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(toast);
|
|
setTimeout(() => toast.remove(), 3000);
|
|
|
|
// Only update form data without affecting visibility
|
|
loadUserData();
|
|
|
|
// Ensure settings view stays visible
|
|
const defaultView = document.getElementById("defaultView");
|
|
const settingsView = document.getElementById("settingsView");
|
|
if (defaultView && settingsView) {
|
|
defaultView.classList.add("hidden");
|
|
settingsView.classList.remove("hidden");
|
|
}
|
|
} catch (err) {
|
|
console.error("Failed to save settings:", err);
|
|
|
|
// Log the settings update error with details
|
|
const errorDetails = {
|
|
attempted_major: majorSelect?.value || "none",
|
|
attempted_graduation_year: gradYearSelect?.value || "none",
|
|
attempted_member_id: memberIdInput?.value || "none",
|
|
error_message:
|
|
err instanceof Error ? err.message : "Unknown error",
|
|
};
|
|
|
|
await logger.send(
|
|
"error",
|
|
"profile settings",
|
|
`Failed to update profile settings:\nAttempted changes:\n` +
|
|
`Major: ${errorDetails.attempted_major}\n` +
|
|
`Graduation Year: ${errorDetails.attempted_graduation_year}\n` +
|
|
`IEEE Member ID: ${errorDetails.attempted_member_id}\n` +
|
|
`Error: ${errorDetails.error_message}`
|
|
);
|
|
|
|
// Show error toast
|
|
const toast = document.createElement("div");
|
|
toast.className =
|
|
"alert alert-error fixed bottom-4 right-4 w-auto z-50";
|
|
toast.innerHTML = `
|
|
<div class="flex items-center gap-2">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" viewBox="0 0 20 20" fill="currentColor">
|
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
|
</svg>
|
|
<span>Failed to save settings. Please try again.</span>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(toast);
|
|
setTimeout(() => toast.remove(), 3000);
|
|
} finally {
|
|
button.classList.remove("loading");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Load initial data
|
|
loadUserData();
|
|
|
|
// Update when auth state changes
|
|
auth.onAuthStateChange(() => {
|
|
loadUserData();
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
</style>
|