Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions backend/app/routes/folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
AddFolderRequest,
AddFolderResponse,
AddFolderData,
CheckFolderRequest,
CheckFolderEmptyResponse,
CheckFolderEmptyData,
ErrorResponse,
UpdateAITaggingRequest,
UpdateAITaggingResponse,
Expand Down Expand Up @@ -476,3 +479,26 @@ def get_all_folders():
message=f"Unable to retrieve folders: {str(e)}",
).model_dump(),
)

@router.post("/check-empty")
def check_folder_empty(request: CheckFolderRequest):
if not os.path.exists(request.folder_path):
raise HTTPException(status_code=404, detail="Folder not found")

if not os.path.isdir(request.folder_path):
raise HTTPException(status_code=400, detail="Path is not a directory")

try:
# Efficiently check if directory yields any entries (files or subdirs)
with os.scandir(request.folder_path) as it:
is_empty = not any(it)

return CheckFolderEmptyResponse(
success=True,
data=CheckFolderEmptyData(is_empty=is_empty),
message="Folder emptiness checked successfully"
)
except PermissionError:
raise HTTPException(status_code=403, detail="Permission denied accessing folder")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
11 changes: 10 additions & 1 deletion backend/app/schemas/folders.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ class SyncFolderRequest(BaseModel):
folder_path: str # Path of the folder to sync
folder_id: str # UUID of the folder to sync

class CheckFolderRequest(BaseModel):
folder_path: str

# Response Data Models (for the 'data' field)
class FolderDetails(BaseModel):
Expand Down Expand Up @@ -60,6 +62,8 @@ class SyncFolderData(BaseModel):
folder_id: str
folder_path: str

class CheckFolderEmptyData(BaseModel):
is_empty: bool

# Response Models
class GetAllFoldersResponse(BaseModel):
Expand All @@ -82,7 +86,6 @@ class UpdateAITaggingResponse(BaseModel):
error: Optional[str] = None
data: Optional[UpdateAITaggingData] = None


class DeleteFoldersResponse(BaseModel):
success: bool
message: Optional[str] = None
Expand All @@ -101,3 +104,9 @@ class ErrorResponse(BaseModel):
success: bool = False
message: Optional[str] = None
error: Optional[str] = None

class CheckFolderEmptyResponse(BaseModel):
success: bool
message: Optional[str] = None
error: Optional[str] = None
data: Optional[CheckFolderEmptyData] = None
12 changes: 12 additions & 0 deletions frontend/src/api/api-functions/folders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export interface SyncFolderRequest {
folder_id: string;
}

export interface CheckFolderEmptyRequest {
folder_path: string;
}

// API Functions
export const getAllFolders = async (): Promise<APIResponse> => {
const response = await apiClient.get<APIResponse>(
Expand All @@ -41,6 +45,14 @@ export const addFolder = async (
return response.data;
};

export const checkFolderEmpty = async (request: CheckFolderEmptyRequest): Promise<APIResponse> => {
const response = await apiClient.post<APIResponse>(
foldersEndpoints.checkEmpty,
request,
);
return response.data;
};

export const enableAITagging = async (
request: UpdateAITaggingRequest,
): Promise<APIResponse> => {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/api/apiEndpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const faceClustersEndpoints = {
export const foldersEndpoints = {
getAllFolders: '/folders/all-folders',
addFolder: '/folders/add-folder',
checkEmpty: '/folders/check-empty',
enableAITagging: '/folders/enable-ai-tagging',
disableAITagging: '/folders/disable-ai-tagging',
deleteFolders: '/folders/delete-folders',
Expand Down
52 changes: 48 additions & 4 deletions frontend/src/components/OnboardingSteps/FolderSetupStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { markCompleted, previousStep } from '@/features/onboardingSlice';
import { AppFeatures } from '@/components/OnboardingSteps/AppFeatures';
import { useFolder } from '@/hooks/useFolder';
import { useEffect, useState } from 'react';
import { setIsEditing } from '@/features/onboardingSlice';
import { Alert, AlertTitle } from '../ui/alert';
import { checkFolderEmpty } from '@/api/api-functions/folders';

interface FolderSetupStepProps {
stepIndex: number;
totalSteps: number;
Expand All @@ -29,8 +31,18 @@ export function FolderSetupStep({
}: FolderSetupStepProps) {
const dispatch = useDispatch<AppDispatch>();

// Local state for folders
const [folder, setFolder] = useState<string>('');
const [folder, setFolder] = useState<string>(''); // Local state for folders
const [error, setError] = useState<string>('');

useEffect(() => {
if (error) {
const timer = setTimeout(() => {
setError('');
}, 4000);
return () => clearTimeout(timer);
}
}, [error]);

const isEditing = useSelector(
(state: RootState) => state.onboarding.isEditing,
);
Expand All @@ -48,7 +60,27 @@ export function FolderSetupStep({
const handleSelectFolders = async () => {
const selectedFolder = await pickSingleFolder();
if (selectedFolder) {
setFolder(selectedFolder);
try {
const data = await checkFolderEmpty({ folder_path: selectedFolder });
console.log(data);

if (!data.success) {
setError('Error checking folder contents. Please try again.');
return;
}

if (data.data && data.data.is_empty) {
setError('Selected folder is empty. Please select a non-empty folder.');
return;
}

setFolder(selectedFolder);
setError('');
}
catch (err) {
console.error(err);
setError('Unable to verify folder. Please try again.');
}
}
};

Expand All @@ -57,6 +89,10 @@ export function FolderSetupStep({
};

const handleNext = () => {
if (!folder || folder.trim() === '') {
setError('Please select a folder to proceed');
return
}
localStorage.setItem('folderChosen', 'true');
addFolderMutate(folder);
dispatch(markCompleted(stepIndex));
Expand All @@ -77,6 +113,14 @@ export function FolderSetupStep({
return (
<>
<Card className="flex max-h-full w-1/2 flex-col border p-4">
{error ? (
<div className="absolute top-4 left-1/2 transform -translate-x-1/2 z-50 px-4 animate-in fade-in slide-in-from-top-2 duration-300">
<Alert variant={'destructive'}>
<AlertTitle>{error}</AlertTitle>
</Alert>
</div>
) : null}

<CardHeader className="p-3">
<div className="text-muted-foreground mb-1 flex justify-between text-xs">
<span>
Expand Down
Loading