jonatanklosko commited on
Commit
b6f0124
·
0 Parent(s):

Initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .formatter.exs +5 -0
  2. .gitignore +36 -0
  3. README.md +1 -0
  4. assets/css/app.css +5 -0
  5. assets/js/app.js +26 -0
  6. assets/tailwind.config.js +75 -0
  7. assets/vendor/topbar.js +165 -0
  8. config/config.exs +45 -0
  9. config/dev.exs +38 -0
  10. config/prod.exs +18 -0
  11. config/runtime.exs +25 -0
  12. config/test.exs +14 -0
  13. lib/chai.ex +2 -0
  14. lib/chai/application.ex +28 -0
  15. lib/chai_web.ex +111 -0
  16. lib/chai_web/components/core_components.ex +137 -0
  17. lib/chai_web/components/layouts.ex +5 -0
  18. lib/chai_web/components/layouts/app.html.heex +31 -0
  19. lib/chai_web/components/layouts/root.html.heex +18 -0
  20. lib/chai_web/controllers/error_html.ex +7 -0
  21. lib/chai_web/endpoint.ex +44 -0
  22. lib/chai_web/router.ex +31 -0
  23. lib/chai_web/telemetry.ex +69 -0
  24. mix.exs +51 -0
  25. mix.lock +28 -0
  26. priv/hero_icons/LICENSE.md +21 -0
  27. priv/hero_icons/UPGRADE.md +7 -0
  28. priv/hero_icons/optimized/20/solid/academic-cap.svg +3 -0
  29. priv/hero_icons/optimized/20/solid/adjustments-horizontal.svg +3 -0
  30. priv/hero_icons/optimized/20/solid/adjustments-vertical.svg +3 -0
  31. priv/hero_icons/optimized/20/solid/archive-box-arrow-down.svg +3 -0
  32. priv/hero_icons/optimized/20/solid/archive-box-x-mark.svg +4 -0
  33. priv/hero_icons/optimized/20/solid/archive-box.svg +4 -0
  34. priv/hero_icons/optimized/20/solid/arrow-down-circle.svg +3 -0
  35. priv/hero_icons/optimized/20/solid/arrow-down-left.svg +3 -0
  36. priv/hero_icons/optimized/20/solid/arrow-down-on-square-stack.svg +3 -0
  37. priv/hero_icons/optimized/20/solid/arrow-down-on-square.svg +3 -0
  38. priv/hero_icons/optimized/20/solid/arrow-down-right.svg +3 -0
  39. priv/hero_icons/optimized/20/solid/arrow-down-tray.svg +4 -0
  40. priv/hero_icons/optimized/20/solid/arrow-down.svg +3 -0
  41. priv/hero_icons/optimized/20/solid/arrow-left-circle.svg +10 -0
  42. priv/hero_icons/optimized/20/solid/arrow-left-on-rectangle.svg +4 -0
  43. priv/hero_icons/optimized/20/solid/arrow-left.svg +3 -0
  44. priv/hero_icons/optimized/20/solid/arrow-long-down.svg +3 -0
  45. priv/hero_icons/optimized/20/solid/arrow-long-left.svg +3 -0
  46. priv/hero_icons/optimized/20/solid/arrow-long-right.svg +3 -0
  47. priv/hero_icons/optimized/20/solid/arrow-long-up.svg +3 -0
  48. priv/hero_icons/optimized/20/solid/arrow-path-rounded-square.svg +3 -0
  49. priv/hero_icons/optimized/20/solid/arrow-path.svg +3 -0
  50. priv/hero_icons/optimized/20/solid/arrow-right-circle.svg +3 -0
.formatter.exs ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [
2
+ import_deps: [:phoenix],
3
+ plugins: [Phoenix.LiveView.HTMLFormatter],
4
+ inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"]
5
+ ]
.gitignore ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # The directory Mix will write compiled artifacts to.
2
+ /_build/
3
+
4
+ # If you run "mix test --cover", coverage assets end up here.
5
+ /cover/
6
+
7
+ # The directory Mix downloads your dependencies sources to.
8
+ /deps/
9
+
10
+ # Where 3rd-party dependencies like ExDoc output generated docs.
11
+ /doc/
12
+
13
+ # Ignore .fetch files in case you like to edit your project deps locally.
14
+ /.fetch
15
+
16
+ # If the VM crashes, it generates a dump, let's ignore it too.
17
+ erl_crash.dump
18
+
19
+ # Also ignore archive artifacts (built via "mix archive.build").
20
+ *.ez
21
+
22
+ # Temporary files, for example, from tests.
23
+ /tmp/
24
+
25
+ # Ignore package tarball (built via "mix hex.build").
26
+ chai-*.tar
27
+
28
+ # Ignore assets that are produced by build tools.
29
+ /priv/static/assets/
30
+
31
+ # Ignore digested assets cache.
32
+ /priv/static/cache_manifest.json
33
+
34
+ # In case you use Node.js/npm, you want to ignore these.
35
+ npm-debug.log
36
+ /assets/node_modules/
README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Chai 🍵
assets/css/app.css ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ @import "tailwindcss/base";
2
+ @import "tailwindcss/components";
3
+ @import "tailwindcss/utilities";
4
+
5
+ /* This file is for your main application CSS */
assets/js/app.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import "phoenix_html";
2
+ import { Socket } from "phoenix";
3
+ import { LiveSocket } from "phoenix_live_view";
4
+ import topbar from "../vendor/topbar";
5
+
6
+ let csrfToken = document
7
+ .querySelector("meta[name='csrf-token']")
8
+ .getAttribute("content");
9
+
10
+ let liveSocket = new LiveSocket("/live", Socket, {
11
+ params: { _csrf_token: csrfToken },
12
+ });
13
+
14
+ // Show progress bar on live navigation and form submits
15
+ topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" });
16
+ window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300));
17
+ window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide());
18
+
19
+ // Connect if there are any LiveViews on the page
20
+ liveSocket.connect();
21
+
22
+ // Expose liveSocket on window for web console debug logs and latency simulation:
23
+ // >> liveSocket.enableDebug()
24
+ // >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
25
+ // >> liveSocket.disableLatencySim()
26
+ window.liveSocket = liveSocket;
assets/tailwind.config.js ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const plugin = require("tailwindcss/plugin");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+
5
+ module.exports = {
6
+ content: ["./js/**/*.js", "../lib/*_web.ex", "../lib/*_web/**/*.*ex"],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ brand: "#FD4F00",
11
+ },
12
+ },
13
+ },
14
+ plugins: [
15
+ require("@tailwindcss/forms"),
16
+ plugin(({ addVariant }) =>
17
+ addVariant("phx-no-feedback", [".phx-no-feedback&", ".phx-no-feedback &"])
18
+ ),
19
+ plugin(({ addVariant }) =>
20
+ addVariant("phx-click-loading", [
21
+ ".phx-click-loading&",
22
+ ".phx-click-loading &",
23
+ ])
24
+ ),
25
+ plugin(({ addVariant }) =>
26
+ addVariant("phx-submit-loading", [
27
+ ".phx-submit-loading&",
28
+ ".phx-submit-loading &",
29
+ ])
30
+ ),
31
+ plugin(({ addVariant }) =>
32
+ addVariant("phx-change-loading", [
33
+ ".phx-change-loading&",
34
+ ".phx-change-loading &",
35
+ ])
36
+ ),
37
+
38
+ plugin(function ({ matchComponents, theme }) {
39
+ let iconsDir = path.join(__dirname, "../priv/hero_icons/optimized");
40
+ let values = {};
41
+ let icons = [
42
+ ["", "/24/outline"],
43
+ ["-solid", "/24/solid"],
44
+ ["-mini", "/20/solid"],
45
+ ];
46
+ icons.forEach(([suffix, dir]) => {
47
+ fs.readdirSync(path.join(iconsDir, dir)).map((file) => {
48
+ let name = path.basename(file, ".svg") + suffix;
49
+ values[name] = { name, fullPath: path.join(iconsDir, dir, file) };
50
+ });
51
+ });
52
+ matchComponents(
53
+ {
54
+ hero: ({ name, fullPath }) => {
55
+ let content = fs
56
+ .readFileSync(fullPath)
57
+ .toString()
58
+ .replace(/\r?\n|\r/g, "");
59
+ return {
60
+ [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,
61
+ "-webkit-mask": `var(--hero-${name})`,
62
+ mask: `var(--hero-${name})`,
63
+ "background-color": "currentColor",
64
+ "vertical-align": "middle",
65
+ display: "inline-block",
66
+ width: theme("spacing.5"),
67
+ height: theme("spacing.5"),
68
+ };
69
+ },
70
+ },
71
+ { values }
72
+ );
73
+ }),
74
+ ],
75
+ };
assets/vendor/topbar.js ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license MIT
3
+ * topbar 2.0.0, 2023-02-04
4
+ * https://buunguyen.github.io/topbar
5
+ * Copyright (c) 2021 Buu Nguyen
6
+ */
7
+ (function (window, document) {
8
+ "use strict";
9
+
10
+ // https://gist.github.com/paulirish/1579671
11
+ (function () {
12
+ var lastTime = 0;
13
+ var vendors = ["ms", "moz", "webkit", "o"];
14
+ for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
15
+ window.requestAnimationFrame =
16
+ window[vendors[x] + "RequestAnimationFrame"];
17
+ window.cancelAnimationFrame =
18
+ window[vendors[x] + "CancelAnimationFrame"] ||
19
+ window[vendors[x] + "CancelRequestAnimationFrame"];
20
+ }
21
+ if (!window.requestAnimationFrame)
22
+ window.requestAnimationFrame = function (callback, element) {
23
+ var currTime = new Date().getTime();
24
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
25
+ var id = window.setTimeout(function () {
26
+ callback(currTime + timeToCall);
27
+ }, timeToCall);
28
+ lastTime = currTime + timeToCall;
29
+ return id;
30
+ };
31
+ if (!window.cancelAnimationFrame)
32
+ window.cancelAnimationFrame = function (id) {
33
+ clearTimeout(id);
34
+ };
35
+ })();
36
+
37
+ var canvas,
38
+ currentProgress,
39
+ showing,
40
+ progressTimerId = null,
41
+ fadeTimerId = null,
42
+ delayTimerId = null,
43
+ addEvent = function (elem, type, handler) {
44
+ if (elem.addEventListener) elem.addEventListener(type, handler, false);
45
+ else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
46
+ else elem["on" + type] = handler;
47
+ },
48
+ options = {
49
+ autoRun: true,
50
+ barThickness: 3,
51
+ barColors: {
52
+ 0: "rgba(26, 188, 156, .9)",
53
+ ".25": "rgba(52, 152, 219, .9)",
54
+ ".50": "rgba(241, 196, 15, .9)",
55
+ ".75": "rgba(230, 126, 34, .9)",
56
+ "1.0": "rgba(211, 84, 0, .9)",
57
+ },
58
+ shadowBlur: 10,
59
+ shadowColor: "rgba(0, 0, 0, .6)",
60
+ className: null,
61
+ },
62
+ repaint = function () {
63
+ canvas.width = window.innerWidth;
64
+ canvas.height = options.barThickness * 5; // need space for shadow
65
+
66
+ var ctx = canvas.getContext("2d");
67
+ ctx.shadowBlur = options.shadowBlur;
68
+ ctx.shadowColor = options.shadowColor;
69
+
70
+ var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);
71
+ for (var stop in options.barColors)
72
+ lineGradient.addColorStop(stop, options.barColors[stop]);
73
+ ctx.lineWidth = options.barThickness;
74
+ ctx.beginPath();
75
+ ctx.moveTo(0, options.barThickness / 2);
76
+ ctx.lineTo(
77
+ Math.ceil(currentProgress * canvas.width),
78
+ options.barThickness / 2
79
+ );
80
+ ctx.strokeStyle = lineGradient;
81
+ ctx.stroke();
82
+ },
83
+ createCanvas = function () {
84
+ canvas = document.createElement("canvas");
85
+ var style = canvas.style;
86
+ style.position = "fixed";
87
+ style.top = style.left = style.right = style.margin = style.padding = 0;
88
+ style.zIndex = 100001;
89
+ style.display = "none";
90
+ if (options.className) canvas.classList.add(options.className);
91
+ document.body.appendChild(canvas);
92
+ addEvent(window, "resize", repaint);
93
+ },
94
+ topbar = {
95
+ config: function (opts) {
96
+ for (var key in opts)
97
+ if (options.hasOwnProperty(key)) options[key] = opts[key];
98
+ },
99
+ show: function (delay) {
100
+ if (showing) return;
101
+ if (delay) {
102
+ if (delayTimerId) return;
103
+ delayTimerId = setTimeout(() => topbar.show(), delay);
104
+ } else {
105
+ showing = true;
106
+ if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);
107
+ if (!canvas) createCanvas();
108
+ canvas.style.opacity = 1;
109
+ canvas.style.display = "block";
110
+ topbar.progress(0);
111
+ if (options.autoRun) {
112
+ (function loop() {
113
+ progressTimerId = window.requestAnimationFrame(loop);
114
+ topbar.progress(
115
+ "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)
116
+ );
117
+ })();
118
+ }
119
+ }
120
+ },
121
+ progress: function (to) {
122
+ if (typeof to === "undefined") return currentProgress;
123
+ if (typeof to === "string") {
124
+ to =
125
+ (to.indexOf("+") >= 0 || to.indexOf("-") >= 0
126
+ ? currentProgress
127
+ : 0) + parseFloat(to);
128
+ }
129
+ currentProgress = to > 1 ? 1 : to;
130
+ repaint();
131
+ return currentProgress;
132
+ },
133
+ hide: function () {
134
+ clearTimeout(delayTimerId);
135
+ delayTimerId = null;
136
+ if (!showing) return;
137
+ showing = false;
138
+ if (progressTimerId != null) {
139
+ window.cancelAnimationFrame(progressTimerId);
140
+ progressTimerId = null;
141
+ }
142
+ (function loop() {
143
+ if (topbar.progress("+.1") >= 1) {
144
+ canvas.style.opacity -= 0.05;
145
+ if (canvas.style.opacity <= 0.05) {
146
+ canvas.style.display = "none";
147
+ fadeTimerId = null;
148
+ return;
149
+ }
150
+ }
151
+ fadeTimerId = window.requestAnimationFrame(loop);
152
+ })();
153
+ },
154
+ };
155
+
156
+ if (typeof module === "object" && typeof module.exports === "object") {
157
+ module.exports = topbar;
158
+ } else if (typeof define === "function" && define.amd) {
159
+ define(function () {
160
+ return topbar;
161
+ });
162
+ } else {
163
+ this.topbar = topbar;
164
+ }
165
+ }.call(this, window, document));
config/config.exs ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Config
2
+
3
+ # Configures the endpoint
4
+ config :chai, ChaiWeb.Endpoint,
5
+ url: [host: "localhost"],
6
+ render_errors: [
7
+ formats: [html: ChaiWeb.ErrorHTML],
8
+ layout: false
9
+ ],
10
+ pubsub_server: Chai.PubSub,
11
+ live_view: [signing_salt: "HhjjaWHp"]
12
+
13
+ # Configure esbuild (the version is required)
14
+ config :esbuild,
15
+ version: "0.14.41",
16
+ default: [
17
+ args:
18
+ ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
19
+ cd: Path.expand("../assets", __DIR__),
20
+ env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
21
+ ]
22
+
23
+ # Configure tailwind (the version is required)
24
+ config :tailwind,
25
+ version: "3.2.4",
26
+ default: [
27
+ args: ~w(
28
+ --config=tailwind.config.js
29
+ --input=css/app.css
30
+ --output=../priv/static/assets/app.css
31
+ ),
32
+ cd: Path.expand("../assets", __DIR__)
33
+ ]
34
+
35
+ # Configures Elixir's Logger
36
+ config :logger, :console,
37
+ format: "$time $metadata[$level] $message\n",
38
+ metadata: [:request_id]
39
+
40
+ # Use Jason for JSON parsing in Phoenix
41
+ config :phoenix, :json_library, Jason
42
+
43
+ # Import environment specific config. This must remain at the bottom
44
+ # of this file so it overrides the configuration defined above.
45
+ import_config "#{config_env()}.exs"
config/dev.exs ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Config
2
+
3
+ # For development, we disable any cache and enable
4
+ # debugging and code reloading.
5
+ config :chai, ChaiWeb.Endpoint,
6
+ # Binding to loopback ipv4 address prevents access from other machines.
7
+ # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.
8
+ http: [ip: {127, 0, 0, 1}, port: 4040],
9
+ check_origin: false,
10
+ code_reloader: true,
11
+ debug_errors: true,
12
+ secret_key_base: "POQcb0mcoX3qbIKsRel3LlM4dvpDOssogUkyUN9Tyt4TPLgtQWKzDnjxxedUfcxC",
13
+ watchers: [
14
+ esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
15
+ tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
16
+ ]
17
+
18
+ # Watch static and templates for browser reloading.
19
+ config :chai, ChaiWeb.Endpoint,
20
+ live_reload: [
21
+ patterns: [
22
+ ~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
23
+ ~r"lib/chai_web/(controllers|live|components)/.*(ex|heex)$"
24
+ ]
25
+ ]
26
+
27
+ # Enable dev routes for dashboard and mailbox
28
+ config :chai, dev_routes: true
29
+
30
+ # Do not include metadata nor timestamps in development logs
31
+ config :logger, :console, format: "[$level] $message\n"
32
+
33
+ # Set a higher stacktrace during development. Avoid configuring such
34
+ # in production as building large stacktraces may be expensive.
35
+ config :phoenix, :stacktrace_depth, 20
36
+
37
+ # Initialize plugs at runtime for faster development compilation
38
+ config :phoenix, :plug_init_mode, :runtime
config/prod.exs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Config
2
+
3
+ # For production, don't forget to configure the url host
4
+ # to something meaningful, Phoenix uses this information
5
+ # when generating URLs.
6
+
7
+ # Note we also include the path to a cache manifest
8
+ # containing the digested version of static files. This
9
+ # manifest is generated by the `mix phx.digest` task,
10
+ # which you should run after static files are built and
11
+ # before starting your production server.
12
+ config :chai, ChaiWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
13
+
14
+ # Do not print debug messages in production
15
+ config :logger, level: :info
16
+
17
+ # Runtime production configuration, including reading
18
+ # of environment variables, is done on config/runtime.exs.
config/runtime.exs ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Config
2
+
3
+ if System.get_env("PHX_SERVER") do
4
+ config :chai, ChaiWeb.Endpoint, server: true
5
+ end
6
+
7
+ if config_env() == :prod do
8
+ secret_key_base =
9
+ System.get_env("SECRET_KEY_BASE") ||
10
+ raise """
11
+ environment variable SECRET_KEY_BASE is missing.
12
+ You can generate one by calling: mix phx.gen.secret
13
+ """
14
+
15
+ host = System.get_env("PHX_HOST") || "example.com"
16
+ port = String.to_integer(System.get_env("PORT") || "4000")
17
+
18
+ config :chai, ChaiWeb.Endpoint,
19
+ url: [host: host, port: 443, scheme: "https"],
20
+ http: [
21
+ ip: {0, 0, 0, 0, 0, 0, 0, 0},
22
+ port: port
23
+ ],
24
+ secret_key_base: secret_key_base
25
+ end
config/test.exs ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Config
2
+
3
+ # We don't run a server during test. If one is required,
4
+ # you can enable the server option below.
5
+ config :chai, ChaiWeb.Endpoint,
6
+ http: [ip: {127, 0, 0, 1}, port: 4002],
7
+ secret_key_base: "kKQJBTFBlnw1PnHtYTDG3G9sfIuXM3DxJLCyhmxnfyJ9wdAwOdRfthMTQR+Xa4ln",
8
+ server: false
9
+
10
+ # Print only warnings and errors during test
11
+ config :logger, level: :warning
12
+
13
+ # Initialize plugs at runtime for faster test compilation
14
+ config :phoenix, :plug_init_mode, :runtime
lib/chai.ex ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ defmodule Chai do
2
+ end
lib/chai/application.ex ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defmodule Chai.Application do
2
+ @moduledoc false
3
+
4
+ use Application
5
+
6
+ @impl true
7
+ def start(_type, _args) do
8
+ children = [
9
+ # Start the Telemetry supervisor
10
+ ChaiWeb.Telemetry,
11
+ # Start the PubSub system
12
+ {Phoenix.PubSub, name: Chai.PubSub},
13
+ # Start the Endpoint (http/https)
14
+ ChaiWeb.Endpoint
15
+ ]
16
+
17
+ opts = [strategy: :one_for_one, name: Chai.Supervisor]
18
+ Supervisor.start_link(children, opts)
19
+ end
20
+
21
+ # Tell Phoenix to update the endpoint configuration
22
+ # whenever the application is updated.
23
+ @impl true
24
+ def config_change(changed, _new, removed) do
25
+ ChaiWeb.Endpoint.config_change(changed, removed)
26
+ :ok
27
+ end
28
+ end
lib/chai_web.ex ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defmodule ChaiWeb do
2
+ @moduledoc """
3
+ The entrypoint for defining your web interface, such
4
+ as controllers, components, channels, and so on.
5
+
6
+ This can be used in your application as:
7
+
8
+ use ChaiWeb, :controller
9
+ use ChaiWeb, :html
10
+
11
+ The definitions below will be executed for every controller,
12
+ component, etc, so keep them short and clean, focused
13
+ on imports, uses and aliases.
14
+
15
+ Do NOT define functions inside the quoted expressions
16
+ below. Instead, define additional modules and import
17
+ those modules here.
18
+ """
19
+
20
+ def static_paths, do: ~w(assets fonts images favicon.svg logo.svg robots.txt)
21
+
22
+ def router do
23
+ quote do
24
+ use Phoenix.Router, helpers: false
25
+
26
+ # Import common connection and controller functions to use in pipelines
27
+ import Plug.Conn
28
+ import Phoenix.Controller
29
+ import Phoenix.LiveView.Router
30
+ end
31
+ end
32
+
33
+ def channel do
34
+ quote do
35
+ use Phoenix.Channel
36
+ end
37
+ end
38
+
39
+ def controller do
40
+ quote do
41
+ use Phoenix.Controller,
42
+ formats: [:html, :json],
43
+ layouts: [html: ChaiWeb.Layouts]
44
+
45
+ import Plug.Conn
46
+
47
+ unquote(verified_routes())
48
+ end
49
+ end
50
+
51
+ def live_view do
52
+ quote do
53
+ use Phoenix.LiveView,
54
+ layout: {ChaiWeb.Layouts, :app}
55
+
56
+ unquote(html_helpers())
57
+ end
58
+ end
59
+
60
+ def live_component do
61
+ quote do
62
+ use Phoenix.LiveComponent
63
+
64
+ unquote(html_helpers())
65
+ end
66
+ end
67
+
68
+ def html do
69
+ quote do
70
+ use Phoenix.Component
71
+
72
+ # Import convenience functions from controllers
73
+ import Phoenix.Controller,
74
+ only: [get_csrf_token: 0, view_module: 1, view_template: 1]
75
+
76
+ # Include general helpers for rendering HTML
77
+ unquote(html_helpers())
78
+ end
79
+ end
80
+
81
+ defp html_helpers do
82
+ quote do
83
+ # HTML escaping functionality
84
+ import Phoenix.HTML
85
+ # Core UI components and translation
86
+ import ChaiWeb.CoreComponents
87
+
88
+ # Shortcut for generating JS commands
89
+ alias Phoenix.LiveView.JS
90
+
91
+ # Routes generation with the ~p sigil
92
+ unquote(verified_routes())
93
+ end
94
+ end
95
+
96
+ def verified_routes do
97
+ quote do
98
+ use Phoenix.VerifiedRoutes,
99
+ endpoint: ChaiWeb.Endpoint,
100
+ router: ChaiWeb.Router,
101
+ statics: ChaiWeb.static_paths()
102
+ end
103
+ end
104
+
105
+ @doc """
106
+ When used, dispatch to the appropriate controller/view/etc.
107
+ """
108
+ defmacro __using__(which) when is_atom(which) do
109
+ apply(__MODULE__, which, [])
110
+ end
111
+ end
lib/chai_web/components/core_components.ex ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defmodule ChaiWeb.CoreComponents do
2
+ @moduledoc """
3
+ Provides core UI components.
4
+ """
5
+
6
+ use Phoenix.Component
7
+
8
+ alias Phoenix.LiveView.JS
9
+
10
+ @doc """
11
+ Renders flash notices.
12
+
13
+ ## Examples
14
+
15
+ <.flash kind={:info} flash={@flash} />
16
+ <.flash kind={:info} phx-mounted={show("#flash")}>Welcome Back!</.flash>
17
+
18
+ """
19
+ attr :id, :string, default: "flash", doc: "the optional id of flash container"
20
+ attr :flash, :map, default: %{}, doc: "the map of flash messages to display"
21
+ attr :title, :string, default: nil
22
+ attr :kind, :atom, values: [:info, :error], doc: "used for styling and flash lookup"
23
+ attr :autoshow, :boolean, default: true, doc: "whether to auto show the flash on mount"
24
+ attr :close, :boolean, default: true, doc: "whether the flash can be closed"
25
+ attr :rest, :global, doc: "the arbitrary HTML attributes to add to the flash container"
26
+
27
+ slot :inner_block, doc: "the optional inner block that renders the flash message"
28
+
29
+ def flash(assigns) do
30
+ ~H"""
31
+ <div
32
+ :if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}
33
+ id={@id}
34
+ phx-mounted={@autoshow && show("##{@id}")}
35
+ phx-click={JS.push("lv:clear-flash", value: %{key: @kind}) |> hide("##{@id}")}
36
+ role="alert"
37
+ class={[
38
+ "fixed hidden top-2 right-2 w-80 sm:w-96 z-50 rounded-lg p-3 shadow-md shadow-zinc-900/5 ring-1",
39
+ @kind == :info && "bg-emerald-50 text-emerald-800 ring-emerald-500 fill-cyan-900",
40
+ @kind == :error && "bg-rose-50 p-3 text-rose-900 shadow-md ring-rose-500 fill-rose-900"
41
+ ]}
42
+ {@rest}
43
+ >
44
+ <p :if={@title} class="flex items-center gap-1.5 text-[0.8125rem] font-semibold leading-6">
45
+ <.icon :if={@kind == :info} name="hero-information-circle-mini" class="w-4 h-4" />
46
+ <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="w-4 h-4" />
47
+ <%= @title %>
48
+ </p>
49
+ <p class="mt-2 text-[0.8125rem] leading-5"><%= msg %></p>
50
+ <button :if={@close} type="button" class="group absolute top-2 right-1 p-2" aria-label="close">
51
+ <.icon name="hero-x-mark-solid" class="w-5 h-5 opacity-40 group-hover:opacity-70" />
52
+ </button>
53
+ </div>
54
+ """
55
+ end
56
+
57
+ @doc """
58
+ Shows the flash group with standard titles and content.
59
+
60
+ ## Examples
61
+
62
+ <.flash_group flash={@flash} />
63
+
64
+ """
65
+ attr :flash, :map, required: true, doc: "the map of flash messages"
66
+
67
+ def flash_group(assigns) do
68
+ ~H"""
69
+ <.flash kind={:info} title="Success!" flash={@flash} />
70
+ <.flash kind={:error} title="Error!" flash={@flash} />
71
+ <.flash
72
+ id="disconnected"
73
+ kind={:error}
74
+ title="We can't find the internet"
75
+ close={false}
76
+ autoshow={false}
77
+ phx-disconnected={show("#disconnected")}
78
+ phx-connected={hide("#disconnected")}
79
+ >
80
+ Attempting to reconnect <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" />
81
+ </.flash>
82
+ """
83
+ end
84
+
85
+ @doc """
86
+ Renders a [Hero Icon](https://heroicons.com).
87
+
88
+ Hero icons come in three styles – outline, solid, and mini.
89
+ By default, the outline style is used, but solid an mini may
90
+ be applied by using the `-solid` and `-mini` suffix.
91
+
92
+ You can customize the size and colors of the icons by setting
93
+ width, height, and background color classes.
94
+
95
+ Icons are extracted from your `priv/hero_icons` directory and bundled
96
+ within your compiled app.css by the plugin in your `assets/tailwind.config.js`.
97
+
98
+ ## Examples
99
+
100
+ <.icon name="hero-cake" />
101
+ <.icon name="hero-cake-solid" />
102
+ <.icon name="hero-cake-mini" />
103
+ <.icon name="hero-bolt" class="bg-blue-500 w-10 h-10" />
104
+
105
+ """
106
+ attr :name, :string, required: true
107
+ attr :class, :string, default: nil
108
+
109
+ def icon(%{name: "hero-" <> _} = assigns) do
110
+ ~H"""
111
+ <span class={[@name, @class]} />
112
+ """
113
+ end
114
+
115
+ ## JS Commands
116
+
117
+ def show(js \\ %JS{}, selector) do
118
+ JS.show(js,
119
+ to: selector,
120
+ transition:
121
+ {"transition-all transform ease-out duration-300",
122
+ "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
123
+ "opacity-100 translate-y-0 sm:scale-100"}
124
+ )
125
+ end
126
+
127
+ def hide(js \\ %JS{}, selector) do
128
+ JS.hide(js,
129
+ to: selector,
130
+ time: 200,
131
+ transition:
132
+ {"transition-all transform ease-in duration-200",
133
+ "opacity-100 translate-y-0 sm:scale-100",
134
+ "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
135
+ )
136
+ end
137
+ end
lib/chai_web/components/layouts.ex ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ defmodule ChaiWeb.Layouts do
2
+ use ChaiWeb, :html
3
+
4
+ embed_templates "layouts/*"
5
+ end
lib/chai_web/components/layouts/app.html.heex ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <header class="px-4 sm:px-6 lg:px-8">
2
+ <div class="flex items-center justify-between border-b border-zinc-100 py-3">
3
+ <div class="flex items-center gap-4">
4
+ <a href={~p"/"}>
5
+ <img src={~s"/logo.svg"} class="h-8" />
6
+ </a>
7
+ </div>
8
+ <div class="flex items-center gap-4">
9
+ <a
10
+ href="https://huggingface.co/spaces/jonatanklosko/chai/tree/main"
11
+ target="_blank"
12
+ class="text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
13
+ >
14
+ Source
15
+ </a>
16
+ <a
17
+ href="https://github.com/elixir-nx/bumblebee"
18
+ target="_blank"
19
+ class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
20
+ >
21
+ Learn more <span aria-hidden="true">&rarr;</span>
22
+ </a>
23
+ </div>
24
+ </div>
25
+ </header>
26
+ <main class="px-4 py-20 sm:px-6 lg:px-8">
27
+ <div class="mx-auto max-w-2xl">
28
+ <.flash_group flash={@flash} />
29
+ <%= @inner_content %>
30
+ </div>
31
+ </main>
lib/chai_web/components/layouts/root.html.heex ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" style="scrollbar-gutter: stable;">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="csrf-token" content={get_csrf_token()} />
7
+ <link rel="icon" type="image/svg+xml" href={~p"/favicon.svg"} />
8
+ <.live_title>
9
+ <%= assigns[:page_title] || "Chai" %>
10
+ </.live_title>
11
+ <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
12
+ <script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
13
+ </script>
14
+ </head>
15
+ <body class="bg-white antialiased">
16
+ <%= @inner_content %>
17
+ </body>
18
+ </html>
lib/chai_web/controllers/error_html.ex ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ defmodule ChaiWeb.ErrorHTML do
2
+ use ChaiWeb, :html
3
+
4
+ def render(template, _assigns) do
5
+ Phoenix.Controller.status_message_from_template(template)
6
+ end
7
+ end
lib/chai_web/endpoint.ex ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defmodule ChaiWeb.Endpoint do
2
+ use Phoenix.Endpoint, otp_app: :chai
3
+
4
+ # The session will be stored in the cookie and signed,
5
+ # this means its contents can be read but not tampered with.
6
+ # Set :encryption_salt if you would also like to encrypt it.
7
+ @session_options [
8
+ store: :cookie,
9
+ key: "_chai_key",
10
+ signing_salt: "HnJ/u6tl",
11
+ same_site: "Lax"
12
+ ]
13
+
14
+ socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
15
+
16
+ plug Plug.Static,
17
+ at: "/",
18
+ from: :chai,
19
+ gzip: false,
20
+ only: ChaiWeb.static_paths()
21
+
22
+ if code_reloading? do
23
+ socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
24
+ plug Phoenix.LiveReloader
25
+ plug Phoenix.CodeReloader
26
+ end
27
+
28
+ plug Phoenix.LiveDashboard.RequestLogger,
29
+ param_key: "request_logger",
30
+ cookie_key: "request_logger"
31
+
32
+ plug Plug.RequestId
33
+ plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
34
+
35
+ plug Plug.Parsers,
36
+ parsers: [:urlencoded, :multipart, :json],
37
+ pass: ["*/*"],
38
+ json_decoder: Phoenix.json_library()
39
+
40
+ plug Plug.MethodOverride
41
+ plug Plug.Head
42
+ plug Plug.Session, @session_options
43
+ plug ChaiWeb.Router
44
+ end
lib/chai_web/router.ex ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defmodule ChaiWeb.Router do
2
+ use ChaiWeb, :router
3
+
4
+ pipeline :browser do
5
+ plug :accepts, ["html"]
6
+ plug :fetch_session
7
+ plug :fetch_live_flash
8
+ plug :put_root_layout, {ChaiWeb.Layouts, :root}
9
+ plug :protect_from_forgery
10
+ plug :put_secure_browser_headers
11
+ end
12
+
13
+ pipeline :api do
14
+ plug :accepts, ["json"]
15
+ end
16
+
17
+ scope "/", ChaiWeb do
18
+ pipe_through :browser
19
+ end
20
+
21
+ # Enable LiveDashboard in development
22
+ if Application.compile_env(:chai, :dev_routes) do
23
+ import Phoenix.LiveDashboard.Router
24
+
25
+ scope "/dev" do
26
+ pipe_through :browser
27
+
28
+ live_dashboard "/dashboard", metrics: ChaiWeb.Telemetry
29
+ end
30
+ end
31
+ end
lib/chai_web/telemetry.ex ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defmodule ChaiWeb.Telemetry do
2
+ use Supervisor
3
+ import Telemetry.Metrics
4
+
5
+ def start_link(arg) do
6
+ Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
7
+ end
8
+
9
+ @impl true
10
+ def init(_arg) do
11
+ children = [
12
+ # Telemetry poller will execute the given period measurements
13
+ # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
14
+ {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
15
+ # Add reporters as children of your supervision tree.
16
+ # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
17
+ ]
18
+
19
+ Supervisor.init(children, strategy: :one_for_one)
20
+ end
21
+
22
+ def metrics do
23
+ [
24
+ # Phoenix Metrics
25
+ summary("phoenix.endpoint.start.system_time",
26
+ unit: {:native, :millisecond}
27
+ ),
28
+ summary("phoenix.endpoint.stop.duration",
29
+ unit: {:native, :millisecond}
30
+ ),
31
+ summary("phoenix.router_dispatch.start.system_time",
32
+ tags: [:route],
33
+ unit: {:native, :millisecond}
34
+ ),
35
+ summary("phoenix.router_dispatch.exception.duration",
36
+ tags: [:route],
37
+ unit: {:native, :millisecond}
38
+ ),
39
+ summary("phoenix.router_dispatch.stop.duration",
40
+ tags: [:route],
41
+ unit: {:native, :millisecond}
42
+ ),
43
+ summary("phoenix.socket_connected.duration",
44
+ unit: {:native, :millisecond}
45
+ ),
46
+ summary("phoenix.channel_join.duration",
47
+ unit: {:native, :millisecond}
48
+ ),
49
+ summary("phoenix.channel_handled_in.duration",
50
+ tags: [:event],
51
+ unit: {:native, :millisecond}
52
+ ),
53
+
54
+ # VM Metrics
55
+ summary("vm.memory.total", unit: {:byte, :kilobyte}),
56
+ summary("vm.total_run_queue_lengths.total"),
57
+ summary("vm.total_run_queue_lengths.cpu"),
58
+ summary("vm.total_run_queue_lengths.io")
59
+ ]
60
+ end
61
+
62
+ defp periodic_measurements do
63
+ [
64
+ # A module, function and arguments to be invoked periodically.
65
+ # This function must call :telemetry.execute/3 and a metric must be added above.
66
+ # {ChaiWeb, :count_users, []}
67
+ ]
68
+ end
69
+ end
mix.exs ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ defmodule Chai.MixProject do
2
+ use Mix.Project
3
+
4
+ def project do
5
+ [
6
+ app: :chai,
7
+ version: "0.1.0",
8
+ elixir: "~> 1.14",
9
+ elixirc_paths: elixirc_paths(Mix.env()),
10
+ start_permanent: Mix.env() == :prod,
11
+ aliases: aliases(),
12
+ deps: deps()
13
+ ]
14
+ end
15
+
16
+ def application do
17
+ [
18
+ mod: {Chai.Application, []},
19
+ extra_applications: [:logger, :runtime_tools]
20
+ ]
21
+ end
22
+
23
+ defp elixirc_paths(:test), do: ["lib", "test/support"]
24
+ defp elixirc_paths(_), do: ["lib"]
25
+
26
+ defp deps do
27
+ [
28
+ {:phoenix, "~> 1.7.1"},
29
+ {:phoenix_html, "~> 3.3"},
30
+ {:phoenix_live_reload, "~> 1.2", only: :dev},
31
+ {:phoenix_live_view, "~> 0.18.16"},
32
+ {:floki, ">= 0.30.0", only: :test},
33
+ {:phoenix_live_dashboard, "~> 0.7.2"},
34
+ {:esbuild, "~> 0.5", runtime: Mix.env() == :dev},
35
+ {:tailwind, "~> 0.1.8", runtime: Mix.env() == :dev},
36
+ {:telemetry_metrics, "~> 0.6"},
37
+ {:telemetry_poller, "~> 1.0"},
38
+ {:jason, "~> 1.2"},
39
+ {:plug_cowboy, "~> 2.5"}
40
+ ]
41
+ end
42
+
43
+ defp aliases do
44
+ [
45
+ setup: ["deps.get", "assets.setup", "assets.build"],
46
+ "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
47
+ "assets.build": ["tailwind default", "esbuild default"],
48
+ "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]
49
+ ]
50
+ end
51
+ end
mix.lock ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ %{
2
+ "castore": {:hex, :castore, "1.0.1", "240b9edb4e9e94f8f56ab39d8d2d0a57f49e46c56aced8f873892df8ff64ff5a", [:mix], [], "hexpm", "b4951de93c224d44fac71614beabd88b71932d0b1dea80d2f80fb9044e01bbb3"},
3
+ "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
4
+ "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
5
+ "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
6
+ "esbuild": {:hex, :esbuild, "0.7.0", "ce3afb13cd2c5fd63e13c0e2d0e0831487a97a7696cfa563707342bb825d122a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4ae9f4f237c5ebcb001390b8ada65a12fb2bb04f3fe3d1f1692b7a06fbfe8752"},
7
+ "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
8
+ "floki": {:hex, :floki, "0.34.2", "5fad07ef153b3b8ec110b6b155ec3780c4b2c4906297d0b4be1a7162d04a7e02", [:mix], [], "hexpm", "26b9d50f0f01796bc6be611ca815c5e0de034d2128e39cc9702eee6b66a4d1c8"},
9
+ "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
10
+ "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
11
+ "phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"},
12
+ "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"},
13
+ "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
14
+ "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.4.1", "2aff698f5e47369decde4357ba91fc9c37c6487a512b41732818f2204a8ef1d3", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "9bffb834e7ddf08467fe54ae58b5785507aaba6255568ae22b4d46e2bb3615ab"},
15
+ "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
16
+ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
17
+ "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"},
18
+ "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"},
19
+ "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"},
20
+ "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"},
21
+ "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
22
+ "tailwind": {:hex, :tailwind, "0.1.10", "21ed80ae1f411f747ee513470578acaaa1d0eb40170005350c5b0b6d07e2d624", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "e0fc474dfa8ed7a4573851ac69c5fd3ca70fbb0a5bada574d1d657ebc6f2f1f1"},
23
+ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
24
+ "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
25
+ "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"},
26
+ "websock": {:hex, :websock, "0.5.0", "f6bbce90226121d62a0715bca7c986c5e43de0ccc9475d79c55381d1796368cc", [:mix], [], "hexpm", "b51ac706df8a7a48a2c622ee02d09d68be8c40418698ffa909d73ae207eb5fb8"},
27
+ "websock_adapter": {:hex, :websock_adapter, "0.5.0", "cea35d8bbf1a6964e32d4b02ceb561dfb769c04f16d60d743885587e7d2ca55b", [:mix], [{:bandit, "~> 0.6", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "16318b124effab8209b1eb7906c636374f623dc9511a8278ad09c083cea5bb83"},
28
+ }
priv/hero_icons/LICENSE.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Refactoring UI Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
priv/hero_icons/UPGRADE.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ You are running heroicons v2.0.16. To upgrade in place, you can run the following command,
2
+ where your `HERO_VSN` export is your desired version:
3
+
4
+ export HERO_VSN="2.0.16" ; \
5
+ curl -L -o optimized.zip "https://github.com/tailwindlabs/heroicons/archive/refs/tags/v${HERO_VSN}.zip" ; \
6
+ tar --strip-components=1 -xvf optimized.zip heroicons-${HERO_VSN}/optimized ; \
7
+ rm optimized.zip
priv/hero_icons/optimized/20/solid/academic-cap.svg ADDED
priv/hero_icons/optimized/20/solid/adjustments-horizontal.svg ADDED
priv/hero_icons/optimized/20/solid/adjustments-vertical.svg ADDED
priv/hero_icons/optimized/20/solid/archive-box-arrow-down.svg ADDED
priv/hero_icons/optimized/20/solid/archive-box-x-mark.svg ADDED
priv/hero_icons/optimized/20/solid/archive-box.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-down-circle.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-down-left.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-down-on-square-stack.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-down-on-square.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-down-right.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-down-tray.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-down.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-left-circle.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-left-on-rectangle.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-left.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-long-down.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-long-left.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-long-right.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-long-up.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-path-rounded-square.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-path.svg ADDED
priv/hero_icons/optimized/20/solid/arrow-right-circle.svg ADDED