Initial commit

This commit is contained in:
jjbrunton committed 2025-12-27 19:10:11 +00:00
commit 4d53d83769
73 files changed
+37728

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
COPY turbo.json ./
COPY apps/admin/package.json ./apps/admin/
COPY packages/shared/package.json ./packages/shared/
# Install dependencies (only what's needed for admin)
RUN npm install
# Copy source
COPY . .
# Build shared package first
RUN npx turbo run build --filter=@sauci/shared
# Build admin app with env vars
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_ANON_KEY
ENV VITE_SUPABASE_URL=$VITE_SUPABASE_URL
ENV VITE_SUPABASE_ANON_KEY=$VITE_SUPABASE_ANON_KEY
RUN npx turbo run build --filter=@sauci/admin
FROM nginx:alpine
COPY --from=builder /app/apps/admin/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+13
View File
@@ -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>Sauci Admin</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+25
View File
@@ -0,0 +1,25 @@
{
"name": "@sauci/admin",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@supabase/supabase-js": "^2.39.0",
"ra-supabase": "^1.0.0",
"@sauci/shared": "*",
"react": "^18.2.0",
"react-admin": "^4.16.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@vitejs/plugin-react": "^4.2.0",
"typescript": "^5.3.0",
"vite": "^5.0.0"
}
}
+35
View File
@@ -0,0 +1,35 @@
import { Admin, Resource } from "react-admin";
import { dataProvider } from "./providers/supabaseProvider";
import { PackList, PackEdit, PackCreate } from "./resources/packs";
import { QuestionList, QuestionEdit, QuestionCreate } from "./resources/questions";
import { ProfileList, ResponseList } from "./resources/users";
import { authProvider } from "./providers/authProvider";
const App = () => (
<Admin dataProvider={dataProvider} authProvider={authProvider} requireAuth>
<Resource
name="question_packs"
list={PackList}
edit={PackEdit}
create={PackCreate}
recordRepresentation="name"
/>
<Resource
name="questions"
list={QuestionList}
edit={QuestionEdit}
create={QuestionCreate}
/>
<Resource
name="profiles"
list={ProfileList}
/>
<Resource
name="responses"
list={ResponseList}
/>
<Resource name="couples" />
</Admin>
);
export default App;
+4
View File
@@ -0,0 +1,4 @@
export const supabaseConfig = {
url: import.meta.env.VITE_SUPABASE_URL,
anonKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
};
+9
View File
@@ -0,0 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
+30
View File
@@ -0,0 +1,30 @@
import { AuthProvider } from 'react-admin';
import { supabase } from '../config';
export const authProvider: AuthProvider = {
login: async ({ username, password }) => {
const { data, error } = await supabase.auth.signInWithPassword({
email: username,
password,
});
if (error) {
throw new Error(error.message);
}
return Promise.resolve();
},
logout: async () => {
await supabase.auth.signOut();
return Promise.resolve();
},
checkError: async (error) => {
if (error.status === 401 || error.status === 403) {
return Promise.reject();
}
return Promise.resolve();
},
checkAuth: async () => {
const session = await supabase.auth.getSession();
return session.data.session ? Promise.resolve() : Promise.reject();
},
getPermissions: () => Promise.resolve(),
};
+22
View File
@@ -0,0 +1,22 @@
import { createClient } from '@supabase/supabase-js';
import { supabaseConfig } from '../config';
export const supabase = createClient(
supabaseConfig.url,
supabaseConfig.anonKey
);
// Custom data provider wrapper if needed for ra-data-supabase
import { supabaseDataProvider } from 'ra-supabase';
export const dataProvider = supabaseDataProvider(
supabase,
{
resources: {
question_packs: 'question_packs',
questions: 'questions',
profiles: 'profiles',
couples: 'couples'
}
}
);
+39
View File
@@ -0,0 +1,39 @@
import { List, Datagrid, TextField, BooleanField, EditButton, Create, SimpleForm, TextInput, BooleanInput, Edit } from 'react-admin';
export const PackList = () => (
<List>
<Datagrid rowClick="edit">
<TextField source="name" />
<TextField source="description" />
<BooleanField source="is_premium" />
<BooleanField source="is_public" />
<EditButton />
</Datagrid>
</List>
);
export const PackEdit = () => (
<Edit>
<SimpleForm>
<TextInput source="name" />
<TextInput source="description" multiline />
<TextInput source="icon" />
<BooleanInput source="is_premium" />
<BooleanInput source="is_public" />
<TextInput source="sort_order" />
</SimpleForm>
</Edit>
);
export const PackCreate = () => (
<Create>
<SimpleForm>
<TextInput source="name" />
<TextInput source="description" multiline />
<TextInput source="icon" />
<BooleanInput source="is_premium" />
<BooleanInput source="is_public" />
<TextInput source="sort_order" />
</SimpleForm>
</Create>
);
+38
View File
@@ -0,0 +1,38 @@
import { List, Datagrid, TextField, ReferenceField, NumberField, EditButton, Create, SimpleForm, TextInput, NumberInput, ReferenceInput, SelectInput, Edit } from 'react-admin';
export const QuestionList = () => (
<List>
<Datagrid rowClick="edit">
<TextField source="text" />
<ReferenceField source="pack_id" reference="question_packs">
<TextField source="name" />
</ReferenceField>
<NumberField source="intensity" />
<EditButton />
</Datagrid>
</List>
);
export const QuestionEdit = () => (
<Edit>
<SimpleForm>
<ReferenceInput source="pack_id" reference="question_packs">
<SelectInput optionText="name" />
</ReferenceInput>
<TextInput source="text" multiline fullWidth />
<NumberInput source="intensity" min={1} max={5} />
</SimpleForm>
</Edit>
);
export const QuestionCreate = () => (
<Create>
<SimpleForm>
<ReferenceInput source="pack_id" reference="question_packs">
<SelectInput optionText="name" />
</ReferenceInput>
<TextInput source="text" multiline fullWidth />
<NumberInput source="intensity" min={1} max={5} />
</SimpleForm>
</Create>
);
+30
View File
@@ -0,0 +1,30 @@
import { List, Datagrid, TextField, BooleanField, DateField, ReferenceField } from 'react-admin';
export const ProfileList = () => (
<List>
<Datagrid>
<TextField source="name" />
<TextField source="email" />
<BooleanField source="is_premium" />
<ReferenceField source="couple_id" reference="couples">
<TextField source="id" />
</ReferenceField>
<DateField source="created_at" />
</Datagrid>
</List>
);
export const ResponseList = () => (
<List>
<Datagrid>
<ReferenceField source="user_id" reference="profiles">
<TextField source="name" />
</ReferenceField>
<ReferenceField source="question_id" reference="questions">
<TextField source="text" />
</ReferenceField>
<TextField source="answer" />
<DateField source="created_at" />
</Datagrid>
</List>
);
+10
View File
@@ -0,0 +1,10 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
host: true,
port: 3001,
},
});