feat: implement override feature

This commit is contained in:
MAZE 2024-01-03 18:42:06 +03:30
parent 1a23e004a6
commit 0f62f0795c
6 changed files with 203 additions and 3 deletions

View file

@ -9,6 +9,7 @@ import { StoreConsumer } from '@/components/store-consumer';
import { Buttons } from '@/components/buttons'; import { Buttons } from '@/components/buttons';
import { Categories } from '@/components/categories'; import { Categories } from '@/components/categories';
import { ScrollToTop } from '@/components/scroll-to-top'; import { ScrollToTop } from '@/components/scroll-to-top';
import { SharedModal } from '@/components/modals/shared';
import { Menu } from '@/components/menu/menu'; import { Menu } from '@/components/menu/menu';
import { SnackbarProvider } from '@/contexts/snackbar'; import { SnackbarProvider } from '@/contexts/snackbar';
@ -61,6 +62,7 @@ export function App() {
<ScrollToTop /> <ScrollToTop />
<Menu /> <Menu />
<SharedModal />
</StoreConsumer> </StoreConsumer>
</SnackbarProvider> </SnackbarProvider>
); );

View file

@ -1,4 +1,4 @@
import { useMemo } from 'react'; import { useMemo, useEffect, useState } from 'react';
import { IoCopyOutline, IoCheckmark } from 'react-icons/io5/index'; import { IoCopyOutline, IoCheckmark } from 'react-icons/io5/index';
import { Modal } from '@/components/modal'; import { Modal } from '@/components/modal';
@ -14,6 +14,7 @@ interface ShareLinkModalProps {
} }
export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) { export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) {
const [isMounted, setIsMounted] = useState(false);
const sounds = useSoundStore(state => state.sounds); const sounds = useSoundStore(state => state.sounds);
const { copy, copying } = useCopy(); const { copy, copying } = useCopy();
@ -38,8 +39,15 @@ export function ShareLinkModal({ onClose, show }: ShareLinkModalProps) {
}, [selected]); }, [selected]);
const url = useMemo(() => { const url = useMemo(() => {
if (!isMounted)
return `https://moodist.app/?share=${encodeURIComponent(string)}`; return `https://moodist.app/?share=${encodeURIComponent(string)}`;
}, [string]);
return `${window.location.protocol}//${
window.location.host
}/?share=${encodeURIComponent(string)}`;
}, [string, isMounted]);
useEffect(() => setIsMounted(true), []);
return ( return (
<Modal show={show} onClose={onClose}> <Modal show={show} onClose={onClose}>

View file

@ -0,0 +1 @@
export { SharedModal } from './shared';

View file

@ -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);
}
}
}
}

View file

@ -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<string, string> = {};
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<string, number> = {};
sharedSounds.forEach(sound => {
newSounds[sound.id] = sound.volume;
});
override(newSounds);
setIsOpen(false);
showSnackbar('Overrode sounds! You can now play them.');
};
return (
<Modal show={isOpen} onClose={() => setIsOpen(false)}>
<h1 className={styles.heading}>New sound mix detected!</h1>
<p className={styles.desc}>
Someone has shared the following mix with you. Would you want to
override your current selection?
</p>
<div className={styles.sounds}>
{sharedSounds.map(sound => (
<div className={styles.sound} key={sound.id}>
{sound.label}
</div>
))}
</div>
<div className={styles.footer}>
<button className={cn(styles.button)} onClick={() => setIsOpen(false)}>
Cancel
</button>
<button
className={cn(styles.button, styles.primary)}
onClick={handleOverride}
>
Override
</button>
</div>
</Modal>
);
}

View file

@ -5,6 +5,7 @@ import type { SoundState } from './sound.state';
import { pickMany, random } from '@/helpers/random'; import { pickMany, random } from '@/helpers/random';
export interface SoundActions { export interface SoundActions {
override: (sounds: Record<string, number>) => void;
pause: () => void; pause: () => void;
play: () => void; play: () => void;
restoreHistory: () => void; restoreHistory: () => void;
@ -24,6 +25,19 @@ export const createActions: StateCreator<
SoundActions SoundActions
> = (set, get) => { > = (set, get) => {
return { 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() { pause() {
set({ isPlaying: false }); set({ isPlaying: false });
}, },