chore: added changeset
This commit is contained in:
parent
8afa96590e
commit
cdf77523a5
5
.changeset/rude-insects-smell.md
Normal file
5
.changeset/rude-insects-smell.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'pastebar-app-ui': patch
|
||||
---
|
||||
|
||||
Added protected collection with PIN access
|
@ -218,7 +218,9 @@ function App() {
|
||||
isSingleClickToCopyPaste: settings.isSingleClickToCopyPaste?.valueBool ?? false,
|
||||
hasPinProtectedCollections:
|
||||
settings.hasPinProtectedCollections?.valueBool ?? false,
|
||||
protectedCollections: settings.protectedCollections?.valueText.split(','),
|
||||
protectedCollections: settings.protectedCollections?.valueText
|
||||
? settings.protectedCollections.valueText.split(',').filter(Boolean)
|
||||
: [],
|
||||
isSingleClickToCopyPasteQuickWindow:
|
||||
settings.isSingleClickToCopyPasteQuickWindow?.valueBool ?? false,
|
||||
isKeepPinnedOnClearEnabled:
|
||||
|
@ -42,7 +42,7 @@ type Props = {
|
||||
|
||||
export default function ModalLockScreenConfirmationWithPasscodeOrPassword({
|
||||
open,
|
||||
title = 'Unlock Application Screen',
|
||||
title = 'Confirm Passcode',
|
||||
isLockScreen = false,
|
||||
showPasscode = true,
|
||||
onConfirmSuccess,
|
||||
@ -171,6 +171,16 @@ export default function ModalLockScreenConfirmationWithPasscodeOrPassword({
|
||||
setFocusField(confirmPasscodeCurrentFocus.value)
|
||||
}, [confirmPasscodeCurrentFocus.value])
|
||||
|
||||
// Set initial focus when modal opens
|
||||
useEffect(() => {
|
||||
if (open && showPasscode && screenLockPassCode) {
|
||||
setTimeout(() => {
|
||||
confirmPasscodeCurrentFocus.value = 0
|
||||
setFocusField(0)
|
||||
}, 100)
|
||||
}
|
||||
}, [open, showPasscode])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
|
@ -1,12 +1,8 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { collectionsStoreAtom, settingsStoreAtom } from '~/store'
|
||||
import {
|
||||
isCollectionPinModalOpenAtom,
|
||||
collectionPinModalPropsAtom,
|
||||
} from '~/store/uiStore'
|
||||
import { useAtomValue, useSetAtom } from 'jotai'
|
||||
import { collectionsStoreAtom } from '~/store'
|
||||
import { useAtomValue } from 'jotai'
|
||||
|
||||
import { Collection, Item, Tabs } from '~/types/menu'
|
||||
|
||||
@ -253,25 +249,11 @@ export function useUpdateCollectionById() {
|
||||
|
||||
export function useSelectCollectionById() {
|
||||
const queryClient = useQueryClient()
|
||||
const { protectedCollections, screenLockPassCode } =
|
||||
useAtomValue(settingsStoreAtom)
|
||||
const { collections, updateCurrentCollectionId } = useAtomValue(collectionsStoreAtom)
|
||||
|
||||
const setIsCollectionPinModalOpen = useSetAtom(isCollectionPinModalOpenAtom)
|
||||
const setCollectionPinModalProps = useSetAtom(collectionPinModalPropsAtom)
|
||||
|
||||
const { mutate: invokeSelectCollectionById, isSuccess: selectCollectionByIdSuccess } =
|
||||
const { mutate: selectCollectionById, isSuccess: selectCollectionByIdSuccess } =
|
||||
useInvokeMutation<Record<string, unknown>, string>('select_collection_by_id', {
|
||||
onSuccess: async (data, variables) => {
|
||||
onSuccess: async data => {
|
||||
if (data === 'ok') {
|
||||
// The actual update to currentCollectionId in collectionsStoreAtom
|
||||
// might be implicitly handled by backend or needs explicit call here
|
||||
// For now, assume backend handles state post 'select_collection_by_id'
|
||||
// and query invalidations refresh the frontend state.
|
||||
// If direct update is needed:
|
||||
// const { selectCollection } = variables as { selectCollection: { collectionId: string } };
|
||||
// updateCurrentCollectionId(selectCollection.collectionId);
|
||||
|
||||
await invoke('build_system_menu')
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['get_collections'],
|
||||
@ -283,58 +265,14 @@ export function useSelectCollectionById() {
|
||||
queryKey: ['get_active_collection_with_menu_items'],
|
||||
})
|
||||
} else {
|
||||
console.log('select collection error', data)
|
||||
console.log('update collection error', data)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const selectCollectionById = (params: {
|
||||
selectCollection: { collectionId: string }
|
||||
}) => {
|
||||
const { collectionId: targetCollectionId } = params.selectCollection // Renamed for clarity in logs
|
||||
const isProtected = protectedCollections.includes(targetCollectionId)
|
||||
const targetCollection = collections.find(c => c.collectionId === targetCollectionId)
|
||||
|
||||
if (isProtected && screenLockPassCode && targetCollection) {
|
||||
console.log(`[Debug] Setting up PIN prompt for collection: ${targetCollectionId}, Title: ${targetCollection.title}`);
|
||||
|
||||
const onConfirmSuccessCallback = async () => { // Make callback async
|
||||
console.log(`[Debug] PIN Confirmed! Attempting to switch to collection via invoke: ${targetCollectionId}`);
|
||||
try {
|
||||
// Instead of directly updating UI state, invoke the backend to select the collection.
|
||||
// The backend will then trigger state updates through query invalidations via the mutation's onSuccess.
|
||||
await invoke('select_collection_by_id', { collectionId: targetCollectionId });
|
||||
console.log(`[Debug] Invoke 'select_collection_by_id' for ${targetCollectionId} successful.`);
|
||||
|
||||
// Explicitly call query invalidations and menu build,
|
||||
// as this path is outside the direct TanStack Mutation onSuccess flow.
|
||||
console.log('[Debug] Manually triggering query invalidations and menu build post-PIN success.');
|
||||
queryClient.invalidateQueries({ queryKey: ['get_collections'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['get_active_collection_with_clips'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['get_active_collection_with_menu_items'] });
|
||||
await invoke('build_system_menu');
|
||||
console.log('[Debug] Explicit query invalidations and menu build complete after PIN.');
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Debug] Error invoking 'select_collection_by_id' for ${targetCollectionId}:`, error);
|
||||
// Handle error appropriately, e.g., show a toast message to the user
|
||||
}
|
||||
}
|
||||
|
||||
setCollectionPinModalProps({
|
||||
title: `Unlock Collection: ${targetCollection.title}`,
|
||||
onConfirmSuccess: onConfirmSuccessCallback,
|
||||
})
|
||||
setIsCollectionPinModalOpen(true)
|
||||
} else {
|
||||
// Not protected or no PIN set, proceed as normal
|
||||
invokeSelectCollectionById(params)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selectCollectionByIdSuccess,
|
||||
selectCollectionById, // This is now our wrapped function
|
||||
selectCollectionById,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
openOnBoardingTourName,
|
||||
openOSXSystemPermissionsModal,
|
||||
openProtectedContentModal,
|
||||
pendingProtectedCollectionId,
|
||||
playerStoreAtom,
|
||||
resetPassCodeNextDelayInSeconds,
|
||||
resetPassCodeNumberOfTried,
|
||||
@ -57,6 +58,7 @@ import ModalLockScreenConfirmationWithPasscodeOrPassword from '~/components/orga
|
||||
import ModalOSXSystemPermissions from '~/components/organisms/modals/system-permissions-osx-modal'
|
||||
import { Box, Button, Flex, Text } from '~/components/ui'
|
||||
|
||||
import { useSelectCollectionById } from '~/hooks/queries/use-collections'
|
||||
import { useClipboardPaste, useCopyPaste } from '~/hooks/use-copypaste'
|
||||
import { useLocalStorage } from '~/hooks/use-localstorage'
|
||||
import { useSignal } from '~/hooks/use-signal'
|
||||
@ -90,6 +92,7 @@ const Container: React.ForwardRefRenderFunction<HTMLDivElement, MainContainerPro
|
||||
const { historyListSimpleBar, clipboardHistory } = useAtomValue(
|
||||
clipboardHistoryStoreAtom
|
||||
)
|
||||
const { selectCollectionById } = useSelectCollectionById()
|
||||
|
||||
const {
|
||||
appToursCompletedList,
|
||||
@ -619,17 +622,23 @@ const Container: React.ForwardRefRenderFunction<HTMLDivElement, MainContainerPro
|
||||
{openProtectedContentModal.value && (
|
||||
<ModalLockScreenConfirmationWithPasscodeOrPassword
|
||||
open={openProtectedContentModal.value}
|
||||
title={'Test Protected Content'}
|
||||
isLockScreen={false} // Important: This makes it cancellable and not the full lock screen
|
||||
showPasscode={true} // We need PIN (passcode) input
|
||||
title={t('Enter PIN to Access Protected Collection', { ns: 'collections' })}
|
||||
isLockScreen={false}
|
||||
showPasscode={true}
|
||||
onConfirmSuccess={() => {
|
||||
openProtectedContentModal.value = false
|
||||
// modalProps.onConfirmSuccess()
|
||||
// setIsOpen(false)
|
||||
// setModalProps(null) // Clear props after success
|
||||
if (pendingProtectedCollectionId.value) {
|
||||
selectCollectionById({
|
||||
selectCollection: {
|
||||
collectionId: pendingProtectedCollectionId.value,
|
||||
},
|
||||
})
|
||||
pendingProtectedCollectionId.value = null
|
||||
}
|
||||
}}
|
||||
onClose={() => {
|
||||
openProtectedContentModal.value = false
|
||||
pendingProtectedCollectionId.value = null
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -17,6 +17,8 @@ import {
|
||||
openAboutPasteBarModal,
|
||||
openContactUsFormModal,
|
||||
openOnBoardingTourName,
|
||||
openProtectedContentModal,
|
||||
pendingProtectedCollectionId,
|
||||
playerStoreAtom,
|
||||
settingsStoreAtom,
|
||||
showInvalidTrackWarningAddSong,
|
||||
@ -44,6 +46,7 @@ import {
|
||||
ExternalLink,
|
||||
FileCog,
|
||||
LibrarySquare,
|
||||
LockKeyhole,
|
||||
Maximize,
|
||||
Minus,
|
||||
Pause,
|
||||
@ -214,6 +217,8 @@ export function NavBar() {
|
||||
setIsSimplifiedLayout,
|
||||
isMainWindowOnTop,
|
||||
setIsMainWindowOnTop,
|
||||
hasPinProtectedCollections,
|
||||
protectedCollections,
|
||||
} = useAtomValue(settingsStoreAtom)
|
||||
|
||||
const {
|
||||
@ -1264,7 +1269,7 @@ export function NavBar() {
|
||||
height: 'auto',
|
||||
maxHeight: '400px',
|
||||
width: '100%',
|
||||
minWidth: '200px',
|
||||
minWidth: '220px',
|
||||
}}
|
||||
autoHide={false}
|
||||
>
|
||||
@ -1291,16 +1296,40 @@ export function NavBar() {
|
||||
value={collectionId}
|
||||
disabled={!isEnabled}
|
||||
onClick={() => {
|
||||
selectCollectionById({
|
||||
selectCollection: {
|
||||
collectionId,
|
||||
},
|
||||
})
|
||||
const isProtectedCollection =
|
||||
hasPinProtectedCollections &&
|
||||
protectedCollections.includes(collectionId)
|
||||
|
||||
if (isProtectedCollection) {
|
||||
pendingProtectedCollectionId.value = collectionId
|
||||
openProtectedContentModal.value = true
|
||||
} else {
|
||||
selectCollectionById({
|
||||
selectCollection: {
|
||||
collectionId,
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className={isSelected ? 'font-semibold' : ''}>
|
||||
{title}
|
||||
</span>
|
||||
<Flex
|
||||
className={`${
|
||||
isSelected ? 'font-semibold' : ''
|
||||
} items-center justify-start gap-2`}
|
||||
>
|
||||
{hasPinProtectedCollections &&
|
||||
protectedCollections.includes(collectionId) ? (
|
||||
<>
|
||||
<span className="truncate max-w-[150px]">{title}</span>
|
||||
<LockKeyhole
|
||||
size={12}
|
||||
className="text-gray-600 dark:text-gray-500 flex-shrink-0"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<span className="truncate max-w-[210px]">{title}</span>
|
||||
)}
|
||||
</Flex>
|
||||
</MenubarRadioItem>
|
||||
))}
|
||||
</MenubarRadioGroup>
|
||||
|
@ -18,9 +18,18 @@ Manage Collections: Manage Collections
|
||||
Mark collections as protected: Mark collections as protected
|
||||
Pin Protected Collections: Pin Protected Collections
|
||||
Protect Collections with PIN: Protect Collections with PIN
|
||||
Protected Collection: Protected Collection
|
||||
Select Collections: Select Collections
|
||||
Select protected collections: Select protected collections
|
||||
Show collection name on the navbar: Show collection name on the navbar
|
||||
Switch collections: Switch collections
|
||||
You can add the selected text to your clips or menu. Please select the option below.: You can add the selected text to your clips or menu. Please select the option below.
|
||||
You need to select a different collection before deleting the current one.: You need to select a different collection before deleting the current one.
|
||||
Enable PIN Protection: Enable PIN Protection
|
||||
Disable PIN Protection: Disable PIN Protection
|
||||
Add To Protected Collections: Add To Protected Collections
|
||||
Remove From Protected Collections: Remove From Protected Collections
|
||||
Confirm Add To Protected Collections: Confirm Add To Protected Collections
|
||||
Confirm Disable PIN Protection: Confirm Disable PIN Protection
|
||||
Confirm Remove From Protected Collections: Confirm Remove From Protected Collections
|
||||
Enter PIN to Access Protected Collection: Enter PIN to Access Protected Collection
|
@ -2,7 +2,8 @@
|
||||
' This permission ensures PasteBar can access the clipboard and perform copy and paste operations across applications.': ' This permission ensures PasteBar can access the clipboard and perform copy and paste operations across applications.'
|
||||
About PasteBar: About PasteBar
|
||||
Action Menu: Action Menu
|
||||
Add <b>{{Clipboard}}</b> field to template. This allows you to copy text to the clipboard, and it will be inserted into the template: Add <b>{{Clipboard}}</b> field to template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||
? Add <b>{{Clipboard}}</b> field to template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||
: Add <b>{{Clipboard}}</b> field to template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||
Add Clip: Add Clip
|
||||
Add First Option: Add First Option
|
||||
Add Link Card: Add Link Card
|
||||
@ -49,11 +50,14 @@ Close History Window: Close History Window
|
||||
Close modal and continue in browser: Close modal and continue in browser
|
||||
Confirm: Confirm
|
||||
Confirm action: Confirm action
|
||||
Confirm Add To Protected Collections: Confirm Add To Protected Collections
|
||||
Confirm Delete: Confirm Delete
|
||||
Confirm Disable PIN Protection: Confirm Disable PIN Protection
|
||||
Confirm Email: Confirm Email
|
||||
Confirm Passcode: Confirm Passcode
|
||||
Confirm Password: Confirm Password
|
||||
Confirm Remove: Confirm Remove
|
||||
Confirm Remove From Protected Collections: Confirm Remove From Protected Collections
|
||||
Confirm Your Passcode: Confirm Your Passcode
|
||||
Confirm passcode reset: Confirm passcode reset
|
||||
Confirm password reset: Confirm password reset
|
||||
@ -96,6 +100,7 @@ Enabled: Enabled
|
||||
Enter Current Passcode: Enter Current Passcode
|
||||
Enter Digits Only Passcode: Enter Digits Only Passcode
|
||||
Enter Email: Enter Email
|
||||
Enter PIN to Access Protected Collection: Enter PIN to Access Protected Collection
|
||||
Enter Passcode: Enter Passcode
|
||||
Enter Password: Enter Password
|
||||
Enter Recovery Password: Enter Recovery Password
|
||||
@ -107,7 +112,8 @@ Errors:
|
||||
Something went wrong! {{err}} Please try again.: Something went wrong! {{err}} Please try again.
|
||||
Expand Edit: Expand Edit
|
||||
Expires on {{proExpiresOn}}: Expires on {{proExpiresOn}}
|
||||
Field <b>{{Clipboard}}</b> has been found in the template. This allows you to copy text to the clipboard, and it will be inserted into the template: Field <b>{{Clipboard}}</b> has been found in the template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||
? Field <b>{{Clipboard}}</b> has been found in the template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||
: Field <b>{{Clipboard}}</b> has been found in the template. This allows you to copy text to the clipboard, and it will be inserted into the template
|
||||
Field is not found in the template: Field is not found in the template
|
||||
Find Clip: Find Clip
|
||||
Find History: Find History
|
||||
@ -172,7 +178,8 @@ Pasted: Pasted
|
||||
Path: Path
|
||||
Pause: Pause
|
||||
Pause Playing: Pause Playing
|
||||
'Permission Check Failed: PasteBar has not been successfully added to Accessibility settings. Please grant the required permissions and click Done again.': 'Permission Check Failed: PasteBar has not been successfully added to Accessibility settings. Please grant the required permissions and click Done again.'
|
||||
? 'Permission Check Failed: PasteBar has not been successfully added to Accessibility settings. Please grant the required permissions and click Done again.'
|
||||
: 'Permission Check Failed: PasteBar has not been successfully added to Accessibility settings. Please grant the required permissions and click Done again.'
|
||||
Pin Selected: Pin Selected
|
||||
Pinned: Pinned
|
||||
Play: Play
|
||||
@ -337,7 +344,8 @@ View Edit: View Edit
|
||||
Views:
|
||||
Paste Menu: Paste Menu
|
||||
We apologize but you found a bug. Please report this issue to us and try again: We apologize but you found a bug. Please report this issue to us and try again
|
||||
We couldn't confirm this file's safety. Failed ID3 tag verification and integrity check. MP3 files can potentially contain malware. Please be cautious.: We couldn't confirm this file's safety. Failed ID3 tag verification and integrity check. MP3 files can potentially contain malware. Please be cautious.
|
||||
? We couldn't confirm this file's safety. Failed ID3 tag verification and integrity check. MP3 files can potentially contain malware. Please be cautious.
|
||||
: We couldn't confirm this file's safety. Failed ID3 tag verification and integrity check. MP3 files can potentially contain malware. Please be cautious.
|
||||
Yes: Yes
|
||||
Yes, activate: Yes, activate
|
||||
chars: chars
|
||||
|
@ -1,15 +1,19 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import createMenuTree from '~/libs/create-menu-tree'
|
||||
import { collectionsStoreAtom, settingsStoreAtom, uiStoreAtom } from '~/store'
|
||||
import { useAtom, useAtomValue } from 'jotai'
|
||||
import {
|
||||
CheckSquare,
|
||||
ChevronDown,
|
||||
ListFilter,
|
||||
LockKeyhole,
|
||||
Trash,
|
||||
Trash2,
|
||||
} from 'lucide-react'
|
||||
ACTION_TYPE_COMFIRMATION_MODAL,
|
||||
actionNameForConfirmModal,
|
||||
actionTypeConfirmed,
|
||||
actionTypeForConfirmModal,
|
||||
collectionsStoreAtom,
|
||||
openActionConfirmModal,
|
||||
openProtectedContentModal,
|
||||
pendingProtectedCollectionId,
|
||||
settingsStoreAtom,
|
||||
uiStoreAtom,
|
||||
} from '~/store'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { CheckSquare, ChevronDown, LockKeyhole, Trash, Trash2 } from 'lucide-react'
|
||||
// Added ChevronDown, ListFilter
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link } from 'react-router-dom'
|
||||
@ -102,6 +106,17 @@ export default function ManageCollectionsSection({
|
||||
const [confirmDeleteCollectionId, setConfirmDeleteCollectionId] = useState<
|
||||
string | null
|
||||
>(null)
|
||||
const [pendingProtectionToggle, setPendingProtectionToggle] = useState(false)
|
||||
const [pendingProtectedCollectionChange, setPendingProtectedCollectionChange] =
|
||||
useState<{
|
||||
collectionId: string
|
||||
checked: boolean
|
||||
} | null>(null)
|
||||
|
||||
// Use action types from constants
|
||||
const ACTION_TOGGLE_PROTECTION = ACTION_TYPE_COMFIRMATION_MODAL.toggleProtection
|
||||
const ACTION_CHANGE_PROTECTED_COLLECTIONS =
|
||||
ACTION_TYPE_COMFIRMATION_MODAL.changeProtectedCollections
|
||||
|
||||
useEffect(() => {
|
||||
if (collectionCardEditId) {
|
||||
@ -123,6 +138,38 @@ export default function ManageCollectionsSection({
|
||||
setData(menuItems.length > 0 ? createMenuTree(menuItems) : [])
|
||||
}, [menuItems, menuItems.length])
|
||||
|
||||
// Handle confirmed actions after PIN verification
|
||||
useEffect(() => {
|
||||
if (actionTypeConfirmed.value === ACTION_TOGGLE_PROTECTION) {
|
||||
setHasPinProtectedCollections(pendingProtectionToggle).then(() => {
|
||||
setPendingProtectionToggle(false)
|
||||
actionTypeConfirmed.value = null
|
||||
})
|
||||
} else if (
|
||||
actionTypeConfirmed.value === ACTION_CHANGE_PROTECTED_COLLECTIONS &&
|
||||
pendingProtectedCollectionChange
|
||||
) {
|
||||
const currentProtectedIds = [...protectedCollections]
|
||||
if (pendingProtectedCollectionChange.checked) {
|
||||
if (
|
||||
!currentProtectedIds.includes(pendingProtectedCollectionChange.collectionId)
|
||||
) {
|
||||
currentProtectedIds.push(pendingProtectedCollectionChange.collectionId)
|
||||
}
|
||||
} else {
|
||||
const index = currentProtectedIds.indexOf(
|
||||
pendingProtectedCollectionChange.collectionId
|
||||
)
|
||||
if (index > -1) {
|
||||
currentProtectedIds.splice(index, 1)
|
||||
}
|
||||
}
|
||||
setProtectedCollections(currentProtectedIds)
|
||||
setPendingProtectedCollectionChange(null)
|
||||
actionTypeConfirmed.value = null
|
||||
}
|
||||
}, [actionTypeConfirmed.value])
|
||||
|
||||
return (
|
||||
<AutoSize disableWidth>
|
||||
{({ height }) => {
|
||||
@ -205,7 +252,7 @@ export default function ManageCollectionsSection({
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Text
|
||||
<Flex
|
||||
className={`${
|
||||
isSelected
|
||||
? isEnabled
|
||||
@ -214,19 +261,45 @@ export default function ManageCollectionsSection({
|
||||
: 'hover:text-slate-500'
|
||||
} !font-medium ${
|
||||
isEnabled ? 'cursor-pointer' : 'text-muted-foreground'
|
||||
}`}
|
||||
} items-center gap-2`}
|
||||
onClick={() => {
|
||||
if (isEnabled && !isSelected) {
|
||||
selectCollectionById({
|
||||
selectCollection: {
|
||||
collectionId,
|
||||
},
|
||||
})
|
||||
// Check if collection is protected
|
||||
const isProtectedCollection =
|
||||
hasPinProtectedCollections &&
|
||||
protectedCollections.includes(collectionId)
|
||||
|
||||
if (isProtectedCollection) {
|
||||
// Store pending collection and show PIN modal
|
||||
pendingProtectedCollectionId.value = collectionId
|
||||
openProtectedContentModal.value = true
|
||||
} else {
|
||||
// Switch directly
|
||||
selectCollectionById({
|
||||
selectCollection: {
|
||||
collectionId,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
<Text className="font-medium truncate">{title}</Text>
|
||||
{hasPinProtectedCollections &&
|
||||
protectedCollections.includes(collectionId) && (
|
||||
<ToolTip
|
||||
text={t('Protected Collection', {
|
||||
ns: 'collections',
|
||||
})}
|
||||
isCompact
|
||||
>
|
||||
<LockKeyhole
|
||||
size={15}
|
||||
className="text-gray-600 dark:text-gray-500 flex-shrink-0"
|
||||
/>
|
||||
</ToolTip>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
</CardTitle>
|
||||
{!isCardEdit && isEnabled && (
|
||||
@ -244,11 +317,21 @@ export default function ManageCollectionsSection({
|
||||
size="xs"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
selectCollectionById({
|
||||
selectCollection: {
|
||||
collectionId,
|
||||
},
|
||||
})
|
||||
// Check if collection is protected
|
||||
const isProtectedCollection =
|
||||
hasPinProtectedCollections &&
|
||||
protectedCollections.includes(collectionId)
|
||||
|
||||
if (isProtectedCollection) {
|
||||
pendingProtectedCollectionId.value = collectionId
|
||||
openProtectedContentModal.value = true
|
||||
} else {
|
||||
selectCollectionById({
|
||||
selectCollection: {
|
||||
collectionId,
|
||||
},
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('Select', { ns: 'common' })}
|
||||
@ -445,7 +528,12 @@ export default function ManageCollectionsSection({
|
||||
</Card>
|
||||
|
||||
{screenLockPassCode && (
|
||||
<Card>
|
||||
<Card
|
||||
className={`${
|
||||
!hasPinProtectedCollections &&
|
||||
'opacity-80 bg-gray-100 dark:bg-gray-900/80'
|
||||
}`}
|
||||
>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-1">
|
||||
<CardTitle className="animate-in fade-in text-md font-medium border-red-300 border-1 w-full">
|
||||
<Flex className="flex flex-row items-center justify-start space-y-0 pb-1 gap-2">
|
||||
@ -454,10 +542,19 @@ export default function ManageCollectionsSection({
|
||||
</Flex>
|
||||
</CardTitle>
|
||||
<Switch
|
||||
checked={isShowCollectionNameOnNavBar}
|
||||
checked={hasPinProtectedCollections}
|
||||
className="ml-auto"
|
||||
onCheckedChange={() => {
|
||||
setHasPinProtectedCollections(!hasPinProtectedCollections)
|
||||
onCheckedChange={checked => {
|
||||
if (hasPinProtectedCollections) {
|
||||
setPendingProtectionToggle(checked)
|
||||
actionNameForConfirmModal.value = checked
|
||||
? t('Enable PIN Protection', { ns: 'collections' })
|
||||
: t('Disable PIN Protection', { ns: 'collections' })
|
||||
actionTypeForConfirmModal.value = ACTION_TOGGLE_PROTECTION
|
||||
openActionConfirmModal.value = true
|
||||
} else {
|
||||
setHasPinProtectedCollections(checked)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</CardHeader>
|
||||
@ -482,28 +579,48 @@ export default function ManageCollectionsSection({
|
||||
{collections.map(collection => (
|
||||
<DropdownMenuCheckboxItem
|
||||
key={collection.collectionId}
|
||||
disabled={!hasPinProtectedCollections}
|
||||
checked={protectedCollections.includes(
|
||||
collection.collectionId
|
||||
)}
|
||||
onCheckedChange={checked => {
|
||||
const currentProtectedIds = [...protectedCollections]
|
||||
if (checked) {
|
||||
if (
|
||||
!currentProtectedIds.includes(
|
||||
// If PIN protection is enabled, require PIN to change protected collections
|
||||
if (hasPinProtectedCollections) {
|
||||
setPendingProtectedCollectionChange({
|
||||
collectionId: collection.collectionId,
|
||||
checked,
|
||||
})
|
||||
actionNameForConfirmModal.value = checked
|
||||
? t('Add To Protected Collections', {
|
||||
ns: 'collections',
|
||||
})
|
||||
: t('Remove From Protected Collections', {
|
||||
ns: 'collections',
|
||||
})
|
||||
actionTypeForConfirmModal.value =
|
||||
ACTION_CHANGE_PROTECTED_COLLECTIONS
|
||||
openActionConfirmModal.value = true
|
||||
} else {
|
||||
// If PIN protection is not enabled, allow changes without PIN
|
||||
const currentProtectedIds = [...protectedCollections]
|
||||
if (checked) {
|
||||
if (
|
||||
!currentProtectedIds.includes(
|
||||
collection.collectionId
|
||||
)
|
||||
) {
|
||||
currentProtectedIds.push(collection.collectionId)
|
||||
}
|
||||
} else {
|
||||
const index = currentProtectedIds.indexOf(
|
||||
collection.collectionId
|
||||
)
|
||||
) {
|
||||
currentProtectedIds.push(collection.collectionId)
|
||||
}
|
||||
} else {
|
||||
const index = currentProtectedIds.indexOf(
|
||||
collection.collectionId
|
||||
)
|
||||
if (index > -1) {
|
||||
currentProtectedIds.splice(index, 1)
|
||||
if (index > -1) {
|
||||
currentProtectedIds.splice(index, 1)
|
||||
}
|
||||
}
|
||||
setProtectedCollections(currentProtectedIds)
|
||||
}
|
||||
setProtectedCollections(currentProtectedIds)
|
||||
}}
|
||||
>
|
||||
{collection.title}
|
||||
@ -512,11 +629,11 @@ export default function ManageCollectionsSection({
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Box className="mt-4">
|
||||
<Text className="text-sm font-medium mb-1">
|
||||
<Text className="text-sm font-medium my-2">
|
||||
{t('Pin Protected Collections', { ns: 'collections' })}:
|
||||
</Text>
|
||||
{protectedCollections.length > 0 ? (
|
||||
<Flex className="flex-wrap gap-2">
|
||||
<Flex className="flex-wrap gap-2 justify-start">
|
||||
{protectedCollections.map(id => {
|
||||
const collection = collections.find(
|
||||
c => c.collectionId === id
|
||||
@ -524,7 +641,7 @@ export default function ManageCollectionsSection({
|
||||
return collection ? (
|
||||
<Badge
|
||||
key={id}
|
||||
variant="secondary"
|
||||
variant="graySecondary"
|
||||
className="font-normal"
|
||||
>
|
||||
{collection.title}
|
||||
|
@ -29,6 +29,8 @@ export const CONTENT_TYPE_LANGUAGE = {
|
||||
export const ACTION_TYPE_COMFIRMATION_MODAL = {
|
||||
resetPassword: 'RESET_PASSWORD',
|
||||
resetPasscode: 'RESET_PASSCODE',
|
||||
toggleProtection: 'TOGGLE_PROTECTION',
|
||||
changeProtectedCollections: 'CHANGE_PROTECTED_COLLECTIONS',
|
||||
} as const
|
||||
|
||||
export const APP_TOURS = {
|
||||
|
@ -214,7 +214,7 @@ export interface SettingsStoreState {
|
||||
setClipTextMinLength: (width: number) => void
|
||||
setClipTextMaxLength: (height: number) => void
|
||||
setProtectedCollections: (ids: string[]) => void
|
||||
setHasPinProtectedCollections: (hasPinProtectedCollections: boolean) => void
|
||||
setHasPinProtectedCollections: (hasPinProtectedCollections: boolean) => Promise<void>
|
||||
}
|
||||
|
||||
const initialState: SettingsStoreState & Settings = {
|
||||
@ -296,7 +296,7 @@ const initialState: SettingsStoreState & Settings = {
|
||||
isKeepStarredOnClearEnabled: false,
|
||||
protectedCollections: [],
|
||||
hasPinProtectedCollections: false,
|
||||
setHasPinProtectedCollections: () => {},
|
||||
setHasPinProtectedCollections: async () => {},
|
||||
CONST: {
|
||||
APP_DETECT_LANGUAGES_SUPPORTED: [],
|
||||
},
|
||||
@ -776,8 +776,8 @@ export const settingsStore = createStore<SettingsStoreState & Settings>()((set,
|
||||
setProtectedCollections: async (ids: string[]) => {
|
||||
return get().updateSetting('protectedCollections', ids.join(','))
|
||||
},
|
||||
setHasPinProtectedCollections: (hasPinProtectedCollections: boolean) => {
|
||||
return set(() => ({ hasPinProtectedCollections }))
|
||||
setHasPinProtectedCollections: async (hasPinProtectedCollections: boolean) => {
|
||||
return get().updateSetting('hasPinProtectedCollections', hasPinProtectedCollections)
|
||||
},
|
||||
isNotTourCompletedOrSkipped: (tourName: string) => {
|
||||
const { appToursCompletedList, appToursSkippedList } = get()
|
||||
|
@ -11,7 +11,7 @@ import { ACTION_TYPE_COMFIRMATION_MODAL, APP_TOURS } from './constants'
|
||||
import { Song, SongSourceType } from './playerStore'
|
||||
|
||||
type ValueOf<T> = T[keyof T]
|
||||
type ActionType = ValueOf<typeof ACTION_TYPE_COMFIRMATION_MODAL>
|
||||
export type ActionType = ValueOf<typeof ACTION_TYPE_COMFIRMATION_MODAL>
|
||||
export type AppTourType = ValueOf<typeof APP_TOURS>
|
||||
|
||||
export const visibilityCopyPopup = signal(false)
|
||||
@ -27,6 +27,7 @@ export const openAboutPasteBarModal = signal(false)
|
||||
export const openContactUsFormModal = signal(false)
|
||||
export const openOnBoardingTourName = signal<AppTourType | null>(null)
|
||||
export const openProtectedContentModal = signal(false)
|
||||
export const pendingProtectedCollectionId = signal<string | null>(null)
|
||||
export const onBoardingTourSingleElements = signal<string | string[] | null>(null)
|
||||
export const openOSXSystemPermissionsModal = signal(false)
|
||||
export const actionNameForConfirmModal = signal<string | null>(null)
|
||||
|
@ -252,10 +252,3 @@ export const uiStore = createStore<UIStoreState>()(
|
||||
)
|
||||
|
||||
export const uiStoreAtom = atomWithStore(uiStore)
|
||||
|
||||
// Atoms for Collection PIN Prompt Modal (using LockScreenConfirmationModal)
|
||||
export const isCollectionPinModalOpenAtom = atom(false)
|
||||
export const collectionPinModalPropsAtom = atom<{
|
||||
title: string
|
||||
onConfirmSuccess: () => void
|
||||
} | null>(null)
|
||||
|
Loading…
x
Reference in New Issue
Block a user