initialize project with basic structure and dependencies
This commit is contained in:
+153
-4
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user