Replace Bootstrap with Tailwind CSS + Alpine.js #14
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
.PHONY: test lint fmt fmt-check check build run dev deps docker clean hooks
|
.PHONY: test lint fmt fmt-check check build run dev deps docker clean hooks css
|
||||||
|
|
||||||
# Default target
|
# Default target
|
||||||
.DEFAULT_GOAL := check
|
.DEFAULT_GOAL := check
|
||||||
@@ -41,3 +41,6 @@ hooks:
|
|||||||
@printf '#!/bin/sh\nmake check\n' > .git/hooks/pre-commit
|
@printf '#!/bin/sh\nmake check\n' > .git/hooks/pre-commit
|
||||||
@chmod +x .git/hooks/pre-commit
|
@chmod +x .git/hooks/pre-commit
|
||||||
@echo "pre-commit hook installed"
|
@echo "pre-commit hook installed"
|
||||||
|
|
||||||
|
css:
|
||||||
|
tailwindcss -i static/css/input.css -o static/css/tailwind.css --minify
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
/* Source the templates */
|
||||||
|
@source "../../templates/**/*.html";
|
||||||
|
|
||||||
|
/* Material Design inspired theme customization */
|
||||||
|
@theme {
|
||||||
|
/* Primary colors */
|
||||||
|
--color-primary-50: #e3f2fd;
|
||||||
|
--color-primary-100: #bbdefb;
|
||||||
|
--color-primary-200: #90caf9;
|
||||||
|
--color-primary-300: #64b5f6;
|
||||||
|
--color-primary-400: #42a5f5;
|
||||||
|
--color-primary-500: #2196f3;
|
||||||
|
--color-primary-600: #1e88e5;
|
||||||
|
--color-primary-700: #1976d2;
|
||||||
|
--color-primary-800: #1565c0;
|
||||||
|
--color-primary-900: #0d47a1;
|
||||||
|
|
||||||
|
/* Error colors */
|
||||||
|
--color-error-50: #ffebee;
|
||||||
|
--color-error-500: #f44336;
|
||||||
|
--color-error-700: #d32f2f;
|
||||||
|
|
||||||
|
/* Success colors */
|
||||||
|
--color-success-50: #e8f5e9;
|
||||||
|
--color-success-500: #4caf50;
|
||||||
|
--color-success-700: #388e3c;
|
||||||
|
|
||||||
|
/* Warning colors */
|
||||||
|
--color-warning-50: #fff3e0;
|
||||||
|
--color-warning-500: #ff9800;
|
||||||
|
--color-warning-700: #f57c00;
|
||||||
|
|
||||||
|
/* Material Design elevation shadows */
|
||||||
|
--shadow-elevation-1: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||||
|
--shadow-elevation-2: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
|
||||||
|
--shadow-elevation-3: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Material Design component styles */
|
||||||
|
@layer components {
|
||||||
|
/* Buttons */
|
||||||
|
.btn-primary {
|
||||||
|
@apply inline-flex items-center justify-center px-4 py-2 rounded-md font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed bg-primary-600 text-white hover:bg-primary-700 active:bg-primary-800 focus:ring-primary-500 shadow-elevation-1 hover:shadow-elevation-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
@apply inline-flex items-center justify-center px-4 py-2 rounded-md font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed bg-white text-gray-700 border border-gray-300 hover:bg-gray-50 active:bg-gray-100 focus:ring-primary-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
@apply inline-flex items-center justify-center px-4 py-2 rounded-md font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed bg-error-500 text-white hover:bg-error-700 active:bg-red-800 focus:ring-red-500 shadow-elevation-1 hover:shadow-elevation-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
@apply inline-flex items-center justify-center px-4 py-2 rounded-md font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed text-primary-600 hover:bg-primary-50 active:bg-primary-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards */
|
||||||
|
.card {
|
||||||
|
@apply bg-white rounded-lg shadow-elevation-1 overflow-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-elevated {
|
||||||
|
@apply bg-white rounded-lg shadow-elevation-1 overflow-hidden hover:shadow-elevation-2 transition-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form inputs */
|
||||||
|
.input {
|
||||||
|
@apply w-full px-4 py-3 border border-gray-300 rounded-md text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
@apply block text-sm font-medium text-gray-700 mb-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
@apply mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status badges */
|
||||||
|
.badge-success {
|
||||||
|
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-success-50 text-success-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-error {
|
||||||
|
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-error-50 text-error-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-info {
|
||||||
|
@apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-50 text-primary-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* App bar / Navigation */
|
||||||
|
.app-bar {
|
||||||
|
@apply bg-white shadow-elevation-1 px-6 py-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alert / Message boxes */
|
||||||
|
.alert-error {
|
||||||
|
@apply p-4 rounded-md mb-4 bg-error-50 text-error-700 border border-error-500/20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
@apply p-4 rounded-md mb-4 bg-success-50 text-success-700 border border-success-500/20;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-59
@@ -1,59 +1 @@
|
|||||||
/* Webhooker main stylesheet */
|
/* Webhooker custom styles — see input.css for Tailwind theme */
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom styles for Webhooker */
|
|
||||||
|
|
||||||
/* Navbar customization */
|
|
||||||
.navbar-brand {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card hover effects */
|
|
||||||
.card {
|
|
||||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 10px 30px rgba(0,0,0,0.15) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Background opacity utilities */
|
|
||||||
.bg-opacity-10 {
|
|
||||||
background-color: rgba(var(--bs-success-rgb), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-primary.bg-opacity-10 {
|
|
||||||
background-color: rgba(var(--bs-primary-rgb), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* User dropdown styling */
|
|
||||||
.navbar .dropdown-toggle::after {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar .dropdown-menu {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer styling */
|
|
||||||
footer {
|
|
||||||
margin-top: auto;
|
|
||||||
padding: 2rem 0;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-top: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive adjustments */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.display-4 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/*! tailwindcss v4.0.14 | MIT License | https://tailwindcss.com */
|
||||||
|
@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(.637 .237 25.331);--color-red-800:oklch(.444 .177 26.899);--color-gray-50:oklch(.985 .002 247.839);--color-gray-100:oklch(.967 .003 264.542);--color-gray-200:oklch(.928 .006 264.531);--color-gray-300:oklch(.872 .01 258.338);--color-gray-500:oklch(.551 .027 264.364);--color-gray-600:oklch(.446 .03 256.802);--color-gray-700:oklch(.373 .034 259.733);--color-gray-900:oklch(.21 .034 264.665);--color-white:#fff;--spacing:.25rem;--container-md:28rem;--container-4xl:56rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--font-weight-light:300;--font-weight-medium:500;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-font-feature-settings:var(--font-sans--font-feature-settings);--default-font-variation-settings:var(--font-sans--font-variation-settings);--default-mono-font-family:var(--font-mono);--default-mono-font-feature-settings:var(--font-mono--font-feature-settings);--default-mono-font-variation-settings:var(--font-mono--font-variation-settings);--color-primary-50:#e3f2fd;--color-primary-100:#bbdefb;--color-primary-500:#2196f3;--color-primary-600:#1e88e5;--color-primary-700:#1976d2;--color-primary-800:#1565c0;--color-error-50:#ffebee;--color-error-500:#f44336;--color-error-700:#d32f2f;--color-success-50:#e8f5e9;--color-success-500:#4caf50;--color-success-700:#388e3c}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}body{line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1;color:color-mix(in oklab,currentColor 50%,transparent)}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-weLine truncated
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
(()=>{var nt=!1,it=!1,W=[],ot=-1;function Ut(e){Rn(e)}function Rn(e){W.includes(e)||W.push(e),Mn()}function Wt(e){let t=W.indexOf(e);t!==-1&&t>ot&&W.splice(t,1)}function Mn(){!it&&!nt&&(nt=!0,queueMicrotask(Nn))}function Nn(){nt=!1,it=!0;for(let e=0;e<W.length;e++)W[e](),ot=e;W.length=0,ot=-1,it=!1}var T,N,$,at,st=!0;function Gt(e){st=!1,e(),st=!0}function Jt(e){T=e.reactive,$=e.release,N=t=>e.effect(t,{scheduler:r=>{st?Ut(r):r()}}),at=e.raw}function ct(e){N=e}function Yt(e){let t=()=>{};return[n=>{let i=N(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),$(i))},i},()=>{t()}]}function ve(e,t){let r=!0,n,i=N(()=>{let o=e();JSON.stringify(o),r?n=o:queueMicrotask(()=>{t(o,n),n=o}),r=!1});return()=>$(i)}var Xt=[],Zt=[],Qt=[];function er(e){Qt.push(e)}function te(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Zt.push(t))}function Ae(e){Xt.push(e)}function Oe(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function lt(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}function tr(e){for(e._x_effects?.forEach(Wt);e._x_cleanups?.length;)e._x_cleanups.pop()()}var ut=new MutationObserver(mt),ft=!1;function ue(){ut.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ft=!0}function dt(){kn(),ut.disconnect(),ft=!1}var le=[];function kn(){let e=ut.takeRecords();le.push(()=>e.length>0&&mt(e));let t=le.length;queueMicrotask(()=>{if(le.length===t)for(;le.length>0;)le.shift()()})}function m(e){if(!ft)return e();dt();let t=e();return ue(),t}var pt=!1,Se=[];function rr(){pt=!0}function nr(){pt=!1,mt(Se),Se=[]}function mt(e){if(pt){Se=Se.concat(e);return}let t=[],r=new Set,n=new Map,i=new Map;for(let o=0;o<e.length;o++)if(!e[o].target._x_ignoreMutationObserver&&(e[o].type==="childList"&&(e[o].removedNodes.forEach(s=>{s.nodeType===1&&s._x_marker&&r.add(s)}),e[o].addedNodes.forEach(s=>{if(s.nodeType===1){if(r.has(s)){r.delete(s);return}s._x_marker||t.push(s)}})),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{lt(s,o)}),n.forEach((o,s)=>{Xt.forEach(a=>a(s,o))});for(let o of r)t.some(s=>s.contains(o))||Zt.forEach(s=>s(o));for(let o of t)o.isConnected&&Qt.forEach(s=>s(o));t=null,r=null,n=null,i=null}function Ce(e){return z(B(e))}function k(e,t,r){return e._x_dataStack=[t,...B(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function B(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?B(e.host):e.parentNode?B(e.parentNode):[]}function z(e){return new Proxy({objects:e},Dn)}var Dn={ownKeys({objects:e}){return Array.from(new Set(e.flatMap(t=>Object.keys(t))))},has({objects:e},t){return t==Symbol.unscopables?!1:e.some(r=>Object.prototype.hasOwnProperty.call(r,t)||Reflect.has(r,t))},get({objects:e},t,r){return t=="toJSON"?Pn:Reflect.get(e.find(n=>Reflect.has(n,t))||{},t,r)},set({objects:e},t,r,n){let i=e.find(s=>Object.prototype.hasOwnProperty.call(s,t))||e[e.length-1],o=Object.getOwnPropertyDescriptor(i,t);return o?.set&&o?.get?o.set.call(n,r)||!0:Reflect.set(i,t,r)}};function Pn(){return Reflect.ownKeys(this).reduce((t,r)=>(t[r]=Reflect.get(this,r),t),{})}function Te(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0||typeof s=="object"&&s!==null&&s.__v_skip)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Re(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>In(n,i),s=>ht(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function In(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function ht(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),ht(e[t[0]],t.slice(1),r)}}var ir={};function y(e,t){ir[e]=t}function fe(e,t){let r=Ln(t);return Object.entries(ir).forEach(([n,i])=>{Object.defineProperty(e,`$${n}`,{get(){return i(t,r)},enumerable:!1})}),e}function Ln(e){let[t,r]=_t(e),n={interceptor:Re,...t};return te(e,r),n}function or(e,t,r,...n){try{return r(...n)}catch(i){re(i,e,t)}}function re(e,tLine truncated
|
||||||
|
|
||||||
|
${r?'Expression: "'+r+`"
|
||||||
|
|
||||||
|
`:""}`,t),setTimeout(()=>{throw e},0)}var Me=!0;function ke(e){let t=Me;Me=!1;let r=e();return Me=t,r}function R(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return sr(...e)}var sr=xt;function ar(e){sr=e}function xt(e,t){let r={};fe(r,e);let n=[r,...B(e)],i=typeof t=="function"?$n(n,t):Fn(n,t,e);return or.bind(null,e,t,i)}function $n(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(z([n,...e]),i);Ne(r,o)}}var gt={};function jn(e,t){if(gt[e])return gt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,o=(()=>{try{let s=new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`);return Object.defineProperty(s,"name",{value:`[Alpine] ${e}`}),s}catch(s){return re(s,t,e),Promise.resolve()}})();return gt[e]=o,o}function Fn(e,t,r){let n=jn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=z([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>re(l,r,t));n.finished?(Ne(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ne(i,l,a,s,r)}).catch(l=>re(l,r,t)).finally(()=>n.result=void 0)}}}function Ne(e,t,r,n,i){if(Me&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ne(e,s,r,n)).catch(s=>re(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var wt="x-";function C(e=""){return wt+e}function cr(e){wt=e}var De={};function d(e,t){return De[e]=t,{before(r){if(!De[r]){console.warn(String.raw`Cannot find directive \`${r}\`. \`${e}\` will use the default order of execution`);return}let n=G.indexOf(r);G.splice(n>=0?n:G.indexOf("DEFAULT"),0,e)}}}function lr(e){return Object.keys(De).includes(e)}function pe(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=Et(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(dr((o,s)=>n[o]=s)).filter(mr).map(zn(n,r)).sort(Kn).map(o=>Bn(e,o))}function Et(e){return Array.from(e).map(dr()).filter(t=>!mr(t))}var yt=!1,de=new Map,ur=Symbol();function fr(e){yt=!0;let t=Symbol();ur=t,de.set(t,[]);let r=()=>{for(;de.get(t).length;)de.get(t).shift()();de.delete(t)},n=()=>{yt=!1,r()};e(r),n()}function _t(e){let t=[],r=a=>t.push(a),[n,i]=Yt(e);return t.push(i),[{Alpine:K,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:R.bind(R,e)},()=>t.forEach(a=>a())]}function Bn(e,t){let r=()=>{},n=De[t.type]||r,[i,o]=_t(e);Oe(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),yt?de.get(ur).push(n):n())};return s.runCleanups=o,s}var Pe=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Ie=e=>e;function dr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=pr.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var pr=[];function ne(e){pr.push(e)}function mr({name:e}){return hr().test(e)}var hr=()=>new RegExp(`^${wt}([^:^.]+)\\b`);function zn(e,t){return({name:r,value:n})=>{let i=r.match(hr()),o=r.match(/:([a-zA-Z0-9\-_:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var bt="DEFAULT",G=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",bt,"teleport"];function Kn(e,t){let r=G.indexOf(e.type)===-1?bt:e.type,n=G.indexOf(t.type)===-1?bt:t.type;return G.indexOf(r)-G.indexOf(n)}function J(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function D(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>D(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)D(n,t,!1),n=n.nextElementSibling}function E(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var _r=!1;function gr(){_r&&E("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),_r=!0,document.body||E("Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine's `<script>` tag?"),J(document,"alpine:init"),J(document,"alpine:initializing"),ue(),er(t=>S(t,D)),te(t=>P(t)),Ae((t,r)=>{pe(t,r).forEach(n=>n())});let e=t=>!Y(t.parentElement,!0);Array.from(document.querySelectorAll(br().join(","))).filter(e).forEach(t=>{S(t)}),J(document,"alpine:initialized"),setTimeout(()=>{Vn()})}var vt=[],xr=[];function yr(){return vt.map(e=>e())}function br(){return vt.concat(xr).map(e=>e())}function Le(e){vt.push(e)}function $e(e){xr.push(e)}function Y(e,t=!1){return j(e,r=>{if((t?br():yr()).some(i=>r.matches(i)))return!0})}function j(e,t){if(e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),!!e.parentElement)return j(e.parentElement,t)}}function wrLine truncated
|
||||||
+19
-5
@@ -4,15 +4,29 @@
|
|||||||
<head>
|
<head>
|
||||||
{{template "htmlheader" .}}
|
{{template "htmlheader" .}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="bg-gray-50 min-h-screen flex flex-col">
|
||||||
|
<div class="flex-grow">
|
||||||
{{template "navbar" .}}
|
{{template "navbar" .}}
|
||||||
|
|
||||||
<!-- Main content -->
|
|
||||||
{{block "content" .}}{{end}}
|
{{block "content" .}}{{end}}
|
||||||
|
</div>
|
||||||
<script src="/s/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
{{template "footer" .}}
|
||||||
|
<script defer src="/s/js/alpine.min.js"></script>
|
||||||
<script src="/s/js/app.js"></script>
|
<script src="/s/js/app.js"></script>
|
||||||
{{block "scripts" .}}{{end}}
|
{{block "scripts" .}}{{end}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
{{define "footer"}}
|
||||||
|
<footer class="bg-gray-100 border-t border-gray-200 shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.1)] mt-8">
|
||||||
|
<div class="max-w-6xl mx-auto px-8 py-6">
|
||||||
|
<div class="text-center text-sm text-gray-500 font-mono font-light">
|
||||||
|
<a href="https://git.eeqj.de/sneak/webhooker" class="hover:text-gray-700">Webhooker</a>
|
||||||
|
<span class="mx-1">by</span>
|
||||||
|
<a href="https://sneak.berlin" class="hover:text-gray-700">@sneak</a>
|
||||||
|
<span class="mx-3">|</span>
|
||||||
|
<span>{{if .Version}}{{.Version}}{{else}}dev{{end}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
{{end}}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{block "title" .}}Webhooker{{end}}</title>
|
<title>{{block "title" .}}Webhooker{{end}}</title>
|
||||||
<link href="/s/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
<link rel="stylesheet" href="/s/css/tailwind.css">
|
||||||
<link href="/s/css/style.css" rel="stylesheet">
|
<style>[x-cloak] { display: none !important; }</style>
|
||||||
{{block "head" .}}{{end}}
|
{{block "head" .}}{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
+28
-38
@@ -3,75 +3,65 @@
|
|||||||
{{define "title"}}Home - Webhooker{{end}}
|
{{define "title"}}Home - Webhooker{{end}}
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="container mt-5">
|
<div class="max-w-4xl mx-auto px-6 py-12">
|
||||||
<div class="row">
|
<div class="text-center mb-10">
|
||||||
<div class="col-lg-8 mx-auto">
|
<h1 class="text-4xl font-medium text-gray-900">Welcome to Webhooker</h1>
|
||||||
<div class="text-center mb-5">
|
<p class="mt-3 text-lg text-gray-500">A reliable webhook proxy service for event delivery</p>
|
||||||
<h1 class="display-4">Welcome to Webhooker</h1>
|
|
||||||
<p class="lead text-muted">A reliable webhook proxy service for event delivery</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<!-- Server Status Card -->
|
<!-- Server Status Card -->
|
||||||
<div class="col-md-6">
|
<div class="card-elevated p-6">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="flex items-center mb-4">
|
||||||
<div class="card-body">
|
<div class="rounded-full bg-success-50 p-3 mr-4">
|
||||||
<div class="d-flex align-items-center mb-3">
|
<svg class="w-6 h-6 text-success-500" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<div class="rounded-circle bg-success bg-opacity-10 p-3 me-3">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-server text-success" viewBox="0 0 16 16">
|
|
||||||
<path d="M1.333 2.667C1.333 1.194 4.318 0 8 0s6.667 1.194 6.667 2.667V4c0 1.473-2.985 2.667-6.667 2.667S1.333 5.473 1.333 4V2.667z"/>
|
<path d="M1.333 2.667C1.333 1.194 4.318 0 8 0s6.667 1.194 6.667 2.667V4c0 1.473-2.985 2.667-6.667 2.667S1.333 5.473 1.333 4V2.667z"/>
|
||||||
<path d="M1.333 6.334v3C1.333 10.805 4.318 12 8 12s6.667-1.194 6.667-2.667V6.334a6.51 6.51 0 0 1-1.458.79C11.81 7.684 9.967 8 8 8c-1.966 0-3.809-.317-5.208-.876a6.508 6.508 0 0 1-1.458-.79z"/>
|
<path d="M1.333 6.334v3C1.333 10.805 4.318 12 8 12s6.667-1.194 6.667-2.667V6.334a6.51 6.51 0 0 1-1.458.79C11.81 7.684 9.967 8 8 8c-1.966 0-3.809-.317-5.208-.876a6.508 6.508 0 0 1-1.458-.79z"/>
|
||||||
<path d="M14.667 11.668a6.51 6.51 0 0 1-1.458.789c-1.4.56-3.242.876-5.21.876-1.966 0-3.809-.316-5.208-.876a6.51 6.51 0 0 1-1.458-.79v1.666C1.333 14.806 4.318 16 8 16s6.667-1.194 6.667-2.667v-1.665z"/>
|
<path d="M14.667 11.668a6.51 6.51 0 0 1-1.458.789c-1.4.56-3.242.876-5.21.876-1.966 0-3.809-.316-5.208-.876a6.51 6.51 0 0 1-1.458-.79v1.666C1.333 14.806 4.318 16 8 16s6.667-1.194 6.667-2.667v-1.665z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h5 class="card-title mb-1">Server Status</h5>
|
<h2 class="text-lg font-medium text-gray-900">Server Status</h2>
|
||||||
<p class="text-success mb-0">Online</p>
|
<span class="badge-success">Online</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="space-y-3">
|
||||||
<small class="text-muted">Uptime</small>
|
<div>
|
||||||
<p class="h4 mb-0">{{.Uptime}}</p>
|
<p class="text-sm text-gray-500">Uptime</p>
|
||||||
|
<p class="text-2xl font-medium text-gray-900">{{.Uptime}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<small class="text-muted">Version</small>
|
<p class="text-sm text-gray-500">Version</p>
|
||||||
<p class="mb-0"><code>{{.Version}}</code></p>
|
<p class="font-mono text-sm text-gray-700">{{.Version}}</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Users Card -->
|
<!-- Users Card -->
|
||||||
<div class="col-md-6">
|
<div class="card-elevated p-6">
|
||||||
<div class="card h-100 shadow-sm">
|
<div class="flex items-center mb-4">
|
||||||
<div class="card-body">
|
<div class="rounded-full bg-primary-50 p-3 mr-4">
|
||||||
<div class="d-flex align-items-center mb-3">
|
<svg class="w-6 h-6 text-primary-500" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<div class="rounded-circle bg-primary bg-opacity-10 p-3 me-3">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-people text-primary" viewBox="0 0 16 16">
|
|
||||||
<path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8zm-7.978-1A.261.261 0 0 1 7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002a.274.274 0 0 1-.014.002H7.022zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0zM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816zM4.92 10A5.493 5.493 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275zM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/>
|
<path d="M15 14s1 0 1-1-1-4-5-4-5 3-5 4 1 1 1 1h8zm-7.978-1A.261.261 0 0 1 7 12.996c.001-.264.167-1.03.76-1.72C8.312 10.629 9.282 10 11 10c1.717 0 2.687.63 3.24 1.276.593.69.758 1.457.76 1.72l-.008.002a.274.274 0 0 1-.014.002H7.022zM11 7a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm3-2a3 3 0 1 1-6 0 3 3 0 0 1 6 0zM6.936 9.28a5.88 5.88 0 0 0-1.23-.247A7.35 7.35 0 0 0 5 9c-4 0-5 3-5 4 0 .667.333 1 1 1h4.216A2.238 2.238 0 0 1 5 13c0-1.01.377-2.042 1.09-2.904.243-.294.526-.569.846-.816zM4.92 10A5.493 5.493 0 0 0 4 13H1c0-.26.164-1.03.76-1.724.545-.636 1.492-1.256 3.16-1.275zM1.5 5.5a3 3 0 1 1 6 0 3 3 0 0 1-6 0zm3-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h5 class="card-title mb-1">Users</h5>
|
<h2 class="text-lg font-medium text-gray-900">Users</h2>
|
||||||
<p class="text-muted mb-0">Registered accounts</p>
|
<p class="text-sm text-gray-500">Registered accounts</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="h2 mb-0">{{.UserCount}}</p>
|
<p class="text-4xl font-medium text-gray-900">{{.UserCount}}</p>
|
||||||
<small class="text-muted">Total users</small>
|
<p class="text-sm text-gray-500 mt-1">Total users</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if not .User}}
|
{{if not .User}}
|
||||||
<div class="text-center mt-5">
|
<div class="text-center mt-10">
|
||||||
<p class="text-muted">Ready to get started?</p>
|
<p class="text-gray-500 mb-4">Ready to get started?</p>
|
||||||
<a href="/pages/login" class="btn btn-primary">Login to your account</a>
|
<a href="/pages/login" class="btn-primary">Login to your account</a>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
+38
-67
@@ -2,85 +2,56 @@
|
|||||||
|
|
||||||
{{define "title"}}Login - Webhooker{{end}}
|
{{define "title"}}Login - Webhooker{{end}}
|
||||||
|
|
||||||
{{define "head"}}
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
.login-container {
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 100px auto;
|
|
||||||
}
|
|
||||||
.login-card {
|
|
||||||
background: white;
|
|
||||||
border-radius: 10px;
|
|
||||||
box-shadow: 0 0 20px rgba(0,0,0,0.1);
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
.login-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
.login-header h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
.login-header p {
|
|
||||||
color: #666;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
.form-control:focus {
|
|
||||||
border-color: #007bff;
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
|
|
||||||
}
|
|
||||||
.btn-primary {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
.error-message {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="container">
|
<div class="min-h-screen flex items-center justify-center py-12 px-4">
|
||||||
<div class="login-container">
|
<div class="max-w-md w-full">
|
||||||
<div class="login-card">
|
<div class="text-center mb-8">
|
||||||
<div class="login-header">
|
<h1 class="text-3xl font-medium text-gray-900">Webhooker</h1>
|
||||||
<h1>Webhooker</h1>
|
<p class="mt-2 text-gray-600">Sign in to your account</p>
|
||||||
<p>Sign in to your account</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card p-8">
|
||||||
{{if .Error}}
|
{{if .Error}}
|
||||||
<div class="alert alert-danger error-message" role="alert">
|
<div class="alert-error">
|
||||||
{{.Error}}
|
<div class="flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
<span>{{.Error}}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<form method="POST" action="/pages/login">
|
<form method="POST" action="/pages/login" class="space-y-6">
|
||||||
<div class="mb-3">
|
<div class="form-group">
|
||||||
<label for="username" class="form-label">Username</label>
|
<label for="username" class="label">Username</label>
|
||||||
<input type="text" class="form-control" id="username" name="username"
|
<input
|
||||||
placeholder="Enter your username" required autofocus>
|
type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
autofocus
|
||||||
|
autocomplete="username"
|
||||||
|
placeholder="Enter your username"
|
||||||
|
class="input"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="form-group">
|
||||||
<label for="password" class="form-label">Password</label>
|
<label for="password" class="label">Password</label>
|
||||||
<input type="password" class="form-control" id="password" name="password"
|
<input
|
||||||
placeholder="Enter your password" required>
|
type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
required
|
||||||
|
autocomplete="current-password"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
class="input"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Sign In</button>
|
<button type="submit" class="btn-primary w-full py-3">Sign In</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="mt-4 text-center text-muted">
|
|
||||||
<small>© 2025 Webhooker. All rights reserved.</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+36
-32
@@ -1,46 +1,50 @@
|
|||||||
{{define "navbar"}}
|
{{define "navbar"}}
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
<nav class="app-bar" x-data="{ open: false }">
|
||||||
<div class="container-fluid">
|
<div class="max-w-6xl mx-auto flex justify-between items-center">
|
||||||
<a class="navbar-brand" href="/">Webhooker</a>
|
<div class="flex items-center gap-3">
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
<a href="/" class="text-xl font-medium text-gray-900 hover:text-primary-600 transition-colors">Webhooker</a>
|
||||||
<span class="navbar-toggler-icon"></span>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile menu button -->
|
||||||
|
<button @click="open = !open" class="md:hidden p-2 rounded-md text-gray-500 hover:bg-gray-100">
|
||||||
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path x-show="!open" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
|
||||||
|
<path x-show="open" x-cloak stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
|
||||||
<ul class="navbar-nav me-auto">
|
<!-- Desktop navigation -->
|
||||||
|
<div class="hidden md:flex items-center gap-4">
|
||||||
{{if .User}}
|
{{if .User}}
|
||||||
<li class="nav-item">
|
<a href="/sources" class="btn-text">Sources</a>
|
||||||
<a class="nav-link" href="/sources">Sources</a>
|
<a href="/user/{{.User.Username}}" class="btn-text">
|
||||||
</li>
|
<svg class="w-5 h-5 mr-1" fill="currentColor" viewBox="0 0 16 16">
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
<ul class="navbar-nav">
|
|
||||||
{{if .User}}
|
|
||||||
<!-- Logged in state -->
|
|
||||||
<li class="nav-item dropdown">
|
|
||||||
<a class="nav-link dropdown-toggle d-flex align-items-center" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-person-circle me-2" viewBox="0 0 16 16">
|
|
||||||
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
||||||
</svg>
|
</svg>
|
||||||
{{.User.Username}}
|
{{.User.Username}}
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<form method="POST" action="/pages/logout" class="inline">
|
||||||
<li><a class="dropdown-item" href="/user/{{.User.Username}}">Profile</a></li>
|
<button type="submit" class="btn-text">Logout</button>
|
||||||
<li><hr class="dropdown-divider"></li>
|
|
||||||
<li>
|
|
||||||
<form method="POST" action="/pages/logout" class="m-0">
|
|
||||||
<button type="submit" class="dropdown-item">Logout</button>
|
|
||||||
</form>
|
</form>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
{{else}}
|
{{else}}
|
||||||
<!-- Logged out state -->
|
<a href="/pages/login" class="btn-primary">Login</a>
|
||||||
<li class="nav-item">
|
{{end}}
|
||||||
<a class="nav-link" href="/pages/login">Login</a>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile navigation -->
|
||||||
|
<div x-show="open" x-cloak x-transition class="md:hidden mt-4 pt-4 border-t border-gray-200">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{{if .User}}
|
||||||
|
<a href="/sources" class="btn-text w-full text-left">Sources</a>
|
||||||
|
<a href="/user/{{.User.Username}}" class="btn-text w-full text-left">Profile</a>
|
||||||
|
<form method="POST" action="/pages/logout">
|
||||||
|
<button type="submit" class="btn-text w-full text-left">Logout</button>
|
||||||
|
</form>
|
||||||
|
{{else}}
|
||||||
|
<a href="/pages/login" class="btn-primary w-full">Login</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
+27
-30
@@ -3,51 +3,48 @@
|
|||||||
{{define "title"}}Profile - Webhooker{{end}}
|
{{define "title"}}Profile - Webhooker{{end}}
|
||||||
|
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<div class="container mt-5">
|
<div class="max-w-4xl mx-auto px-6 py-12">
|
||||||
<div class="row">
|
<h1 class="text-2xl font-medium text-gray-900 mb-6">User Profile</h1>
|
||||||
<div class="col-lg-8 mx-auto">
|
|
||||||
<h1 class="mb-4">User Profile</h1>
|
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="card p-6">
|
||||||
<div class="card-body">
|
<div class="flex items-center mb-6">
|
||||||
<div class="row align-items-center mb-3">
|
<div class="mr-4">
|
||||||
<div class="col-auto">
|
<svg class="w-16 h-16 text-primary-500" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" class="bi bi-person-circle text-primary" viewBox="0 0 16 16">
|
|
||||||
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
|
||||||
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div>
|
||||||
<h3 class="mb-0">{{.User.Username}}</h3>
|
<h2 class="text-xl font-medium text-gray-900">{{.User.Username}}</h2>
|
||||||
<p class="text-muted mb-0">User ID: {{.User.ID}}</p>
|
<p class="text-sm text-gray-500">User ID: {{.User.ID}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr>
|
<hr class="border-gray-200 mb-6">
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<h5>Account Information</h5>
|
<h3 class="text-lg font-medium text-gray-900 mb-3">Account Information</h3>
|
||||||
<dl class="row">
|
<dl class="space-y-3">
|
||||||
<dt class="col-sm-4">Username</dt>
|
<div class="flex">
|
||||||
<dd class="col-sm-8">{{.User.Username}}</dd>
|
<dt class="w-32 text-sm font-medium text-gray-500">Username</dt>
|
||||||
|
<dd class="text-sm text-gray-900">{{.User.Username}}</dd>
|
||||||
<dt class="col-sm-4">Account Type</dt>
|
</div>
|
||||||
<dd class="col-sm-8">Standard User</dd>
|
<div class="flex">
|
||||||
|
<dt class="w-32 text-sm font-medium text-gray-500">Account Type</dt>
|
||||||
|
<dd class="text-sm text-gray-900">Standard User</dd>
|
||||||
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<h5>Settings</h5>
|
<h3 class="text-lg font-medium text-gray-900 mb-3">Settings</h3>
|
||||||
<p class="text-muted">Profile settings and preferences will be available here.</p>
|
<p class="text-sm text-gray-500">Profile settings and preferences will be available here.</p>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-6">
|
||||||
<a href="/" class="btn btn-secondary">Back to Home</a>
|
<a href="/" class="btn-secondary">Back to Home</a>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
Reference in new issue
Block a user