Skip to content

Commit 467c0e4

Browse files
committed
feat: client only
1 parent af12ed1 commit 467c0e4

File tree

19 files changed

+356
-518
lines changed

19 files changed

+356
-518
lines changed

.github/workflows/test.yaml

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ on:
77
branches:
88
- main
99
jobs:
10-
lint:
11-
name: Lint Go
10+
lint-test-go:
11+
name: Lint and Test Go
1212
runs-on: ubuntu-latest
1313
steps:
1414
- name: Install Go
@@ -24,18 +24,6 @@ jobs:
2424
with:
2525
working-directory: ./api
2626

27-
test:
28-
name: Test Go
29-
runs-on: ubuntu-latest
30-
steps:
31-
- name: Install Go
32-
uses: actions/setup-go@v5
33-
with:
34-
go-version: 1.24
35-
36-
- name: Check out code
37-
uses: actions/checkout@v4
38-
3927
- name: Run Tests
4028
working-directory: ./api
4129
run: go test -v -bench=. -race ./...
@@ -45,7 +33,7 @@ jobs:
4533
runs-on: ubuntu-latest
4634
strategy:
4735
matrix:
48-
node_version: [20]
36+
node_version: [22]
4937

5038
steps:
5139
- name: Check out code

README.md

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -268,37 +268,24 @@ Where `{SURVEY_ID}` id the UUID of a given survey.
268268

269269
## Installation & Deployment
270270

271-
You can build and run both API and UI with Docker Compose:
271+
### API and Postgres with Docker Compose
272+
273+
You can build and run both API and Postgres with Docker Compose:
272274

273275
```
274276
docker-compose up -d --build
275277
```
276278

277-
And you should be able to access the UI on [localhost:3000](http://localhost:3000)
278-
279-
You can deploy individual services to any cloud provider or self host them.
280-
281-
- Go backend
282-
- Next.js frontend
283-
- Postgres database
284-
285-
### Environment Variables
279+
Environment variables:
286280

287281
API:
288282

289283
- `DATABASE_URL` - Postgres connection string
290284
- `SURVEYS_DIR` - Directory with surveys, e.g. `/root/surveys`. It's suggested to use mounted volume for this directory.
291285
- `UPLOADS_DIR` - Directory for uploading files from the survey forms.
292286

293-
UI:
294-
295-
- `CONSOLE_API_ADDR` - Public address of the Go backend. Need to be accessible from the browser.
296-
- `CONSOLE_API_ADDR_INTERNAL` - Internal address of the Go backend, e.g. `http://api:8080` (could be the same as `CONSOLE_API_ADDR`).
297-
298287
### Run UI with npm
299288

300-
It's also possible to run UI using `npm`:
301-
302289
```
303290
cd ui
304291
npm install
@@ -307,13 +294,12 @@ npm run dev
307294
308295
### Run API locally
309296
310-
Assuming you have Postgres running locally, you can run the API with:
297+
Assuming you have Postgres running locally (`docker-compose up -d postgres`), you can run the API with:
311298
312299
```
313300
cd api
314301
export DATABASE_URL="postgres://user:pass@localhost:5432/formulosity?sslmode=disable"
315302
export SURVEYS_DIR="./surveys"
316-
export UPLOADS_DIR="./uploads"
317303
go run main.go
318304
```
319305

compose.yaml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,6 @@ services:
1414
volumes:
1515
- ./api/surveys:/root/surveys
1616
- ./api/uploads:/root/uploads
17-
ui:
18-
restart: always
19-
build:
20-
context: ./ui
21-
ports:
22-
- "3000:3000"
23-
environment:
24-
- CONSOLE_API_ADDR_INTERNAL=http://api:8080
25-
- CONSOLE_API_ADDR=http://localhost:9900
26-
depends_on:
27-
- api
2817
postgres:
2918
image: postgres:16.0-alpine
3019
restart: always

ui/.env.example

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
CONSOLE_API_ADDR=http://localhost:9900
2-
CONSOLE_API_ADDR_INTERNAL=http://localhost:9900
1+
NEXT_PUBLIC_API_ADDR=http://localhost:9900
32
IRON_SESSION_SECRET=not_very_secret_replace_me
43
HTTP_BASIC_AUTH=user:pass
54

ui/Dockerfile

Lines changed: 0 additions & 41 deletions
This file was deleted.

ui/src/app/app/page.tsx

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
1-
import { Metadata } from 'next'
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
24

35
import AppLayout from 'components/app/AppLayout'
46
import { SurveysPage } from 'components/app/SurveysPage'
57
import { ErrCode } from 'components/ui/ErrCode'
68
import { getSurveys } from 'lib/api'
9+
import { Survey } from 'lib/types'
710

8-
export const metadata: Metadata = {
9-
title: 'Formulosity',
10-
}
11+
export default function AppPage() {
12+
const [surveys, setSurveys] = useState<Survey[]>([])
13+
const [errMsg, setErrMsg] = useState('')
14+
const [loading, setLoading] = useState(true)
1115

12-
export default async function AppPage() {
13-
let errMsg = ''
16+
useEffect(() => {
17+
const fetchSurveys = async () => {
18+
const surveysResp = await getSurveys()
19+
if (surveysResp.error) {
20+
setErrMsg('Failed to fetch surveys')
21+
} else {
22+
setSurveys(surveysResp.data.data)
23+
}
24+
setLoading(false)
25+
}
26+
fetchSurveys()
27+
}, [])
1428

15-
let surveys = []
16-
const surveysResp = await getSurveys()
17-
if (surveysResp.error) {
18-
errMsg = 'Failed to fetch surveys'
19-
} else {
20-
surveys = surveysResp.data.data
29+
if (loading) {
30+
return (
31+
<AppLayout>
32+
<div>Loading...</div>
33+
</AppLayout>
34+
)
2135
}
2236

2337
if (errMsg) {
@@ -28,11 +42,9 @@ export default async function AppPage() {
2842
)
2943
}
3044

31-
const apiURL = process.env.CONSOLE_API_ADDR || ''
32-
3345
return (
3446
<AppLayout>
35-
<SurveysPage surveys={surveys} apiURL={apiURL} />
47+
<SurveysPage surveys={surveys} />
3648
</AppLayout>
3749
)
3850
}
Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,70 @@
1-
import { Metadata } from 'next'
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
4+
import { useParams } from 'next/navigation'
25

36
import AppLayout from 'components/app/AppLayout'
47
import { ErrCode } from 'components/ui/ErrCode'
58
import { getSurveys, getSurveySessions } from 'lib/api'
69
import { Survey, SurveySessionsLimit } from 'lib/types'
710
import { SurveyResponsesPage } from 'components/app/SurveyResponsesPage'
811

9-
export const metadata: Metadata = {
10-
title: 'Survey Responses',
11-
}
12+
export default function ResponsesPage() {
13+
const params = useParams()
14+
const [currentSurvey, setCurrentSurvey] = useState<Survey | undefined>(
15+
undefined
16+
)
17+
const [errMsg, setErrMsg] = useState('')
18+
const [loading, setLoading] = useState(true)
1219

13-
export default async function ResponsesPage({
14-
params,
15-
}: {
16-
params: { survey_uuid: string }
17-
}) {
18-
let errMsg = ''
20+
useEffect(() => {
21+
const fetchData = async () => {
22+
if (!params.survey_uuid) return
1923

20-
let currentSurvey = undefined
21-
const surveysResp = await getSurveys()
22-
if (surveysResp.error) {
23-
errMsg = 'Unable to fetch surveys'
24-
} else {
25-
const surveys = surveysResp.data.data
26-
const survey = surveys.find(
27-
(survey: Survey) => survey.uuid === params.survey_uuid
28-
)
29-
if (!survey) {
30-
errMsg = 'Survey not found'
31-
} else {
32-
currentSurvey = survey
24+
const surveysResp = await getSurveys()
25+
if (surveysResp.error) {
26+
setErrMsg('Unable to fetch surveys')
27+
setLoading(false)
28+
return
29+
}
30+
31+
const surveys = surveysResp.data.data
32+
const survey = surveys.find(
33+
(survey: Survey) => survey.uuid === params.survey_uuid
34+
)
35+
36+
if (!survey) {
37+
setErrMsg('Survey not found')
38+
setLoading(false)
39+
return
40+
}
3341

3442
const surveySessionsResp = await getSurveySessions(
35-
currentSurvey.uuid,
36-
`limit=${SurveySessionsLimit}&offset=0&sort_by=created_at&order=desc`,
37-
''
43+
survey.uuid,
44+
`limit=${SurveySessionsLimit}&offset=0&sort_by=created_at&order=desc`
3845
)
46+
3947
if (surveySessionsResp.error) {
40-
errMsg = 'Unable to fetch survey sessions'
48+
setErrMsg('Unable to fetch survey sessions')
4149
} else {
42-
currentSurvey = surveySessionsResp.data.data.survey
43-
currentSurvey.sessions = surveySessionsResp.data.data.sessions
44-
currentSurvey.pages_count = surveySessionsResp.data.data.pages_count
50+
const updatedSurvey = surveySessionsResp.data.data.survey
51+
updatedSurvey.sessions = surveySessionsResp.data.data.sessions
52+
updatedSurvey.pages_count = surveySessionsResp.data.data.pages_count
53+
setCurrentSurvey(updatedSurvey)
4554
}
55+
56+
setLoading(false)
4657
}
58+
59+
fetchData()
60+
}, [params.survey_uuid])
61+
62+
if (loading) {
63+
return (
64+
<AppLayout>
65+
<div>Loading...</div>
66+
</AppLayout>
67+
)
4768
}
4869

4970
if (errMsg) {
@@ -54,11 +75,9 @@ export default async function ResponsesPage({
5475
)
5576
}
5677

57-
const apiURL = process.env.CONSOLE_API_ADDR || ''
58-
5978
return (
6079
<AppLayout>
61-
<SurveyResponsesPage currentSurvey={currentSurvey} apiURL={apiURL} />
80+
<SurveyResponsesPage currentSurvey={currentSurvey!} />
6281
</AppLayout>
6382
)
6483
}

ui/src/app/layout.tsx

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,11 @@
1+
'use client'
2+
13
import 'styles/global.css'
24
import { ReactNode } from 'react'
3-
import { Metadata } from 'next'
4-
import { siteConfig } from 'lib/siteConfig'
5-
6-
export const metadata: Metadata = {
7-
title: {
8-
default: siteConfig.name,
9-
template: `%s | ${siteConfig.name}`,
10-
},
11-
description: siteConfig.description,
12-
keywords: [],
13-
alternates: {
14-
canonical: '/',
15-
},
16-
openGraph: {
17-
type: 'website',
18-
locale: 'en_US',
19-
title: siteConfig.name,
20-
description: siteConfig.description,
21-
siteName: siteConfig.name,
22-
},
23-
icons: {
24-
icon: '/favicon.ico',
25-
},
26-
manifest: '/manifest.webmanifest',
27-
}
285

296
type LayoutProps = { children?: ReactNode }
307

31-
export default async function RootLayout({ children }: LayoutProps) {
8+
export default function RootLayout({ children }: LayoutProps) {
329
return (
3310
<html lang="en" className="dark" suppressHydrationWarning>
3411
<head />

0 commit comments

Comments
 (0)