-
Notifications
You must be signed in to change notification settings - Fork 0
Frontend for achievements #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <script lang="ts"> | ||
| import CheckIcon from '$lib/components/icons/CheckIcon.svelte'; | ||
| import CrossIcon from '$lib/components/icons/CrossIcon.svelte'; | ||
|
|
||
| let { checked, children } = $props(); | ||
| </script> | ||
|
|
||
| <span class="inline-flex flex-row items-baseline"> | ||
| {#if checked} | ||
| <span class="text-green-400 size-5 inline-block my-auto mr-1"> | ||
| <CheckIcon /> | ||
| </span> | ||
| {:else} | ||
| <span class="text-red-400 size-5 inline-block my-auto mr-1"> | ||
| <CrossIcon /> | ||
| </span> | ||
| {/if} | ||
| {@render children()} | ||
| </span> |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,40 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <script lang="ts"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import CheckedLabel from '$lib/components/CheckedLabel.svelte'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let { achievement } = $props(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let goalsUnlocked = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (let i = 0; i < achievement.goals.length; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| goalsUnlocked.push(Math.random() >= 0.5); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let imgSrc = 'https://cdn.fastly.steamstatic.com/steamcommunity/public/images/apps/1903340/893a5719f74928a4706ad295b4ab42cf0a2ffacb.jpg'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const achievementUnlocked = () => goalsUnlocked.every(g => g); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const progressBarStyle = `width: ${goalsUnlocked.filter(g => g).length * 100 / goalsUnlocked.length}%`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+3
to
+15
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let { achievement } = $props(); | |
| let goalsUnlocked = []; | |
| for (let i = 0; i < achievement.goals.length; i++) { | |
| goalsUnlocked.push(Math.random() >= 0.5); | |
| } | |
| let imgSrc = 'https://cdn.fastly.steamstatic.com/steamcommunity/public/images/apps/1903340/893a5719f74928a4706ad295b4ab42cf0a2ffacb.jpg'; | |
| const achievementUnlocked = () => goalsUnlocked.every(g => g); | |
| const progressBarStyle = `width: ${goalsUnlocked.filter(g => g).length * 100 / goalsUnlocked.length}%`; | |
| import { onMount } from 'svelte'; | |
| let { achievement } = $props(); | |
| let goalsUnlocked: boolean[] = Array.from( | |
| { length: achievement.goals.length }, | |
| () => false | |
| ); | |
| onMount(() => { | |
| // Randomize goals on the client only, to avoid SSR/CSR mismatches | |
| goalsUnlocked = goalsUnlocked.map(() => Math.random() >= 0.5); | |
| }); | |
| let imgSrc = 'https://cdn.fastly.steamstatic.com/steamcommunity/public/images/apps/1903340/893a5719f74928a4706ad295b4ab42cf0a2ffacb.jpg'; | |
| const achievementUnlocked = () => goalsUnlocked.every(g => g); | |
| $: progressBarStyle = `width: ${ | |
| goalsUnlocked.length === 0 | |
| ? 0 | |
| : (goalsUnlocked.filter(g => g).length * 100) / goalsUnlocked.length | |
| }%`; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||||||||||||||||||||||||||||||||||
| <script lang="ts"> | ||||||||||||||||||||||||||||||||||||||
| import AchievementServiceGroup from '$lib/components/achievements/AchievementServiceGroup.svelte'; | ||||||||||||||||||||||||||||||||||||||
| import { createQuery, type CreateQueryResult } from '@tanstack/svelte-query'; | ||||||||||||||||||||||||||||||||||||||
| import { type AchievementService, getAchievementServices } from '$lib/globalFunctions-Types'; | ||||||||||||||||||||||||||||||||||||||
| import AchievementEditModal from '$lib/components/achievements/AchievementEditModal.svelte'; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| let { admin } = $props(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| let query: CreateQueryResult<AchievementService[]> = createQuery({ | ||||||||||||||||||||||||||||||||||||||
| queryKey: [`achievement-services`], | ||||||||||||||||||||||||||||||||||||||
| queryFn: getAchievementServices, | ||||||||||||||||||||||||||||||||||||||
| retry: false | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| let editModal: AchievementEditModal | undefined = $state(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| </script> | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| {#if $query.isSuccess} | ||||||||||||||||||||||||||||||||||||||
| <div class="flex flex-col items-center w-full md:w-4/5 mx-auto px-10"> | ||||||||||||||||||||||||||||||||||||||
| <div class="flex flex-row justify-end w-full mt-10"> | ||||||||||||||||||||||||||||||||||||||
| <button class="bg-orange-200 hover:bg-orange-300 px-4 py-2 rounded-md font-semibold text-orange-900" | ||||||||||||||||||||||||||||||||||||||
| onclick={editModal?.open} | ||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||
| Add Service | ||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+29
|
||||||||||||||||||||||||||||||||||||||
| <div class="flex flex-row justify-end w-full mt-10"> | |
| <button class="bg-orange-200 hover:bg-orange-300 px-4 py-2 rounded-md font-semibold text-orange-900" | |
| onclick={editModal?.open} | |
| > | |
| Add Service | |
| </button> | |
| </div> | |
| {#if admin} | |
| <div class="flex flex-row justify-end w-full mt-10"> | |
| <button | |
| class="bg-orange-200 hover:bg-orange-300 px-4 py-2 rounded-md font-semibold text-orange-900" | |
| disabled={!editModal} | |
| onclick={() => { if (editModal) editModal.open(); }} | |
| > | |
| Add Service | |
| </button> | |
| </div> | |
| {/if} |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,74 @@ | ||||||
| <script lang="ts"> | ||||||
| import { createDialog } from 'svelte-headlessui'; | ||||||
| import Transition from 'svelte-transition'; | ||||||
|
|
||||||
| const dialog = createDialog({ label: 'Edit Service' }); | ||||||
|
|
||||||
| export function close() { | ||||||
| dialog.close(); | ||||||
| } | ||||||
|
|
||||||
| export function open() { | ||||||
| dialog.open(); | ||||||
| } | ||||||
|
|
||||||
| function save() { | ||||||
| } | ||||||
|
|
||||||
|
|
||||||
| </script> | ||||||
| <div class="relative z-10"> | ||||||
| <Transition show={$dialog.expanded}> | ||||||
| <Transition | ||||||
| enter="ease-out duration-300" | ||||||
| enterFrom="opacity-0" | ||||||
| enterTo="opacity-100" | ||||||
| leave="ease-in duration-200" | ||||||
| leaveFrom="opacity-100" | ||||||
| leaveTo="opacity-0" | ||||||
| > | ||||||
| <button class="fixed inset-0 bg-black/25" aria-label="close" onclick={dialog.close}></button> | ||||||
| </Transition> | ||||||
|
|
||||||
| <div class="fixed inset-0 overflow-y-auto"> | ||||||
| <div class="flex min-h-full items-center justify-center p-4 text-center"> | ||||||
| <Transition | ||||||
| enter="ease-out duration-300" | ||||||
| enterFrom="opacity-0 scale-95" | ||||||
| enterTo="opacity-100 scale-100" | ||||||
| leave="ease-in duration-200" | ||||||
| leaveFrom="opacity-100 scale-100" | ||||||
| leaveTo="opacity-0 scale-95" | ||||||
| > | ||||||
| <div | ||||||
| class="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all" | ||||||
| use:dialog.modal | ||||||
| > | ||||||
| <h3 class="text-lg leading-6 font-medium text-gray-900">Edit Service</h3> | ||||||
| <div class="mt-2 flex flex-row justify-center"> | ||||||
| // Middle Content | ||||||
|
||||||
| // Middle Content | |
| <!-- Middle Content --> |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||||
| <script lang="ts"> | ||||||
| import { getAchievementsFromService, toTitleCase } from '$lib/globalFunctions-Types'; | ||||||
| import type { Achievement as AchievementPayload } from '$lib/globalFunctions-Types'; | ||||||
| import ArrowNoBaseIcon from '$lib/components/icons/ArrowNoBaseIcon.svelte'; | ||||||
| import Achievement from '$lib/components/achievements/Achievement.svelte'; | ||||||
| import { createQuery, type CreateQueryResult } from '@tanstack/svelte-query'; | ||||||
| import { slide } from 'svelte/transition'; | ||||||
| import PencilIcon from '$lib/components/icons/PencilIcon.svelte'; | ||||||
|
|
||||||
| let { service, editAllowed, editModal } = $props(); | ||||||
|
|
||||||
| let isOpen = $state(false); | ||||||
|
|
||||||
| function toggle() { | ||||||
| isOpen = !isOpen; | ||||||
| } | ||||||
|
|
||||||
| let query: CreateQueryResult<AchievementPayload[]> = createQuery({ | ||||||
| queryKey: [`service-${service.id}-achievements`], | ||||||
| queryFn: async () => getAchievementsFromService(service.id), | ||||||
| retry: false | ||||||
| }); | ||||||
|
|
||||||
| function keyClickHandler(event: any, clickFn: Function) { | ||||||
| if (event.key === 'Enter' || event.key === ' ') { | ||||||
| event.preventDefault(); | ||||||
| clickFn(); | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| function openEdit() { | ||||||
| toggle(); | ||||||
| editModal.open(); | ||||||
|
||||||
| editModal.open(); | |
| editModal?.open(); |
Copilot
AI
Mar 3, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
openEdit() both toggles the accordion and is triggered from a <button> nested inside a parent element that also has onclick={toggle}. Because the button click bubbles, toggle() is called twice (once in openEdit, once in the parent), leaving isOpen unchanged and causing flaky behavior. Stop propagation on the edit button click/keydown, or remove the toggle() call from openEdit and manage accordion state explicitly when opening the modal.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <script lang="ts"> | ||
| let { fill = 'none', stroke = '#fff' } = $props(); | ||
| </script> | ||
|
|
||
|
|
||
| <svg viewBox="0 0 32.00 32.00" | ||
| style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> | ||
| <g transform="matrix(1,0,0,1,-96,-192)"> | ||
| <g transform="matrix(0.785714,0,0,0.785714,-15.2857,40)"> | ||
| <path | ||
| d="M162,196L165.152,198.235L168.65,198.482L170.613,201.387L174.124,203L173.765,206.848L175.3,210L173.765,213.152L174.124,217L170.613,218.613L168.65,221.518L165.152,221.765L162,224L158.848,221.765L155.35,221.518L153.387,218.613L149.876,217L150.235,213.152L148.7,210L150.235,206.848L149.876,203L153.387,201.387L155.35,198.482L158.848,198.235L162,196Z" | ||
| fill={fill}></path> | ||
| </g> | ||
| <g transform="matrix(0.866025,0.5,-0.5,0.866025,121.962,-23.4275)"> | ||
| <path d="M102,213L102,221L105,219L108,221L108,213" fill={fill}></path> | ||
| </g> | ||
| <g transform="matrix(-0.866025,0.5,0.5,0.866025,102.031,-23.4275)"> | ||
| <path d="M102,213L102,221L105,219L108,221L108,213" fill={fill}></path> | ||
| </g> | ||
| <path | ||
| d="M102.835,211.702L98.931,218.464C98.746,218.783 98.753,219.178 98.947,219.492C99.142,219.805 99.493,219.986 99.861,219.962L102.794,219.773C102.794,219.773 104.097,222.407 104.097,222.407C104.26,222.738 104.592,222.952 104.961,222.964C105.329,222.975 105.675,222.783 105.859,222.464L109.789,215.658L110.265,215.996C111.304,216.733 112.696,216.733 113.735,215.996L114.206,215.661L118.134,222.464C118.318,222.783 118.664,222.975 119.032,222.964C119.401,222.952 119.733,222.738 119.896,222.407L121.199,219.773C121.199,219.773 124.132,219.962 124.132,219.962C124.5,219.986 124.851,219.805 125.046,219.492C125.24,219.178 125.247,218.783 125.062,218.464L121.16,211.706C122.01,211.093 122.49,210.066 122.39,208.995C122.355,208.622 122.32,208.24 122.292,207.942C122.275,207.759 122.308,207.576 122.388,207.411C122.388,207.411 122.923,206.313 122.923,206.313C123.326,205.484 123.326,204.516 122.923,203.687C122.923,203.687 122.388,202.589 122.388,202.589C122.308,202.424 122.275,202.241 122.292,202.058C122.32,201.76 122.355,201.378 122.39,201.005C122.509,199.736 121.813,198.531 120.655,198C120.314,197.843 119.966,197.683 119.693,197.558C119.527,197.482 119.385,197.361 119.282,197.209C119.282,197.209 118.599,196.197 118.599,196.197C118.083,195.433 117.244,194.949 116.324,194.884C116.324,194.884 115.106,194.798 115.106,194.798C114.923,194.785 114.748,194.722 114.598,194.616C114.354,194.443 114.041,194.221 113.735,194.004C112.696,193.267 111.304,193.267 110.265,194.004L110.265,194.004C109.959,194.221 109.646,194.443 109.402,194.616C109.252,194.722 109.077,194.785 108.894,194.798C108.894,194.798 107.676,194.884 107.676,194.884C106.756,194.949 105.917,195.433 105.401,196.197C105.401,196.197 104.718,197.209 104.718,197.209C104.615,197.361 104.473,197.482 104.307,197.558C104.034,197.683 103.686,197.843 103.345,198C102.187,198.531 101.491,199.736 101.61,201.005C101.645,201.378 101.68,201.76 101.708,202.058C101.725,202.241 101.692,202.424 101.612,202.589C101.612,202.589 101.077,203.687 101.077,203.687C100.674,204.516 100.674,205.484 101.077,206.313C101.077,206.313 101.612,207.411 101.612,207.411C101.692,207.576 101.725,207.759 101.708,207.942C101.68,208.24 101.645,208.622 101.61,208.995C101.51,210.064 101.989,211.088 102.835,211.702ZM104.599,212.646L101.597,217.846L103.331,217.734C103.733,217.708 104.112,217.927 104.291,218.289C104.291,218.289 105.061,219.846 105.061,219.846L107.787,215.124L107.676,215.116C106.756,215.051 105.917,214.567 105.401,213.803C105.401,213.803 104.718,212.791 104.718,212.791C104.683,212.739 104.643,212.691 104.599,212.646ZM119.397,212.651C119.354,212.694 119.316,212.741 119.282,212.791C119.282,212.791 118.599,213.803 118.599,213.803C118.083,214.567 117.244,215.051 116.324,215.116L116.206,215.124L118.932,219.846L119.702,218.289C119.881,217.927 120.26,217.708 120.662,217.734C120.662,217.734 122.396,217.846 122.396,217.846L119.397,212.651ZM110.559,196.248L111.422,195.636C111.768,195.39 112.232,195.39 112.578,195.636L113.441,196.248C113.89,196.566 114.417,196.754 114.965,196.793L116.183,196.879C116.49,196.901 116.769,197.062 116.941,197.317L117.625,198.328C117.932,198.784 118.359,199.146 118.859,199.376L119.82,199.817C120.206,199.994 120.438,200.396 120.399,200.819L120.3,201.872C120.249,202.42 120.349,202.97 120.59,203.465L121.125,204.562C121.259,204.839 121.259,205.161 121.125,205.438L120.59,206.535C120.349,207.03 120.249,207.58 120.3,208.128L120.399,209.181C120.438,209.604 120.206,210.006 119.82,210.183L118.859,210.624C118.359,210.854 117.932,211.216 117.625,211.672L116.941,212.683C116.769,212.938 116.49,213.099 116.183,213.121L114.965,213.207C114.417,213.246 113.89,213.434 113.441,213.752L112.578,214.364C112.232,214.61 111.768,214.61 111.422,214.364L110.559,213.752C110.11,213.434 109.583,213.246 109.035,213.207L107.817,213.121C107.51,213.099 107.231,212.938 107.059,212.683L106.375,211.672C106.068,211.216 105.641,210.854 105.141,210.624L104.18,210.183C103.794,210.006 103.562,209.604 103.601,209.181L103.7,208.128C103.751,207.58 103.651,207.03 103.41,206.535L102.875,205.438C102.741,205.161 102.741,204.839 102.875,204.562L103.41,203.465C103.651,202.97 103.751,202.42 103.7,201.872L103.601,200.819C103.562,200.396 103.794,199.994 104.18,199.817L105.141,199.376C105.641,199.146 106.068,198.784 106.375,198.328L107.059,197.317C107.231,197.062 107.51,196.901 107.817,196.879L109.035,196.793C109.583,196.754 110.11,196.566 110.559,196.248ZM112,199C108.689,199 106,201.689 106,205C106,208.311 108.689,211 112,211C115.311,211 118,208.311 118,205C118,201.689 115.311,199 112,199ZM112,201C114.208,201 116,202.792 116,205C116,207.208 114.208,209 112,209C109.792,209 108,207.208 108,205C108,202.792 109.792,201 112,201Z" | ||
| fill={stroke}></path> | ||
| </g> | ||
| </svg> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| <svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||
| <path | ||
| d="M18.2929 15.2893C18.6834 14.8988 18.6834 14.2656 18.2929 13.8751L13.4007 8.98766C12.6195 8.20726 11.3537 8.20757 10.5729 8.98835L5.68257 13.8787C5.29205 14.2692 5.29205 14.9024 5.68257 15.2929C6.0731 15.6835 6.70626 15.6835 7.09679 15.2929L11.2824 11.1073C11.673 10.7168 12.3061 10.7168 12.6966 11.1073L16.8787 15.2893C17.2692 15.6798 17.9024 15.6798 18.2929 15.2893Z" /> | ||
| </svg> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||
| <g transform="scale(1.3, 1.3) translate(-2.6, -4)"> | ||
| <path d="M4 12.6111L8.92308 17.5L20 6.5" stroke-width="2" stroke-linecap="round" | ||
| stroke-linejoin="round" /> | ||
| </g> | ||
| </svg> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| <svg viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||
| <g transform="scale(1.9) translate(-5.6,-5.6)"> | ||
| <path | ||
| d="M6.99486 7.00636C6.60433 7.39689 6.60433 8.03005 6.99486 8.42058L10.58 12.0057L6.99486 15.5909C6.60433 15.9814 6.60433 16.6146 6.99486 17.0051C7.38538 17.3956 8.01855 17.3956 8.40907 17.0051L11.9942 13.4199L15.5794 17.0051C15.9699 17.3956 16.6031 17.3956 16.9936 17.0051C17.3841 16.6146 17.3841 15.9814 16.9936 15.5909L13.4084 12.0057L16.9936 8.42059C17.3841 8.03007 17.3841 7.3969 16.9936 7.00638C16.603 6.61585 15.9699 6.61585 15.5794 7.00638L11.9942 10.5915L8.40907 7.00636C8.01855 6.61584 7.38538 6.61584 6.99486 7.00636Z" /> | ||
| </g> | ||
| </svg> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On small screens the "Achievements" link hides its text (
hidden md:inline-block) and only shows an icon. That leaves the link without an accessible name for screen readers unless the icon provides one. Add anaria-labelon the<a>or include ansr-onlytext label so the link is still announced correctly.