initialize project with basic structure and dependencies

This commit is contained in:
ImBenji
2026-04-27 22:26:40 +01:00
parent a3b3bd6b04
commit 83f2837ce6
7 changed files with 250 additions and 6 deletions
+153 -4
View File
@@ -20,6 +20,10 @@ const submitting = ref(false);
const submitted = ref(false);
const formError = ref<string | null>(null);
const showLicenseModal = ref(false);
const licenseKey = ref("");
const licenseError = ref<string | null>(null);
const typeLabels: Record<UploadType, string> = {
slides: "Lecture Slides",
past_paper: "Past Paper",
@@ -93,16 +97,28 @@ async function applyOverride(entry: UploadedFile, newType: UploadType) {
}
}
async function submit() {
function requestSubmit() {
if (uploadedFiles.value.length === 0) {
formError.value = "Add at least one PDF to continue.";
return;
}
formError.value = null;
licenseError.value = null;
licenseKey.value = "";
showLicenseModal.value = true;
}
async function submit() {
if (!licenseKey.value.trim()) {
licenseError.value = "Enter a license key.";
return;
}
showLicenseModal.value = false;
submitting.value = true;
try {
const { courseId } = await $fetch<{ courseId: string }>("/api/courses", {
method: "POST",
headers: { "x-license-key": licenseKey.value.trim() },
});
for (const entry of uploadedFiles.value) {
await uploadFile(entry, courseId);
@@ -115,8 +131,15 @@ async function submit() {
submitted.value = true;
setTimeout(() => router.push("/"), 3000);
} catch (err: any) {
formError.value = err?.data?.message ?? "Something went wrong. Please try again.";
submitting.value = false;
const msg = err?.data?.message ?? "Something went wrong. Please try again.";
if (err?.status === 401) {
licenseError.value = msg;
showLicenseModal.value = true;
submitting.value = false;
} else {
formError.value = msg;
submitting.value = false;
}
}
}
</script>
@@ -227,7 +250,7 @@ async function submit() {
class="submit-btn"
:class="{ 'submit-btn--loading': submitting }"
:disabled="submitting || uploadedFiles.length === 0"
@click="submit"
@click="requestSubmit"
>
<span v-if="!submitting">Generate my course →</span>
<span v-else class="btn-loading">
@@ -238,6 +261,35 @@ async function submit() {
</div>
</div>
<!-- license key modal -->
<Teleport to="body">
<Transition name="modal">
<div v-if="showLicenseModal" class="modal-backdrop" @click.self="showLicenseModal = false">
<div class="modal-box">
<p class="modal-title">License key required</p>
<p class="modal-sub">Run <code>npm run gen:key</code> locally to get a key.</p>
<input
v-model="licenseKey"
class="modal-input"
type="text"
placeholder="paste key here"
autofocus
@keydown.enter="submit"
@keydown.esc="showLicenseModal = false"
/>
<p v-if="licenseError" class="modal-error">{{ licenseError }}</p>
<div class="modal-actions">
<button class="modal-cancel" @click="showLicenseModal = false">Cancel</button>
<button class="modal-confirm" @click="submit">Continue →</button>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
@@ -531,4 +583,101 @@ async function submit() {
.file-list-leave-active { transition: all 0.2s ease; }
.file-list-enter-from { opacity: 0; transform: translateY(-6px); }
.file-list-leave-to { opacity: 0; transform: translateX(10px); }
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0,0,0,0.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
}
.modal-box {
background: var(--bg);
border: 1px solid var(--border-2);
border-radius: var(--r-surface);
padding: 28px 28px 24px;
width: 100%;
max-width: 420px;
}
.modal-title {
font-size: 16px;
font-weight: 600;
color: var(--text);
margin-bottom: 6px;
}
.modal-sub {
font-size: 13px;
color: var(--text-3);
margin-bottom: 18px;
}
.modal-sub code {
font-family: monospace;
background: var(--surface);
padding: 1px 5px;
border-radius: 4px;
font-size: 12px;
color: var(--accent);
}
.modal-input {
width: 100%;
background: var(--surface);
border: 1px solid var(--border-2);
border-radius: var(--r-sm);
color: var(--text);
font-size: 13px;
font-family: monospace;
padding: 10px 12px;
outline: none;
box-sizing: border-box;
transition: border-color 0.15s;
}
.modal-input:focus { border-color: var(--accent); }
.modal-error {
font-size: 12px;
color: oklch(42% 0.12 15);
margin-top: 8px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.modal-cancel {
font-size: 14px;
color: var(--text-3);
background: none;
border: 1px solid var(--border-2);
border-radius: var(--r-btn);
padding: 8px 16px;
cursor: pointer;
transition: color 0.15s;
}
.modal-cancel:hover { color: var(--text); }
.modal-confirm {
font-size: 14px;
font-weight: 500;
background: var(--accent);
color: white;
border: none;
border-radius: var(--r-btn);
padding: 8px 20px;
cursor: pointer;
transition: opacity 0.15s;
}
.modal-confirm:hover { opacity: 0.88; }
.modal-enter-active, .modal-leave-active { transition: opacity 0.18s ease; }
.modal-enter-from, .modal-leave-to { opacity: 0; }
</style>