cloudflare-htmx
Advanced tools
Comparing version 1.0.7 to 1.1.0
@@ -1,4 +0,4 @@ | ||
import RootLayout from '@src/layouts/RootLayout'; | ||
import { applyLayout } from '@src/lib/html'; | ||
import RootLayout from '@layouts/RootLayout'; | ||
import { applyLayout } from '@lib/html'; | ||
export const onRequestGet = [applyLayout(RootLayout)]; |
@@ -1,19 +0,9 @@ | ||
import DashLayout from '@src/layouts/DashLayout'; | ||
import { applyLayout } from '@src/lib/html'; | ||
import { getSupabase } from '@src/model/supabase'; | ||
import DashLayout from '@layouts/DashLayout'; | ||
import { authConfig } from '@lib/constants'; | ||
import { applyLayout } from '@lib/html'; | ||
import { middlewareGuard } from 'cloudflare-auth'; | ||
const authentication: PagesFunction = async ({ request, next }) => { | ||
const url = new URL(request.url); | ||
const supabase = await getSupabase(request); | ||
const { | ||
data: { user }, | ||
} = await supabase.auth.getUser(); | ||
if (!user) { | ||
console.error('authentication: redirect to login', url.origin); | ||
return Response.redirect(url.origin, 303); | ||
} else { | ||
return next(); | ||
} | ||
}; | ||
export const onRequestGet = [authentication, applyLayout(DashLayout)]; | ||
export const onRequestGet = [ | ||
middlewareGuard(authConfig), | ||
applyLayout(DashLayout), | ||
]; |
@@ -1,4 +0,4 @@ | ||
import SubLayout from '@src/layouts/SubLayout'; | ||
import { applyLayout } from '@src/lib/html'; | ||
import SubLayout from '@layouts/SubLayout'; | ||
import { applyLayout } from '@lib/html'; | ||
export const onRequestGet = [applyLayout(SubLayout)]; |
@@ -1,2 +0,2 @@ | ||
import { html, htmlResponse } from "@src/lib/html"; | ||
import { html, htmlResponse } from '@lib/html'; | ||
@@ -3,0 +3,0 @@ export const onRequestPost: PagesFunction = async ({ request }) => { |
@@ -1,5 +0,5 @@ | ||
import Stat from '@src/components/Stat'; | ||
import Table from '@src/components/Table'; | ||
import Stat from '@components/Stat'; | ||
import Table from '@components/Table'; | ||
import { html, htmlResponse } from '@src/lib/html'; | ||
import { html, htmlResponse } from '@lib/html'; | ||
@@ -6,0 +6,0 @@ const timer = (ms: number) => new Promise((res) => setTimeout(res, ms)); |
@@ -1,38 +0,34 @@ | ||
import Toast from "@src/components/Toast"; | ||
import { getSupabase } from "@src/model/supabase"; | ||
import Toast from '@components/Toast'; | ||
import { Env, isAuthorised, login } from 'cloudflare-auth'; | ||
import { html, htmlResponse } from "@src/lib/html"; | ||
import { html, htmlResponse } from '@lib/html'; | ||
import { authConfig } from '@lib/constants'; | ||
export const onRequestPost: PagesFunction = async (event) => { | ||
const data = await event.request.formData(); | ||
const email = data.get("email") as string; | ||
export const onRequestPost: PagesFunction<Env> = async ({ request, env }) => { | ||
const url = new URL(request.url); | ||
const data = await request.formData(); | ||
const email = data.get('email') as string; | ||
if (!email) { | ||
return htmlResponse(Toast("Email not specified", "alert-error")); | ||
return htmlResponse(Toast('Email not specified')); | ||
} | ||
const supabase = await getSupabase(); | ||
const url = new URL(event.request.url); | ||
const { error } = await supabase.auth.signInWithOtp({ | ||
email, | ||
options: { | ||
emailRedirectTo: `${url.origin}/logging-in`, | ||
}, | ||
}); | ||
if (error) { | ||
return htmlResponse(Toast(error.message, "alert-error")); | ||
const token = await login(email, env); | ||
const magicLink = `${url.origin}/verify?token=${token}`; | ||
try { | ||
return htmlResponse( | ||
Toast( | ||
'Click to login: <a href="' + magicLink + '">' + magicLink + '</a>', | ||
'alert-success' | ||
) | ||
); | ||
} catch { | ||
return htmlResponse(Toast('Magic link failed to send!', 'alert-failure')); | ||
} | ||
return htmlResponse( | ||
Toast("Please check your email for a magic link to log into the website") | ||
); | ||
}; | ||
export const onRequestGet: PagesFunction = async ({ request }) => { | ||
const supabase = await getSupabase(request); | ||
const { | ||
data: { user }, | ||
} = await supabase.auth.getUser(); | ||
if (user) { | ||
console.log("redirect to dashboard"); | ||
const loggedIn = await isAuthorised(authConfig, request); | ||
if (loggedIn) { | ||
console.log('redirect to dashboard'); | ||
const url = new URL(request.url); | ||
return Response.redirect(url.origin + "/dashboard", 303); | ||
return Response.redirect(url.origin + '/dashboard', 303); | ||
} | ||
@@ -39,0 +35,0 @@ return htmlResponse(html` |
@@ -1,7 +0,7 @@ | ||
import Spinner from "@src/components/Spinner"; | ||
import { html, htmlResponse } from "@src/lib/html"; | ||
import Spinner from '@components/Spinner'; | ||
import { html, htmlResponse } from '@lib/html'; | ||
export const onRequestGet: PagesFunction = async () => | ||
htmlResponse(html` | ||
<div class="h-screen">${Spinner("Logging in ...")}</div> | ||
<div class="h-screen">${Spinner('Logging in ...')}</div> | ||
`); |
@@ -1,13 +0,6 @@ | ||
import Toast from '@src/components/Toast'; | ||
import { htmlResponse } from '@src/lib/html'; | ||
import { authConfig } from '@lib/constants'; | ||
import { logout } from 'cloudflare-auth'; | ||
export const onRequestPost: PagesFunction = async ({ request }) => { | ||
let response = htmlResponse(Toast('Successfully logged out')); | ||
const accessCookie = `sb-access-token=''; path=/; max-age=-1; SameSite=Lax;`; | ||
const refreshCookie = `sb-refresh-token=''; path=/; max-age=-1; SameSite=Lax;`; | ||
response.headers.append('Set-Cookie', accessCookie); | ||
response.headers.append('Set-Cookie', refreshCookie); | ||
response.headers.append('HX-Redirect', '/'); | ||
return response; | ||
return logout(authConfig, new URL(request.url)); | ||
}; |
@@ -7,5 +7,6 @@ { | ||
"dev": "wrangler pages dev ./static --compatibility-date=2023-05-01", | ||
"daisyui": "^2.51.5", | ||
"publish": "wrangler pages publish ./static", | ||
"tailwind": "tailwindcss build -i ./tailwind.css -o ./static/assets/css/output.css --watch" | ||
"tailwind": "tailwindcss build -i ./tailwind.css -o ./static/assets/css/output.css --watch", | ||
"db:create": "wrangler d1 create auth --experimental-backend", | ||
"db:init": "wrangler d1 execute auth --local --file=./schema.sql" | ||
}, | ||
@@ -16,7 +17,7 @@ "devDependencies": { | ||
"tailwindcss": "^3.3.1", | ||
"wrangler": "2.15.0" | ||
"wrangler": "^3.0.1" | ||
}, | ||
"private": true, | ||
"dependencies": { | ||
"@supabase/supabase-js": "^2.21.0", | ||
"cloudflare-auth": "^1.0.0", | ||
"cookie": "^0.5.0", | ||
@@ -23,0 +24,0 @@ "daisyui": "^2.51.6" |
@@ -1,2 +0,2 @@ | ||
import { html, htmlResponse } from "@src/lib/html"; | ||
import { html, htmlResponse } from '@lib/html'; | ||
@@ -14,4 +14,4 @@ /* | ||
const links = [ | ||
{ text: "Home", href: "/dashboard" }, | ||
{ text: "Page", href: "/dashboard/4" }, | ||
{ text: 'Home', href: '/dashboard' }, | ||
{ text: 'Page', href: '/dashboard/4' }, | ||
]; | ||
@@ -37,3 +37,3 @@ return html` <div class="navbar bg-base-100"> | ||
) | ||
.join("\n")} | ||
.join('\n')} | ||
</ul> | ||
@@ -40,0 +40,0 @@ </div> |
@@ -1,2 +0,2 @@ | ||
import { html } from '@src/lib/html'; | ||
import { html } from '@lib/html'; | ||
@@ -3,0 +3,0 @@ export default (message: string) => html` |
@@ -1,2 +0,2 @@ | ||
import { html, htmlResponse } from "@src/lib/html"; | ||
import { html, htmlResponse } from '@lib/html'; | ||
@@ -3,0 +3,0 @@ export default () => html` |
@@ -1,2 +0,2 @@ | ||
import { html, htmlResponse } from "@src/lib/html"; | ||
import { html, htmlResponse } from '@lib/html'; | ||
@@ -90,3 +90,3 @@ type Item = { | ||
) | ||
.join("\n")} | ||
.join('\n')} | ||
</tbody> | ||
@@ -93,0 +93,0 @@ <!-- foot --> |
@@ -1,8 +0,12 @@ | ||
import { html } from '@src/lib/html'; | ||
import { html } from '@lib/html'; | ||
export default (message: string, alertClass = 'alert-success') => { | ||
export default ( | ||
message: string, | ||
alertClass = 'alert-success', | ||
remove = true | ||
) => { | ||
return html` | ||
<div | ||
class="toast toast-end w-full md:w-76 mt-10" | ||
_="on load wait 3s add .fadeOut wait 1s remove me" | ||
${remove ? `_="on load wait 3s add .fadeOut wait 1s remove me"` : ''} | ||
> | ||
@@ -9,0 +13,0 @@ <div class="alert ${alertClass} justify-center"> |
@@ -1,3 +0,3 @@ | ||
import NavBar from "@src/components/NavBar"; | ||
import { LayoutFunction, html } from "@src/lib/html"; | ||
import NavBar from '@components/NavBar'; | ||
import { LayoutFunction, html } from '@lib/html'; | ||
@@ -4,0 +4,0 @@ const _layout: LayoutFunction = async ({ children }) => { |
@@ -1,3 +0,2 @@ | ||
import SupabaseAuth from '@src/components/SupabaseAuth'; | ||
import { html, LayoutFunction } from '@src/lib/html'; | ||
import { html, LayoutFunction } from '@lib/html'; | ||
@@ -22,3 +21,2 @@ // this is the layout for the entire site | ||
<div id="modal"></div> | ||
${SupabaseAuth('/dashboard')} | ||
</body> | ||
@@ -25,0 +23,0 @@ </html> |
@@ -1,2 +0,2 @@ | ||
import { html, LayoutFunction } from "@src/lib/html"; | ||
import { html, LayoutFunction } from '@lib/html'; | ||
@@ -3,0 +3,0 @@ // this is the layout for all pages inside the [paramId] folder and its subfolders |
{ | ||
"compilerOptions": { | ||
"target": "es2021", | ||
"lib": [ | ||
"es2021" | ||
], | ||
"module": "es2022", | ||
"moduleResolution": "node", | ||
"types": [ | ||
"@cloudflare/workers-types" | ||
], | ||
"resolveJsonModule": true, | ||
"allowJs": true, | ||
"checkJs": false, | ||
"noEmit": true, | ||
"isolatedModules": true, | ||
"allowSyntheticDefaultImports": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"strict": true, | ||
"skipLibCheck": true, | ||
"baseUrl": "src", | ||
"paths": { | ||
"@src/*": ["*"] | ||
} | ||
} | ||
"compilerOptions": { | ||
"target": "es2021", | ||
"lib": ["es2021"], | ||
"module": "es2022", | ||
"moduleResolution": "node", | ||
"types": ["@cloudflare/workers-types"], | ||
"resolveJsonModule": true, | ||
"allowJs": true, | ||
"checkJs": false, | ||
"noEmit": true, | ||
"isolatedModules": true, | ||
"allowSyntheticDefaultImports": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"strict": true, | ||
"skipLibCheck": true, | ||
"baseUrl": "src", | ||
"paths": { | ||
"@*": ["*"] | ||
} | ||
} | ||
} |
19
index.ts
@@ -5,11 +5,13 @@ const HEADERS = { headers: { 'content-type': 'text/html;charset=UTF-8' } }; | ||
export const htmlResponse = (dom: string) => new Response(dom, HEADERS); | ||
export interface LayoutFunction { | ||
(props: { children: string; request?: Request; params?: Params<any> }): | ||
| string | ||
| Promise<string>; | ||
} | ||
declare type LayoutFunction<Env = unknown, Params extends string = any> = ( | ||
children: string, | ||
request?: Request, | ||
params?: Params, | ||
env?: Env | ||
) => string | Promise<string>; | ||
export const applyLayout = | ||
(layout: LayoutFunction): PagesFunction => | ||
async ({ request, next }) => { | ||
async ({ request, env, params, next }) => { | ||
const url = new URL(request.url); | ||
@@ -28,3 +30,6 @@ const method = request.method; | ||
if (response.ok) { | ||
return new Response(await layout({ children, request }), response); | ||
return new Response( | ||
await layout(children, request, params, env), | ||
response | ||
); | ||
} else { | ||
@@ -31,0 +36,0 @@ return new Response('NOT FOUND', response); |
{ | ||
"name": "cloudflare-htmx", | ||
"version": "1.0.7", | ||
"version": "1.1.0", | ||
"description": "HTMX library for Cloudflare Pages", | ||
@@ -5,0 +5,0 @@ "main": "index.ts", |
@@ -22,3 +22,3 @@ # Cloudflare Pages + HTMX | ||
- Endpoints should return HTML strings wrapped in a `new Response()`. | ||
- `import { html, htmlResponse } from "@src/lib/html"` declaration allows a string template to be syntax highlighted in VS Code | ||
- `import { html, htmlResponse } from "@lib/html"` declaration allows a string template to be syntax highlighted in VS Code | ||
- Use `_middleware.ts` files at any level of the folder structure to apply a layout. You should use this to at least apply your root level HTML wrapper. | ||
@@ -29,4 +29,4 @@ e.g. | ||
// functions/_middleware.ts | ||
import RootLayout from '@src/layouts/RootLayout'; | ||
import { applyLayout } from '@src/lib/html'; | ||
import RootLayout from '@layouts/RootLayout'; | ||
import { applyLayout } from '@lib/html'; | ||
@@ -40,4 +40,4 @@ export const onRequestGet = [applyLayout(RootLayout)]; | ||
```typescript | ||
import SupabaseAuth from '@src/components/SupabaseAuth'; | ||
import { html, LayoutFunction } from '@src/lib/html'; | ||
import SupabaseAuth from '@components/SupabaseAuth'; | ||
import { html, LayoutFunction } from '@lib/html'; | ||
@@ -76,5 +76,5 @@ // this is the layout for the entire site | ||
// functions/dashboard/_middleware.ts | ||
import DashLayout from '@src/layouts/DashLayout'; | ||
import { applyLayout } from '@src/lib/html'; | ||
import { getSupabase } from '@src/model/supabase'; | ||
import DashLayout from '@layouts/DashLayout'; | ||
import { applyLayout } from '@lib/html'; | ||
import { getSupabase } from '@model/supabase'; | ||
@@ -81,0 +81,0 @@ const authentication: PagesFunction = async ({ request, next }) => { |
import { applyLayout } from 'cloudflare-htmx'; | ||
import RootLayout from '@src/layouts/RootLayout'; | ||
import RootLayout from '@layouts/RootLayout'; | ||
export const onRequestGet = [applyLayout(RootLayout)]; |
{ | ||
"compilerOptions": { | ||
"target": "es2021", | ||
"lib": [ | ||
"es2021" | ||
], | ||
"module": "es2022", | ||
"moduleResolution": "node", | ||
"types": [ | ||
"@cloudflare/workers-types" | ||
], | ||
"resolveJsonModule": true, | ||
"allowJs": true, | ||
"checkJs": false, | ||
"noEmit": true, | ||
"isolatedModules": true, | ||
"allowSyntheticDefaultImports": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"strict": true, | ||
"skipLibCheck": true, | ||
"baseUrl": "src", | ||
"paths": { | ||
"@src/*": ["*"] | ||
} | ||
} | ||
"compilerOptions": { | ||
"target": "es2021", | ||
"lib": ["es2021"], | ||
"module": "es2022", | ||
"moduleResolution": "node", | ||
"types": ["@cloudflare/workers-types"], | ||
"resolveJsonModule": true, | ||
"allowJs": true, | ||
"checkJs": false, | ||
"noEmit": true, | ||
"isolatedModules": true, | ||
"allowSyntheticDefaultImports": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"strict": true, | ||
"skipLibCheck": true, | ||
"baseUrl": "src", | ||
"paths": { | ||
"@*": ["*"] | ||
} | ||
} | ||
} |
@@ -16,4 +16,8 @@ { | ||
"strict": true, | ||
"skipLibCheck": true | ||
"skipLibCheck": true, | ||
"baseUrl": ".", | ||
"paths": { | ||
"cloudflare-htmx": ["/"] | ||
} | ||
} | ||
} |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Native code
Supply chain riskContains native code (e.g., compiled binaries or shared libraries). Including native code can obscure malicious behavior.
Found 3 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1409890
50
22834
7