diff --git a/main.w b/main.w index 6f94211..ff15170 100644 --- a/main.w +++ b/main.w @@ -50,7 +50,7 @@ class Util { class Store { table: ddb.DynamoDBTable; init() { - this.table = new ddb.DynamoDBTable(hashKey: "Name") as "Items"; + this.table = new ddb.DynamoDBTable(hashKey: "Name") as "Entries"; } inflight setEntry(entry: Entry) { @@ -58,16 +58,16 @@ class Store { } inflight getRandomPair(): Array { - let items = this.table.scan(); + let entries = this.table.scan(); - let firstIdx = math.floor(math.random() * items.length); - let var secondIdx = math.floor(math.random() * items.length); - while secondIdx != firstIdx { - secondIdx = math.floor(math.random() * items.length); + let firstIdx = math.floor(math.random() * entries.length); + let var secondIdx = math.floor(math.random() * entries.length); + while secondIdx == firstIdx { + secondIdx = math.floor(math.random() * entries.length); } - let first = _mapToEntry(items.at(firstIdx)); - let second = _mapToEntry(items.at(secondIdx)); + let first = _mapToEntry(entries.at(firstIdx)); + let second = _mapToEntry(entries.at(secondIdx)); return [first, second]; } @@ -101,6 +101,7 @@ class Store { let store = new Store() as "VotingAppStore"; +// about 40 items... any more and we need to start paginating let foods = [ "Nigiri sushi", "Pizza margherita", @@ -163,7 +164,11 @@ website.addJson("config.json", { apiUrl: api.url }); // Select two random items from the list of items for the user to choose between api.post("/requestChoices", inflight (_) => { - let items = store.getRandomPair(); + let entries = store.getRandomPair(); + let entryNames = MutArray[]; + for entry in entries { + entryNames.push(entry.name); + } return cloud.ApiResponse { // TODO: refactor to a constant - https://github.com/winglang/wing/issues/3119 headers: { @@ -172,13 +177,13 @@ api.post("/requestChoices", inflight (_) => { "Access-Control-Allow-Methods" => "OPTIONS,GET", }, status: 200, - body: Json.stringify(items), + body: Json.stringify(entryNames), }; }); -// Obtain a list of all items and their scores -api.get("/items", inflight (_) => { - let items = store.list(); +// Obtain a list of all entries and their scores +api.get("/leaderboard", inflight (_) => { + let entries = store.list(); return cloud.ApiResponse { // TODO: refactor to a constant - https://github.com/winglang/wing/issues/3119 headers: { @@ -187,7 +192,7 @@ api.get("/items", inflight (_) => { "Access-Control-Allow-Methods" => "OPTIONS,GET", }, status: 200, - body: Json.stringify(items), + body: Json.stringify(entries), }; }); diff --git a/website/package-lock.json b/website/package-lock.json index 8fae65e..18c56df 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -11,9 +11,14 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^29.5.3", + "@types/node": "^20.4.8", + "@types/react": "^18.2.18", + "@types/react-dom": "^18.2.7", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "typescript": "^4.9.5", "web-vitals": "^2.1.4" } }, @@ -4317,9 +4322,9 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "20.4.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz", - "integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg==" + "version": "20.4.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.8.tgz", + "integrity": "sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==" }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -4352,9 +4357,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/react": { - "version": "18.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.17.tgz", - "integrity": "sha512-u+e7OlgPPh+aryjOm5UJMX32OvB2E3QASOAqVMY6Ahs90djagxwv2ya0IctglNbNTexC12qCSMZG47KPfy1hAA==", + "version": "18.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.18.tgz", + "integrity": "sha512-da4NTSeBv/P34xoZPhtcLkmZuJ+oYaCxHmyHzwaDQo9RQPBeXV+06gEk2FpqEcsX9XrnNLvRpVh6bdavDSjtiQ==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -16377,7 +16382,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/website/package.json b/website/package.json index a321edd..30ed611 100644 --- a/website/package.json +++ b/website/package.json @@ -6,9 +6,14 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "@types/jest": "^29.5.3", + "@types/node": "^20.4.8", + "@types/react": "^18.2.18", + "@types/react-dom": "^18.2.7", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/website/src/App.js b/website/src/App.js deleted file mode 100644 index e4f56c2..0000000 --- a/website/src/App.js +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useState, useEffect } from "react"; - -const Table = () => { - const [data, setData] = useState([]); - useEffect(() => { - // Function to fetch data from the endpoint - const fetchData = async () => { - try { - const config = await fetch("./config.json"); - const baseUrl = (await config.json()).apiUrl; - const response = await fetch(baseUrl + "/items", { - - }); - if (!response.ok) { - throw new Error('Failed to fetch data'); - } - const jsonData = await response.json(); - setData(jsonData); - } catch (error) { - console.error('Error fetching data:', error); - } - }; - - fetchData(); // Call the fetch function when the component mounts - }, []); - - return ( -
-

Data Table

- - - - - - - - - {data.map((item) => ( - - - - - ))} - -
NameScore
{item.Name}{item.Score}
-
- ); -}; - -function App() { - return ( - - ); -} - -export default App; diff --git a/website/src/App.test.js b/website/src/App.test.tsx similarity index 100% rename from website/src/App.test.js rename to website/src/App.test.tsx diff --git a/website/src/App.tsx b/website/src/App.tsx new file mode 100644 index 0000000..88b9e3e --- /dev/null +++ b/website/src/App.tsx @@ -0,0 +1,119 @@ +import React, { useState, useEffect } from "react"; + +interface Config { + baseUrl: string; +} + +interface Entry { + name: string; + score: number; +} + +const fetchConfig = async () => { + const response = await fetch("./config.json"); + if (!response.ok) { + throw new Error('Failed to fetch config'); + } + const config: Config = await response.json(); + return config; +} + +const fetchItems = async () => { + const baseUrl = (await fetchConfig()).baseUrl; + const response = await fetch(baseUrl + "/items"); + if (!response.ok) { + throw new Error('Failed to fetch leaderboard data'); + } + const jsonData: Entry[] = await response.json(); + return jsonData; +} + +const fetchChoices = async () => { + const baseUrl = (await fetchConfig()).baseUrl; + const response = await fetch(baseUrl + "/requestChoices", { + method: "POST", + }); + if (!response.ok) { + throw new Error('Failed to request choices'); + } + const jsonData: string[] = await response.json(); + return jsonData; +} + +interface LeaderboardProps { + swapViews: () => void; +} + +const Leaderboard = (props: LeaderboardProps) => { + const [data, setData] = useState([]); + useEffect(() => { + fetchItems().then((items) => setData(items)); + }, []); + + return ( +
+

Leaderboard

+
+ + + + + + + + {data.sort((a, b) => b.score - a.score).map((item) => ( + + + + + ))} + +
NameScore
{item.name}{item.score}
+ + + ); +}; + +interface VotingProps { + swapViews: () => void; +} + +const Voting = (props: VotingProps) => { + const [choices, setChoices] = useState([]); + useEffect(() => { + fetchChoices().then((choices) => setChoices(choices)); + }, []); + + return ( +
+

Which is better?

+
+ {choices.map((item) => ( +
+
{item}
+ +
+ ))} +
+ +
+ ) +} + +type View = "voting" | "leaderboard"; + +function App() { + let [view, setView] = useState("voting"); + const swapViews = () => { + setView(view === "voting" ? "leaderboard" : "voting"); + }; + + switch (view) { + case "voting": + return ; + case "leaderboard": + return ; + } +} + +export default App; diff --git a/website/src/index.js b/website/src/index.tsx similarity index 99% rename from website/src/index.js rename to website/src/index.tsx index d563c0f..9757200 100644 --- a/website/src/index.js +++ b/website/src/index.tsx @@ -4,7 +4,7 @@ import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -const root = ReactDOM.createRoot(document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById('root')!); root.render( diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 0000000..a273b0c --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +}