From 0f62f0795c5a9e06fa4e62b6b7b1e6c0774dfe0f Mon Sep 17 00:00:00 2001 From: MAZE Date: Wed, 3 Jan 2024 18:42:06 +0330 Subject: [PATCH] feat: implement override feature --- src/components/app/app.tsx | 2 + .../modals/share-link/share-link.tsx | 14 ++- src/components/modals/shared/index.ts | 1 + .../modals/shared/shared.module.css | 68 +++++++++++ src/components/modals/shared/shared.tsx | 107 ++++++++++++++++++ src/store/sound/sound.actions.ts | 14 +++ 6 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 src/components/modals/shared/index.ts create mode 100644 src/components/modals/shared/shared.module.css create mode 100644 src/components/modals/shared/shared.tsx diff --git a/src/components/app/app.tsx b/src/components/app/app.tsx index 0658c57..aa4d138 100644 --- a/src/components/app/app.tsx +++ b/src/components/app/app.tsx @@ -9,6 +9,7 @@ import { StoreConsumer } from '@/components/store-consumer'; import { Buttons } from '@/components/buttons'; import { Categories } from '@/components/categories'; import { ScrollToTop } from '@/components/scroll-to-top'; +import { SharedModal } from '@/components/modals/shared'; import { Menu } from '@/components/menu/menu'; import { SnackbarProvider } from '@/contexts/snackbar'; @@ -61,6 +62,7 @@ export function App() { + ); diff --git a/src/components/modals/share-link/share-link.tsx b/src/components/modals/share-link/share-link.tsx index 368c5eb..7134ecb 100644 --- a/src/components/modals/share-link/share-link.tsx +++ b/src/components/modals/share-link/share-link.tsx @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useMemo, useEffect, useState } from 'react'; import { IoCopyOutline, IoCheckmark } from 'react-icons/io5/index'; import { Modal } from '@/components/modal'; @@ -14,6 +14,7 @@ interface ShareLinkModalProps { } export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) { + const [isMounted, setIsMounted] = useState(false); const sounds = useSoundStore(state => state.sounds); const { copy, copying } = useCopy(); @@ -38,8 +39,15 @@ export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) { }, [selected]); const url = useMemo(() => { - return `https://moodist.app/?share=${encodeURIComponent(string)}`; - }, [string]); + if (!isMounted) + return `https://moodist.app/?share=${encodeURIComponent(string)}`; + + return `${window.location.protocol}//${ + window.location.host + }/?share=${encodeURIComponent(string)}`; + }, [string, isMounted]); + + useEffect(() => setIsMounted(true), []); return ( diff --git a/src/components/modals/shared/index.ts b/src/components/modals/shared/index.ts new file mode 100644 index 0000000..995eba6 --- /dev/null +++ b/src/components/modals/shared/index.ts @@ -0,0 +1 @@ +export { SharedModal } from './shared'; diff --git a/src/components/modals/shared/shared.module.css b/src/components/modals/shared/shared.module.css new file mode 100644 index 0000000..2cfd0ae --- /dev/null +++ b/src/components/modals/shared/shared.module.css @@ -0,0 +1,68 @@ +.heading { + font-family: var(--font-heading); + font-size: var(--font-md); + font-weight: 700; +} + +.desc { + margin-top: 12px; + line-height: 1.6; + color: var(--color-foreground-subtle); +} + +.sounds { + display: flex; + flex-wrap: wrap; + gap: 8px; + width: 100%; + padding: 12px; + margin-top: 12px; + background-color: var(--color-neutral-50); + border: 1px solid var(--color-neutral-200); + border-radius: 4px; + + & .sound { + padding: 8px 16px; + font-size: var(--font-sm); + font-weight: 500; + background-color: var(--color-neutral-100); + border: 1px solid var(--color-neutral-200); + border-radius: 100px; + } +} + +.footer { + display: flex; + column-gap: 8px; + align-items: center; + justify-content: flex-end; + margin-top: 12px; + + .button { + padding: 12px 16px; + font-family: var(--font-heading); + font-size: var(--font-sm); + font-weight: 600; + color: var(--color-foreground-subtle); + cursor: pointer; + background-color: var(--color-neutral-200); + border: none; + border-radius: 4px; + outline: none; + transition: 0.2s; + + &:hover { + color: var(--color-foreground); + background-color: var(--color-neutral-300); + } + + &.primary { + color: var(--color-neutral-200); + background-color: var(--color-neutral-950); + + &:hover { + background-color: var(--color-neutral-800); + } + } + } +} diff --git a/src/components/modals/shared/shared.tsx b/src/components/modals/shared/shared.tsx new file mode 100644 index 0000000..8a8a3d6 --- /dev/null +++ b/src/components/modals/shared/shared.tsx @@ -0,0 +1,107 @@ +import { useState, useEffect } from 'react'; + +import { Modal } from '@/components/modal'; + +import { useSoundStore } from '@/store'; +import { useSnackbar } from '@/contexts/snackbar'; +import { cn } from '@/helpers/styles'; +import { sounds } from '@/data/sounds'; + +import styles from './shared.module.css'; + +export function SharedModal() { + const override = useSoundStore(state => state.override); + const showSnackbar = useSnackbar(); + + const [isOpen, setIsOpen] = useState(false); + const [sharedSounds, setSharedSounds] = useState< + Array<{ + id: string; + label: string; + volume: number; + }> + >([]); + + useEffect(() => { + const searchParams = new URLSearchParams(window.location.search); + const share = searchParams.get('share'); + + if (share) { + try { + const parsed = JSON.parse(decodeURIComponent(share)); + const allSounds: Record = {}; + + sounds.categories.forEach(category => { + category.sounds.forEach(sound => { + allSounds[sound.id] = sound.label; + }); + }); + + const _sharedSounds: Array<{ + id: string; + label: string; + volume: number; + }> = []; + + Object.keys(parsed).forEach(sound => { + if (allSounds[sound]) { + _sharedSounds.push({ + id: sound, + label: allSounds[sound], + volume: Number(parsed[sound]), + }); + } + }); + + if (_sharedSounds.length) { + setIsOpen(true); + setSharedSounds(_sharedSounds); + } + } catch (error) { + return; + } finally { + history.pushState({}, '', location.href.split('?')[0]); + } + } + }, []); + + const handleOverride = () => { + const newSounds: Record = {}; + + sharedSounds.forEach(sound => { + newSounds[sound.id] = sound.volume; + }); + + override(newSounds); + setIsOpen(false); + showSnackbar('Overrode sounds! You can now play them.'); + }; + + return ( + setIsOpen(false)}> +

New sound mix detected!

+

+ Someone has shared the following mix with you. Would you want to + override your current selection? +

+
+ {sharedSounds.map(sound => ( +
+ {sound.label} +
+ ))} +
+
+ + +
+
+ ); +} diff --git a/src/store/sound/sound.actions.ts b/src/store/sound/sound.actions.ts index 709695a..a167301 100644 --- a/src/store/sound/sound.actions.ts +++ b/src/store/sound/sound.actions.ts @@ -5,6 +5,7 @@ import type { SoundState } from './sound.state'; import { pickMany, random } from '@/helpers/random'; export interface SoundActions { + override: (sounds: Record) => void; pause: () => void; play: () => void; restoreHistory: () => void; @@ -24,6 +25,19 @@ export const createActions: StateCreator< SoundActions > = (set, get) => { return { + override(newSounds) { + get().unselectAll(); + + const sounds = get().sounds; + + Object.keys(newSounds).forEach(sound => { + sounds[sound].isSelected = true; + sounds[sound].volume = newSounds[sound]; + }); + + set({ sounds: { ...sounds } }); + }, + pause() { set({ isPlaying: false }); },