Skip to content
Merged
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,10 @@ VALUES
```

This lot can be added via the Supabase admin, or straight into the `public` schema of the `postgres` DB.

Login for the admin app is just the defaults:

```
DASHBOARD_USERNAME=supabase
DASHBOARD_PASSWORD=this_password_is_insecure_and_should_be_updated
```
13 changes: 13 additions & 0 deletions pages/fl-53/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>app</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/fl-53/main.tsx"></script>
</body>
</html>
141 changes: 141 additions & 0 deletions src/fl-53/ImageManager.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { type FormEvent, useState, Fragment } from 'react'
import { supabaseClient } from '../lib/supabase.ts'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'

export default function ImageManager() {
const queryClient = useQueryClient()
const [selectedFile, setSelectedFile] = useState<File | null>(null)

const uploadMutation = useMutation({
mutationFn: uploadFile,
onSuccess: () => {
console.log('File uploaded successfully')
void queryClient.invalidateQueries({ queryKey: ['ALL_IMAGES'] })
},
onError: (error) => {
console.error('Error uploading file:', error)
},
})

const deleteMutation = useMutation({
mutationFn: deleteImages,
onSuccess: () => {
console.log('All files deleted successfully')
void queryClient.invalidateQueries({ queryKey: ['ALL_IMAGES'] })
},
onError: (error) => {
console.error('Error deleting files:', error)
},
})

const imageUrls = useQuery<string[]>({
queryKey: ['ALL_IMAGES'],
queryFn: fetchImageUrls,
})

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelectedFile(event.target.files?.[0] || null)
}

const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
if (selectedFile === null) {
return console.log("Ain't no file selected")
}

uploadMutation.mutate(selectedFile)
event.currentTarget.reset()
}

const handleDelete = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault()
deleteMutation.mutate()
}

return (
<div>
<form onSubmit={handleSubmit}>
<input
type="file"
name="file"
accept="image/png"
onChange={handleChange}
/>
<button type="submit">Upload Image</button>
<br />
<button onClick={handleDelete}>Delete all</button>
</form>
<hr />
{imageUrls.data === undefined
? 'nothing'
: imageUrls.data.map((imageUrl: string) => (
<Fragment key={imageUrl}>
<img
src={imageUrl}
alt={getFilenameFromUrl(imageUrl) || 'Uploaded image'}
width="200px"
/>
<br />
{getFilenameFromUrl(imageUrl)}
<hr />
</Fragment>
))}
</div>
)
}

async function uploadFile(file: File) {
const { data, error } = await supabaseClient.storage
.from('images')
.upload(`private/${file.name}`, file)
if (error === null) {
return Promise.resolve(data)
}

return Promise.reject(error)
}

async function fetchImageUrls(): Promise<string[]> {
const { data, error } = await supabaseClient.storage
.from('images')
.list('private/')
if (error !== null) {
return Promise.reject(error)
}

const urls = data
.filter((file) => file.name.endsWith('.png'))
.map(async (file) => {
const { data, error } = await supabaseClient.storage
.from('images')
.createSignedUrl(`private/${file.name}`, 60)
if (error !== null) {
console.error(`Failed to create signed URL for ${file.name}:`, error)
throw error
}
return data === null ? '' : data.signedUrl
})
return Promise.all(urls)
}

async function deleteImages() {
const { data: files, error: listError } = await supabaseClient.storage
.from('images')
.list('private/')
if (listError !== null || files === null) {
return Promise.reject(listError || new Error('Failed to list files'))
}
const paths = files.map((file) => `private/${file.name}`)

const { data, error } = await supabaseClient.storage
.from('images')
.remove(paths)
if (error !== null) {
return Promise.reject(error)
}
return Promise.resolve(data)
}

function getFilenameFromUrl(url: string) {
return url.split('?')[0].split('/').pop()
}
17 changes: 17 additions & 0 deletions src/fl-53/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import ImageManager from './ImageManager.tsx'
import ErrorBoundary from '../lib/ErrorBoundary.tsx'

const queryClient = new QueryClient()

createRoot(document.getElementById('root')!).render(
<StrictMode>
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<ImageManager />
</QueryClientProvider>
</ErrorBoundary>
</StrictMode>
)
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import ErrorBoundary from './profiles/lib/ErrorBoundary.tsx'
import ErrorBoundary from './lib/ErrorBoundary.tsx'
import Home from './profiles/home/Home.tsx'
import LoginForm from './profiles/login/LoginForm.tsx'
import Gallery from './profiles/gallery/Gallery.tsx'
Expand Down
2 changes: 1 addition & 1 deletion src/profiles/add/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StatusCodes } from 'http-status-codes'
import { useNavigate } from 'react-router-dom'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { type Mugshot, type UnsavedMugshot } from '../mugshot.tsx'
import { supabaseClient } from '../lib/supabase.ts'
import { supabaseClient } from '../../lib/supabase.ts'

import './styles.css'

Expand Down
2 changes: 1 addition & 1 deletion src/profiles/gallery/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type ReactNode } from 'react'
import { useQuery } from '@tanstack/react-query'
import './gallery.css'
import { type Mugshot } from '../mugshot.tsx'
import { supabaseClient } from '../lib/supabase.ts'
import { supabaseClient } from '../../lib/supabase.ts'
import { StatusCodes } from 'http-status-codes'

function Profile({ src, alt }: { src: string; alt: string }) {
Expand Down
4 changes: 2 additions & 2 deletions src/profiles/login/LoginForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { type ChangeEvent, type FormEvent, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useMutation } from '@tanstack/react-query'
import { supabaseClient } from '../lib/supabase.ts'
import { loginCredentials } from '../lib/testCredentials.ts'
import { supabaseClient } from '../../lib/supabase.ts'
import { loginCredentials } from '../../lib/testCredentials.ts'

type User = { email: string; password: string }
export default function LoginForm() {
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/profiles/gallery.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { render, screen, waitFor } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { supabaseClient } from '@/profiles/lib/supabase.ts'
import { loginCredentials } from '@/profiles/lib/testCredentials'
import { supabaseClient } from '@/lib/supabase'
import { loginCredentials } from '@/lib/testCredentials'
import Gallery from '@/profiles/gallery/Gallery.tsx'

describe('Testing Gallery component with live data', () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/profiles/add/form.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { StatusCodes } from 'http-status-codes'
import { MemoryRouter } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { supabaseClient } from '@/profiles/lib/supabase.ts'
import { supabaseClient } from '@/lib/supabase'
import Form from '@/profiles/add/Form.tsx'

const mockedUsedNavigate = vi.fn()
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/profiles/gallery/gallery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react'
import { describe, it, expect, vi, afterEach } from 'vitest'
import { StatusCodes } from 'http-status-codes'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { supabaseClient } from '@/profiles/lib/supabase.ts'
import { supabaseClient } from '@/lib/supabase'
import { type UserResponse } from '@supabase/supabase-js'
import Gallery from '@/profiles/gallery/Gallery.tsx'

Expand Down
1 change: 1 addition & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default defineConfig({
__dirname,
'pages/fl-51/baseline/index.html'
),
fl53_image_manager: resolve(__dirname, 'pages/fl-53/index.html'),
},
},
},
Expand Down