فهرست منبع

Merge pull request #55 from qor5/login-doc

add login doc
xuxinx 1 سال پیش
والد
کامیت
81f4d4a8ee
47فایلهای تغییر یافته به همراه808 افزوده شده و 16 حذف شده
  1. 4 0
      docs/activity-log.html
  2. 4 0
      docs/advanced-functions/the-go-html-builder.html
  3. 4 0
      docs/appendix/all-demo-examples.html
  4. 4 0
      docs/basics/brand.html
  5. 4 0
      docs/basics/confirm-dialog.html
  6. 4 0
      docs/basics/event-handling.html
  7. 4 0
      docs/basics/filter.html
  8. 4 0
      docs/basics/form-handling.html
  9. 4 0
      docs/basics/layout-function-and-page-injector.html
  10. 4 0
      docs/basics/layout.html
  11. 4 0
      docs/basics/listing.html
  12. 360 0
      docs/basics/login.html
  13. 4 0
      docs/basics/manipulate-page-url-in-event-func.html
  14. 4 0
      docs/basics/menu.html
  15. 4 0
      docs/basics/notification-center.html
  16. 4 0
      docs/basics/page-func-and-event-func.html
  17. 4 0
      docs/basics/partial-refresh-with-portal.html
  18. 4 0
      docs/basics/reload-page-with-a-flash.html
  19. 4 0
      docs/basics/scope-component.html
  20. 4 0
      docs/basics/shortcut.html
  21. 4 0
      docs/basics/summary-of-event-response.html
  22. 4 0
      docs/basics/switch-pages-with-push-state.html
  23. 4 0
      docs/basics/worker.html
  24. 4 0
      docs/components-guide/composite-new-component-with-go.html
  25. 4 0
      docs/components-guide/integrate-a-heavy-vue-component.html
  26. 4 0
      docs/getting-started/one-minute-quick-start.html
  27. 23 9
      docs/index.html
  28. 4 0
      docs/presets-guide/detail-page-for-complex-object.html
  29. 4 0
      docs/presets-guide/editing-customizations.html
  30. 4 0
      docs/presets-guide/its-the-whole-house.html
  31. 4 0
      docs/presets-guide/permissions.html
  32. 4 0
      docs/presets-guide/role.html
  33. 0 0
      docs/search_indexes.json
  34. 4 0
      docs/seo.html
  35. 4 0
      docs/slug.html
  36. 4 0
      docs/vuetify-components/a-taste-of-using-vuetify-in-go.html
  37. 4 0
      docs/vuetify-components/auto-complete.html
  38. 4 0
      docs/vuetify-components/basic-inputs.html
  39. 4 0
      docs/vuetify-components/lazy-portals.html
  40. 4 0
      docs/vuetify-components/linkage-select.html
  41. 116 0
      docsrc/content/basics/login.go
  42. 151 0
      docsrc/examples/example_basics/login.go
  43. 0 0
      docsrc/generated/g1.go
  44. 0 0
      docsrc/generated/g5.go
  45. 1 0
      docsrc/menu.go
  46. 4 3
      go.mod
  47. 5 4
      go.sum

+ 4 - 0
docs/activity-log.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/advanced-functions/the-go-html-builder.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/appendix/all-demo-examples.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/brand.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/confirm-dialog.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/event-handling.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/filter.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/form-handling.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/layout-function-and-page-injector.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/layout.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-blue-500'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/listing.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 360 - 0
docs/basics/login.html

@@ -0,0 +1,360 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<title>Building Admin - Login - QOR5 Document</title>
+
+<meta name='description'>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<base href='/docs/'>
+
+<link href='index.css' rel='stylesheet' type='text/css'>
+
+<script type='text/javascript' defer src='index.js'></script>
+</head>
+
+<body>
+<div id='app' v-cloak>
+<div v-init-context:vars='{hideAside: false}' class='flex h-screen'>
+<div class='flex-1 flex flex-col overflow-hidden'>
+<div class='flex h-full'>
+<aside v-show='!vars.hideAside' id='menuScroller' class='flex flex-col w-80 h-full bg-gray-50 border-r border-gray-200 overflow-y-auto'>
+<div class='h-12'><search></search></div>
+
+<ul class='px-0 py-3 mx-0 text-base font-normal list-none text-gray-700'>
+<li class='m-0'>
+<a href='index.html' id='index.html' onclick='window.storeMenuState("index.html")' class='inline-block px-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Introduction</a>
+</li>
+
+<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Getting Started</li>
+
+<li class='m-0'>
+<a href='getting-started/one-minute-quick-start.html' id='getting-started/one-minute-quick-start.html' onclick='window.storeMenuState("getting-started/one-minute-quick-start.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>1 Minute Quick Start</a>
+</li>
+
+<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Building Admin</li>
+
+<li class='m-0'>
+<a href='basics/listing.html' id='basics/listing.html' onclick='window.storeMenuState("basics/listing.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Listing</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/filter.html' id='basics/filter.html' onclick='window.storeMenuState("basics/filter.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Filters</a>
+</li>
+
+<li class='m-0'>
+<a href='presets-guide/editing-customizations.html' id='presets-guide/editing-customizations.html' onclick='window.storeMenuState("presets-guide/editing-customizations.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Editing</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/brand.html' id='basics/brand.html' onclick='window.storeMenuState("basics/brand.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Brand</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/menu.html' id='basics/menu.html' onclick='window.storeMenuState("basics/menu.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Menu</a>
+</li>
+
+<li class='m-0'>
+<a href='presets-guide/detail-page-for-complex-object.html' id='presets-guide/detail-page-for-complex-object.html' onclick='window.storeMenuState("presets-guide/detail-page-for-complex-object.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Detailing</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-blue-500'>Login</a>
+</li>
+
+<li class='m-0'>
+<a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
+</li>
+
+<li class='m-0'>
+<a href='presets-guide/role.html' id='presets-guide/role.html' onclick='window.storeMenuState("presets-guide/role.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Role</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/notification-center.html' id='basics/notification-center.html' onclick='window.storeMenuState("basics/notification-center.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Notification Center</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/shortcut.html' id='basics/shortcut.html' onclick='window.storeMenuState("basics/shortcut.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Keyboard Shortcut</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/confirm-dialog.html' id='basics/confirm-dialog.html' onclick='window.storeMenuState("basics/confirm-dialog.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Confirm Dialog</a>
+</li>
+
+<li class='m-0'>
+<a href='slug.html' id='slug.html' onclick='window.storeMenuState("slug.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Slug</a>
+</li>
+
+<li class='m-0'>
+<a href='seo.html' id='seo.html' onclick='window.storeMenuState("seo.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>SEO</a>
+</li>
+
+<li class='m-0'>
+<a href='activity-log.html' id='activity-log.html' onclick='window.storeMenuState("activity-log.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Activity Log</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/worker.html' id='basics/worker.html' onclick='window.storeMenuState("basics/worker.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Worker</a>
+</li>
+
+<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Web Application</li>
+
+<li class='m-0'>
+<a href='basics/page-func-and-event-func.html' id='basics/page-func-and-event-func.html' onclick='window.storeMenuState("basics/page-func-and-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Page Func and Event Func</a>
+</li>
+
+<li class='m-0'>
+<a href='advanced-functions/the-go-html-builder.html' id='advanced-functions/the-go-html-builder.html' onclick='window.storeMenuState("advanced-functions/the-go-html-builder.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>The Go HTML builder</a>
+</li>
+
+<li class='m-0'>
+<a href='presets-guide/its-the-whole-house.html' id='presets-guide/its-the-whole-house.html' onclick='window.storeMenuState("presets-guide/its-the-whole-house.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Not just scaffolding, it&#39;s the whole house</a>
+</li>
+
+<li class='m-0'>
+<a href='vuetify-components/lazy-portals.html' id='vuetify-components/lazy-portals.html' onclick='window.storeMenuState("vuetify-components/lazy-portals.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Lazy Portals</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/layout-function-and-page-injector.html' id='basics/layout-function-and-page-injector.html' onclick='window.storeMenuState("basics/layout-function-and-page-injector.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout Function and Page Injector</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/switch-pages-with-push-state.html' id='basics/switch-pages-with-push-state.html' onclick='window.storeMenuState("basics/switch-pages-with-push-state.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Switch Pages with Push State</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/reload-page-with-a-flash.html' id='basics/reload-page-with-a-flash.html' onclick='window.storeMenuState("basics/reload-page-with-a-flash.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Reload Page with a Flash</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/partial-refresh-with-portal.html' id='basics/partial-refresh-with-portal.html' onclick='window.storeMenuState("basics/partial-refresh-with-portal.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Partial Refresh with Portal</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/manipulate-page-url-in-event-func.html' id='basics/manipulate-page-url-in-event-func.html' onclick='window.storeMenuState("basics/manipulate-page-url-in-event-func.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Manipulate Page URL in Event Func</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/summary-of-event-response.html' id='basics/summary-of-event-response.html' onclick='window.storeMenuState("basics/summary-of-event-response.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Summary of Event Response</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/scope-component.html' id='basics/scope-component.html' onclick='window.storeMenuState("basics/scope-component.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Scope Component</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/event-handling.html' id='basics/event-handling.html' onclick='window.storeMenuState("basics/event-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Event Handling</a>
+</li>
+
+<li class='m-0'>
+<a href='basics/form-handling.html' id='basics/form-handling.html' onclick='window.storeMenuState("basics/form-handling.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Form Handling</a>
+</li>
+
+<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>UI Components</li>
+
+<li class='m-0'>
+<a href='vuetify-components/basic-inputs.html' id='vuetify-components/basic-inputs.html' onclick='window.storeMenuState("vuetify-components/basic-inputs.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Basic Inputs</a>
+</li>
+
+<li class='m-0'>
+<a href='vuetify-components/a-taste-of-using-vuetify-in-go.html' id='vuetify-components/a-taste-of-using-vuetify-in-go.html' onclick='window.storeMenuState("vuetify-components/a-taste-of-using-vuetify-in-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>A Taste of using Vuetify in Go</a>
+</li>
+
+<li class='m-0'>
+<a href='vuetify-components/linkage-select.html' id='vuetify-components/linkage-select.html' onclick='window.storeMenuState("vuetify-components/linkage-select.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Linkage Select</a>
+</li>
+
+<li class='m-0'>
+<a href='vuetify-components/auto-complete.html' id='vuetify-components/auto-complete.html' onclick='window.storeMenuState("vuetify-components/auto-complete.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Auto Complete</a>
+</li>
+
+<li class='m-0'>
+<a href='components-guide/composite-new-component-with-go.html' id='components-guide/composite-new-component-with-go.html' onclick='window.storeMenuState("components-guide/composite-new-component-with-go.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Composite new Component With Go</a>
+</li>
+
+<li class='m-0'>
+<a href='components-guide/integrate-a-heavy-vue-component.html' id='components-guide/integrate-a-heavy-vue-component.html' onclick='window.storeMenuState("components-guide/integrate-a-heavy-vue-component.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Integrate a heavy Vue Component</a>
+</li>
+
+<li class='cursor-default px-4 py-1 truncate break-words w-64 m-0'>Appendix</li>
+
+<li class='m-0'>
+<a href='appendix/all-demo-examples.html' id='appendix/all-demo-examples.html' onclick='window.storeMenuState("appendix/all-demo-examples.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>All Demo Examples</a>
+</li>
+</ul>
+</aside>
+
+<main class='flex flex-col w-full bg-white overflow-x-hidden overflow-y-auto'>
+<div id='docContentBox' class='flex flex-row w-full'>
+<div class='flex flex-grow flex-col w-2/3'>
+<div class='flex flex-row'>
+<button @click='vars.hideAside = !vars.hideAside' class='w-12 h-12 p-4'>
+<div class='w-4 h-4 fill-current text-gray-300'>
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
+<g id="surface1">
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(0%,0%,0%);fill-opacity:1;" d="M 2 12 L 2 11 L 14 11 L 14 12 Z M 2 8.5 L 2 7.5 L 14 7.5 L 14 8.5 Z M 2 5 L 2 4 L 14 4 L 14 5 Z M 2 5 "/>
+</g>
+</svg>
+</div>
+</button>
+</div>
+
+<div id='docMainBox' class='px-16 pb-12 pt-4 overflow-auto'>
+<h1 class='mb-8'>Login</h1>
+
+<div class='border-t'><p>Login package provides comprehensive login authentication logic and related UI interfaces. It is designed to simplify the process of adding user authentication to QOR5-based backend development project.<br>
+In QOR5 admin development, we recommend using <a href="https://github.com/qor5/admin/tree/main/login" rel="nofollow">github.com/qor5/admin/login</a>, which wraps <a href="https://github.com/qor5/x/tree/master/login" rel="nofollow">github.com/qor5/x/login</a> to keep the theme of login UI consistent with Presets and provide more powerful features.</p>
+<h2><a name="basic-usage" class="anchor" href="#basic-usage" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+Basic Usage</h2>
+
+<p>The example shows how to enable both username/password login and OAuth login.</p>
+
+<highlightjs :language='"go"' :code='"import (\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/markbates/goth/providers/github\"\n\t\"github.com/markbates/goth/providers/google\"\n\tplogin \"github.com/qor5/admin/login\"\n\t\"github.com/qor5/admin/presets\"\n\t. \"github.com/qor5/ui/vuetify\"\n\t\"github.com/qor5/web\"\n\t\"github.com/qor5/x/login\"\n\t. \"github.com/theplant/htmlgo\"\n\t\"gorm.io/gorm\"\n)\n\ntype User struct {\n\tgorm.Model\n\n\tName    string\n\tAddress string\n\n\tlogin.UserPass\n\tlogin.OAuthInfo\n\tlogin.SessionSecure\n}\n\nfunc serve() {\n\tpb := presets.New()\n\tlb := plogin.New(pb).\n\t\tDB(DB).\n\t\tUserModel(\u0026User{}).\n\t\tSecret(os.Getenv(\"LOGIN_SECRET\")).\n\t\tOAuthProviders(\n\t\t\t\u0026login.Provider{\n\t\t\t\tGoth: google.New(os.Getenv(\"LOGIN_GOOGLE_KEY\"), os.Getenv(\"LOGIN_GOOGLE_SECRET\"), os.Getenv(\"BASE_URL\")+\"/auth/callback?provider=google\"),\n\t\t\t\tKey:  \"google\",\n\t\t\t\tText: \"Google\",\n\t\t\t},\n\t\t\t\u0026login.Provider{\n\t\t\t\tGoth: github.New(os.Getenv(\"LOGIN_GITHUB_KEY\"), os.Getenv(\"LOGIN_GITHUB_SECRET\"), os.Getenv(\"BASE_URL\")+\"/auth/callback?provider=github\"),\n\t\t\t\tKey:  \"github\",\n\t\t\t\tText: \"Login with Github\",\n\t\t\t},\n\t\t)\n\tpb.ProfileFunc(func(ctx *web.EventContext) HTMLComponent {\n\t\treturn A(Text(\"logout\")).Href(lb.LogoutURL)\n\t})\n\n\tr := http.NewServeMux()\n\tr.Handle(\"/\", pb)\n\tlb.Mount(r)\n\n\tmux := http.NewServeMux()\n\tmux.Handle(\"/\", lb.Middleware()(r))\n\thttp.ListenAndServe(\":8080\", nil)\n}\n"'></highlightjs>
+<h2><a name="username-password-login" class="anchor" href="#username-password-login" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>Username/Password Login</h2>
+
+<p>To enable Username/Password login, the <code>UserModel</code> needs to implement the <a href="https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/user_pass.go#L13" rel="nofollow">UserPasser</a> interface. There is a default implementation - <a href="https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/user_pass.go#L44" rel="nofollow">UserPass</a>.</p>
+
+<highlightjs :language='"go"' :code='"type User struct {\n\tgorm.Model\n\n\tlogin.UserPass\n}"'></highlightjs>
+<h3><a name="change-password" class="anchor" href="#change-password" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>Change Password</h3>
+
+<p>There are three ways to change the password:</p>
+
+<p>1. Visit the default change password page.</p>
+
+<p>2. Call the <code>OpenChangePasswordDialogEvent</code> event to change it in dialog.</p>
+
+<highlightjs :language='"go"' :code='"VBtn(\"Change Password\").OnClick(plogin.OpenChangePasswordDialogEvent)"'></highlightjs>
+<p>3. Change the password directly in Editing.</p>
+
+<highlightjs :language='"go"' :code='"userModelBuilder.Editing().Field(\"Password\").\n\tSetterFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error) {\n\t\tu := obj.(*User)\n\t\tif v := ctx.R.FormValue(field.Name); v != \"\" {\n\t\t\tu.Password = v\n\t\t\tu.EncryptPassword()\n\t\t}\n\t\treturn nil\n\t})"'></highlightjs>
+<h3><a name="maxretrycount" class="anchor" href="#maxretrycount" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>MaxRetryCount</h3>
+
+<p>By default, it allows 5 login attempts with incorrect credentials, and if the limit is exceeded, the user will be locked for 1 hour. This helps to prevent brute-force attacks on the login system. You can call <code>MaxRetryCount</code> to set the maximum retry count. If you set MaxRetryCount to a value less than or equal to 0, it means there is no limit of login attempts, and the user will not be locked after a certain number of failed login attempts.</p>
+
+<highlightjs :language='"go"' :code='"loginBuilder.MaxRetryCount(count)"'></highlightjs>
+<h3><a name="totp" class="anchor" href="#totp" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>TOTP</h3>
+
+<p>There is TOTP (Time-based One-time Password) functionality out of the box, which is enabled by default.</p>
+
+<highlightjs :language='"go"' :code='"loginBuilder.TOTP(enable, login.TOTPConfig{\n\tIssuer: \"Issuer\",\n})"'></highlightjs>
+<h3><a name="google-recaptcha" class="anchor" href="#google-recaptcha" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>Google reCAPTCHA</h3>
+
+<p>Google reCAPTCHA is disabled by default.</p>
+
+<highlightjs :language='"go"' :code='"loginBuilder.Recaptcha(enable, login.RecaptchaConfig{\n\tSiteKey:   \"SiteKey\",\n\tSecretKey: \"SecretKey\",\n})"'></highlightjs>
+<h2><a name="oauth-login" class="anchor" href="#oauth-login" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>OAuth Login</h2>
+
+<p>OAuth login does not require a <code>UserModel</code>. If there is a <code>UserModel</code>, it needs to implement the <a href="https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/oauth_user.go#L5" rel="nofollow">OAuthUser</a> interface. There is a default implementation - <a href="https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/oauth_user.go#L13" rel="nofollow">OAuthInfo</a>.</p>
+
+<highlightjs :language='"go"' :code='"type User struct {\n\tgorm.Model\n\n\tlogin.OAuthInfo\n}"'></highlightjs>
+<h2><a name="session-secure" class="anchor" href="#session-secure" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>Session Secure</h2>
+
+<p>The <a href="https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/session_secure.go#L11" rel="nofollow">SessionSecurer</a> provides a way to manage unique salt for a user record. There is a default implementation - <a href="https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/session_secure.go#L16" rel="nofollow">SessionSecure</a>.</p>
+
+<highlightjs :language='"go"' :code='"type User struct {\n\tgorm.Model\n\n\tlogin.UserPass\n\tlogin.OAuthInfo\n\tlogin.SessionSecure\n}"'></highlightjs>
+<p><code>SessionSecurer</code> helps to ensure user security even in the event of secret leakage. When a user logs in, <code>SessionSecurer</code> generates a random salt and associates it with the user&#39;s record. This salt is then used to sign the user&#39;s session token. When the user makes requests to the server, the server verifies that the session token has been signed with the correct salt. If the salt has been changed, the session token is considered invalid and the user is logged out.</p>
+<h2><a name="hooks" class="anchor" href="#hooks" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>Hooks</h2>
+
+<p><a href="https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/builder.go#L39" rel="nofollow">Hooks</a> are functions that are called before or after certain events.<br>
+The following hooks are available:</p>
+<h3><a name="beforesetpassword" class="anchor" href="#beforesetpassword" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+BeforeSetPassword</h3>
+<h4><a name="extra-values" class="anchor" href="#extra-values" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+Extra Values</h4>
+
+<ul>
+<li>password</li>
+</ul>
+
+<p>This hook is called before resetting or changing a password. The hook can be used to validate password formats.</p>
+<h3><a name="afterlogin" class="anchor" href="#afterlogin" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterLogin</h3>
+
+<p>This hook is called after a successful login.</p>
+<h3><a name="afterfailedtologin" class="anchor" href="#afterfailedtologin" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterFailedToLogin</h3>
+<h4><a name="extra-values" class="anchor" href="#extra-values" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+Extra Values</h4>
+
+<ul>
+<li>login error</li>
+</ul>
+
+<p>This hook is called after a failed login. Note that the <code>user</code> parameter may be nil.</p>
+<h3><a name="afteruserlocked" class="anchor" href="#afteruserlocked" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterUserLocked</h3>
+
+<p>This hook is called after a user is locked.</p>
+<h3><a name="afterlogout" class="anchor" href="#afterlogout" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterLogout</h3>
+
+<p>This hook is called after a logout.</p>
+<h3><a name="afterconfirmsendresetpasswordlink" class="anchor" href="#afterconfirmsendresetpasswordlink" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterConfirmSendResetPasswordLink</h3>
+<h4><a name="extra-values" class="anchor" href="#extra-values" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+Extra Values</h4>
+
+<ul>
+<li>reset link</li>
+</ul>
+
+<p>This hook is called after confirming the sending of a password reset link. This is where the code to send the reset link to the user should be written.</p>
+<h3><a name="afterresetpassword" class="anchor" href="#afterresetpassword" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterResetPassword</h3>
+
+<p>This hook is called after a password is reset.</p>
+<h3><a name="afterchangepassword" class="anchor" href="#afterchangepassword" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterChangePassword</h3>
+
+<p>This hook is called after a password is changed.</p>
+<h3><a name="afterextendsession" class="anchor" href="#afterextendsession" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterExtendSession</h3>
+<h4><a name="extra-values" class="anchor" href="#extra-values" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+Extra Values</h4>
+
+<ul>
+<li>old session token</li>
+</ul>
+
+<p>This hook is called after a session is extended.</p>
+<h3><a name="aftertotpcodereused" class="anchor" href="#aftertotpcodereused" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterTOTPCodeReused</h3>
+
+<p>This hook is called after a TOTP code has been reused.</p>
+<h3><a name="afteroauthcomplete" class="anchor" href="#afteroauthcomplete" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>
+AfterOAuthComplete</h3>
+
+<p>This hook is called after an OAuth authentication is completed.</p>
+<h2><a name="customize-pages" class="anchor" href="#customize-pages" rel="nofollow" aria-hidden="true"><span class="octicon octicon-link"></span></a>Customize Pages</h2>
+
+<p>To customize pages, there are two ways:</p>
+
+<p>1. Each page has a corresponding <code>xxxPageFunc</code> to rewrite the page content. You can easily customize a page by copying the <a href="https://github.com/qor5/admin/blob/main/login/views.go" rel="nofollow">default page func</a> and modifying it according to your needs.</p>
+
+<highlightjs :language='"go"' :code='"loginBuilder.LoginPageFunc(func(ctx *web.EventContext) (r web.PageResponse, err error) {\n\tr.Body = Text(\"This is login page\")\n\treturn\n})"'></highlightjs>
+<p>2. Only mount the API and serve the login pages manually.<br>
+When you want to embed the login form into an existing page, this way can be very useful.</p>
+
+<highlightjs :language='"go"' :code='"loginBuilder.LoginPageURL(\"/custom-login-page\")\nloginBuilder.MountAPI(mux)\nmux.Handle(\"/custom-login-page\", loginPage)"'></highlightjs>
+</div>
+</div>
+</div>
+
+<div class='font-medium text-base hidden xl:block text-gray-600 pt-4'>
+<div class='sticky top-4 w-52'>On This Page<toc></toc></div>
+</div>
+</div>
+<search-result></search-result></main>
+</div>
+</div>
+</div>
+</div>
+</body>
+</html>

+ 4 - 0
docs/basics/manipulate-page-url-in-event-func.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/menu.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/notification-center.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/page-func-and-event-func.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/partial-refresh-with-portal.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/reload-page-with-a-flash.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/scope-component.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/shortcut.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/summary-of-event-response.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/switch-pages-with-push-state.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/basics/worker.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/components-guide/composite-new-component-with-go.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/components-guide/integrate-a-heavy-vue-component.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/getting-started/one-minute-quick-start.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 23 - 9
docs/index.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>
@@ -205,25 +209,35 @@
 <div id='docMainBox' class='px-16 pb-12 pt-4 overflow-auto'>
 <h1 class='mb-8'>Introduction</h1>
 
-<div class='border-t'><p>QOR5 is a Go library to build web applications. We aim to accelerate the development speed and make the website highly customizable.</p>
+<div class='border-t'><p>QOR5 is a Go library designed to help developers build web applications with ease and high customization. By focusing on static typing in the Go language and minimizing the need for JavaScript or TypeScript, QOR5 streamlines the development process and encourages reusability of components.</p>
+
+<p>In QOR5, the traditional approach of using template languages for rendering HTML is discouraged. Instead, QOR5 encourages developers to write HTML <a href="/advanced-functions/the-go-html-builder.html" rel="nofollow">using static typing in the Go language</a>. This design choice provides several benefits:</p>
 
 <ul>
-<li>It prefers writing HTML in <a href="/advanced-functions/the-go-html-builder.html" rel="nofollow">static typing Go language</a>, rather than a certain type of template language, Not even go template.</li>
-<li>It try to minify the needs to write any JavaScript/Typescript for building interactive web applications</li>
-<li>It maximize the reusability of Components. since it uses Go to write components, You can abstract component very easy, and use component from a third party Go package is also like using normal Go packages.
-<br></li>
+<li><p>Improved Readability and Maintainability: By using Go&#39;s static typing, you can maintain a consistent coding style throughout the entire project, making it easier to read and maintain.</p></li>
+
+<li><p>Better Error Checking: Static typing allows for compile-time error checking, which can help catch issues before they cause problems in production.</p></li>
+
+<li><p>Enhanced Reusability: QOR5 promotes the use of components, which can be easily abstracted and reused across different parts of your application. Since components are written in Go, using third-party components from other Go packages is as simple as importing and using regular Go packages.</p></li>
+
+<li><p>Simplified Development Process: By minimizing the need for JavaScript or TypeScript, QOR5 streamlines the development process and reduces the complexity of building interactive web applications.</p></li>
 </ul>
 
+<p>QOR5&#39;s approach to rendering HTML using Go&#39;s static typing eliminates the need for developers to learn and work with multiple template languages. This results in a more consistent and streamlined development experience, allowing developers to focus on the core functionality of their web applications.</p>
+
 <h2 id='how-is-this-document-organized'>How is this document organized
 <a href='#how-is-this-document-organized' class='anchor'></a>
 </h2>
 <p>Most of latter examples are based on the initial sample project. In another word, we will demonstrate how to build a rich functioned website by this document.</p>
 
 <ul>
-<li>First, we will start with a quick sample project that would give you a rough but visual idea of what QOR5 can do.</li>
-<li>Second, we will introduce the basic functions, The sequence is from listing page to editing page. You can find all commonly used Admin website features in this section.</li>
-<li>Third, we will introduce the essentials of QOR5 and advanced functions, You would understand how QOR5 render a page and advanced features like &#34;how to partially refresh a page&#34;.</li>
-<li>At last, the digging deeper part, you would learn how to create new component for QOR5</li>
+<li><p>Quick Sample Project: We will begin with a brief overview of a sample project, giving you a visual idea of QOR5&#39;s capabilities and functionalities.</p></li>
+
+<li><p>Basic Functions: In this section, we will explore the core features of QOR5, starting from listing pages to editing pages. This section covers common features found in admin websites.</p></li>
+
+<li><p>QOR5 Essentials and Advanced Functions: We will dive into the inner workings of QOR5, covering topics such as rendering pages and advanced features like partial page refreshing.</p></li>
+
+<li><p>Digging Deeper: In the final section, you will learn how to create new components for QOR5, extending its capabilities and adapting it to your specific needs.</p></li>
 </ul>
 
 <p><strong>Join the Discord community</strong>: <a href="https://discord.gg/76YPsVBE4E" rel="nofollow">https://discord.gg/76YPsVBE4E</a></p>

+ 4 - 0
docs/presets-guide/detail-page-for-complex-object.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/presets-guide/editing-customizations.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/presets-guide/its-the-whole-house.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/presets-guide/permissions.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-blue-500'>Permissions</a>
 </li>

+ 4 - 0
docs/presets-guide/role.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
docs/search_indexes.json


+ 4 - 0
docs/seo.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/slug.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/vuetify-components/a-taste-of-using-vuetify-in-go.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/vuetify-components/auto-complete.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/vuetify-components/basic-inputs.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/vuetify-components/lazy-portals.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 4 - 0
docs/vuetify-components/linkage-select.html

@@ -62,6 +62,10 @@
 <a href='basics/layout.html' id='basics/layout.html' onclick='window.storeMenuState("basics/layout.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Layout</a>
 </li>
 
+<li class='m-0'>
+<a href='basics/login.html' id='basics/login.html' onclick='window.storeMenuState("basics/login.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Login</a>
+</li>
+
 <li class='m-0'>
 <a href='presets-guide/permissions.html' id='presets-guide/permissions.html' onclick='window.storeMenuState("presets-guide/permissions.html")' class='inline-block pl-10 pr-4 py-1 truncate break-words w-64 hover:text-blue-400 text-gray-700'>Permissions</a>
 </li>

+ 116 - 0
docsrc/content/basics/login.go

@@ -0,0 +1,116 @@
+package basics
+
+import (
+	"github.com/qor5/docs/docsrc/generated"
+	. "github.com/theplant/docgo"
+	"github.com/theplant/docgo/ch"
+)
+
+var Login = Doc(
+	Markdown(`
+Login package provides comprehensive login authentication logic and related UI interfaces. It is designed to simplify the process of adding user authentication to QOR5-based backend development project.   
+In QOR5 admin development, we recommend using [github.com/qor5/admin/login](https://github.com/qor5/admin/tree/main/login), which wraps [github.com/qor5/x/login](https://github.com/qor5/x/tree/master/login) to keep the theme of login UI consistent with Presets and provide more powerful features.
+## Basic Usage
+The example shows how to enable both username/password login and OAuth login.
+    `),
+	ch.Code(generated.LoginBasicUsage).Language("go"),
+	Markdown(`
+## Username/Password Login
+To enable Username/Password login, the ~UserModel~ needs to implement the [UserPasser](https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/user_pass.go#L13) interface. There is a default implementation - [UserPass](https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/user_pass.go#L44).  
+    `),
+	ch.Code(generated.LoginEnableUserPass).Language("go"),
+	Markdown(`
+### Change Password
+There are three ways to change the password:
+
+1\. Visit the default change password page.
+
+2\. Call the ~OpenChangePasswordDialogEvent~ event to change it in dialog.
+    `),
+	ch.Code(generated.LoginOpenChangePasswordDialog).Language("go"),
+	Markdown(`
+
+3\. Change the password directly in Editing.
+    `),
+	ch.Code(generated.LoginChangePasswordInEditing).Language("go"),
+	Markdown(`
+### MaxRetryCount
+By default, it allows 5 login attempts with incorrect credentials, and if the limit is exceeded, the user will be locked for 1 hour. This helps to prevent brute-force attacks on the login system. You can call ~MaxRetryCount~ to set the maximum retry count. If you set MaxRetryCount to a value less than or equal to 0, it means there is no limit of login attempts, and the user will not be locked after a certain number of failed login attempts. 
+    `),
+	ch.Code(generated.LoginSetMaxRetryCount).Language("go"),
+	Markdown(`
+### TOTP
+There is TOTP (Time-based One-time Password) functionality out of the box, which is enabled by default.
+    `),
+	ch.Code(generated.LoginSetTOTP).Language("go"),
+	Markdown(`
+### Google reCAPTCHA
+Google reCAPTCHA is disabled by default.
+    `),
+	ch.Code(generated.LoginSetRecaptcha).Language("go"),
+	Markdown(`
+## OAuth Login
+OAuth login is based on [goth](https://github.com/markbates/goth).   
+OAuth login does not require a ~UserModel~. If there is a ~UserModel~, it needs to implement the [OAuthUser](https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/oauth_user.go#L5) interface. There is a default implementation - [OAuthInfo](https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/oauth_user.go#L13).  
+    `),
+	ch.Code(generated.LoginEnableOAuth).Language("go"),
+	Markdown(`
+## Session Secure
+The [SessionSecurer](https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/session_secure.go#L11) provides a way to manage unique salt for a user record. There is a default implementation - [SessionSecure](https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/session_secure.go#L16).  
+    `),
+	ch.Code(generated.LoginEnableSessionSecure).Language("go"),
+	Markdown(`
+~SessionSecurer~ helps to ensure user security even in the event of secret leakage. When a user logs in, ~SessionSecurer~ generates a random salt and associates it with the user's record. This salt is then used to sign the user's session token. When the user makes requests to the server, the server verifies that the session token has been signed with the correct salt. If the salt has been changed, the session token is considered invalid and the user is logged out.
+    `),
+	Markdown(`
+## Hooks
+[Hooks](https://github.com/qor5/x/blob/8f986dddfeaf235fd42bb3361717551d06695517/login/builder.go#L39) are functions that are called before or after certain events.   
+The following hooks are available:
+### BeforeSetPassword
+#### Extra Values
+- password
+
+This hook is called before resetting or changing a password. The hook can be used to validate password formats.
+### AfterLogin
+This hook is called after a successful login.
+### AfterFailedToLogin
+#### Extra Values
+- login error
+
+This hook is called after a failed login. Note that the ~user~ parameter may be nil.
+### AfterUserLocked
+This hook is called after a user is locked.
+### AfterLogout
+This hook is called after a logout.
+### AfterConfirmSendResetPasswordLink
+#### Extra Values
+- reset link
+
+This hook is called after confirming the sending of a password reset link. This is where the code to send the reset link to the user should be written.
+### AfterResetPassword
+This hook is called after a password is reset.
+### AfterChangePassword
+This hook is called after a password is changed.
+### AfterExtendSession
+#### Extra Values
+- old session token
+
+This hook is called after a session is extended.
+### AfterTOTPCodeReused
+This hook is called after a TOTP code has been reused.
+### AfterOAuthComplete
+This hook is called after an OAuth authentication is completed.
+    `),
+	Markdown(`
+## Customize Pages
+To customize pages, there are two ways:
+
+1\. Each page has a corresponding ~xxxPageFunc~ to rewrite the page content. You can easily customize a page by copying the [default page func](https://github.com/qor5/admin/blob/main/login/views.go) and modifying it according to your needs.
+    `),
+	ch.Code(generated.LoginCustomizePage).Language("go"),
+	Markdown(`
+2\. Only mount the API and serve the login pages manually.  
+When you want to embed the login form into an existing page, this way can be very useful.
+    `),
+	ch.Code(generated.LoginCustomizePage2).Language("go"),
+).Slug("basics/login").Title("Login")

+ 151 - 0
docsrc/examples/example_basics/login.go

@@ -0,0 +1,151 @@
+package example_basics
+
+// @snippet_begin(LoginBasicUsage)
+import (
+	"net/http"
+	"os"
+
+	"github.com/markbates/goth/providers/github"
+	"github.com/markbates/goth/providers/google"
+	plogin "github.com/qor5/admin/login"
+	"github.com/qor5/admin/presets"
+	. "github.com/qor5/ui/vuetify"
+	"github.com/qor5/web"
+	"github.com/qor5/x/login"
+	. "github.com/theplant/htmlgo"
+	"gorm.io/gorm"
+)
+
+type User struct {
+	gorm.Model
+
+	Name    string
+	Address string
+
+	login.UserPass
+	login.OAuthInfo
+	login.SessionSecure
+}
+
+func serve() {
+	pb := presets.New()
+	lb := plogin.New(pb).
+		DB(DB).
+		UserModel(&User{}).
+		Secret(os.Getenv("LOGIN_SECRET")).
+		OAuthProviders(
+			&login.Provider{
+				Goth: google.New(os.Getenv("LOGIN_GOOGLE_KEY"), os.Getenv("LOGIN_GOOGLE_SECRET"), os.Getenv("BASE_URL")+"/auth/callback?provider=google"),
+				Key:  "google",
+				Text: "Google",
+			},
+			&login.Provider{
+				Goth: github.New(os.Getenv("LOGIN_GITHUB_KEY"), os.Getenv("LOGIN_GITHUB_SECRET"), os.Getenv("BASE_URL")+"/auth/callback?provider=github"),
+				Key:  "github",
+				Text: "Login with Github",
+			},
+		)
+	pb.ProfileFunc(func(ctx *web.EventContext) HTMLComponent {
+		return A(Text("logout")).Href(lb.LogoutURL)
+	})
+
+	r := http.NewServeMux()
+	r.Handle("/", pb)
+	lb.Mount(r)
+
+	mux := http.NewServeMux()
+	mux.Handle("/", lb.Middleware()(r))
+	http.ListenAndServe(":8080", nil)
+}
+
+// @snippet_end
+
+func loginPieces() {
+	// @snippet_begin(LoginEnableUserPass)
+	type User struct {
+		gorm.Model
+
+		login.UserPass
+	}
+	// @snippet_end
+
+	var loginBuilder *login.Builder
+	var count int
+	// @snippet_begin(LoginSetMaxRetryCount)
+	loginBuilder.MaxRetryCount(count)
+	// @snippet_end
+
+	var enable bool
+	// @snippet_begin(LoginSetTOTP)
+	loginBuilder.TOTP(enable, login.TOTPConfig{
+		Issuer: "Issuer",
+	})
+	// @snippet_end
+
+	// @snippet_begin(LoginSetRecaptcha)
+	loginBuilder.Recaptcha(enable, login.RecaptchaConfig{
+		SiteKey:   "SiteKey",
+		SecretKey: "SecretKey",
+	})
+	// @snippet_end
+}
+
+func loginPiece2() {
+	// @snippet_begin(LoginEnableOAuth)
+	type User struct {
+		gorm.Model
+
+		login.OAuthInfo
+	}
+	// @snippet_end
+}
+
+func loginPiece3() {
+	// @snippet_begin(LoginEnableSessionSecure)
+	type User struct {
+		gorm.Model
+
+		login.UserPass
+		login.OAuthInfo
+		login.SessionSecure
+	}
+	// @snippet_end
+}
+
+func loginPiece4() {
+	var loginBuilder *login.Builder
+	// @snippet_begin(LoginCustomizePage)
+	loginBuilder.LoginPageFunc(func(ctx *web.EventContext) (r web.PageResponse, err error) {
+		r.Body = Text("This is login page")
+		return
+	})
+	// @snippet_end
+
+	var mux *http.ServeMux
+	var loginPage http.Handler
+
+	// @snippet_begin(LoginCustomizePage2)
+	loginBuilder.LoginPageURL("/custom-login-page")
+	loginBuilder.MountAPI(mux)
+	mux.Handle("/custom-login-page", loginPage)
+	// @snippet_end
+}
+
+func loginPiece5() {
+	// @snippet_begin(LoginOpenChangePasswordDialog)
+	VBtn("Change Password").OnClick(plogin.OpenChangePasswordDialogEvent)
+	// @snippet_end
+
+	var userModelBuilder *presets.ModelBuilder
+	// @snippet_begin(LoginChangePasswordInEditing)
+	userModelBuilder.Editing().Field("Password").
+		SetterFunc(func(obj interface{}, field *presets.FieldContext, ctx *web.EventContext) (err error) {
+			u := obj.(*User)
+			if v := ctx.R.FormValue(field.Name); v != "" {
+				u.Password = v
+				u.EncryptPassword()
+			}
+			return nil
+		})
+	// @snippet_end
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
docsrc/generated/g1.go


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 0 - 0
docsrc/generated/g5.go


+ 1 - 0
docsrc/menu.go

@@ -32,6 +32,7 @@ var DocTree = []interface{}{
 			basics.ManageMenu,
 			advanced_functions.DetailPageForComplexObject,
 			basics.Layout,
+			basics.Login,
 			// permission
 			basics.Permissions,
 			basics.Role,

+ 4 - 3
go.mod

@@ -6,12 +6,13 @@ require (
 	github.com/fatih/color v1.13.0
 	github.com/go-chi/chi v1.5.4
 	github.com/manifoldco/promptui v0.9.0
+	github.com/markbates/goth v1.76.0
 	github.com/ory/ladon v1.2.0
 	github.com/qor/oss v0.0.0-20210412121326-3c5583a62015
-	github.com/qor5/admin v0.0.0-20230309055501-c8d08dc6944b
+	github.com/qor5/admin v0.0.0-20230420031413-a0401013364a
 	github.com/qor5/ui v1.0.1-0.20230323061917-a88f4521ec30
 	github.com/qor5/web v1.2.4
-	github.com/qor5/x v1.2.1-0.20230308023320-5dd0a2f09c49
+	github.com/qor5/x v1.2.1-0.20230420023921-8f986dddfeaf
 	github.com/shurcooL/sanitized_anchor_name v1.0.0
 	github.com/sunfmin/reflectutils v1.0.3
 	github.com/theplant/docgo v0.0.15
@@ -24,6 +25,7 @@ require (
 )
 
 require (
+	cloud.google.com/go v0.67.0 // indirect
 	github.com/NYTimes/gziphandler v1.1.1 // indirect
 	github.com/aws/aws-sdk-go v1.38.62 // indirect
 	github.com/aymerick/douceur v0.2.0 // indirect
@@ -56,7 +58,6 @@ require (
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/lib/pq v1.10.7 // indirect
-	github.com/markbates/goth v1.76.0 // indirect
 	github.com/mattn/go-colorable v0.1.9 // indirect
 	github.com/mattn/go-isatty v0.0.14 // indirect
 	github.com/mattn/go-sqlite3 v1.14.16 // indirect

+ 5 - 4
go.sum

@@ -13,6 +13,7 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV
 cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
 cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
 cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.67.0 h1:YIkzmqUfVGiGPpT98L8sVvUIkDno6UlrDxw4NR6z5ak=
 cloud.google.com/go v0.67.0/go.mod h1:YNan/mUhNZFrYUor0vqrsQ0Ffl7Xtm/ACOy/vsTS858=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
@@ -293,14 +294,14 @@ github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/qor/oss v0.0.0-20210412121326-3c5583a62015 h1:gmzorMEb+tKC8wkYBsIO9z+tpul0nWyIyKErqj9GG2o=
 github.com/qor/oss v0.0.0-20210412121326-3c5583a62015/go.mod h1:JhtPzUhP5KGtCB2yksmxuYAD4hEWw4qGQJpucjsm3U0=
-github.com/qor5/admin v0.0.0-20230309055501-c8d08dc6944b h1:j9wGiuweDoSAVxBH/+zAKS3wrkijmVWODnvpCIKnYJE=
-github.com/qor5/admin v0.0.0-20230309055501-c8d08dc6944b/go.mod h1:cTtdZJvUGLNTGFfe53xT1H5noFgawFbfHsBLJyKEZYs=
+github.com/qor5/admin v0.0.0-20230420031413-a0401013364a h1:H0roJ8yzDYAjkNT/RpWP8NGGD/epNi3mu7AliJZgtn4=
+github.com/qor5/admin v0.0.0-20230420031413-a0401013364a/go.mod h1:GUwpp9VtbwITOhE2z+gW0rwmy2IEqg/26d4qNH9zoiM=
 github.com/qor5/ui v1.0.1-0.20230323061917-a88f4521ec30 h1:uoZT2RyVu5l2YqaONRjgsdgx1YLwNYGqV63qeYG+5lQ=
 github.com/qor5/ui v1.0.1-0.20230323061917-a88f4521ec30/go.mod h1:bgBqjIytHRdfTsiZea8df/ltAcyQyuHiLbecgo8Iwgw=
 github.com/qor5/web v1.2.4 h1:CsChErtiYgaMA7CkqkC7nP2YEAOjDZ7JNrrUJTYGNLc=
 github.com/qor5/web v1.2.4/go.mod h1:4VXydGmy5Uwz8rEeKjcmCetciJo8TpU0mnN7Ca5kMR0=
-github.com/qor5/x v1.2.1-0.20230308023320-5dd0a2f09c49 h1:IAh1ichaQ6X9jpds5qdxjPUZ/IcchPeidsuF58M/ycA=
-github.com/qor5/x v1.2.1-0.20230308023320-5dd0a2f09c49/go.mod h1:pqE7TSENrUKHlwzDSdaww0pk3E9X1ffoqAfCHzoZrJk=
+github.com/qor5/x v1.2.1-0.20230420023921-8f986dddfeaf h1:vbRMFexTTIUBpWR/0HjB4A4BEijN3rO/fYVmFoj3/PQ=
+github.com/qor5/x v1.2.1-0.20230420023921-8f986dddfeaf/go.mod h1:Zfy7B3X5DnQSud0HTV4h/ih5TTQgaT2NWwuSIRGLdcM=
 github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
 github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
 github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است