diff --git a/js/src/components/Tag.vue b/js/src/components/Tag.vue index 64eff4b5d..2fcf28699 100644 --- a/js/src/components/Tag.vue +++ b/js/src/components/Tag.vue @@ -17,7 +17,8 @@ span.tag { background: $purple-3; color: $violet-2; text-transform: uppercase; - &::before { + + &:not(.category)::before { content: "#"; } } diff --git a/js/src/graphql/event.ts b/js/src/graphql/event.ts index d2dcc4b3b..605d9ddc3 100644 --- a/js/src/graphql/event.ts +++ b/js/src/graphql/event.ts @@ -23,6 +23,7 @@ const FULL_EVENT_FRAGMENT = gql` joinOptions draft language + category picture { id url @@ -203,7 +204,7 @@ export const CREATE_EVENT = gql` $picture: MediaInput $onlineAddress: String $phoneAddress: String - $category: String + $category: EventCategory $physicalAddress: AddressInput $options: EventOptionsInput $contacts: [Contact] @@ -253,7 +254,7 @@ export const EDIT_EVENT = gql` $phoneAddress: String $organizerActorId: ID $attributedToId: ID - $category: String + $category: EventCategory $physicalAddress: AddressInput $options: EventOptionsInput $contacts: [Contact] diff --git a/js/src/graphql/search.ts b/js/src/graphql/search.ts index 9f4462d3e..22e2d9a33 100644 --- a/js/src/graphql/search.ts +++ b/js/src/graphql/search.ts @@ -11,6 +11,7 @@ export const SEARCH_EVENTS_AND_GROUPS = gql` $tags: String $term: String $type: EventType + $category: String $beginsOn: DateTime $endsOn: DateTime $eventPage: Int @@ -23,6 +24,7 @@ export const SEARCH_EVENTS_AND_GROUPS = gql` tags: $tags term: $term type: $type + category: $category beginsOn: $beginsOn endsOn: $endsOn page: $eventPage diff --git a/js/src/i18n/en_US.json b/js/src/i18n/en_US.json index 4399aa369..236522afd 100644 --- a/js/src/i18n/en_US.json +++ b/js/src/i18n/en_US.json @@ -1306,5 +1306,23 @@ "IP Address": "IP Address", "Last seen on": "Last seen on", "No user matches the filters": "No user matches the filters", - "Reset filters": "Reset filters" + "Reset filters": "Reset filters", + "Arts": "Arts", + "Book Clubs": "Book Clubs", + "Business": "Business", + "Causes": "Causes", + "Comedy": "Comedy", + "Crafts": "Crafts", + "Food & Drink": "Food & Drink", + "Health": "Health", + "Music": "Music", + "Auto, Boat & Air": "Auto, Boat & Air", + "Community": "Community", + "Family & Education": "Family & Education", + "Fashion & Beauty": "Fashion & Beauty", + "Film & Media": "Film & Media", + "Games": "Games", + "Category": "Category", + "Select a category": "Select a category", + "Any category": "Any category" } diff --git a/js/src/i18n/fr_FR.json b/js/src/i18n/fr_FR.json index 839266cbf..b3fc7b805 100644 --- a/js/src/i18n/fr_FR.json +++ b/js/src/i18n/fr_FR.json @@ -1305,5 +1305,23 @@ "{timezoneLongName} ({timezoneShortName})": "{timezoneLongName} ({timezoneShortName})", "{title} ({count} todos)": "{title} ({count} todos)", "{username} was invited to {group}": "{username} a été invité à {group}", - "© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap" + "© The OpenStreetMap Contributors": "© Les Contributeur⋅ices OpenStreetMap", + "Arts": "Arts", + "Book Clubs": "Clubs de lecture", + "Business": "Entreprises", + "Causes": "Causes", + "Comedy": "Comédie", + "Crafts": "Artisanat", + "Food & Drink": "Alimentation et boissons", + "Health": "Santé", + "Music": "Musique", + "Auto, Boat & Air": "Automobile, bateaux et aéronautique", + "Community": "Communauté", + "Family & Education": "Famille et éducation", + "Fashion & Beauty": "Mode et beauté", + "Film & Media": "Films et médias", + "Games": "Jeux", + "Category": "Catégorie", + "Select a category": "Choisissez une categorie", + "Any category": "N'importe quelle catégorie" } diff --git a/js/src/types/enums.ts b/js/src/types/enums.ts index 2f05bd0d0..1061d196b 100644 --- a/js/src/types/enums.ts +++ b/js/src/types/enums.ts @@ -72,14 +72,6 @@ export enum EventVisibilityJoinOptions { LIMITED = "LIMITED", } -export enum Category { - BUSINESS = "business", - CONFERENCE = "conference", - BIRTHDAY = "birthday", - DEMONSTRATION = "demonstration", - MEETING = "meeting", -} - export enum LoginErrorCode { NEED_TO_LOGIN = "need_to_login", } diff --git a/js/src/types/event.model.ts b/js/src/types/event.model.ts index e091af117..e06703665 100644 --- a/js/src/types/event.model.ts +++ b/js/src/types/event.model.ts @@ -53,6 +53,7 @@ interface IEventEditJSON { options: IEventOptions; contacts: { id?: string }[]; metadata: IEventMetadata[]; + category: string; } export interface IEvent { @@ -91,6 +92,7 @@ export interface IEvent { metadata: IEventMetadata[]; contacts: IActor[]; language: string; + category: string; toEditJSON(): IEventEditJSON; } @@ -166,6 +168,8 @@ export class EventModel implements IEvent { metadata: IEventMetadata[] = []; + category = "MEETING"; + constructor(hash?: IEvent | IEditableEvent) { if (!hash) return; @@ -214,6 +218,7 @@ export class EventModel implements IEvent { this.tags = hash.tags; this.metadata = hash.metadata; this.language = hash.language; + this.category = hash.category; if (hash.options) this.options = hash.options; } @@ -240,6 +245,7 @@ export function toEditJSON(event: IEditableEvent): IEventEditJSON { beginsOn: event.beginsOn ? event.beginsOn.toISOString() : null, endsOn: event.endsOn ? event.endsOn.toISOString() : null, status: event.status, + category: event.category, visibility: event.visibility, joinOptions: event.joinOptions, draft: event.draft, diff --git a/js/src/utils/categories.ts b/js/src/utils/categories.ts new file mode 100644 index 000000000..981659c4c --- /dev/null +++ b/js/src/utils/categories.ts @@ -0,0 +1,68 @@ +import { i18n } from "@/utils/i18n"; + +export const eventCategories = [ + { + id: "ARTS", + label: i18n.t("Arts"), + icon: "palette", + }, + { + id: "BOOK_CLUBS", + label: i18n.t("Book Clubs"), + icon: "favourite-book", + }, + { + id: "BUSINESS", + label: i18n.t("Business"), + }, + { + id: "CAUSES", + label: i18n.t("Causes"), + }, + { + id: "COMEDY", + label: i18n.t("Comedy"), + }, + { + id: "CRAFTS", + label: i18n.t("Crafts"), + }, + { + id: "FOOD_DRINK", + label: i18n.t("Food & Drink"), + }, + { + id: "HEALTH", + label: i18n.t("Health"), + }, + { + id: "MUSIC", + label: i18n.t("Music"), + }, + { + id: "AUTO_BOAT_AIR", + label: i18n.t("Auto, Boat & Air"), + }, + { + id: "COMMUNITY", + label: i18n.t("Community"), + }, + { + id: "FAMILY_EDUCATION", + label: i18n.t("Family & Education"), + }, + { + id: "FASHION_BEAUTY", + label: i18n.t("Fashion & Beauty"), + }, + { + id: "FILM_MEDIA", + label: i18n.t("Film & Media"), + }, + { + id: "GAMES", + label: i18n.t("Games"), + }, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore +].sort(({ label: label1 }, { label: label2 }) => label1.localeCompare(label2)); diff --git a/js/src/views/Event/Edit.vue b/js/src/views/Event/Edit.vue index efb20bf29..9a3e0c0dd 100644 --- a/js/src/views/Event/Edit.vue +++ b/js/src/views/Event/Edit.vue @@ -32,7 +32,28 @@ /> - +
+ + + + + + +
-

+

+ {{ + eventCategory + }} { + return eventCategory.id === this.event?.category; + })?.label as string; + } } diff --git a/lib/graphql/schema.ex b/lib/graphql/schema.ex index f8b143d05..e89641b4a 100644 --- a/lib/graphql/schema.ex +++ b/lib/graphql/schema.ex @@ -22,12 +22,15 @@ defmodule Mobilizon.GraphQL.Schema do alias Mobilizon.Events.{Event, Participant} alias Mobilizon.GraphQL.Middleware.{CurrentActorProvider, ErrorHandler} alias Mobilizon.GraphQL.Schema + alias Mobilizon.GraphQL.Schema.Custom alias Mobilizon.Storage.Repo + @pipeline_modifier Custom.EnumTypes + import_types(Absinthe.Type.Custom) import_types(Absinthe.Plug.Types) - import_types(Schema.Custom.UUID) - import_types(Schema.Custom.Point) + import_types(Custom.UUID) + import_types(Custom.Point) import_types(Schema.ActivityType) import_types(Schema.UserType) diff --git a/lib/graphql/schema/custom/enum_types.ex b/lib/graphql/schema/custom/enum_types.ex new file mode 100644 index 000000000..781449ec7 --- /dev/null +++ b/lib/graphql/schema/custom/enum_types.ex @@ -0,0 +1,109 @@ +defmodule Mobilizon.GraphQL.Schema.Custom.EnumTypes do + alias Absinthe.Blueprint.Schema + alias Absinthe.Schema.Notation + alias Absinthe.{Blueprint, Pipeline, Phase} + + @categories [ + %{ + id: :arts, + label: "ARTS" + }, + %{ + id: :book_clubs, + label: "BOOK_CLUBS" + }, + %{ + id: :business, + label: "BUSINESS" + }, + %{ + id: :causes, + label: "CAUSES" + }, + %{ + id: :comedy, + label: "COMEDY" + }, + %{ + id: :crafts, + label: "CRAFTS" + }, + %{ + id: :food_drink, + label: "FOOD_DRINK" + }, + %{ + id: :health, + label: "HEALTH" + }, + %{ + id: :music, + label: "MUSIC" + }, + %{ + id: :auto_boat_air, + label: "AUTO_BOAT_AIR" + }, + %{ + id: :community, + label: "COMMUNITY" + }, + %{ + id: :family_education, + label: "FAMILY_EDUCATION" + }, + %{ + id: :fashion_beauty, + label: "FASHION_BEAUTY" + }, + %{ + id: :film_media, + label: "FILM_MEDIA" + }, + %{ + id: :games, + label: "GAMES" + }, + # Legacy default value + %{ + id: :meeting, + label: "MEETING" + } + ] + + def pipeline(pipeline) do + Pipeline.insert_after(pipeline, Phase.Schema.TypeImports, __MODULE__) + end + + def run(blueprint = %Blueprint{}, _) do + %{schema_definitions: [schema]} = blueprint + + new_enum = build_dynamic_enum() + + schema = + Map.update!(schema, :type_definitions, fn type_definitions -> + [new_enum | type_definitions] + end) + + {:ok, %{blueprint | schema_definitions: [schema]}} + end + + def build_dynamic_enum() do + %Schema.EnumTypeDefinition{ + name: "EventCategory", + identifier: :event_category, + module: __MODULE__, + __reference__: Notation.build_reference(__ENV__), + values: + Enum.map(@categories, fn %{id: id, label: label} -> + %Schema.EnumValueDefinition{ + identifier: id, + value: label, + name: label, + module: __MODULE__, + __reference__: Notation.build_reference(__ENV__) + } + end) + } + end +end diff --git a/lib/graphql/schema/event.ex b/lib/graphql/schema/event.ex index 503c6e7e9..9a4dd9ea3 100644 --- a/lib/graphql/schema/event.ex +++ b/lib/graphql/schema/event.ex @@ -66,7 +66,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do description: "The event's tags" ) - field(:category, :string, description: "The event's category") + field(:category, :event_category, description: "The event's category") field(:draft, :boolean, description: "Whether or not the event is a draft") @@ -399,7 +399,11 @@ defmodule Mobilizon.GraphQL.Schema.EventType do arg(:attributed_to_id, :id, description: "Who the event is attributed to ID (often a group)") - arg(:category, :string, default_value: "meeting", description: "The event's category") + arg(:category, :event_category, + default_value: "MEETING", + description: "The event's category" + ) + arg(:physical_address, :address_input, description: "The event's physical address") arg(:options, :event_options_input, default_value: %{}, description: "The event options") arg(:metadata, list_of(:event_metadata_input), description: "The event metadata") @@ -448,7 +452,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do arg(:attributed_to_id, :id, description: "Who the event is attributed to ID (often a group)") - arg(:category, :string, description: "The event's category") + arg(:category, :event_category, description: "The event's category") arg(:physical_address, :address_input, description: "The event's physical address") arg(:options, :event_options_input, description: "The event options") arg(:metadata, list_of(:event_metadata_input), description: "The event metadata") diff --git a/lib/graphql/schema/search.ex b/lib/graphql/schema/search.ex index db0c7be58..2da1d8fa4 100644 --- a/lib/graphql/schema/search.ex +++ b/lib/graphql/schema/search.ex @@ -93,6 +93,7 @@ defmodule Mobilizon.GraphQL.Schema.SearchType do arg(:tags, :string, description: "A comma-separated string listing the tags") arg(:location, :string, description: "A geohash for coordinates") arg(:type, :event_type, description: "Whether the event is online or in person") + arg(:category, :string, description: "The category for the event") arg(:radius, :float, default_value: 50, diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index 72656e1d7..6d4b1a50f 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -54,14 +54,6 @@ defmodule Mobilizon.Events do :cancelled ]) - defenum(EventCategory, :event_category, [ - :business, - :conference, - :birthday, - :demonstration, - :meeting - ]) - defenum(ParticipantRole, :participant_role, [ :not_approved, :not_confirmed, @@ -536,6 +528,7 @@ defmodule Mobilizon.Events do |> events_for_search_query() |> events_for_begins_on(args) |> events_for_ends_on(args) + |> events_for_category(args) |> events_for_tags(args) |> events_for_location(args) |> filter_online(args) @@ -1313,6 +1306,13 @@ defmodule Mobilizon.Events do end end + @spec events_for_category(Ecto.Queryable.t(), map()) :: Ecto.Query.t() + defp events_for_category(query, %{category: category}) when is_valid_string(category) do + where(query, [q], q.category == ^category) + end + + defp events_for_category(query, _args), do: query + @spec events_for_tags(Ecto.Queryable.t(), map()) :: Ecto.Query.t() defp events_for_tags(query, %{tags: tags}) when is_valid_string(tags) do query diff --git a/mix.exs b/mix.exs index 20375bb1d..882bd7b49 100644 --- a/mix.exs +++ b/mix.exs @@ -299,7 +299,6 @@ defmodule Mobilizon.Mixfile do Mobilizon.Events.Tag, Mobilizon.Events.TagRelations, Mobilizon.Events.Track, - Mobilizon.Events.EventCategory, Mobilizon.Events.EventStatus, Mobilizon.Events.EventVisibility, Mobilizon.Events.JoinOptions, diff --git a/priv/repo/migrations/20220328153640_set_all_events_category_to_meeting.exs b/priv/repo/migrations/20220328153640_set_all_events_category_to_meeting.exs new file mode 100644 index 000000000..b51fa3b9e --- /dev/null +++ b/priv/repo/migrations/20220328153640_set_all_events_category_to_meeting.exs @@ -0,0 +1,11 @@ +defmodule Mobilizon.Storage.Repo.Migrations.SetAllEventsCategoryToMeeting do + use Ecto.Migration + + def up do + Ecto.Migration.execute("UPDATE events SET category = 'MEETING'") + end + + def down do + Ecto.Migration.execute("UPDATE events SET category = 'meeting' WHERE category = 'MEETING'") + end +end diff --git a/test/graphql/resolvers/event_test.exs b/test/graphql/resolvers/event_test.exs index 23b1bb8ec..79cdc16ae 100644 --- a/test/graphql/resolvers/event_test.exs +++ b/test/graphql/resolvers/event_test.exs @@ -21,7 +21,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do begins_on: DateTime.utc_now() |> DateTime.truncate(:second), uuid: "b5126423-f1af-43e4-a923-002a03003ba4", url: "some url", - category: "meeting" + category: "MEETING" } @find_event_query """ diff --git a/test/graphql/resolvers/participant_test.exs b/test/graphql/resolvers/participant_test.exs index 80d3c7b06..52194029f 100644 --- a/test/graphql/resolvers/participant_test.exs +++ b/test/graphql/resolvers/participant_test.exs @@ -20,7 +20,7 @@ defmodule Mobilizon.GraphQL.Resolvers.ParticipantTest do |> DateTime.truncate(:second), uuid: "b5126423-f1af-43e4-a923-002a03003ba4", url: "some url", - category: "meeting", + category: "MEETING", options: %{} } diff --git a/test/mobilizon/events/events_test.exs b/test/mobilizon/events/events_test.exs index efc0ae550..1e76249e0 100644 --- a/test/mobilizon/events/events_test.exs +++ b/test/mobilizon/events/events_test.exs @@ -17,7 +17,7 @@ defmodule Mobilizon.EventsTest do title: "some title", url: "some url", uuid: "b5126423-f1af-43e4-a923-002a03003ba4", - category: "meeting" + category: "MEETING" } describe "list_events/5" do