Explorar el Código

Implement basic UI for competitions

ghorsington hace 5 años
padre
commit
3ba525c7b1

+ 3 - 3
web/src/components/Nav.svelte

@@ -6,11 +6,11 @@
 
 <style lang="css">
   nav {
-    @apply .w-2/12 .bg-gray-800 .p-2 .rounded-l-sm .overflow-y-auto .flex .flex-col;
+    @apply .w-2/12 .bg-gray-800 .rounded-l-sm .overflow-y-auto .flex .flex-col;
   }
 
   .user-box {
-    @apply .flex .items-center;
+    @apply .flex .items-center p-2 bg-gray-dark;
 
     & > img {
       @apply .w-1/5 .rounded-full;
@@ -22,7 +22,7 @@
   }
 
   .button-list {
-    @apply .flex .flex-col .m-2;
+    @apply .flex .flex-col .m-2 p-2;
 
     & > h2 {
       @apply .text-lg .my-1 .text-white .font-bold .uppercase .mt-2;

+ 66 - 0
web/src/routes/dashboard/contest/_components/AddContestForm.svelte

@@ -0,0 +1,66 @@
+<script>
+  import EmojiSelector from "./EmojiSelector.svelte";
+  import DurationInput from "./DurationInput.svelte";
+
+  import { createEventDispatcher } from "svelte";
+  const dispatch = createEventDispatcher();
+
+  function submit(e) {
+    e.preventDefault();
+  }
+</script>
+
+<style>
+  div.emoji-selector {
+    @apply .flex;
+
+    & input[type="radio"] {
+      @apply hidden;
+    }
+  }
+
+  input[type="submit"] {
+    flex-grow: 0 !important;
+  }
+
+  span.info {
+    @əpply italic pl-10;
+  }
+</style>
+
+<form class="form-ctrl" on:submit={submit}>
+  <div>
+    <label for="channel-id">Channel</label>
+    <select id="channel-id">
+      <option value="01312">#test</option>
+      <option value="01313">#test2</option>
+      <option value="01314">#test3</option>
+    </select>
+  </div>
+  <div>
+    <label for="entry-duration">Entry duration</label>
+    <DurationInput id="entry-duration" unitId="entry-duration-unit" />
+  </div>
+  <div>
+    <label for="voting-duration">Voting duration</label>
+    <DurationInput id="voting-duration" unitId="voting-duration-unit" />
+  </div>
+  <div>
+    <div>Voting emoji</div>
+    <EmojiSelector />
+  </div>
+  <div>
+    <label for="max-winners">Max winners</label>
+    <input
+      id="max-winners"
+      name="max-winners"
+      type="number"
+      step="1"
+      min="1"
+      value="1" />
+  </div>
+  <div>
+    <div />
+    <input class="btn" type="submit" value="Create competition" />
+  </div>
+</form>

+ 53 - 0
web/src/routes/dashboard/contest/_components/ContestEntry.svelte

@@ -0,0 +1,53 @@
+<script>
+  export let contest = {};
+  export let index = 0;
+  let visible = false;
+
+  function toggleView() {
+    visible = !visible;
+  }
+</script>
+
+<style>
+  h3 {
+    @apply .font-bold;
+  }
+</style>
+
+<tr class="entry" data-nth={index % 2 == 0 ? 'even' : 'odd'}>
+  <td>{contest.id}</td>
+  <td>
+    <pre>{contest.channel}</pre>
+  </td>
+  <td>{contest.started.toISOString()}</td>
+  <td>{contest.entryDuration}</td>
+  <td>{contest.voteDuration}</td>
+  <td>
+    <button class="btn" on:click={toggleView}>View info and actions</button>
+  </td>
+</tr>
+
+{#if visible}
+  <tr class="info">
+    <td colspan="6">
+      <h3>Actions</h3>
+      <div class="py-1 pb-4">
+        <button class="btn" disabled="disabled">End entry phase</button>
+        <button class="btn">Start voting</button>
+        <button class="btn">End vote phase</button>
+        <button class="btn">Announce winners</button>
+      </div>
+
+      {#if contest.winners}
+        <h3>Winners</h3>
+        <div class="py-1">
+          <ol class="list-decimal list-inside">
+            {#each contest.winners as winner}
+              <li>{winner}</li>
+            {/each}
+          </ol>
+        </div>
+      {/if}
+    </td>
+  </tr>
+{/if}

+ 23 - 0
web/src/routes/dashboard/contest/_components/ContestTable.svelte

@@ -0,0 +1,23 @@
+<script>
+  import ContestEntry from "./ContestEntry.svelte";
+
+  export let contests = [];
+</script>
+
+<table>
+  <thead>
+    <tr>
+      <th>ID</th>
+      <th>In channel</th>
+      <th>Date started</th>
+      <th>Entry phase duration</th>
+      <th>Vote phase duration</th>
+      <th>Actions</th>
+    </tr>
+  </thead>
+  <tbody>
+    {#each contests as contest, i (contest.id)}
+      <ContestEntry {contest} index={i} />
+    {/each}
+  </tbody>
+</table>

+ 26 - 0
web/src/routes/dashboard/contest/_components/DurationInput.svelte

@@ -0,0 +1,26 @@
+<script>
+  export let id;
+  export let unitId;
+</script>
+
+<style>
+  div {
+    @apply flex;
+  }
+
+  input {
+    @apply flex-grow mr-1;
+  }
+</style>
+
+<div>
+  <input {id} type="number" required />
+  <select id={unitId}>
+    <option value="s">seconds</option>
+    <option value="m">minutes</option>
+    <option value="h">hours</option>
+    <option value="d">days</option>
+    <option value="m">months</option>
+    <option value="y">years</option>
+  </select>
+</div>

+ 55 - 0
web/src/routes/dashboard/contest/_components/EmojiSelector.svelte

@@ -0,0 +1,55 @@
+<script>
+  export let emojis = [
+    ...[...Array(50).keys()].map(i => ({
+      id: `${i}`,
+      url:
+        "https://pbs.twimg.com/profile_images/1094633303196487687/AJ5HL3Tz_400x400.png",
+      name: "asd"
+    }))
+  ];
+  export let name = "";
+
+  let selectedEmoji = "";
+  function emojiSelected(e) {
+    selectedEmoji = e.target.dataset.emojiId;
+  }
+</script>
+
+<style>
+  div.emoji-selector {
+    @apply .flex .flex-wrap .bg-gray-800 .py-1 .px-1 .rounded .border .border-gray-900 w-1/3;
+
+    &:focus {
+      @apply .border-blue-500 .shadow;
+    }
+
+    & > div {
+      @apply .rounded .mr-1 .mb-1;
+
+      & > img {
+        @apply .w-8 .h-auto;
+      }
+
+      &:hover {
+        @apply .bg-gray-500;
+      }
+
+      &[data-selected="true"] {
+        @apply .bg-gray-300;
+      }
+    }
+  }
+</style>
+
+<div class="emoji-selector">
+  <input required type="hidden" {name} value={selectedEmoji} />
+  {#each emojis as emoji (emoji.id)}
+    <div title={emoji.name} data-selected={selectedEmoji === emoji.id}>
+      <img
+        data-emoji-id={emoji.id}
+        on:click={emojiSelected}
+        alt="Emote {emoji.id}"
+        src={emoji.url} />
+    </div>
+  {/each}
+</div>

+ 59 - 3
web/src/routes/dashboard/contest/index.svelte

@@ -1,7 +1,63 @@
+<script>
+  import ContestTable from "./_components/ContestTable.svelte";
+  import AddContestForm from "./_components/AddContestForm.svelte";
+
+  let contests = [
+    {
+      id: 1,
+      channel: "#channel1",
+      started: new Date(),
+      entryDuration: "1d",
+      voteDuration: "1d",
+      winners: ["link1", "link2", "link3"]
+    },
+    {
+      id: 2,
+      channel: "#channel1",
+      started: new Date(),
+      entryDuration: "1d",
+      voteDuration: "1d",
+      winners: ["link1", "link2", "link3"]
+    },
+    {
+      id: 3,
+      channel: "#channel1",
+      started: new Date(),
+      entryDuration: "1d",
+      voteDuration: "1d",
+      winners: ["link1", "link2", "link3"]
+    }
+  ];
+
+  let showAddContest = false;
+
+  function toggleAddContest() {
+    showAddContest = !showAddContest;
+  }
+
+  function contestAdded(newContest) {
+    contests = [newContest, ...contests];
+  }
+</script>
+
+<style lang="css">
+  h2 {
+    @apply .text-xl .font-bold;
+  }
+</style>
+
 <svelte:head>
   <title>Contest control</title>
 </svelte:head>
+<div class="nav-content">
+  <h2>Actions</h2>
+  <div class="pb-5">
+    <button on:click={toggleAddContest} class="btn">Add contest</button>
+    {#if showAddContest}
+      <AddContestForm on:contestAdded={contestAdded} />
+    {/if}
+  </div>
 
-<div class="content">
-  <h1>Contensts!</h1>
-</div>
+  <h2>Current contests</h2>
+  <ContestTable {contests}/>
+</div>

+ 68 - 2
web/src/style/main.css

@@ -3,9 +3,75 @@
 @import "tailwindcss/utilities";
 
 body {
-    @apply .bg-gray-900 .text-white;
+    @apply .bg-gray-900 .text-white .font-sans;
 }
 
 .shadow-big {
     box-shadow: 3px 3px 10px -1px rgba(0, 0, 0, 0.75);
-}
+}
+
+.nav-content {
+    & h1 {
+        @apply .text-4xl .font-bold;
+    }
+}
+
+table {
+    @apply .border-gray-900 .rounded-sm .border .w-full;
+
+    & thead tr {
+        @apply .bg-gray-900;
+    }
+
+    th, td {
+        @apply .p-1 .border-gray-900 .border-r .border-b;
+    }
+
+    tbody tr.entry[data-nth="even"] {
+        @apply .bg-gray-800;
+    }
+
+    tbody tr.entry[data-nth="odd"] {
+        @apply .bg-gray-700;
+    }
+}
+
+.btn {
+    @apply .px-2 .py-1 .bg-blue-700 .rounded-sm .font-normal .border .border-blue-700;
+
+    &:hover {
+      @apply .bg-blue-600;
+    }
+
+    &[disabled] {
+      @apply .bg-transparent .border .border-blue-700 .text-gray-400 .cursor-not-allowed;
+    }
+}
+
+.input-ctrl {
+    @apply .bg-gray-800 .py-1 .px-1 .rounded .border .border-gray-900;
+}
+
+.input-ctrl-hover {
+    @apply .border-blue-500 .shadow;
+}
+
+input, select {
+    @apply .input-ctrl;
+
+    &:focus {
+        @apply .input-ctrl-hover;
+    }
+}
+
+form.form-ctrl > div {
+    @apply .flex .py-1 .items-center;
+
+    & > *:nth-child(1) {
+      @apply .flex-grow-0 w-1/6;
+    }
+
+    & > *:nth-child(2) {
+      @apply .flex-grow-0 w-1/3;
+    }
+  }