@swizzyweb/swizzy-frontend-template-web-service 0.2.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/Dockerfile ADDED
@@ -0,0 +1,10 @@
1
+ FROM node:24
2
+ USER root
3
+ WORKDIR /home/app/
4
+ COPY . .
5
+
6
+ RUN npm install
7
+ RUN npm run build
8
+ EXPOSE 3005
9
+ ENTRYPOINT ./entrypoint.sh
10
+
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @swizzyweb/swizzy-frontend-template-web-service
2
+
3
+ Sample react and tailwind based swizzy frontend web service. The sample app has a react based frontend
4
+ and a swizzy web service backend. A sample implementation of an api can be found in the
5
+ routers/Api directory.
6
+
7
+ ## Web service
8
+
9
+ The Swizzy web service logic can be found in the src directory.
10
+
11
+ ## React
12
+
13
+ The react code is in the react directory.
14
+
15
+ ## Running
16
+
17
+ ## Install
18
+
19
+ ```npm
20
+ npm install
21
+ ```
22
+
23
+ ## Build and run immediately
24
+
25
+ ```npm
26
+ npm run dev
27
+ ```
28
+
29
+ ## Only build
30
+
31
+ ```npm
32
+ npm run build
33
+ ```
34
+
35
+ ## Running server after build
36
+
37
+ ```npm
38
+ npm run server
39
+ ```
40
+
41
+ ## With swerve
42
+
43
+ After build you can just run `swerve` in the root directory.
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ presets: [
3
+ "@babel/preset-env",
4
+ ["@babel/preset-react", { runtime: "automatic" }], // 'automatic' for new JSX transform
5
+ "@babel/preset-typescript",
6
+ ],
7
+ };
package/dist/app.js ADDED
@@ -0,0 +1,15 @@
1
+ import { FunnyJokeClient } from "./client/index.js";
2
+ import { SampleFrontendWebService } from "./web-service.js";
3
+ export async function getWebservice(props) {
4
+ const state = {
5
+ funnyJokeClient: new FunnyJokeClient({
6
+ baseUrl: props.serviceArgs.funnyJokeBaseUrl,
7
+ }),
8
+ };
9
+ return new SampleFrontendWebService({
10
+ ...props,
11
+ ...props.serviceArgs,
12
+ state,
13
+ });
14
+ }
15
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAO5D,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAA6C;IAE7C,MAAM,KAAK,GAAG;QACZ,eAAe,EAAE,IAAI,eAAe,CAAC;YACnC,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,gBAAgB;SAC5C,CAAC;KACH,CAAC;IACF,OAAO,IAAI,wBAAwB,CAAC;QAClC,GAAG,KAAK;QACR,GAAG,KAAK,CAAC,WAAW;QACpB,KAAK;KACN,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,15 @@
1
+ const DEFAULT_BASE_URL = "https://official-joke-api.appspot.com";
2
+ export class FunnyJokeClient {
3
+ baseUrl;
4
+ constructor(props) {
5
+ this.baseUrl = props.baseUrl ?? DEFAULT_BASE_URL;
6
+ }
7
+ async getFunnyJoke(request) {
8
+ const response = await fetch(`${this.baseUrl}/jokes/random`, {
9
+ method: "get",
10
+ });
11
+ const joke = await response.json();
12
+ return joke;
13
+ }
14
+ }
15
+ //# sourceMappingURL=funny-joke-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"funny-joke-client.js","sourceRoot":"","sources":["../../src/client/funny-joke-client.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG,uCAAuC,CAAC;AAgBjE,MAAM,OAAO,eAAe;IAClB,OAAO,CAAS;IACxB,YAAY,KAA2B;QACrC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,gBAAgB,CAAC;IACnD,CAAC;IACD,KAAK,CAAC,YAAY,CAAC,OAAY;QAC7B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,eAAe,EAAE;YAC3D,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export * from "./funny-joke-client.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { RequestIdMiddleware, RequestLoggerMiddleware, SwizzyRequestMiddleware, WebRouter, } from "@swizzyweb/swizzy-web-service";
2
+ import { FunnyJokeController } from "./controllers/funny-joke-controller.js";
3
+ export class ApiWebRouter extends WebRouter {
4
+ constructor(props) {
5
+ super({
6
+ ...props,
7
+ name: "ApiWebRouter",
8
+ path: "api",
9
+ stateConverter: ApiRouterStateConverter,
10
+ webControllerClasses: [FunnyJokeController],
11
+ middleware: [
12
+ SwizzyRequestMiddleware,
13
+ RequestIdMiddleware,
14
+ RequestLoggerMiddleware,
15
+ ],
16
+ });
17
+ }
18
+ }
19
+ const ApiRouterStateConverter = async function (props) {
20
+ return { ...props.state };
21
+ };
22
+ //# sourceMappingURL=api-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-router.js","sourceRoot":"","sources":["../../../src/routers/ApiRouter/api-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,EACnB,uBAAuB,EAGvB,uBAAuB,EACvB,SAAS,GACV,MAAM,+BAA+B,CAAC;AAKvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAQ7E,MAAM,OAAO,YAAa,SAAQ,SAGjC;IACC,YAAY,KAAqB;QAC/B,KAAK,CAAC;YACJ,GAAG,KAAK;YACR,IAAI,EAAE,cAAc;YACpB,IAAI,EAAE,KAAK;YACX,cAAc,EAAE,uBAAuB;YACvC,oBAAoB,EAAE,CAAC,mBAAmB,CAAC;YAC3C,UAAU,EAAE;gBACV,uBAAuB;gBACvB,mBAAmB;gBACnB,uBAAuB;aACxB;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,uBAAuB,GAGzB,KAAK,WACP,KAAyD;IAEzD,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { DefaultStateExporter, RequestMethod, WebController, } from "@swizzyweb/swizzy-web-service";
2
+ export class FunnyJokeController extends WebController {
3
+ constructor(props) {
4
+ super({
5
+ ...props,
6
+ name: "FunnyJokeController",
7
+ action: "funnyJoke",
8
+ method: RequestMethod.get,
9
+ stateConverter: DefaultStateExporter,
10
+ middleware: [],
11
+ });
12
+ }
13
+ async getInitializedController(props) {
14
+ const logger = this.logger;
15
+ const getState = this.getState.bind(this);
16
+ return async function (req, res) {
17
+ logger.info("We got a jokster lookin for jokes!");
18
+ try {
19
+ const { funnyJokeClient } = getState();
20
+ const joke = await funnyJokeClient.getFunnyJoke({});
21
+ res.json({
22
+ message: "Here's your funny joke",
23
+ joke,
24
+ });
25
+ }
26
+ catch (e) {
27
+ res.status(500);
28
+ res.json({ message: "Internal error occurred" });
29
+ }
30
+ };
31
+ }
32
+ }
33
+ //# sourceMappingURL=funny-joke-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"funny-joke-controller.js","sourceRoot":"","sources":["../../../../src/routers/ApiRouter/controllers/funny-joke-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EAGpB,aAAa,EACb,aAAa,GAEd,MAAM,+BAA+B,CAAC;AAcvC,MAAM,OAAO,mBAAoB,SAAQ,aAGxC;IACC,YAAY,KAA+B;QACzC,KAAK,CAAC;YACJ,GAAG,KAAK;YACR,IAAI,EAAE,qBAAqB;YAC3B,MAAM,EAAE,WAAW;YACnB,MAAM,EAAE,aAAa,CAAC,GAAG;YACzB,cAAc,EAAE,oBAAoB;YACpC,UAAU,EAAE,EAAE;SACf,CAAC,CAAC;IACL,CAAC;IAES,KAAK,CAAC,wBAAwB,CACtC,KAEC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1C,OAAO,KAAK,WAAW,GAAY,EAAE,GAAa;YAChD,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;YAClD,IAAI,CAAC;gBACH,MAAM,EAAE,eAAe,EAAE,GAAG,QAAQ,EAAG,CAAC;gBACxC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;gBACpD,GAAG,CAAC,IAAI,CAAC;oBACP,OAAO,EAAE,wBAAwB;oBACjC,IAAI;iBACL,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAChB,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ import { RequestIdMiddleware, RequestLoggerMiddleware, SwizzyRequestMiddleware, WebRouter, } from "@swizzyweb/swizzy-web-service";
2
+ import path from "path";
3
+ // @ts-ignore
4
+ import express from "@swizzyweb/express";
5
+ import { fileURLToPath } from "node:url";
6
+ // This gives you the directory where *this* file is located
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ export class PageWebRouter extends WebRouter {
10
+ constructor(props) {
11
+ super({
12
+ ...props,
13
+ name: "PageWebRouter",
14
+ path: "",
15
+ stateConverter: PageRouterStateConverter,
16
+ webControllerClasses: [],
17
+ middleware: [
18
+ SwizzyRequestMiddleware,
19
+ RequestIdMiddleware,
20
+ RequestLoggerMiddleware,
21
+ () => express.static(path.join(__dirname, "../../../bundle")),
22
+ ],
23
+ });
24
+ }
25
+ }
26
+ const PageRouterStateConverter = async function (props) {
27
+ return { ...props.state };
28
+ };
29
+ //# sourceMappingURL=page-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-router.js","sourceRoot":"","sources":["../../../src/routers/PageRouter/page-router.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,EACnB,uBAAuB,EAGvB,uBAAuB,EACvB,SAAS,GACV,MAAM,+BAA+B,CAAC;AAEvC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,aAAa;AACb,OAAO,OAAO,MAAM,oBAAoB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,4DAA4D;AAC5D,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAM3C,MAAM,OAAO,aAAc,SAAQ,SAGlC;IACC,YAAY,KAAsB;QAChC,KAAK,CAAC;YACJ,GAAG,KAAK;YACR,IAAI,EAAE,eAAe;YACrB,IAAI,EAAE,EAAE;YACR,cAAc,EAAE,wBAAwB;YACxC,oBAAoB,EAAE,EAAE;YACxB,UAAU,EAAE;gBACV,uBAAuB;gBACvB,mBAAmB;gBACnB,uBAAuB;gBACvB,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;aAC9D;SACF,CAAC,CAAC;IACL,CAAC;CACF;AAED,MAAM,wBAAwB,GAG1B,KAAK,WACP,KAAyD;IAEzD,OAAO,EAAE,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;AAC5B,CAAC,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { WebService } from "@swizzyweb/swizzy-web-service";
2
+ import { PageWebRouter } from "./routers/PageRouter/page-router.js";
3
+ import { ApiWebRouter } from "./routers/ApiRouter/api-router.js";
4
+ export class SampleFrontendWebService extends WebService {
5
+ constructor(props) {
6
+ super({
7
+ ...props,
8
+ name: "SampleFrontendWebService",
9
+ path: props.path ?? "",
10
+ packageName: "@swizzyweb/swizzy-frontend-template-web-service",
11
+ routerClasses: [PageWebRouter, ApiWebRouter],
12
+ middleware: [],
13
+ });
14
+ }
15
+ }
16
+ //# sourceMappingURL=web-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-service.js","sourceRoot":"","sources":["../src/web-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAajE,MAAM,OAAO,wBAAyB,SAAQ,UAAyC;IACrF,YAAY,KAAoC;QAC9C,KAAK,CAAC;YACJ,GAAG,KAAK;YACR,IAAI,EAAE,0BAA0B;YAChC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;YACtB,WAAW,EAAE,iDAAiD;YAC9D,aAAa,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC;YAC5C,UAAU,EAAE,EAAE;SACf,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ services:
2
+ swizzywebsite:
3
+ build: .
4
+ ports:
5
+ - 3705:3005
6
+ volumes:
7
+ - ./logs:/home/app/logs
8
+ - ./appdata:/home/app/appdata
package/entrypoint.sh ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/bash
2
+
3
+ npm run server
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@swizzyweb/swizzy-frontend-template-web-service",
3
+ "version": "0.2.0",
4
+ "description": "Template for swizzyweb frontend web services",
5
+ "main": "dist/app.js",
6
+ "type": "module",
7
+ "author": "Jason Gallagher",
8
+ "scripts": {
9
+ "dev": "npm run build && npm run server",
10
+ "server": "swerve",
11
+ "build:service": "tsc",
12
+ "build": "npm run build:webpack && npm run build:service",
13
+ "start:webpack": "webpack serve --config webpack.config.cjs",
14
+ "build:webpack": "webpack --config webpack.config.cjs"
15
+ },
16
+ "keywords": [
17
+ "react",
18
+ "webpack",
19
+ "tailwind",
20
+ "route-optimizer",
21
+ "typescript"
22
+ ],
23
+ "license": "UNLICENSED",
24
+ "devDependencies": {
25
+ "@babel/core": "^7.28.4",
26
+ "@babel/preset-env": "^7.28.3",
27
+ "@babel/preset-react": "^7.27.1",
28
+ "@babel/preset-typescript": "^7.27.1",
29
+ "@swizzyweb/swerve": "^0.5.6",
30
+ "@tailwindcss/postcss": "^4.1.13",
31
+ "autoprefixer": "^10.4.21",
32
+ "babel-loader": "^9.2.1",
33
+ "css-loader": "^7.1.2",
34
+ "html-webpack-plugin": "^5.6.4",
35
+ "mini-css-extract-plugin": "^2.9.4",
36
+ "postcss": "^8.5.6",
37
+ "postcss-loader": "^8.2.0",
38
+ "style-loader": "^4.0.0",
39
+ "tailwindcss": "^4.1.7",
40
+ "typescript": "^5.9.2",
41
+ "webpack": "^5.101.3",
42
+ "webpack-cli": "^5.1.4",
43
+ "webpack-dev-server": "^5.2.2"
44
+ },
45
+ "dependencies": {
46
+ "@swizzyweb/express": "^4.19.2",
47
+ "@swizzyweb/swizzy-common": "^0.3.2",
48
+ "@swizzyweb/swizzy-web-service": "^0.5.4",
49
+ "react": "^18.3.1",
50
+ "react-dom": "^18.3.1"
51
+ }
52
+ }
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ plugins: {
3
+ // Use the new PostCSS plugin for Tailwind CSS
4
+ "@tailwindcss/postcss": {},
5
+ autoprefixer: {},
6
+ },
7
+ };
package/react/App.tsx ADDED
@@ -0,0 +1,153 @@
1
+ import React, { useState } from "react"; // Keep this as it's the actual library import
2
+ import { FunnyJokeTeller } from "./FunnyJokeTeller";
3
+
4
+ // Main App component
5
+ const App = () => {
6
+ return (
7
+ <div className="min-h-screen bg-gray-100 font-sans antialiased flex flex-col">
8
+ {/* Header Section */}
9
+ <header className="bg-gradient-to-r from-blue-600 to-purple-700 text-white p-6 shadow-lg rounded-b-xl">
10
+ <div className="container mx-auto flex justify-between items-center">
11
+ <h1 className="text-4xl font-extrabold tracking-tight">
12
+ My Awesome SwizzyWeb Site
13
+ </h1>
14
+ <nav>
15
+ <ul className="flex space-x-6">
16
+ <li>
17
+ <a
18
+ href="#"
19
+ className="text-white hover:text-blue-200 transition duration-300 text-lg font-medium"
20
+ >
21
+ Home
22
+ </a>
23
+ </li>
24
+ <li>
25
+ <a
26
+ href="#"
27
+ className="text-white hover:text-blue-200 transition duration-300 text-lg font-medium"
28
+ >
29
+ About
30
+ </a>
31
+ </li>
32
+ <li>
33
+ <a
34
+ href="#"
35
+ className="text-white hover:text-blue-200 transition duration-300 text-lg font-medium"
36
+ >
37
+ Services
38
+ </a>
39
+ </li>
40
+ <li>
41
+ <a
42
+ href="#"
43
+ className="text-white hover:text-blue-200 transition duration-300 text-lg font-medium"
44
+ >
45
+ Contact
46
+ </a>
47
+ </li>
48
+ </ul>
49
+ </nav>
50
+ </div>
51
+ </header>
52
+
53
+ {/* Main Content Section */}
54
+ <main className="flex-grow container mx-auto p-8 py-12">
55
+ <section className="bg-white p-8 rounded-xl shadow-lg mb-8">
56
+ <h2 className="text-3xl font-bold text-gray-800 mb-4">
57
+ Welcome to Our Site!
58
+ </h2>
59
+ <p className="text-gray-700 leading-relaxed text-lg">
60
+ This is a sample SwizzyWeb website built with the power and
61
+ flexibility of Tailwind CSS. Enjoy the clean design and responsive
62
+ layout that adapts beautifully to any screen size. We've focused on
63
+ creating a modern and user-friendly experience.
64
+ </p>
65
+ <div className="mt-6 flex space-x-4">
66
+ <button className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-3 px-6 rounded-lg shadow-md transition duration-300 transform hover:scale-105">
67
+ Learn More
68
+ </button>
69
+ <button className="bg-purple-500 hover:bg-purple-600 text-white font-semibold py-3 px-6 rounded-lg shadow-md transition duration-300 transform hover:scale-105">
70
+ Get Started
71
+ </button>
72
+ </div>
73
+ </section>
74
+
75
+ <section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
76
+ {/* Feature Card 1 */}
77
+ <div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition duration-300">
78
+ <h3 className="text-2xl font-semibold text-gray-800 mb-3">
79
+ Responsive Design
80
+ </h3>
81
+ <p className="text-gray-600">
82
+ Our website looks great on desktops, tablets, and mobile phones,
83
+ ensuring a seamless experience for all users.
84
+ </p>
85
+ </div>
86
+
87
+ {/* Feature Card 2 */}
88
+ <div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition duration-300">
89
+ <h3 className="2xl font-semibold text-gray-800 mb-3">Modern UI</h3>
90
+ <p className="text-gray-600">
91
+ Leveraging Tailwind CSS, we've crafted a clean, modern, and
92
+ intuitive user interface.
93
+ </p>
94
+ </div>
95
+
96
+ {/* Feature Card 3 */}
97
+ <div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition duration-300">
98
+ <h3 className="2xl font-semibold text-gray-800 mb-3">
99
+ Easy to Customize
100
+ </h3>
101
+ <p className="text-gray-600">
102
+ The component-based structure makes it incredibly easy to extend
103
+ and customize.
104
+ </p>
105
+ </div>
106
+ {/* Feature Card 4 */}
107
+ <div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200 hover:shadow-xl transition duration-300">
108
+ <h3 className="2xl font-semibold text-gray-800 mb-3">
109
+ Easy to add custom api's
110
+ </h3>
111
+ <p className="text-gray-600">
112
+ Adding api's is as simple as creating new controllers, checkout
113
+ our funny joke API by clicking the button below.
114
+ </p>
115
+
116
+ <FunnyJokeTeller />
117
+ </div>
118
+ </section>
119
+ </main>
120
+
121
+ {/* Footer Section */}
122
+ <footer className="bg-gray-800 text-white p-6 mt-8 rounded-t-xl shadow-inner">
123
+ <div className="container mx-auto text-center">
124
+ <p className="text-gray-400">
125
+ &copy; {new Date().getFullYear()} My Awesome SwizzyWeb Site. All
126
+ rights reserved.
127
+ </p>
128
+ <p className="text-gray-400 mt-2">
129
+ Made with{" "}
130
+ <span className="font-semibold text-white">@swizzyweb</span>
131
+ </p>
132
+ <div className="flex justify-center space-x-4 mt-3">
133
+ <a
134
+ href="#"
135
+ className="text-gray-400 hover:text-white transition duration-300"
136
+ >
137
+ Privacy Policy
138
+ </a>
139
+ <span className="text-gray-500">|</span>
140
+ <a
141
+ href="#"
142
+ className="text-gray-400 hover:text-white transition duration-300"
143
+ >
144
+ Terms of Service
145
+ </a>
146
+ </div>
147
+ </div>
148
+ </footer>
149
+ </div>
150
+ );
151
+ };
152
+
153
+ export default App;
@@ -0,0 +1,44 @@
1
+ import React, { useState, useEffect } from "react";
2
+
3
+ interface Joke {
4
+ id: number;
5
+ type: string;
6
+ setup: string;
7
+ punchline: string;
8
+ }
9
+
10
+ export function FunnyJokeTeller(props: any) {
11
+ let [joke, setJoke] = useState<Joke>();
12
+ let [setupText, setSetupText] = useState("");
13
+ let [punchlineText, setPunchlineText] = useState("");
14
+ let [categoryText, setCategoryText] = useState("");
15
+ async function getFunnyJoke() {
16
+ const res = await fetch("/api/funnyJoke", {
17
+ method: "get",
18
+ });
19
+ const body = await res.json();
20
+ const newFunnyJoke = body.joke;
21
+ setJoke(newFunnyJoke);
22
+ setSetupText(newFunnyJoke.setup);
23
+ setPunchlineText(newFunnyJoke.punchline);
24
+ setCategoryText(newFunnyJoke.type);
25
+ }
26
+
27
+ return (
28
+ <section id="funny-joke">
29
+ {" "}
30
+ <button
31
+ className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-3 px-6 rounded-lg shadow-md transition duration-300 transform hover:scale-105 mt-5 mb-5"
32
+ onClick={getFunnyJoke}
33
+ >
34
+ Get funny joke
35
+ </button>
36
+ {joke && (
37
+ <div>
38
+ <div>{setupText}</div>
39
+ <div>{punchlineText}</div>
40
+ </div>
41
+ )}
42
+ </section>
43
+ );
44
+ }
@@ -0,0 +1,4 @@
1
+ @import "tailwindcss";
2
+ @tailwind base;
3
+ @tailwind components;
4
+ @tailwind utilities;
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>SwizzyWebSite</title>
7
+ <link
8
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap"
9
+ rel="stylesheet"
10
+ />
11
+ </head>
12
+ <body>
13
+ <div id="root"></div>
14
+ </body>
15
+ </html>
@@ -0,0 +1,19 @@
1
+ // ./react/index.tsx
2
+ import React from 'react';
3
+ import ReactDOM from 'react-dom/client'; // Import from 'react-dom/client' for React 18+
4
+ import App from './App'; // Assuming your main component is in App.tsx
5
+
6
+ // Import your main CSS file (e.g., where Tailwind's output is imported)
7
+ import './index.css'; // Make sure this file exists and imports your Tailwind CSS
8
+
9
+ const rootElement = document.getElementById('root');
10
+ if (rootElement) {
11
+ const root = ReactDOM.createRoot(rootElement);
12
+ root.render(
13
+ <React.StrictMode>
14
+ <App />
15
+ </React.StrictMode>
16
+ );
17
+ } else {
18
+ console.error('Root element with ID "root" not found in the document.');
19
+ }
package/src/app.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { FunnyJokeClient } from "./client/index.js";
2
+ import { SampleFrontendWebService } from "./web-service.js";
3
+
4
+ export interface GetSampleFrontendWebserviceProps {
5
+ serviceArgs: {
6
+ funnyJokeBaseUrl?: string;
7
+ };
8
+ }
9
+ export async function getWebservice(
10
+ props: GetSampleFrontendWebserviceProps & any,
11
+ ) {
12
+ const state = {
13
+ funnyJokeClient: new FunnyJokeClient({
14
+ baseUrl: props.serviceArgs.funnyJokeBaseUrl,
15
+ }),
16
+ };
17
+ return new SampleFrontendWebService({
18
+ ...props,
19
+ ...props.serviceArgs,
20
+ state,
21
+ });
22
+ }
@@ -0,0 +1,29 @@
1
+ const DEFAULT_BASE_URL = "https://official-joke-api.appspot.com";
2
+
3
+ export interface Joke {
4
+ id: number;
5
+ punchline: string;
6
+ setup: string;
7
+ type: string;
8
+ }
9
+
10
+ export interface FunnyJokeClientProps {
11
+ baseUrl?: string;
12
+ }
13
+ export interface IFunnyJokeClient {
14
+ getFunnyJoke(request: any): Promise<Joke>;
15
+ }
16
+
17
+ export class FunnyJokeClient implements IFunnyJokeClient {
18
+ private baseUrl: string;
19
+ constructor(props: FunnyJokeClientProps) {
20
+ this.baseUrl = props.baseUrl ?? DEFAULT_BASE_URL;
21
+ }
22
+ async getFunnyJoke(request: any): Promise<Joke> {
23
+ const response = await fetch(`${this.baseUrl}/jokes/random`, {
24
+ method: "get",
25
+ });
26
+ const joke = await response.json();
27
+ return joke;
28
+ }
29
+ }
@@ -0,0 +1 @@
1
+ export * from "./funny-joke-client.js";
@@ -0,0 +1,49 @@
1
+ import {
2
+ IWebRouterProps,
3
+ RequestIdMiddleware,
4
+ RequestLoggerMiddleware,
5
+ StateConverter,
6
+ StateConverterProps,
7
+ SwizzyRequestMiddleware,
8
+ WebRouter,
9
+ } from "@swizzyweb/swizzy-web-service";
10
+ import { SampleFrontendWebServiceState } from "../../web-service.js";
11
+ import path from "path";
12
+ // @ts-ignore
13
+ import express from "@swizzyweb/express";
14
+ import { FunnyJokeController } from "./controllers/funny-joke-controller.js";
15
+ import { IFunnyJokeClient } from "../../client/index.js";
16
+ export interface ApiRouterState {
17
+ funnyJokeClient: IFunnyJokeClient;
18
+ }
19
+
20
+ export interface ApiRouterProps
21
+ extends IWebRouterProps<SampleFrontendWebServiceState, ApiRouterState> {}
22
+ export class ApiWebRouter extends WebRouter<
23
+ SampleFrontendWebServiceState,
24
+ ApiRouterState
25
+ > {
26
+ constructor(props: ApiRouterProps) {
27
+ super({
28
+ ...props,
29
+ name: "ApiWebRouter",
30
+ path: "api",
31
+ stateConverter: ApiRouterStateConverter,
32
+ webControllerClasses: [FunnyJokeController],
33
+ middleware: [
34
+ SwizzyRequestMiddleware,
35
+ RequestIdMiddleware,
36
+ RequestLoggerMiddleware,
37
+ ],
38
+ });
39
+ }
40
+ }
41
+
42
+ const ApiRouterStateConverter: StateConverter<
43
+ SampleFrontendWebServiceState,
44
+ ApiRouterState
45
+ > = async function (
46
+ props: StateConverterProps<SampleFrontendWebServiceState>,
47
+ ): Promise<ApiRouterState> {
48
+ return { ...props.state };
49
+ };
@@ -0,0 +1,59 @@
1
+ import {
2
+ DefaultStateExporter,
3
+ IWebControllerInitProps,
4
+ IWebControllerProps,
5
+ RequestMethod,
6
+ WebController,
7
+ WebControllerFunction,
8
+ } from "@swizzyweb/swizzy-web-service";
9
+ import { ApiRouterState, ApiWebRouter } from "../api-router.js";
10
+ // @ts-ignore
11
+ import { Request, Response, json } from "@swizzyweb/express";
12
+ import path from "path";
13
+ import { IFunnyJokeClient } from "../../../client/index.js";
14
+
15
+ export interface FunnyJokeControllerState {
16
+ funnyJokeClient: IFunnyJokeClient;
17
+ }
18
+
19
+ export interface FunnyJokeControllerProps
20
+ extends IWebControllerProps<ApiRouterState, FunnyJokeControllerState> {}
21
+
22
+ export class FunnyJokeController extends WebController<
23
+ ApiRouterState,
24
+ FunnyJokeControllerState
25
+ > {
26
+ constructor(props: FunnyJokeControllerProps) {
27
+ super({
28
+ ...props,
29
+ name: "FunnyJokeController",
30
+ action: "funnyJoke",
31
+ method: RequestMethod.get,
32
+ stateConverter: DefaultStateExporter,
33
+ middleware: [],
34
+ });
35
+ }
36
+
37
+ protected async getInitializedController(
38
+ props: IWebControllerInitProps<ApiRouterState> & {
39
+ state: FunnyJokeControllerState | undefined;
40
+ },
41
+ ): Promise<WebControllerFunction> {
42
+ const logger = this.logger;
43
+ const getState = this.getState.bind(this);
44
+ return async function (req: Request, res: Response) {
45
+ logger.info("We got a jokster lookin for jokes!");
46
+ try {
47
+ const { funnyJokeClient } = getState()!;
48
+ const joke = await funnyJokeClient.getFunnyJoke({});
49
+ res.json({
50
+ message: "Here's your funny joke",
51
+ joke,
52
+ });
53
+ } catch (e: any) {
54
+ res.status(500);
55
+ res.json({ message: "Internal error occurred" });
56
+ }
57
+ };
58
+ }
59
+ }
@@ -0,0 +1,52 @@
1
+ import {
2
+ IWebRouterProps,
3
+ RequestIdMiddleware,
4
+ RequestLoggerMiddleware,
5
+ StateConverter,
6
+ StateConverterProps,
7
+ SwizzyRequestMiddleware,
8
+ WebRouter,
9
+ } from "@swizzyweb/swizzy-web-service";
10
+ import { SampleFrontendWebServiceState } from "../../web-service.js";
11
+ import path from "path";
12
+ // @ts-ignore
13
+ import express from "@swizzyweb/express";
14
+ import { fileURLToPath } from "node:url";
15
+
16
+ // This gives you the directory where *this* file is located
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+
20
+ export interface PageRouterState {}
21
+
22
+ export interface PageRouterProps
23
+ extends IWebRouterProps<SampleFrontendWebServiceState, PageRouterState> {}
24
+ export class PageWebRouter extends WebRouter<
25
+ SampleFrontendWebServiceState,
26
+ PageRouterState
27
+ > {
28
+ constructor(props: PageRouterProps) {
29
+ super({
30
+ ...props,
31
+ name: "PageWebRouter",
32
+ path: "",
33
+ stateConverter: PageRouterStateConverter,
34
+ webControllerClasses: [],
35
+ middleware: [
36
+ SwizzyRequestMiddleware,
37
+ RequestIdMiddleware,
38
+ RequestLoggerMiddleware,
39
+ () => express.static(path.join(__dirname, "../../../bundle")),
40
+ ],
41
+ });
42
+ }
43
+ }
44
+
45
+ const PageRouterStateConverter: StateConverter<
46
+ SampleFrontendWebServiceState,
47
+ PageRouterState
48
+ > = async function (
49
+ props: StateConverterProps<SampleFrontendWebServiceState>,
50
+ ): Promise<PageRouterState> {
51
+ return { ...props.state };
52
+ };
@@ -0,0 +1,27 @@
1
+ import { IWebServiceProps, WebService } from "@swizzyweb/swizzy-web-service";
2
+ import { PageWebRouter } from "./routers/PageRouter/page-router.js";
3
+ import { ApiWebRouter } from "./routers/ApiRouter/api-router.js";
4
+ import { FunnyJokeClient, IFunnyJokeClient } from "./client/index.js";
5
+
6
+ export interface SampleFrontendWebServiceState {
7
+ funnyJokeClient: IFunnyJokeClient;
8
+ }
9
+
10
+ export interface SampleFrontendWebServiceProps
11
+ extends IWebServiceProps<SampleFrontendWebServiceState> {
12
+ port: number;
13
+ path?: string;
14
+ }
15
+
16
+ export class SampleFrontendWebService extends WebService<SampleFrontendWebServiceState> {
17
+ constructor(props: SampleFrontendWebServiceProps) {
18
+ super({
19
+ ...props,
20
+ name: "SampleFrontendWebService",
21
+ path: props.path ?? "",
22
+ packageName: "@swizzyweb/swizzy-frontend-template-web-service",
23
+ routerClasses: [PageWebRouter, ApiWebRouter],
24
+ middleware: [],
25
+ });
26
+ }
27
+ }
@@ -0,0 +1,25 @@
1
+ // tailwind.config.js
2
+ /** @type {import('tailwindcss').Config} */
3
+ module.exports = {
4
+ // This 'content' array is CRUCIAL. It tells Tailwind where to look for
5
+ // all the utility classes you are using in your project.
6
+ content: [
7
+ // This glob pattern is designed to scan all JavaScript, JSX, TypeScript,
8
+ // and TSX files within your 'react' directory and any of its subdirectories.
9
+ // This is where your App.tsx and other React components reside.
10
+ "./react/**/*.{js,jsx,ts,tsx}",
11
+ // If your main index.html file (the template used by HtmlWebpackPlugin)
12
+ // also contains Tailwind classes (e.g., on the <body> tag), include it here.
13
+ "./react/index.html",
14
+ ],
15
+ theme: {
16
+ extend: {
17
+ // Ensure your custom font 'Inter' is defined here if you're using it.
18
+ fontFamily: {
19
+ inter: ["Inter", "sans-serif"],
20
+ },
21
+ // Any other theme extensions (custom colors, spacing, etc.) would go here.
22
+ },
23
+ },
24
+ plugins: [], // Add any Tailwind CSS plugins here if you use them (e.g., @tailwindcss/forms)
25
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2024", // Or a more recent ECMAScript version (e.g., "es2020", "esnext") based on your Node.js environment
4
+ "module": "nodenext", // Or "esnext" if your service uses ES modules (and you have "type": "module" in package.json)
5
+ "outDir": "./dist", // Output directory for compiled JavaScript files from your service code
6
+ //"rootDir": "./", // Specifies the root directory of input files. Relative to this tsconfig.json.
7
+ "strict": true, // Enable all strict type-checking options for robust code
8
+ "esModuleInterop": true, // Allows default imports from modules with no default export
9
+ "skipLibCheck": true, // Skip type checking of all declaration files (*.d.ts)
10
+ "forceConsistentCasingInFileNames": true, // Ensure consistent casing for file paths
11
+ "resolveJsonModule": true, // Allows importing .json files in your service code
12
+ "sourceMap": true, // Generates source map files for debugging
13
+ "isolatedModules": true
14
+ },
15
+ "include": [
16
+ // Include all .ts files in the current directory (./src/) and its subdirectories
17
+ "src" // Include declaration files if any
18
+ //"react/home.tsx",
19
+ // "react/search.tsx"
20
+ ],
21
+ "exclude": [
22
+ "node_modules", // Exclude the node_modules directory
23
+ "./react", // Explicitly exclude the React application's directory
24
+ "./bundle" // Exclude the webpack output directory
25
+ ]
26
+ }
@@ -0,0 +1,111 @@
1
+ const path = require("path");
2
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
3
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin"); // Import the CSS extraction plugin
4
+
5
+ module.exports = {
6
+ // Set the mode to 'development' for easier debugging during development.
7
+ // Change to 'production' for optimized, minified output suitable for deployment.
8
+ mode: "development",
9
+
10
+ // The main entry point for your React application.
11
+ // Webpack starts building its dependency graph from this file.
12
+ // Ensure that 'index.tsx' exists inside your './react/' directory.
13
+ entry: "./react/index.tsx",
14
+
15
+ // Configuration for where Webpack should output the bundled files.
16
+ output: {
17
+ // The absolute path to the output directory.
18
+ // path.resolve(__dirname, 'bundle') creates a 'bundle' folder
19
+ // at the root of your project (where this webpack.config.js resides).
20
+ path: path.resolve(__dirname, "bundle"),
21
+ // The name of the main JavaScript bundle file.
22
+ filename: "js/bundle.js",
23
+ // This option cleans the output directory ('./bundle/') before each new build.
24
+ // This prevents old, unused files from accumulating.
25
+ clean: true,
26
+ },
27
+
28
+ // Rules define how different types of modules (files) are processed by Webpack.
29
+ module: {
30
+ rules: [
31
+ {
32
+ // This rule applies to files with .js, .jsx, .ts, or .tsx extensions.
33
+ test: /\.(js|jsx|ts|tsx)$/,
34
+ // Exclude files from the 'node_modules' directory to speed up compilation,
35
+ // as these are typically pre-compiled.
36
+ exclude: /node_modules/,
37
+ use: {
38
+ // 'babel-loader' is used to transpile (convert) modern JavaScript,
39
+ // JSX, and TypeScript code into a format compatible with older browsers.
40
+ loader: "babel-loader",
41
+ options: {
42
+ // Babel presets define sets of transformations.
43
+ presets: [
44
+ "@babel/preset-env", // Transpiles modern JavaScript (ES6+) to ES5.
45
+ "@babel/preset-react", // Transpiles JSX syntax into React.createElement calls.
46
+ "@babel/preset-typescript", // Transpiles TypeScript into JavaScript.
47
+ ],
48
+ },
49
+ },
50
+ },
51
+ {
52
+ // This rule applies to CSS files.
53
+ test: /\.css$/,
54
+ // The 'use' array specifies the loaders to apply, in reverse order.
55
+ use: [
56
+ // 1. MiniCssExtractPlugin.loader: Extracts CSS into separate files.
57
+ // This is crucial for getting a physical 'styles.css' file in your bundle.
58
+ MiniCssExtractPlugin.loader,
59
+ // 2. 'css-loader': Interprets `@import` and `url()` like `import`/`require()`
60
+ // and resolves them. It turns CSS into CommonJS modules.
61
+ "css-loader",
62
+ // 3. 'postcss-loader': Processes CSS with PostCSS plugins.
63
+ // This is where Tailwind CSS and Autoprefixer are applied,
64
+ // using your 'postcss.config.js' configuration.
65
+ "postcss-loader",
66
+ ],
67
+ },
68
+ ],
69
+ },
70
+
71
+ // Configure how modules are resolved.
72
+ resolve: {
73
+ // This allows you to import modules without specifying their file extensions.
74
+ // For example, you can write `import App from './App'` instead of `import App from './App.tsx'`.
75
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
76
+ },
77
+
78
+ // Plugins extend Webpack's capabilities beyond simple module transformations.
79
+ plugins: [
80
+ // HtmlWebpackPlugin: Simplifies the creation of HTML files to serve your webpack bundles.
81
+ // It automatically injects your bundled JavaScript and CSS into the HTML.
82
+ new HtmlWebpackPlugin({
83
+ // The template HTML file to use. This file should be in your './react/' directory.
84
+ template: "./react/index.html",
85
+ // The name of the output HTML file that will be placed in the 'bundle' directory.
86
+ filename: "index.html",
87
+ }),
88
+ // MiniCssExtractPlugin: Extracts CSS into separate files.
89
+ // This plugin works in conjunction with its loader (defined in module.rules)
90
+ // to create a dedicated CSS file (e.g., 'styles.css').
91
+ new MiniCssExtractPlugin({
92
+ // The filename for the extracted CSS file.
93
+ filename: "css/styles.css", // You can customize this name if you wish
94
+ }),
95
+ ],
96
+
97
+ // Configuration for the Webpack Development Server (optional, for local development).
98
+ devServer: {
99
+ // Specifies the directory from which to serve static files.
100
+ // In this case, it serves files from your 'bundle' directory.
101
+ static: {
102
+ directory: path.join(__dirname, "bundle"),
103
+ },
104
+ // Enables GZIP compression for everything served by the development server.
105
+ compress: true,
106
+ // The port on which the development server will run.
107
+ port: 3000,
108
+ // Automatically opens your default web browser to the application when the server starts.
109
+ open: true,
110
+ },
111
+ };