Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ on:

jobs:
test:
name: Test and Lint
name: Test and Build
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24.x

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
Expand All @@ -26,8 +31,11 @@ jobs:
- name: Lint code
run: bun run lint

- name: Build userscript (test)
- name: Run tests
run: bun run test

- name: Build userscript
run: bun run build:userscript

- name: Build apps (test)
- name: Build apps
run: bun run build:apps
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ dist/

.astro/
.wrangler/

.env
Empty file added apps/icbe/README.md
Empty file.
29 changes: 29 additions & 0 deletions apps/icbe/astro.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import cloudflare from "@astrojs/cloudflare";
import solid from "@astrojs/solid-js";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig, envField } from "astro/config";

export default defineConfig({
output: "server",
adapter: cloudflare(),
integrations: [solid()],
vite: { plugins: [tailwindcss()] },
site: "https://icbe.rman.dev",

env: {
schema: {
GITLAB_ACCESS_TOKEN: envField.string({
context: "server",
access: "secret",
startsWith: "glpat",
length: 26,
}),
GITLAB_PROJECT_ID: envField.number({
context: "server",
access: "secret",
int: true,
gt: 1,
}),
},
},
});
17 changes: 16 additions & 1 deletion apps/icbe/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@
"name": "web-app",
"type": "module",
"private": true,
"imports": {
"#styles": "./src/styles/global.css",
"#layout": "./src/layouts/Layout.astro",
"#schemas": "./src/schemas/index.ts",
"#utils/*": "./src/utils/*.ts",
"#components/*": "./src/components/*.tsx"
},
"scripts": {
"typegen": "wrangler types ./src/worker-configuration.d.ts",
"test": "bun test",
"build": "astro build",
"deploy": "wrangler deploy",
"dev": "astro dev"
},
"dependencies": {
"astro": "catalog:"
"@astrojs/cloudflare": "^13.1.3",
"@astrojs/solid-js": "^6.0.1",
"@gitbeaker/rest": "^43.8.0",
"@tailwindcss/vite": "catalog:",
"astro": "catalog:",
"solid-js": "^1.9.11",
"tailwindcss": "catalog:"
},
"devDependencies": {
"wrangler": "catalog:"
Expand Down
2 changes: 2 additions & 0 deletions apps/icbe/public/.assetsignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_worker.js
_routes.json
8 changes: 8 additions & 0 deletions apps/icbe/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {
submitDisprovenElements,
submitProvenElements,
} from "./submit-elements";

export const server = {
elements: { submitDisprovenElements, submitProvenElements },
};
65 changes: 65 additions & 0 deletions apps/icbe/src/actions/submit-elements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ActionError, defineAction } from "astro:actions";
import * as z from "astro/zod";
import { DataProvenSchema } from "#schemas";
import { saveToCloud } from "#utils/gitlab";
import { getDisprovenToSave, getProvenToSave } from "#utils/submit-elements";

const submitDisprovenElements = defineAction({
input: z.object({ elements: z.array(z.object({ name: z.string() })) }),
async handler(input, context) {
const role = null;

if (!role) {
throw new ActionError({
code: "UNAUTHORIZED",
message: "Please login to use this feature.",
});
}

const canSubmitElements = false;

if (!canSubmitElements) {
throw new ActionError({
code: "FORBIDDEN",
message: "You don't have permissions to use this feature.",
});
}

const elements = input.elements.map((element) => element.name);

const data = await getDisprovenToSave(elements);
await saveToCloud(data);

return { success: true } as const;
},
});

const submitProvenElements = defineAction({
input: z.object({ elements: DataProvenSchema }),
handler: async (input, context) => {
const role = null;

if (!role) {
throw new ActionError({
code: "UNAUTHORIZED",
message: "Please login to use this feature.",
});
}

const canSubmitElements = false;

if (!canSubmitElements) {
throw new ActionError({
code: "FORBIDDEN",
message: "You don't have permissions to use this feature.",
});
}

const data = await getProvenToSave(input.elements);
await saveToCloud(data);

return { success: true } as const;
},
});

export { submitDisprovenElements, submitProvenElements };
11 changes: 11 additions & 0 deletions apps/icbe/src/components/AdminDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { JSX } from "solid-js";

export default function AdminDashboard(): JSX.Element {
return (
<div class="p-6 min-h-screen flex flex-col">
<h2 class="text-2xl font-bold mb-6 text-neutral-800 dark:text-neutral-100">
Admin Controls
</h2>
</div>
);
}
40 changes: 40 additions & 0 deletions apps/icbe/src/components/UserDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { type JSX, Show } from "solid-js";

import SubmitDisprovenElementsSection from "./dashboard/SubmitDisprovenElementsSection";
import SubmitProvenElementsSection from "./dashboard/SubmitProvenElementsSection";
import Divider from "./ui/Divider";
import Link from "./ui/Link";

const TopBar = (): JSX.Element => (
<div class="flex items-center justify-between pb-4">
<h1 class="text-2xl font-bold text-neutral-800 dark:text-neutral-100">
User Dashboard
</h1>
</div>
);

export default function UserDashboard(props: {
canSubmitElements: boolean;
}): JSX.Element {
const isAdmin = false;

return (
<div class="p-6 min-h-screen flex flex-col">
<TopBar />
<Show when={isAdmin}>
<Divider />
<Link
href="/admin/dashboard"
color="blue"
label="Open Admin Dashboard"
/>
</Show>
<Show when={props.canSubmitElements}>
<Divider />
<SubmitDisprovenElementsSection />
<Divider />
<SubmitProvenElementsSection />
</Show>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { actions } from "astro:actions";
import type { JSX } from "solid-js";
import { createSignal, Show } from "solid-js";
import Button from "#components/ui/Button";

type SubmitState =
| { status: "not-started" }
| { status: "submitting" }
| { status: "complete"; success: boolean }
| { status: "error"; error?: string };

const SubmitDisprovenElementsResultContainer = (props: {
state: SubmitState;
}) => (
<Show when={props.state.status === "complete" && props.state} keyed>
{(state) => (
<p
class={`mt-4 p-4 rounded-md ${
state.success
? "bg-green-50 dark:bg-green-950"
: "bg-red-50 dark:bg-red-950"
}`}
>
{state.success
? "Elements submitted successfully!"
: "Elements submission failed."}
</p>
)}
</Show>
Comment on lines +12 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Surface action failures in the result container.

Line 15 only renders feedback for status === "complete", but Lines 49-57 set status: "error" on both failure paths. Rejected submissions are currently silent in the UI.

Also applies to: 49-57

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/icbe/src/components/dashboard/SubmitDisprovenElementsSection.tsx` around
lines 12 - 29, The result container currently only shows feedback when
props.state.status === "complete", so errors set as status === "error" (from the
submission paths) are not shown; update SubmitDisprovenElementsResultContainer
to render for both complete and error statuses (e.g., Show when
props.state.status === "complete" || props.state.status === "error") and use the
passed-in state.success or state.status to decide styling and message so
rejected submissions display an error message; ensure you reference the same
Show usage and the props.state value in the component.

);

function SubmitDisprovenElementsSection(): JSX.Element {
const [elementsToBeSubmitted, setElementsToBeSubmitted] = createSignal<
string[]
>([]);
const [submitState, setSubmitState] = createSignal<SubmitState>({
status: "not-started",
});

const handleSubmit = async () => {
try {
setSubmitState({ status: "submitting" });
const disprovenElements = elementsToBeSubmitted().map((name) => ({
name,
}));
const result = await actions.elements.submitDisprovenElements({
elements: disprovenElements,
});
if (result.error) {
console.error("Submit failed:", result.error);
setSubmitState({ status: "error", error: result.error.message });
} else {
setSubmitState({ status: "complete", success: result.data.success });
}
} catch (err) {
console.error("Submit error:", err);
setSubmitState({ status: "error" });
}
};

return (
<div class="flex flex-col gap-4">
<textarea
class="text-white p-1 min-h-64"
placeholder="Enter disproven elements, one per line"
Comment on lines +63 to +65
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Textarea content will be unreadable on the default background.

Line 64 forces white text without setting a matching dark background. On the native light <textarea> background, the user's input becomes invisible.

Suggested fix
-        class="text-white p-1 min-h-64"
+        class="min-h-64 p-2 border rounded-md bg-white text-black dark:bg-gray-800 dark:text-white dark:border-gray-700"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<textarea
class="text-white p-1 min-h-64"
placeholder="Enter disproven elements, one per line"
<textarea
class="min-h-64 p-2 border rounded-md bg-white text-black dark:bg-gray-800 dark:text-white dark:border-gray-700"
placeholder="Enter disproven elements, one per line"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/icbe/src/components/dashboard/SubmitDisprovenElementsSection.tsx` around
lines 63 - 65, The textarea in SubmitDisprovenElementsSection.tsx forces white
text via the class "text-white" without a matching dark background, making input
unreadable on the default light textarea; update the textarea's class list to
either remove "text-white" (so it uses the default/OS colors) or pair it with a
dark background utility (e.g., add a bg-slate-800 or bg-gray-900 class and
appropriate focus/border classes) and ensure placeholder styling remains
visible; locate the <textarea> in the SubmitDisprovenElementsSection component
and adjust the class/className accordingly.

data-1p-ignore
onInput={(e) =>
setElementsToBeSubmitted(
e.currentTarget.value
.split("\n")
.map((s) => s.trim())
.filter(Boolean),
)
}
/>

<Button
onClick={handleSubmit}
disabled={
submitState().status === "submitting" ||
elementsToBeSubmitted().length === 0
}
label={
submitState().status === "submitting"
? "Submitting..."
: elementsToBeSubmitted().length
? `Submit disproven Elements (${elementsToBeSubmitted().length})`
: "Submit disproven Elements"
}
/>

<SubmitDisprovenElementsResultContainer state={submitState()} />
</div>
);
}

export default SubmitDisprovenElementsSection;
Loading