Compare commits
42 Commits
faedb21808
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0af0129054 | ||
|
|
8ff2ec3766 | ||
|
|
843c71946f | ||
|
|
51e3629fa5 | ||
|
|
ddc003eb13 | ||
|
|
e81a22dd10 | ||
|
|
269cb2967c | ||
|
|
6b764ae61b | ||
|
|
44c89e6559 | ||
|
|
a541a2dac2 | ||
|
|
ff7baa0a24 | ||
|
|
623d05da91 | ||
|
|
8601288230 | ||
|
|
fa11926642 | ||
|
|
9c8ec7fb0d | ||
|
|
78c9bd1d61 | ||
|
|
9c19bd7b6b | ||
|
|
de2fd2bdc0 | ||
|
|
81235e3ce6 | ||
|
|
4f7b2fcd17 | ||
|
|
e13fa186e1 | ||
|
|
f238940622 | ||
|
|
7f4b8cab8a | ||
|
|
b0dd8cded1 | ||
|
|
f48d958a2c | ||
|
|
2cb3cc7c35 | ||
|
|
f4ab1ac7ec | ||
|
|
2769f5e8dc | ||
|
|
d7dce88751 | ||
|
|
b036002ca8 | ||
|
|
70ab1e59c4 | ||
|
|
133fa0df2b | ||
|
|
58ca4ecafa | ||
|
|
843e55841b | ||
|
|
cfe3994bc9 | ||
|
|
8b95d963fe | ||
|
|
83d8cd07d2 | ||
|
|
6b323ba746 | ||
|
|
e834ff06ab | ||
|
|
f30d9dad94 | ||
|
|
ee914c8014 | ||
|
|
b8d0dc96fd |
0
.go/pkg/mod/cache/lock
vendored
Normal file
136
16-countries-page-from-api/README.org
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
* Frontend Mentor - REST Countries API with color theme switcher solution
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: frontend-mentor---rest-countries-api-with-color-theme-switcher-solution
|
||||||
|
:END:
|
||||||
|
This is a solution to the
|
||||||
|
[[https://www.frontendmentor.io/challenges/rest-countries-api-with-color-theme-switcher-5cacc469fec04111f7b848ca][REST
|
||||||
|
Countries API with color theme switcher challenge on Frontend Mentor]].
|
||||||
|
Frontend Mentor challenges help you improve your coding skills by
|
||||||
|
building realistic projects.
|
||||||
|
|
||||||
|
** Overview
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: overview
|
||||||
|
:END:
|
||||||
|
*** The challenge
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: the-challenge
|
||||||
|
:END:
|
||||||
|
Users should be able to:
|
||||||
|
|
||||||
|
- See all countries from the API on the homepage
|
||||||
|
- Search for a country using an =input= field
|
||||||
|
- Filter countries by region
|
||||||
|
- Click on a country to see more detailed information on a separate page
|
||||||
|
- Click through to the border countries on the detail page
|
||||||
|
- Toggle the color scheme between light and dark mode /(optional)/
|
||||||
|
|
||||||
|
*** Screenshot
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: screenshot
|
||||||
|
:END:
|
||||||
|
[[mobile-main.png]]
|
||||||
|
[[mobile-single.png]]
|
||||||
|
[[desktop-main.png]]
|
||||||
|
[[desktop-single.png]]
|
||||||
|
|
||||||
|
|
||||||
|
*** Links
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: links
|
||||||
|
:END:
|
||||||
|
- Solution URL: https://github.com/efim/Learning-HTMX/tree/master/16-countries-page-from-api
|
||||||
|
- Live Site URL: https://efim-frontentmentor-countries-page.onrender.com/
|
||||||
|
|
||||||
|
** My process
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: my-process
|
||||||
|
:END:
|
||||||
|
*** Built with
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: built-with
|
||||||
|
:END:
|
||||||
|
- Scala Server Side Rendering
|
||||||
|
- Htmx
|
||||||
|
- TailwindCSS
|
||||||
|
- Flexbox
|
||||||
|
- CSS Grid
|
||||||
|
- Mobile-first workflow
|
||||||
|
|
||||||
|
|
||||||
|
*** What I learned
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: what-i-learned
|
||||||
|
:END:
|
||||||
|
**** polling download for 'infinite scroll'
|
||||||
|
https://htmx.org/examples/infinite-scroll/
|
||||||
|
|
||||||
|
I've done almost same thing as example,
|
||||||
|
but i'm appending additional empty div, that inserts responses instead of itself
|
||||||
|
|
||||||
|
this way i have one template fragment with repeater,
|
||||||
|
and don't have to add hx attributes to the last one,
|
||||||
|
|
||||||
|
i just have separate fragment that i append to response which will trigger new
|
||||||
|
load on enter view.
|
||||||
|
**** having html 5 native autocomplete with <datalist> tag
|
||||||
|
just server side rendering it with all country names is yay
|
||||||
|
**** inserting thymeleaf templates by css selector
|
||||||
|
https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#appendix-c-markup-selector-syntax
|
||||||
|
|
||||||
|
this way i can insert <script> tag from index page, without declaring it as a fragment,
|
||||||
|
because i'll want is static anyway.
|
||||||
|
|
||||||
|
and can share 'dark mode' js code between pages
|
||||||
|
**** building docker image
|
||||||
|
First off: using nix docker tools, and sbt assembly to create 'uber jar'
|
||||||
|
now 'nix build .#countries-page-image' builds an image and symlinks it to ./result
|
||||||
|
|
||||||
|
$ docker load < result
|
||||||
|
loads the image
|
||||||
|
|
||||||
|
and it can be started
|
||||||
|
$ docker image list
|
||||||
|
$ docker run -d -p 9090:8080 <image-id>
|
||||||
|
**** deploying to render.com
|
||||||
|
need to auth the docker cli, then tag an image with my repository name
|
||||||
|
$ docker login
|
||||||
|
$ docker tag 141 efim1234/rock-paper-scissors:latest
|
||||||
|
$ podman push localhost/efim1234/countries-page:latest docker.io/efim1234/countries-page:latest
|
||||||
|
|
||||||
|
and now render.com, when creating "new service"
|
||||||
|
can find "efim1234/countries-page:latest" as public image and use it
|
||||||
|
**** using browser history
|
||||||
|
with either server side
|
||||||
|
HX-Push -> "some-url"
|
||||||
|
or
|
||||||
|
hx-push-url="true" in the html side
|
||||||
|
|
||||||
|
so that url is put into address bar,
|
||||||
|
and browser save the body into history
|
||||||
|
|
||||||
|
and now my website has forward and backward navigation
|
||||||
|
|
||||||
|
only bug - the 'filter by region' for some reason doesn't maintain the selected value
|
||||||
|
|
||||||
|
*** Continued development
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: continued-development
|
||||||
|
:END:
|
||||||
|
In future project i'll probably want to lean and use go.
|
||||||
|
For smaller binaries of the server, using PocketBase as embedded backend functions,
|
||||||
|
and maybe having a better chances of just doing some real world small projects.
|
||||||
|
|
||||||
|
*** Useful resources
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: useful-resources
|
||||||
|
:END:
|
||||||
|
- htmx documentation and examples
|
||||||
|
https://htmx.org/docs/#introduction
|
||||||
|
- thymeleaf documentation
|
||||||
|
https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html#introducing-thymeleaf
|
||||||
|
- TailwindCSS documentation
|
||||||
|
https://tailwindcss.com/docs/installation
|
||||||
|
- heroicons: MIT licensed svg icons
|
||||||
|
https://github.com/tailwindlabs/heroicons
|
||||||
|
- render.com for providing free hosting for services from docker images
|
||||||
BIN
16-countries-page-from-api/desktop-main.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
16-countries-page-from-api/desktop-single.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
16-countries-page-from-api/mobile-main.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
16-countries-page-from-api/mobile-single.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
@@ -61,10 +61,10 @@
|
|||||||
hx-get="/country"
|
hx-get="/country"
|
||||||
hx-target="body"
|
hx-target="body"
|
||||||
hx-push-url="true"
|
hx-push-url="true"
|
||||||
class="md:w-1/4 md:min-w-max"
|
class="contents"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex flex-row items-center bg-white shadow-md h-14 md:h-16 rounded-lg md:w-full dark:bg-dark-blue"
|
class="md:w-1/4 md:min-w-max flex flex-row items-center bg-white shadow-md h-14 md:h-16 rounded-lg dark:bg-dark-blue"
|
||||||
>
|
>
|
||||||
<!-- <p class="w-16 grid place-content-center">IC</p> -->
|
<!-- <p class="w-16 grid place-content-center">IC</p> -->
|
||||||
<svg
|
<svg
|
||||||
@@ -87,6 +87,9 @@
|
|||||||
name="countryName"
|
name="countryName"
|
||||||
list="country.nameViews-list"
|
list="country.nameViews-list"
|
||||||
placeholder="Search for a country..."
|
placeholder="Search for a country..."
|
||||||
|
hx-post="/countries-cards"
|
||||||
|
hx-target="#countries-main-list"
|
||||||
|
hx-trigger="keyup changed delay:500ms"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<datalist id="country.nameViews-list">
|
<datalist id="country.nameViews-list">
|
||||||
@@ -96,12 +99,10 @@
|
|||||||
value=""
|
value=""
|
||||||
></option>
|
></option>
|
||||||
</datalist>
|
</datalist>
|
||||||
</form>
|
|
||||||
<form>
|
|
||||||
<select
|
<select
|
||||||
name="region"
|
name="region"
|
||||||
class="block w-62 mt-1 h-14 md:h-16 w-64 bg-white shadow-lg rounded-md shadow-sm focus:outline-none px-7 dark:bg-dark-blue"
|
class="block w-62 mt-1 h-14 md:h-16 w-64 bg-white shadow-lg rounded-md shadow-sm focus:outline-none px-7 dark:bg-dark-blue"
|
||||||
hx-get="/countries-cards"
|
hx-post="/countries-cards"
|
||||||
hx-target="#countries-main-list"
|
hx-target="#countries-main-list"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
@@ -180,7 +181,7 @@
|
|||||||
th:if="${nextPage != -1}"
|
th:if="${nextPage != -1}"
|
||||||
id="next-page-anchor"
|
id="next-page-anchor"
|
||||||
hx-trigger="revealed"
|
hx-trigger="revealed"
|
||||||
th:hx-get="@{~/countries-cards(region=${selectedRegion},page=${nextPage})}"
|
th:hx-get="@{~/countries-cards(region=${selectedRegion},page=${nextPage},countryName=${countryName})}"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
|||||||
@@ -33,15 +33,15 @@ case class Routes(countries: List[Country])(implicit
|
|||||||
private val pageSize = 12
|
private val pageSize = 12
|
||||||
|
|
||||||
@cask.get("/")
|
@cask.get("/")
|
||||||
def indexPage(region: Option[String] = None, page: Int = 0) = {
|
def indexPage(
|
||||||
|
region: Option[String] = None,
|
||||||
|
page: Int = 0,
|
||||||
|
countryName: Option[String] = None
|
||||||
|
) = {
|
||||||
val context = new Context()
|
val context = new Context()
|
||||||
|
|
||||||
val regions = countries.map(_.region).distinct.sorted.asJava
|
val regions = countries.map(_.region).distinct.sorted.asJava
|
||||||
val selectedCountries = region match {
|
val selectedCountries = getSelectedCountries(region, countryName)
|
||||||
case None => countries
|
|
||||||
case Some("") => countries
|
|
||||||
case Some(selectedRegion) => countries.filter(_.region == selectedRegion)
|
|
||||||
}
|
|
||||||
|
|
||||||
val startIndex = page * pageSize
|
val startIndex = page * pageSize
|
||||||
val countriesPage =
|
val countriesPage =
|
||||||
@@ -53,7 +53,9 @@ case class Routes(countries: List[Country])(implicit
|
|||||||
context.setVariable("regionsSet", regions)
|
context.setVariable("regionsSet", regions)
|
||||||
context.setVariable("countriesList", countriesPage.asJava)
|
context.setVariable("countriesList", countriesPage.asJava)
|
||||||
context.setVariable("allCountriesList", countries.asJava)
|
context.setVariable("allCountriesList", countries.asJava)
|
||||||
|
// for anchor to request next pages from same countries
|
||||||
context.setVariable("selectedRegion", region.getOrElse(""))
|
context.setVariable("selectedRegion", region.getOrElse(""))
|
||||||
|
context.setVariable("countryName", countryName.getOrElse(""))
|
||||||
|
|
||||||
val indexPage = engine.process("index", context)
|
val indexPage = engine.process("index", context)
|
||||||
Response(
|
Response(
|
||||||
@@ -62,28 +64,41 @@ case class Routes(countries: List[Country])(implicit
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@cask.postForm("/countries-cards")
|
||||||
|
def postPageOfCountriesCards(
|
||||||
|
region: cask.FormValue,
|
||||||
|
countryName: cask.FormValue
|
||||||
|
) = {
|
||||||
|
println(s"in get for countries cards with $region and $countryName")
|
||||||
|
getPageOfCountriesCards(Some(region.value), 0, Some(countryName.value))
|
||||||
|
}
|
||||||
|
|
||||||
/** this method returns directly set of cards and new anchor for loading next
|
/** this method returns directly set of cards and new anchor for loading next
|
||||||
* page of cards
|
* page of cards
|
||||||
*
|
*
|
||||||
* intended to be called from "next-page-anchor" with htmx
|
* intended to be called from "next-page-anchor" with htmx
|
||||||
*/
|
*/
|
||||||
@cask.get("/countries-cards")
|
@cask.get("/countries-cards")
|
||||||
def getPageOfCountriesCards(region: Option[String] = None, page: Int = 0) = {
|
def getPageOfCountriesCards(
|
||||||
|
region: Option[String] = None,
|
||||||
|
page: Int = 0,
|
||||||
|
countryName: Option[String] = None
|
||||||
|
) = {
|
||||||
val context = new Context()
|
val context = new Context()
|
||||||
|
|
||||||
val selectedCountries = region match {
|
val selectedCountries = getSelectedCountries(region, countryName)
|
||||||
case None => countries
|
|
||||||
case Some("") => countries
|
|
||||||
case Some(selectedRegion) => countries.filter(_.region == selectedRegion)
|
|
||||||
}
|
|
||||||
|
|
||||||
val startIndex = page * pageSize
|
val startIndex = page * pageSize
|
||||||
val countriesPage =
|
val countriesPage =
|
||||||
selectedCountries.slice(startIndex, startIndex + pageSize)
|
selectedCountries.slice(startIndex, startIndex + pageSize)
|
||||||
|
context.setVariable("countriesList", countriesPage.asJava)
|
||||||
|
|
||||||
|
// for anchor to request next pages from same countries
|
||||||
|
context.setVariable("selectedRegion", region.getOrElse(""))
|
||||||
|
context.setVariable("countryName", countryName.getOrElse(""))
|
||||||
|
|
||||||
// if current page is not full - there will be no next page
|
// if current page is not full - there will be no next page
|
||||||
val nextPage = if (countriesPage.length == pageSize) page + 1 else -1
|
val nextPage = if (countriesPage.length == pageSize) page + 1 else -1
|
||||||
context.setVariable("countriesList", countriesPage.asJava)
|
|
||||||
context.setVariable("selectedRegion", region.getOrElse(""))
|
|
||||||
context.setVariable("nextPage", nextPage)
|
context.setVariable("nextPage", nextPage)
|
||||||
|
|
||||||
val cards = engine.process(
|
val cards = engine.process(
|
||||||
@@ -92,7 +107,7 @@ case class Routes(countries: List[Country])(implicit
|
|||||||
context
|
context
|
||||||
)
|
)
|
||||||
// this is to store switch to another region in the history
|
// this is to store switch to another region in the history
|
||||||
val newUrl = s"/?region=${region.getOrElse("")}"
|
val newUrl = s"/?region=${region.getOrElse("")}&countryName=${countryName.getOrElse("")}"
|
||||||
// only save url when new region is requested, not on addtional card loads
|
// only save url when new region is requested, not on addtional card loads
|
||||||
val urlHeaderOpt = if (page == 0) Seq("HX-Push" -> newUrl) else Seq.empty
|
val urlHeaderOpt = if (page == 0) Seq("HX-Push" -> newUrl) else Seq.empty
|
||||||
Response(
|
Response(
|
||||||
@@ -103,8 +118,38 @@ case class Routes(countries: List[Country])(implicit
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def getSelectedCountries(
|
||||||
|
region: Option[String] = None,
|
||||||
|
countryName: Option[String] = None
|
||||||
|
) = {
|
||||||
|
val countriesFromSelectedRegion = region match {
|
||||||
|
case None => countries
|
||||||
|
case Some("") => countries
|
||||||
|
case Some(selectedRegion) => countries.filter(_.region == selectedRegion)
|
||||||
|
}
|
||||||
|
// applying country name filtering, can be partial name
|
||||||
|
val selectedCountries = countryName match {
|
||||||
|
case None => countriesFromSelectedRegion
|
||||||
|
case Some("") => countriesFromSelectedRegion
|
||||||
|
case Some(partialSearchString) =>
|
||||||
|
val lowerSearch = partialSearchString.toLowerCase()
|
||||||
|
countriesFromSelectedRegion
|
||||||
|
.filter(country => {
|
||||||
|
val nameContainsPartialMatch =
|
||||||
|
country.name.common.toLowerCase().contains(lowerSearch) ||
|
||||||
|
country.name.nativeName.values.foldLeft(false) {
|
||||||
|
(acc, nativeName) =>
|
||||||
|
acc || nativeName.common.toLowerCase().contains(lowerSearch)
|
||||||
|
}
|
||||||
|
|
||||||
|
nameContainsPartialMatch
|
||||||
|
})
|
||||||
|
}
|
||||||
|
selectedCountries
|
||||||
|
}
|
||||||
|
|
||||||
@cask.get("/country")
|
@cask.get("/country")
|
||||||
def getCountryPage(countryName: String) = {
|
def getCountryPage(countryName: String, region: Option[String] = None) = {
|
||||||
val context = new Context()
|
val context = new Context()
|
||||||
countries.find(_.name.common == countryName) match {
|
countries.find(_.name.common == countryName) match {
|
||||||
case Some(selectedCountry) =>
|
case Some(selectedCountry) =>
|
||||||
|
|||||||
18
17-results-summary-component-go/.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Avoid accidental upload of the Sketch and Figma design files
|
||||||
|
#####################################################
|
||||||
|
## Please do not remove lines 5 and 6 - thanks! 🙂 ##
|
||||||
|
#####################################################
|
||||||
|
*.sketch
|
||||||
|
*.fig
|
||||||
|
|
||||||
|
# Avoid accidental XD upload if you convert the design file
|
||||||
|
###############################################
|
||||||
|
## Please do not remove line 12 - thanks! 🙂 ##
|
||||||
|
###############################################
|
||||||
|
*.xd
|
||||||
|
|
||||||
|
# Avoid your project being littered with annoying .DS_Store files!
|
||||||
|
.DS_Store
|
||||||
|
.prettierignore
|
||||||
|
|
||||||
|
public/out.css
|
||||||
0
17-results-summary-component-go/.project
Normal file
96
17-results-summary-component-go/README.org
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
* Frontend Mentor - Results summary component solution
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: frontend-mentor---results-summary-component-solution
|
||||||
|
:END:
|
||||||
|
This is a solution to the
|
||||||
|
[[https://www.frontendmentor.io/challenges/results-summary-component-CE_K6s0maV][Results
|
||||||
|
summary component challenge on Frontend Mentor]]. Frontend Mentor
|
||||||
|
challenges help you improve your coding skills by building realistic
|
||||||
|
projects.
|
||||||
|
|
||||||
|
** Overview
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: overview
|
||||||
|
:END:
|
||||||
|
*** The challenge
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: the-challenge
|
||||||
|
:END:
|
||||||
|
Users should be able to:
|
||||||
|
|
||||||
|
- View the optimal layout for the interface depending on their device's
|
||||||
|
screen size
|
||||||
|
- See hover and focus states for all interactive elements on the page
|
||||||
|
- *Bonus*: Use the local JSON data to dynamically populate the content
|
||||||
|
|
||||||
|
*** Screenshot
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: screenshot
|
||||||
|
:END:
|
||||||
|
[[screenshot-mobile.png]]
|
||||||
|
[[screenshot-desktop.png]]
|
||||||
|
|
||||||
|
*** Links
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: links
|
||||||
|
:END:
|
||||||
|
- Solution URL: https://github.com/efim/Learning-HTMX/tree/master/17-results-summary-component-go
|
||||||
|
- Live Site URL: https://efim-frontentmentor-results-summary-go.onrender.com/
|
||||||
|
|
||||||
|
** My process
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: my-process
|
||||||
|
:END:
|
||||||
|
*** Built with
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: built-with
|
||||||
|
:END:
|
||||||
|
- Semantic HTML5 markup
|
||||||
|
- TailwindCSS
|
||||||
|
- Flexbox
|
||||||
|
- CSS Grid
|
||||||
|
- Mobile-first workflow
|
||||||
|
- Go Server Side Rendering
|
||||||
|
- Nix for building and docker image creation
|
||||||
|
|
||||||
|
*** What I learned
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: what-i-learned
|
||||||
|
:END:
|
||||||
|
**** restarting server of file watch
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: restarting-server-of-file-watch
|
||||||
|
:END:
|
||||||
|
go doesn't have built in restart on file watch, and there are separate programs that do that
|
||||||
|
|
||||||
|
#+begin_src bash
|
||||||
|
wgo -verbose -file .go -file .gohtml -file tailwind.config.js tailwindcss -i ./input.css -o public/out.css :: go run main.go -p 1234
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
and doing tailwind first, because server embeds the output css file, so
|
||||||
|
it's needed for the build step
|
||||||
|
**** setting up static routes with default go web server
|
||||||
|
**** generating template and returning it as response of go route
|
||||||
|
**** embedding files into the binary for portability
|
||||||
|
**** using css variables to set custom colors in TailwindCSS
|
||||||
|
**** getting cli arguments in the go program
|
||||||
|
|
||||||
|
*** Continued development
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: continued-development
|
||||||
|
:END:
|
||||||
|
Further things to learn: getting data from forms, general go things - coroutines, utilizing sqlite, embedding PocketBase
|
||||||
|
|
||||||
|
*** Useful resources
|
||||||
|
:PROPERTIES:
|
||||||
|
:CUSTOM_ID: useful-resources
|
||||||
|
:END:
|
||||||
|
- https://pkg.go.dev/text/template#example-Template-Block
|
||||||
|
main go templating documentation
|
||||||
|
- https://pkg.go.dev/net/http
|
||||||
|
main default server documentation
|
||||||
|
- https://nixos.wiki/wiki/Go - packaging Go
|
||||||
|
- https://golangdocs.com/command-line-arguments-in-golang
|
||||||
|
command line arguments
|
||||||
|
|
||||||
|
|
||||||
22
17-results-summary-component-go/data.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"category": "Reaction",
|
||||||
|
"score": 80,
|
||||||
|
"icon": "./assets/images/icon-reaction.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Memory",
|
||||||
|
"score": 92,
|
||||||
|
"icon": "./assets/images/icon-memory.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Verbal",
|
||||||
|
"score": 61,
|
||||||
|
"icon": "./assets/images/icon-verbal.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"category": "Visual",
|
||||||
|
"score": 72,
|
||||||
|
"icon": "./assets/images/icon-visual.svg"
|
||||||
|
}
|
||||||
|
]
|
||||||
32
17-results-summary-component-go/default.nix
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
pname = "results-summary-component";
|
||||||
|
version = "0.0.1";
|
||||||
|
in rec {
|
||||||
|
package = pkgs.buildGoModule {
|
||||||
|
inherit pname version;
|
||||||
|
src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
|
||||||
|
vendorHash = null; # set to "" when get dependencies in go.mod
|
||||||
|
|
||||||
|
# Adding the Tailwind build step to preBuild
|
||||||
|
preBuild = ''
|
||||||
|
${pkgs.nodePackages.tailwindcss}/bin/tailwindcss -i ./input.css -o public/out.css
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
image = pkgs.dockerTools.buildLayeredImage {
|
||||||
|
name = pname;
|
||||||
|
tag = "latest";
|
||||||
|
created = "now";
|
||||||
|
config = {
|
||||||
|
Cmd = [
|
||||||
|
"${package}/bin/results-summary-component-go"
|
||||||
|
"-p"
|
||||||
|
"8080"
|
||||||
|
"-h"
|
||||||
|
"0.0.0.0"
|
||||||
|
];
|
||||||
|
ExposedPorts = { "8080/tcp" = { }; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# nixos module
|
||||||
|
}
|
||||||
BIN
17-results-summary-component-go/design/active-states.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
17-results-summary-component-go/design/desktop-design.jpg
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
17-results-summary-component-go/design/desktop-preview.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
17-results-summary-component-go/design/mobile-design.jpg
Normal file
|
After Width: | Height: | Size: 19 KiB |
3
17-results-summary-component-go/go.mod
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
module sunshine.industries/results-summary-component-go
|
||||||
|
|
||||||
|
go 1.20
|
||||||
34
17-results-summary-component-go/input.css
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--summary-item-color-var: 0deg 100% 67%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'HankenGrotesk';
|
||||||
|
src: url('/static/public/fonts/static/HankenGrotesk-Medium.ttf') format('truetype');
|
||||||
|
font-weight: 400; /* Regular */
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'HankenGrotesk';
|
||||||
|
src: url('/static/public/fonts/static/HankenGrotesk-Bold.ttf') format('truetype');
|
||||||
|
font-weight: 700; /* Bold */
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'HankenGrotesk';
|
||||||
|
src: url('/static/public/fonts/static/HankenGrotesk-ExtraBold.ttf') format('truetype');
|
||||||
|
font-weight: 800; /* ExtraBold */
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
79
17-results-summary-component-go/main.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed public
|
||||||
|
var publicContent embed.FS
|
||||||
|
|
||||||
|
//go:embed templates
|
||||||
|
var templates embed.FS
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
Name string
|
||||||
|
ColorHsl string
|
||||||
|
Score int
|
||||||
|
IconPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func iconPath(categoryName string) string {
|
||||||
|
return fmt.Sprintf("/static/public/images/icon-%s.svg", categoryName)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResultsSummaryData struct {
|
||||||
|
Categories []Category
|
||||||
|
TotalScore int
|
||||||
|
UpperPercent int
|
||||||
|
}
|
||||||
|
|
||||||
|
// starts webserver that serves html page for exercise
|
||||||
|
func main() {
|
||||||
|
// serves public static resources: bundled images, fonts, css
|
||||||
|
// visible on http://localhost:8080/static/public/some-text.txt
|
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(publicContent))))
|
||||||
|
|
||||||
|
http.HandleFunc("/hello", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
io.WriteString(w, "This is temporary here, hello")
|
||||||
|
})
|
||||||
|
|
||||||
|
resultsData := ResultsSummaryData{
|
||||||
|
Categories: []Category{
|
||||||
|
{Name: "Reaction", ColorHsl: "0deg 100% 67%",
|
||||||
|
Score: 80, IconPath: iconPath("reaction")},
|
||||||
|
{Name: "Memory", ColorHsl: "39deg 100% 56%",
|
||||||
|
Score: 92, IconPath: iconPath("memory")},
|
||||||
|
{Name: "Verbal", ColorHsl: "166deg 100% 37%",
|
||||||
|
Score: 61, IconPath: iconPath("verbal")},
|
||||||
|
{Name: "Visual", ColorHsl: "234deg 85% 45%",
|
||||||
|
Score: 72, IconPath: iconPath("visual")},
|
||||||
|
},
|
||||||
|
TotalScore: 76,
|
||||||
|
UpperPercent: 65,
|
||||||
|
}
|
||||||
|
|
||||||
|
// main page with results summary
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
templateName := "templates/summary-component.gohtml"
|
||||||
|
tmpl := template.Must(template.ParseFS(templates, "templates/index.gohtml", templateName))
|
||||||
|
if err := tmpl.Execute(w, resultsData); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
var port int
|
||||||
|
var host string
|
||||||
|
flag.IntVar(&port, "p", 8080, "Specify port for server to start on")
|
||||||
|
flag.StringVar(&host, "h", "localhost", "Specify host for server to start on")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
address := fmt.Sprintf("%s:%d", host, port)
|
||||||
|
log.Printf("starting server on %s", address)
|
||||||
|
log.Fatal(http.ListenAndServe(address, nil))
|
||||||
|
}
|
||||||
54
17-results-summary-component-go/public/fonts/README.txt
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
Hanken Grotesk Variable Font
|
||||||
|
============================
|
||||||
|
|
||||||
|
This download contains Hanken Grotesk as both variable fonts and static fonts.
|
||||||
|
|
||||||
|
Hanken Grotesk is a variable font with this axis:
|
||||||
|
wght
|
||||||
|
|
||||||
|
This means all the styles are contained in these files:
|
||||||
|
HankenGrotesk-VariableFont_wght.ttf
|
||||||
|
|
||||||
|
If your app fully supports variable fonts, you can now pick intermediate styles
|
||||||
|
that aren’t available as static fonts. Not all apps support variable fonts, and
|
||||||
|
in those cases you can use the static font files for Hanken Grotesk:
|
||||||
|
static/HankenGrotesk-Medium.ttf
|
||||||
|
static/HankenGrotesk-Bold.ttf
|
||||||
|
static/HankenGrotesk-ExtraBold.ttf
|
||||||
|
|
||||||
|
Get started
|
||||||
|
-----------
|
||||||
|
|
||||||
|
1. Install the font files you want to use
|
||||||
|
|
||||||
|
2. Use your app's font picker to view the font family and all the
|
||||||
|
available styles
|
||||||
|
|
||||||
|
Learn more about variable fonts
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
|
||||||
|
https://variablefonts.typenetwork.com
|
||||||
|
https://medium.com/variable-fonts
|
||||||
|
|
||||||
|
In desktop apps
|
||||||
|
|
||||||
|
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
|
||||||
|
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
|
||||||
|
|
||||||
|
Online
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/getting_started
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
|
||||||
|
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
|
||||||
|
|
||||||
|
Installing fonts
|
||||||
|
|
||||||
|
MacOS: https://support.apple.com/en-us/HT201749
|
||||||
|
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
|
||||||
|
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
|
||||||
|
|
||||||
|
Android Apps
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/android
|
||||||
|
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
|
||||||
BIN
17-results-summary-component-go/public/images/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20"><path stroke="#FFB21E" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="M5.833 11.667a2.5 2.5 0 1 0 .834 4.858"/><path stroke="#FFB21E" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="M3.553 13.004a3.333 3.333 0 0 1-.728-5.53m.025-.067a2.083 2.083 0 0 1 2.983-2.824m.199.054A2.083 2.083 0 1 1 10 3.75v12.917a1.667 1.667 0 0 1-3.333 0M10 5.833a2.5 2.5 0 0 0 2.5 2.5m1.667 3.334a2.5 2.5 0 1 1-.834 4.858"/><path stroke="#FFB21E" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="M16.447 13.004a3.334 3.334 0 0 0 .728-5.53m-.025-.067a2.083 2.083 0 0 0-2.983-2.824M10 3.75a2.085 2.085 0 0 1 2.538-2.033 2.084 2.084 0 0 1 1.43 2.92m-.635 12.03a1.667 1.667 0 0 1-3.333 0"/></svg>
|
||||||
|
After Width: | Height: | Size: 831 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20"><path stroke="#F55" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="M10.833 8.333V2.5l-6.666 9.167h5V17.5l6.666-9.167h-5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 247 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20"><path stroke="#00BB8F" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="M7.5 10h5M10 18.333A8.333 8.333 0 1 0 1.667 10c0 1.518.406 2.942 1.115 4.167l-.699 3.75 3.75-.699A8.295 8.295 0 0 0 10 18.333Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 323 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" viewBox="0 0 20 20"><path stroke="#1125D6" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="M10 11.667a1.667 1.667 0 1 0 0-3.334 1.667 1.667 0 0 0 0 3.334Z"/><path stroke="#1125D6" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.25" d="M17.5 10c-1.574 2.492-4.402 5-7.5 5s-5.926-2.508-7.5-5C4.416 7.632 6.66 5 10 5s5.584 2.632 7.5 5Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 453 B |
2
17-results-summary-component-go/public/some-text.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
hello!
|
||||||
|
yoyo
|
||||||
BIN
17-results-summary-component-go/results-summary-component-go
Executable file
BIN
17-results-summary-component-go/screenshot-desktop.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
17-results-summary-component-go/screenshot-mobile.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
49
17-results-summary-component-go/style-guide.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Front-end Style Guide
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
The designs were created to the following widths:
|
||||||
|
|
||||||
|
- Mobile: 375px
|
||||||
|
- Desktop: 1440px
|
||||||
|
|
||||||
|
## Colors
|
||||||
|
|
||||||
|
### Primary
|
||||||
|
|
||||||
|
- Light red: hsl(0, 100%, 67%)
|
||||||
|
- Orangey yellow: hsl(39, 100%, 56%)
|
||||||
|
- Green teal: hsl(166, 100%, 37%)
|
||||||
|
- Cobalt blue: hsl(234, 85%, 45%)
|
||||||
|
|
||||||
|
## Gradients
|
||||||
|
|
||||||
|
- Light slate blue (background): hsl(252, 100%, 67%)
|
||||||
|
- Light royal blue (background): hsl(241, 81%, 54%)
|
||||||
|
|
||||||
|
- Violet blue (circle): hsla(256, 72%, 46%, 1)
|
||||||
|
- Persian blue (circle): hsla(241, 72%, 46%, 0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Neutral
|
||||||
|
|
||||||
|
- White: hsl(0, 0%, 100%)
|
||||||
|
- Pale blue: hsl(221, 100%, 96%)
|
||||||
|
- Light lavender: hsl(241, 100%, 89%)
|
||||||
|
- Dark gray blue: hsl(224, 30%, 27%)
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
Use transparency to get the colour variations necessary to match the design. Hint: look into using `hsla()`.
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
### Body Copy
|
||||||
|
|
||||||
|
- Font size (paragraphs): 18px
|
||||||
|
|
||||||
|
### Font
|
||||||
|
|
||||||
|
- Family: [Hanken Grotesk](https://fonts.google.com/specimen/Hanken+Grotesk)
|
||||||
|
- Weights: 500, 700, 800
|
||||||
24
17-results-summary-component-go/tailwind.config.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ["./**/*.gohtml"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'light-slate-blue': 'hsl(252, 100%, 67%)',
|
||||||
|
'light-royal-blue': 'hsl(241, 81%, 54%)',
|
||||||
|
'violet-blue': 'hsla(256, 72%, 46%)',
|
||||||
|
'persian-blue': 'hsla(241, 72%, 46%)',
|
||||||
|
'pale-blue': 'hsl(221, 100%, 96%)',
|
||||||
|
'light-lavender': 'hsl(241, 100%, 89%)',
|
||||||
|
'dark-gray-blue': 'hsl(224, 30%, 27%)',
|
||||||
|
'summary-item-color': 'hsl(var(--summary-item-color-var) / <alpha-value>)',
|
||||||
|
},
|
||||||
|
'fontFamily': {
|
||||||
|
'sans': ['HankenGrotesk', 'sans-serif'],
|
||||||
|
'added': ['HankenGrotesk', 'sans-serif'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
|
||||||
45
17-results-summary-component-go/templates/index.gohtml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<!-- displays site properly based on user's device -->
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="static/public/out.css"
|
||||||
|
type="text/css"
|
||||||
|
media="screen"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="static/public/images/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<title>Frontend Mentor | Results summary component</title>
|
||||||
|
|
||||||
|
<!-- Feel free to remove these styles or customise in your own stylesheet 👍 -->
|
||||||
|
<style>
|
||||||
|
.attribution {
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.attribution a {
|
||||||
|
color: hsl(228, 45%, 44%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="grid place-items-center w-screen h-screen">
|
||||||
|
{{ template "results-summary-component" . }}
|
||||||
|
</main>
|
||||||
|
<footer class="fixed inset-x-0 bottom-1 attribution">
|
||||||
|
Challenge by
|
||||||
|
<a href="https://www.frontendmentor.io?ref=challenge" target="_blank"
|
||||||
|
>Frontend Mentor</a
|
||||||
|
>. Coded by <a href="#">Your Name Here</a>.
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<!-- displays site properly based on user's device -->
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="static/public/out.css"
|
||||||
|
type="text/css"
|
||||||
|
media="screen"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="icon"
|
||||||
|
type="image/png"
|
||||||
|
sizes="32x32"
|
||||||
|
href="static/public/images/favicon-32x32.png"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<title>THIS IS FILE FOR THE COMPONENT</title>
|
||||||
|
|
||||||
|
<!-- Feel free to remove these styles or customise in your own stylesheet 👍 -->
|
||||||
|
<style>
|
||||||
|
.attribution {
|
||||||
|
font-size: 11px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.attribution a {
|
||||||
|
color: hsl(228, 45%, 44%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="grid place-items-center w-screen h-screen">
|
||||||
|
{{/* only things inside of block component are used, yay */}}
|
||||||
|
{{ block "results-summary-component" . }}
|
||||||
|
<article
|
||||||
|
class="flex flex-col w-full h-full md:flex-row md:bg-white md:rounded-3xl md:shadow-2xl md:shadow-light-lavender/50 md:w-[715px] md:h-[500px]"
|
||||||
|
>
|
||||||
|
<section
|
||||||
|
id="results-pane"
|
||||||
|
class="flex flex-col gap-y-4 items-center px-10 pt-4 pb-8 text-white bg-gradient-to-t from-50% rounded-b-3xl md:justify-around md:w-1/2 md:rounded-3xl from-light-royal-blue to-light-slate-blue"
|
||||||
|
>
|
||||||
|
<h1 class="font-bold md:pt-2 md:text-xl text-pale-blue">
|
||||||
|
Your Result
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
class="flex flex-col justify-center items-center w-32 h-32 bg-gradient-to-t rounded-full md:w-44 md:h-44 from-violet-blue/60 to-persian-blue/75"
|
||||||
|
>
|
||||||
|
<span class="text-5xl font-extrabold md:text-6xl"
|
||||||
|
>{{ .TotalScore }}</span
|
||||||
|
>
|
||||||
|
<span class="text-pale-blue"> of 100 </span>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-y-3 items-center">
|
||||||
|
<h2 class="text-xl font-bold md:text-2xl">Great</h2>
|
||||||
|
<p class="text-sm text-center md:text-base text-pale-blue">
|
||||||
|
You scored higher than {{ .UpperPercent }}% of the people who have
|
||||||
|
taken these tests.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="summary-pane"
|
||||||
|
class="flex flex-col gap-y-4 p-7 md:justify-around md:px-8 md:w-1/2"
|
||||||
|
>
|
||||||
|
<h2 class="font-bold md:text-xl">Summary</h2>
|
||||||
|
<dl class="flex flex-col gap-y-4">
|
||||||
|
{{ range .Categories }}
|
||||||
|
<div
|
||||||
|
class="flex flex-row items-center px-5 h-12 text-sm rounded-lg bg-summary-item-color/5"
|
||||||
|
style="--summary-item-color-var: {{ .ColorHsl }}"
|
||||||
|
>
|
||||||
|
<img src="{{ .IconPath }}" alt="" class="pr-2" />
|
||||||
|
<dt class="flex-1 text-summary-item-color">{{ .Name }}</dt>
|
||||||
|
<dd class="flex flex-row">
|
||||||
|
<strong class="pr-2">{{ .Score }}</strong>
|
||||||
|
<span class="text-dark-gray-blue">/ 100</span>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</dl>
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
flex
|
||||||
|
flex-row
|
||||||
|
class="grid place-items-center w-full h-12 text-white rounded-full bg-dark-gray-blue"
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
</main>
|
||||||
|
<footer class="fixed inset-x-0 bottom-1 attribution">
|
||||||
|
Challenge by
|
||||||
|
<a href="https://www.frontendmentor.io?ref=challenge" target="_blank"
|
||||||
|
>Frontend Mentor</a
|
||||||
|
>. Coded by <a href="#">Your Name Here</a>.
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
18-expenses-chart/.envrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
use flake
|
||||||
5
18-expenses-chart/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/.direnv/
|
||||||
|
/.go/
|
||||||
|
*~
|
||||||
|
*_templ.go
|
||||||
|
static/output.css
|
||||||
28
18-expenses-chart/Makefile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# List of all .templ files
|
||||||
|
TEMPL_FILES := $(shell find templates -type f -name '*.templ')
|
||||||
|
|
||||||
|
# Name of the generated file(s) from templ
|
||||||
|
GENERATED_FILES := $(TEMPL_FILES:.templ=_templ.go)
|
||||||
|
|
||||||
|
# Generate Go files from .templ files only if they have changed
|
||||||
|
$(GENERATED_FILES): $(TEMPL_FILES)
|
||||||
|
templ generate
|
||||||
|
|
||||||
|
TAILWIND_CONFIG := tailwind.config.js
|
||||||
|
INPUT_CSS := input.css
|
||||||
|
OUTPUT_CSS := static/output.css
|
||||||
|
|
||||||
|
# generate tailwindcss output
|
||||||
|
$(OUTPUT_CSS): $(TEMPL_FILES) $(TAILWIND_CONFIG) $(INPUT_CSS)
|
||||||
|
tailwindcss -i $(INPUT_CSS) -o $(OUTPUT_CSS)
|
||||||
|
|
||||||
|
# Run the server, with dependencies on templ and tailwind generation
|
||||||
|
run: $(GENERATED_FILES) $(OUTPUT_CSS)
|
||||||
|
go run .
|
||||||
|
|
||||||
|
# this uses wgo to re-execute 'make run'
|
||||||
|
# which will toggle templ generate, when needed
|
||||||
|
# and then do `go run .` to rebuild all else
|
||||||
|
.PHONY: run/live
|
||||||
|
run/live:
|
||||||
|
wgo -verbose -file=.go -file=.templ -file=$(INPUT_CSS) -file=$(TAILWIND_CONFIG) make run
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Frontend Mentor - REST Countries API with color theme switcher solution
|
# Frontend Mentor - Expenses chart component solution
|
||||||
|
|
||||||
This is a solution to the [REST Countries API with color theme switcher challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/rest-countries-api-with-color-theme-switcher-5cacc469fec04111f7b848ca). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
|
This is a solution to the [Expenses chart component challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/expenses-chart-component-e7yJBUdjwt). Frontend Mentor challenges help you improve your coding skills by building realistic projects.
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
@@ -24,12 +24,11 @@ This is a solution to the [REST Countries API with color theme switcher challeng
|
|||||||
|
|
||||||
Users should be able to:
|
Users should be able to:
|
||||||
|
|
||||||
- See all countries from the API on the homepage
|
- View the bar chart and hover over the individual bars to see the correct amounts for each day
|
||||||
- Search for a country using an `input` field
|
- See the current day’s bar highlighted in a different colour to the other bars
|
||||||
- Filter countries by region
|
- View the optimal layout for the content depending on their device’s screen size
|
||||||
- Click on a country to see more detailed information on a separate page
|
- See hover states for all interactive elements on the page
|
||||||
- Click through to the border countries on the detail page
|
- **Bonus**: Use the JSON data file provided to dynamically size the bars on the chart
|
||||||
- Toggle the color scheme between light and dark mode *(optional)*
|
|
||||||
|
|
||||||
### Screenshot
|
### Screenshot
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Frontend Mentor - REST Countries API with color theme switcher
|
# Frontend Mentor - Expenses chart component
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Welcome! 👋
|
## Welcome! 👋
|
||||||
|
|
||||||
@@ -8,38 +8,35 @@ Thanks for checking out this front-end coding challenge.
|
|||||||
|
|
||||||
[Frontend Mentor](https://www.frontendmentor.io) challenges help you improve your coding skills by building realistic projects.
|
[Frontend Mentor](https://www.frontendmentor.io) challenges help you improve your coding skills by building realistic projects.
|
||||||
|
|
||||||
**To do this challenge, you need a good understanding of HTML, CSS, and JavaScript.**
|
**To do this challenge, you need a decent understanding of HTML, CSS and JavaScript.**
|
||||||
|
|
||||||
## The challenge
|
## The challenge
|
||||||
|
|
||||||
Your challenge is to integrate with the [REST Countries API](https://restcountries.com) to pull country data and display it like in the designs.
|
Your challenge is to build out this bar chart component and get it looking as close to the design as possible.
|
||||||
|
|
||||||
You can use any JavaScript framework/library on the front-end such as [React](https://reactjs.org) or [Vue](https://vuejs.org). You also have complete control over which packages you use to do things like make HTTP requests or style your project.
|
You can use any tools you like to help you complete the challenge. So if you've got something you'd like to practice, feel free to give it a go.
|
||||||
|
|
||||||
|
We provide the data for the chart in a local `data.json` file. So you can use that to dynamically add the bars if you choose.
|
||||||
|
|
||||||
Your users should be able to:
|
Your users should be able to:
|
||||||
|
|
||||||
- See all countries from the API on the homepage
|
- View the bar chart and hover over the individual bars to see the correct amounts for each day
|
||||||
- Search for a country using an `input` field
|
- See the current day's bar highlighted in a different colour to the other bars
|
||||||
- Filter countries by region
|
- View the optimal layout for the content depending on their device's screen size
|
||||||
- Click on a country to see more detailed information on a separate page
|
- See hover states for all interactive elements on the page
|
||||||
- Click through to the border countries on the detail page
|
- **Bonus**: See dynamically generated bars based on the data provided in the local JSON file
|
||||||
- Toggle the color scheme between light and dark mode *(optional)*
|
|
||||||
|
|
||||||
Want some support on the challenge? [Join our Slack community](https://www.frontendmentor.io/slack) and ask questions in the **#help** channel.
|
Want some support on the challenge? [Join our community](https://www.frontendmentor.io/community) and ask questions in the **#help** channel.
|
||||||
|
|
||||||
**⚠️ NOTE ⚠️: Sometimes the REST Countries API can go down. We've added a `data.json` file with all the country data if you prefer to use that instead. However, please be aware that the data in the JSON file might not be up-to-date.**
|
|
||||||
|
|
||||||
## Where to find everything
|
## Where to find everything
|
||||||
|
|
||||||
Your task is to build out the project to the designs inside the `/design` folder.
|
Your task is to build out the project to the designs inside the `/design` folder. You will find both a mobile and a desktop version of the design.
|
||||||
|
|
||||||
In this challenge, you will find mobile and desktop designs in light and dark mode color schemes for both pages.
|
|
||||||
|
|
||||||
The designs are in JPG static format. Using JPGs will mean that you'll need to use your best judgment for styles such as `font-size`, `padding` and `margin`.
|
The designs are in JPG static format. Using JPGs will mean that you'll need to use your best judgment for styles such as `font-size`, `padding` and `margin`.
|
||||||
|
|
||||||
If you would like the design files (we provide Sketch & Figma versions) to inspect the design in more detail, you can [subscribe as a PRO member](https://www.frontendmentor.io/pro).
|
If you would like the design files (we provide Sketch & Figma versions) to inspect the design in more detail, you can [subscribe as a PRO member](https://www.frontendmentor.io/pro).
|
||||||
|
|
||||||
There are no assets for this challenge, as the country flags will be pulled from the [REST Countries API](https://restcountries.com) and you can use an icon font library for the icons.
|
You will find all the required assets in the `/images` folder. The assets are already optimized.
|
||||||
|
|
||||||
There is also a `style-guide.md` file containing the information you'll need, such as color palette and fonts.
|
There is also a `style-guide.md` file containing the information you'll need, such as color palette and fonts.
|
||||||
|
|
||||||
@@ -56,7 +53,7 @@ Feel free to use any workflow that you feel comfortable with. Below is a suggest
|
|||||||
|
|
||||||
## Deploying your project
|
## Deploying your project
|
||||||
|
|
||||||
As mentioned above, there are many ways to host your project for free. Our recommend hosts are:
|
As mentioned above, there are many ways to host your project for free. Our recommended hosts are:
|
||||||
|
|
||||||
- [GitHub Pages](https://pages.github.com/)
|
- [GitHub Pages](https://pages.github.com/)
|
||||||
- [Vercel](https://vercel.com/)
|
- [Vercel](https://vercel.com/)
|
||||||
@@ -82,7 +79,7 @@ Remember, if you're looking for feedback on your solution, be sure to ask questi
|
|||||||
|
|
||||||
There are multiple places you can share your solution:
|
There are multiple places you can share your solution:
|
||||||
|
|
||||||
1. Share your solution page in the **#finished-projects** channel of the [Slack community](https://www.frontendmentor.io/slack).
|
1. Share your solution page in the **#finished-projects** channel of the [community](https://www.frontendmentor.io/community).
|
||||||
2. Tweet [@frontendmentor](https://twitter.com/frontendmentor) and mention **@frontendmentor**, including the repo and live URLs in the tweet. We'd love to take a look at what you've built and help share it around.
|
2. Tweet [@frontendmentor](https://twitter.com/frontendmentor) and mention **@frontendmentor**, including the repo and live URLs in the tweet. We'd love to take a look at what you've built and help share it around.
|
||||||
3. Share your solution on other social channels like LinkedIn.
|
3. Share your solution on other social channels like LinkedIn.
|
||||||
4. Blog about your experience building your project. Writing about your workflow, technical choices, and talking through your code is a brilliant way to reinforce what you've learned. Great platforms to write on are [dev.to](https://dev.to/), [Hashnode](https://hashnode.com/), and [CodeNewbie](https://community.codenewbie.org/).
|
4. Blog about your experience building your project. Writing about your workflow, technical choices, and talking through your code is a brilliant way to reinforce what you've learned. Great platforms to write on are [dev.to](https://dev.to/), [Hashnode](https://hashnode.com/), and [CodeNewbie](https://community.codenewbie.org/).
|
||||||
30
18-expenses-chart/data.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"day": "mon",
|
||||||
|
"amount": 17.45
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"day": "tue",
|
||||||
|
"amount": 34.91
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"day": "wed",
|
||||||
|
"amount": 52.36
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"day": "thu",
|
||||||
|
"amount": 31.07
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"day": "fri",
|
||||||
|
"amount": 23.39
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"day": "sat",
|
||||||
|
"amount": 43.28
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"day": "sun",
|
||||||
|
"amount": 25.48
|
||||||
|
}
|
||||||
|
]
|
||||||
BIN
18-expenses-chart/design/active-states.jpg
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
18-expenses-chart/design/desktop-design.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
18-expenses-chart/design/desktop-preview.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
18-expenses-chart/design/mobile-design.jpg
Normal file
|
After Width: | Height: | Size: 18 KiB |
211
18-expenses-chart/flake.lock
generated
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1709126324,
|
||||||
|
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_2": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1694529238,
|
||||||
|
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1667395993,
|
||||||
|
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gitignore": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"templ",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1694102001,
|
||||||
|
"narHash": "sha256-vky6VPK1n1od6vXbqzOXnekrQpTL4hbPAwUhT5J9c9E=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"rev": "9e21c80adf67ebcb077d75bd5e7d724d21eeafd6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "gitignore.nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gomod2nix": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_2",
|
||||||
|
"nixpkgs": [
|
||||||
|
"templ",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1705314449,
|
||||||
|
"narHash": "sha256-yfQQ67dLejP0FLK76LKHbkzcQqNIrux6MFe32MMFGNQ=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "gomod2nix",
|
||||||
|
"rev": "30e3c3a9ec4ac8453282ca7f67fca9e1da12c3e6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "gomod2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1710097495,
|
||||||
|
"narHash": "sha256-B7Ea7q7hU7SE8wOPJ9oXEBjvB89yl2csaLjf5v/7jr8=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "d40e866b1f98698d454dad8f592fe7616ff705a4",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1701282334,
|
||||||
|
"narHash": "sha256-MxCVrXY6v4QmfTwIysjjaX0XUhqBbxTWWB4HXtDYsdk=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "057f9aecfb71c4437d2b27d3323df7f93c010b7e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "23.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs",
|
||||||
|
"templ": "templ"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"templ": {
|
||||||
|
"inputs": {
|
||||||
|
"gitignore": "gitignore",
|
||||||
|
"gomod2nix": "gomod2nix",
|
||||||
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"xc": "xc"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1709917943,
|
||||||
|
"narHash": "sha256-zDQxUFSmG/VX+xtK+nZ3ObRMVcMjjx+EUAxHLNcHHF8=",
|
||||||
|
"owner": "a-h",
|
||||||
|
"repo": "templ",
|
||||||
|
"rev": "df2a32403bb4a8e4745ac10ddc8b3e77386d8045",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "a-h",
|
||||||
|
"repo": "templ",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"xc": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_3",
|
||||||
|
"nixpkgs": [
|
||||||
|
"templ",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1703164129,
|
||||||
|
"narHash": "sha256-kCcCqqwvjN07H8FPG4tXsRVRcMqT8dUNt9pwW1kKAe8=",
|
||||||
|
"owner": "joerdav",
|
||||||
|
"repo": "xc",
|
||||||
|
"rev": "0655cccfcf036556aeaddfb8f45dc7e8dd1b3680",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "joerdav",
|
||||||
|
"repo": "xc",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
80
18-expenses-chart/flake.nix
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
rec {
|
||||||
|
description = "expenses chart exercise";
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
templ.url = "github:a-h/templ";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, flake-utils, templ }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
templPkg = templ.packages.${system}.templ;
|
||||||
|
pname = "expenses-chart";
|
||||||
|
version = "0.0.1";
|
||||||
|
in rec {
|
||||||
|
thePackage = pkgs.buildGoModule {
|
||||||
|
inherit pname version;
|
||||||
|
src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;
|
||||||
|
vendorHash = "sha256-sKuP3TsRD3MXBGtdSRnX62eXBQE1ASWFQu0kLlXSlFA=";
|
||||||
|
|
||||||
|
preBuild = ''
|
||||||
|
# Adding the Tailwind build step to preBuild
|
||||||
|
${pkgs.nodePackages.tailwindcss}/bin/tailwindcss -i ./input.css -o static/output.css
|
||||||
|
# Adding generation of go code from templ files
|
||||||
|
${templPkg}/bin/templ generate
|
||||||
|
'';
|
||||||
|
|
||||||
|
};
|
||||||
|
packages = rec {
|
||||||
|
"${pname}" = thePackage;
|
||||||
|
"${pname}-image" = pkgs.dockerTools.buildLayeredImage {
|
||||||
|
name = pname;
|
||||||
|
tag = "latest";
|
||||||
|
created = "now";
|
||||||
|
config = {
|
||||||
|
Cmd = [ "${thePackage}/bin/templ-exercise" ];
|
||||||
|
ExposedPorts = { "3000/tcp" = { }; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
image-hello = pkgs.dockerTools.buildLayeredImage { # so, wow, this works
|
||||||
|
name = pname;
|
||||||
|
tag = "latest";
|
||||||
|
config.Cmd = [ "${pkgs.hello}/bin/hello" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
image = pkgs.dockerTools.buildImage {
|
||||||
|
name = pname;
|
||||||
|
tag = "latest";
|
||||||
|
created = "now";
|
||||||
|
copyToRoot = pkgs.buildEnv {
|
||||||
|
name = "image-root";
|
||||||
|
paths = [ thePackage pkgs.dockerTools.binSh pkgs.coreutils ];
|
||||||
|
pathsToLink = [ "/bin" "/dist" "/public" ];
|
||||||
|
};
|
||||||
|
config = {
|
||||||
|
Cmd = [ "/bin/templ-exercise" ];
|
||||||
|
ExposedPorts = { "8080/tcp" = { }; };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
pkgs.go
|
||||||
|
pkgs.wgo
|
||||||
|
pkgs.semgrep
|
||||||
|
pkgs.gopls
|
||||||
|
pkgs.gnumake
|
||||||
|
templPkg
|
||||||
|
pkgs.nodePackages.tailwindcss
|
||||||
|
];
|
||||||
|
shellHook = ''
|
||||||
|
export GOPATH=$PWD/.go
|
||||||
|
export PATH=$GOPATH/bin:$PATH
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
# see https://serokell.io/blog/practical-nix-flakes
|
||||||
|
}
|
||||||
0
18-expenses-chart/flake.nix~
Normal file
5
18-expenses-chart/go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module sunshine.industries/temp-exercise
|
||||||
|
|
||||||
|
go 1.21.7
|
||||||
|
|
||||||
|
require github.com/a-h/templ v0.2.598
|
||||||
4
18-expenses-chart/go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/a-h/templ v0.2.598 h1:6jMIHv6wQZvdPxTuv87erW4RqN/FPU0wk7ZHN5wVuuo=
|
||||||
|
github.com/a-h/templ v0.2.598/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
7
18-expenses-chart/input.css
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
33
18-expenses-chart/main.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"sunshine.industries/temp-exercise/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
func randomDailyExpense() float32 {
|
||||||
|
return 10 + rand.Float32()*100
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
component := templates.IndexPage(templates.PageData{
|
||||||
|
Balance: 500 + rand.Float32()*1000,
|
||||||
|
Expenses: []float32{randomDailyExpense(), randomDailyExpense(), randomDailyExpense(), randomDailyExpense(), randomDailyExpense(), randomDailyExpense(), randomDailyExpense()},
|
||||||
|
TotalThisMonth: 50 + rand.Float32()*600,
|
||||||
|
PercentComparedToLastMonth: -5 + 10*rand.Float32(),
|
||||||
|
})
|
||||||
|
component.Render(context.Background(), w)
|
||||||
|
})
|
||||||
|
|
||||||
|
staticFs := http.FileServer(http.Dir("./static"))
|
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", staticFs))
|
||||||
|
|
||||||
|
fmt.Println("starting to serve on :3000")
|
||||||
|
http.ListenAndServe(":3000", nil)
|
||||||
|
}
|
||||||
107
18-expenses-chart/notes.org
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
* new exersice after long time
|
||||||
|
now will be learning https://github.com/a-h/templ
|
||||||
|
it's a templating solution for go, more similar to jsx
|
||||||
|
but it also needs a pre-processing step, so i'm adding it like tailwind
|
||||||
|
|
||||||
|
with Makefile for make run/live
|
||||||
|
and will need to add as a step in project build
|
||||||
|
|
||||||
|
* setting up
|
||||||
|
https://git.sunshine.industries/efim/golang-templ-and-tailwind
|
||||||
|
have separate repo, but i guess i'll remove .git from this dir,
|
||||||
|
to have single commits across Learning HTMX?
|
||||||
|
|
||||||
|
or maybe magit will just allow for simple integration?
|
||||||
|
just not pushing into origin, committing, and then committing from overall repository?
|
||||||
|
|
||||||
|
** not so simple, but still quite OK!
|
||||||
|
|
||||||
|
there's now 'subtree' which is separate concept from submodule,
|
||||||
|
and it is maybe simpler:
|
||||||
|
|
||||||
|
https://magit.vc/manual/magit/Subtree.html
|
||||||
|
|
||||||
|
i'm using the repo url
|
||||||
|
ssh://gitea@git.sunshine.industries:65433/efim/golang-templ-and-tailwind.git
|
||||||
|
|
||||||
|
and the command magit subtree add,
|
||||||
|
specify the directory, and it gets embedded it seems.
|
||||||
|
i have one level magit dispatch and my notes would be committed to the main repo,
|
||||||
|
i would potentially be able to pull in commits from the parent?
|
||||||
|
|
||||||
|
and maybe somehow contribute, but yeah, this is still confusing 🙂
|
||||||
|
how would i commit to subtree upstream if there are conflicting changes?
|
||||||
|
maybe i'll just get denied, as simple as that.
|
||||||
|
|
||||||
|
allright! let's add exercise specific data (again)
|
||||||
|
|
||||||
|
* so, starting dev
|
||||||
|
|
||||||
|
old process:
|
||||||
|
1. open second browser and open png with style
|
||||||
|
first mobile
|
||||||
|
2. enable view C-S-m in firefox
|
||||||
|
the responsive mode
|
||||||
|
|
||||||
|
i suppose next is adding colors to tailwind configuration?
|
||||||
|
from the styleguide file
|
||||||
|
|
||||||
|
also
|
||||||
|
3. check desktop style, whether any big rearrangements are required
|
||||||
|
so that i'd start mobile with way to rearrange things
|
||||||
|
|
||||||
|
do i have a separate note with all of these things?
|
||||||
|
i've also used some commands to vertically center my stuff
|
||||||
|
|
||||||
|
** DONE allright, tailwind config go
|
||||||
|
font, size, colors
|
||||||
|
|
||||||
|
** well, i don't really want to go on.
|
||||||
|
but yeah, i'll need hierarchy of the elements.
|
||||||
|
|
||||||
|
** ok. thoughs:
|
||||||
|
the componets would be
|
||||||
|
- my balance bubble
|
||||||
|
because it is spacially separate
|
||||||
|
- spending summary
|
||||||
|
- graph inside spending summary
|
||||||
|
or maybe don't even need that
|
||||||
|
|
||||||
|
the component should take up all space provided to it.
|
||||||
|
if parent would want to add space around - that's easy to do,
|
||||||
|
but if component insists on adding space around itself - harder to reuse?
|
||||||
|
because then if parent would want to have child take up all space - negative padding?
|
||||||
|
|
||||||
|
*** allright, @ to include sub templates
|
||||||
|
|
||||||
|
** well, last part? preparing the docker container for deployment on Render.com?
|
||||||
|
|
||||||
|
which port should be used?
|
||||||
|
also, this would be first time i'll add templ as a build step
|
||||||
|
|
||||||
|
** for some reason image doesn't work well
|
||||||
|
|
||||||
|
podman run -d -p 9090:8080 localhost/expenses-chart:latest /nix/store/l4r6glmzbvkhg97lp4dn7nm76w6hz41g-expenses-chart-0.0.1/bin/temp-exercise
|
||||||
|
|
||||||
|
this runs,
|
||||||
|
but podman run -it expenses-chart:latest
|
||||||
|
Error: crun: executable file `/nix/store/l4r6glmzbvkhg97lp4dn7nm76w6hz41g-expenses-chart-0.0.1/bin/templ-exercise` not found in $PATH: No such file or directory: OCI runtime attempted to invoke a command that was not found
|
||||||
|
|
||||||
|
oh, well
|
||||||
|
|
||||||
|
** you know what - i give up
|
||||||
|
|
||||||
|
for some reason when i open url to app running in container,
|
||||||
|
and it's on the 8080 port of the host machine - the static files are served
|
||||||
|
|
||||||
|
if i use any other port of local machine, to forward into container,
|
||||||
|
the static files are not served
|
||||||
|
|
||||||
|
maybe this is bug in go? i don't know
|
||||||
|
StripPrefix doesn't seem to do anything
|
||||||
|
|
||||||
|
and for some reason in this specific exercise CMD doesn't work in the container
|
||||||
|
so let's just give up!
|
||||||
|
|
||||||
|
i could try to write a docker file and check with that, but yeah
|
||||||
|
|
||||||
BIN
18-expenses-chart/static/images/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
1
18-expenses-chart/static/images/logo.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg width="72" height="48" viewBox="0 0 72 48" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle fill="#382314" cx="48" cy="24" r="24"/><circle stroke="#FFF" stroke-width="2" cx="24" cy="24" r="23"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 238 B |
37
18-expenses-chart/style-guide.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Front-end Style Guide
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
The designs were created to the following widths:
|
||||||
|
|
||||||
|
- Mobile: 375px
|
||||||
|
- Desktop: 1440px
|
||||||
|
|
||||||
|
> 💡 These are just the design sizes. Ensure content is responsive and meets WCAG requirements by testing the full range of screen sizes from 320px to large screens.
|
||||||
|
|
||||||
|
## Colors
|
||||||
|
|
||||||
|
### Primary
|
||||||
|
|
||||||
|
- Soft red: hsl(10, 79%, 65%)
|
||||||
|
- Cyan: hsl(186, 34%, 60%)
|
||||||
|
|
||||||
|
### Neutral
|
||||||
|
|
||||||
|
- Dark brown: hsl(25, 47%, 15%)
|
||||||
|
- Medium brown: hsl(28, 10%, 53%)
|
||||||
|
- Cream: hsl(27, 66%, 92%)
|
||||||
|
- Very pale orange: hsl(33, 100%, 98%)
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
### Body Copy
|
||||||
|
|
||||||
|
- Font size: 18px
|
||||||
|
|
||||||
|
### Font
|
||||||
|
|
||||||
|
- Family: [DM Sans](https://fonts.google.com/specimen/DM+Sans)
|
||||||
|
- Weights: 400, 700
|
||||||
|
|
||||||
|
> 💎 [Upgrade to Pro](https://www.frontendmentor.io/pro?ref=style-guide) for design file access to see all design details and get hands-on experience using a professional workflow with tools like Figma.
|
||||||
29
18-expenses-chart/tailwind.config.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: ["./templates/index.templ"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
'sans': ['DM Sans', 'sans-serif'],
|
||||||
|
},
|
||||||
|
fontWeight: {
|
||||||
|
'normal': 400,
|
||||||
|
'bold': 700,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
'soft-red': 'hsl(10, 79%, 65%)',
|
||||||
|
cyan: 'hsl(186, 34%, 60%)',
|
||||||
|
},
|
||||||
|
neutral: {
|
||||||
|
'dark-brown': 'hsl(25, 47%, 15%)',
|
||||||
|
'medium-brown': 'hsl(28, 10%, 53%)',
|
||||||
|
cream: 'hsl(27, 66%, 92%)',
|
||||||
|
'very-pale-orange': 'hsl(33, 100%, 98%)',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
|
|
||||||
133
18-expenses-chart/templates/index.templ
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "slices"
|
||||||
|
|
||||||
|
type PageData struct {
|
||||||
|
Balance float32
|
||||||
|
Expenses []float32
|
||||||
|
TotalThisMonth float32
|
||||||
|
PercentComparedToLastMonth float32
|
||||||
|
}
|
||||||
|
|
||||||
|
templ myBalanceComponent(balance float32) {
|
||||||
|
<div class="flex flex-row rounded-xl text-neutral-very-pale-orange text-xs md:text-base bg-primary-soft-red p-4 shadow md:rounded-[1rem] md:p-7">
|
||||||
|
<div class="grow ">
|
||||||
|
My balance
|
||||||
|
<p class="text-xl md:text-2xl font-bold">${ fmt.Sprintf("%.2f", balance) }</p>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
src="./static/images/logo.svg"
|
||||||
|
alt="App logo: a white circle overlapping from the left a filled in black circle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
var days []string = []string{"mon", "tue", "wed", "thu", "fri", "sat", "sun"}
|
||||||
|
|
||||||
|
// returns templ SummaryComponent
|
||||||
|
// with all attributes computed from the expenses slice
|
||||||
|
// the percentages of the columns, current day number, etc
|
||||||
|
// NOTE: this seems to be the way to mix go calculations and templates
|
||||||
|
func prepareSummaryComponent(expenses []float32, totalThisMonth, percentComparedToLastMonth float32) templ.Component {
|
||||||
|
fmt.Println("hello, preparing expenses: ", expenses)
|
||||||
|
max := slices.Max(expenses)
|
||||||
|
percentages := make([]float32, 0, len(expenses))
|
||||||
|
for _, price := range expenses {
|
||||||
|
percentages = append(percentages, price/max)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDayNum := 2
|
||||||
|
|
||||||
|
return spendingSummaryComponent(expenses, percentages, currentDayNum, percentComparedToLastMonth, totalThisMonth)
|
||||||
|
}
|
||||||
|
|
||||||
|
css expenseBarVars(percentage float32) {
|
||||||
|
--height-percentage: { fmt.Sprintf("%.2f", percentage) };
|
||||||
|
height: calc(var(--height-percentage) * 8rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
templ dayExpenseColumn(expense, percentage float32, day string, isCurrentDay bool) {
|
||||||
|
<div class="flex flex-col justify-end items-center h-32">
|
||||||
|
<div
|
||||||
|
class={ "rounded group relative w-full h-12",
|
||||||
|
expenseBarVars(percentage),
|
||||||
|
templ.KV("bg-primary-soft-red hover:bg-primary-soft-red/70", !isCurrentDay),
|
||||||
|
templ.KV("bg-primary-cyan hover:bg-primary-cyan/70", isCurrentDay),
|
||||||
|
"hover:cursor-pointer" }
|
||||||
|
>
|
||||||
|
<span class="absolute left-1/2 -translate-x-1/2 -translate-y-7 opacity-0 group-hover:opacity-100 bg-neutral-dark-brown text-neutral-cream rounded text-xs p-1">${ fmt.Sprintf("%.2f", expense ) }</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-neutral-medium-brown text-xs py-2">{ day }</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 7 vertical bars of the per-day expenses
|
||||||
|
templ expensesChart(expenses, percentages []float32, currentDayNum int) {
|
||||||
|
<div class="grid grid-cols-7 place-content-between gap-x-3">
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
@dayExpenseColumn(expenses[i], percentages[i], days[i], currentDayNum == i)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Big container with chard and total expenses of the week
|
||||||
|
templ spendingSummaryComponent(expenses, percentages []float32, currentDayNum int, totalThisMonth, percentComparedToLastMonth float32) {
|
||||||
|
<div class="bg-neutral-very-pale-orange rounded-xl shadow p-4 py-6 flex flex-col gap-y-4 md:p-7 md:px-10 md:rounded-[1rem] md:gap-y-5">
|
||||||
|
<p2 class="text-neutral-dark-brown text-xl font-bold pb-6 md:text-2xl md:pb-12 ">Spending - Last 7 days</p2>
|
||||||
|
@expensesChart(expenses, percentages, currentDayNum)
|
||||||
|
<hr class="bg-neutral-cream border-t-0 h-0.5"/>
|
||||||
|
<div class="grid grid-cols-2 text-sm text-neutral-medium-brown">
|
||||||
|
<p class="col-span-full">Total this month</p>
|
||||||
|
<div class="grow">
|
||||||
|
<p class="grid items-center text-neutral-dark-brown text-2xl font-bold h-full md:text-4xl md:py-2">$ { fmt.Sprintf("%.2f", totalThisMonth) }</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-right grid content-center">
|
||||||
|
<div class="text-neutral-dark-brown font-bold ">
|
||||||
|
if (percentComparedToLastMonth > 0) {
|
||||||
|
<span class="inline">+</span>
|
||||||
|
}
|
||||||
|
{ fmt.Sprintf("%.2f", percentComparedToLastMonth) }%
|
||||||
|
</div>
|
||||||
|
from last month
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ WidgetsComponend(pageData PageData) {
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
@myBalanceComponent(pageData.Balance)
|
||||||
|
@prepareSummaryComponent(pageData.Expenses, pageData.PercentComparedToLastMonth, pageData.TotalThisMonth)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ IndexPage(pageData PageData) {
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"/>
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap" rel="stylesheet"/>
|
||||||
|
<link rel="stylesheet" href="/styles/templ.css"/>
|
||||||
|
<link href="/static/output.css" rel="stylesheet"/>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <!-- displays site properly based on user's device -->
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="./static/images/favicon-32x32.png"/>
|
||||||
|
<title>Frontend Mentor | Expenses chart component</title>
|
||||||
|
<!-- Feel free to remove these styles or customise in your own stylesheet 👍 -->
|
||||||
|
<style>
|
||||||
|
.attribution { font-size: 11px; text-align: center; }
|
||||||
|
.attribution a { color: hsl(228, 45%, 44%); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-neutral-cream text-bold p-4 h-full grid place-items-center">
|
||||||
|
<main class="w-full md:w-1/4">
|
||||||
|
@WidgetsComponend(pageData)
|
||||||
|
</main>
|
||||||
|
<div class="attribution fixed bottom-0 inset-x-0">
|
||||||
|
Challenge by <a href="https://www.frontendmentor.io?ref=challenge" target="_blank">Frontend Mentor</a>.
|
||||||
|
Coded by <a href="#">Your Name Here</a>.
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
}
|
||||||
19
flake.nix
@@ -36,16 +36,31 @@
|
|||||||
inherit pkgs sbt-derivation;
|
inherit pkgs sbt-derivation;
|
||||||
lib = pkgs.lib;
|
lib = pkgs.lib;
|
||||||
};
|
};
|
||||||
|
results-component-go = import ./17-results-summary-component-go {
|
||||||
|
inherit pkgs;
|
||||||
|
lib = pkgs.lib;
|
||||||
|
};
|
||||||
in {
|
in {
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
pkgs.scala-cli
|
pkgs.scala-cli
|
||||||
pkgs.sbt
|
pkgs.sbt
|
||||||
pkgs.scalafmt
|
pkgs.scalafmt
|
||||||
|
pkgs.jdk
|
||||||
|
|
||||||
pkgs.nodePackages.tailwindcss
|
pkgs.nodePackages.tailwindcss
|
||||||
pkgs.nodePackages.prettier
|
pkgs.nodePackages.prettier
|
||||||
pkgs.jdk
|
pkgs.rustywind
|
||||||
|
|
||||||
|
pkgs.go
|
||||||
|
pkgs.wgo
|
||||||
|
pkgs.semgrep
|
||||||
|
pkgs.gopls
|
||||||
];
|
];
|
||||||
|
shellHook = ''
|
||||||
|
export GOPATH=$PWD/.go
|
||||||
|
export PATH=$GOPATH/bin:$PATH
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
packages.price-grid-app = price-grid.package;
|
packages.price-grid-app = price-grid.package;
|
||||||
nixosModules.price-grid-app = price-grid.module;
|
nixosModules.price-grid-app = price-grid.module;
|
||||||
@@ -64,6 +79,8 @@
|
|||||||
nixosModules.multi-step-form = multi-step-form.module;
|
nixosModules.multi-step-form = multi-step-form.module;
|
||||||
packages.countries-page = countries-page.package;
|
packages.countries-page = countries-page.package;
|
||||||
packages.countries-page-image = countries-page.image;
|
packages.countries-page-image = countries-page.image;
|
||||||
|
packages.results-component-go = results-component-go.package;
|
||||||
|
packages.results-component-go-image = results-component-go.image;
|
||||||
});
|
});
|
||||||
# see https://serokell.io/blog/practical-nix-flakes
|
# see https://serokell.io/blog/practical-nix-flakes
|
||||||
}
|
}
|
||||||
|
|||||||