From 7001e75ac2bc71e84196c6830e3ec1647a304c89 Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 15 Jan 2023 16:26:06 -0500 Subject: [PATCH] Migrate "my listings" page --- src/JobsJobsJobs/App/src/router/index.ts | 13 --- src/JobsJobsJobs/App/src/views/Home.vue | 19 ---- .../App/src/views/listing/MyListings.vue | 88 ------------------- src/JobsJobsJobs/Server/Handlers.fs | 37 +++++--- .../Server/JobsJobsJobs.Server.fsproj | 1 + src/JobsJobsJobs/Server/Views/Common.fs | 9 +- src/JobsJobsJobs/Server/Views/Listing.fs | 63 +++++++++++++ 7 files changed, 95 insertions(+), 135 deletions(-) delete mode 100644 src/JobsJobsJobs/App/src/views/Home.vue delete mode 100644 src/JobsJobsJobs/App/src/views/listing/MyListings.vue create mode 100644 src/JobsJobsJobs/Server/Views/Listing.fs diff --git a/src/JobsJobsJobs/App/src/router/index.ts b/src/JobsJobsJobs/App/src/router/index.ts index 85cc3a4..a5b0b1c 100644 --- a/src/JobsJobsJobs/App/src/router/index.ts +++ b/src/JobsJobsJobs/App/src/router/index.ts @@ -6,7 +6,6 @@ import { RouteRecordRaw } from "vue-router" import store, { Mutations } from "@/store" -import Home from "@/views/Home.vue" /** The URL to which the user should be pointed once they have authorized with Mastodon */ export const AFTER_LOG_ON_URL = "jjj-after-log-on-url" @@ -24,12 +23,6 @@ export function queryValue (route: RouteLocationNormalizedLoaded, key : string) } const routes: Array = [ - { - path: "/", - name: "Home", - component: Home, - meta: { title: "Welcome!" } - }, { path: "/how-it-works", name: "HowItWorks", @@ -68,12 +61,6 @@ const routes: Array = [ component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/ListingView.vue"), meta: { auth: true, title: "Loading Job Listing..." } }, - { - path: "/listings/mine", - name: "MyListings", - component: () => import(/* webpackChunkName: "joblist" */ "../views/listing/MyListings.vue"), - meta: { auth: true, title: "My Job Listings" } - }, // Success Story URLs { path: "/success-story/list", diff --git a/src/JobsJobsJobs/App/src/views/Home.vue b/src/JobsJobsJobs/App/src/views/Home.vue deleted file mode 100644 index e1b0ae8..0000000 --- a/src/JobsJobsJobs/App/src/views/Home.vue +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/src/JobsJobsJobs/App/src/views/listing/MyListings.vue b/src/JobsJobsJobs/App/src/views/listing/MyListings.vue deleted file mode 100644 index 3bd01eb..0000000 --- a/src/JobsJobsJobs/App/src/views/listing/MyListings.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - diff --git a/src/JobsJobsJobs/Server/Handlers.fs b/src/JobsJobsJobs/Server/Handlers.fs index 79f0939..89181e2 100644 --- a/src/JobsJobsJobs/Server/Handlers.fs +++ b/src/JobsJobsJobs/Server/Handlers.fs @@ -486,19 +486,24 @@ module Home = renderHandler "Terms of Service" Home.termsOfService -/// Handlers for /api/listing[s] routes +/// Handlers for /listing[s] routes [] module Listing = + + // GET: /listings/mine + let mine : HttpHandler = requireUser >=> fun next ctx -> task { + let! listings = Listings.findByCitizen (currentCitizenId ctx) + return! Listing.mine listings (timeZone ctx) |> render "My Job Listings" next ctx + } + + +/// Handlers for /api/listing[s] routes +[] +module ListingApi = /// Parse the string we receive from JSON into a NodaTime local date let private parseDate = DateTime.Parse >> LocalDate.FromDateTime - // GET: /api/listings/mine - let mine : HttpHandler = authorize >=> fun next ctx -> task { - let! listings = Listings.findByCitizen (currentCitizenId ctx) - return! json listings next ctx - } - // GET: /api/listing/[id] let get listingId : HttpHandler = authorize >=> fun next ctx -> task { match! Listings.findById (ListingId listingId) with @@ -826,6 +831,11 @@ let allEndpoints = [ ] ] GET_HEAD [ route "/how-it-works" Home.howItWorks ] + subRoute "/listing" [ + GET_HEAD [ + route "s/mine" Listing.mine + ] + ] GET_HEAD [ route "/privacy-policy" Home.privacyPolicy ] subRoute "/profile" [ GET_HEAD [ @@ -851,14 +861,13 @@ let allEndpoints = [ GET_HEAD [ route "/continents" Continent.all ] subRoute "/listing" [ GET_HEAD [ - routef "/%O" Listing.get - route "/search" Listing.search - routef "/%O/view" Listing.view - route "s/mine" Listing.mine + routef "/%O" ListingApi.get + route "/search" ListingApi.search + routef "/%O/view" ListingApi.view ] - PATCH [ routef "/%O" Listing.expire ] - POST [ route "s" Listing.add ] - PUT [ routef "/%O" Listing.update ] + PATCH [ routef "/%O" ListingApi.expire ] + POST [ route "s" ListingApi.add ] + PUT [ routef "/%O" ListingApi.update ] ] POST [ route "/markdown-preview" Api.markdownPreview ] subRoute "/profile" [ diff --git a/src/JobsJobsJobs/Server/JobsJobsJobs.Server.fsproj b/src/JobsJobsJobs/Server/JobsJobsJobs.Server.fsproj index b3facc3..285b8a9 100644 --- a/src/JobsJobsJobs/Server/JobsJobsJobs.Server.fsproj +++ b/src/JobsJobsJobs/Server/JobsJobsJobs.Server.fsproj @@ -15,6 +15,7 @@ + diff --git a/src/JobsJobsJobs/Server/Views/Common.fs b/src/JobsJobsJobs/Server/Views/Common.fs index c5cd512..65e7af8 100644 --- a/src/JobsJobsJobs/Server/Views/Common.fs +++ b/src/JobsJobsJobs/Server/Views/Common.fs @@ -96,7 +96,14 @@ let yesOrNo value = open NodaTime open NodaTime.Text -/// Generate a full date from an instant in the citizen's local time zone +/// Generate a full date in the citizen's local time zone let fullDate (value : Instant) tz = (ZonedDateTimePattern.CreateWithCurrentCulture ("MMMM d, yyyy", DateTimeZoneProviders.Tzdb)) .Format(value.InZone(DateTimeZoneProviders.Tzdb[tz])) + +/// Generate a full date/time in the citizen's local time +let fullDateTime (value : Instant) tz = + let dtPattern = ZonedDateTimePattern.CreateWithCurrentCulture ("MMMM d, yyyy h:mm", DateTimeZoneProviders.Tzdb) + let amPmPattern = ZonedDateTimePattern.CreateWithCurrentCulture ("tt", DateTimeZoneProviders.Tzdb) + let tzValue = value.InZone(DateTimeZoneProviders.Tzdb[tz]) + $"{dtPattern.Format(tzValue)} {amPmPattern.Format(tzValue).ToLowerInvariant()}" diff --git a/src/JobsJobsJobs/Server/Views/Listing.fs b/src/JobsJobsJobs/Server/Views/Listing.fs new file mode 100644 index 0000000..59c028e --- /dev/null +++ b/src/JobsJobsJobs/Server/Views/Listing.fs @@ -0,0 +1,63 @@ +/// Views for /profile URLs +[] +module JobsJobsJobs.Views.Listing + +open Giraffe.ViewEngine +open Giraffe.ViewEngine.Htmx +open JobsJobsJobs.Domain +open JobsJobsJobs.Domain.SharedTypes +open JobsJobsJobs.ViewModels + + +/// "My Listings" page +let mine (listings : ListingForView list) tz = + let active = listings |> List.filter (fun it -> not it.Listing.IsExpired) + let expired = listings |> List.filter (fun it -> it.Listing.IsExpired) + article [] [ + h3 [ _class "pb-3" ] [ rawText "My Job Listings" ] + p [] [ a [ _href "/listing/new/edit"; _class "btn btn-outline-primary" ] [ rawText "Add a New Job Listing" ] ] + if not (List.isEmpty expired) then h4 [ _class "pb-2" ] [ rawText "Active Job Listings" ] + if List.isEmpty active then + p [ _class "pb-3 fst-italic" ] [ rawText "You have no active job listings" ] + else + table [ _class "pb-3 table table-sm table-hover pt-3" ] [ + thead [] [ + [ "Action"; "Title"; "Continent / Region"; "Created"; "Updated" ] + |> List.map (fun it -> th [ _scope "col" ] [ rawText it ]) + |> tr [] + ] + active + |> List.map (fun it -> + let listId = ListingId.toString it.Listing.Id + tr [] [ + td [] [ + a [ _href $"/listing/{listId}/edit" ] [ rawText "Edit" ]; rawText " ~ " + a [ _href $"/listing/{listId}/view" ] [ rawText "View" ]; rawText " ~ " + a [ _href $"/listing/{listId}/expire" ] [ rawText "Expire" ] + ] + td [] [ str it.Listing.Title ] + td [] [ str it.Continent.Name; rawText " / "; str it.Listing.Region ] + td [] [ str (fullDateTime it.Listing.CreatedOn tz) ] + td [] [ str (fullDateTime it.Listing.UpdatedOn tz) ] + ]) + |> tbody [] + ] + if not (List.isEmpty expired) then + h4 [ _class "pb-2" ] [ rawText "Expired Job Listings" ] + table [ _class "table table-sm table-hover pt-3" ] [ + thead [] [ + [ "Action"; "Title"; "Filled Here?"; "Expired" ] + |> List.map (fun it -> th [ _scope "col" ] [ rawText it ]) + |> tr [] + ] + expired + |> List.map (fun it -> + tr [] [ + td [] [ a [ _href $"/listing/{ListingId.toString it.Listing.Id}/view" ] [rawText "View" ] ] + td [] [ str it.Listing.Title ] + td [] [ str (yesOrNo (defaultArg it.Listing.WasFilledHere false)) ] + td [] [ str (fullDateTime it.Listing.UpdatedOn tz) ] + ]) + |> tbody [] + ] + ]