Skip to content

Commit de8484a

Browse files
committed
landingsside
1 parent e6c9654 commit de8484a

File tree

7 files changed

+144
-1
lines changed

7 files changed

+144
-1
lines changed

core/src/main/kotlin/no/javazone/feedback/pages/FeedbackPage.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,52 @@ package no.javazone.feedback.pages
33
import kotlinx.html.*
44
import no.javazone.feedback.domain.FeedbackChannel
55

6+
fun HTML.landingPage(channels: List<FeedbackChannel>) {
7+
head {
8+
meta { charset = "utf-8" }
9+
meta {
10+
name = "viewport"
11+
content = "width=device-width, initial-scale=1"
12+
}
13+
title { +"Feedback Channels" }
14+
link {
15+
rel = "stylesheet"
16+
href = "/static/css/feedback.css"
17+
}
18+
}
19+
body {
20+
main("landing") {
21+
div("landing-header") {
22+
h1 { +"Feedback Channels" }
23+
p { +"Scan a QR code or tap a card to leave feedback for a session." }
24+
}
25+
if (channels.isEmpty()) {
26+
div("card empty-state") {
27+
p { +"No feedback channels have been created yet." }
28+
}
29+
} else {
30+
div("channel-grid") {
31+
channels.forEach { channel ->
32+
a(href = "/session/${channel.externalId}", classes = "channel-card card") {
33+
img(alt = "QR code for ${channel.title}") {
34+
src = "/v1/feedback/channel/${channel.externalId}/qrcode"
35+
width = "200"
36+
height = "200"
37+
}
38+
div("channel-info") {
39+
h2 { +channel.title }
40+
p("speakers") {
41+
+channel.speakers.joinToString(", ")
42+
}
43+
}
44+
}
45+
}
46+
}
47+
}
48+
}
49+
}
50+
}
51+
652
fun HTML.feedbackPage(channel: FeedbackChannel) {
753
head {
854
meta { charset = "utf-8" }

core/src/main/kotlin/no/javazone/feedback/setupRouting.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import no.javazone.feedback.domain.adapters.FeedbackAdapter
1818
import no.javazone.feedback.domain.errors.ChannelNotFoundError
1919
import no.javazone.feedback.domain.generators.ExternalIdGeneratorDefault
2020
import no.javazone.feedback.pages.feedbackPage
21+
import no.javazone.feedback.pages.landingPage
2122
import no.javazone.feedback.pages.thankYouFragment
2223
import no.javazone.feedback.qrcode.QRCodeGenerator
2324
import no.javazone.feedback.request.channel.FeedbackChannelCreationDTO
@@ -38,6 +39,11 @@ fun Application.setupRouting() {
3839
staticResources("/static", "static")
3940
staticResources("/", "static")
4041

42+
get("/") {
43+
val channels = feedbackAdapter.findAllChannels()
44+
call.respondHtml { landingPage(channels) }
45+
}
46+
4147
get("/health") {
4248
if (isDatabaseHealthy()) {
4349
call.respond(HttpStatusCode.OK, mapOf("status" to "ok"))

core/src/main/resources/static/css/feedback.css

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,71 @@ button[type="submit"]:disabled {
184184
.thank-you p {
185185
color: #666;
186186
}
187+
188+
/* Landing page */
189+
190+
.landing {
191+
max-width: 960px;
192+
}
193+
194+
.landing-header {
195+
margin-bottom: 2rem;
196+
}
197+
198+
.landing-header h1 {
199+
font-size: 2rem;
200+
margin-bottom: 0.25rem;
201+
}
202+
203+
.landing-header p {
204+
color: #666;
205+
font-size: 1rem;
206+
}
207+
208+
.channel-grid {
209+
display: grid;
210+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
211+
gap: 1.5rem;
212+
}
213+
214+
.channel-card {
215+
display: flex;
216+
flex-direction: column;
217+
align-items: center;
218+
text-decoration: none;
219+
color: inherit;
220+
padding: 1.5rem;
221+
transition: box-shadow 0.2s, transform 0.2s;
222+
}
223+
224+
.channel-card:hover {
225+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
226+
transform: translateY(-2px);
227+
}
228+
229+
.channel-card img {
230+
border-radius: 8px;
231+
margin-bottom: 1rem;
232+
}
233+
234+
.channel-info {
235+
text-align: center;
236+
width: 100%;
237+
}
238+
239+
.channel-info h2 {
240+
font-size: 1.1rem;
241+
font-weight: 600;
242+
margin-bottom: 0.25rem;
243+
}
244+
245+
.channel-info .speakers {
246+
margin-bottom: 0;
247+
font-size: 0.9rem;
248+
}
249+
250+
.empty-state {
251+
text-align: center;
252+
padding: 3rem 2rem;
253+
color: #666;
254+
}

database/src/main/kotlin/no/javazone/feedback/database/repository/FeedbackRepositoryDb.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,25 @@ object FeedbackRepositoryDb : FeedbackRepository {
8484
}
8585
}
8686

87+
override fun findAllChannels(): List<FeedbackChannel> {
88+
return transaction {
89+
val results = FeedbackChannels.selectAll()
90+
91+
results
92+
.groupBy { it[FeedbackChannels.id].value }
93+
.map { (_, rows) ->
94+
val firstRow = rows.first()
95+
FeedbackChannel(
96+
id = firstRow[FeedbackChannels.id].value,
97+
title = firstRow[FeedbackChannels.title],
98+
speakers = firstRow[FeedbackChannels.speakers],
99+
externalId = firstRow[FeedbackChannels.externalId],
100+
ratingCategories = emptyList()
101+
)
102+
}
103+
}
104+
}
105+
87106
override fun findByChannelId(channelId: String): FeedbackChannel? {
88107
return transaction {
89108
val results = FeedbackChannels.join(otherTable = RatingTypes, joinType = JoinType.INNER) {

domain/src/main/kotlin/no/javazone/feedback/domain/FeedbackChannel.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,5 @@ class FeedbackChannel(
1010
init {
1111
require(speakers.all { it.isNotEmpty() }) { "All speakers must not be empty." }
1212
require(title.isNotEmpty()) { "Title must not be empty." }
13-
require(ratingCategories.isNotEmpty()) { "Ratings must not be empty." }
1413
}
1514
}

domain/src/main/kotlin/no/javazone/feedback/domain/adapters/FeedbackAdapter.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ class FeedbackAdapter(
3636
return repository.findByChannelId(channelId)
3737
}
3838

39+
fun findAllChannels(): List<FeedbackChannel> {
40+
return repository.findAllChannels()
41+
}
42+
3943
fun generateQrCode(channelId: String, qrCodeGenerator: (FeedbackChannel) -> ByteArray): ByteArray? {
4044
return repository.findByChannelId(channelId)?.let {
4145
qrCodeGenerator(it)

domain/src/main/kotlin/no/javazone/feedback/domain/persistence/FeedbackRepository.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ interface FeedbackRepository {
77
fun intializeChannel(channel: FeedbackChannel): FeedbackChannel
88
fun submitFeedback(feedback: Feedback, feedbackChannel: FeedbackChannel): Feedback
99
fun findByChannelId(channelId: String): FeedbackChannel?
10+
fun findAllChannels(): List<FeedbackChannel>
1011
}

0 commit comments

Comments
 (0)