wizard.astro 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. ---
  2. import Head from "@components/_head.astro"
  3. import Footer from "@components/dashboard/_footer.astro"
  4. import Topbar from "@components/dashboard/_topbar.astro"
  5. import Sidenav from "@components/dashboard/_sidenav.astro"
  6. import Scripts from "@components/_scripts.astro"
  7. const title = "AdminLTE 4 | Form Wizard"
  8. const path = "../../../dist"
  9. const mainPage = "forms"
  10. const page = "wizard";
  11. ---
  12. <!DOCTYPE html>
  13. <html lang="en">
  14. <head>
  15. <Head title={title} path={path} />
  16. <style is:inline>
  17. .wizard-steps {
  18. counter-reset: step;
  19. list-style: none;
  20. padding: 0;
  21. display: flex;
  22. justify-content: space-between;
  23. position: relative;
  24. }
  25. .wizard-steps::before {
  26. content: "";
  27. position: absolute;
  28. top: 1rem;
  29. left: 0;
  30. right: 0;
  31. height: 2px;
  32. background: var(--bs-border-color);
  33. z-index: 0;
  34. }
  35. .wizard-steps li {
  36. position: relative;
  37. z-index: 1;
  38. background: var(--bs-body-bg);
  39. padding: 0 .75rem;
  40. text-align: center;
  41. color: var(--bs-secondary-color);
  42. font-size: .875rem;
  43. }
  44. .wizard-steps li::before {
  45. counter-increment: step;
  46. content: counter(step);
  47. display: flex;
  48. align-items: center;
  49. justify-content: center;
  50. width: 2rem;
  51. height: 2rem;
  52. margin: 0 auto .5rem;
  53. border-radius: 50%;
  54. background: var(--bs-body-tertiary-bg);
  55. border: 2px solid var(--bs-border-color);
  56. color: var(--bs-secondary-color);
  57. font-weight: 600;
  58. }
  59. .wizard-steps li.active {
  60. color: var(--bs-primary);
  61. font-weight: 600;
  62. }
  63. .wizard-steps li.active::before {
  64. background: var(--bs-primary);
  65. border-color: var(--bs-primary);
  66. color: #fff;
  67. }
  68. .wizard-steps li.completed::before {
  69. background: var(--bs-success);
  70. border-color: var(--bs-success);
  71. color: #fff;
  72. content: "\f633";
  73. font-family: "bootstrap-icons";
  74. }
  75. </style>
  76. </head>
  77. <body class="layout-fixed sidebar-expand-lg bg-body-tertiary">
  78. <div class="app-wrapper">
  79. <Topbar path={path} />
  80. <Sidenav path={path} mainPage={mainPage} page={page} />
  81. <main class="app-main">
  82. <div class="app-content-header">
  83. <div class="container-fluid">
  84. <div class="row">
  85. <div class="col-sm-6">
  86. <h3 class="mb-0">Form Wizard</h3>
  87. </div>
  88. <div class="col-sm-6">
  89. <ol class="breadcrumb float-sm-end">
  90. <li class="breadcrumb-item"><a href="#">Home</a></li>
  91. <li class="breadcrumb-item"><a href="#">Forms</a></li>
  92. <li class="breadcrumb-item active" aria-current="page">Wizard</li>
  93. </ol>
  94. </div>
  95. </div>
  96. </div>
  97. </div>
  98. <div class="app-content">
  99. <div class="container-fluid">
  100. <div class="row justify-content-center">
  101. <div class="col-lg-10 col-xl-8">
  102. <div class="card">
  103. <div class="card-body p-4">
  104. <!-- Step indicators -->
  105. <ol class="wizard-steps mb-4" id="wizard-steps">
  106. <li class="active" data-step="0">Account</li>
  107. <li data-step="1">Profile</li>
  108. <li data-step="2">Preferences</li>
  109. <li data-step="3">Review</li>
  110. </ol>
  111. <!-- Form -->
  112. <form id="wizard-form" novalidate>
  113. <!-- Step 1 -->
  114. <fieldset class="wizard-step" data-step="0">
  115. <h2 class="h5 mb-3">Create your account</h2>
  116. <div class="row g-3">
  117. <div class="col-md-6">
  118. <label class="form-label" for="wz-email">Email</label>
  119. <input
  120. type="email"
  121. class="form-control"
  122. id="wz-email"
  123. required
  124. />
  125. <div class="invalid-feedback">
  126. Please enter a valid email.
  127. </div>
  128. </div>
  129. <div class="col-md-6">
  130. <label class="form-label" for="wz-username">
  131. Username
  132. </label>
  133. <input
  134. type="text"
  135. class="form-control"
  136. id="wz-username"
  137. required
  138. minlength="3"
  139. />
  140. <div class="invalid-feedback">
  141. Username must be at least 3 characters.
  142. </div>
  143. </div>
  144. <div class="col-md-6">
  145. <label class="form-label" for="wz-password">
  146. Password
  147. </label>
  148. <input
  149. type="password"
  150. class="form-control"
  151. id="wz-password"
  152. required
  153. minlength="8"
  154. />
  155. <div class="invalid-feedback">
  156. Password must be at least 8 characters.
  157. </div>
  158. </div>
  159. <div class="col-md-6">
  160. <label class="form-label" for="wz-password2">
  161. Confirm password
  162. </label>
  163. <input
  164. type="password"
  165. class="form-control"
  166. id="wz-password2"
  167. required
  168. />
  169. <div class="invalid-feedback">
  170. Passwords must match.
  171. </div>
  172. </div>
  173. </div>
  174. </fieldset>
  175. <!-- Step 2 -->
  176. <fieldset class="wizard-step d-none" data-step="1">
  177. <h2 class="h5 mb-3">Tell us about yourself</h2>
  178. <div class="row g-3">
  179. <div class="col-md-6">
  180. <label class="form-label" for="wz-first">
  181. First name
  182. </label>
  183. <input
  184. type="text"
  185. class="form-control"
  186. id="wz-first"
  187. required
  188. />
  189. <div class="invalid-feedback">
  190. First name is required.
  191. </div>
  192. </div>
  193. <div class="col-md-6">
  194. <label class="form-label" for="wz-last">
  195. Last name
  196. </label>
  197. <input
  198. type="text"
  199. class="form-control"
  200. id="wz-last"
  201. required
  202. />
  203. <div class="invalid-feedback">
  204. Last name is required.
  205. </div>
  206. </div>
  207. <div class="col-md-6">
  208. <label class="form-label" for="wz-company">
  209. Company
  210. </label>
  211. <input
  212. type="text"
  213. class="form-control"
  214. id="wz-company"
  215. />
  216. </div>
  217. <div class="col-md-6">
  218. <label class="form-label" for="wz-role">
  219. Role
  220. </label>
  221. <select class="form-select" id="wz-role" required>
  222. <option value="">Choose&hellip;</option>
  223. <option>Founder / CEO</option>
  224. <option>Engineering</option>
  225. <option>Design</option>
  226. <option>Marketing</option>
  227. <option>Other</option>
  228. </select>
  229. <div class="invalid-feedback">
  230. Please select a role.
  231. </div>
  232. </div>
  233. </div>
  234. </fieldset>
  235. <!-- Step 3 -->
  236. <fieldset class="wizard-step d-none" data-step="2">
  237. <h2 class="h5 mb-3">Notification preferences</h2>
  238. <div class="form-check form-switch mb-2">
  239. <input
  240. class="form-check-input"
  241. type="checkbox"
  242. id="wz-notif-product"
  243. role="switch"
  244. checked
  245. />
  246. <label class="form-check-label" for="wz-notif-product">
  247. Product updates &amp; releases
  248. </label>
  249. </div>
  250. <div class="form-check form-switch mb-2">
  251. <input
  252. class="form-check-input"
  253. type="checkbox"
  254. id="wz-notif-security"
  255. role="switch"
  256. checked
  257. />
  258. <label class="form-check-label" for="wz-notif-security">
  259. Security alerts
  260. </label>
  261. </div>
  262. <div class="form-check form-switch mb-3">
  263. <input
  264. class="form-check-input"
  265. type="checkbox"
  266. id="wz-notif-marketing"
  267. role="switch"
  268. />
  269. <label class="form-check-label" for="wz-notif-marketing">
  270. Marketing &amp; tips
  271. </label>
  272. </div>
  273. <label class="form-label" for="wz-frequency">
  274. Digest frequency
  275. </label>
  276. <select class="form-select" id="wz-frequency">
  277. <option>Real time</option>
  278. <option selected>Daily</option>
  279. <option>Weekly</option>
  280. <option>Never</option>
  281. </select>
  282. </fieldset>
  283. <!-- Step 4 -->
  284. <fieldset class="wizard-step d-none" data-step="3">
  285. <h2 class="h5 mb-3">Review &amp; confirm</h2>
  286. <dl class="row mb-3" id="wz-summary"></dl>
  287. <div class="form-check">
  288. <input
  289. class="form-check-input"
  290. type="checkbox"
  291. id="wz-terms"
  292. required
  293. />
  294. <label class="form-check-label" for="wz-terms">
  295. I agree to the <a href="#">terms of service</a>.
  296. </label>
  297. <div class="invalid-feedback">
  298. You must accept the terms to continue.
  299. </div>
  300. </div>
  301. </fieldset>
  302. <!-- Navigation -->
  303. <div class="d-flex justify-content-between mt-4">
  304. <button
  305. type="button"
  306. class="btn btn-outline-secondary"
  307. id="wz-prev"
  308. disabled
  309. >
  310. <i class="bi bi-arrow-left me-1" aria-hidden="true"></i>
  311. Previous
  312. </button>
  313. <button
  314. type="button"
  315. class="btn btn-primary"
  316. id="wz-next"
  317. >
  318. Next
  319. <i class="bi bi-arrow-right ms-1" aria-hidden="true"></i>
  320. </button>
  321. <button
  322. type="submit"
  323. class="btn btn-success d-none"
  324. id="wz-submit"
  325. >
  326. <i class="bi bi-check-lg me-1" aria-hidden="true"></i>
  327. Submit
  328. </button>
  329. </div>
  330. </form>
  331. </div>
  332. </div>
  333. </div>
  334. </div>
  335. </div>
  336. </div>
  337. </main>
  338. <Footer />
  339. </div>
  340. <Scripts path={path} />
  341. <script is:inline>
  342. document.addEventListener("DOMContentLoaded", () => {
  343. const form = document.getElementById("wizard-form");
  344. const steps = form.querySelectorAll(".wizard-step");
  345. const indicators = document.querySelectorAll("#wizard-steps li");
  346. const prevBtn = document.getElementById("wz-prev");
  347. const nextBtn = document.getElementById("wz-next");
  348. const submitBtn = document.getElementById("wz-submit");
  349. let current = 0;
  350. const show = (i) => {
  351. steps.forEach((s, idx) => s.classList.toggle("d-none", idx !== i));
  352. indicators.forEach((li, idx) => {
  353. li.classList.toggle("active", idx === i);
  354. li.classList.toggle("completed", idx < i);
  355. });
  356. prevBtn.disabled = i === 0;
  357. const last = i === steps.length - 1;
  358. nextBtn.classList.toggle("d-none", last);
  359. submitBtn.classList.toggle("d-none", !last);
  360. if (last) renderSummary();
  361. };
  362. const validateStep = (i) => {
  363. const step = steps[i];
  364. const fields = step.querySelectorAll("input, select, textarea");
  365. let valid = true;
  366. fields.forEach((field) => {
  367. field.classList.remove("is-invalid");
  368. if (!field.checkValidity()) {
  369. field.classList.add("is-invalid");
  370. valid = false;
  371. }
  372. });
  373. // Password match check on step 0
  374. if (i === 0) {
  375. const p1 = document.getElementById("wz-password");
  376. const p2 = document.getElementById("wz-password2");
  377. if (p1.value !== p2.value) {
  378. p2.classList.add("is-invalid");
  379. valid = false;
  380. }
  381. }
  382. return valid;
  383. };
  384. const renderSummary = () => {
  385. const summary = document.getElementById("wz-summary");
  386. const get = (id) => document.getElementById(id);
  387. const rows = [
  388. ["Email", get("wz-email").value],
  389. ["Username", get("wz-username").value],
  390. ["Name", `${get("wz-first").value} ${get("wz-last").value}`],
  391. ["Company", get("wz-company").value || "—"],
  392. ["Role", get("wz-role").value || "—"],
  393. ["Digest", get("wz-frequency").value]
  394. ];
  395. summary.innerHTML = rows
  396. .map(
  397. ([k, v]) =>
  398. `<dt class="col-sm-4 text-secondary fw-normal">${k}</dt><dd class="col-sm-8 fw-semibold">${v}</dd>`
  399. )
  400. .join("");
  401. };
  402. nextBtn.addEventListener("click", () => {
  403. if (!validateStep(current)) return;
  404. if (current < steps.length - 1) {
  405. current++;
  406. show(current);
  407. }
  408. });
  409. prevBtn.addEventListener("click", () => {
  410. if (current > 0) {
  411. current--;
  412. show(current);
  413. }
  414. });
  415. form.addEventListener("submit", (e) => {
  416. e.preventDefault();
  417. if (!validateStep(current)) return;
  418. alert("Wizard complete! Form would submit here.");
  419. });
  420. show(0);
  421. });
  422. </script>
  423. </body>
  424. </html>