jops-core 1.0.15 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +344 -28
- package/bin/init.js +138 -20
- package/lib/jops-core.d.ts +251 -15
- package/lib/jops-core.min.js +1 -1
- package/lib/nextjs/JopsApp.d.ts +35 -0
- package/lib/nextjs/JopsApp.js +50 -0
- package/package.json +33 -5
- package/templates/nextjs-jops-spa/README.md +34 -0
- package/templates/nextjs-jops-spa/data/quotes.json +62 -0
- package/templates/nextjs-jops-spa/gitignore +21 -0
- package/templates/nextjs-jops-spa/next.config.ts +15 -0
- package/templates/nextjs-jops-spa/package.json +25 -0
- package/templates/nextjs-jops-spa/public/jops-spa/App.js +10 -0
- package/templates/nextjs-jops-spa/public/jops-spa/Login.js +26 -0
- package/templates/nextjs-jops-spa/public/jops-spa/Quotes.js +102 -0
- package/templates/nextjs-jops-spa/public/jops-spa/api.js +15 -0
- package/templates/nextjs-jops-spa/public/jops-spa/badge.js +8 -0
- package/templates/nextjs-jops-spa/public/jops-spa/format.js +5 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/login.html +34 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/main.html +17 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/quotes.html +50 -0
- package/templates/nextjs-jops-spa/public/jops-spa/layout/quotes_row.html +8 -0
- package/templates/nextjs-jops-spa/public/jops-spa/skeleton.js +7 -0
- package/templates/nextjs-jops-spa/src/app/about/page.tsx +59 -0
- package/templates/nextjs-jops-spa/src/app/api/quotes/route.ts +11 -0
- package/templates/nextjs-jops-spa/src/app/globals.css +6 -0
- package/templates/nextjs-jops-spa/src/app/jops-spa/page.tsx +11 -0
- package/templates/nextjs-jops-spa/src/app/layout.tsx +27 -0
- package/templates/nextjs-jops-spa/src/app/page.tsx +5 -0
- package/templates/nextjs-jops-spa/tsconfig.json +21 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<div class="quotes-list">
|
|
2
|
+
<div class="mb-6 flex flex-wrap items-end justify-between gap-4">
|
|
3
|
+
<div>
|
|
4
|
+
<h1 class="text-2xl font-semibold text-slate-900">Sales quotes</h1>
|
|
5
|
+
<p class="mt-1 text-sm text-slate-500">
|
|
6
|
+
Signed in as <span id="user_name" class="font-medium text-slate-700"></span>.
|
|
7
|
+
Data from <code class="rounded bg-slate-100 px-1">GET /api/quotes</code>.
|
|
8
|
+
</p>
|
|
9
|
+
</div>
|
|
10
|
+
<button
|
|
11
|
+
jops-event-bind
|
|
12
|
+
type="button"
|
|
13
|
+
onclick="logout"
|
|
14
|
+
class="rounded-md border border-slate-300 bg-white px-3.5 py-2 text-sm font-medium text-slate-700 hover:bg-slate-50"
|
|
15
|
+
>
|
|
16
|
+
Log out
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- Filter tabs: buttons (not href="#" links), so a click adds no history entry. -->
|
|
21
|
+
<nav id="tabs" class="mb-4 flex flex-wrap gap-1" aria-label="Filter by status">
|
|
22
|
+
<button jops-event-bind type="button" onclick="filterQuotes" data-status="" class="rounded-md px-3 py-1.5 text-sm capitalize bg-indigo-600 text-white">all</button>
|
|
23
|
+
<button jops-event-bind type="button" onclick="filterQuotes" data-status="draft" class="rounded-md px-3 py-1.5 text-sm capitalize bg-white text-slate-600 ring-1 ring-slate-200 hover:bg-slate-50">draft</button>
|
|
24
|
+
<button jops-event-bind type="button" onclick="filterQuotes" data-status="sent" class="rounded-md px-3 py-1.5 text-sm capitalize bg-white text-slate-600 ring-1 ring-slate-200 hover:bg-slate-50">sent</button>
|
|
25
|
+
<button jops-event-bind type="button" onclick="filterQuotes" data-status="accepted" class="rounded-md px-3 py-1.5 text-sm capitalize bg-white text-slate-600 ring-1 ring-slate-200 hover:bg-slate-50">accepted</button>
|
|
26
|
+
<button jops-event-bind type="button" onclick="filterQuotes" data-status="rejected" class="rounded-md px-3 py-1.5 text-sm capitalize bg-white text-slate-600 ring-1 ring-slate-200 hover:bg-slate-50">rejected</button>
|
|
27
|
+
</nav>
|
|
28
|
+
|
|
29
|
+
<!-- Skeleton while GET /api/quotes runs. -->
|
|
30
|
+
<div id="quotes-loading"></div>
|
|
31
|
+
<div id="quotes-table" hidden class="overflow-x-auto rounded-lg border border-slate-200 bg-white shadow-sm">
|
|
32
|
+
<table class="min-w-full divide-y divide-slate-200 text-sm">
|
|
33
|
+
<thead class="bg-slate-50 text-left text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
34
|
+
<tr>
|
|
35
|
+
<th class="px-4 py-3">Quote</th>
|
|
36
|
+
<th class="px-4 py-3">Company</th>
|
|
37
|
+
<th class="px-4 py-3">Deal</th>
|
|
38
|
+
<th class="px-4 py-3">Status</th>
|
|
39
|
+
<th class="px-4 py-3">Created</th>
|
|
40
|
+
<th class="px-4 py-3 text-right">Total</th>
|
|
41
|
+
</tr>
|
|
42
|
+
</thead>
|
|
43
|
+
<!-- One QuoteRow view per quote, added by Quotes.loadQuotes(). -->
|
|
44
|
+
<tbody id="quote-rows" class="divide-y divide-slate-100"></tbody>
|
|
45
|
+
</table>
|
|
46
|
+
</div>
|
|
47
|
+
<p id="quotes-empty" hidden class="mt-4 rounded-lg border border-dashed border-slate-300 p-8 text-center text-slate-500">
|
|
48
|
+
No quotes here yet.
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<tr class="hover:bg-slate-50">
|
|
2
|
+
<td id="quote_number" class="whitespace-nowrap px-4 py-3 font-medium text-indigo-700"></td>
|
|
3
|
+
<td id="company_name" class="px-4 py-3 text-slate-700"></td>
|
|
4
|
+
<td id="deal_title" class="px-4 py-3 text-slate-700"></td>
|
|
5
|
+
<td id="quote_status" class="px-4 py-3 text-slate-700"></td>
|
|
6
|
+
<td id="created_at" class="whitespace-nowrap px-4 py-3 text-slate-500"></td>
|
|
7
|
+
<td id="total" class="px-4 py-3 text-right tabular-nums font-medium text-slate-700"></td>
|
|
8
|
+
</tr>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Loading placeholder for a table. Full class names, so Tailwind finds them.
|
|
2
|
+
const BAR = "animate-pulse rounded bg-slate-200 h-4 flex-1";
|
|
3
|
+
|
|
4
|
+
export function tableSkeleton(rows = 4, cols = 6) {
|
|
5
|
+
const row = `<div class="flex gap-4">${`<div class="${BAR}"></div>`.repeat(cols)}</div>`;
|
|
6
|
+
return `<div class="rounded-lg border border-slate-200 bg-white shadow-sm p-4"><div class="space-y-3">${row.repeat(rows)}</div></div>`;
|
|
7
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = { title: "About" };
|
|
5
|
+
|
|
6
|
+
const steps = [
|
|
7
|
+
{
|
|
8
|
+
title: "Next.js owns the routes, the shell and the backend",
|
|
9
|
+
text: "This page and the header are plain React Server Components. The quotes data comes from a Next.js Route Handler (src/app/api/quotes/route.ts) reading data/quotes.json.",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
title: "JopsApp launches the SPA",
|
|
13
|
+
text: "src/app/jops-spa/page.tsx renders <JopsApp app=\"/jops-spa/App.js\" /> from jops-core/nextjs. It loads /lib/jops-core.min.js once, adds the \"jops-core\" import map and mounts the app into its own <div>.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
title: "The SPA lives in public/jops-spa/",
|
|
17
|
+
text: "Plain ES modules and HTML layouts, served as-is (never bundled): a Router with a mock login (#login) and a quotes list (#quotes) that calls GET /api/quotes. Paths are absolute (/jops-spa/…), and the library is vendored in public/lib/.",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
title: "JOPS owns the #hash, Next.js owns the /path",
|
|
21
|
+
text: "Inside /jops-spa the JOPS Router handles #login and #quotes. Leaving the page (a link or Back) ends the JOPS app completely; returning launches it again.",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
title: "One stylesheet",
|
|
25
|
+
text: "Tailwind is generated by Next.js and also covers the classes used in public/jops-spa/; JOPS's setStyle() has no effect in an embedded app.",
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// A React-only page: no JOPS involved.
|
|
30
|
+
export default function AboutPage() {
|
|
31
|
+
return (
|
|
32
|
+
<article className="space-y-8">
|
|
33
|
+
<header>
|
|
34
|
+
<h1 className="text-2xl font-semibold text-slate-900">Next.js + JOPS embedded SPA</h1>
|
|
35
|
+
<p className="mt-2 max-w-2xl text-slate-600">
|
|
36
|
+
A Next.js App Router project that hosts a JOPS single-page app on one of its pages. Next.js
|
|
37
|
+
provides the routes, the shell and the API; JOPS provides a class-based SPA with its own
|
|
38
|
+
views, lifecycle and router.
|
|
39
|
+
</p>
|
|
40
|
+
</header>
|
|
41
|
+
<ol className="space-y-4">
|
|
42
|
+
{steps.map((step, i) => (
|
|
43
|
+
<li key={step.title} className="rounded-lg border border-slate-200 bg-white p-5 shadow-sm">
|
|
44
|
+
<p className="font-medium text-slate-900">
|
|
45
|
+
{i + 1}. {step.title}
|
|
46
|
+
</p>
|
|
47
|
+
<p className="mt-1 text-sm text-slate-600">{step.text}</p>
|
|
48
|
+
</li>
|
|
49
|
+
))}
|
|
50
|
+
</ol>
|
|
51
|
+
<Link
|
|
52
|
+
href="/jops-spa"
|
|
53
|
+
className="inline-flex rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-500"
|
|
54
|
+
>
|
|
55
|
+
Open the JOPS SPA →
|
|
56
|
+
</Link>
|
|
57
|
+
</article>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
2
|
+
import quotes from "../../../../data/quotes.json";
|
|
3
|
+
|
|
4
|
+
// GET /api/quotes[?status=draft|sent|accepted|rejected]
|
|
5
|
+
// The JOPS SPA's only backend call (getQuotes() in public/jops-spa/api.js).
|
|
6
|
+
// Static demo data from data/quotes.json; replace with your own data source.
|
|
7
|
+
export async function GET(request: NextRequest) {
|
|
8
|
+
const status = request.nextUrl.searchParams.get("status");
|
|
9
|
+
const data = status ? quotes.filter((q) => q.status === status) : quotes;
|
|
10
|
+
return Response.json({ data, total: data.length });
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import JopsApp from "jops-core/nextjs";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = { title: "JOPS SPA" };
|
|
5
|
+
|
|
6
|
+
// Launches the JOPS SPA in public/jops-spa/ inside this page. JopsApp loads
|
|
7
|
+
// /lib/jops-core.min.js once, adds its import map and mounts the app into its
|
|
8
|
+
// own <div>; leaving the page ends the app completely.
|
|
9
|
+
export default function JopsSpaPage() {
|
|
10
|
+
return <JopsApp app="/jops-spa/App.js" id="jops-spa-root" />;
|
|
11
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
import "./globals.css";
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: { default: "Next.js + JOPS", template: "%s · Next.js + JOPS" },
|
|
7
|
+
description: "A Next.js app hosting an embedded JOPS SPA",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// Root layout (Next.js): a shared shell around every page. The JOPS SPA is
|
|
11
|
+
// launched only by /jops-spa, inside this shell.
|
|
12
|
+
export default function RootLayout({ children }: LayoutProps<"/">) {
|
|
13
|
+
return (
|
|
14
|
+
<html lang="en" className="h-full antialiased">
|
|
15
|
+
<body className="min-h-full font-sans">
|
|
16
|
+
<header className="border-b border-slate-200 bg-white">
|
|
17
|
+
<nav className="mx-auto flex max-w-5xl items-center gap-6 px-6 py-4 text-sm">
|
|
18
|
+
<span className="font-semibold text-indigo-700">Next.js + JOPS</span>
|
|
19
|
+
<Link href="/about" className="text-slate-600 hover:text-slate-900">About</Link>
|
|
20
|
+
<Link href="/jops-spa" className="text-slate-600 hover:text-slate-900">JOPS SPA</Link>
|
|
21
|
+
</nav>
|
|
22
|
+
</header>
|
|
23
|
+
<main className="mx-auto max-w-5xl px-6 py-8">{children}</main>
|
|
24
|
+
</body>
|
|
25
|
+
</html>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": { "@/*": ["./src/*"] }
|
|
18
|
+
},
|
|
19
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts", "**/*.mts"],
|
|
20
|
+
"exclude": ["node_modules", "public"]
|
|
21
|
+
}
|