diff --git a/.gitignore b/.gitignore index f4b9c99fb..a6f2826c3 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ # Mac finder artifacts .DS_Store .ruby-gemset + +public/sitemap.xml +public/system/ diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 000000000..be2d522a4 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,56 @@ +inherit_from: .rubocop_todo.yml + +AllCops: + Include: + - '**/Rakefile' + - '**/config.ru' + Exclude: + - 'db/**/*' + - 'config/**/*' + - 'script/**/*' + TargetRubyVersion: 2.3 + +Rails: + Enabled: true + +Documentation: + Enabled: false + +Metrics/LineLength: + Max: 140 + +Style/IndentationConsistency: + EnforcedStyle: rails + +Style/StringLiterals: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: false + +Style/PercentLiteralDelimiters: + Enabled: false + +Style/EmptyLinesAroundClassBody: + Enabled: false + +Style/EmptyLinesAroundBlockBody: + Enabled: false + +Style/EmptyLinesAroundModuleBody: + Enabled: false + +Style/SpaceBeforeBlockBraces: + Enabled: false + +Style/SpaceInsideBrackets: + Enabled: false + +Style/SpaceInsideHashLiteralBraces: + Enabled: false + +Style/SpaceInsideBlockBraces: + Enabled: false + +Style/TrailingBlankLines: + Enabled: false diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..443782795 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,838 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2016-11-25 13:34:56 +0100 using RuboCop version 0.45.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 3 +Lint/AmbiguousRegexpLiteral: + Exclude: + - 'app/helpers/verification_helper.rb' + - 'spec/features/verification/residence_spec.rb' + +# Offense count: 3 +# Configuration parameters: AllowSafeAssignment. +Lint/AssignmentInCondition: + Exclude: + - 'app/controllers/management/sessions_controller.rb' + - 'app/models/notification.rb' + - 'lib/capistrano/template.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AlignWith, SupportedStyles. +# SupportedStyles: either, start_of_block, start_of_line +Lint/BlockAlignment: + Exclude: + - 'spec/features/tracks_spec.rb' + - 'spec/models/proposal_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: AlignWith, SupportedStyles, AutoCorrect. +# SupportedStyles: start_of_line, def +Lint/DefEndAlignment: + Exclude: + - 'app/controllers/comments_controller.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: runtime_error, standard_error +Lint/InheritException: + Exclude: + - 'app/controllers/concerns/feature_flags.rb' + +# Offense count: 7 +Lint/NestedMethodDefinition: + Exclude: + - 'lib/acts_as_paranoid_aliases.rb' + +# Offense count: 13 +Lint/ParenthesesAsGroupedExpression: + Exclude: + - 'spec/features/admin/organizations_spec.rb' + - 'spec/features/debates_spec.rb' + - 'spec/features/proposals_spec.rb' + - 'spec/models/debate_spec.rb' + +# Offense count: 1 +Lint/UnderscorePrefixedVariableName: + Exclude: + - 'lib/manager_authenticator.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. +Lint/UnusedBlockArgument: + Exclude: + - 'app/controllers/admin/spending_proposals_controller.rb' + - 'app/models/ahoy/data_source.rb' + - 'spec/spec_helper.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. +Lint/UnusedMethodArgument: + Exclude: + - 'app/controllers/organizations/registrations_controller.rb' + - 'app/controllers/users/omniauth_callbacks_controller.rb' + - 'app/controllers/users/registrations_controller.rb' + - 'app/models/abilities/everyone.rb' + - 'app/models/abilities/valuator.rb' + +# Offense count: 135 +Lint/UselessAssignment: + Enabled: false + +# Offense count: 41 +Metrics/AbcSize: + Max: 44 + +# Offense count: 4 +# Configuration parameters: CountComments. +Metrics/ClassLength: + Max: 205 + +# Offense count: 5 +Metrics/CyclomaticComplexity: + Max: 8 + +# Offense count: 52 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives. +# URISchemes: http, https +Metrics/LineLength: + Max: 307 + +# Offense count: 28 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 38 + +# Offense count: 2 +# Configuration parameters: CountComments. +Metrics/ModuleLength: + Max: 193 + +# Offense count: 3 +Metrics/PerceivedComplexity: + Max: 11 + +# Offense count: 1 +# Cop supports --auto-correct. +Performance/RedundantBlockCall: + Exclude: + - 'app/mailers/mailer.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +Performance/RedundantMatch: + Exclude: + - 'app/controllers/valuation/spending_proposals_controller.rb' + - 'app/helpers/embed_videos_helper.rb' + +# Offense count: 2 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: strict, flexible +Rails/Date: + Exclude: + - 'app/controllers/concerns/commentable_actions.rb' + - 'app/models/direct_message.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: Whitelist. +# Whitelist: find_by_sql +Rails/DynamicFindBy: + Exclude: + - 'app/controllers/users/confirmations_controller.rb' + - 'app/controllers/users/registrations_controller.rb' + - 'spec/features/management/users_spec.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/FindBy: + Exclude: + - 'app/models/setting.rb' + - 'app/models/verification/email.rb' + - 'app/models/verification/management/email.rb' + - 'app/models/verification/residence.rb' + +# Offense count: 24 +# Cop supports --auto-correct. +# Configuration parameters: Include. +# Include: spec/**/*, test/**/* +Rails/HttpPositionalArguments: + Exclude: + - 'spec/controllers/admin/api/stats_controller_spec.rb' + - 'spec/controllers/concerns/has_filters_spec.rb' + - 'spec/controllers/concerns/has_orders_spec.rb' + - 'spec/controllers/debates_controller_spec.rb' + - 'spec/controllers/management/sessions_controller_spec.rb' + - 'spec/controllers/pages_controller_spec.rb' + - 'spec/controllers/users/registrations_controller_spec.rb' + +# Offense count: 12 +Rails/OutputSafety: + Exclude: + - 'app/controllers/spending_proposals_controller.rb' + - 'app/helpers/application_helper.rb' + - 'app/helpers/text_with_links_helper.rb' + - 'app/helpers/users_helper.rb' + - 'app/helpers/valuation_helper.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +Rails/PluralizationGrammar: + Exclude: + - 'spec/features/admin/banners_spec.rb' + - 'spec/features/debates_spec.rb' + - 'spec/features/proposals_spec.rb' + - 'spec/models/residence_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: referer, referrer +Rails/RequestReferer: + Exclude: + - 'app/controllers/users/sessions_controller.rb' + +# Offense count: 11 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: strict, flexible +Rails/TimeZone: + Exclude: + - 'lib/score_calculator.rb' + - 'spec/controllers/admin/api/stats_controller_spec.rb' + - 'spec/models/ahoy/data_source_spec.rb' + +# Offense count: 7 +# Cop supports --auto-correct. +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/Validation: + Exclude: + - 'app/models/comment.rb' + - 'app/models/spending_proposal.rb' + - 'app/models/verification/residence.rb' + - 'app/models/verification/sms.rb' + +# Offense count: 9 +Style/AccessorMethodName: + Exclude: + - 'app/controllers/application_controller.rb' + - 'app/controllers/concerns/commentable_actions.rb' + - 'app/controllers/management/proposals_controller.rb' + - 'app/controllers/management/spending_proposals_controller.rb' + - 'app/controllers/proposals_controller.rb' + +# Offense count: 30 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle, SupportedLastArgumentHashStyles. +# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit +Style/AlignHash: + Exclude: + - 'spec/features/admin/banners_spec.rb' + +# Offense count: 21 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: with_first_parameter, with_fixed_indentation +Style/AlignParameters: + Exclude: + - 'app/helpers/search_helper.rb' + - 'app/models/verification/management/email.rb' + - 'bin/rspec' + - 'bin/spring' + - 'spec/features/admin/spending_proposals_spec.rb' + - 'spec/features/spending_proposals_spec.rb' + - 'spec/features/valuation/spending_proposals_spec.rb' + - 'spec/features/verification/verified_user_spec.rb' + - 'spec/i18n_spec.rb' + - 'spec/models/user_spec.rb' + - 'spec/rails_helper.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: always, conditionals +Style/AndOr: + Exclude: + - 'app/helpers/embed_videos_helper.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. +# SupportedStyles: line_count_based, semantic, braces_for_chaining +# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object +# FunctionalMethods: let, let!, subject, watch +# IgnoredMethods: lambda, proc, it +Style/BlockDelimiters: + Exclude: + - 'spec/features/users_auth_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/BlockEndNewline: + Exclude: + - 'app/models/banner.rb' + - 'spec/features/users_auth_spec.rb' + +# Offense count: 19 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: braces, no_braces, context_dependent +Style/BracesAroundHashParameters: + Exclude: + - 'app/controllers/valuation/spending_proposals_controller.rb' + - 'app/models/concerns/searchable.rb' + - 'app/models/verification/residence.rb' + - 'lib/manager_authenticator.rb' + - 'spec/controllers/management/users_controller_spec.rb' + - 'spec/features/admin/spending_proposals_spec.rb' + - 'spec/lib/manager_authenticator_spec.rb' + - 'spec/models/residence_spec.rb' + - 'spec/models/user_spec.rb' + +# Offense count: 57 +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: nested, compact +Style/ClassAndModuleChildren: + Enabled: false + +# Offense count: 5 +Style/ClassVars: + Exclude: + - 'app/models/concerns/measurable.rb' + - 'app/models/organization.rb' + - 'app/models/user.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/ClosingParenthesisIndentation: + Exclude: + - 'spec/rails_helper.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'app/controllers/comments_controller.rb' + - 'app/controllers/management/spending_proposals_controller.rb' + - 'app/controllers/spending_proposals_controller.rb' + - 'app/controllers/verification/sms_controller.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: leading, trailing +Style/DotPosition: + Exclude: + - 'app/models/banner.rb' + - 'app/models/tag_cloud.rb' + - 'app/models/verification/management/managed_user.rb' + +# Offense count: 1 +Style/DoubleNegation: + Exclude: + - 'app/models/flag.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/EmptyCaseCondition: + Exclude: + - 'app/models/concerns/verification.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +Style/EmptyLines: + Exclude: + - 'app/models/concerns/search_cache.rb' + - 'app/models/notification.rb' + - 'spec/features/admin/spending_proposals_spec.rb' + - 'spec/features/admin/verifications_spec.rb' + - 'spec/features/debates_spec.rb' + - 'spec/features/registration_form_spec.rb' + - 'spec/features/users_auth_spec.rb' + - 'spec/features/verification/verified_user_spec.rb' + - 'spec/support/verifiable.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/EmptyLinesAroundAccessModifier: + Exclude: + - 'app/controllers/users_controller.rb' + +# Offense count: 29 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. +Style/ExtraSpacing: + Enabled: false + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: consistent, special_for_inner_method_call, special_for_inner_method_call_in_parentheses +Style/FirstParameterIndentation: + Exclude: + - 'app/controllers/users_controller.rb' + +# Offense count: 32 +# Configuration parameters: MinBodyLength. +Style/GuardClause: + Enabled: false + +# Offense count: 10 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. +# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys +Style/HashSyntax: + Exclude: + - 'lib/sms_api.rb' + - 'spec/factories.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: MaxLineLength. +Style/IfUnlessModifier: + Exclude: + - 'app/controllers/annotations_controller.rb' + - 'app/controllers/verification/letter_controller.rb' + - 'app/controllers/welcome_controller.rb' + - 'app/helpers/embed_videos_helper.rb' + - 'app/models/proposal.rb' + - 'app/models/spending_proposal.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Style/IndentArray: + EnforcedStyle: consistent + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces +Style/IndentHash: + EnforcedStyle: consistent + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: normal, rails +Style/IndentationConsistency: + Exclude: + - 'spec/features/tracks_spec.rb' + - 'spec/models/proposal_spec.rb' + +# Offense count: 11 +# Cop supports --auto-correct. +# Configuration parameters: Width. +Style/IndentationWidth: + Exclude: + - 'app/controllers/annotations_controller.rb' + - 'app/helpers/flags_helper.rb' + - 'app/mailers/devise_mailer.rb' + - 'app/mailers/mailer.rb' + - 'app/models/ahoy/data_source.rb' + - 'app/models/comment_notifier.rb' + - 'app/models/concerns/search_cache.rb' + - 'spec/features/comments/proposals_spec.rb' + - 'spec/features/moderation/users_spec.rb' + - 'spec/features/tracks_spec.rb' + - 'spec/features/verification/verified_user_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: line_count_dependent, lambda, literal +Style/Lambda: + Exclude: + - 'app/models/banner.rb' + - 'app/models/direct_message.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/MethodCallParentheses: + Exclude: + - 'app/controllers/management/document_verifications_controller.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline +Style/MethodDefParentheses: + Exclude: + - 'spec/helpers/comments_helper_spec.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: symmetrical, new_line, same_line +Style/MultilineArrayBraceLayout: + Exclude: + - 'app/controllers/valuation/spending_proposals_controller.rb' + - 'app/helpers/search_helper.rb' + +# Offense count: 1 +Style/MultilineBlockChain: + Exclude: + - 'app/controllers/valuation/spending_proposals_controller.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/MultilineBlockLayout: + Exclude: + - 'app/models/banner.rb' + - 'spec/features/users_auth_spec.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: symmetrical, new_line, same_line +Style/MultilineHashBraceLayout: + Exclude: + - 'app/controllers/valuation/spending_proposals_controller.rb' + - 'app/models/debate.rb' + - 'app/models/proposal.rb' + - 'app/models/spending_proposal.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/MultilineIfThen: + Exclude: + - 'app/controllers/management/users_controller.rb' + - 'lib/census_api.rb' + +# Offense count: 6 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: symmetrical, new_line, same_line +Style/MultilineMethodCallBraceLayout: + Exclude: + - 'app/controllers/users_controller.rb' + - 'app/models/comment.rb' + - 'app/models/organization.rb' + - 'app/models/user.rb' + - 'spec/rails_helper.rb' + +# Offense count: 33 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: aligned, indented, indented_relative_to_receiver +Style/MultilineMethodCallIndentation: + Exclude: + - 'app/models/banner.rb' + - 'app/models/tag_cloud.rb' + - 'app/models/verification/management/managed_user.rb' + - 'spec/models/comment_spec.rb' + - 'spec/models/debate_spec.rb' + - 'spec/models/proposal_spec.rb' + - 'spec/models/user_spec.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: aligned, indented +Style/MultilineOperationIndentation: + Exclude: + - 'app/helpers/users_helper.rb' + - 'app/helpers/valuation_helper.rb' + - 'app/models/verification/letter.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +Style/MutableConstant: + Exclude: + - 'app/models/activity.rb' + - 'app/models/proposal.rb' + - 'lib/tag_sanitizer.rb' + - 'lib/wysiwyg_sanitizer.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/NegatedIf: + Exclude: + - 'spec/spec_helper.rb' + +# Offense count: 17 +# Cop supports --auto-correct. +Style/NestedParenthesizedCalls: + Exclude: + - 'spec/features/debates_spec.rb' + - 'spec/features/valuation/spending_proposals_spec.rb' + - 'spec/helpers/settings_helper_spec.rb' + - 'spec/helpers/verification_helper_spec.rb' + +# Offense count: 12 +# Cop supports --auto-correct. +Style/NumericLiterals: + MinDigits: 9 + +# Offense count: 19 +# Cop supports --auto-correct. +# Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'spec/**/*' + - 'app/controllers/users_controller.rb' + - 'app/controllers/valuation/spending_proposals_controller.rb' + - 'app/helpers/banners_helper.rb' + - 'app/helpers/debates_helper.rb' + - 'app/helpers/votes_helper.rb' + - 'app/models/concerns/conflictable.rb' + - 'app/models/concerns/taggable.rb' + - 'app/models/concerns/verification.rb' + - 'app/models/debate.rb' + - 'app/models/lock.rb' + - 'app/models/user.rb' + - 'app/models/verification/management/email.rb' + - 'lib/score_calculator.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/ParallelAssignment: + Exclude: + - 'lib/active_model/dates.rb' + - 'spec/support/common_actions.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: AllowSafeAssignment. +Style/ParenthesesAroundCondition: + Exclude: + - 'app/controllers/proposals_controller.rb' + - 'app/models/debate.rb' + - 'app/models/proposal.rb' + +# Offense count: 6 +# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. +# NamePrefix: is_, has_, have_ +# NamePrefixBlacklist: is_, has_, have_ +# NameWhitelist: is_a? +Style/PredicateName: + Exclude: + - 'spec/**/*' + - 'app/controllers/concerns/has_filters.rb' + - 'app/controllers/concerns/has_orders.rb' + - 'app/helpers/banners_helper.rb' + - 'app/helpers/debates_helper.rb' + - 'app/models/user.rb' + - 'lib/census_api.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: compact, exploded +Style/RaiseArgs: + Exclude: + - 'app/controllers/management/base_controller.rb' + - 'app/controllers/users/omniauth_callbacks_controller.rb' + - 'app/controllers/valuation/spending_proposals_controller.rb' + +# Offense count: 16 +# Cop supports --auto-correct. +Style/RedundantParentheses: + Exclude: + - 'app/controllers/admin/settings_controller.rb' + - 'app/helpers/proposals_helper.rb' + - 'spec/features/admin/organizations_spec.rb' + - 'spec/features/debates_spec.rb' + - 'spec/features/proposals_spec.rb' + - 'spec/models/debate_spec.rb' + +# Offense count: 5 +# Cop supports --auto-correct. +# Configuration parameters: AllowMultipleReturnValues. +Style/RedundantReturn: + Exclude: + - 'app/models/ahoy/data_source.rb' + - 'app/models/verification/management/document.rb' + - 'lib/capistrano/template.rb' + - 'lib/census_api.rb' + +# Offense count: 49 +# Cop supports --auto-correct. +Style/RedundantSelf: + Enabled: false + +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. +# SupportedStyles: slashes, percent_r, mixed +Style/RegexpLiteral: + Exclude: + - 'app/helpers/embed_videos_helper.rb' + - 'spec/customization_engine_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/RescueModifier: + Exclude: + - 'app/controllers/concerns/commentable_actions.rb' + - 'app/controllers/verification/sms_controller.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/SpaceAfterColon: + Exclude: + - 'spec/models/user_spec.rb' + +# Offense count: 14 +# Cop supports --auto-correct. +Style/SpaceAfterComma: + Exclude: + - 'app/models/ahoy/data_source.rb' + - 'app/models/banner.rb' + - 'app/models/concerns/search_cache.rb' + - 'app/models/concerns/taggable.rb' + - 'app/models/spending_proposal.rb' + - 'app/models/user.rb' + - 'spec/features/valuation/spending_proposals_spec.rb' + - 'spec/lib/census_api_spec.rb' + - 'spec/models/abilities/moderator_spec.rb' + - 'spec/models/verification/management/email_spec.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/SpaceAfterNot: + Exclude: + - 'app/models/flag.rb' + +# Offense count: 20 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceAroundEqualsInParameterDefault: + Exclude: + - 'app/helpers/cache_keys_helper.rb' + - 'app/helpers/proposals_helper.rb' + - 'app/helpers/spending_proposals_helper.rb' + - 'app/helpers/stats_helper.rb' + - 'app/helpers/tracks_helper.rb' + - 'app/helpers/valuation_helper.rb' + - 'app/mailers/devise_mailer.rb' + - 'app/models/comment.rb' + - 'app/models/tag_cloud.rb' + - 'app/models/verification/residence.rb' + - 'lib/acts_as_paranoid_aliases.rb' + - 'lib/capistrano/template.rb' + - 'lib/census_api.rb' + - 'lib/manager_authenticator.rb' + - 'spec/support/common_actions.rb' + +# Offense count: 66 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Style/SpaceAroundOperators: + Enabled: false + +# Offense count: 6 +# Cop supports --auto-correct. +Style/SpaceBeforeComma: + Exclude: + - 'app/models/proposal.rb' + - 'spec/controllers/management/sessions_controller_spec.rb' + - 'spec/controllers/management/users_controller_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: AllowForAlignment. +Style/SpaceBeforeFirstArg: + Exclude: + - 'spec/factories.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: require_no_space, require_space +Style/SpaceInLambdaLiteral: + Exclude: + - 'app/models/concerns/filterable.rb' + - 'app/models/spending_proposal.rb' + - 'app/models/user.rb' + - 'app/models/verified_user.rb' + +# Offense count: 8 +# Cop supports --auto-correct. +Style/SpaceInsideParens: + Exclude: + - 'app/models/user.rb' + - 'lib/manager_authenticator.rb' + - 'spec/features/proposals_spec.rb' + - 'spec/models/abilities/moderator_spec.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/SpaceInsidePercentLiteralDelimiters: + Exclude: + - 'app/models/activity.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: single_quotes, double_quotes +Style/StringLiteralsInInterpolation: + Exclude: + - 'app/controllers/stats_controller.rb' + - 'app/models/proposal.rb' + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: respond_to, define_method +Style/SymbolProc: + Exclude: + - 'app/controllers/admin/settings_controller.rb' + - 'app/controllers/notifications_controller.rb' + - 'app/controllers/organizations/registrations_controller.rb' + - 'lib/manager_authenticator.rb' + - 'spec/factories.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Style/TrailingWhitespace: + Exclude: + - 'app/controllers/admin/api/stats_controller.rb' + +# Offense count: 31 +# Cop supports --auto-correct. +Style/UnneededInterpolation: + Exclude: + - 'app/controllers/users/omniauth_callbacks_controller.rb' + - 'spec/factories.rb' + - 'spec/features/campaigns_spec.rb' + - 'spec/features/management/managed_users_spec.rb' + - 'spec/features/management/proposals_spec.rb' + - 'spec/features/management/spending_proposals_spec.rb' + - 'spec/models/residence_spec.rb' + - 'spec/models/spending_proposal_spec.rb' + +# Offense count: 13 +# Configuration parameters: SupportedStyles. +# SupportedStyles: snake_case, normalcase, non_integer +Style/VariableNumber: + EnforcedStyle: normalcase + +# Offense count: 9 +# Cop supports --auto-correct. +# Configuration parameters: SupportedStyles, WordRegex. +# SupportedStyles: percent, brackets +Style/WordArray: + EnforcedStyle: percent + MinSize: 3 + +# Offense count: 1 +# Cop supports --auto-correct. +Style/ZeroLengthPredicate: + Exclude: + - 'app/models/concerns/verification.rb' diff --git a/.ruby-version b/.ruby-version index 585940699..e7034819f 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.2.3 +2.3.2 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index a45f36990..7d5f6169a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,16 @@ language: ruby addons: postgresql: "9.4" rvm: - - "2.2.3" + - "2.3.2" cache: bundler before_script: - "for i in config/*.example; do cp \"$i\" \"${i/.example}\"; done" - bundle exec rake db:setup -script: bundle exec rake +script: + - "bundle exec rake knapsack:rspec" +env: + global: + - CI_NODE_TOTAL=2 + matrix: + - CI_NODE_INDEX=0 + - CI_NODE_INDEX=1 \ No newline at end of file diff --git a/CONTRIBUTING_EN.md b/CONTRIBUTING_EN.md index 0c322d506..17f93bc63 100644 --- a/CONTRIBUTING_EN.md +++ b/CONTRIBUTING_EN.md @@ -34,6 +34,16 @@ If you want to contribute code to solve an issue: * Follow these [best practices](https://github.com/styleguide/ruby) * Open a *pull request* to the main repository describing what issue you are addressing. +## Cleaning up + +In the rush of time sometimes things get messy, you can help us cleaning things up: + +* implement [pending specs](https://travis-ci.org/consul/consul) +* increase [code coverage](https://coveralls.io/github/consul/consul?branch=master) +* improve [code quality](https://codeclimate.com/github/consul/consul) +* update [dependencies](https://gemnasium.com/consul/consul) +* make [code consistent](https://github.com/bbatsov/rubocop) + ## Other ways of contributing without coding * If you think there's a feature missing, or find a bug, create an issue (make sure it has not already been reported). diff --git a/CONTRIBUTING_ES.md b/CONTRIBUTING_ES.md index 7a8d10877..aaee9ec92 100644 --- a/CONTRIBUTING_ES.md +++ b/CONTRIBUTING_ES.md @@ -34,6 +34,16 @@ Cuando quieras resolver una incidencia mediante código: * Sigue estas [buenas prácticas](https://github.com/styleguide/ruby) * Envía una *pull request* al repositorio principal indicando la incidencia que se está arreglando +## Limpiar + +En la urgencia del momento, las cosas a veces se ensucian, puedes ayudarnos a limpiar la casa: + +* implementando [tests pendientes](https://travis-ci.org/consul/consul) +* incrementando la [cobertura de tests](https://coveralls.io/github/consul/consul?branch=master) +* mejorando la [calidad del código](https://codeclimate.com/github/consul/consul) +* actualizando [dependecias](https://gemnasium.com/consul/consul) +* haciendo el [código consistente](https://github.com/bbatsov/rubocop) + ## Otras formas de contribuir sin código * Si crees que hay una funcionalidad que hace falta, o descubres un problema, abre una incidencia (asegúrate de que diff --git a/CUSTOMIZE_ES.md b/CUSTOMIZE_ES.md new file mode 100644 index 000000000..33c91b268 --- /dev/null +++ b/CUSTOMIZE_ES.md @@ -0,0 +1,221 @@ +# Personalización + +Puedes modificar consul y ponerle tu propia imagen, para esto debes primero hacer un fork de [https://github.com/consul/consul](https://github.com/consul/consul) creando un repositorio nuevo en Github. Puedes usar otro servicio como Gitlab, pero no te olvides de poner el enlace en el footer a tu repositorio en cumplimiento con la licencia de este proyecto (GPL Affero 3). + +Hemos creado una estructura específica donde puedes sobreescribir y personalizar la aplicación para que puedas actualizar sin que tengas problemas al hacer merge y se sobreescriban por error tus cambios. Intentamos que Consul sea una aplicación Ruby on Rails lo más plain vanilla posible para facilitar el acceso de nuevas desarrolladoras. + +## Ficheros y directorios especiales + +Para adaptarlo puedes hacerlo a través de los directorios que están en custom dentro de: + +* `config/locales/custom/` +* `app/assets/images/custom/` +* `app/views/custom/` +* `app/controllers/custom/` +* `app/models/custom/` + +Aparte de estos directorios también cuentas con ciertos ficheros para: + +* `app/assets/stylesheets/custom.css` +* `app/assets/stylesheets/_custom_settings.css` +* `app/assets/javascripts/custom.js` +* `Gemfile_custom` +* `config/application.custom.rb` + +### Internacionalización + +Si quieres modificar algún texto de la web deberías encontrarlos en los ficheros formato YML disponibles en `config/locales/`. Puedes leer la [guía de internacionalización](http://guides.rubyonrails.org/i18n.html) de Ruby on Rails sobre como funciona este sistema. + +Las adaptaciones los debes poner en el directorio `config/locales/custom/`, recomendamos poner solo los textos que quieras personalizar. Por ejemplo si quieres personalizar el texto de "Ayuntamiento de Madrid, 2016" que se encuentra en el footer en todas las páginas, primero debemos ubicar en que plantilla se encuentra (`app/views/layouts/_footer.html.erb`), vemos que en el código pone lo siguiente: + +```ruby +<%= t("layouts.footer.copyright", year: Time.current.year) %> +``` + +Y que en el fichero `config/locales/es.yml` sigue esta estructura (solo ponemos lo relevante para este caso): + +```yml +es: + layouts: + footer: + copyright: Ayuntamiento de Madrid, %{year} + +``` + +Si creamos el fichero `config/locales/custom/es.yml` y modificamos "Ayuntamiento de Madrid" por el nombre de la organización que se este haciendo la modificación. Recomendamos directamente copiar los ficheros `config/locales/` e ir revisando y corrigiendo las que querramos, borrando las líneas que no querramos traducir. + +### Imágenes + +Si quieres sobreescribir alguna imagen debes primero fijarte el nombre que tiene, por defecto se encuentran en `app/assets/images`. Por ejemplo si quieres modificar `app/assets/images/logo_header.png` debes poner otra con ese mismo nombre en el directorio `app/assets/images/custom`. Los iconos que seguramente quieras modificar son: + +* apple-touch-icon-200.png +* icon_home.png +* logo_email.png +* logo_header.png +* map.jpg +* social-media-icon.png + +### Vistas (HTML) + +Si quieres modificar el HTML de alguna página puedes hacerlo copiando el HTML de `app/views` y poniendolo en `app/views/custom` respetando los subdirectorios que encuentres ahí. Por ejemplo si quieres modificar `app/views/pages/conditions.html` debes copiarlo y modificarla en `app/views/custom/pages/conditions.html.erb` + +### CSS + +Si quieres cambiar algun selector CSS (de las hojas de estilo) puedes hacerlo en el fichero `app/assets/stylesheets/custom.scss`. Por ejemplo si quieres cambiar el color del header (`.top-links`) puedes hacerlo agregando: + +```css +.top-links { + background: red; +} +``` +Si quieres cambiar alguna variable de foundation puedes hacerlo en el fichero `app/assets/stylesheets/_custom_settings.scss`. Por ejemplo para cambiar el color general de la aplicación puedes hacerlo agregando: + +```css +$brand: #446336; +``` + +Usamos un preprocesador de CSS, [SASS, con la sintaxis SCSS](http://sass-lang.com/guide). + +### Javascript + +Si quieres agregar código Javascript puedes hacerlo en el fichero `app/assets/javascripts/custom.js`. Por ejemplo si quieres que salga una alerta puedes poner lo siguiente: + +```js +$(function(){ + alert('foobar'); +}); +``` + +### Modelos + +Si quieres agregar modelos nuevos, o modificar o agregar métodos a uno ya existente puedes hacerlo en `app/models/custom`. En el caso de los modelos antiguos debes primero hacer un require de la dependencia. + +Por ejemplo en el caso del Ayuntamiento de Madrid se requiere comprobar que el código postal durante la verificación sigue un cierto formato (empieza con 280). Esto se realiza creando este fichero en `app/models/custom/verification/residence.rb`: + +```ruby +require_dependency Rails.root.join('app', 'models', 'verification', 'residence').to_s + +class Verification::Residence + + validate :postal_code_in_madrid + validate :residence_in_madrid + + def postal_code_in_madrid + errors.add(:postal_code, I18n.t('verification.residence.new.error_not_allowed_postal_code')) unless valid_postal_code? + end + + def residence_in_madrid + return if errors.any? + + unless residency_valid? + errors.add(:residence_in_madrid, false) + store_failed_attempt + Lock.increase_tries(user) + end + end + + private + + def valid_postal_code? + postal_code =~ /^280/ + end + +end +``` + +No olvides poner los tests relevantes en `spec/models/custom`, siguiendo con el ejemplo pondriamos lo siguiente en `spec/models/custom/residence_spec.rb`: + + +```ruby +require 'rails_helper' + +describe Verification::Residence do + + let(:residence) { build(:verification_residence, document_number: "12345678Z") } + + describe "verification" do + + describe "postal code" do + it "should be valid with postal codes starting with 280" do + residence.postal_code = "28012" + residence.valid? + expect(residence.errors[:postal_code].size).to eq(0) + + residence.postal_code = "28023" + residence.valid? + expect(residence.errors[:postal_code].size).to eq(0) + end + + it "should not be valid with postal codes not starting with 280" do + residence.postal_code = "12345" + residence.valid? + expect(residence.errors[:postal_code].size).to eq(1) + + residence.postal_code = "13280" + residence.valid? + expect(residence.errors[:postal_code].size).to eq(1) + expect(residence.errors[:postal_code]).to include("In order to be verified, you must be registered in the municipality of Madrid.") + end + end + + end + +end +``` + +### Controladores + +TODO + +### Gemfile + +Para agregar librerías (gems) nuevas puedes hacerlo en el fichero `Gemfile_custom`. Por ejemplo si quieres agregar la gema [rails-footnotes](https://github.com/josevalim/rails-footnotes) debes hacerlo agregandole + +```ruby +gem 'rails-footnotes', '~> 4.0' +``` + +Y siguiendo el flujo clásico en Ruby on Rails (`bundle install` y seguir con los pasos específicos de la gema en la documentación) + +### application.rb + +Cuando necesites extender o modificar el `config/application.rb` puedes hacerlo a través del fichero `config/application_custom.rb`. Por ejemplo si quieres modificar el idioma por defecto al inglés pondrías lo siguiente: + + +```ruby +module Consul + class Application < Rails::Application + config.i18n.default_locale = :en + config.i18n.available_locales = [:en, :es] + end +end +``` + +Recuerda que para ver reflejado estos cambios debes reiniciar el servidor de desarrollo. + +### lib/ + +TODO + +### public/ + +TODO + +### Seeds + +TODO + +## Actualizar + +Te recomendamos que agregues el remote de consul para facilitar este proceso de merge: + +``` +$ git remote add consul https://github.com/consul/consul +``` + +Con esto puedes actualizarte con + +``` +git checkout -b consul_update +git pull consul master +``` diff --git a/Capfile b/Capfile index 372653a5e..ea11eb9b6 100644 --- a/Capfile +++ b/Capfile @@ -4,13 +4,17 @@ require 'capistrano/setup' # Include default deployment tasks require 'capistrano/deploy' -require 'capistrano/rvm' -require 'capistrano/bundler' +require "capistrano/bundler" require 'capistrano/rails/assets' require 'capistrano/rails/migrations' #require 'capistrano/passenger' -require 'capistrano/delayed-job' +require 'capistrano/delayed_job' require 'whenever/capistrano' +require 'rvm1/capistrano3' + +#SCM: Git +require "capistrano/scm/git" +install_plugin Capistrano::SCM::Git # Load custom tasks from `lib/capistrano/tasks` if you have any defined Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } diff --git a/Gemfile b/Gemfile index e925fb642..05d1379c5 100644 --- a/Gemfile +++ b/Gemfile @@ -1,64 +1,71 @@ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '4.2.6' +gem 'rails', '4.2.8' # Use PostgreSQL -gem 'pg' +gem 'pg', '~> 0.20.0' # Use SCSS for stylesheets gem 'sass-rails', '~> 5.0', '>= 5.0.4' # Use Uglifier as compressor for JavaScript assets -gem 'uglifier', '>= 1.3.0' +gem 'uglifier', '~> 3.2.0' # Use CoffeeScript for .coffee assets and views -gem 'coffee-rails', '~> 4.1.0' +gem 'coffee-rails', '~> 4.2.1' # See https://github.com/rails/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use jquery as the JavaScript library -gem 'jquery-rails' +gem 'jquery-rails', '~> 4.3.1' gem 'jquery-ui-rails' # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks gem 'turbolinks' -gem 'devise', '~> 3.5.6' +# Fix sprockets on the +gem 'sprockets', '~> 3.7.1' + +gem 'devise', '~> 3.5.7' +gem 'devise_security_extension' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' gem 'omniauth' gem 'omniauth-twitter' -gem 'omniauth-facebook', '~> 3.0.0' +gem 'omniauth-facebook', '~> 4.0.0' gem 'omniauth-google-oauth2', '~> 0.4.0' -gem 'kaminari' -gem 'ancestry' +gem 'kaminari', '~> 1.0.1' +gem 'ancestry', '~> 2.2.2' gem 'acts-as-taggable-on' -gem 'responders' -gem 'foundation-rails' -gem 'foundation_rails_helper' +gem 'responders', '~> 2.4.0' +gem 'foundation-rails', '~> 6.2.4.0' +gem 'foundation_rails_helper', '~> 2.0.0' gem 'acts_as_votable' -gem 'simple_captcha2', require: 'simple_captcha' -gem 'ckeditor', '~> 4.1.5' -gem 'cancancan' -gem 'social-share-button', git: 'https://github.com/huacnlee/social-share-button.git', ref: 'e46a6a3e82b86023bc' -gem 'initialjs-rails', '0.2.0.1' -gem 'unicorn', '~> 5.1.0' -gem 'paranoia' -gem 'rinku', require: 'rails_rinku' +gem 'ckeditor', '~> 4.2.3' +gem 'invisible_captcha', '~> 0.9.2' +gem 'cancancan', '~> 1.16.0' +gem 'social-share-button', '~> 0.10' +gem 'initialjs-rails', '0.2.0.4' +gem 'unicorn', '~> 5.3.0' +gem 'paranoia', '~> 2.3.1' +gem 'rinku', '~> 2.0.2', require: 'rails_rinku' gem 'savon' gem 'dalli' -gem 'rollbar', '~> 2.8.0' +gem 'rollbar', '~> 2.14.1' gem 'delayed_job_active_record', '~> 4.1.0' gem 'daemons' gem 'devise-async' -gem 'newrelic_rpm', '~> 3.14' +gem 'newrelic_rpm', '~> 4.1.0.333' gem 'whenever', require: false gem 'pg_search' +gem 'sitemap_generator', '~> 5.3.1' -gem 'ahoy_matey', '~> 1.2.1' -gem 'groupdate' # group temporary data -gem 'tolk' # Web interface for translations +gem 'ahoy_matey', '~> 1.6.0' +gem 'groupdate', '~> 3.2.0' # group temporary data +gem 'tolk', '~> 2.0.0' # Web interface for translations gem 'browser' -gem 'turnout' -gem 'redcarpet' +gem 'turnout', '~> 2.4.0' +gem 'redcarpet', '~> 3.4.0' + +gem 'paperclip' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console @@ -66,31 +73,35 @@ group :development, :test do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-commands-rspec' - gem 'rspec-rails', '~> 3.3' - gem 'capybara' - gem 'factory_girl_rails' + gem 'rspec-rails', '~> 3.6' + gem 'capybara', '~> 2.14.0' + gem 'factory_girl_rails', '~> 4.8.0' gem 'fuubar' gem 'launchy' gem 'quiet_assets' - gem 'letter_opener_web', '~> 1.3.0' - gem 'i18n-tasks' - gem 'capistrano', '3.4.0', require: false - gem "capistrano-bundler", '1.1.4', require: false - gem "capistrano-rails", '1.1.6', require: false - gem "capistrano-rvm", require: false - gem 'capistrano3-delayed-job', '~> 1.0' - gem "bullet" - gem "faker" + gem 'letter_opener_web', '~> 1.3.1' + gem 'i18n-tasks', '~> 0.9.15' + gem 'capistrano', '~> 3.8.1', require: false + gem 'capistrano-bundler', '~> 1.2', require: false + gem "capistrano-rails", '~> 1.2.3', require: false + gem 'rvm1-capistrano3', require: false + gem 'capistrano3-delayed-job', '~> 1.7.3' + gem "bullet", '~> 5.5.1' + gem "faker", '~> 1.7.3' + gem 'rubocop', '~> 0.48.1', require: false + gem 'knapsack' end group :test do gem 'database_cleaner' - gem 'poltergeist' - gem 'coveralls', require: false + gem 'poltergeist', '~> 1.15.0' + gem 'coveralls', '~> 0.8.21', require: false gem 'email_spec' end group :development do # Access an IRB console on exception pages or by using <%= console %> in views - gem 'web-console', '~> 3.0' + gem 'web-console', '3.3.0' end + +eval_gemfile './Gemfile_custom' diff --git a/Gemfile.lock b/Gemfile.lock index 755561fb2..0989276ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,99 +1,92 @@ -GIT - remote: https://github.com/huacnlee/social-share-button.git - revision: e46a6a3e82b86023bc43b72c9379d3c6afe36b6b - ref: e46a6a3e82b86023bc - specs: - social-share-button (0.1.9) - coffee-rails - sass-rails - GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.6) - actionpack (= 4.2.6) - actionview (= 4.2.6) - activejob (= 4.2.6) + actionmailer (4.2.8) + actionpack (= 4.2.8) + actionview (= 4.2.8) + activejob (= 4.2.8) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.6) - actionview (= 4.2.6) - activesupport (= 4.2.6) + actionpack (4.2.8) + actionview (= 4.2.8) + activesupport (= 4.2.8) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.6) - activesupport (= 4.2.6) + actionview (4.2.8) + activesupport (= 4.2.8) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.6) - activesupport (= 4.2.6) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (4.2.8) + activesupport (= 4.2.8) globalid (>= 0.3.0) - activemodel (4.2.6) - activesupport (= 4.2.6) + activemodel (4.2.8) + activesupport (= 4.2.8) builder (~> 3.1) - activerecord (4.2.6) - activemodel (= 4.2.6) - activesupport (= 4.2.6) + activerecord (4.2.8) + activemodel (= 4.2.8) + activesupport (= 4.2.8) arel (~> 6.0) - activesupport (4.2.6) + activesupport (4.2.8) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) - acts-as-taggable-on (3.5.0) - activerecord (>= 3.2, < 5) + acts-as-taggable-on (4.0.0) + activerecord (>= 4.0) acts_as_votable (0.10.0) - addressable (2.4.0) - ahoy_matey (1.2.1) + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + ahoy_matey (1.6.0) addressable - browser (>= 0.4.0) - errbase + browser (~> 2.0) geocoder - rails + rack-attack (< 6) + railties referer-parser (>= 0.3.0) request_store + safely_block (>= 0.1.1) user_agent_parser uuidtools + airbrussh (1.2.0) + sshkit (>= 1.6.1, != 1.7.0) akami (1.3.1) gyoku (>= 0.4.0) nokogiri - ancestry (2.1.0) + ancestry (2.2.2) activerecord (>= 3.0.0) - arel (6.0.3) - ast (2.2.0) + arel (6.0.4) + ast (2.3.0) babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) execjs (~> 2.0) - bcrypt (3.1.10) - browser (1.1.0) - builder (3.2.2) - bullet (5.0.0) + bcrypt (3.1.11) + browser (2.3.0) + builder (3.2.3) + bullet (5.5.1) activesupport (>= 3.0.0) - uniform_notifier (~> 1.9.0) - byebug (8.2.2) - cancancan (1.13.1) - capistrano (3.4.0) + uniform_notifier (~> 1.10.0) + byebug (9.0.6) + cancancan (1.16.0) + capistrano (3.8.1) + airbrussh (>= 1.0.0) i18n rake (>= 10.0.0) - sshkit (~> 1.3) - capistrano-bundler (1.1.4) + sshkit (>= 1.9.0) + capistrano-bundler (1.2.0) capistrano (~> 3.1) sshkit (~> 1.2) - capistrano-rails (1.1.6) + capistrano-rails (1.2.3) capistrano (~> 3.1) capistrano-bundler (~> 1.1) - capistrano-rvm (0.1.2) - capistrano (~> 3.0) - sshkit (~> 1.2) - capistrano3-delayed-job (1.6.0) - capistrano (>= 3.0.0) - capybara (2.6.2) + capistrano3-delayed-job (1.7.3) + capistrano (~> 3.0, >= 3.0.0) + daemons (~> 1.2.4) + capybara (2.14.0) addressable mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -101,95 +94,97 @@ GEM rack-test (>= 0.5.4) xpath (~> 2.0) chronic (0.10.2) - ckeditor (4.1.6) + ckeditor (4.2.3) cocaine orm_adapter (~> 0.5.0) - climate_control (0.0.3) - activesupport (>= 3.0) + climate_control (0.1.0) cliver (0.3.2) cocaine (0.5.8) climate_control (>= 0.0.3, < 1.0) - coffee-rails (4.1.1) + coffee-rails (4.2.1) coffee-script (>= 2.2.0) - railties (>= 4.0.0, < 5.1.x) + railties (>= 4.0.0, < 5.2.x) coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.10.0) - concurrent-ruby (1.0.1) - coveralls (0.8.13) - json (~> 1.8) - simplecov (~> 0.11.0) + coffee-script-source (1.12.2) + concurrent-ruby (1.0.5) + coveralls (0.8.21) + json (>= 1.8, < 3) + simplecov (~> 0.14.1) term-ansicolor (~> 1.3) - thor (~> 0.19.1) - tins (~> 1.6.0) - daemons (1.2.3) + thor (~> 0.19.4) + tins (~> 1.6) + daemons (1.2.4) dalli (2.7.6) - database_cleaner (1.5.1) + database_cleaner (1.5.3) debug_inspector (0.0.2) - delayed_job (4.1.1) - activesupport (>= 3.0, < 5.0) - delayed_job_active_record (4.1.0) - activerecord (>= 3.0, < 5) + delayed_job (4.1.2) + activesupport (>= 3.0, < 5.1) + delayed_job_active_record (4.1.1) + activerecord (>= 3.0, < 5.1) delayed_job (>= 3.0, < 5) - devise (3.5.6) + devise (3.5.10) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 3.2.6, < 5) responders thread_safe (~> 0.1) warden (~> 1.2.3) - devise-async (0.10.1) - devise (~> 3.2) - diff-lcs (1.2.5) + devise-async (0.10.2) + devise (>= 3.2, < 4.0) + devise_security_extension (0.10.0) + devise (>= 3.0.0, < 4.0) + railties (>= 3.2.6, < 5.0) + diff-lcs (1.3) docile (1.1.5) easy_translate (0.5.0) json thread thread_safe - email_spec (2.0.0) + email_spec (2.1.0) htmlentities (~> 4.3.3) launchy (~> 2.1) mail (~> 2.6.3) errbase (0.0.3) erubis (2.7.0) - execjs (2.6.0) - factory_girl (4.5.0) + execjs (2.7.0) + factory_girl (4.8.0) activesupport (>= 3.0.0) - factory_girl_rails (4.6.0) - factory_girl (~> 4.5.0) + factory_girl_rails (4.8.0) + factory_girl (~> 4.8.0) railties (>= 3.0.0) - faker (1.6.3) + faker (1.7.3) i18n (~> 0.5) - faraday (0.9.2) + faraday (0.11.0) multipart-post (>= 1.2, < 3) - foundation-rails (6.2.0.1) + foundation-rails (6.2.4.0) railties (>= 3.1.0) sass (>= 3.3.0, < 3.5) sprockets-es6 (>= 0.9.0) - foundation_rails_helper (1.1.0) - actionpack (~> 4.1) - activemodel (~> 4.1) - activesupport (~> 4.1) - railties (~> 4.1) + foundation_rails_helper (2.0.0) + actionpack (>= 4.1) + activemodel (>= 4.1) + activesupport (>= 4.1) + railties (>= 4.1) tzinfo (~> 1.2, >= 1.2.2) - fuubar (2.0.0) - rspec (~> 3.0) + fuubar (2.2.0) + rspec-core (~> 3.0) ruby-progressbar (~> 1.4) - geocoder (1.3.1) - globalid (0.3.6) + geocoder (1.4.3) + globalid (0.3.7) activesupport (>= 4.1.0) - groupdate (2.5.2) + groupdate (3.2.0) activesupport (>= 3) gyoku (1.3.1) builder (>= 2.1.2) - hashie (3.4.3) + hashie (3.5.5) highline (1.7.8) htmlentities (4.3.4) httpi (2.4.1) rack - i18n (0.7.0) - i18n-tasks (0.9.5) + i18n (0.8.1) + i18n-tasks (0.9.15) activesupport (>= 4.0.2) ast (>= 2.1.0) easy_translate (>= 0.5.0) @@ -197,58 +192,75 @@ GEM highline (>= 1.7.3) i18n parser (>= 2.2.3.0) - term-ansicolor (>= 1.3.2) + rainbow (~> 2.2) terminal-table (>= 1.5.1) - initialjs-rails (0.2.0.1) - railties (>= 3.1, < 5.0) - jquery-rails (4.1.1) + initialjs-rails (0.2.0.4) + railties (>= 3.1, < 6.0) + invisible_captcha (0.9.2) + rails (>= 3.2.0) + jquery-rails (4.3.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - jquery-ui-rails (5.0.5) + jquery-ui-rails (6.0.1) railties (>= 3.2.16) - json (1.8.3) - jwt (1.5.4) - kaminari (0.16.3) - actionpack (>= 3.0.0) - activesupport (>= 3.0.0) - kgio (2.10.0) + json (2.1.0) + jwt (1.5.6) + kaminari (1.0.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.0.1) + kaminari-activerecord (= 1.0.1) + kaminari-core (= 1.0.1) + kaminari-actionview (1.0.1) + actionview + kaminari-core (= 1.0.1) + kaminari-activerecord (1.0.1) + activerecord + kaminari-core (= 1.0.1) + kaminari-core (1.0.1) + kgio (2.11.0) + knapsack (1.13.3) + rake + timecop (>= 0.1.0) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.4.1) launchy (~> 2.2) - letter_opener_web (1.3.0) + letter_opener_web (1.3.1) actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) loofah (2.0.3) nokogiri (>= 1.5.9) - mail (2.6.3) - mime-types (>= 1.16, < 3) - mime-types (2.99.1) - mini_portile2 (2.0.0) - minitest (5.8.4) - multi_json (1.11.2) - multi_xml (0.5.5) + mail (2.6.5) + mime-types (>= 1.16, < 4) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mimemagic (0.3.2) + mini_portile2 (2.1.0) + minitest (5.10.1) + multi_json (1.12.1) + multi_xml (0.6.0) multipart-post (2.0.0) net-scp (1.2.1) net-ssh (>= 2.6.5) - net-ssh (3.0.2) - newrelic_rpm (3.15.1.316) - nokogiri (1.6.7.2) - mini_portile2 (~> 2.0.0.rc2) + net-ssh (4.1.0) + newrelic_rpm (4.1.0.333) + nokogiri (1.7.2) + mini_portile2 (~> 2.1.0) nori (2.6.0) - oauth (0.5.0) - oauth2 (1.0.0) - faraday (>= 0.8, < 0.10) + oauth (0.5.1) + oauth2 (1.3.1) + faraday (>= 0.8, < 0.12) jwt (~> 1.0) multi_json (~> 1.3) multi_xml (~> 0.5) - rack (~> 1.2) - omniauth (1.3.1) - hashie (>= 1.2, < 4) - rack (>= 1.0, < 3) - omniauth-facebook (3.0.0) + rack (>= 1.2, < 3) + omniauth (1.6.1) + hashie (>= 3.4.6, < 3.6.0) + rack (>= 1.6.2, < 3) + omniauth-facebook (4.0.0) omniauth-oauth2 (~> 1.2) omniauth-google-oauth2 (0.4.1) jwt (~> 1.5.2) @@ -261,91 +273,110 @@ GEM omniauth-oauth2 (1.4.0) oauth2 (~> 1.0) omniauth (~> 1.2) - omniauth-twitter (1.2.1) - json (~> 1.3) + omniauth-twitter (1.4.0) omniauth-oauth (~> 1.1) + rack orm_adapter (0.5.0) - paranoia (2.1.5) - activerecord (~> 4.0) - parser (2.3.0.6) + paperclip (5.1.0) + activemodel (>= 4.2.0) + activesupport (>= 4.2.0) + cocaine (~> 0.5.5) + mime-types + mimemagic (~> 0.3.0) + paranoia (2.3.1) + activerecord (>= 4.0, < 5.2) + parser (2.4.0.0) ast (~> 2.2) - pg (0.18.4) - pg_search (1.0.5) - activerecord (>= 3.1) - activesupport (>= 3.1) - arel - poltergeist (1.9.0) + pg (0.20.0) + pg_search (2.0.1) + activerecord (>= 4.2) + activesupport (>= 4.2) + arel (>= 6) + poltergeist (1.15.0) capybara (~> 2.1) cliver (~> 0.3.1) - multi_json (~> 1.0) websocket-driver (>= 0.2.0) + powerpack (0.1.1) + public_suffix (2.0.5) quiet_assets (1.1.0) railties (>= 3.1, < 5.0) - rack (1.6.4) + rack (1.6.5) rack-accept (0.4.5) rack (>= 0.4) + rack-attack (5.0.1) + rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.6) - actionmailer (= 4.2.6) - actionpack (= 4.2.6) - actionview (= 4.2.6) - activejob (= 4.2.6) - activemodel (= 4.2.6) - activerecord (= 4.2.6) - activesupport (= 4.2.6) + rails (4.2.8) + actionmailer (= 4.2.8) + actionpack (= 4.2.8) + actionview (= 4.2.8) + activejob (= 4.2.8) + activemodel (= 4.2.8) + activerecord (= 4.2.8) + activesupport (= 4.2.8) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.6) + railties (= 4.2.8) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) + rails-dom-testing (1.0.8) activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) + nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (4.2.6) - actionpack (= 4.2.6) - activesupport (= 4.2.6) + railties (4.2.8) + actionpack (= 4.2.8) + activesupport (= 4.2.8) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - raindrops (0.16.0) - rake (11.1.2) - redcarpet (3.3.4) + rainbow (2.2.2) + rake + raindrops (0.18.0) + rake (12.0.0) + redcarpet (3.4.0) referer-parser (0.3.0) - request_store (1.3.0) - responders (2.1.2) - railties (>= 4.2.0, < 5.1) - rinku (1.7.3) - rollbar (2.8.3) + request_store (1.3.2) + responders (2.4.0) + actionpack (>= 4.2.0, < 5.3) + railties (>= 4.2.0, < 5.3) + rinku (2.0.2) + rollbar (2.14.1) multi_json - rspec (3.4.0) - rspec-core (~> 3.4.0) - rspec-expectations (~> 3.4.0) - rspec-mocks (~> 3.4.0) - rspec-core (3.4.4) - rspec-support (~> 3.4.0) - rspec-expectations (3.4.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.4.0) - rspec-mocks (3.4.1) + rspec-support (~> 3.6.0) + rspec-mocks (3.6.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.4.0) - rspec-rails (3.4.2) - actionpack (>= 3.0, < 4.3) - activesupport (>= 3.0, < 4.3) - railties (>= 3.0, < 4.3) - rspec-core (~> 3.4.0) - rspec-expectations (~> 3.4.0) - rspec-mocks (~> 3.4.0) - rspec-support (~> 3.4.0) - rspec-support (3.4.1) - ruby-progressbar (1.7.5) + rspec-support (~> 3.6.0) + rspec-rails (3.6.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-support (~> 3.6.0) + rspec-support (3.6.0) + rubocop (0.48.1) + parser (>= 2.3.3.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.8.1) + rvm1-capistrano3 (1.4.0) + capistrano (~> 3.0) + sshkit (>= 1.2) safe_yaml (1.0.4) - sass (3.4.21) - sass-rails (5.0.4) - railties (>= 4.0.0, < 5.0) + safely_block (0.2.0) + errbase + sass (3.4.23) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) @@ -358,55 +389,62 @@ GEM nokogiri (>= 1.4.0) nori (~> 2.4) wasabi (~> 3.4) - simple_captcha2 (0.4.0) - rails (>= 4.1) - simplecov (0.11.2) + simplecov (0.14.1) docile (~> 1.1.0) - json (~> 1.8) + json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - spring (1.6.4) + sitemap_generator (5.3.1) + builder (~> 3.0) + social-share-button (0.10.0) + coffee-rails + spring (2.0.1) + activesupport (>= 4.2) spring-commands-rspec (1.0.4) spring (>= 0.9.1) - sprockets (3.5.2) + sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-es6 (0.9.0) + sprockets-es6 (0.9.2) babel-source (>= 5.8.11) babel-transpiler sprockets (>= 3.0.0) - sprockets-rails (3.0.4) + sprockets-rails (3.2.0) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sshkit (1.8.1) + sshkit (1.13.1) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - term-ansicolor (1.3.2) + term-ansicolor (1.6.0) tins (~> 1.0) - terminal-table (1.5.2) - thor (0.19.1) + terminal-table (1.7.3) + unicode-display_width (~> 1.1.1) + thor (0.19.4) thread (0.2.2) - thread_safe (0.3.5) - tilt (2.0.2) - tins (1.6.0) - tolk (1.9.3) - rails (>= 4.0, < 4.3) + thread_safe (0.3.6) + tilt (2.0.7) + timecop (0.8.1) + tins (1.13.2) + tolk (2.0.0) + rails (>= 4.0) safe_yaml (>= 0.8.6) turbolinks (2.5.3) coffee-rails - turnout (2.3.0) - rack (~> 1.3) + turnout (2.4.0) + i18n (~> 0.7) + rack (>= 1.3, < 3) rack-accept (~> 0.4) tilt (>= 1.4, < 3) - tzinfo (1.2.2) + tzinfo (1.2.3) thread_safe (~> 0.1) - uglifier (3.0.0) + uglifier (3.2.0) execjs (>= 0.3.0, < 3) - unicorn (5.1.0) + unicode-display_width (1.1.3) + unicorn (5.3.0) kgio (~> 2.6) raindrops (~> 0.7) - uniform_notifier (1.9.0) + uniform_notifier (1.10.0) user_agent_parser (2.3.0) uuidtools (2.1.5) warden (1.2.6) @@ -414,14 +452,14 @@ GEM wasabi (3.5.0) httpi (~> 2.0) nokogiri (>= 1.4.2) - web-console (3.1.1) + web-console (3.3.0) activemodel (>= 4.2) debug_inspector railties (>= 4.2) - websocket-driver (0.6.3) + websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) - whenever (0.9.4) + whenever (0.9.7) chronic (>= 0.6.3) xpath (2.0.0) nokogiri (~> 1.3) @@ -432,70 +470,76 @@ PLATFORMS DEPENDENCIES acts-as-taggable-on acts_as_votable - ahoy_matey (~> 1.2.1) - ancestry + ahoy_matey (~> 1.6.0) + ancestry (~> 2.2.2) browser - bullet + bullet (~> 5.5.1) byebug - cancancan - capistrano (= 3.4.0) - capistrano-bundler (= 1.1.4) - capistrano-rails (= 1.1.6) - capistrano-rvm - capistrano3-delayed-job (~> 1.0) - capybara - ckeditor (~> 4.1.5) - coffee-rails (~> 4.1.0) - coveralls + cancancan (~> 1.16.0) + capistrano (~> 3.8.1) + capistrano-bundler (~> 1.2) + capistrano-rails (~> 1.2.3) + capistrano3-delayed-job (~> 1.7.3) + capybara (~> 2.14.0) + ckeditor (~> 4.2.3) + coffee-rails (~> 4.2.1) + coveralls (~> 0.8.21) daemons dalli database_cleaner delayed_job_active_record (~> 4.1.0) - devise (~> 3.5.6) + devise (~> 3.5.7) devise-async + devise_security_extension email_spec - factory_girl_rails - faker - foundation-rails - foundation_rails_helper + factory_girl_rails (~> 4.8.0) + faker (~> 1.7.3) + foundation-rails (~> 6.2.4.0) + foundation_rails_helper (~> 2.0.0) fuubar - groupdate - i18n-tasks - initialjs-rails (= 0.2.0.1) - jquery-rails + groupdate (~> 3.2.0) + i18n-tasks (~> 0.9.15) + initialjs-rails (= 0.2.0.4) + invisible_captcha (~> 0.9.2) + jquery-rails (~> 4.3.1) jquery-ui-rails - kaminari + kaminari (~> 1.0.1) + knapsack launchy - letter_opener_web (~> 1.3.0) - newrelic_rpm (~> 3.14) + letter_opener_web (~> 1.3.1) + newrelic_rpm (~> 4.1.0.333) omniauth - omniauth-facebook (~> 3.0.0) + omniauth-facebook (~> 4.0.0) omniauth-google-oauth2 (~> 0.4.0) omniauth-twitter - paranoia - pg + paperclip + paranoia (~> 2.3.1) + pg (~> 0.20.0) pg_search - poltergeist + poltergeist (~> 1.15.0) quiet_assets - rails (= 4.2.6) - redcarpet - responders - rinku - rollbar (~> 2.8.0) - rspec-rails (~> 3.3) + rails (= 4.2.8) + redcarpet (~> 3.4.0) + responders (~> 2.4.0) + rinku (~> 2.0.2) + rollbar (~> 2.14.1) + rspec-rails (~> 3.6) + rubocop (~> 0.48.1) + rvm1-capistrano3 sass-rails (~> 5.0, >= 5.0.4) savon - simple_captcha2 - social-share-button! + sitemap_generator (~> 5.3.1) + social-share-button (~> 0.10) spring spring-commands-rspec - tolk + sprockets (~> 3.7.1) + tolk (~> 2.0.0) turbolinks - turnout - uglifier (>= 1.3.0) - unicorn (~> 5.1.0) - web-console (~> 3.0) + turnout (~> 2.4.0) + uglifier (~> 3.2.0) + unicorn (~> 5.3.0) + web-console (= 3.3.0) whenever BUNDLED WITH - 1.11.2 + 1.14.6 diff --git a/Gemfile_custom b/Gemfile_custom new file mode 100644 index 000000000..9d91e0680 --- /dev/null +++ b/Gemfile_custom @@ -0,0 +1,5 @@ +# Overrides and adds customized gems in this file +# Read more on documentation: +# * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md#gemfile +# * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md#gemfile +# diff --git a/README.md b/README.md index 251a0f8e3..20ed21003 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -![Logo of Consul] -(https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) +![Logo of Consul](https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) # Consul @@ -8,17 +7,19 @@ Citizen Participation and Open Government Application [![Build Status](https://travis-ci.org/consul/consul.svg?branch=master)](https://travis-ci.org/consul/consul) [![Code Climate](https://codeclimate.com/github/consul/consul/badges/gpa.svg)](https://codeclimate.com/github/consul/consul) [![Dependency Status](https://gemnasium.com/consul/consul.svg)](https://gemnasium.com/consul/consul) -[![Coverage Status](https://coveralls.io/repos/consul/consul/badge.svg?branch=master&service=github)](https://coveralls.io/github/consul/consul?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/consul/consul/badge.svg?branch=master)](https://coveralls.io/github/consul/consul?branch=master) +[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](http://www.gnu.org/licenses/agpl-3.0) + +[![Accessibility conformance](https://img.shields.io/badge/accessibility-WAI:AA-green.svg)](https://www.w3.org/WAI/eval/Overview) +[![A11y issues checked with Rocket Validator](https://rocketvalidator.com/badges/checked_with_rocket_validator.svg?url=https://rocketvalidator.com)](https://rocketvalidator.com/opensource) + +[![Join the chat at https://gitter.im/consul/consul](https://badges.gitter.im/consul/consul.svg)](https://gitter.im/consul/consul?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) This is the opensource code repository of the eParticipation website originally developed for the Madrid City government eParticipation website ## Current state -Development started on [2015 July 15th](https://github.com/consul/consul/commit/8db36308379accd44b5de4f680a54c41a0cc6fc6). Code was deployed to production on 2015 september 7th to [decide.madrid.es](https://decide.madrid.es). Since then new features are added often. You can take a look at a roadmap and future features in the [open issues list](https://github.com/consul/consul/issues). - -## Roadmap - -See [ROADMAP_ES.md](ROADMAP_ES.md) +Development started on [2015 July 15th](https://github.com/consul/consul/commit/8db36308379accd44b5de4f680a54c41a0cc6fc6). Code was deployed to production on 2015 september 7th to [decide.madrid.es](https://decide.madrid.es). Since then new features are added often. You can take a look at the current features in the [docs](https://github.com/consul/consul/tree/master/doc) and future features in the [open issues list](https://github.com/consul/consul/issues). ## Tech stack @@ -28,7 +29,9 @@ Frontend tools used include [SCSS](http://sass-lang.com/) over [Foundation](http ## Configuration for development and test environments -Prerequisites: install git, ImageMagick, Ruby 2.2.3, bundler gem, ghostscript and PostgreSQL (>=9.4). +**NOTE**: For more detailed instructions check the [docs](https://github.com/consul/consul/tree/master/doc/en/dev_test_setup.md) + +Prerequisites: install git, Ruby 2.3.2, bundler gem, ghostscript and PostgreSQL (>=9.4). ``` git clone https://github.com/consul/consul.git @@ -36,10 +39,9 @@ cd consul bundle install cp config/database.yml.example config/database.yml cp config/secrets.yml.example config/secrets.yml -rake db:create bin/rake db:setup bin/rake db:dev_seed -RAILS_ENV=test bin/rake db:setup +RAILS_ENV=test rake db:setup ``` Run the app locally: @@ -48,7 +50,7 @@ bin/rails s ``` -Prerequisites for testing: install PhantomJS >= 2.0 +Prerequisites for testing: install PhantomJS >= 1.9.8 Run the tests with: @@ -56,10 +58,30 @@ Run the tests with: bin/rspec ``` -## Licence +You can use the default admin user from the seeds file: + + **user:** admin@consul.dev + **pass:** 12345678 + +But for some actions like voting, you will need a verified user, the seeds file also includes one: + + **user:** verified@consul.dev + **pass:** 12345678 + +### Customization + +See [CUSTOMIZE_ES.md](CUSTOMIZE_ES.md) + +### OAuth + +To test authentication services with external OAuth suppliers - right now Twitter, Facebook and Google - you'll need to create an "application" in each of the supported platforms and set the *key* and *secret* provided in your *secrets.yml* + +In the case of Google, verify that the APIs *Contacts API* and *Google+ API* are enabled for the application. + +## License Code published under AFFERO GPL v3 (see [LICENSE-AGPLv3.txt](LICENSE-AGPLv3.txt)) ## Contributions -See [CONTRIBUTING_EN.md](CONTRIBUTING_EN.md) \ No newline at end of file +See [CONTRIBUTING_EN.md](CONTRIBUTING_EN.md) diff --git a/README_ES.md b/README_ES.md index c3fd3cdff..2c1e362c3 100644 --- a/README_ES.md +++ b/README_ES.md @@ -1,5 +1,4 @@ -![Logotipo de Consul] -(https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) +![Logotipo de Consul](https://raw.githubusercontent.com/consul/consul/master/public/consul_logo.png) # Consul @@ -8,17 +7,20 @@ Aplicación de Participación Ciudadana y Gobierno Abierto [![Build Status](https://travis-ci.org/consul/consul.svg?branch=master)](https://travis-ci.org/consul/consul) [![Code Climate](https://codeclimate.com/github/consul/consul/badges/gpa.svg)](https://codeclimate.com/github/consul/consul) [![Dependency Status](https://gemnasium.com/consul/consul.svg)](https://gemnasium.com/consul/consul) -[![Coverage Status](https://coveralls.io/repos/consul/consul/badge.svg?branch=master&service=github)](https://coveralls.io/github/consul/consul?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/consul/consul/badge.svg?branch=master)](https://coveralls.io/github/consul/consul?branch=master) +[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](http://www.gnu.org/licenses/agpl-3.0) + +[![Accessibility conformance](https://img.shields.io/badge/accessibility-WAI:AA-green.svg)](https://www.w3.org/WAI/eval/Overview) +[![A11y issues checked with Rocket Validator](https://rocketvalidator.com/badges/checked_with_rocket_validator.svg?url=https://rocketvalidator.com)](https://rocketvalidator.com/opensource) + +[![Join the chat at https://gitter.im/consul/consul](https://badges.gitter.im/consul/consul.svg)](https://gitter.im/consul/consul?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + Este es el repositorio de código abierto de la Aplicación de Participación Ciudadana Consul, creada originariamente por el Ayuntamiento de Madrid. ## Estado del proyecto -El desarrollo de esta aplicación comenzó el [15 de Julio de 2015](https://github.com/consul/consul/commit/8db36308379accd44b5de4f680a54c41a0cc6fc6) y el código fue puesto en producción el día 7 de Septiembre de 2015 en [decide.madrid.es](https://decide.madrid.es). Desde entonces se le añaden mejoras y funcionalidades constantemente. La evolución y futura lista de funcionalidades a implementar se pueden consultar en la lista de [tareas por hacer](https://github.com/consul/consul/issues). - -## Hoja de ruta - -Ver fichero [ROADMAP_ES.md](ROADMAP_ES.md) +El desarrollo de esta aplicación comenzó el [15 de Julio de 2015](https://github.com/consul/consul/commit/8db36308379accd44b5de4f680a54c41a0cc6fc6) y el código fue puesto en producción el día 7 de Septiembre de 2015 en [decide.madrid.es](https://decide.madrid.es). Desde entonces se le añaden mejoras y funcionalidades constantemente. Las funcionalidades actuales se pueden consultar en la [documentación](https://github.com/consul/consul/tree/master/doc) y las siguientes funcionaliades en la lista de [tareas por hacer](https://github.com/consul/consul/issues). ## Tecnología @@ -27,7 +29,9 @@ Las herramientas utilizadas para el frontend no están cerradas aún. Los estilo ## Configuración para desarrollo y tests -Prerequisitos: tener instalado git, ImageMagick, Ruby 2.2.3, la gema `bundler`, ghostscript y PostgreSQL (9.4 o superior). +**NOTA**: para unas instrucciones más detalladas consulta la [documentación](https://github.com/consul/consul/tree/master/doc/es/dev_test_setup.md) + +Prerequisitos: tener instalado git, Ruby 2.3.2, la gema `bundler`, ghostscript y PostgreSQL (9.4 o superior). ``` @@ -36,10 +40,9 @@ cd consul bundle install cp config/database.yml.example config/database.yml cp config/secrets.yml.example config/secrets.yml -rake db:create bin/rake db:setup bin/rake db:dev_seed -RAILS_ENV=test bin/rake db:setup +RAILS_ENV=test rake db:setup ``` Para ejecutar la aplicación en local: @@ -47,7 +50,7 @@ Para ejecutar la aplicación en local: bin/rails s ``` -Prerequisitos para los tests: tener instalado PhantomJS >= 2.0 +Prerequisitos para los tests: tener instalado PhantomJS >= 1.9.8 Para ejecutar los tests: @@ -55,6 +58,20 @@ Para ejecutar los tests: bin/rspec ``` +Puedes usar el usuario administrador por defecto del fichero seeds: + + **user:** admin@consul.dev + **pass:** 12345678 + +Pero para ciertas acciones, como apoyar, necesitarás un usuario verificado, el fichero seeds proporciona uno: + + **user:** verified@consul.dev + **pass:** 12345678 + +### Customización + +Ver fichero [CUSTOMIZE_ES.md](CUSTOMIZE_ES.md) + ### OAuth Para probar los servicios de autenticación mediante proveedores externos OAuth — en este momento Twitter, Facebook y Google —, necesitas crear una "aplicación" en cada una de las plataformas soportadas y configurar la *key* y el *secret* proporcionados en tu *secrets.yml* diff --git a/ROADMAP_ES.md b/ROADMAP_ES.md deleted file mode 100644 index 9e53b53c8..000000000 --- a/ROADMAP_ES.md +++ /dev/null @@ -1,26 +0,0 @@ -## Hoja de ruta - -Las necesidades y líneas de desarrollo de Consul a corto plazo se actualizan continuamente, y son añadidas como Issues https://github.com/consul/consul/issues La hoja de ruta de desarrollo a medio plazo comprende lo siguiente: - -## Debates y propuestas - -Entre las mejoras a desarrollar destacamos las siguientes: -- Búsquedas avanzadas -- Notificaciones de usuarios -- Listas de correo informativas para los usuarios -- Clasificaciones temáticas y locales -- Mejora del sistema de etiquetado -- Sistema de identificación de propuestas similares -- Mejora de algoritmos de ordenación -- Ampliación de licencias de contenidos -- Añadido de idiomas disponibles -- Universalización del código, y facilitación de adaptación a diferentes sistemas locales -- Creación de sección de procesos participativos ad-hoc - -## Presupuestos participativos - -Este espacio permitirá que una cierta cantidad de dinero se distribuya por decisión ciudadana. Para ello permitirá: recibir propuestas para este fin; que dichas propuestas sean tasadas y evaluadas por un cierto perfil de usuarios mostrando dicho trabajo junto a las mismas; mostrar las propuestas de tal forma que los usuarios verificados puedan votar un conjunto de ellas acorde con la cantidad de dinero reservada. El mecanismo contemplará mecanismos de pre-selección y selección, además de categorizaciones geográficas o temáticas de las propuestas. - -## Legislación colaborativa - -Este módulo permitirá la participación ciudadana en todas las fases de un desarrollo legislativo. Desde la propuesta y selección de los redactores y expertos de la legislación, pasando por la fase de debate y propuesta asociada a la misma (que a su vez se dividirá en espacios de debate libre y mecanismos de pregunta-respuesta), hasta la redacción colaborativa y la revisión detallada del texto creado. El módulo permitirá activar independientemente las diferentes fases del proceso, de tal forma que su uso pueda extenderse mucho más allá del caso de la redacción colaborativa. diff --git a/Rakefile b/Rakefile index ba6b733dd..ef2c381ff 100644 --- a/Rakefile +++ b/Rakefile @@ -4,3 +4,4 @@ require File.expand_path('../config/application', __FILE__) Rails.application.load_tasks +Knapsack.load_tasks if defined?(Knapsack) \ No newline at end of file diff --git a/app/assets/fonts/icons.eot b/app/assets/fonts/icons.eot index 5fe0c1d3d..c1afd24fd 100644 Binary files a/app/assets/fonts/icons.eot and b/app/assets/fonts/icons.eot differ diff --git a/app/assets/fonts/icons.svg b/app/assets/fonts/icons.svg index 51044c3e2..68d878942 100644 --- a/app/assets/fonts/icons.svg +++ b/app/assets/fonts/icons.svg @@ -49,4 +49,17 @@ + + + + + + + + + + + + + diff --git a/app/assets/fonts/icons.ttf b/app/assets/fonts/icons.ttf index b0b156581..a9443499b 100644 Binary files a/app/assets/fonts/icons.ttf and b/app/assets/fonts/icons.ttf differ diff --git a/app/assets/fonts/icons.woff b/app/assets/fonts/icons.woff index edcc9bc61..09a6f6dc5 100644 Binary files a/app/assets/fonts/icons.woff and b/app/assets/fonts/icons.woff differ diff --git a/app/assets/images/admin_avatar.png b/app/assets/images/admin_avatar.png deleted file mode 100644 index 28ea4a143..000000000 Binary files a/app/assets/images/admin_avatar.png and /dev/null differ diff --git a/app/assets/images/auth_logo.png b/app/assets/images/auth_logo.png deleted file mode 100644 index bc7b1408b..000000000 Binary files a/app/assets/images/auth_logo.png and /dev/null differ diff --git a/app/assets/images/avatar_admin.png b/app/assets/images/avatar_admin.png new file mode 100644 index 000000000..4f4d23c06 Binary files /dev/null and b/app/assets/images/avatar_admin.png differ diff --git a/app/assets/images/collective_avatar.png b/app/assets/images/avatar_collective.png similarity index 100% rename from app/assets/images/collective_avatar.png rename to app/assets/images/avatar_collective.png diff --git a/app/assets/images/avatar_moderator.png b/app/assets/images/avatar_moderator.png new file mode 100644 index 000000000..c56a5de60 Binary files /dev/null and b/app/assets/images/avatar_moderator.png differ diff --git a/app/assets/images/ballot.gif b/app/assets/images/ballot.gif new file mode 100644 index 000000000..18cf0717f Binary files /dev/null and b/app/assets/images/ballot.gif differ diff --git a/app/assets/images/ballot_tiny.gif b/app/assets/images/ballot_tiny.gif new file mode 100644 index 000000000..976d37591 Binary files /dev/null and b/app/assets/images/ballot_tiny.gif differ diff --git a/app/assets/images/banners/banner1.png b/app/assets/images/banners/banner1.png new file mode 100644 index 000000000..eff1cb9d7 Binary files /dev/null and b/app/assets/images/banners/banner1.png differ diff --git a/app/assets/images/banners/banner2.png b/app/assets/images/banners/banner2.png new file mode 100644 index 000000000..8ae0715e2 Binary files /dev/null and b/app/assets/images/banners/banner2.png differ diff --git a/app/assets/images/banners/banner3.png b/app/assets/images/banners/banner3.png new file mode 100644 index 000000000..e4ff49093 Binary files /dev/null and b/app/assets/images/banners/banner3.png differ diff --git a/app/assets/images/custom/.keep b/app/assets/images/custom/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/images/header_bg.jpg b/app/assets/images/header_bg.jpg deleted file mode 100644 index 4449c9897..000000000 Binary files a/app/assets/images/header_bg.jpg and /dev/null differ diff --git a/app/assets/images/header_logo_madrid.png b/app/assets/images/header_logo_madrid.png deleted file mode 100644 index 50e388cad..000000000 Binary files a/app/assets/images/header_logo_madrid.png and /dev/null differ diff --git a/app/assets/images/header_print_proposals.jpg b/app/assets/images/header_print_proposals.jpg index 4d2eb18a5..6e8531c08 100644 Binary files a/app/assets/images/header_print_proposals.jpg and b/app/assets/images/header_print_proposals.jpg differ diff --git a/app/assets/images/icon_mailer_comment.png b/app/assets/images/icon_mailer_comment.png new file mode 100644 index 000000000..d3c9bff73 Binary files /dev/null and b/app/assets/images/icon_mailer_comment.png differ diff --git a/app/assets/images/icon_mailer_reply.png b/app/assets/images/icon_mailer_reply.png new file mode 100644 index 000000000..0c9159e84 Binary files /dev/null and b/app/assets/images/icon_mailer_reply.png differ diff --git a/app/assets/images/icon_mailer_share.png b/app/assets/images/icon_mailer_share.png new file mode 100644 index 000000000..f95d44141 Binary files /dev/null and b/app/assets/images/icon_mailer_share.png differ diff --git a/app/assets/images/logo_email.png b/app/assets/images/logo_email.png new file mode 100644 index 000000000..ca2d6d54d Binary files /dev/null and b/app/assets/images/logo_email.png differ diff --git a/app/assets/images/logo_email_gobierno_abierto.png b/app/assets/images/logo_email_gobierno_abierto.png deleted file mode 100644 index 5c865e102..000000000 Binary files a/app/assets/images/logo_email_gobierno_abierto.png and /dev/null differ diff --git a/app/assets/images/logo_header.png b/app/assets/images/logo_header.png new file mode 100644 index 000000000..ef5c4f6ff Binary files /dev/null and b/app/assets/images/logo_header.png differ diff --git a/app/assets/images/logo_madrid_white.png b/app/assets/images/logo_madrid_white.png deleted file mode 100644 index 927514733..000000000 Binary files a/app/assets/images/logo_madrid_white.png and /dev/null differ diff --git a/app/assets/images/moderator_avatar.png b/app/assets/images/moderator_avatar.png deleted file mode 100644 index 1c726e57b..000000000 Binary files a/app/assets/images/moderator_avatar.png and /dev/null differ diff --git a/app/assets/images/more_info/budgets_en.png b/app/assets/images/more_info/budgets_en.png new file mode 100644 index 000000000..1c1002145 Binary files /dev/null and b/app/assets/images/more_info/budgets_en.png differ diff --git a/app/assets/images/more_info/budgets_es.png b/app/assets/images/more_info/budgets_es.png new file mode 100644 index 000000000..89d93947b Binary files /dev/null and b/app/assets/images/more_info/budgets_es.png differ diff --git a/app/assets/images/more_info/budgets_fr.png b/app/assets/images/more_info/budgets_fr.png new file mode 100644 index 000000000..1c1002145 Binary files /dev/null and b/app/assets/images/more_info/budgets_fr.png differ diff --git a/app/assets/images/more_info/budgets_pt-BR.png b/app/assets/images/more_info/budgets_pt-BR.png new file mode 100644 index 000000000..1c1002145 Binary files /dev/null and b/app/assets/images/more_info/budgets_pt-BR.png differ diff --git a/app/assets/images/more_info/debates.png b/app/assets/images/more_info/debates.png new file mode 100644 index 000000000..30b195668 Binary files /dev/null and b/app/assets/images/more_info/debates.png differ diff --git a/app/assets/images/more_info/proposals_en.png b/app/assets/images/more_info/proposals_en.png new file mode 100644 index 000000000..60357f222 Binary files /dev/null and b/app/assets/images/more_info/proposals_en.png differ diff --git a/app/assets/images/more_info/proposals_es.png b/app/assets/images/more_info/proposals_es.png new file mode 100644 index 000000000..3466949bb Binary files /dev/null and b/app/assets/images/more_info/proposals_es.png differ diff --git a/app/assets/images/more_info/proposals_fr.png b/app/assets/images/more_info/proposals_fr.png new file mode 100644 index 000000000..60357f222 Binary files /dev/null and b/app/assets/images/more_info/proposals_fr.png differ diff --git a/app/assets/images/more_info/proposals_pt-BR.png b/app/assets/images/more_info/proposals_pt-BR.png new file mode 100644 index 000000000..60357f222 Binary files /dev/null and b/app/assets/images/more_info/proposals_pt-BR.png differ diff --git a/app/assets/javascripts/advanced_search.js.coffee b/app/assets/javascripts/advanced_search.js.coffee index a5e657633..b38efb874 100644 --- a/app/assets/javascripts/advanced_search.js.coffee +++ b/app/assets/javascripts/advanced_search.js.coffee @@ -23,6 +23,8 @@ App.AdvancedSearch = $('.js-calendar').datepicker regional: locale maxDate: "+0d" + $('.js-calendar-full').datepicker + regional: locale initialize: -> App.AdvancedSearch.init_calendar() diff --git a/app/assets/javascripts/allow_participation.js.coffee b/app/assets/javascripts/allow_participation.js.coffee new file mode 100644 index 000000000..116e374ca --- /dev/null +++ b/app/assets/javascripts/allow_participation.js.coffee @@ -0,0 +1,12 @@ +App.AllowParticipation = + + initialize: -> + $(document).on { + 'mouseenter focus': -> + $(this).find(".js-participation-not-allowed").show(); + $(this).find(".js-participation-allowed").hide(); + mouseleave: -> + $(this).find(".js-participation-not-allowed").hide(); + $(this).find(".js-participation-allowed").show(); + }, ".js-participation" + false diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index a688ae0b3..928f06edd 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,11 +12,11 @@ // //= require jquery //= require jquery_ujs -//= require jquery-ui/datepicker -//= require jquery-ui/datepicker-es +//= require jquery-ui/widgets/datepicker +//= require jquery-ui/i18n/datepicker-es //= require foundation //= require turbolinks -//= require ckeditor/init +//= require ckeditor/loader //= require_directory ./ckeditor //= require social-share-button //= require initial @@ -36,18 +36,27 @@ //= require tags //= require users //= require votes +//= require allow_participation //= require annotatable //= require advanced_search //= require registration_form //= require suggest //= require forms +//= require tracks +//= require valuation_budget_investment_form //= require valuation_spending_proposal_form //= require embed_video +//= require fixed_bar +//= require banners +//= require social_share +//= require checkbox_toggle +//= require custom var initialize_modules = function() { App.Comments.initialize(); App.Users.initialize(); App.Votes.initialize(); + App.AllowParticipation.initialize(); App.Tags.initialize(); App.Dropdown.initialize(); App.LocationChanger.initialize(); @@ -59,8 +68,14 @@ var initialize_modules = function() { App.RegistrationForm.initialize(); App.Suggest.initialize(); App.Forms.initialize(); + App.Tracks.initialize(); + App.ValuationBudgetInvestmentForm.initialize(); App.ValuationSpendingProposalForm.initialize(); App.EmbedVideo.initialize(); + App.FixedBar.initialize(); + App.Banners.initialize(); + App.SocialShare.initialize(); + App.CheckboxToggle.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/banners.js.coffee b/app/assets/javascripts/banners.js.coffee new file mode 100644 index 000000000..ec00abe95 --- /dev/null +++ b/app/assets/javascripts/banners.js.coffee @@ -0,0 +1,25 @@ +App.Banners = + + update_banner: (selector, text) -> + $(selector).html(text) + + update_style: (selector, style) -> + $(selector).removeClass($(selector).attr("class"), true) + .addClass(style, true) + + initialize: -> + $('[data-js-banner-title]').on + change: -> + App.Banners.update_banner("#js-banner-title", $(this).val()) + + $('[data-js-banner-description]').on + change: -> + App.Banners.update_banner("#js-banner-description", $(this).val()) + + $("#banner_style").on + change: -> + App.Banners.update_style("#js-banner-style", $(this).val()) + + $("#banner_image").on + change: -> + App.Banners.update_style("#js-banner-image", $(this).val()) diff --git a/app/assets/javascripts/checkbox_toggle.js.coffee b/app/assets/javascripts/checkbox_toggle.js.coffee new file mode 100644 index 000000000..096ce7e25 --- /dev/null +++ b/app/assets/javascripts/checkbox_toggle.js.coffee @@ -0,0 +1,12 @@ +App.CheckboxToggle = + + initialize: -> + $('[data-checkbox-toggle]').on 'change', -> + $this = $(this) + $target = $($this.data('checkbox-toggle')) + if $this.is(':checked') + $target.show() + else + $target.hide() + + diff --git a/app/assets/javascripts/ckeditor/loader.js.erb b/app/assets/javascripts/ckeditor/loader.js.erb new file mode 100644 index 000000000..66e1d8347 --- /dev/null +++ b/app/assets/javascripts/ckeditor/loader.js.erb @@ -0,0 +1,3 @@ +//= require ckeditor/init + +CKEDITOR.config.customConfig = '<%= javascript_path 'ckeditor/config.js' %>'; diff --git a/app/assets/javascripts/comments.js.coffee b/app/assets/javascripts/comments.js.coffee index dd8006b9b..fefe85544 100644 --- a/app/assets/javascripts/comments.js.coffee +++ b/app/assets/javascripts/comments.js.coffee @@ -30,11 +30,11 @@ App.Comments = $("#js-comment-form-#{id}").toggle() toggle_arrow: (id) -> - arrow = "i##{id}_arrow" - if $(arrow).hasClass("icon-angle-right") - $(arrow).removeClass("icon-angle-right").addClass("icon-angle-down") + arrow = "span##{id}_arrow" + if $(arrow).hasClass("icon-arrow-right") + $(arrow).removeClass("icon-arrow-right").addClass("icon-arrow-down") else - $(arrow).removeClass("icon-angle-down").addClass("icon-angle-right") + $(arrow).removeClass("icon-arrow-down").addClass("icon-arrow-right") initialize: -> $('body .js-add-comment-link').each -> @@ -52,5 +52,6 @@ App.Comments = children_container_id = "#{$(this).data().id}_children" $("##{children_container_id}").toggle('slow') App.Comments.toggle_arrow(children_container_id) + $(this).children('.js-child-toggle').toggle() false ) diff --git a/app/assets/javascripts/custom.js b/app/assets/javascripts/custom.js new file mode 100644 index 000000000..6c880b3a9 --- /dev/null +++ b/app/assets/javascripts/custom.js @@ -0,0 +1,7 @@ +// Overrides and adds customized javascripts in this file +// Read more on documentation: +// * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md#javascript +// * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md#javascript +// +// + diff --git a/app/assets/javascripts/fixed_bar.js.coffee b/app/assets/javascripts/fixed_bar.js.coffee new file mode 100644 index 000000000..8ccbf987f --- /dev/null +++ b/app/assets/javascripts/fixed_bar.js.coffee @@ -0,0 +1,13 @@ +App.FixedBar = + initialize: -> + $('[data-fixed-bar]').each -> + $this = $(this) + fixedBarTopPosition = $this.offset().top + + $(window).on 'scroll', -> + if $(window).scrollTop() > fixedBarTopPosition + $this.addClass('is-fixed') + $("#check-ballot").css({ 'display': "inline-block" }); + else + $this.removeClass('is-fixed') + $("#check-ballot").hide() diff --git a/app/assets/javascripts/social_share.js.coffee b/app/assets/javascripts/social_share.js.coffee new file mode 100644 index 000000000..a61f2d8f4 --- /dev/null +++ b/app/assets/javascripts/social_share.js.coffee @@ -0,0 +1,7 @@ +App.SocialShare = + + initialize: -> + $(".social-share-button a").each -> + element = $(this) + site = element.data('site') + element.append("#{site}") \ No newline at end of file diff --git a/app/assets/javascripts/tracks.js.coffee b/app/assets/javascripts/tracks.js.coffee new file mode 100644 index 000000000..1b6cfe82e --- /dev/null +++ b/app/assets/javascripts/tracks.js.coffee @@ -0,0 +1,28 @@ +App.Tracks = + + tracking_enabled: -> + _paq? + + set_custom_var: (id, name, value, scope) -> + _paq.push(['setCustomVariable', id, name, value, scope]) + _paq.push(['trackPageView']) + + track_event: ($this) -> + category = $this.data('track-event-category') + action = $this.data('track-event-action') + _paq.push(['trackEvent', category, action]) + + initialize: -> + if App.Tracks.tracking_enabled() + $('[data-track-usertype]').each -> + $this = $(this) + usertype = $this.data('track-usertype') + App.Tracks.set_custom_var(1, "usertype", usertype, "visit") + + $('[data-track-event-category]').each -> + $this = $(this) + App.Tracks.track_event($this) + + $('[data-track-click]').on 'click', -> + $this = $(this) + App.Tracks.track_event($this) diff --git a/app/assets/javascripts/valuation_budget_investment_form.js.coffee b/app/assets/javascripts/valuation_budget_investment_form.js.coffee new file mode 100644 index 000000000..d79ff600e --- /dev/null +++ b/app/assets/javascripts/valuation_budget_investment_form.js.coffee @@ -0,0 +1,32 @@ +App.ValuationBudgetInvestmentForm = + + showFeasibleFields: -> + $('#valuation_budget_investment_edit_form #unfeasible_fields').hide('down') + $('#valuation_budget_investment_edit_form #feasible_fields').show() + + showNotFeasibleFields: -> + $('#valuation_budget_investment_edit_form #feasible_fields').hide('down') + $('#valuation_budget_investment_edit_form #unfeasible_fields').show() + + showAllFields: -> + $('#valuation_budget_investment_edit_form #feasible_fields').show('down') + $('#valuation_budget_investment_edit_form #unfeasible_fields').show('down') + + showFeasibilityFields: -> + feasibility = $("#valuation_budget_investment_edit_form input[type=radio][name='budget_investment[feasibility]']:checked").val() + if feasibility == 'feasible' + App.ValuationBudgetInvestmentForm.showFeasibleFields() + else if feasibility == 'unfeasible' + App.ValuationBudgetInvestmentForm.showNotFeasibleFields() + + + showFeasibilityFieldsOnChange: -> + $("#valuation_budget_investment_edit_form input[type=radio][name='budget_investment[feasibility]']").change -> + App.ValuationBudgetInvestmentForm.showAllFields() + App.ValuationBudgetInvestmentForm.showFeasibilityFields() + + + initialize: -> + App.ValuationBudgetInvestmentForm.showFeasibilityFields() + App.ValuationBudgetInvestmentForm.showFeasibilityFieldsOnChange() + false \ No newline at end of file diff --git a/app/assets/javascripts/votes.js.coffee b/app/assets/javascripts/votes.js.coffee index 43469909d..b842c3ffc 100644 --- a/app/assets/javascripts/votes.js.coffee +++ b/app/assets/javascripts/votes.js.coffee @@ -1,18 +1,16 @@ App.Votes = hoverize: (votes) -> - $(votes).hover -> - $("div.anonymous-votes", votes).show(); - $("div.organizations-votes", votes).show(); - $("div.not-logged", votes).show(); - $("div.logged", votes).hide(); - , -> - $("div.anonymous-votes", votes).hide(); - $("div.organizations-votes", votes).hide(); - $("div.not-logged", votes).hide(); - $("div.logged", votes).show(); + $(document).on { + 'mouseenter focus': -> + $("div.participation-not-allowed", this).show(); + $("div.participation-allowed", this).hide(); + mouseleave: -> + $("div.participation-not-allowed", this).hide(); + $("div.participation-allowed", this).show(); + }, votes initialize: -> - App.Votes.hoverize votes for votes in $("div.votes") - App.Votes.hoverize votes for votes in $("div.supports") + App.Votes.hoverize "div.votes" + App.Votes.hoverize "div.supports" false diff --git a/app/assets/stylesheets/_custom_settings.scss b/app/assets/stylesheets/_custom_settings.scss new file mode 100644 index 000000000..92a304e6f --- /dev/null +++ b/app/assets/stylesheets/_custom_settings.scss @@ -0,0 +1,5 @@ +// Overrides and adds customized foundation settings in this file +// Read more on documentation: +// * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md#css +// * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md#css +// diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss index 2eacc2021..b804d62ba 100644 --- a/app/assets/stylesheets/_settings.scss +++ b/app/assets/stylesheets/_settings.scss @@ -3,6 +3,7 @@ // // Table of Contents: // +// 0. Custom variables // 1. Global // 2. Breakpoints // 3. The Grid @@ -25,48 +26,114 @@ // 20. Label // 21. Media Object // 22. Menu -// 23. Off-canvas -// 24. Orbit -// 25. Pagination -// 26. Progress Bar -// 27. Reveal -// 28. Slider -// 29. Switch -// 30. Table -// 31. Tabs -// 32. Thumbnail -// 33. Title Bar -// 34. Tooltip -// 35. Top Bar +// 23. Meter +// 24. Off-canvas +// 25. Orbit +// 26. Pagination +// 27. Progress Bar +// 28. Reveal +// 29. Slider +// 30. Switch +// 31. Table +// 32. Tabs +// 33. Thumbnail +// 34. Title Bar +// 35. Tooltip +// 36. Top Bar +// 37. Custom variables @import 'util/util'; +// 0. Custom variables +// -------------------- + +$base-font-size: rem-calc(17); +$base-line: rem-calc(26); +$small-font-size: rem-calc(14); +$line-height: rem-calc(24); +$tiny-font-size: rem-calc(12); + +$brand: #004A83; +$dark: darken($brand, 10%); + +$text: #222222; +$text-medium: #515151; +$text-light: #BFBFBF; + +$border: #DEE0E3; + +$link: $brand; +$link-hover: darken($link, 20%); + +$debates: $brand; + +$like: #7BD2A8; +$unlike: #EF8585; + +$delete: #F04124; +$check: #46DB91; + +$proposals: #FFA42D; +$proposals-dark: #794500; + +$budget: #7E328A; +$budget-hover: #7571BF; + +$highlight: #E7F2FC; +$light: #F5F7FA; +$featured: #FFDC5C; + +$footer-border: #BFC1C3; + +$success-bg: #DFF0D8; +$success-border: #D6E9C6; +$color-success: #3C763D; + +$info-bg: #D9EDF7; +$info-border: #BCE8F1; +$color-info: #31708F; + +$warning-bg: #FCF8E3; +$warning-border: #FAEBCC; +$color-warning: #8A6D3B; + +$alert-bg: #F2DEDE; +$alert-border: #EBCCD1; +$color-alert: #A94442; + + // 1. Global // --------- $global-font-size: 100%; $global-width: rem-calc(1200); $global-lineheight: 1.5; -$primary-color: #2199e8; -$secondary-color: #777; -$success-color: #43ac6a; -$warning-color: #ffae00; -$alert-color: #ec5840; +$foundation-palette: ( + primary: #2199e8, + secondary: #777, + success: #3adb76, + warning: #ffae00, + alert: #ec5840, +); $light-gray: #e6e6e6; $medium-gray: #cacaca; $dark-gray: #8a8a8a; -$black: #0a0a0a; -$white: #fefefe; +$black: #222222; +$white: #ffffff; $body-background: $white; $body-font-color: $black; -$body-font-family: 'Helvetica Neue', Helvetica, Roboto, Arial, sans-serif; +$body-font-family: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif !important; $body-antialiased: true; $global-margin: 1rem; $global-padding: 1rem; $global-weight-normal: normal; $global-weight-bold: bold; -$global-radius: 3px; +$global-radius: rem-calc(3); $global-text-direction: ltr; +$global-flexbox: false; +$print-transparent-backgrounds: true; + +@include add-foundation-colors; // 2. Breakpoints // -------------- @@ -75,8 +142,6 @@ $breakpoints: ( small: 0, medium: 640px, large: 1024px, - xlarge: 1200px, - xxlarge: 1440px, ); $breakpoint-classes: (small medium large); @@ -85,7 +150,7 @@ $breakpoint-classes: (small medium large); $grid-row-width: $global-width; $grid-column-count: 12; -$grid-column-responsive-gutter: ( +$grid-column-gutter: ( small: 20px, medium: 30px, ); @@ -101,27 +166,27 @@ $header-font-style: normal; $font-family-monospace: Consolas, 'Liberation Mono', Courier, monospace; $header-sizes: ( small: ( - 'h1': 24, - 'h2': 20, - 'h3': 19, + 'h1': 34, + 'h2': 24, + 'h3': 20, 'h4': 18, - 'h5': 17, - 'h6': 16, + 'h5': 16, + 'h6': 14, ), medium: ( - 'h1': 48, - 'h2': 40, - 'h3': 31, - 'h4': 25, - 'h5': 20, - 'h6': 16, + 'h1': 44, + 'h2': 34, + 'h3': 24, + 'h4': 19, + 'h5': 16, + 'h6': 13, ), ); $header-color: inherit; $header-lineheight: 1.4; $header-margin-bottom: 0.5rem; $header-text-rendering: optimizeLegibility; -$small-font-size: 80%; +$small-font-size: rem-calc(14); $header-small-font-color: $medium-gray; $paragraph-lineheight: 1.6; $paragraph-margin-bottom: 1rem; @@ -177,9 +242,9 @@ $stat-font-size: 2.5rem; $abide-inputs: true; $abide-labels: true; -$input-background-invalid: $alert-color; -$form-label-color-invalid: $alert-color; -$input-error-color: $alert-color; +$input-background-invalid: map-get($foundation-palette, alert); +$form-label-color-invalid: map-get($foundation-palette, alert); +$input-error-color: map-get($foundation-palette, alert); $input-error-font-size: rem-calc(12); $input-error-font-weight: $global-weight-bold; @@ -188,12 +253,12 @@ $input-error-font-weight: $global-weight-bold; $accordion-background: $white; $accordion-plusminus: true; -$accordion-item-color: foreground($accordion-background, $primary-color); +$accordion-item-color: foreground($accordion-background, $text); $accordion-item-background-hover: $light-gray; $accordion-item-padding: 1.25rem 1rem; $accordion-content-background: $white; $accordion-content-border: 1px solid $light-gray; -$accordion-content-color: foreground($accordion-background, $primary-color); +$accordion-content-color: foreground($accordion-background, $text); $accordion-content-padding: 1rem; // 8. Accordion Menu @@ -231,11 +296,11 @@ $button-margin: 0 0 $global-margin 0; $button-fill: solid; $button-background: $primary-color; $button-background-hover: scale-color($button-background, $lightness: -15%); -$button-color: #fff; -$button-color-alt: #000; +$button-color: $white; +$button-color-alt: $black; $button-radius: $global-radius; $button-sizes: ( - tiny: $small-font-size, + tiny: 0.6rem, small: 0.75rem, default: 0.9rem, large: 1.25rem, @@ -320,7 +385,7 @@ $fieldset-padding: rem-calc(20); $fieldset-margin: rem-calc(18 0); $legend-padding: rem-calc(0 3); $form-spacing: rem-calc(16); -$helptext-color: #333; +$helptext-color: $black; $helptext-font-size: rem-calc(13); $helptext-font-style: italic; $input-prefix-color: $black; @@ -332,9 +397,10 @@ $form-label-font-size: rem-calc(14); $form-label-font-weight: $global-weight-normal; $form-label-line-height: 1.8; $select-background: $white; -$select-triangle-color: #333; +$select-triangle-color: $dark-gray; $select-radius: $global-radius; $input-color: $black; +$input-placeholder-color: $medium-gray; $input-font-family: inherit; $input-font-size: rem-calc(16); $input-background: $white; @@ -344,7 +410,7 @@ $input-border: 1px solid $medium-gray; $input-border-focus: 1px solid $dark-gray; $input-shadow: inset 0 1px 2px rgba($black, 0.1); $input-shadow-focus: 0 0 5px $medium-gray; -$input-cursor-disabled: default; +$input-cursor-disabled: not-allowed; $input-transition: box-shadow 0.5s, border-color 0.25s ease-in-out; $input-number-spinners: true; $input-radius: $global-radius; @@ -356,7 +422,7 @@ $label-background: $primary-color; $label-color: foreground($label-background); $label-font-size: 0.8rem; $label-padding: 0.33333rem 0.5rem; -$label-radius: $global-radius*10; +$label-radius: $global-radius; // 21. Media Object // ---------------- @@ -371,10 +437,21 @@ $mediaobject-image-width-stacked: 100%; $menu-margin: 0; $menu-margin-nested: 1rem; $menu-item-padding: 0.7rem 1rem; +$menu-item-color-active: $white; +$menu-item-background-active: map-get($foundation-palette, primary); $menu-icon-spacing: 0.25rem; -$menu-expand-max: 6; -// 23. Off-canvas +// 23. Meter +// --------- + +$meter-height: 1rem; +$meter-radius: $global-radius; +$meter-background: $medium-gray; +$meter-fill-good: $success-color; +$meter-fill-medium: $warning-color; +$meter-fill-bad: $alert-color; + +// 24. Off-canvas // -------------- $offcanvas-size: 250px; @@ -387,7 +464,7 @@ $offcanvas-exit-background: rgba($white, 0.25); $maincontent-class: 'off-canvas-content'; $maincontent-shadow: 0 0 10px rgba($black, 0.5); -// 24. Orbit +// 25. Orbit // --------- $orbit-bullet-background: $medium-gray; @@ -402,7 +479,7 @@ $orbit-control-background-hover: rgba($black, 0.5); $orbit-control-padding: 1rem; $orbit-control-zindex: 10; -// 25. Pagination +// 26. Pagination // -------------- $pagination-font-size: rem-calc(14); @@ -416,10 +493,10 @@ $pagination-item-background-current: $primary-color; $pagination-item-color-current: foreground($pagination-item-background-current); $pagination-item-color-disabled: $medium-gray; $pagination-ellipsis-color: $black; -$pagination-mobile-items: true; +$pagination-mobile-items: false; $pagination-arrows: true; -// 26. Progress Bar +// 27. Progress Bar // ---------------- $progress-height: 1rem; @@ -428,24 +505,24 @@ $progress-margin-bottom: $global-margin; $progress-meter-background: $primary-color; $progress-radius: $global-radius; -// 27. Reveal +// 28. Reveal // ---------- $reveal-background: $white; $reveal-width: 600px; $reveal-max-width: $global-width; -$reveal-offset: rem-calc(100); $reveal-padding: $global-padding; $reveal-border: 1px solid $medium-gray; $reveal-radius: $global-radius; $reveal-zindex: 1005; $reveal-overlay-background: rgba($black, 0.45); -// 28. Slider +// 29. Slider // ---------- +$slider-width-vertical: 0.5rem; +$slider-transition: all 0.2s ease-in-out; $slider-height: 0.5rem; -$slider-width-vertical: $slider-height; $slider-background: $light-gray; $slider-fill-background: $medium-gray; $slider-handle-height: 1.4rem; @@ -453,9 +530,8 @@ $slider-handle-width: 1.4rem; $slider-handle-background: $primary-color; $slider-opacity-disabled: 0.25; $slider-radius: $global-radius; -$slider-transition: all 0.2s ease-in-out; -// 29. Switch +// 30. Switch // ---------- $switch-background: $medium-gray; @@ -471,7 +547,7 @@ $switch-paddle-offset: 0.25rem; $switch-paddle-radius: $global-radius; $switch-paddle-transition: all 0.25s ease-out; -// 30. Table +// 31. Table // --------- $table-background: $white; @@ -488,23 +564,22 @@ $table-foot-background: smart-scale($table-background, $table-color-scale); $table-head-font-color: $body-font-color; $show-header-for-stacked: false; -// 31. Tabs +// 32. Tabs // -------- $tab-margin: 0; $tab-background: $white; -$tab-background-active: $light-gray; -$tab-border: $light-gray; -$tab-item-color: foreground($tab-background, $primary-color); +$tab-background-active: $white; +$tab-item-font-size: $base-font-size; $tab-item-background-hover: $white; -$tab-item-padding: 1.25rem 1.5rem; +$tab-item-padding: $line-height/2 0; $tab-expand-max: 6; $tab-content-background: $white; -$tab-content-border: $light-gray; +$tab-content-border: $border; $tab-content-color: foreground($tab-background, $primary-color); $tab-content-padding: 1rem; -// 32. Thumbnail +// 33. Thumbnail // ------------- $thumbnail-border: solid 4px $white; @@ -514,7 +589,7 @@ $thumbnail-shadow-hover: 0 0 6px 1px rgba($primary-color, 0.5); $thumbnail-transition: box-shadow 200ms ease-out; $thumbnail-radius: $global-radius; -// 33. Title Bar +// 34. Title Bar // ------------- $titlebar-background: $black; @@ -525,7 +600,7 @@ $titlebar-icon-color: $white; $titlebar-icon-color-hover: $medium-gray; $titlebar-icon-spacing: 0.25rem; -// 34. Tooltip +// 35. Tooltip // ----------- $has-tip-font-weight: $global-weight-bold; @@ -536,13 +611,14 @@ $tooltip-padding: 0.75rem; $tooltip-font-size: $small-font-size; $tooltip-pip-width: 0.75rem; $tooltip-pip-height: $tooltip-pip-width * 0.866; -$tooltip-pip-offset: 1.25rem; $tooltip-radius: $global-radius; -// 35. Top Bar +// 36. Top Bar // ----------- $topbar-padding: 0.5rem; $topbar-background: $light-gray; +$topbar-submenu-background: $topbar-background; $topbar-title-spacing: 1rem; $topbar-input-width: 200px; +$topbar-unstack-breakpoint: medium; diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index e01655f6d..fd6ed0815 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -5,70 +5,133 @@ // 03. List elements // 04. Stats // 05. Management +// 06. Polls // // 01. Global styles -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ----------------- + +$admin-color: #CF3638; body.admin { + header { + border: 0; + + .top-links { + background: darken($admin-color, 15%); + } + + .back-web { + padding-top: $line-height/4; + text-decoration: underline; + } + } + + .top-bar { + background: $admin-color !important; + height: auto; + } + + .top-bar-title { + + h1 { + margin-bottom: 0; + } + } + form { + .button { margin-top: 0; + + &.margin-top { + margin-top: $line-height; + } } input[type="text"], textarea { - height: 48px\9; - line-height: 48px\9; - margin-bottom: 24px\9; width: 100%; } + + .fieldset { + + select { + height: $line-height*2; + } + + .input-group input[type="text"] { + border-radius: 0; + margin-bottom: 0 !important; + } + } } table { + th { + text-align: left; + + &.text-center { + text-align: center; + } + + &.text-right { + padding-right: $line-height; + text-align: right; + } + + &.with-button { + line-height: $line-height*2; + } + } + tr { - background: #f4f4f4; + background: white; + border: 1px solid $border; - &:nth-child(odd) { - background: white; - } - - &:nth-child(even) { - background: #f0f0f0; + &:hover { + background: #f3f6f7; } } - td.small { - font-size: $small-font-size; + &.fixed { + table-layout: fixed; } + + input[type="submit"] ~ a, a ~ a { + margin-left: 0; + margin-right: 0; + margin-top: $line-height/2; + + @include breakpoint(medium) { + margin-left: $line-height/2; + margin-right: $line-height/2; + margin-top: 0; + } + } + } + + hr { + max-width: none; + } + + .menu.simple li.active { + border-bottom: 2px solid $admin-color; + color: $admin-color; + } + + .tabs-panel { + padding-left: 0; + padding-right: 0; } #proposals { width: 100% !important; } - .dashboard { - margin-bottom: rem-calc(48); - - ul, ol { - margin-left: rem-calc(12); - - li { - font-size: rem-calc(15); - line-height: rem-calc(30); - margin-bottom: rem-calc(12); - } - } - - p { - font-size: rem-calc(15); - line-height: rem-calc(30); - } - - h3 { - font-weight: bold; - } + .accordion-title { + font-size: $base-font-size; } .button.secondary { @@ -77,7 +140,7 @@ body.admin { .admin-content { - .proposal-new, .proposal-edit { + .proposal-form { padding-top: 0; } @@ -99,104 +162,144 @@ body.admin { display: none; } +.admin-content { + padding: $line-height !important; +} + +@include breakpoint(medium) { + + tr { + + .on-hover-block { + display: none; + } + + &:hover .on-hover-block { + display: block; + margin: 0; + margin-top: $line-height/2; + width: 100%; + } + } +} + +@include breakpoint(medium) { + + tr { + + .on-hover { + display: none; + } + + &:hover .on-hover { + display: inline; + } + } +} + +.input-group { + + .input-group-button { + padding-bottom: rem-calc(16); + } +} + // 02. Sidebar -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ----------- .admin-sidebar { + border-right: 1px solid $border; - a { - color: white\9 !important; + @include breakpoint(medium) { + min-height: rem-calc(1100); } ul { list-style-type: none; + margin-bottom: 0; margin-left: 0; padding: 0; [class^="icon-"] { + color: $admin-color; display: inline-block; font-size: rem-calc(24); - padding-right: rem-calc(24); - padding-top: rem-calc(4); - padding-left: 12px\9 !important; - padding-right: 12px\9 !important; + line-height: $line-height; + padding: $line-height/2 $line-height/4; + padding-left: 0; + vertical-align: middle; } li { - background: #2E343F; - border-bottom: 1px solid #292f39; - border-top: 1px solid #353c49; + background: white; margin: 0; outline: 0; - &:first-child { - font-weight: bold; - text-transform: uppercase; + ul { + margin-left: $line-height/1.5; + border-left: 1px solid $border; + padding-left: $line-height/2; } - &.active{ - background: #373D47; + &.section-title { + border-bottom: 1px solid $border; + } - a:not(.button) { - color: white; - } + &.active a { + background: #f3f6f7; + border-radius: rem-calc(6); + -moz-border-radius: rem-calc(6); + -webkit-border-radius: rem-calc(6); + color: $admin-color; + font-weight: bold; } } - li a:not(.button) { - color: rgba(255,255,255,0.3); + li a { + color: $text; + display: block; line-height: rem-calc(48); padding-left: rem-calc(12); vertical-align: top; &:hover { - color: white; + background: #f3f6f7; + border-radius: rem-calc(6); + -moz-border-radius: rem-calc(6); + -webkit-border-radius: rem-calc(6); + color: $admin-color; + text-decoration: none; } } } + + .is-accordion-submenu-parent { + + & > a::after { + border-color: $admin-color transparent transparent; + } + } + + .submenu { + border-bottom: 0; + margin-left: $line-height; + + li:first-child { + padding-top: $line-height/2; + } + + li:last-child { + padding-bottom: $line-height/2; + } + + a { + font-weight: normal; + } + } } // 03. List elements -// - - - - - - - - - - - - - - - - - - - - - - - - - - -.admin-list { - list-style-type: none; - margin: 0; - margin-bottom: rem-calc(48); - - form { - clear: both; - - .checkbox { - font-size: $small-font-size; - } - } - - li { - border-bottom: 1px solid #E7E9EC; - font-size: rem-calc(14); - min-height: rem-calc(72); - padding: rem-calc(12); - - &:first-child { - border-top: 1px solid #E7E9EC; - } - - &:nth-child(odd) { - background: #F0F2F6; - } - } - - .tag { - float: left; - font-size: rem-calc(18); - padding: 0; - } - - .button { - margin: 0; - } -} +// ----------------- .delete { border-bottom: 1px dotted #CF2A0E; @@ -209,6 +312,11 @@ body.admin { } } +[class^="icon-"].delete { + border: 0; + font-size: $base-font-size; +} + .verified { color: $check; @@ -239,14 +347,9 @@ body.admin { font-style: italic; } -.level { - font-size: rem-calc(12); -} - .official { background-color: #e7e7e7; border-radius: rem-calc(3); - font-size: rem-calc(12); font-weight: normal; padding: rem-calc(6) rem-calc(12); } @@ -262,13 +365,10 @@ body.admin { .moderation-description { max-height: rem-calc(65); overflow: hidden; - max-width: rem-calc(590); + max-width: rem-calc(700); &:hover { - cursor: text; max-height: rem-calc(1000); - outline: 3px solid #ffbf47; - padding: rem-calc(12); transition: max-height 0.9s; -moz-transition: max-height 0.9s; -webkit-transition: max-height 0.9s; @@ -276,7 +376,7 @@ body.admin { } // 04. Stats -// - - - - - - - - - - - - - - - - - - - - - - - - - +// --------- .stats { background: white; @@ -304,7 +404,7 @@ body.admin { } // 05. Management -// - - - - - - - - - - - - - - - - - - - - - - - - - +// -------------- .user-permissions { @@ -337,8 +437,8 @@ body.admin { border-radius: rem-calc(3); font-size: rem-calc(16); font-weight: normal; - margin-bottom: rem-calc(12); - padding: rem-calc(6) rem-calc(12); + margin: $line-height; + padding: $line-height/2; strong { font-size: rem-calc(18); @@ -349,3 +449,101 @@ body.admin { margin-bottom: 0; } +body.admin { + + .investment-projects-list.medium-9 { + width: 100%; + } + + .investment-projects-summary { + + th, td { + text-align: center; + + &:first-child { + font-weight: bold; + text-align: left; + } + + &:last-child { + font-weight: bold; + } + } + + tr { + &:nth-child(even) td:last-child { + background: $success-border; + } + + &:nth-child(odd) td:last-child { + background: $success-bg; + } + } + } +} + +.admin-content .select-geozone, .admin-content .select-heading { + + a { + display: block; + + &.active { + color: $brand; + font-weight: bold; + text-decoration: underline; + } + } +} + +table.investment-projects-summary { + + td.total-price { + white-space: nowrap; + } +} + +body.admin { + + .geozone { + background: #ececec; + border-radius: rem-calc(6); + color: $text; + display: inline-block; + font-size: $small-font-size; + margin-bottom: $line-height/3; + padding: $line-height/4 $line-height/3; + text-decoration: none; + + &:hover { + background: #e0e0e0; + } + } +} + +// 06. Polls +// ----------------- + +.count-error { + background: $alert-bg !important; + color: $color-alert; + font-weight: bold; +} + +table { + + .callout { + height: $line-height*2; + line-height: $line-height*2; + padding: 0 $line-height/2; + } +} + +// 07. CMS +// -------------- +.cms_page_list { + + [class^="icon-"] { + padding-right: $menu-icon-spacing; + vertical-align: middle; + } +} diff --git a/app/assets/stylesheets/annotator_overrides.scss b/app/assets/stylesheets/annotator_overrides.scss index 0a8dd92bb..962cdefd7 100644 --- a/app/assets/stylesheets/annotator_overrides.scss +++ b/app/assets/stylesheets/annotator_overrides.scss @@ -24,7 +24,7 @@ border-radius: 0; bottom: $line-height; box-shadow: 0 0 5px rgba(0,0,0,0.05); - font-family: $font-sans; + font-family: $body-font-family; font-size: $base-font-size; line-height: $line-height; min-width: $line-height*13; @@ -57,7 +57,7 @@ border: none; box-shadow: none; color: $link; - font-family: $font-sans; + font-family: $body-font-family; font-size: $base-font-size; font-weight: normal; text-shadow: none; diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 68065e82a..0720c103f 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -2,11 +2,12 @@ @import "foundation_and_overrides"; @import "fonts"; @import "icons"; -@import "variables"; @import "mixins"; @import "admin"; @import "layout"; @import "participation"; +@import "pages"; +@import "custom"; @import "c3"; @import "annotator.min"; @import "annotator_overrides"; diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss new file mode 100644 index 000000000..090eb0342 --- /dev/null +++ b/app/assets/stylesheets/custom.scss @@ -0,0 +1,5 @@ +// Overrides and adds customized styles in this file +// Read more on documentation: +// * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md#css +// * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md#css +// diff --git a/app/assets/stylesheets/datepicker_overrides.scss b/app/assets/stylesheets/datepicker_overrides.scss index abab720e0..dbbab4bd5 100644 --- a/app/assets/stylesheets/datepicker_overrides.scss +++ b/app/assets/stylesheets/datepicker_overrides.scss @@ -51,7 +51,7 @@ border-right: 1px solid $dark; tr th { - color: white; + color: $dark; } } @@ -85,7 +85,7 @@ background: white; .ui-state-default { - background: $input-bg; + background: #F8F8F8; color: $text-medium; } } diff --git a/app/assets/stylesheets/foundation_and_overrides.scss b/app/assets/stylesheets/foundation_and_overrides.scss index f175ce046..992b0cc81 100644 --- a/app/assets/stylesheets/foundation_and_overrides.scss +++ b/app/assets/stylesheets/foundation_and_overrides.scss @@ -1,14 +1,9 @@ @charset 'utf-8'; @import 'settings'; +@import 'custom_settings'; @import 'foundation'; -// If you'd like to include motion-ui, you need to install the motion-ui sass package. -// -// @import 'motion-ui/motion-ui'; - -// We include everything by default. To slim your CSS, remove components you don't use. - @include foundation-global-styles; @include foundation-grid; @include foundation-typography; @@ -18,7 +13,6 @@ @include foundation-float-classes; @include foundation-accordion; @include foundation-accordion-menu; -@include foundation-badge; @include foundation-breadcrumbs; @include foundation-button-group; @include foundation-callout; @@ -31,10 +25,8 @@ @include foundation-media-object; @include foundation-menu; @include foundation-off-canvas; -@include foundation-orbit; @include foundation-pagination; @include foundation-progress-bar; -@include foundation-slider; @include foundation-sticky; @include foundation-reveal; @include foundation-switch; @@ -42,10 +34,5 @@ @include foundation-tabs; @include foundation-thumbnail; @include foundation-title-bar; -@include foundation-tooltip; @include foundation-top-bar; - -// If you'd like to include motion-ui, you need to install the motion-ui sass package. -// -// @include motion-ui-transitions; -// @include motion-ui-animations; +@include foundation-menu-icon; diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss index 2c06f745a..cde59e594 100644 --- a/app/assets/stylesheets/icons.scss +++ b/app/assets/stylesheets/icons.scss @@ -163,3 +163,36 @@ .icon-whatsapp:before { content: "\50"; } +.icon-zip:before { + content: "\4f"; +} +.icon-banner:before { + content: "\51"; +} +.icon-arrow-down:before { + content: "\52"; +} +.icon-arrow-left:before { + content: "\54"; +} +.icon-arrow-right:before { + content: "\55"; +} +.icon-check-circle:before { + content: "\56"; +} +.icon-arrow-top:before { + content: "\57"; +} +.icon-help-1:before { + content: "\58"; +} +.icon-checkmark-circle:before { + content: "\59"; +} +.icon-telegram:before { + content: "\31"; +} +.icon-instagram:before { + content: "\32"; +} diff --git a/app/assets/stylesheets/ie.scss b/app/assets/stylesheets/ie.scss new file mode 100644 index 000000000..2c494747b --- /dev/null +++ b/app/assets/stylesheets/ie.scss @@ -0,0 +1,199 @@ +// Stylesheet for Internet Explorer +// +// Table of Contents +// 01. Global styles +// 02. Admin +// + +// 01. Global styles +// ----------------- + +*, *:before, *:after { + box-sizing: border-box !important; +} + +.show-for-medium-down, +.show-for-medium, +.show-for-small { + display: none !important; +} + +.show-for-large-up, +.hide-for-medium-down { + display: block !important; +} + +.column, .columns { + display: inline-block !important; + float: none !important; + box-sizing: border-box !important; +} + +.small-1, .row .small-1 { width: 7.33333%; } +.small-2, .row .small-2 { width: 15.66667%; } +.small-3, .row .small-3 { width: 24%; } +.small-4, .row .small-4 { width: 32.33333%; } +.small-5, .row .small-5 { width: 40.66667%; } +.small-6, .row .small-6 { width: 49%; } +.small-7, .row .small-7 { width: 57.33333%; } +.small-8, .row .small-8 { width: 65.66667%; } +.small-9, .row .small-9 { width: 74%; } +.small-10, .row .small-10 { width: 82.33333%; } +.small-11, .row .small-11 { width: 90.66667%; } +.small-12, .row .small-12 { width: 99%; } + +.medium-1, .row .medium-1 { width: 7.33333%; } +.medium-2, .row .medium-2 { width: 15.66667%; } +.medium-3, .row .medium-3 { width: 24%; } +.medium-4, .row .medium-4 { width: 32.33333%; } +.medium-5, .row .medium-5 { width: 40.66667%; } +.medium-6, .row .medium-6 { width: 49%; } +.medium-7, .row .medium-7 { width: 57.33333%; } +.medium-8, .row .medium-8 { width: 65.66667%; } +.medium-9, .row .medium-9 { width: 74%; } +.medium-10, .row .medium-10 { width: 82.33333%; } +.medium-11, .row .medium-11 { width: 90.66667%; } +.medium-12, .row .medium-12 { width: 99%; } + +.large-1, .row .large-1 { width: 7.33333%; } +.large-2, .row .large-2 { width: 15.66667%; } +.large-3, .row .large-3 { width: 24%; } +.large-4, .row .large-4 { width: 32.33333%; } +.large-5, .row .large-5 { width: 40.66667%; } +.large-6, .row .large-6 { width: 49%; } +.large-7, .row .large-7 { width: 57.33333%; } +.large-8, .row .large-8 { width: 65.66667%; } +.large-9, .row .large-9 { width: 74%; } +.large-10, .row .large-10 { width: 82.33333%; } +.large-11, .row .large-11 { width: 90.66667%; } +.large-12, .row .large-12 { width: 99%; } + +.row .small-offset-1 { margin-left: 7.33333%; } +.row .small-offset-2 { margin-left: 15.66667%; } +.row .small-offset-3 { margin-left: 24%; } +.row .small-offset-4 { margin-left: 32.33333%; } +.row .small-offset-5 { margin-left: 40.66667%; } +.row .small-offset-6 { margin-left: 49%; } +.row .small-offset-7 { margin-left: 57.33333%; } +.row .small-offset-8 { margin-left: 65.66667%; } +.row .small-offset-9 { margin-left: 74%; } +.row .small-offset-10 { margin-left: 82.33333%; } +.row .small-offset-11 { margin-left: 90.66667%; } +.row .small-offset-12 { margin-left: 99%; } + +.row .medium-offset-1 { margin-left: 7.33333%; } +.row .medium-offset-2 { margin-left: 15.66667%; } +.row .medium-offset-3 { margin-left: 24%; } +.row .medium-offset-4 { margin-left: 32.33333%; } +.row .medium-offset-5 { margin-left: 40.66667%; } +.row .medium-offset-6 { margin-left: 49%; } +.row .medium-offset-7 { margin-left: 57.33333%; } +.row .medium-offset-8 { margin-left: 65.66667%; } +.row .medium-offset-9 { margin-left: 74%; } +.row .medium-offset-10 { margin-left: 82.33333%; } +.row .medium-offset-11 { margin-left: 90.66667%; } +.row .medium-offset-12 { margin-left: 99%; } + +.row .large-offset-1 { margin-left: 7.33333%; } +.row .large-offset-2 { margin-left: 15.66667%; } +.row .large-offset-3 { margin-left: 24%; } +.row .large-offset-4 { margin-left: 32.33333%; } +.row .large-offset-5 { margin-left: 40.66667%; } +.row .large-offset-6 { margin-left: 49%; } +.row .large-offset-7 { margin-left: 57.33333%; } +.row .large-offset-8 { margin-left: 65.66667%; } +.row .large-offset-9 { margin-left: 74%; } +.row .large-offset-10 { margin-left: 82.33333%; } +.row .large-offset-11 { margin-left: 90.66667%; } +.row .large-offset-12 { margin-left: 99%; } + +.top-bar { + clear: both !important; + height: 100px !important; +} + +.locale, .external-links { + background: #002d50 !important; +} + +.locale { + float: left !important; +} + +.external-links { + color: white !important; + float: right !important; +} + +.top-bar-title, .top-bar-title a, .top-bar-title a { + display: inline-block !important; + float: none !important; +} + +.top-bar-title a { + line-height: 24px !important; + width: 100% !important; +} + +.proposal .supports { + display: inline-block !important; + margin: 0 !important; + position: inherit !important; + + &:after { + content: none !important; + } +} + +form { + + input, textarea { + height: 48px !important; + line-height: 48px !important; + margin-bottom: 24px !important; + width: 100% !important; + } + + input[type="checkbox"], + input[type="radio"] { + height: auto !important; + line-height: inherit !important; + width: auto !important; + } + + input[type="radio"] { + width: 18px !important; + } +} + +.subnavigation { + display: block; + height: 60px !important; + width: 100%; + + ul li a { + margin-left: 10px !important; + margin-right: 10px !important; + } +} + +.truncate { + background: none; +} + +// 02. Admin +// --------- + +body.admin form { + + input[type="text"], textarea { + height: 48px !important; + line-height: 48px !important; + margin-bottom: 24px !important; + } +} + +.admin-sidebar ul [class^="icon-"] { + padding-left: 12px !important; + padding-right: 12px !important; +} diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index 75f8cf741..fa0fd3455 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -6,73 +6,41 @@ // 04. Tags // 05. Auth pages // 06. Forms -// 07. Alerts +// 07. Callout // 08. User account // 09. Search // 10. Official levels -// 12. Tables -// 13. Social -// 14. Pages -// 15. Verification -// 16. Comments -// 17. Flags -// 18. Activity +// 11. Tables +// 12. Social +// 13. Pages +// 14. Verification +// 15. Comments +// 16. Flags +// 17. Activity +// 18. Banners // // 01. Global styles -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ----------------- -*, html { - margin: 0; - padding: 0; -} - -html, body { - height: 100%; +::selection { + color: white; + background-color: $brand; } body { - background: white; - color: $text; - font-family: $font-sans; font-size: $base-font-size; } h1, h2, h3, h4, h5, h6 { clear: both; - font-family: $font-sans; - font-weight: $font-bold; -} - -h1 { - @include h1(); -} - -h2 { - @include h2(); -} - -h3 { - @include h3(); -} - -h4 { - @include h4(); -} - -h5 { - @include h5(); -} - -h6 { - @include h6(); - font-weight: $font-normal; + font-weight: 700; } p { font-size: $base-font-size; - font-weight: $font-normal; - line-height: $base-line-height; + font-weight: 400; + line-height: $base-line; } a { @@ -88,7 +56,8 @@ a { } &:focus { - outline: 0; + color: $link-hover; + outline: 3px solid #ffbf47; } } @@ -99,6 +68,15 @@ a { &:hover { text-decoration: none !important; } + + &.warning, &.warning:hover { + color: black; + } +} + +.button.hollow { + border: 1px solid $link; + color: $link; } .postfix.button { @@ -157,15 +135,23 @@ a { margin-bottom: $line-height; } +.margin-right { + margin-right: $line-height; +} + .no-margin-top { margin-top: rem-calc(-24); } -.padding { +.padding { padding-bottom: $line-height; padding-top: $line-height; } +.light { + background: $light; +} + .highlight { background: $highlight; } @@ -181,10 +167,6 @@ a { height: 100%; } -.wrap { - background: white; -} - .footer, .push { clear: both; min-height: $line-height*12; @@ -196,7 +178,7 @@ a { width: 100%; a.close { - font-size: $h2-font-size; + font-size: rem-calc(34); top: 20%; } } @@ -212,13 +194,6 @@ a { border: 0; } -#drop-help.f-dropdown.content { - - @media (min-width: $small-breakpoint) { - max-width: $line-height*15; - } -} - .menu.simple { border-bottom: 1px solid $border; margin: $line-height 0; @@ -237,6 +212,38 @@ a { } } +.menu.vertical { + background: white; + margin: $line-height 0; + padding: $line-height; + + li { + margin-bottom: $line-height; + + a { + color: $text-medium; + padding: 0; + } + + h2 { + font-size: $base-font-size; + } + + &.active { + border-bottom: 2px solid $brand; + color: $brand; + } + } + + &.no-margin-top { + margin-top: 0; + } + + &.no-padding-top { + padding-top: 0; + } +} + .small { font-size: $small-font-size; } @@ -254,11 +261,55 @@ a { } .back, .icon-angle-left { - @include back; + clear: both; + color: $text-medium; + float: left; +} + +.tabs-content { + border: 0; +} + +.tabs { + border: { + left: 0; + right: 0; + top: 0; + }; + margin-bottom: $line-height; + + .tabs-title > a { + color: $text-medium; + margin-bottom: rem-calc(-1); + margin-right: $line-height; + + &[aria-selected='true'], + &.is-active { + color: $brand; + border-bottom: 2px solid $brand; + font-weight: bold; + } + } + + h2, h3 { + font-size: $base-font-size; + } +} + +.no-max-width { + max-width: none; +} + +.button.float-right ~ .button.float-right { + margin: 0 $line-height/2; +} + +.pagination .current { + background: $brand; } // 02. Header -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ---------- header { background: $brand; @@ -270,13 +321,9 @@ header { } .locale { - float: none; + float: left; height: $line-height*1.5; - - @media (min-width: $small-breakpoint) { - float: left; - margin-left: $line-height/2; - } + margin-left: $line-height/2; } .external-links { @@ -284,7 +331,7 @@ header { padding: rem-calc(6) 0; text-align: center; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { float: right; } } @@ -298,7 +345,7 @@ header { padding-bottom: 0; padding-top: 0; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { padding: 0; } @@ -318,7 +365,7 @@ header { &.menu > li { display: block; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { display: table-cell; height: $line-height*3.5; } @@ -327,7 +374,7 @@ header { color: white; padding-left: 0; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { font-size: $small-font-size; padding: rem-calc(11) rem-calc(16); } @@ -335,7 +382,7 @@ header { &.button { text-align: left; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { background: white; color: $brand; text-align: center; @@ -369,11 +416,33 @@ header { text-decoration: underline; } } + + ul { + margin-bottom: 0; + + li { + display: block; + } + + @include breakpoint(medium) { + li { + display: inline-block; + + &:after { + content: "|"; + } + + &:last-child:after { + content: none; + } + } + } + } } .subnavigation { - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { background: white; padding-bottom: 0; } @@ -383,7 +452,7 @@ header { margin: 0; li { - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { display: inline-block; margin-right: rem-calc(36); } @@ -396,12 +465,10 @@ header { text-align: left; width: 100%; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { color: $text; display: block; font-weight: bold; - margin-left: 18px\9; - margin-right: 18px\9; width: auto; &:hover { @@ -412,7 +479,7 @@ header { &.active { color: white; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { border-bottom: 2px solid $brand; color: $brand; } @@ -424,7 +491,7 @@ header { .input-group { padding-top: $line-height/4; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { margin-bottom: 0; } } @@ -435,6 +502,7 @@ header { .input-group-button { line-height: $line-height*1.5; + padding-bottom: 0; button { background: $border; @@ -456,7 +524,6 @@ header { } .submenu { - background: white; border-bottom: 1px solid $border; clear: both; margin-bottom: $line-height/2; @@ -469,7 +536,7 @@ header { position: relative; text-align: left; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { margin-right: $line-height*1.5; } @@ -480,18 +547,30 @@ header { &.active { border-bottom: 2px solid $brand; color: $brand; + + &:hover { + text-decoration: none; + } } } + + h2 { + font-size: $base-font-size; + } +} + +.search-form-header input[type=text] { + max-width: none; } // 03. Footer -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ---------- footer { - color: $footer-color; + color: $text; .logo a { - font-family: $font-logo; + font-family: 'Lato' !important; text-decoration: none; &:hover { @@ -502,16 +581,17 @@ footer { p { font-size: $small-font-size; - .info a { + &.info a { text-decoration: underline; } } a, a:active, a:focus { - color: $footer-link; + color: $text; + text-decoration: underline; &:hover { - color: $footer-color; + color: $text-medium; } } @@ -519,32 +599,30 @@ footer { padding-left: 0; } - h2 a { - - border-bottom: 1px solid $footer-border; - display: block; - font-size: $h3-font-size; - line-height: $h3-line-height; - padding-bottom: $line-height/4; + a.title { + font-weight: bold; text-decoration: none; } } .footer { - background: $footer-bg; + background: $border; border-top: 6px solid $brand; + margin-top: $line-height*2; padding-top: $line-height; } .subfooter { - border-top: 1px solid $footer-border; + border-top: 1px solid $text-light; + font-size: $small-font-size; padding-top: $line-height/2; } // 04. Tags -// - - - - - - - - - - - - - - - - - - - - - - - - - +// -------- -.tags a , .tag-cloud a, .categories a, .geozone a { +.tags a , .tag-cloud a, .categories a, .geozone a, .sidebar-links a, +.tags span { background: #ececec; border-radius: rem-calc(6); color: $text; @@ -564,12 +642,12 @@ footer { color: $link; &:hover { - background: $highlight; - color: $link-hover; + background: $brand; + color: white; } } -h3.sidebar-title { +h2.sidebar-title { border-top: 2px solid $brand; display: inline-block; font-size: rem-calc(16); @@ -579,15 +657,31 @@ h3.sidebar-title { } // 05. Auth pages -// - - - - - - - - - - - - - - - - - - - - - - - - - +// -------------- .auth-page { + + .wrapper { + margin: 0 auto (-$line-height)*14; + } +} + +.auth-image { background: $brand image-url("auth_bg.jpg"); background-repeat: no-repeat; background-size: cover; - h1:not(.logo) { - @include logo; + @include breakpoint(medium) { + min-height: $line-height*42; + } + + h1 { + margin-top: $line-height; + + img { + height: rem-calc(80); + width: rem-calc(80); + } a { color: white; @@ -595,7 +689,7 @@ h3.sidebar-title { line-height: rem-calc(80); // Same as logo image height text-align: center; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { display: inline-block; text-align: left; } @@ -603,20 +697,32 @@ h3.sidebar-title { } } -.auth { +.auth-form { + + @include breakpoint(medium) { + padding-top: $line-height*4; + } p, a, .checkbox { font-size: $small-font-size; } +} - .panel { +.auth-divider { + border-bottom: 1px solid $border; + height: rem-calc(14); + margin: $line-height 0; + text-align: center; + + span { background: white; - border: 0; + font-weight: bold; + padding: 0 $line-height/2; } } // 06. Forms -// - - - - - - - - - - - - - - - - - - - - - - - - - +// --------- form.locale-form { display: inline-block; @@ -651,12 +757,15 @@ form.locale-form { padding-left: rem-calc(3); padding-right: $line-height; width: auto; + + &:focus { + outline: 3px solid #ffbf47; + } } } } form { - font-family: $font-sans; label { font-size: $base-font-size; @@ -668,24 +777,9 @@ form { font-weight: bold; } - input, textarea { - height: 48px\9; - line-height: 48px\9; - margin-bottom: 24px\9; - width: 100%\9; - } - - input[type="checkbox"], - input[type="radio"] { - height: auto\9; - line-height: inherit\9; - width: auto\9; - } - input[type="radio"] { height: $line-height !important; vertical-align: top; - width: 18px\9; + label { font-weight: normal; @@ -697,7 +791,7 @@ form { } input[type]:not([type=submit]):not([type=file]):not([type=checkbox]):not([type=radio]) { - background: $input-bg; + background: #F8F8F8; height: $line-height*2; margin-bottom: rem-calc(16); @@ -741,63 +835,91 @@ form { line-height: $line-height; vertical-align: middle; } +} - .captcha { - border: 0; - padding: $line-height/2 0; +// 07. Callout +// ----------- - input { - margin-bottom: 0 !important; +.callout-slide { + animation-duration: 1s; + -webkit-animation-duration: 1s; + animation-fill-mode: both; + -webkit-animation-fill-mode: both; + animation-name: slide; + -webkit-animation-name: slide; +} + +@-webkit-keyframes slide { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slide { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.notice-container { + min-width: $line-height*12; + position: absolute; + right: 24px; + top: 24px; + + .notice { + height: $line-height*4; + + .notice-text { + width: 95%; } } - - .button.margin-top { - margin-top: $line-height; - } } -.captcha { - - label { - display: none; - } -} - -// 07. Alerts -// - - - - - - - - - - - - - - - - - - - - - - - - - - .callout { - font-family: $font-sans; font-size: $small-font-size; - &.success { + a { + font-weight: bold; + text-decoration: underline; + } + + &.success, &.notice { background-color: $success-bg; border-color: $success-border; - color: $success-color; + color: $color-success; } &.primary { background-color: $info-bg; border-color: $info-border; - color: $info-color; + color: $color-info; } &.warning { background-color: $warning-bg; border-color: $warning-border; - color: $warning-color; - - a { - color: $warning-color; - font-weight: bold; - text-decoration: underline; - } + color: $color-warning; } - &.alert { + &.alert, &.error { background-color: $alert-bg; border-color: $alert-border; - color: $alert-color; + color: $color-alert; } .close { @@ -807,27 +929,27 @@ form { span.error, small.error { background: $alert-bg; - color: $alert-color; + color: $color-alert; } span.no-error, small.no-error { background: $success-bg; - color: $success-color; + color: $color-success; } .error small.error { background: $alert-bg; - color: $alert-color; + color: $color-alert; display: inline-block; margin: 0 $line-height/4; } label.error, label.error a { - color: $alert-color; + color: $color-alert; } // 08. User account -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ---------------- .account { @@ -843,7 +965,7 @@ label.error, label.error a { .final-votes-info { background: $warning-bg; border: 1px solid $warning-border; - color: $warning-color; + color: $color-warning; margin-top: $line-height; padding: $line-height/2; @@ -866,16 +988,12 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar { } .author-deleted, .user-deleted { - background-color: rgba(255,255,255,.5); color: rgba(0,0,0,.4); - font-size: rem-calc(40); - left: 11px; - position: absolute; - top: 72px; -} - -.user-deleted { - top: -4px; + display: inline-block; + font-size: rem-calc(32); + line-height: rem-calc(32); + height: rem-calc(32); + vertical-align: top; } .user-permissions { @@ -920,16 +1038,16 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar { } [class^="icon-"] { - font-size: $h4-font-size; + font-size: rem-calc(19); vertical-align: middle; } .icon-circle { color: #ecf00b; - font-size: $tiny-font-size; + font-size: rem-calc(10); position: absolute; - right: 4px; - top: -6px; + right: 8px; + top: 6px; } } @@ -937,9 +1055,9 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar { background: $border; content: ''; height: 100%; - left: 28px; + left: 22px; position: absolute; - top: 84px; + top: 60px; width: 2px; } @@ -968,7 +1086,7 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar { color: $brand; content: "\4d"; font-family: "icons" !important; - left: 6px; + left: 0; position: absolute; } @@ -984,32 +1102,18 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar { } // 09. Search -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ---------- -.search-form { - - h3 { - border-top: 1px solid $votes-border; - display: inline-block; - font-size: rem-calc(16); - margin: -1px 0 $line-height/2; - padding-top: $line-height/4; - text-transform: uppercase; - } -} - -.search-results { - - h2 { - margin-bottom: 0; - } +.search-results h2 { + margin-bottom: 0; } .advanced-search { float: left; margin: $line-height 0; + position: inherit; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { float: right; margin-bottom: 0; margin-top: rem-calc(28); @@ -1026,55 +1130,46 @@ img.avatar, img.admin-avatar, img.moderator-avatar, img.initialjs-avatar { height: $line-height*2; } - @media (max-width: $medium-breakpoint) { - - .column { + @include breakpoint(medium) { + & > .column { padding: 0; } } } // 10. Officials levels -// - - - - - - - - - - - - - - - - - - - - - - - - - +// -------------------- [class^="level-"] { - color: white; + color: black; +} + +.is-author, .is-association { + color: black; } .is-author { - background: $author; + background: #00A5F1; } .is-association { - background: $association; -} - -.is-deleted { - background: $deleted; + background: #E38D83; } .level-1 { - background: $level-1; + background: #1ABC9C; } -.level-2 { - background: $level-2; -} - -.level-3 { - background: $level-3; -} - -.level-4 { - background: $level-4; +.level-2, .level-3, .level-4 { + background: #43AC6A; } .level-5 { - background: $level-5; + background: #F08A24; } -// 12. Tables -// - - - - - - - - - - - - - - - - - - - - - - - - - +// 11. Tables +// ---------- table { border: $border; @@ -1107,12 +1202,26 @@ table { } } -// 13. Social -// - - - - - - - - - - - - - - - - - - - - - - - - - +.table-for-mobile { + + @include breakpoint(medium down) { + th, td { + display: block; + text-align: left; + } + } +} + +// 12. Social +// ---------- .button.button-twitter, .button.button-facebook, -.button.button-google { +.button.button-google, +.button.button-telegram { + background: white; + color: $text; + font-weight: bold; height: $line-height*2; line-height: $line-height*2; padding: 0; @@ -1120,9 +1229,11 @@ table { } .button.button-twitter { - background: #45B0E3; + background: #ECF7FC; + border-left: 3px solid #45B0E3; &:before { + color: #45B0E3; content: "f"; font-family: "icons" !important; font-size: rem-calc(24); @@ -1134,12 +1245,13 @@ table { } } -.social-share-button-twitter { +.ssb-twitter { background: #45B0E3; + background-image: none !important; color: white; - height: $line-height*2; + height: $line-height*2 !important; position: relative; - width: $line-height*2; + width: $line-height*2 !important; &:before { content: "f"; @@ -1152,16 +1264,18 @@ table { top: 0; } - &:hover { + &:hover, &:focus { background: white; color: #40A2D1; } } .button.button-facebook { - background: #3B5998; + background: #EBEEF4; + border-left: 3px solid #3B5998; &:before { + color: #3B5998; content: "A"; font-family: "icons" !important; font-size: rem-calc(24); @@ -1173,12 +1287,13 @@ table { } } -.social-share-button-facebook { +.ssb-facebook { background: #3B5998; + background-image: none !important; color: white; - height: rem-calc(48); + height: rem-calc(48) !important; position: relative; - width: rem-calc(48); + width: rem-calc(48) !important; &:before { content: "A"; @@ -1191,16 +1306,18 @@ table { top: 0; } - &:hover { + &:hover, &:focus { background: white; color: #354F88; } } .button.button-google { - background: #DE4C34; + background: #FCEDEA; + border-left: 3px solid #DE4C34; &:before { + color: #DE4C34; content: "B"; font-family: "icons" !important; font-size: rem-calc(24); @@ -1212,12 +1329,13 @@ table { } } -.social-share-button-google_plus { +.ssb-google_plus { background: #DE4C34; + background-image: none !important; color: white; - height: $line-height*2; + height: $line-height*2 !important; position: relative; - width: $line-height*2; + width: $line-height*2 !important; &:before { content: "B"; @@ -1230,16 +1348,65 @@ table { top: 0; } - &:hover { + &:hover, &:focus { background: white; color: #CE3E26; } } +.button.button-telegram { + background: #ECF7FC; + border-left: 3px solid #0088cc; + + &:before { + color: #0088cc; + content: "1"; + font-family: "icons" !important; + font-size: rem-calc(24); + left: 0; + line-height: $line-height*2; + padding: 0 rem-calc(20); + position: absolute; + top: 0; + } +} + +.ssb-telegram { + background: #0088cc; + background-image: none !important; + color: white; + height: $line-height*2 !important; + position: relative; + width: $line-height*2 !important; + + &:before { + content: "1"; + font-family: "icons" !important; + font-size: rem-calc(24); + left: 50%; + line-height: $line-height*2; + margin-left: rem-calc(-11); + position: absolute; + top: 0; + } + + &:hover, &:focus { + background: white; + color: #40A2D1; + } +} + +@include breakpoint(medium) { + + .button.button-telegram, .ssb-telegram { + display: none !important; + } +} + .social { a { - font-size: $h3-font-size; + font-size: rem-calc(24); margin: 0 $line-height/2; text-decoration: none; @@ -1250,13 +1417,14 @@ table { } .share-supported { + text-align: center; .social-share-button { display: inline-block; } - .social-share-button-twitter { - background: none; + .ssb-twitter { + background: #45B0E3; color: white; height: $line-height; position: relative; @@ -1273,13 +1441,14 @@ table { top: 0; } - &:hover { + &:hover, &:focus { + background: white; color: #40A2D1; } } - .social-share-button-facebook { - background: none; + .ssb-facebook { + background: #3B5998; color: white; height: rem-calc(24); position: relative; @@ -1296,13 +1465,14 @@ table { top: 0; } - &:hover { + &:hover, &:focus { + background: white; color: #354F88; } } - .social-share-button-google_plus { - background: none; + .ssb-google_plus { + background: #DE4C34; color: white; height: rem-calc(24); position: relative; @@ -1319,14 +1489,39 @@ table { top: 0; } - &:hover { + &:hover, &:focus { + background: white; color: #CE3E26; } } + + .ssb-telegram { + background: #0088cc; + color: white; + height: $line-height; + position: relative; + width: $line-height*2; + + &:before { + content: "1"; + font-family: "icons" !important; + font-size: rem-calc(24); + left: 50%; + line-height: $line-height*2; + margin-left: rem-calc(-11); + position: absolute; + top: 0; + } + + &:hover, &:focus { + background: white; + color: #40A2D1; + } + } } -// 14. Pages -// - - - - - - - - - - - - - - - - - - - - - - - - - +// 13. Pages +// --------- .more-information { @@ -1365,7 +1560,7 @@ table { right: 0; top: 24px; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { content: 'c'; } } @@ -1373,12 +1568,12 @@ table { } } -// 15. Verification -// - - - - - - - - - - - - - - - - - - - - - - - - - +// 14. Verification +// ---------------- .verification { - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { .left + .left { margin-left: $line-height/2; } @@ -1390,7 +1585,7 @@ table { float: left; width: 30%; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { width: 25%; } } @@ -1399,7 +1594,7 @@ table { .progress { height: $line-height*3; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { height: $line-height*2; } @@ -1416,7 +1611,7 @@ table { text-transform: uppercase; vertical-align: top; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { line-height: $line-height*2; padding: 0; } @@ -1424,7 +1619,7 @@ table { .number { display: block; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { background-color: white; border-radius: rem-calc(30); color: #43AC6A; @@ -1514,7 +1709,7 @@ table { .icon-letter { background: white; - color: $info-color; + color: $color-info; font-size: rem-calc(24); margin-left: -27px; padding: 0 $line-height/2; @@ -1524,13 +1719,12 @@ table { } } -// 16. Comments -// - - - - - - - - - - - - - - - - - - - - - - - - - +// 15. Comments +// ------------ .comments { background: $white; background-repeat: repeat-x; - padding-top: $line-height; padding-bottom: $line-height*4; h2 { @@ -1554,7 +1748,6 @@ table { .comment-votes { color: $text-medium; - font-family: $font-sans; font-size: $small-font-size; line-height: $line-height; @@ -1565,17 +1758,29 @@ table { &:hover { color: $text-medium; + text-decoration: none; + + .icon-like { + color: $like; + } + + .icon-unlike { + color: $unlike; + } } } [class^="icon-"] { - font-size: rem-calc(20); - vertical-align: middle; + font-size: $base-font-size; + vertical-align: sub; } } .comment-body { - margin-left: rem-calc(42); + + img { + margin-right: $line-height/2; + } .reply { background: white; @@ -1583,12 +1788,28 @@ table { border-left: 0; border-right: 0; font-size: $small-font-size; - margin: rem-calc(6) 0; - padding: rem-calc(6); + margin: $line-height/4 0; + padding: $line-height/4; + position: relative; + + a.relative, [class^="icon-arrow"] { + padding-left: $line-height/2; + } + + [class^="icon-arrow"] { + font-size: $base-font-size; + left: -20px; + position: absolute; + top: -1px; + } .divider { color: $text-light; } + + form { + margin-top: $line-height/2; + } } .comment-user { @@ -1596,62 +1817,39 @@ table { padding: $line-height/4 0; overflow: hidden; - @each $n in ("1", "2", "3","4", "5") { - &.level-#{$n} { - @if $n == "5" { - background: $comment-level-5; - padding: $line-height/4 $line-height/2; - } - @elseif $n == "1" { - background: none; - padding: $line-height/4 $line-height/2; - } - @else { - background: $comment-official; - padding: $line-height/4 $line-height/2; - } - } - } - - &.is-author { - background: $comment-author; + &.level-1, &.level-2, &.level-3, &.level-4, &.level-5, + &.is-author, &.is-admin, &.is-moderator { + background: rgba(70,219,145,.3); padding: $line-height/4 $line-height/2; } - &.is-admin, &.is-moderator { - background: $comment-admin; - padding: $line-height/4 $line-height/2; + &.level-1 { + background: none; } &.level-5 { - background: $comment-level-5; - padding: $line-height/4 $line-height/2; + background: rgba(255,241,204,1); + } + + &.is-author, &.is-admin, &.is-moderator { + background: rgba(45,144,248,.15); } } } - .is-deleted { - background: $deleted; - margin-left: rem-calc(42); - padding: $line-height/4 $line-height/2; - } - .comment-children { border-left: 1px dashed $border; - margin-left: rem-calc(42); - padding-left: $line-height/4; - - @media only screen and (max-width: 40em) { - margin-left: rem-calc(16); - } + display: inline-block; + margin-left: rem-calc(16); + padding-left: rem-calc(8); + width: 100%; } .comment-info { color: $text-medium; - font-family: $font-sans; + display: inline-block; font-size: $small-font-size; - margin-top: $line-height/4; - vertical-align: middle; + line-height: rem-calc(32); // Same as avatar height span.user-name { color: $text; @@ -1669,7 +1867,7 @@ table { float: none; margin-top: 0; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { float: right; margin-top: rem-calc(-24); } @@ -1678,14 +1876,19 @@ table { padding-right: $line-height/2; float: none; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { float: right; } } } -// 17. Flags -// - - - - - - - - - - - - - - - - - - - - - - - - - +.comment-form { + display: inline-block; + width: 100%; +} + +// 16. Flags +// --------- .flag-content { @@ -1697,7 +1900,8 @@ table { } .flag-disable, .flag-active { - vertical-align: middle; + line-height: 0; + vertical-align: sub; } .flag-disable { @@ -1712,8 +1916,8 @@ table { color: $text-medium; } -// 18. Activity -// - - - - - - - - - - - - - - - - - - - - - - - - - +// 17. Activity +// ------------ .activity { margin-bottom: $line-height*2; @@ -1722,9 +1926,12 @@ table { border: 0; td { - padding-left: $line-height*1.5; position: relative; - word-break: break-all; + + &:first-child { + padding-left: $line-height*1.5; + width: 75%; + } &:before { color: $brand; @@ -1735,19 +1942,97 @@ table { } } - &.activity-comments td:before { + &.activity-comments td:first-child:before { content: "e"; top: 18px; } - &.activity-debates td:before { + &.activity-debates td:first-child:before { content: "i"; top: 14px; } - &.activity-proposals td:before { - content: "h"; - top: 18px; + &.activity-proposals { + + td:first-child:before { + content: "h"; + top: 18px; + } + + .retired { + text-decoration: line-through; + } + } + + &.activity-investment-projects td:first-child:before, &.activity-ballot td:first-child:before { + content: "\53"; + top: 10px; + } + } +} + +// 18. Banners +// ----------- + +.banner-style-one { + background-color: $brand; +} + +.banner-style-two { + background-color: $budget; +} + +.banner-style-three { + background-color: #33DADF; +} + +@include breakpoint(large) { + + .banner-img-one { + background-image: image-url('banners/banner1.png'); + } + + .banner-img-two { + background-image: image-url('banners/banner2.png'); + } + + .banner-img-three { + background-image: image-url('banners/banner3.png'); + } +} + +.banner-img-one, .banner-img-two, .banner-img-three { + background-position: bottom right; + background-repeat: no-repeat; +} + +.banner-style-one, .banner-style-two, .banner-style-three { + margin: 0; + margin-bottom: $line-height; + + h2, h3, a { + color: #eaeaf2; + } + + h2 { + padding: $line-height/2; + padding-bottom: 0; + } + + h3 { + padding: $line-height/2; + padding-top: 0; + } + + a:hover h2, a:hover h3 { + color: #eaeaf2 !important; + text-decoration: none; + } + + @include breakpoint(large) { + + h3 { + width: 80%; } } } diff --git a/app/assets/stylesheets/mixins.scss b/app/assets/stylesheets/mixins.scss index 4e99acaab..5ffc534cb 100644 --- a/app/assets/stylesheets/mixins.scss +++ b/app/assets/stylesheets/mixins.scss @@ -1,24 +1,19 @@ // Table of Contents // -// 01. Mixins +// 01. Logo // -// 01. Mixins -// - - - - - - - - - - - - - - - - - - - - - - - - - - -@mixin back { - clear: both; - color: $text-medium; - float: left; -} +// 01. Logo +// -------- @mixin logo { color: white; - font-family: $font-logo; - font-size: $h3-font-size; + display: inline-block; + font-family: 'Lato' !important; + font-size: rem-calc(24); font-weight: lighter; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { line-height: $line-height*2; margin-top: 0; } @@ -27,7 +22,7 @@ height: 48px; width: 48px; - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { height: 80px; margin-right: $line-height/2; margin-top: 0; @@ -35,59 +30,3 @@ } } } - -@mixin h1 { - font-size: $h2-font-size; - line-height: $h2-line-height; - - @media (min-width: $small-breakpoint) { - font-size: $h1-font-size; - line-height: $h1-line-height; - } -} - -@mixin h2 { - font-size: $h3-font-size; - line-height: $h3-line-height; - - @media (min-width: $small-breakpoint) { - font-size: $h2-font-size; - line-height: $h2-line-height; - } -} - -@mixin h3 { - font-size: $h4-font-size; - line-height: $h4-line-height; - - @media (min-width: $small-breakpoint) { - font-size: $h3-font-size; - line-height: $h3-line-height; - } -} - -@mixin h4 { - font-size: $h5-font-size; - line-height: $h5-line-height; - - @media (min-width: $small-breakpoint) { - font-size: $h4-font-size; - line-height: $h4-line-height; - } -} - -@mixin h5 { - font-size: $h6-font-size; - line-height: $h6-line-height; - - @media (min-width: $small-breakpoint) { - font-size: $h5-font-size; - line-height: $h5-line-height; - } -} - -@mixin h6 { - font-size: $h6-font-size; - line-height: $h6-line-height; - text-transform: uppercase; -} \ No newline at end of file diff --git a/app/assets/stylesheets/pages.scss b/app/assets/stylesheets/pages.scss new file mode 100644 index 000000000..f8bf6c7cd --- /dev/null +++ b/app/assets/stylesheets/pages.scss @@ -0,0 +1,111 @@ +// Table of Contents +// +// 01. Header +// 02. Navigation +// 03. Content +// 04. Sidebar +// + +// 01. Header +// ---------------------- + +.jumbo { + margin-bottom: $line-height; + margin-top: rem-calc(-24); + padding-bottom: $line-height; + padding-top: $line-height; + + &.light { + background: #ECF0F1; + } +} + +.lead { + font-size: rem-calc(24); +} + +// 03. Navigation +// ---------------------- + +.menu-pages { + list-style-type: none; + margin: 0; + + li { + display: block; + + @include breakpoint(medium) { + display: inline-block; + margin-right: $line-height/2; + } + } +} + +// 03. Content +// ---------------------- + +.more-info-content { + + h3 { + color: $brand; + } + + .additional-info { + margin-bottom: $line-height; + } + + a:not(.button) { + text-decoration: underline; + } + + figure { + margin: 0; + text-align: center; + + figcaption { + font-size: $small-font-size; + font-style: italic; + } + } + + ul.features { + list-style-type: circle; + margin-left: $line-height; + + @include breakpoint(medium) { + margin: $line-height 0 $line-height $line-height*2.5; + } + + li { + margin-bottom: $line-height + } + } + + .section-content { + border-top: 1px solid $medium-gray; + padding-bottom: $line-height*2; + padding-top: $line-height*2; + + &:first-child { + border-top: 0; + padding-top: 0; + } + } +} + +// 04. Sidebar +// ---------------------- + +.more-info-sidebar { + + .sidebar-card { + border: 1px solid $border; + margin-bottom: $line-height; + padding: $line-height/2; + + &.light { + background: #ECF0F1; + border: 0; + } + } +} diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 10cd6e65d..0c3ff069a 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -1,26 +1,34 @@ -// Table of Contents - Styles for debates and proposals +// Table of Contents // // 01. Votes and supports // 02. New participation // 03. Show participation // 04. List participation // 05. Featured +// 06. Budget +// 07. Proposals successful +// 08. Polls // // 01. Votes and supports -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ---------------------- @mixin votes { - background: $votes-bg; - border-top: 1px solid $votes-border; - font-family: $font-sans; - margin: 0 rem-calc(-12); - padding: rem-calc(14) rem-calc(12); + border-top: 1px solid $border; + margin-top: $line-height; + padding: $line-height 0; position: relative; - .icon-like { + @include breakpoint(medium) { + border-left: 1px solid $border; + border-top: 0; + margin-top: 0; + padding-top: $line-height*2; + } + + .icon-like, .icon-unlike { background: white; - border: 2px solid $votes-border; + border: 2px solid $text-light; border-radius: rem-calc(3); color: $text-light; display: inline-block; @@ -29,190 +37,150 @@ padding: rem-calc(3) rem-calc(6); position: relative; - &:hover { - background: $votes-like; - border-color: white; + &:hover, &:active { color: white; cursor: pointer; opacity: 1 !important; } + } - &:active { - border-color: $votes-like-act; + .icon-like { + + &:hover, &:active { + background: $like; + border: 2px solid $like; } } .icon-unlike { - background: white; - border: 2px solid $votes-border; - border-radius: rem-calc(3); - color: $text-light; - display: inline-block; - font-size: rem-calc(30); - line-height: rem-calc(30); - padding: rem-calc(3) rem-calc(6); - position: relative; - &:hover { - background: $votes-unlike; - border-color: white; - color: white; - cursor: pointer; - opacity: 1 !important; - } - - &:active { - border-color: $votes-unlike-act; + &:hover, &:active { + background: $unlike; + border: 2px solid $unlike; } } .like, .unlike { line-height: rem-calc(48); vertical-align: super; + text-decoration: none; span.percentage { - color: white; + color: $text; display: inline-block; - font-size: rem-calc(16); + font-size: $small-font-size; line-height: $line-height*2; - padding-left: rem-calc(8); + padding-right: $line-height/2; vertical-align: top; + + @include breakpoint(medium) { + display: block; + line-height: $line-height; + padding-right: 0; + } } } .voted { - .icon-like { - background: $votes-like; - border-color: white; + + .icon-like, .icon-unlike { color: white; } + .icon-like { + background: $like; + border: 2px solid $like; + } + .icon-unlike { - background: $votes-unlike; - border-color: white; - color: white; + background: $unlike; + border: 2px solid $unlike; } } .no-voted { + .icon-like, .icon-unlike { - opacity: .5; + opacity: .3; } } .total-votes { - color: white; + font-weight: bold; float: right; line-height: $line-height*2; + + @include breakpoint(medium) { + display: block; + float: none; + } } .divider { margin: 0 rem-calc(6); } - - .not-logged { - background: rgba(22,99,135,.9); - color: white; - font-size: $small-font-size; - height: 100%; - left: 0; - position: absolute; - text-align: center; - top: 0; - width: 100%; - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#222222', endColorstr='#222222'); /* IE */ - - a { - color: white; - text-decoration: underline; - } - } - - .anonymous-votes, .organizations-votes { - background: $warning-bg; - color: $warning-color; - height: 100%; - left: 0; - line-height: $line-height; - padding-top: rem-calc(12); - position: absolute; - text-align: center; - top: 0; - width: 100%; - - p { - color: $warning-color; - margin: 0 rem-calc(12); - text-align: left; - } - - a { - color: $warning-color; - font-weight: bold; - text-decoration: underline; - } - } } @mixin supports { - background: $proposals; - border-top: 1px solid $proposals-border; - font-family: $font-sans; - margin: 0 rem-calc(-12); - padding: rem-calc(14) rem-calc(12); + padding: $line-height 0; position: relative; .progress { - background-color: rgba(255,255,255,.8); - height: rem-calc(12); + background: lighten($proposals, 35%); + border: 1px solid lighten($proposals, 35%); + height: rem-calc(14); + position: relative; .meter { - background: $votes-like; + background: $proposals; + border-radius: 0; + border-bottom-right-radius: rem-calc(3); + border-top-right-radius: rem-calc(3); + display: block; + height: $line-height/2; } } .percentage { - font-size: rem-calc(10); - color: $brand; - vertical-align: top; + color: $text; + font-size: $tiny-font-size; position: absolute; - top: 13px; - right: 20px; + right: 6px; + top: -2px; + vertical-align: top; } abbr { - color: white; + color: $text-medium; &[title] { - border-bottom: 1px dotted white; + border-bottom: 1px dotted $text-medium; } } .button-support { - background: white; - color: $proposals; + background: $proposals; + color: $text; display: inline-block; font-size: $small-font-size; margin-top: rem-calc(12); - &:hover { - background: $proposals-border; - color: white; + &:hover, &:active { + background: lighten($proposals, 25%); cursor: pointer; } - - &:active { - opacity: .75; - } } .total-supports { - color: white; + color: $text; + display: block; + font-weight: bold; text-align: center; - font-size: $small-font-size; span { display: block; + font-size: $small-font-size; + font-weight: normal; } } @@ -220,85 +188,77 @@ margin: 0 rem-calc(6); } - .not-logged { - background: rgba(255,164,45,.9); - color: white; - font-size: $small-font-size; - height: 100%; - left: 0; - position: absolute; - text-align: center; - top: 0; - width: 100%; - filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#222222', endColorstr='#222222'); /* IE */ - - a { - color: white; - text-decoration: underline; - } - } - - .anonymous-votes, .organizations-votes { - background: $warning-bg; - color: $warning-color; - height: 100%; - left: 0; - line-height: $line-height; - padding-top: rem-calc(12); - position: absolute; - text-align: center; - top: 0; - width: 100%; - - p { - color: $warning-color; - margin: 0 rem-calc(12); - text-align: left; - } - - a { - color: $warning-color; - font-weight: bold; - text-decoration: underline; - } - } - .supported { - color: white; + color: $text; margin-top: rem-calc(12); } } +.supports-container { + border-top: 1px solid $border; + + @include breakpoint(medium) { + border-left: 1px solid $border; + border-top: 0; + } +} + +.participation-not-allowed { + background: $warning-bg; + color: $color-warning; + height: 100%; + left: 0; + line-height: $line-height; + padding: $line-height $line-height/2; + position: absolute; + text-align: center; + top: 0; + width: 100%; + z-index: 2; + + p { + color: $color-warning !important; + margin: 0 rem-calc(12); + text-align: left; + } + + a { + color: $color-warning !important; + font-weight: bold; + text-decoration: underline; + } +} + +.reply .participation-not-allowed { + padding-right: $line-height/2; + padding-top: $line-height/6; + text-align: right; +} + // 02. New participation -// - - - - - - - - - - - - - - - - - - - - - - - - - +// --------------------- -.debate-new, .debate-edit, -.proposal-new, .proposal-edit, -.spending-proposal-new, .spending-proposal-edit { +.debate-form, +.proposal-form, +.budget-investment-form, +.spending-proposal-form { - .back { - @include back; + .icon-debates, .icon-proposals, .icon-budget { + font-size: rem-calc(50); + line-height: $line-height; + opacity: .5; } .icon-debates { color: $debates; - font-size: rem-calc(60); - line-height: $line-height; - opacity: .5; } .icon-proposals { color: $proposals; - font-size: rem-calc(50); - line-height: $line-height; - opacity: .5; } .icon-budget { color: $budget; - font-size: rem-calc(50); - line-height: $line-height; - opacity: .5; } .recommendations { @@ -318,14 +278,14 @@ } } -.debate-new, .debate-edit { +.debate-form { .recommendations li:before { color: $debates; } } -.proposal-new, .proposal-edit { +.proposal-form { .recommendations li:before { color: $proposals; @@ -333,9 +293,21 @@ } // 03. Show participation -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ---------------------- -.debate-show, .proposal-show, .investment-project-show { +.debate-show, +.proposal-show, +.investment-project-show, +.budget-investment-show, +.polls-show { + + p { + word-wrap: break-word; + } + + .callout.proposal-retired { + font-size: $base-font-size; + } .social-share-full .social-share-button { display:inline; @@ -354,13 +326,17 @@ margin-bottom: 0; } - .debate-info, .proposal-info, .investment-project-info { + .debate-info, .proposal-info, .investment-project-info, .budget-investment-show { clear: both; color: $text-medium; font-size: $small-font-size; - min-height: $line-height*2; + margin-bottom: $line-height/2; position: relative; + span:not(.label) { + line-height: rem-calc(32); // Same as avatar height + } + a { color: $text-medium; } @@ -370,15 +346,6 @@ line-height: $line-height; margin: 0; } - - .author-deleted { - left: 0; - top: 4px; - } - - .author.deleted { - margin-left: rem-calc(48); - } } .debate-description, .proposal-description { @@ -393,12 +360,19 @@ font-size: rem-calc(15); margin-bottom: rem-calc(15); } + + &.tags, &.geozone { + + li { + margin-bottom: 0; + } + } } .author-photo { - line-height: $line-height*2; + line-height: rem-calc(32); margin-right: rem-calc(6); - vertical-align: middle; + vertical-align: top; width: 32px; } @@ -416,7 +390,7 @@ aside { - h3 { + h2 { border-top: 2px solid $brand; display: inline-block; font-size: rem-calc(16); @@ -427,6 +401,7 @@ } blockquote { + color: #4C4C4C; margin-top: rem-calc(12); padding-top: 0; font-size: rem-calc(15); @@ -464,57 +439,8 @@ } } - .votes { - @include votes; - border: 0; - border-radius: 0; - margin: 0; - - .total-votes { - display: block; - float: none; - line-height: $line-height; - } - - .not-logged { - line-height: $line-height; - padding: rem-calc(24); - } - - @media (min-width: $small-breakpoint + rem-calc(1)) and (max-width:$medium-breakpoint) { - .in-favor, .against { - text-align: left; - width: rem-calc(100); - } - } - - .divider { - display: none; - } - - @media (min-width: $medium-breakpoint) { - .divider { - display: inline-block; - } - } - } - .supports { @include supports; - border: 0; - border-radius: 0; - margin: 0; - - .total-supports { - display: block; - float: none; - line-height: $line-height; - } - - .not-logged { - line-height: $line-height; - padding: rem-calc(24); - } } .leave-comment { @@ -532,22 +458,43 @@ } .bullet { - color: $border; + color: $text; +} + +.investment-project-show p, .budget-investment-show p { + word-break: break-word; +} + +.proposal-show, .investment-project-show, .budget-investment-show { + + .supports { + padding: $line-height/2 0 0; + } + + .share-supported { + display: none; + } } // 04. List participation -// - - - - - - - - - - - - - - - - - - - - - - - - - +// ---------------------- -.debates-list, .proposals-list { +.debates-list, .proposals-list, .investment-projects-list, .budget-investments-list { - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { margin-bottom: rem-calc(48); } } -.debate, .proposal, .investment-project { - margin-bottom: 0; - margin-top: 0; +.investment-projects-list, .budget-investments-list { + + @include breakpoint(medium) { + min-height: $line-height*15; + } +} + +.debate, .proposal, .investment-project, .budget-investment { + margin: $line-height/4 0; .panel { background: white; @@ -559,16 +506,15 @@ min-height: rem-calc(192); padding: rem-calc(12) rem-calc(12) 0 rem-calc(12); - @media (min-width: $small-breakpoint) { + @include breakpoint(medium) { margin-bottom: rem-calc(-1); padding-bottom: rem-calc(12); } - .label-debate, .label-proposal, .label-investment-project { + .label-debate, .label-proposal, .label-investment-project, .label-budget-investment { background: none; clear: both; display: block; - font-family: $font-sans; font-size: rem-calc(12); font-weight: bold; line-height: $line-height; @@ -583,13 +529,17 @@ } .label-proposal { - color: $proposals; + color: $proposals-dark; } .label-investment-project { color: $budget; } + .label-budget-investment { + color: $budget; + } + h3 { font-weight: bold; margin: 0; @@ -599,7 +549,7 @@ } } - .debate-content, .proposal-content, .investment-project-content { + .debate-content, .proposal-content, .investment-project-content, .budget-investment-content { margin: 0; min-height: rem-calc(180); position: relative; @@ -621,7 +571,7 @@ } .icon-proposals { - color: $proposals; + color: $proposals-dark; } .icon-budget { @@ -629,7 +579,7 @@ font-size: $small-font-size; } - .debate-info, .proposal-info, .investment-project-info { + .debate-info, .proposal-info, .investment-project-info, .budget-investment-info { color: $text-medium; font-size: $small-font-size; margin: rem-calc(6) 0 0; @@ -644,7 +594,7 @@ } } - .debate-description, .proposal-description, .investment-project-description { + .debate-description, .proposal-description, .investment-project-description, .budget-investment-description { color: $text; font-size: rem-calc(13); height: rem-calc(72); @@ -681,143 +631,66 @@ } } - .not-logged { - line-height: $line-height; - padding-top: rem-calc(24); - } - - .anonymous-votes, .organizations-votes { - padding-top: rem-calc(24); - } - .divider { display: none; } - @media (min-width: $medium-breakpoint) { + @include breakpoint(medium) { .divider { display: inline-block; } } } -.debate { +.more-info { + clear: both; + color: $text-medium; + font-size: $small-font-size; + + a { + color: $text-medium; + } +} + +.debate, .debate-show { .votes { @include votes; - border: 1px solid $votes-border; - margin: 0 rem-calc(-12); - @media (min-width: $small-breakpoint) { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - margin: 0 rem-calc(-25) 0 rem-calc(12); + .against { + margin-left: $line-height/4; } - &:after { - content: none; - position: absolute; - display: block; - border-style: solid; - border-color: #166387 transparent transparent transparent; - bottom: -14px; - border-left-width: 0; - border-right-color: transparent; - right: -1px; - border-width: 13px 13px 0 0; - - @media (min-width: $small-breakpoint) { - content: ""; - } - } - - .total-votes { - display: inline-block; - line-height: $line-height; - margin-left: rem-calc(24); - padding-top: rem-calc(12); - vertical-align: top; - - @media (min-width: $small-breakpoint) { - display: block; - float: none; - line-height: $line-height*2; - margin-left: 0; - padding-top: 0; - } - } - - @media (min-width: $small-breakpoint) { - .like, .unlike { - - span.percentage { - display: block; - line-height: $line-height/2; - } - } + @include breakpoint(medium) { + text-align: center; } } } +.debate-show .votes { + border: 0; + padding: $line-height/2 0; +} + .proposal { .supports { @include supports; - border: 1px solid $proposals-border; - margin: 0 rem-calc(-12); - - @media (min-width: $small-breakpoint) { - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; - margin: 0 rem-calc(-25) 0 rem-calc(12); - } - - &:after { - content: none; - position: absolute; - display: block; - border-style: solid; - border-color: #664212 transparent transparent transparent; - bottom: -14px; - border-left-width: 0; - border-right-color: transparent; - right: -1px; - border-width: 13px 13px 0 0; - - @media (min-width: $small-breakpoint) { - content: ""; - } - } - - .total-supports { - display: inline-block; - line-height: $line-height; - padding-top: rem-calc(12); - vertical-align: top; - - @media (min-width: $small-breakpoint) { - display: block; - float: none; - margin-left: 0; - padding-top: 0; - } - } } } -.investment-project, .investment-project-show { +.investment-project, .investment-project-show, +.budget-investment, .budget-investment-show { .supports { @include supports; - background: none; - border: 0; - border-left: 1px solid $border; - margin: 0 rem-calc(-12); - min-height: rem-calc(180); - padding-top: $line-height*2; - &:after { - content: none; + .investment-project-amount, + .budget-investment-amount { + color: $budget; + font-size: rem-calc(20); + font-weight: bold; + margin-bottom: 0; } .button-support { @@ -841,26 +714,17 @@ font-weight: bold; } - .supported { + .remove .icon-check-circle { color: $budget; - } - - .not-logged, - .organizations-votes, - .anonymous-votes { - background: rgba(69,67,114,.96); - color: white; - padding: rem-calc(12); - } - - .anonymous-votes p, .anonymous-votes a, - .organizations-votes p { - color: white; + display: block; + font-size: rem-calc(70); + line-height: rem-calc(70); } } } -.investment-project-show .supports { +.investment-project-show .supports, +.budget-investment-show .supports { border: 0; } @@ -871,13 +735,47 @@ } } -// 05. Featured -// - - - - - - - - - - - - - - - - - - - - - - - - - +.investment-project .supports .total-supports.no-button, +.investment-project-show .supports .total-supports.no-button, +.budget-investment .supports .total-supports.no-button, +.budget-investment-show .supports .total-supports.no-button { + display: block; + margin-top: $line-height*1.5; +} -.featured-debates, .featured-proposals { +.budget-investment-show { + + .label-budget-investment { + background: none; + clear: both; + color: $budget; + display: block; + font-size: rem-calc(12); + font-weight: bold; + line-height: $line-height; + padding-bottom: 0; + padding-left: 0; + padding-top: 0; + text-transform: uppercase; + } + + .icon-budget { + color: $budget; + font-size: $small-font-size; + line-height: $line-height; + margin-left: rem-calc(6); + top: 0; + } +} + +// 05. Featured +// ------------ + +.featured-debates, .featured-proposals, +.enquiries-list { padding: $line-height/2 0; - @media (min-width: $medium-breakpoint) { + @include breakpoint(medium) { margin-left: 0 !important; margin-right: 0 !important; } @@ -893,7 +791,7 @@ a { color: $text; - font-size: $h3-font-size; + font-size: rem-calc(24); } } @@ -931,25 +829,27 @@ } .button-support { + background: $text; + color: $featured; margin-top: 0; + + &:hover { + background: white; + color: $text; + } } - .not-logged, - .organizations-votes, - .anonymous-votes { + .participation-not-allowed { background: $featured; - color: $warning-color; font-size: $small-font-size; - line-height: $line-height; padding-top: 0; a { - color: $warning-color; - font-weight: bold; + color: $color-warning; } p { - color: $warning-color; + color: $color-warning; font-size: $small-font-size; line-height: $line-height; } @@ -957,14 +857,18 @@ .supported { margin-top: 0; + font-size: $small-font-size; } .share-supported { - .social-share-button-twitter, - .social-share-button-facebook, - .social-share-button-google_plus { - height: rem-calc(33); + .ssb-twitter, + .ssb-facebook, + .ssb-google_plus { + background: none; + color: $text; + height: rem-calc(33) !important; + &:before { font-size: rem-calc(18); @@ -974,3 +878,634 @@ } } } + +// 06. Budget +// ---------- + +.expanded.budget { + background: $budget; + + h1, h2, p, a.back, .icon-angle-left { + color: white; + } + + .button { + background: white; + color: $budget; + } + + .info { + background: #6A2A72; + + p { + margin-bottom: 0; + text-transform: uppercase; + } + + @include breakpoint(medium) { + border-top: rem-calc(6) solid #54225C; + } + } +} + +.jumbo-budget { + background: $budget; + border-bottom: 1px solid $budget; + + &.budget-heading { + min-height: $line-height*10; + } + + h1 { + margin-bottom: 0; + } + + h1, h2, .back, .icon-angle-left, p, a { + color: white; + } + + .callout.warning { + font-size: $base-font-size; + + a { + color: $color-warning; + } + } + + &.welcome { + background: $budget image-url('spending_proposals_bg.jpg'); + background-position: 50% 50%; + background-repeat: no-repeat; + background-size: cover; + + .spending-proposal-timeline { + padding-top: $line-height; + + ul li { + margin-right: $line-height; + padding-top: $line-height/2; + + .icon-calendar { + display: none; + } + } + } + } + + a { + text-decoration: underline; + + &.button { + background: white; + color: $brand; + margin-bottom: rem-calc(3); + text-decoration: none; + } + } + + .social-share-button a { + color: white; + + &.social-share-button-twitter:hover { + color: #40A2D1; + } + + &.social-share-button-facebook:hover { + color: #354F88; + } + + &.social-share-button-google_plus:hover { + color: #CE3E26; + } + + &.social-share-button-telegram:hover { + color: #CE3E26; + } + } +} + +.progress-votes { + position: relative; + + .progress { + background: #212033; + clear: both; + } + + .progress-meter { + background: #fdcb10; + border-radius: 0; + -webkit-transition: width 2s; + transition: width 2s; + } + + .spent-amount-progress, + .spent-amount-meter { + background: none !important; + } + + .spent-amount-text { + color: white; + font-size: $base-font-size; + font-weight: normal; + position: absolute; + right: 0; + text-align: right; + top: 16px; + width: 100%; + + &:before { + color: #a5a1ff; + content: "\57"; + font-family: 'icons'; + font-size: $small-font-size; + position: absolute; + right: -6px; + top: -17px; + } + } + + .total-amount { + color: white; + font-size: rem-calc(18); + font-weight: bold; + float: right; + } + + .amount-available { + display: block; + text-align: right; + + span { + font-size: rem-calc(24); + font-weight: bold; + } + } +} + +.big-number { + color: $budget; + font-size: rem-calc(60); + line-height: rem-calc(120); + + @include breakpoint(large) { + font-size: rem-calc(90); + line-height: rem-calc(240); + } +} + +.ballot { + + h2, h3 { + font-weight: normal; + + span { + color: $budget; + font-weight: bold; + } + } + + .ballot-content { + border: 2px solid #F9F9F9; + border-radius: rem-calc(6); + padding: $line-height/2; + } + + .subtitle { + border-left: 2px solid $budget; + margin: $line-height/2 0; + padding-left: $line-height/2; + } + + .amount-spent { + background: $success-bg; + font-weight: normal; + padding: $line-height/2; + + span { + font-size: rem-calc(24); + font-weight: bold; + } + } +} + +ul.ballot-list { + list-style: none; + margin-left: 0; + + li { + background: #f9f9f9; + line-height: $line-height; + margin-bottom: $line-height/4; + padding: $line-height $line-height/2; + position: relative; + + a { + color: $text; + } + + span { + display: block; + font-style: italic; + } + + .remove-investment-project { + display: block; + height: 0; + + .icon-x { + color: #9f9f9f; + font-size: rem-calc(24); + line-height: $line-height/2; + position: absolute; + right: 6px; + text-decoration: none; + top: 6px; + + @include breakpoint(medium) { + font-size: $base-font-size; + } + } + } + + &:hover { + background: $budget; + color: white; + + a, span { + color: white; + outline: 0; + text-decoration: none; + } + + .remove-investment-project .icon-x { + color: white; + } + } + } +} + +.select-district a { + display: inline-block; + margin: $line-height/4 0; + padding: $line-height/4; +} + +.select-district .active a { + background: #f9f9f9; + border-radius: rem-calc(3); + color: $budget; + font-weight: bold; + + &:after { + content: "\56"; + font-family: "icons"; + font-size: $small-font-size; + font-weight: normal; + line-height: $line-height; + padding-left: rem-calc(3); + vertical-align: baseline; + + &:hover { + text-decoration: none; + } + } +} + +.progress-bar-nav { + padding: $line-height 0; + position: relative; + z-index: 3; + + @include breakpoint(medium) { + background-color: $budget; + -webkit-transition: height 0.3s; + -moz-transition: height 0.3s; + transition: height 0.3s; + + h1 { + -webkit-transition: font-size 0.3s; + -moz-transition: font-size 0.3s; + transition: font-size 0.3s; + } + + h2 { + margin-bottom: 0; + } + + &.is-fixed { + height: auto; + left: 0; + padding: $line-height; + position: fixed; + top: 0; + width: 100%; + + h1 { + font-size: rem-calc(24); + -webkit-transition: font-size 0.3s; + -moz-transition: font-size 0.3s; + transition: font-size 0.3s; + } + } + } +} + +// 07. Proposals successful +// ------------------------- + +.dark-heading { + background: #2D3E50; + color: white; + + @include breakpoint(medium) { + padding-bottom: $line-height; + } + + p { + + &.title { + color: #FFD200; + } + + &.title-date { + font-size: rem-calc(24); + font-weight: bold; + } + } + + .info { + background: #314253; + padding-top: $line-height; + + @include breakpoint(medium) { + border-top: rem-calc(6) solid #FFD200; + } + } +} + +.featured-proposals-ballot-banner, .sucessfull-proposals-banner { + background: #2D3E50 image-url("ballot_tiny.gif") no-repeat; + background-position: 75% 0; + position: relative; + + h2, a:hover h2 { + color: #FFD200 !important; + } + + p { + color: white; + } + + @include breakpoint(medium) { + margin-left: 0 !important; + margin-right: 0 !important; + } + + @include breakpoint(large) { + background: #2D3E50 image-url("ballot.gif") no-repeat; + background-position: 90% 0; + } +} + +.sucessfull-proposals-banner, +.successful .panel { + + .icon-successful { + border-right: 60px solid #FFD200; + border-top: 0; + border-bottom: 60px solid transparent; + height: 0; + position: absolute; + right: 0; + top: 0; + width: 0; + + &:after { + color: #1B254C; + content: "\59"; + font-family: "icons" !important; + left: 34px; + position: absolute; + top: 5px; + } + } +} + +.successful { + + .panel { + position: relative; + } + + .truncate { + display: none; + } + + .message { + @include supports; + background: none; + border-top: 0; + + @include breakpoint(medium) { + border-left: 1px solid $border; + margin: $line-height rem-calc(-25) 0 rem-calc(12); + } + } +} + +// 08. Polls +// ---------------------- + +.dark-heading { + background: #2D3E50; + color: white; + + .title { + color: #92BA48; + } + + .button { + background: white; + color: $brand; + } + + .callout { + + &.warning a { + color: $color-warning; + } + + &.primary a { + color: $color-info; + } + + &.alert a { + color: $color-alert; + } + } + + .info { + background: #314253; + padding: $line-height; + + @include breakpoint(medium) { + border-top: rem-calc(6) solid #92BA48; + } + } + + a:not(.button) { + color: white; + text-decoration: underline; + } + + .back, .icon-angle-left { + color: white; + } + + &.polls-show-header { + min-height: $line-height*8; + } +} + +.poll, .poll-question { + background: white; + border-radius: rem-calc(6); + margin-bottom: $line-height/2; +} + +.poll { + padding: $line-height; + position: relative; + + .icon-poll-answer { + border-top: 0; + border-bottom: 60px solid transparent; + height: 0; + position: absolute; + right: 0; + top: 0; + width: 0; + + &.can-answer:after, + &.cant-answer:after, + &.not-logged-in:after, + &.already-answer:after, + &.unverified:after { + font-family: "icons" !important; + left: 34px; + position: absolute; + top: 5px; + } + + &.can-answer { + border-right: 60px solid $info-bg; + + &:after { + color: $color-info; + content: "\6c"; + } + } + + &.cant-answer { + border-right: 60px solid $alert-bg; + + &:after { + color: $color-alert; + content: "\74"; + } + } + + &.not-logged-in { + border-right: 60px solid $info-bg; + + &:after { + color: $color-info; + content: "\6f"; + } + } + + &.unverified { + border-right: 60px solid $warning-bg; + + &:after { + color: $color-warning; + content: "\6f"; + } + } + + &.already-answer { + border-right: 60px solid $success-bg; + + &:after { + color: $color-success; + content: "\59"; + } + } + } + + .dates { + color: $text-medium; + font-size: $small-font-size; + margin-bottom: $line-height/2; + } + + h4 { + font-size: rem-calc(30); + line-height: $line-height*1.5; + + a { + color: $text; + } + } +} + +h2.questions-callout { + font-size: $base-font-size; +} + +h3.section-title-divider { + border-bottom: 2px solid $brand; + color: $brand; + margin-bottom: $line-height; +} + +.poll-question { + padding: 0 $line-height; + + h3 { + padding-top: $line-height; + + a { + color: $text; + } + } +} + +.poll-question-answers { + + .button { + margin-right: $line-height/4; + min-width: rem-calc(168); + + &.answered { + background: #F4F8EC; + border: 2px solid #92BA48; + color: $text; + position: relative; + + &:after { + background: #92BA48; + border-radius: rem-calc(20); + content: "\6c"; + color: white; + font-family: "icons" !important; + font-size: rem-calc(12); + padding: $line-height/4; + position: absolute; + right: -6px; + top: -6px; + } + } + } +} diff --git a/app/assets/stylesheets/print.css b/app/assets/stylesheets/print.css index 5297826c8..de5a56a31 100644 --- a/app/assets/stylesheets/print.css +++ b/app/assets/stylesheets/print.css @@ -12,6 +12,8 @@ #print_link { display: none !important; } +#responsive-menu { display: none !important; } + .admin-sidebar { display: none !important; } img.left { display: none !important; } @@ -24,8 +26,6 @@ img.left { display: none !important; } .icon-proposals { display: none !important; } -.captcha { display: none !important; } - li.name { display: none !important; } li.kind { display: none !important; } diff --git a/app/assets/stylesheets/variables.scss b/app/assets/stylesheets/variables.scss deleted file mode 100644 index 5d59ebf8f..000000000 --- a/app/assets/stylesheets/variables.scss +++ /dev/null @@ -1,136 +0,0 @@ -// Table of Contents -// -// 01. Typography -// 02. Colors -// 03. Forms -// 04. Alerts -// 05. Levels -// 06. Responsive -// - -// 01. Typography -// - - - - - - - - - - - - - - - - - - - - - - - - - - -$font-logo: 'Lato' !important; -$font-sans: 'Source Sans Pro', 'Helvetica', 'Arial', sans-serif !important; - -$font-normal: 400; -$font-bold: 700; - -$base-font-size: rem-calc(17); -$base-line-height: rem-calc(26); - -$h1-font-size: rem-calc(44); -$h1-line-height: rem-calc(57); - -$h2-font-size: rem-calc(34); -$h2-line-height: rem-calc(44); - -$h3-font-size: rem-calc(24); -$h3-line-height: rem-calc(31); - -$h4-font-size: rem-calc(19); -$h4-line-height: rem-calc(25); - -$h5-font-size: rem-calc(16); -$h5-line-height: rem-calc(21); - -$h6-font-size: rem-calc(13); -$h6-line-height: rem-calc(17); - -$small-font-size: rem-calc(14); -$tiny-font-size: rem-calc(10); -$line-height: rem-calc(24); - -// 02. Colors -// - - - - - - - - - - - - - - - - - - - - - - - - - - -$brand: #004A83; -$body: #E9E9E9; -$background: #EDEFF0; -$border: #DEE0E3; -$dark: darken($brand, 10%); - -$text: #222222; -$text-medium: #999999; -$text-light: #CCCCCC; - -$link: #2895F1; -$link-hover: #2178BF; - -$debates: #008CCF; -$votes-bg: #26AEEE; -$votes-border: #1F94CB; - -$votes-like: #7BD2A8; -$votes-like-act: #5D9E7F; -$votes-unlike: #EF8585; -$votes-unlike-act: #BD6A6A; - -$delete: #F04124; -$check: #46DB91; - -$proposals: #FFA42D; -$proposals-border: #CC8425; - -$budget: #454372; -$budget-hover: #7571BF; - -$highlight: #E7F2FC; -$featured: #FED900; - -$footer-bg: #DEE0E2; -$footer-color: #171819; -$footer-link: #454A4C; -$footer-border: #BFC1C3; - -// 03. Forms -// - - - - - - - - - - - - - - - - - - - - - - - - - - -$input-bg: #F8F8F8; - -// 04. Alerts -// - - - - - - - - - - - - - - - - - - - - - - - - - - -$success-bg: #DFF0D8; -$success-border: #D6E9C6; -$success-color: #3C763D; - -$info-bg: #D9EDF7; -$info-border: #BCE8F1; -$info-color: #31708F; - -$warning-bg: #FCF8E3; -$warning-border: #FAEBCC; -$warning-color: #8A6D3B; - -$alert-bg: #F2DEDE; -$alert-border: #EBCCD1; -$alert-color: #A94442; - -// 05. Levels -// - - - - - - - - - - - - - - - - - - - - - - - - - - -$level-1: #1ABC9C; -$level-2: #43AC6A; -$level-3: #43AC6A; -$level-4: #43AC6A; -$level-5: #F08A24; - -$author: #008CCF; -$association: #C0392B; -$deleted: #E7E7E7; - -$comment-author: rgba(45,144,248,.15); -$comment-level-5: rgba(255,241,204,1); -$comment-admin: rgba(45,144,248,.15); -$comment-official: rgba(70,219,145,.3); - -// 06. Responsive -// - - - - - - - - - - - - - - - - - - - - - - - - - - -$small: rem-calc(480); -$small-breakpoint: rem-calc(640); -$medium-breakpoint: rem-calc(1024); -$large-breakpoint: rem-calc(1440); -$xlarge-breakpoint: rem-calc(1920); diff --git a/app/controllers/account_controller.rb b/app/controllers/account_controller.rb index 9a0f89bae..c46b6e42f 100644 --- a/app/controllers/account_controller.rb +++ b/app/controllers/account_controller.rb @@ -25,8 +25,8 @@ class AccountController < ApplicationController if @account.organization? params.require(:account).permit(:phone_number, :email_on_comment, :email_on_comment_reply, :newsletter, organization_attributes: [:name, :responsible_name]) else - params.require(:account).permit(:username, :public_activity, :email_on_comment, :email_on_comment_reply, :newsletter) + params.require(:account).permit(:username, :public_activity, :email_on_comment, :email_on_comment_reply, :email_on_direct_message, :email_digest, :newsletter, :official_position_badge) end end -end +end \ No newline at end of file diff --git a/app/controllers/admin/api/stats_controller.rb b/app/controllers/admin/api/stats_controller.rb index e58d9d754..fe8c72cdd 100644 --- a/app/controllers/admin/api/stats_controller.rb +++ b/app/controllers/admin/api/stats_controller.rb @@ -1,9 +1,10 @@ class Admin::Api::StatsController < Admin::Api::BaseController def show - unless params[:events].present? || - params[:visits].present? || - params[:spending_proposals].present? + unless params[:events].present? || + params[:visits].present? || + params[:spending_proposals].present? || + params[:budget_investments].present? return render json: {}, status: :bad_request end @@ -24,6 +25,10 @@ class Admin::Api::StatsController < Admin::Api::BaseController ds.add "Spending proposals", SpendingProposal.group_by_day(:created_at).count end + if params[:budget_investments].present? + ds.add "Budget Investments", Budget::Investment.group_by_day(:created_at).count + end + render json: ds.build end end diff --git a/app/controllers/admin/banners_controller.rb b/app/controllers/admin/banners_controller.rb new file mode 100644 index 000000000..b919e0861 --- /dev/null +++ b/app/controllers/admin/banners_controller.rb @@ -0,0 +1,52 @@ +class Admin::BannersController < Admin::BaseController + + has_filters %w{all with_active with_inactive}, only: :index + + before_action :banner_styles, only: [:edit, :new, :create, :update] + before_action :banner_imgs, only: [:edit, :new, :create, :update] + + respond_to :html, :js + + load_and_authorize_resource + + def index + @banners = Banner.send(@current_filter).page(params[:page]) + end + + def create + @banner = Banner.new(banner_params) + if @banner.save + redirect_to admin_banners_path + else + render :new + end + end + + def update + if @banner.update(banner_params) + redirect_to admin_banners_path + else + render :edit + end + end + + def destroy + @banner.destroy + redirect_to admin_banners_path + end + + private + + def banner_params + params.require(:banner).permit(:title, :description, :target_url, :style, :image, :post_started_at, :post_ended_at) + end + + def banner_styles + @banner_styles = Setting.all.banner_style.map { |banner_style| [banner_style.value, banner_style.key.split('.')[1]] } + end + + def banner_imgs + @banner_imgs = Setting.all.banner_img.map { |banner_img| [banner_img.value, banner_img.key.split('.')[1]] } + end + +end \ No newline at end of file diff --git a/app/controllers/admin/budget_groups_controller.rb b/app/controllers/admin/budget_groups_controller.rb new file mode 100644 index 000000000..9c5a54b98 --- /dev/null +++ b/app/controllers/admin/budget_groups_controller.rb @@ -0,0 +1,17 @@ +class Admin::BudgetGroupsController < Admin::BaseController + include FeatureFlags + feature_flag :budgets + + def create + @budget = Budget.find params[:budget_id] + @budget.groups.create(budget_group_params) + @groups = @budget.groups.includes(:headings) + end + + private + + def budget_group_params + params.require(:budget_group).permit(:name) + end + +end \ No newline at end of file diff --git a/app/controllers/admin/budget_headings_controller.rb b/app/controllers/admin/budget_headings_controller.rb new file mode 100644 index 000000000..56903b744 --- /dev/null +++ b/app/controllers/admin/budget_headings_controller.rb @@ -0,0 +1,18 @@ +class Admin::BudgetHeadingsController < Admin::BaseController + include FeatureFlags + feature_flag :budgets + + def create + @budget = Budget.find params[:budget_id] + @budget_group = @budget.groups.find params[:budget_group_id] + @budget_group.headings.create(budget_heading_params) + @headings = @budget_group.headings + end + + private + + def budget_heading_params + params.require(:budget_heading).permit(:name, :price, :geozone_id) + end + +end \ No newline at end of file diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb new file mode 100644 index 000000000..b47fd4e49 --- /dev/null +++ b/app/controllers/admin/budget_investments_controller.rb @@ -0,0 +1,86 @@ +class Admin::BudgetInvestmentsController < Admin::BaseController + include FeatureFlags + feature_flag :budgets + + has_filters(%w{valuation_open without_admin managed valuating valuation_finished + valuation_finished_feasible selected all}, + only: [:index, :toggle_selection]) + + before_action :load_budget + before_action :load_investment, only: [:show, :edit, :update, :toggle_selection] + before_action :load_ballot, only: [:show, :index] + before_action :load_investments, only: [:index, :toggle_selection] + + def index + end + + def show + end + + def edit + load_admins + load_valuators + load_tags + end + + def update + set_valuation_tags + if @investment.update(budget_investment_params) + redirect_to admin_budget_budget_investment_path(@budget, @investment, Budget::Investment.filter_params(params)), + notice: t("flash.actions.update.budget_investment") + else + load_admins + load_valuators + load_tags + render :edit + end + end + + def toggle_selection + @investment.toggle :selected + @investment.save + end + + private + + def load_investments + @investments = Budget::Investment.scoped_filter(params, @current_filter) + .order(cached_votes_up: :desc, created_at: :desc) + .page(params[:page]) + end + + def budget_investment_params + params.require(:budget_investment) + .permit(:title, :description, :external_url, :heading_id, :administrator_id, :valuation_tag_list, valuator_ids: []) + end + + def load_budget + @budget = Budget.includes(:groups).find params[:budget_id] + end + + def load_investment + @investment = Budget::Investment.where(budget_id: @budget.id).find params[:id] + end + + def load_admins + @admins = Administrator.includes(:user).all + end + + def load_valuators + @valuators = Valuator.includes(:user).all.order("description ASC").order("users.email ASC") + end + + def load_tags + @tags = Budget::Investment.tags_on(:valuation).order(:name).uniq + end + + def load_ballot + query = Budget::Ballot.where(user: current_user, budget: @budget) + @ballot = @budget.balloting? ? query.first_or_create : query.first_or_initialize + end + + def set_valuation_tags + @investment.set_tag_list_on(:valuation, budget_investment_params[:valuation_tag_list]) + params[:budget_investment] = params[:budget_investment].except(:valuation_tag_list) + end +end diff --git a/app/controllers/admin/budgets_controller.rb b/app/controllers/admin/budgets_controller.rb new file mode 100644 index 000000000..0b373c0bd --- /dev/null +++ b/app/controllers/admin/budgets_controller.rb @@ -0,0 +1,48 @@ +class Admin::BudgetsController < Admin::BaseController + include FeatureFlags + feature_flag :budgets + + has_filters %w{current finished}, only: :index + + load_and_authorize_resource + + def index + @budgets = Budget.send(@current_filter).order(created_at: :desc).page(params[:page]) + end + + def show + @budget = Budget.includes(groups: :headings).find(params[:id]) + end + + def new + end + + def edit + end + + def update + if @budget.update(budget_params) + redirect_to admin_budget_path(@budget), notice: t('admin.budgets.update.notice') + else + render :edit + end + end + + def create + @budget = Budget.new(budget_params) + if @budget.save + redirect_to admin_budget_path(@budget), notice: t('admin.budgets.create.notice') + else + render :new + end + end + + private + + def budget_params + descriptions = Budget::PHASES.map{|p| "description_#{p}"}.map(&:to_sym) + valid_attributes = [:name, :phase, :currency_symbol] + descriptions + params.require(:budget).permit(*valid_attributes) + end + +end diff --git a/app/controllers/admin/comments_controller.rb b/app/controllers/admin/comments_controller.rb index a0272f367..1f3d8e837 100644 --- a/app/controllers/admin/comments_controller.rb +++ b/app/controllers/admin/comments_controller.rb @@ -20,8 +20,9 @@ class Admin::CommentsController < Admin::BaseController end private + def load_comment @comment = Comment.with_hidden.find(params[:id]) end -end +end \ No newline at end of file diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index f7aa5c440..5fb5e3c13 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -3,4 +3,4 @@ class Admin::DashboardController < Admin::BaseController def index end -end +end \ No newline at end of file diff --git a/app/controllers/admin/debates_controller.rb b/app/controllers/admin/debates_controller.rb index 0b0b31b8b..bbe4a06d7 100644 --- a/app/controllers/admin/debates_controller.rb +++ b/app/controllers/admin/debates_controller.rb @@ -29,4 +29,4 @@ class Admin::DebatesController < Admin::BaseController @debate = Debate.with_hidden.find(params[:id]) end -end +end \ No newline at end of file diff --git a/app/controllers/admin/geozones_controller.rb b/app/controllers/admin/geozones_controller.rb new file mode 100644 index 000000000..b73c13422 --- /dev/null +++ b/app/controllers/admin/geozones_controller.rb @@ -0,0 +1,49 @@ +class Admin::GeozonesController < Admin::BaseController + + respond_to :html + + load_and_authorize_resource + + def index + @geozones = Geozone.all.order("LOWER(name)") + end + + def new + end + + def edit + end + + def create + @geozone = Geozone.new(geozone_params) + + if @geozone.save + redirect_to admin_geozones_path + else + render :new + end + end + + def update + if @geozone.update(geozone_params) + redirect_to admin_geozones_path + else + render :edit + end + end + + def destroy + if @geozone.safe_to_destroy? + @geozone.destroy + redirect_to admin_geozones_path, notice: t('admin.geozones.delete.success') + else + redirect_to admin_geozones_path, flash: { error: t('admin.geozones.delete.error') } + end + end + + private + + def geozone_params + params.require(:geozone).permit(:name, :external_code, :census_code, :html_map_coordinates) + end +end diff --git a/app/controllers/admin/managers_controller.rb b/app/controllers/admin/managers_controller.rb new file mode 100644 index 000000000..5023a1380 --- /dev/null +++ b/app/controllers/admin/managers_controller.rb @@ -0,0 +1,32 @@ +class Admin::ManagersController < Admin::BaseController + load_and_authorize_resource + + def index + @managers = @managers.page(params[:page]) + end + + def search + @user = User.find_by(email: params[:email]) + + respond_to do |format| + if @user + @manager = Manager.find_or_initialize_by(user: @user) + format.js + else + format.js { render "user_not_found" } + end + end + end + + def create + @manager.user_id = params[:user_id] + @manager.save + + redirect_to admin_managers_path + end + + def destroy + @manager.destroy + redirect_to admin_managers_path + end +end diff --git a/app/controllers/admin/moderators_controller.rb b/app/controllers/admin/moderators_controller.rb index e15e9273f..2566c0c02 100644 --- a/app/controllers/admin/moderators_controller.rb +++ b/app/controllers/admin/moderators_controller.rb @@ -18,15 +18,15 @@ class Admin::ModeratorsController < Admin::BaseController end end - def destroy - @moderator.destroy - redirect_to admin_moderators_path - end - def create @moderator.user_id = params[:user_id] @moderator.save redirect_to admin_moderators_path end + + def destroy + @moderator.destroy + redirect_to admin_moderators_path + end end diff --git a/app/controllers/admin/officials_controller.rb b/app/controllers/admin/officials_controller.rb index 2d570c2dc..e0a0a86ca 100644 --- a/app/controllers/admin/officials_controller.rb +++ b/app/controllers/admin/officials_controller.rb @@ -25,8 +25,9 @@ class Admin::OfficialsController < Admin::BaseController end private + def user_params params.require(:user).permit(:official_position, :official_level) end -end +end \ No newline at end of file diff --git a/app/controllers/admin/organizations_controller.rb b/app/controllers/admin/organizations_controller.rb index 77f71ed65..b85460459 100644 --- a/app/controllers/admin/organizations_controller.rb +++ b/app/controllers/admin/organizations_controller.rb @@ -27,4 +27,4 @@ class Admin::OrganizationsController < Admin::BaseController redirect_to request.query_parameters.merge(action: :index) end -end +end \ No newline at end of file diff --git a/app/controllers/admin/poll/booth_assignments_controller.rb b/app/controllers/admin/poll/booth_assignments_controller.rb new file mode 100644 index 000000000..f81382bf8 --- /dev/null +++ b/app/controllers/admin/poll/booth_assignments_controller.rb @@ -0,0 +1,66 @@ +class Admin::Poll::BoothAssignmentsController < Admin::BaseController + + before_action :load_poll, except: [:create, :destroy] + + def index + @booth_assignments = @poll.booth_assignments.includes(:booth).order('poll_booths.name').page(params[:page]).per(50) + end + + def search_booths + load_search + @booths = ::Poll::Booth.search(@search) + respond_to do |format| + format.js + end + end + + def show + @booth_assignment = @poll.booth_assignments.includes(:recounts, :final_recounts, :voters, officer_assignments: [officer: [:user]]).find(params[:id]) + @voters_by_date = @booth_assignment.voters.group_by {|v| v.created_at.to_date} + end + + def create + @booth_assignment = ::Poll::BoothAssignment.new(poll_id: booth_assignment_params[:poll_id], booth_id: booth_assignment_params[:booth_id]) + + if @booth_assignment.save + notice = t("admin.poll_booth_assignments.flash.create") + else + notice = t("admin.poll_booth_assignments.flash.error_create") + end + redirect_to admin_poll_booth_assignments_path(@booth_assignment.poll_id), notice: notice + end + + def destroy + @booth_assignment = ::Poll::BoothAssignment.find(params[:id]) + + if @booth_assignment.destroy + notice = t("admin.poll_booth_assignments.flash.destroy") + else + notice = t("admin.poll_booth_assignments.flash.error_destroy") + end + redirect_to admin_poll_booth_assignments_path(@booth_assignment.poll_id), notice: notice + end + + private + + def load_booth_assignment + @booth_assignment = ::Poll::BoothAssignment.find(params[:id]) + end + + def booth_assignment_params + params.permit(:booth_id, :poll_id) + end + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/booths_controller.rb b/app/controllers/admin/poll/booths_controller.rb new file mode 100644 index 000000000..ff3700436 --- /dev/null +++ b/app/controllers/admin/poll/booths_controller.rb @@ -0,0 +1,39 @@ +class Admin::Poll::BoothsController < Admin::BaseController + load_and_authorize_resource class: 'Poll::Booth' + + def index + @booths = @booths.order(name: :asc).page(params[:page]) + end + + def show + end + + def new + end + + def create + if @booth.save + redirect_to admin_booths_path, notice: t("flash.actions.create.poll_booth") + else + render :new + end + end + + def edit + end + + def update + if @booth.update(booth_params) + redirect_to admin_booth_path(@booth), notice: t("flash.actions.update.poll_booth") + else + render :edit + end + end + + private + + def booth_params + params.require(:poll_booth).permit(:name, :location) + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/officer_assignments_controller.rb b/app/controllers/admin/poll/officer_assignments_controller.rb new file mode 100644 index 000000000..f37ef9ab8 --- /dev/null +++ b/app/controllers/admin/poll/officer_assignments_controller.rb @@ -0,0 +1,92 @@ +class Admin::Poll::OfficerAssignmentsController < Admin::BaseController + + before_action :load_poll + before_action :redirect_if_blank_required_params, only: [:by_officer] + before_action :load_booth_assignment, only: [:create] + + def index + @officers = ::Poll::Officer. + includes(:user). + order('users.username'). + where( + id: @poll.officer_assignments.select(:officer_id).distinct.map(&:officer_id) + ).page(params[:page]).per(50) + end + + def by_officer + @poll = ::Poll.includes(:booths).find(params[:poll_id]) + @officer = ::Poll::Officer.includes(:user).find(officer_assignment_params[:officer_id]) + @officer_assignments = ::Poll::OfficerAssignment. + joins(:booth_assignment). + includes(:recount, :final_recounts, booth_assignment: :booth). + where("officer_id = ? AND poll_booth_assignments.poll_id = ?", @officer.id, @poll.id). + order(:date) + end + + def search_officers + load_search + @officers = User.joins(:poll_officer).search(@search).order(username: :asc) + + respond_to do |format| + format.js + end + end + + def create + @officer_assignment = ::Poll::OfficerAssignment.new(booth_assignment: @booth_assignment, + officer_id: create_params[:officer_id], + date: create_params[:date]) + @officer_assignment.final = true if @officer_assignment.date > @booth_assignment.poll.ends_at.to_date + + if @officer_assignment.save + notice = t("admin.poll_officer_assignments.flash.create") + else + notice = t("admin.poll_officer_assignments.flash.error_create") + end + redirect_to by_officer_admin_poll_officer_assignments_path(poll_id: create_params[:poll_id], officer_id: create_params[:officer_id]), notice: notice + end + + def destroy + @officer_assignment = ::Poll::OfficerAssignment.includes(:booth_assignment).find(params[:id]) + + if @officer_assignment.destroy + notice = t("admin.poll_officer_assignments.flash.destroy") + else + notice = t("admin.poll_officer_assignments.flash.error_destroy") + end + redirect_to by_officer_admin_poll_officer_assignments_path(poll_id: @officer_assignment.poll_id, officer_id: @officer_assignment.officer_id), notice: notice + end + + private + + def officer_assignment_params + params.permit(:officer_id) + end + + def create_params + params.permit(:poll_id, :booth_id, :date, :officer_id) + end + + def load_booth_assignment + @booth_assignment = ::Poll::BoothAssignment.includes(:poll).find_by(poll_id: create_params[:poll_id], booth_id: create_params[:booth_id]) + end + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end + + def redirect_if_blank_required_params + if officer_assignment_params[:officer_id].blank? + redirect_to admin_poll_path(@poll) + end + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/officers_controller.rb b/app/controllers/admin/poll/officers_controller.rb new file mode 100644 index 000000000..2641a12b5 --- /dev/null +++ b/app/controllers/admin/poll/officers_controller.rb @@ -0,0 +1,39 @@ +class Admin::Poll::OfficersController < Admin::BaseController + load_and_authorize_resource :officer, class: "Poll::Officer", except: [:edit, :show] + + def index + @officers = @officers.page(params[:page]) + end + + def search + @user = User.find_by(email: params[:email]) + + respond_to do |format| + if @user + @officer = Poll::Officer.find_or_initialize_by(user: @user) + format.js + else + format.js { render "user_not_found" } + end + end + end + + def create + @officer.user_id = params[:user_id] + @officer.save + + redirect_to admin_officers_path + end + + def destroy + @officer.destroy + redirect_to admin_officers_path + end + + def show + end + + def edit + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/polls_controller.rb b/app/controllers/admin/poll/polls_controller.rb new file mode 100644 index 000000000..eda5736b0 --- /dev/null +++ b/app/controllers/admin/poll/polls_controller.rb @@ -0,0 +1,86 @@ +class Admin::Poll::PollsController < Admin::BaseController + load_and_authorize_resource + + before_action :load_search, only: [:search_booths, :search_questions, :search_officers] + before_action :load_geozones, only: [:new, :create, :edit, :update] + + def index + end + + def show + @poll = Poll.includes(:questions). + order('poll_questions.title'). + find(params[:id]) + end + + def new + end + + def create + if @poll.save + redirect_to [:admin, @poll], notice: t("flash.actions.create.poll") + else + render :new + end + end + + def edit + end + + def update + if @poll.update(poll_params) + redirect_to [:admin, @poll], notice: t("flash.actions.update.poll") + else + render :edit + end + end + + def add_question + question = ::Poll::Question.find(params[:question_id]) + + if question.present? + @poll.questions << question + notice = t("admin.polls.flash.question_added") + else + notice = t("admin.polls.flash.error_on_question_added") + end + redirect_to admin_poll_path(@poll), notice: notice + end + + def remove_question + question = ::Poll::Question.find(params[:question_id]) + + if @poll.questions.include? question + @poll.questions.delete(question) + notice = t("admin.polls.flash.question_removed") + else + notice = t("admin.polls.flash.error_on_question_removed") + end + redirect_to admin_poll_path(@poll), notice: notice + end + + def search_questions + @questions = ::Poll::Question.where("poll_id IS ? OR poll_id != ?", nil, @poll.id).search({search: @search}).order(title: :asc) + respond_to do |format| + format.js + end + end + + private + def load_geozones + @geozones = Geozone.all.order(:name) + end + + def poll_params + params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, geozone_ids: []) + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb new file mode 100644 index 000000000..ab7297a95 --- /dev/null +++ b/app/controllers/admin/poll/questions_controller.rb @@ -0,0 +1,64 @@ +class Admin::Poll::QuestionsController < Admin::BaseController + load_and_authorize_resource :poll + load_and_authorize_resource :question, class: 'Poll::Question' + + def index + @polls = Poll.all + @search = search_params[:search] + + @questions = @questions.search(search_params).page(params[:page]).order("created_at DESC") + + @proposals = Proposal.successful.sort_by_confidence_score + end + + def new + @polls = Poll.all + @question.valid_answers = I18n.t('poll_questions.default_valid_answers') + proposal = Proposal.find(params[:proposal_id]) if params[:proposal_id].present? + @question.copy_attributes_from_proposal(proposal) + end + + def create + @question.author = @question.proposal.try(:author) || current_user + + if @question.save + redirect_to admin_question_path(@question) + else + render :new + end + end + + def show + end + + def edit + end + + def update + if @question.update(question_params) + redirect_to admin_question_path(@question), notice: t("flash.actions.save_changes.notice") + else + render :edit + end + end + + def destroy + if @question.destroy + notice = "Question destroyed succesfully" + else + notice = t("flash.actions.destroy.error") + end + redirect_to admin_questions_path, notice: notice + end + + private + + def question_params + params.require(:poll_question).permit(:poll_id, :title, :question, :description, :proposal_id, :valid_answers) + end + + def search_params + params.permit(:poll_id, :search) + end + +end diff --git a/app/controllers/admin/poll/recounts_controller.rb b/app/controllers/admin/poll/recounts_controller.rb new file mode 100644 index 000000000..fec546d79 --- /dev/null +++ b/app/controllers/admin/poll/recounts_controller.rb @@ -0,0 +1,16 @@ +class Admin::Poll::RecountsController < Admin::BaseController + before_action :load_poll + + def index + @booth_assignments = @poll.booth_assignments. + includes(:booth, :recounts, :final_recounts, :voters). + order("poll_booths.name"). + page(params[:page]).per(50) + end + + private + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end +end \ No newline at end of file diff --git a/app/controllers/admin/poll/results_controller.rb b/app/controllers/admin/poll/results_controller.rb new file mode 100644 index 000000000..2c5bbba27 --- /dev/null +++ b/app/controllers/admin/poll/results_controller.rb @@ -0,0 +1,13 @@ +class Admin::Poll::ResultsController < Admin::BaseController + before_action :load_poll + + def index + @partial_results = @poll.partial_results + end + + private + + def load_poll + @poll = ::Poll.includes(:questions).find(params[:poll_id]) + end +end \ No newline at end of file diff --git a/app/controllers/admin/proposals_controller.rb b/app/controllers/admin/proposals_controller.rb index e7c4934fa..2a6dfd718 100644 --- a/app/controllers/admin/proposals_controller.rb +++ b/app/controllers/admin/proposals_controller.rb @@ -25,4 +25,4 @@ class Admin::ProposalsController < Admin::BaseController @proposal = Proposal.with_hidden.find(params[:id]) end -end +end \ No newline at end of file diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb index 387f38177..f17afff4c 100644 --- a/app/controllers/admin/settings_controller.rb +++ b/app/controllers/admin/settings_controller.rb @@ -1,9 +1,11 @@ class Admin::SettingsController < Admin::BaseController def index - all_settings = (Setting.all).group_by { |s| s.feature_flag? } - @settings = all_settings[false] - @feature_flags = all_settings[true] + all_settings = (Setting.all).group_by { |s| s.type } + @settings = all_settings['common'] + @feature_flags = all_settings['feature'] + @banner_styles = all_settings['banner-style'] + @banner_imgs = all_settings['banner-img'] end def update @@ -13,7 +15,9 @@ class Admin::SettingsController < Admin::BaseController end private + def settings_params params.require(:setting).permit(:value) end + end \ No newline at end of file diff --git a/app/controllers/admin/signature_sheets_controller.rb b/app/controllers/admin/signature_sheets_controller.rb new file mode 100644 index 000000000..60299c5a6 --- /dev/null +++ b/app/controllers/admin/signature_sheets_controller.rb @@ -0,0 +1,32 @@ +class Admin::SignatureSheetsController < Admin::BaseController + + def index + @signature_sheets = SignatureSheet.all + end + + def new + @signature_sheet = SignatureSheet.new + end + + def create + @signature_sheet = SignatureSheet.new(signature_sheet_params) + @signature_sheet.author = current_user + if @signature_sheet.save + @signature_sheet.delay.verify_signatures + redirect_to [:admin, @signature_sheet], notice: I18n.t('flash.actions.create.signature_sheet') + else + render :new + end + end + + def show + @signature_sheet = SignatureSheet.find(params[:id]) + end + + private + + def signature_sheet_params + params.require(:signature_sheet).permit(:signable_type, :signable_id, :document_numbers) + end + +end \ No newline at end of file diff --git a/app/controllers/admin/site_customization/base_controller.rb b/app/controllers/admin/site_customization/base_controller.rb new file mode 100644 index 000000000..18422f66e --- /dev/null +++ b/app/controllers/admin/site_customization/base_controller.rb @@ -0,0 +1,10 @@ +class Admin::SiteCustomization::BaseController < Admin::BaseController + helper_method :namespace + + private + + def namespace + "admin" + end + +end diff --git a/app/controllers/admin/site_customization/content_blocks_controller.rb b/app/controllers/admin/site_customization/content_blocks_controller.rb new file mode 100644 index 000000000..2f0843ccc --- /dev/null +++ b/app/controllers/admin/site_customization/content_blocks_controller.rb @@ -0,0 +1,40 @@ +class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomization::BaseController + load_and_authorize_resource :content_block, class: "SiteCustomization::ContentBlock" + + def index + @content_blocks = SiteCustomization::ContentBlock.order(:name, :locale) + end + + def create + if @content_block.save + redirect_to admin_site_customization_content_blocks_path, notice: t('admin.site_customization.content_blocks.create.notice') + else + flash.now[:error] = t('admin.site_customization.content_blocks.create.error') + render :new + end + end + + def update + if @content_block.update(content_block_params) + redirect_to admin_site_customization_content_blocks_path, notice: t('admin.site_customization.content_blocks.update.notice') + else + flash.now[:error] = t('admin.site_customization.content_blocks.update.error') + render :edit + end + end + + def destroy + @content_block.destroy + redirect_to admin_site_customization_content_blocks_path, notice: t('admin.site_customization.content_blocks.destroy.notice') + end + + private + + def content_block_params + params.require(:site_customization_content_block).permit( + :name, + :locale, + :body + ) + end +end diff --git a/app/controllers/admin/site_customization/images_controller.rb b/app/controllers/admin/site_customization/images_controller.rb new file mode 100644 index 000000000..c9f318f41 --- /dev/null +++ b/app/controllers/admin/site_customization/images_controller.rb @@ -0,0 +1,43 @@ +class Admin::SiteCustomization::ImagesController < Admin::SiteCustomization::BaseController + load_and_authorize_resource :image, class: "SiteCustomization::Image" + + def index + @images = SiteCustomization::Image.all_images + end + + def update + if params[:site_customization_image].nil? + redirect_to admin_site_customization_images_path + return + end + + if @image.update(image_params) + redirect_to admin_site_customization_images_path, notice: t('admin.site_customization.images.update.notice') + else + flash.now[:error] = t('admin.site_customization.images.update.error') + + @images = SiteCustomization::Image.all_images + idx = @images.index {|e| e.name == @image.name } + @images[idx] = @image + + render :index + end + end + + def destroy + @image.image = nil + if @image.save + redirect_to admin_site_customization_images_path, notice: t('admin.site_customization.images.destroy.notice') + else + redirect_to admin_site_customization_images_path, notice: t('admin.site_customization.images.destroy.error') + end + end + + private + + def image_params + params.require(:site_customization_image).permit( + :image + ) + end +end diff --git a/app/controllers/admin/site_customization/pages_controller.rb b/app/controllers/admin/site_customization/pages_controller.rb new file mode 100644 index 000000000..4d92a6a1e --- /dev/null +++ b/app/controllers/admin/site_customization/pages_controller.rb @@ -0,0 +1,44 @@ +class Admin::SiteCustomization::PagesController < Admin::SiteCustomization::BaseController + load_and_authorize_resource :page, class: "SiteCustomization::Page" + + def index + @pages = SiteCustomization::Page.order('slug').page(params[:page]) + end + + def create + if @page.save + redirect_to admin_site_customization_pages_path, notice: t('admin.site_customization.pages.create.notice') + else + flash.now[:error] = t('admin.site_customization.pages.create.error') + render :new + end + end + + def update + if @page.update(page_params) + redirect_to admin_site_customization_pages_path, notice: t('admin.site_customization.pages.update.notice') + else + flash.now[:error] = t('admin.site_customization.pages.update.error') + render :edit + end + end + + def destroy + @page.destroy + redirect_to admin_site_customization_pages_path, notice: t('admin.site_customization.pages.destroy.notice') + end + + private + + def page_params + params.require(:site_customization_page).permit( + :slug, + :title, + :subtitle, + :content, + :more_info_flag, + :print_content_flag, + :status + ) + end +end diff --git a/app/controllers/admin/spending_proposals_controller.rb b/app/controllers/admin/spending_proposals_controller.rb index 61f9aa78d..b5923f2f6 100644 --- a/app/controllers/admin/spending_proposals_controller.rb +++ b/app/controllers/admin/spending_proposals_controller.rb @@ -2,38 +2,56 @@ class Admin::SpendingProposalsController < Admin::BaseController include FeatureFlags feature_flag :spending_proposals - has_filters %w{valuation_open without_admin managed valuating valuation_finished}, only: :index + has_filters %w{valuation_open without_admin managed valuating valuation_finished all}, only: :index load_and_authorize_resource def index - @spending_proposals = SpendingProposal.scoped_filter(params, @current_filter).order(created_at: :desc).page(params[:page]) + @spending_proposals = SpendingProposal.scoped_filter(params, @current_filter).order(cached_votes_up: :desc, created_at: :desc).page(params[:page]) end def show end def edit - @admins = Administrator.includes(:user).all - @valuators = Valuator.includes(:user).all.order("description ASC").order("users.email ASC") - @tags = ActsAsTaggableOn::Tag.spending_proposal_tags + load_admins + load_valuators + load_tags end def update if @spending_proposal.update(spending_proposal_params) - path = admin_spending_proposal_path( @spending_proposal, - { anchor: 'classification' }.merge(SpendingProposal.filter_params(params))) - - redirect_to path, notice: t("flash.actions.update.spending_proposal") + redirect_to admin_spending_proposal_path(@spending_proposal, SpendingProposal.filter_params(params)), + notice: t("flash.actions.update.spending_proposal") else + load_admins + load_valuators + load_tags render :edit end end + def summary + @spending_proposals = SpendingProposal.group(:geozone).sum(:price).sort_by{|geozone, count| geozone.present? ? geozone.name : "z"} + @spending_proposals_with_supports = SpendingProposal.with_supports.group(:geozone).sum(:price).sort_by{|geozone, count| geozone.present? ? geozone.name : "z"} + end + private def spending_proposal_params - params.require(:spending_proposal).permit(:administrator_id, :tag_list, valuator_ids: []) + params.require(:spending_proposal).permit(:title, :description, :external_url, :geozone_id, :association_name, :administrator_id, :tag_list, valuator_ids: []) end -end + def load_admins + @admins = Administrator.includes(:user).all + end + + def load_valuators + @valuators = Valuator.includes(:user).all.order("description ASC").order("users.email ASC") + end + + def load_tags + @tags = ActsAsTaggableOn::Tag.spending_proposal_tags + end + +end \ No newline at end of file diff --git a/app/controllers/admin/stats_controller.rb b/app/controllers/admin/stats_controller.rb index 7183c3767..9989da796 100644 --- a/app/controllers/admin/stats_controller.rb +++ b/app/controllers/admin/stats_controller.rb @@ -18,6 +18,22 @@ class Admin::StatsController < Admin::BaseController @verified_users = User.with_hidden.level_two_or_three_verified.count @unverified_users = User.with_hidden.unverified.count @users = User.with_hidden.count + @user_ids_who_voted_proposals = ActsAsVotable::Vote.where(votable_type: 'Proposal').distinct.count(:voter_id) + @user_ids_who_didnt_vote_proposals = @verified_users - @user_ids_who_voted_proposals @spending_proposals = SpendingProposal.count + budgets_ids = Budget.where.not(phase: 'finished').pluck(:id) + @budgets = budgets_ids.size + @investments = Budget::Investment.where(budget_id: budgets_ids).count end -end + + def proposal_notifications + @proposal_notifications = ProposalNotification.all + @proposals_with_notifications = @proposal_notifications.select(:proposal_id).distinct.count + end + + def direct_messages + @direct_messages = DirectMessage.count + @users_who_have_sent_message = DirectMessage.select(:sender_id).distinct.count + end + +end \ No newline at end of file diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index eed1c4fea..1e17e1c2d 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -30,4 +30,4 @@ class Admin::UsersController < Admin::BaseController @user = User.with_hidden.find(params[:id]) end -end +end \ No newline at end of file diff --git a/app/controllers/admin/valuators_controller.rb b/app/controllers/admin/valuators_controller.rb index 4561e7579..4b52a753e 100644 --- a/app/controllers/admin/valuators_controller.rb +++ b/app/controllers/admin/valuators_controller.rb @@ -25,9 +25,15 @@ class Admin::ValuatorsController < Admin::BaseController redirect_to admin_valuators_path end + def summary + @valuators = Valuator.order(spending_proposals_count: :desc) + end + private + def create_params params[:valuator][:description] = nil if params[:valuator][:description].blank? params.require(:valuator).permit(:user_id, :description) end -end + +end \ No newline at end of file diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 640adf299..495a25314 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,7 +1,6 @@ require "application_responder" class ApplicationController < ActionController::Base - include SimpleCaptcha::ControllerHelpers include HasFilters include HasOrders @@ -116,4 +115,9 @@ class ApplicationController < ActionController::Base end end + def set_default_budget_filter + if @budget.try(:balloting?) + params[:filter] ||= "selected" + end + end end diff --git a/app/controllers/budgets/ballot/lines_controller.rb b/app/controllers/budgets/ballot/lines_controller.rb new file mode 100644 index 000000000..25f356f1f --- /dev/null +++ b/app/controllers/budgets/ballot/lines_controller.rb @@ -0,0 +1,79 @@ +module Budgets + module Ballot + class LinesController < ApplicationController + before_action :authenticate_user! + #before_action :ensure_final_voting_allowed + before_action :load_budget + before_action :load_ballot + before_action :load_tag_cloud + before_action :load_categories + before_action :load_investments + before_action :load_ballot_referer + + load_and_authorize_resource :budget + load_and_authorize_resource :ballot, class: "Budget::Ballot", through: :budget + load_and_authorize_resource :line, through: :ballot, find_by: :investment_id, class: "Budget::Ballot::Line" + + def create + load_investment + load_heading + + @ballot.add_investment(@investment) + end + + def destroy + @investment = @line.investment + load_heading + + @line.destroy + load_investments + end + + private + + def ensure_final_voting_allowed + return head(:forbidden) unless @budget.balloting? + end + + def line_params + params.permit(:investment_id, :budget_id) + end + + def load_budget + @budget = Budget.find(params[:budget_id]) + end + + def load_ballot + @ballot = Budget::Ballot.where(user: current_user, budget: @budget).first_or_create + end + + def load_investment + @investment = Budget::Investment.find(params[:investment_id]) + end + + def load_investments + if params[:investments_ids].present? + @investment_ids = params[:investments_ids] + @investments = Budget::Investment.where(id: params[:investments_ids]) + end + end + + def load_heading + @heading = @investment.heading + end + + def load_tag_cloud + @tag_cloud = TagCloud.new(Budget::Investment, params[:search]) + end + + def load_categories + @categories = ActsAsTaggableOn::Tag.where("kind = 'category'").order(:name) + end + + def load_ballot_referer + @ballot_referer = session[:ballot_referer] + end + + end + end +end diff --git a/app/controllers/budgets/ballots_controller.rb b/app/controllers/budgets/ballots_controller.rb new file mode 100644 index 000000000..b5b63b4aa --- /dev/null +++ b/app/controllers/budgets/ballots_controller.rb @@ -0,0 +1,26 @@ +module Budgets + class BallotsController < ApplicationController + before_action :authenticate_user! + load_and_authorize_resource :budget + before_action :load_ballot + after_action :store_referer, only: [:show] + + def show + authorize! :show, @ballot + session[:ballot_referer] = request.referer + render template: "budgets/ballot/show" + end + + private + + def load_ballot + query = Budget::Ballot.where(user: current_user, budget: @budget) + @ballot = @budget.balloting? ? query.first_or_create : query.first_or_initialize + end + + def store_referer + session[:ballot_referer] = request.referer + end + + end +end diff --git a/app/controllers/budgets/groups_controller.rb b/app/controllers/budgets/groups_controller.rb new file mode 100644 index 000000000..d84eb2fdd --- /dev/null +++ b/app/controllers/budgets/groups_controller.rb @@ -0,0 +1,13 @@ +module Budgets + class GroupsController < ApplicationController + load_and_authorize_resource :budget + load_and_authorize_resource :group, class: "Budget::Group" + + before_action :set_default_budget_filter, only: :show + has_filters %w{not_unfeasible feasible unfeasible unselected selected}, only: [:show] + + def show + end + + end +end \ No newline at end of file diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb new file mode 100644 index 000000000..7eb1b1291 --- /dev/null +++ b/app/controllers/budgets/investments_controller.rb @@ -0,0 +1,115 @@ +module Budgets + class InvestmentsController < ApplicationController + include FeatureFlags + include CommentableActions + include FlagActions + + before_action :authenticate_user!, except: [:index, :show] + + load_and_authorize_resource :budget + load_and_authorize_resource :investment, through: :budget, class: "Budget::Investment" + + before_action -> { flash.now[:notice] = flash[:notice].html_safe if flash[:html_safe] && flash[:notice] } + before_action :load_ballot, only: [:index, :show] + before_action :load_heading, only: [:index, :show] + before_action :set_random_seed, only: :index + before_action :load_categories, only: [:index, :new, :create] + before_action :set_default_budget_filter, only: :index + + feature_flag :budgets + + has_orders %w{most_voted newest oldest}, only: :show + has_orders ->(c) { c.instance_variable_get(:@budget).investments_orders }, only: :index + has_filters %w{not_unfeasible feasible unfeasible unselected selected}, only: [:index, :show] + + invisible_captcha only: [:create, :update], honeypot: :subtitle, scope: :budget_investment + + respond_to :html, :js + + def index + @investments = @investments.apply_filters_and_search(@budget, params, @current_filter).send("sort_by_#{@current_order}").page(params[:page]).per(10).for_render + @investment_ids = @investments.pluck(:id) + load_investment_votes(@investments) + @tag_cloud = tag_cloud + end + + def new + end + + def show + @commentable = @investment + @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + set_comment_flags(@comment_tree.comments) + load_investment_votes(@investment) + @investment_ids = [@investment.id] + end + + def create + @investment.author = current_user + + if @investment.save + Mailer.budget_investment_created(@investment).deliver_later + redirect_to budget_investment_path(@budget, @investment), + notice: t('flash.actions.create.budget_investment') + else + render :new + end + end + + def destroy + @investment.destroy + redirect_to user_path(current_user, filter: 'budget_investments'), notice: t('flash.actions.destroy.budget_investment') + end + + def vote + @investment.register_selection(current_user) + load_investment_votes(@investment) + respond_to do |format| + format.html { redirect_to budget_investments_path(heading_id: @investment.heading.id) } + format.js + end + end + + private + + def load_investment_votes(investments) + @investment_votes = current_user ? current_user.budget_investment_votes(investments) : {} + end + + def set_random_seed + if params[:order] == 'random' || params[:order].blank? + params[:random_seed] ||= rand(99)/100.0 + seed = Float(params[:random_seed]) rescue 0 + Budget::Investment.connection.execute("select setseed(#{seed})") + else + params[:random_seed] = nil + end + end + + def investment_params + params.require(:budget_investment).permit(:title, :description, :external_url, :heading_id, :tag_list, :organization_name, :location, :terms_of_service) + end + + def load_ballot + query = Budget::Ballot.where(user: current_user, budget: @budget) + @ballot = @budget.balloting? ? query.first_or_create : query.first_or_initialize + end + + def load_heading + if params[:heading_id].present? + @heading = @budget.headings.find(params[:heading_id]) + @assigned_heading = @ballot.try(:heading_for_group, @heading.try(:group)) + end + end + + def load_categories + @categories = ActsAsTaggableOn::Tag.where("kind = 'category'").order(:name) + end + + def tag_cloud + TagCloud.new(Budget::Investment, params[:search]) + end + + end + +end diff --git a/app/controllers/budgets/results_controller.rb b/app/controllers/budgets/results_controller.rb new file mode 100644 index 000000000..aa94eb68b --- /dev/null +++ b/app/controllers/budgets/results_controller.rb @@ -0,0 +1,21 @@ +module Budgets + class ResultsController < ApplicationController + + load_and_authorize_resource :budget + + def show + @result = load_result + end + + private + + def load_result + Budget::Result.new(@budget, heading) + end + + def heading + @budget.headings.find(params[:heading_id]) + end + + end +end \ No newline at end of file diff --git a/app/controllers/budgets_controller.rb b/app/controllers/budgets_controller.rb new file mode 100644 index 000000000..2a53c410b --- /dev/null +++ b/app/controllers/budgets_controller.rb @@ -0,0 +1,18 @@ +class BudgetsController < ApplicationController + include FeatureFlags + feature_flag :budgets + + load_and_authorize_resource + before_action :set_default_budget_filter, only: :show + has_filters %w{not_unfeasible feasible unfeasible unselected selected}, only: :show + + respond_to :html, :js + + def show + end + + def index + @budgets = @budgets.order(:created_at) + end + +end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index df928aa02..0a0018c9f 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -77,4 +77,4 @@ class CommentsController < ApplicationController Notification.add(notifiable.author_id, notifiable) unless comment.author_id == notifiable.author_id end -end +end \ No newline at end of file diff --git a/app/controllers/concerns/commentable_actions.rb b/app/controllers/concerns/commentable_actions.rb index 01066c8ad..287f2d418 100644 --- a/app/controllers/concerns/commentable_actions.rb +++ b/app/controllers/concerns/commentable_actions.rb @@ -1,6 +1,7 @@ module CommentableActions extend ActiveSupport::Concern include Polymorphic + include Search def index @resources = @search_terms.present? ? resource_model.search(@search_terms) : resource_model.all @@ -11,6 +12,8 @@ module CommentableActions index_customization if index_customization.present? @tag_cloud = tag_cloud + @banners = Banner.with_active + set_resource_votes(@resources) set_resources_instance end @@ -38,7 +41,7 @@ module CommentableActions @resource = resource_model.new(strong_params) @resource.author = current_user - if @resource.save_with_captcha + if @resource.save track_event redirect_path = url_for(controller: controller_name, action: :show, id: @resource.id) redirect_to redirect_path, notice: t("flash.actions.create.#{resource_name.underscore}") @@ -55,7 +58,7 @@ module CommentableActions def update resource.assign_attributes(strong_params) - if resource.save_with_captcha + if resource.save redirect_to resource, notice: t("flash.actions.update.#{resource_name.underscore}") else load_categories @@ -98,53 +101,6 @@ module CommentableActions end end - def parse_search_terms - @search_terms = params[:search] if params[:search].present? - end - - def parse_advanced_search_terms - @advanced_search_terms = params[:advanced_search] if params[:advanced_search].present? - parse_search_date - end - - def parse_search_date - return unless search_by_date? - params[:advanced_search][:date_range] = search_date_range - end - - def search_by_date? - params[:advanced_search] && params[:advanced_search][:date_min].present? - end - - def search_start_date - case params[:advanced_search][:date_min] - when '1' - 24.hours.ago - when '2' - 1.week.ago - when '3' - 1.month.ago - when '4' - 1.year.ago - else - Date.parse(params[:advanced_search][:date_min]) rescue nil - end - end - - def search_finish_date - params[:advanced_search][:date_max].try(:to_date) || Date.today - end - - def search_date_range - search_start_date.beginning_of_day..search_finish_date.end_of_day - end - - def set_search_order - if params[:search].present? && params[:order].blank? - params[:order] = 'relevance' - end - end - def set_resource_votes(instance) send("set_#{resource_name}_votes", instance) end @@ -152,4 +108,5 @@ module CommentableActions def index_customization nil end -end + +end \ No newline at end of file diff --git a/app/controllers/concerns/feature_flags.rb b/app/controllers/concerns/feature_flags.rb index 9c96db96e..ca3cd929e 100644 --- a/app/controllers/concerns/feature_flags.rb +++ b/app/controllers/concerns/feature_flags.rb @@ -3,7 +3,7 @@ module FeatureFlags class_methods do def feature_flag(name, *options) - before_filter(*options) do + before_action(*options) do check_feature_flag(name) end end diff --git a/app/controllers/concerns/has_orders.rb b/app/controllers/concerns/has_orders.rb index 31a98e850..90be317f0 100644 --- a/app/controllers/concerns/has_orders.rb +++ b/app/controllers/concerns/has_orders.rb @@ -3,8 +3,9 @@ module HasOrders class_methods do def has_orders(valid_orders, *args) - before_action(*args) do - @valid_orders = valid_orders + before_action(*args) do |c| + @valid_orders = valid_orders.respond_to?(:call) ? valid_orders.call(c) : valid_orders.dup + @valid_orders.delete('relevance') if params[:search].blank? @current_order = @valid_orders.include?(params[:order]) ? params[:order] : @valid_orders.first end end diff --git a/app/controllers/concerns/moderate_actions.rb b/app/controllers/concerns/moderate_actions.rb index 792dacde0..05aa0e97f 100644 --- a/app/controllers/concerns/moderate_actions.rb +++ b/app/controllers/concerns/moderate_actions.rb @@ -4,9 +4,9 @@ module ModerateActions def index @resources = @resources.send(@current_filter) - .send("sort_by_#{@current_order}") - .page(params[:page]) - .per(50) + .send("sort_by_#{@current_order}") + .page(params[:page]) + .per(50) set_resources_instance end diff --git a/app/controllers/concerns/polymorphic.rb b/app/controllers/concerns/polymorphic.rb index 51c5768c9..10d64698d 100644 --- a/app/controllers/concerns/polymorphic.rb +++ b/app/controllers/concerns/polymorphic.rb @@ -1,6 +1,7 @@ module Polymorphic private + def resource @resource ||= instance_variable_get("@#{resource_name}") end diff --git a/app/controllers/concerns/search.rb b/app/controllers/concerns/search.rb new file mode 100644 index 000000000..922ad93cc --- /dev/null +++ b/app/controllers/concerns/search.rb @@ -0,0 +1,57 @@ +module Search + extend ActiveSupport::Concern + + included do + before_action :parse_search_terms, only: [:index, :suggest] + before_action :parse_advanced_search_terms, only: :index + before_action :set_search_order, only: :index + end + + def parse_search_terms + @search_terms = params[:search] if params[:search].present? + end + + def parse_advanced_search_terms + @advanced_search_terms = params[:advanced_search] if params[:advanced_search].present? + parse_search_date + end + + def parse_search_date + return unless search_by_date? + params[:advanced_search][:date_range] = search_date_range + end + + def search_by_date? + params[:advanced_search] && params[:advanced_search][:date_min].present? + end + + def search_start_date + case params[:advanced_search][:date_min] + when '1' + 24.hours.ago + when '2' + 1.week.ago + when '3' + 1.month.ago + when '4' + 1.year.ago + else + Date.parse(params[:advanced_search][:date_min]) rescue 100.years.ago + end + end + + def search_finish_date + (params[:advanced_search][:date_max].to_date rescue Date.today) || Date.today + end + + def search_date_range + [100.years.ago, search_start_date].max.beginning_of_day..[search_finish_date, Date.today].min.end_of_day + end + + def set_search_order + if params[:search].present? && params[:order].blank? + params[:order] = 'relevance' + end + end + +end \ No newline at end of file diff --git a/app/controllers/debate_links_controller.rb b/app/controllers/debate_links_controller.rb deleted file mode 100644 index f6f7c3af8..000000000 --- a/app/controllers/debate_links_controller.rb +++ /dev/null @@ -1,31 +0,0 @@ -class DebateLinksController < ApplicationController - include FeatureFlags - include CommentableActions - - before_action :authenticate_user!, except: [:show] - - load_and_authorize_resource class: "Debate" - - feature_flag :debates - - respond_to :html, :js - - private - - def create_params - params.require(:debate).permit(:title, :external_link, :tag_list, :terms_of_service, :captcha, :captcha_key).merge(link_required: true) - end - - def debate_params - params.require(:debate).permit(:title, :external_link, :tag_list, :terms_of_service, :captcha, :captcha_key).merge(link_required: true) - end - - def after_create_path - debate_path(@resource) - end - - def resource_model - Debate - end - -end diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb index b5a0ae25a..9b29080dd 100644 --- a/app/controllers/debates_controller.rb +++ b/app/controllers/debates_controller.rb @@ -3,14 +3,13 @@ class DebatesController < ApplicationController include CommentableActions include FlagActions - before_action :parse_search_terms, only: [:index, :suggest] - before_action :parse_advanced_search_terms, only: :index before_action :parse_tag_filter, only: :index - before_action :set_search_order, only: :index before_action :authenticate_user!, except: [:index, :show, :map] feature_flag :debates + invisible_captcha only: [:create, :update], honeypot: :subtitle + has_orders %w{hot_score confidence_score created_at relevance}, only: :index has_orders %w{most_voted newest oldest}, only: :show @@ -18,6 +17,11 @@ class DebatesController < ApplicationController helper_method :resource_model, :resource_name respond_to :html, :js + def index_customization + @featured_debates = @debates.featured + @proposal_successfull_exists = Proposal.successful.exists? + end + def show super redirect_to debate_path(@debate), status: :moved_permanently if request.path != debate_path(@debate) @@ -28,10 +32,20 @@ class DebatesController < ApplicationController set_debate_votes(@debate) end + def unmark_featured + @debate.update_attribute(:featured_at, nil) + redirect_to request.query_parameters.merge(action: :index) + end + + def mark_featured + @debate.update_attribute(:featured_at, Time.current) + redirect_to request.query_parameters.merge(action: :index) + end + private def debate_params - params.require(:debate).permit(:title, :description, :tag_list, :terms_of_service, :captcha, :captcha_key) + params.require(:debate).permit(:title, :description, :tag_list, :terms_of_service) end def resource_model diff --git a/app/controllers/direct_messages_controller.rb b/app/controllers/direct_messages_controller.rb new file mode 100644 index 000000000..07f39d941 --- /dev/null +++ b/app/controllers/direct_messages_controller.rb @@ -0,0 +1,36 @@ +class DirectMessagesController < ApplicationController + load_and_authorize_resource + + def new + @receiver = User.find(params[:user_id]) + @direct_message = DirectMessage.new(receiver: @receiver) + end + + def create + @sender = current_user + @receiver = User.find(params[:user_id]) + + @direct_message = DirectMessage.new(parsed_params) + if @direct_message.save + Mailer.direct_message_for_receiver(@direct_message).deliver_later + Mailer.direct_message_for_sender(@direct_message).deliver_later + redirect_to [@receiver, @direct_message], notice: I18n.t("flash.actions.create.direct_message") + else + render :new + end + end + + def show + @direct_message = DirectMessage.find(params[:id]) + end + + private + + def direct_message_params + params.require(:direct_message).permit(:title, :body) + end + + def parsed_params + direct_message_params.merge(sender: @sender, receiver: @receiver) + end +end \ No newline at end of file diff --git a/app/controllers/management/account_controller.rb b/app/controllers/management/account_controller.rb new file mode 100644 index 000000000..64e0ddf4d --- /dev/null +++ b/app/controllers/management/account_controller.rb @@ -0,0 +1,14 @@ +class Management::AccountController < Management::BaseController + + before_action :only_verified_users + + def show + end + + private + + def only_verified_users + check_verified_user t("management.account.alert.unverified_user") + end + +end diff --git a/app/controllers/management/base_controller.rb b/app/controllers/management/base_controller.rb index bced745b4..f7a9c2214 100644 --- a/app/controllers/management/base_controller.rb +++ b/app/controllers/management/base_controller.rb @@ -16,10 +16,20 @@ class Management::BaseController < ActionController::Base session[:manager] end + def current_user + managed_user + end + def managed_user @managed_user ||= Verification::Management::ManagedUser.find(session[:document_type], session[:document_number]) end + def check_verified_user(alert_msg) + unless managed_user.level_two_or_three_verified? + redirect_to management_document_verifications_path, alert: alert_msg + end + end + def set_locale if params[:locale] && I18n.available_locales.include?(params[:locale].to_sym) session[:locale] = params[:locale] diff --git a/app/controllers/management/budgets/investments_controller.rb b/app/controllers/management/budgets/investments_controller.rb new file mode 100644 index 000000000..652d2b9a7 --- /dev/null +++ b/app/controllers/management/budgets/investments_controller.rb @@ -0,0 +1,70 @@ +class Management::Budgets::InvestmentsController < Management::BaseController + + load_resource :budget + load_resource :investment, through: :budget, class: 'Budget::Investment' + + before_action :only_verified_users, except: :print + before_action :load_heading, only: [:index, :show, :print] + + def index + @investments = @investments.apply_filters_and_search(@budget, params).page(params[:page]) + load_investment_votes(@investments) + end + + def new + load_categories + end + + def create + @investment.terms_of_service = "1" + @investment.author = managed_user + + if @investment.save + notice= t('flash.actions.create.notice', resource_name: Budget::Investment.model_name.human, count: 1) + redirect_to management_budget_investment_path(@budget, @investment), notice: notice + else + render :new + end + end + + def show + load_investment_votes(@investment) + end + + def vote + @investment.register_selection(managed_user) + load_investment_votes(@investment) + respond_to do |format| + format.html { redirect_to management_budget_investments_path(heading_id: @investment.heading.id) } + format.js + end + end + + def print + @investments = @investments.apply_filters_and_search(@budget, params).order(cached_votes_up: :desc).for_render.limit(15) + load_investment_votes(@investments) + end + + private + + def load_investment_votes(investments) + @investment_votes = managed_user ? managed_user.budget_investment_votes(investments) : {} + end + + def investment_params + params.require(:budget_investment).permit(:title, :description, :external_url, :heading_id, :tag_list, :organization_name, :location) + end + + def only_verified_users + check_verified_user t("management.budget_investments.alert.unverified_user") + end + + def load_heading + @heading = @budget.headings.find(params[:heading_id]) if params[:heading_id].present? + end + + def load_categories + @categories = ActsAsTaggableOn::Tag.where("kind = 'category'").order(:name) + end + +end diff --git a/app/controllers/management/budgets_controller.rb b/app/controllers/management/budgets_controller.rb new file mode 100644 index 000000000..dd7259c99 --- /dev/null +++ b/app/controllers/management/budgets_controller.rb @@ -0,0 +1,35 @@ +class Management::BudgetsController < Management::BaseController + include FeatureFlags + include HasFilters + feature_flag :budgets + + before_action :only_verified_users, except: :print_investments + + def create_investments + @budgets = Budget.accepting.order(created_at: :desc).page(params[:page]) + + if current_manager_administrator? + @budgets += Budget.reviewing.order(created_at: :desc) + + Budget.selecting.order(created_at: :desc) + end + end + + def support_investments + @budgets = Budget.selecting.order(created_at: :desc).page(params[:page]) + end + + def print_investments + @budgets = Budget.current.order(created_at: :desc).page(params[:page]) + end + + private + + def only_verified_users + check_verified_user t("management.budget_investments.alert.unverified_user") + end + + def current_manager_administrator? + session[:manager]["login"].match("admin") + end + +end diff --git a/app/controllers/management/dashboard_controller.rb b/app/controllers/management/dashboard_controller.rb index abb605341..87b134270 100644 --- a/app/controllers/management/dashboard_controller.rb +++ b/app/controllers/management/dashboard_controller.rb @@ -3,4 +3,4 @@ class Management::DashboardController < Management::BaseController def index end -end +end \ No newline at end of file diff --git a/app/controllers/management/document_verifications_controller.rb b/app/controllers/management/document_verifications_controller.rb index 1b8fabcc2..510e4fa93 100644 --- a/app/controllers/management/document_verifications_controller.rb +++ b/app/controllers/management/document_verifications_controller.rb @@ -33,17 +33,17 @@ class Management::DocumentVerificationsController < Management::BaseController private - def document_verification_params - params.require(:document_verification).permit(:document_type, :document_number) - end + def document_verification_params + params.require(:document_verification).permit(:document_type, :document_number) + end - def set_document - session[:document_type] = params[:document_verification][:document_type] - session[:document_number] = params[:document_verification][:document_number] - end + def set_document + session[:document_type] = params[:document_verification][:document_type] + session[:document_number] = params[:document_verification][:document_number] + end - def clean_document_number - params[:document_verification][:document_number] = params[:document_verification][:document_number].gsub(/[^a-z0-9]+/i, "").upcase unless params[:document_verification][:document_number].blank? - end + def clean_document_number + params[:document_verification][:document_number] = params[:document_verification][:document_number].gsub(/[^a-z0-9]+/i, "").upcase unless params[:document_verification][:document_number].blank? + end end \ No newline at end of file diff --git a/app/controllers/management/email_verifications_controller.rb b/app/controllers/management/email_verifications_controller.rb index 3558e6fbf..7d511fe4b 100644 --- a/app/controllers/management/email_verifications_controller.rb +++ b/app/controllers/management/email_verifications_controller.rb @@ -16,8 +16,8 @@ class Management::EmailVerificationsController < Management::BaseController private - def email_verification_params - params.require(:email_verification).permit(:document_type, :document_number, :email) - end + def email_verification_params + params.require(:email_verification).permit(:document_type, :document_number, :email) + end end \ No newline at end of file diff --git a/app/controllers/management/proposals_controller.rb b/app/controllers/management/proposals_controller.rb index 6e01a8c77..1a30526f5 100644 --- a/app/controllers/management/proposals_controller.rb +++ b/app/controllers/management/proposals_controller.rb @@ -2,7 +2,7 @@ class Management::ProposalsController < Management::BaseController include HasOrders include CommentableActions - before_action :check_verified_user, except: :print + before_action :only_verified_users, except: :print before_action :set_proposal, only: [:vote, :show] before_action :parse_search_terms, only: :index before_action :load_categories, only: [:new, :edit] @@ -13,11 +13,12 @@ class Management::ProposalsController < Management::BaseController def show super + @notifications = @proposal.notifications redirect_to management_proposal_path(@proposal), status: :moved_permanently if request.path != management_proposal_path(@proposal) end def vote - @proposal.register_vote(current_user, 'yes') + @proposal.register_vote(managed_user, 'yes') set_proposal_votes(@proposal) end @@ -33,31 +34,23 @@ class Management::ProposalsController < Management::BaseController end def proposal_params - params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url, :responsible_name, :tag_list, :terms_of_service, :captcha, :captcha_key) + params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url, :responsible_name, :tag_list, :terms_of_service, :geozone_id) end def resource_model Proposal end - def check_verified_user - unless current_user.level_two_or_three_verified? - redirect_to management_document_verifications_path, alert: t("management.proposals.alert.unverified_user") - end + def only_verified_users + check_verified_user t("management.proposals.alert.unverified_user") end - def current_user - managed_user - end - - ### Duplicated in application_controller. Move to a concern. def set_proposal_votes(proposals) - @proposal_votes = current_user ? current_user.proposal_votes(proposals) : {} + @proposal_votes = managed_user ? managed_user.proposal_votes(proposals) : {} end def set_comment_flags(comments) - @comment_flags = current_user ? current_user.comment_flags(comments) : {} + @comment_flags = managed_user ? managed_user.comment_flags(comments) : {} end - ### -end +end \ No newline at end of file diff --git a/app/controllers/management/sessions_controller.rb b/app/controllers/management/sessions_controller.rb index bad38663e..5d0587ae5 100644 --- a/app/controllers/management/sessions_controller.rb +++ b/app/controllers/management/sessions_controller.rb @@ -4,11 +4,10 @@ class Management::SessionsController < ActionController::Base def create destroy_session - if manager = ManagerAuthenticator.new(params).auth - session[:manager] = manager + if admin? || manager? || authenticated_manager? redirect_to management_root_path else - raise ActionController::RoutingError.new('Not Found') + raise CanCan::AccessDenied end end @@ -25,4 +24,22 @@ class Management::SessionsController < ActionController::Base session[:document_number] = nil end + def admin? + if current_user.try(:administrator?) + session[:manager] = {login: "admin_user_#{current_user.id}"} + end + end + + def manager? + if current_user.try(:manager?) + session[:manager] = {login: "manager_user_#{current_user.id}"} + end + end + + def authenticated_manager? + if manager = ManagerAuthenticator.new(params).auth + session[:manager] = manager + end + end + end \ No newline at end of file diff --git a/app/controllers/management/spending_proposals_controller.rb b/app/controllers/management/spending_proposals_controller.rb index e6170722a..6b2c4c0f1 100644 --- a/app/controllers/management/spending_proposals_controller.rb +++ b/app/controllers/management/spending_proposals_controller.rb @@ -1,6 +1,12 @@ class Management::SpendingProposalsController < Management::BaseController - before_action :check_verified_user + before_action :only_verified_users, except: :print + before_action :set_spending_proposal, only: [:vote, :show] + + def index + @spending_proposals = apply_filters_and_search(SpendingProposal).order(cached_votes_up: :desc).page(params[:page]).for_render + set_spending_proposal_votes(@spending_proposals) + end def new @spending_proposal = SpendingProposal.new @@ -10,7 +16,7 @@ class Management::SpendingProposalsController < Management::BaseController @spending_proposal = SpendingProposal.new(spending_proposal_params) @spending_proposal.author = managed_user - if @spending_proposal.save_with_captcha + if @spending_proposal.save redirect_to management_spending_proposal_path(@spending_proposal), notice: t('flash.actions.create.notice', resource_name: t("activerecord.models.spending_proposal", count: 1)) else render :new @@ -18,29 +24,55 @@ class Management::SpendingProposalsController < Management::BaseController end def show - @spending_proposal = SpendingProposal.find(params[:id]) set_spending_proposal_votes(@spending_proposal) end + def vote + @spending_proposal.register_vote(managed_user, 'yes') + set_spending_proposal_votes(@spending_proposal) + end + + def print + params[:geozone] ||= 'all' + @spending_proposals = apply_filters_and_search(SpendingProposal).order(cached_votes_up: :desc).for_render.limit(15) + set_spending_proposal_votes(@spending_proposals) + end + private + def set_spending_proposal + @spending_proposal = SpendingProposal.find(params[:id]) + end + def spending_proposal_params - params.require(:spending_proposal).permit(:title, :description, :external_url, :geozone_id, :terms_of_service, :captcha, :captcha_key) + params.require(:spending_proposal).permit(:title, :description, :external_url, :geozone_id, :terms_of_service) end - def check_verified_user - unless current_user.level_two_or_three_verified? - redirect_to management_document_verifications_path, alert: t("management.spending_proposals.alert.unverified_user") - end - end - - def current_user - managed_user + def only_verified_users + check_verified_user t("management.spending_proposals.alert.unverified_user") end # This should not be necessary. Maybe we could create a specific show view for managers. def set_spending_proposal_votes(spending_proposals) - @spending_proposal_votes = current_user ? current_user.spending_proposal_votes(spending_proposals) : {} + @spending_proposal_votes = managed_user ? managed_user.spending_proposal_votes(spending_proposals) : {} + end + + def set_geozone_name + if params[:geozone] == 'all' + @geozone_name = t('geozones.none') + else + @geozone_name = Geozone.find(params[:geozone]).name + end + end + + def apply_filters_and_search(target) + target = params[:unfeasible].present? ? target.unfeasible : target.not_unfeasible + if params[:geozone].present? + target = target.by_geozone(params[:geozone]) + set_geozone_name + end + target = target.search(params[:search]) if params[:search].present? + target end end diff --git a/app/controllers/management/user_invites_controller.rb b/app/controllers/management/user_invites_controller.rb new file mode 100644 index 000000000..4d9b4909f --- /dev/null +++ b/app/controllers/management/user_invites_controller.rb @@ -0,0 +1,13 @@ +class Management::UserInvitesController < Management::BaseController + + def new + end + + def create + @emails = params[:emails].split(",").map(&:strip) + @emails.each do |email| + Mailer.user_invite(email).deliver_later + end + end + +end \ No newline at end of file diff --git a/app/controllers/management/users_controller.rb b/app/controllers/management/users_controller.rb index 53c432fd1..8d3d148fa 100644 --- a/app/controllers/management/users_controller.rb +++ b/app/controllers/management/users_controller.rb @@ -8,8 +8,8 @@ class Management::UsersController < Management::BaseController @user = User.new(user_params) @user.skip_password_validation = true @user.terms_of_service = '1' - @user.residence_verified_at = Time.now - @user.verified_at = Time.now + @user.residence_verified_at = Time.current + @user.verified_at = Time.current if @user.save then render :show @@ -18,6 +18,12 @@ class Management::UsersController < Management::BaseController end end + def erase + managed_user.erase(t("management.users.erased_by_manager", manager: current_manager['login'])) if current_manager.present? + destroy_session + redirect_to management_document_verifications_path, notice: t("management.users.erased_notice") + end + def logout destroy_session redirect_to management_root_url, notice: t("management.sessions.signed_out_managed_user") @@ -26,7 +32,7 @@ class Management::UsersController < Management::BaseController private def user_params - params.require(:user).permit(:document_type, :document_number, :username, :email) + params.require(:user).permit(:document_type, :document_number, :username, :email, :date_of_birth) end def destroy_session diff --git a/app/controllers/moderation/users_controller.rb b/app/controllers/moderation/users_controller.rb index 3255bb6cc..cd1fff9a5 100644 --- a/app/controllers/moderation/users_controller.rb +++ b/app/controllers/moderation/users_controller.rb @@ -21,13 +21,13 @@ class Moderation::UsersController < Moderation::BaseController private - def load_users - @users = User.with_hidden.search(params[:name_or_email]).page(params[:page]).for_render - end + def load_users + @users = User.with_hidden.search(params[:name_or_email]).page(params[:page]).for_render + end - def block_user - @user.block - Activity.log(current_user, :block, @user) - end + def block_user + @user.block + Activity.log(current_user, :block, @user) + end end \ No newline at end of file diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb index a4ec31b50..ab346e802 100644 --- a/app/controllers/notifications_controller.rb +++ b/app/controllers/notifications_controller.rb @@ -9,7 +9,7 @@ class NotificationsController < ApplicationController def show @notification = current_user.notifications.find(params[:id]) - redirect_to url_for(@notification.notifiable) + redirect_to url_for(@notification.linkable_resource) end def mark_all_as_read diff --git a/app/controllers/officing/base_controller.rb b/app/controllers/officing/base_controller.rb new file mode 100644 index 000000000..97ef23d30 --- /dev/null +++ b/app/controllers/officing/base_controller.rb @@ -0,0 +1,12 @@ +class Officing::BaseController < ApplicationController + layout 'admin' + + before_action :authenticate_user! + before_action :verify_officer + + skip_authorization_check + + def verify_officer + raise CanCan::AccessDenied unless current_user.try(:poll_officer?) || current_user.try(:administrator?) + end +end \ No newline at end of file diff --git a/app/controllers/officing/dashboard_controller.rb b/app/controllers/officing/dashboard_controller.rb new file mode 100644 index 000000000..4d80a974a --- /dev/null +++ b/app/controllers/officing/dashboard_controller.rb @@ -0,0 +1,6 @@ +class Officing::DashboardController < Officing::BaseController + + def index + end + +end diff --git a/app/controllers/officing/final_recounts_controller.rb b/app/controllers/officing/final_recounts_controller.rb new file mode 100644 index 000000000..e381240e7 --- /dev/null +++ b/app/controllers/officing/final_recounts_controller.rb @@ -0,0 +1,47 @@ +class Officing::FinalRecountsController < Officing::BaseController + before_action :load_poll + before_action :load_officer_assignment, only: :create + + def new + @officer_assignments = ::Poll::OfficerAssignment. + includes(:final_recounts, booth_assignment: [:booth]). + joins(:booth_assignment). + final. + where(id: current_user.poll_officer.officer_assignment_ids). + where("poll_booth_assignments.poll_id = ?", @poll.id). + order(date: :asc) + + @final_recounts = @officer_assignments.select {|oa| oa.final_recounts.any?}.map(&:final_recounts).flatten + end + + def create + @final_recount = ::Poll::FinalRecount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, date: final_recount_params[:date]) + @final_recount.officer_assignment_id = @officer_assignment.id + @final_recount.count = final_recount_params[:count] + + if @final_recount.save + msg = { notice: t("officing.final_recounts.flash.create") } + else + msg = { alert: t("officing.final_recounts.flash.error_create") } + end + redirect_to new_officing_poll_final_recount_path(@poll), msg + end + + private + def load_poll + @poll = Poll.expired.find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.final.find_by(id: final_recount_params[:officer_assignment_id]) + if @officer_assignment.blank? + redirect_to new_officing_poll_final_recount_path(@poll), alert: t("officing.final_recounts.flash.error_create") + end + end + + def final_recount_params + params.permit(:officer_assignment_id, :count, :date) + end + +end \ No newline at end of file diff --git a/app/controllers/officing/polls_controller.rb b/app/controllers/officing/polls_controller.rb new file mode 100644 index 000000000..e122284ec --- /dev/null +++ b/app/controllers/officing/polls_controller.rb @@ -0,0 +1,15 @@ +class Officing::PollsController < Officing::BaseController + + def index + @polls = current_user.poll_officer? ? current_user.poll_officer.voting_days_assigned_polls : [] + @polls = @polls.select {|poll| poll.current?(Time.current) || poll.current?(1.day.ago)} + end + + def final + @polls = current_user.poll_officer? ? current_user.poll_officer.final_days_assigned_polls : [] + return unless current_user.poll_officer? + + @polls = @polls.select {|poll| poll.ends_at > 1.week.ago && poll.expired?} + end + +end \ No newline at end of file diff --git a/app/controllers/officing/recounts_controller.rb b/app/controllers/officing/recounts_controller.rb new file mode 100644 index 000000000..1c66e57fd --- /dev/null +++ b/app/controllers/officing/recounts_controller.rb @@ -0,0 +1,46 @@ +class Officing::RecountsController < Officing::BaseController + before_action :load_poll + before_action :load_officer_assignment, only: :create + + def new + @officer_assignments = ::Poll::OfficerAssignment. + includes(:recount, booth_assignment: :booth). + joins(:booth_assignment). + voting_days. + where(id: current_user.poll_officer.officer_assignment_ids). + where("poll_booth_assignments.poll_id = ?", @poll.id). + order(date: :asc) + @recounted = @officer_assignments.select {|oa| oa.recount.present?}.reverse + end + + def create + @recount = ::Poll::Recount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, date: @officer_assignment.date) + @recount.officer_assignment_id = @officer_assignment.id + @recount.count = recount_params[:count] + + if @recount.save + msg = { notice: t("officing.recounts.flash.create") } + else + msg = { alert: t("officing.recounts.flash.error_create") } + end + redirect_to new_officing_poll_recount_path(@poll), msg + end + + private + def load_poll + @poll = Poll.find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.find_by(id: recount_params[:officer_assignment_id]) + if @officer_assignment.blank? + redirect_to new_officing_poll_recount_path(@poll), alert: t("officing.recounts.flash.error_create") + end + end + + def recount_params + params.permit(:officer_assignment_id, :count) + end + +end \ No newline at end of file diff --git a/app/controllers/officing/residence_controller.rb b/app/controllers/officing/residence_controller.rb new file mode 100644 index 000000000..9f24d3a90 --- /dev/null +++ b/app/controllers/officing/residence_controller.rb @@ -0,0 +1,37 @@ +class Officing::ResidenceController < Officing::BaseController + + before_action :load_officer_assignment + before_action :validate_officer_assignment, only: :create + + def new + @residence = Officing::Residence.new + end + + def create + @residence = Officing::Residence.new(residence_params.merge(officer: current_user.poll_officer)) + if @residence.save + redirect_to new_officing_voter_path(id: @residence.user.id), notice: t("officing.residence.flash.create") + else + render :new + end + end + + private + + def residence_params + params.require(:residence).permit(:document_number, :document_type, :year_of_birth) + end + + def load_officer_assignment + @officer_assignments = current_user.poll_officer. + officer_assignments. + voting_days. + where(date: Time.current.to_date) + end + + def validate_officer_assignment + if @officer_assignments.blank? + redirect_to officing_root_path, notice: t("officing.residence.flash.not_allowed") + end + end +end diff --git a/app/controllers/officing/results_controller.rb b/app/controllers/officing/results_controller.rb new file mode 100644 index 000000000..b6377e25b --- /dev/null +++ b/app/controllers/officing/results_controller.rb @@ -0,0 +1,141 @@ +class Officing::ResultsController < Officing::BaseController + before_action :load_poll + + before_action :load_officer_assignments, only: :new + before_action :load_partial_results, only: :new + + before_action :load_officer_assignment, only: :create + before_action :check_booth_and_date, only: :create + before_action :build_results, only: :create + + def new + end + + def create + @results.each { |result| result.save! } + + notice = t("officing.results.flash.create") + redirect_to new_officing_poll_result_path(@poll), notice: notice + end + + def index + @booth_assignment = ::Poll::BoothAssignment.includes(:booth).find(index_params[:booth_assignment_id]) + if current_user.poll_officer.officer_assignments.final. + where(booth_assignment_id: @booth_assignment.id).exists? + + @partial_results = ::Poll::PartialResult.includes(:question). + where(booth_assignment_id: index_params[:booth_assignment_id]). + where(date: index_params[:date]) + @whites = ::Poll::WhiteResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount) + @nulls = ::Poll::NullResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount) + end + end + + private + + def check_booth_and_date + if @officer_assignment.blank? + go_back_to_new(t("officing.results.flash.error_wrong_booth")) + elsif results_params[:date].blank? || + Date.parse(results_params[:date]) < @poll.starts_at.to_date || + Date.parse(results_params[:date]) > @poll.ends_at.to_date + go_back_to_new(t("officing.results.flash.error_wrong_date")) + end + end + + def build_results + @results = [] + + params[:questions].each_pair do |question_id, results| + question = @poll.questions.find(question_id) + go_back_to_new if question.blank? + + results.each_pair do |answer_index, count| + if count.present? + answer = question.valid_answers[answer_index.to_i] + go_back_to_new if question.blank? + + partial_result = ::Poll::PartialResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, + date: results_params[:date], + question_id: question_id, + answer: answer) + partial_result.officer_assignment_id = @officer_assignment.id + partial_result.amount = count.to_i + partial_result.author = current_user + partial_result.origin = 'booth' + @results << partial_result + end + end + end + + build_white_results + build_null_results + end + + def build_white_results + if results_params[:whites].present? + white_result = ::Poll::WhiteResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, + date: results_params[:date]) + white_result.officer_assignment_id = @officer_assignment.id + white_result.amount = results_params[:whites].to_i + white_result.author = current_user + white_result.origin = 'booth' + @results << white_result + end + end + + def build_null_results + if results_params[:nulls].present? + null_result = ::Poll::NullResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, + date: results_params[:date]) + null_result.officer_assignment_id = @officer_assignment.id + null_result.amount = results_params[:nulls].to_i + null_result.author = current_user + null_result.origin = 'booth' + @results << null_result + end + end + + def go_back_to_new(alert = nil) + params[:d] = results_params[:date] + params[:oa] = results_params[:officer_assignment_id] + flash.now[:alert] = (alert || t("officing.results.flash.error_create")) + load_officer_assignments + load_partial_results + render :new + end + + def load_poll + @poll = ::Poll.expired.includes(:questions).find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.final.find_by(id: results_params[:officer_assignment_id]) + end + + def load_officer_assignments + @officer_assignments = ::Poll::OfficerAssignment. + includes(booth_assignment: [:booth]). + joins(:booth_assignment). + final. + where(id: current_user.poll_officer.officer_assignment_ids). + where("poll_booth_assignments.poll_id = ?", @poll.id). + order(date: :asc) + end + + def load_partial_results + if @officer_assignments.present? + @partial_results = ::Poll::PartialResult.where(officer_assignment_id: @officer_assignments.map(&:id)).order(:booth_assignment_id, :date) + end + end + + def results_params + params.permit(:officer_assignment_id, :date, :questions, :whites, :nulls) + end + + def index_params + params.permit(:booth_assignment_id, :date) + end + +end diff --git a/app/controllers/officing/voters_controller.rb b/app/controllers/officing/voters_controller.rb new file mode 100644 index 000000000..dee1e00bd --- /dev/null +++ b/app/controllers/officing/voters_controller.rb @@ -0,0 +1,25 @@ +class Officing::VotersController < Officing::BaseController + respond_to :html, :js + + def new + @user = User.find(params[:id]) + @polls = Poll.answerable_by(@user) + end + + def create + @poll = Poll.find(voter_params[:poll_id]) + @user = User.find(voter_params[:user_id]) + @voter = Poll::Voter.new(document_type: @user.document_type, + document_number: @user.document_number, + user: @user, + poll: @poll) + @voter.save! + end + + private + + def voter_params + params.require(:voter).permit(:poll_id, :user_id) + end + +end diff --git a/app/controllers/organizations/registrations_controller.rb b/app/controllers/organizations/registrations_controller.rb index c0f69da33..a77534d8c 100644 --- a/app/controllers/organizations/registrations_controller.rb +++ b/app/controllers/organizations/registrations_controller.rb @@ -1,5 +1,7 @@ class Organizations::RegistrationsController < Devise::RegistrationsController + invisible_captcha only: [:create], honeypot: :address, scope: :user + def new super do |user| user.build_organization @@ -11,7 +13,7 @@ class Organizations::RegistrationsController < Devise::RegistrationsController def create build_resource(sign_up_params) - if resource.valid_with_captcha? + if resource.valid? super do |user| # Removes unuseful "organization is invalid" error message user.errors.messages.delete(:organization) @@ -22,6 +24,7 @@ class Organizations::RegistrationsController < Devise::RegistrationsController end protected + def after_inactive_sign_up_path_for(resource) organizations_sign_up_success_path end @@ -29,7 +32,7 @@ class Organizations::RegistrationsController < Devise::RegistrationsController private def sign_up_params - params.require(:user).permit(:email, :password, :phone_number, :password_confirmation, :captcha, :captcha_key, :terms_of_service, organization_attributes: [:name, :responsible_name]) + params.require(:user).permit(:email, :password, :phone_number, :password_confirmation, :terms_of_service, organization_attributes: [:name, :responsible_name]) end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index 03f62925a..81d6e8dbd 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -2,7 +2,11 @@ class PagesController < ApplicationController skip_authorization_check def show - render action: params[:id] + if @custom_page = SiteCustomization::Page.published.find_by(slug: params[:id]) + render action: :custom_page + else + render action: params[:id] + end rescue ActionView::MissingTemplate head 404 end diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb new file mode 100644 index 000000000..1849dff97 --- /dev/null +++ b/app/controllers/polls/questions_controller.rb @@ -0,0 +1,27 @@ +class Polls::QuestionsController < ApplicationController + + load_and_authorize_resource :poll + load_and_authorize_resource :question, class: 'Poll::Question' + + has_orders %w{most_voted newest oldest}, only: :show + + def show + @commentable = @question.proposal.present? ? @question.proposal : @question + @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + set_comment_flags(@comment_tree.comments) + + question_answer = @question.answers.where(author_id: current_user.try(:id)).first + @answers_by_question_id = {@question.id => question_answer.try(:answer)} + end + + def answer + answer = @question.answers.find_or_initialize_by(author: current_user) + + answer.answer = params[:answer] + answer.save! + answer.record_voter_participation + + @answers_by_question_id = {@question.id => params[:answer]} + end + +end diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb new file mode 100644 index 000000000..41a038b46 --- /dev/null +++ b/app/controllers/polls_controller.rb @@ -0,0 +1,23 @@ +class PollsController < ApplicationController + + load_and_authorize_resource + + has_filters %w{current expired incoming} + + ::Poll::Answer # trigger autoload + + def index + @polls = @polls.send(@current_filter).includes(:geozones).sort_for_list.page(params[:page]) + end + + def show + @questions = @poll.questions.for_render.sort_for_list + + @answers_by_question_id = {} + poll_answers = ::Poll::Answer.by_question(@poll.question_ids).by_author(current_user.try(:id)) + poll_answers.each do |answer| + @answers_by_question_id[answer.question_id] = answer.answer + end + end + +end diff --git a/app/controllers/proposal_notifications_controller.rb b/app/controllers/proposal_notifications_controller.rb new file mode 100644 index 000000000..36e265e38 --- /dev/null +++ b/app/controllers/proposal_notifications_controller.rb @@ -0,0 +1,33 @@ +class ProposalNotificationsController < ApplicationController + load_and_authorize_resource except: [:new] + + def new + @proposal = Proposal.find(params[:proposal_id]) + @notification = ProposalNotification.new(proposal_id: @proposal.id) + authorize! :new, @notification + end + + def create + @notification = ProposalNotification.new(proposal_notification_params) + @proposal = Proposal.find(proposal_notification_params[:proposal_id]) + if @notification.save + @proposal.voters.each do |voter| + Notification.add(voter.id, @notification) + end + redirect_to @notification, notice: I18n.t("flash.actions.create.proposal_notification") + else + render :new + end + end + + def show + @notification = ProposalNotification.find(params[:id]) + end + + private + + def proposal_notification_params + params.require(:proposal_notification).permit(:title, :body, :proposal_id) + end + +end \ No newline at end of file diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index add59a7cb..d1707a8a7 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -2,15 +2,14 @@ class ProposalsController < ApplicationController include CommentableActions include FlagActions - before_action :parse_search_terms, only: [:index, :suggest] - before_action :parse_advanced_search_terms, only: :index before_action :parse_tag_filter, only: :index - before_action :set_search_order, only: :index before_action :load_categories, only: [:index, :new, :edit, :map, :summary] before_action :load_geozones, only: [:edit, :map, :summary] before_action :authenticate_user!, except: [:index, :show, :map, :summary] - has_orders %w{hot_score confidence_score created_at relevance}, only: :index + invisible_captcha only: [:create, :update], honeypot: :subtitle + + has_orders %w{hot_score confidence_score created_at relevance archival_date}, only: :index has_orders %w{most_voted newest oldest}, only: :show load_and_authorize_resource @@ -19,15 +18,15 @@ class ProposalsController < ApplicationController def show super + @notifications = @proposal.notifications redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal) end def index_customization - @featured_proposals = Proposal.all.sort_by_confidence_score.limit(3) if (!@advanced_search_terms && @search_terms.blank? && @tag_filter.blank?) - if @featured_proposals.present? - set_featured_proposal_votes(@featured_proposals) - @resources = @resources.where('proposals.id NOT IN (?)', @featured_proposals.map(&:id)) - end + discard_archived + load_retired + load_successful_proposals + load_featured unless @proposal_successful_exists end def vote @@ -35,6 +34,17 @@ class ProposalsController < ApplicationController set_proposal_votes(@proposal) end + def retire + if valid_retired_params? && @proposal.update(retired_params.merge(retired_at: Time.current)) + redirect_to proposal_path(@proposal), notice: t('proposals.notice.retired') + else + render action: :retire_form + end + end + + def retire_form + end + def vote_featured @proposal.register_vote(current_user, 'yes') set_featured_proposal_votes(@proposal) @@ -48,7 +58,17 @@ class ProposalsController < ApplicationController private def proposal_params - params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url, :responsible_name, :tag_list, :terms_of_service, :captcha, :captcha_key, :geozone_id) + params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url, :responsible_name, :tag_list, :terms_of_service, :geozone_id) + end + + def retired_params + params.require(:proposal).permit(:retired_reason, :retired_explanation) + end + + def valid_retired_params? + @proposal.errors.add(:retired_reason, I18n.t('errors.messages.blank')) if params[:proposal][:retired_reason].blank? + @proposal.errors.add(:retired_explanation, I18n.t('errors.messages.blank')) if params[:proposal][:retired_explanation].blank? + @proposal.errors.empty? end def resource_model @@ -59,4 +79,29 @@ class ProposalsController < ApplicationController @featured_proposals_votes = current_user ? current_user.proposal_votes(proposals) : {} end + def discard_archived + @resources = @resources.not_archived unless @current_order == "archival_date" + end + + def load_retired + if params[:retired].present? + @resources = @resources.retired + @resources = @resources.where(retired_reason: params[:retired]) if Proposal::RETIRE_OPTIONS.include?(params[:retired]) + else + @resources = @resources.not_retired + end + end + + def load_featured + @featured_proposals = Proposal.not_archived.sort_by_confidence_score.limit(3) if (!@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?) + if @featured_proposals.present? + set_featured_proposal_votes(@featured_proposals) + @resources = @resources.where('proposals.id NOT IN (?)', @featured_proposals.map(&:id)) + end + end + + def load_successful_proposals + @proposal_successful_exists = Proposal.successful.exists? + end + end diff --git a/app/controllers/spending_proposals_controller.rb b/app/controllers/spending_proposals_controller.rb index 9915b0b50..5f8b04717 100644 --- a/app/controllers/spending_proposals_controller.rb +++ b/app/controllers/spending_proposals_controller.rb @@ -1,13 +1,15 @@ class SpendingProposalsController < ApplicationController include FeatureFlags - before_action :authenticate_user!, except: [:index] + before_action :authenticate_user!, except: [:index, :show] before_action -> { flash.now[:notice] = flash[:notice].html_safe if flash[:html_safe] && flash[:notice] } load_and_authorize_resource feature_flag :spending_proposals + invisible_captcha only: [:create, :update], honeypot: :subtitle + respond_to :html, :js def index @@ -27,7 +29,7 @@ class SpendingProposalsController < ApplicationController @spending_proposal = SpendingProposal.new(spending_proposal_params) @spending_proposal.author = current_user - if @spending_proposal.save_with_captcha + if @spending_proposal.save notice = t('flash.actions.create.spending_proposal', activity: "#{t('layouts.header.my_activity_link')}") redirect_to @spending_proposal, notice: notice, flash: { html_safe: true } else @@ -49,7 +51,7 @@ class SpendingProposalsController < ApplicationController private def spending_proposal_params - params.require(:spending_proposal).permit(:title, :description, :external_url, :geozone_id, :association_name, :terms_of_service, :captcha, :captcha_key) + params.require(:spending_proposal).permit(:title, :description, :external_url, :geozone_id, :association_name, :terms_of_service) end def set_geozone_name diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index d600e2e9c..ab6bedd6e 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -23,6 +23,6 @@ class StatsController < ApplicationController private def daily_cache(key, &block) - Rails.cache.fetch("public_stats/#{Time.now.strftime("%Y-%m-%d")}/#{key}", &block) + Rails.cache.fetch("public_stats/#{Time.current.strftime("%Y-%m-%d")}/#{key}", &block) end end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 0bdd2a801..cbe43390b 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -41,9 +41,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController end def save_user(user) - @user.save || - @user.save_requiring_finish_signup || - @user.save_requiring_finish_signup_without_email + @user.save || @user.save_requiring_finish_signup end -end +end \ No newline at end of file diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index 9d6530017..2b25a5126 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -1,6 +1,8 @@ class Users::RegistrationsController < Devise::RegistrationsController prepend_before_action :authenticate_scope!, only: [:edit, :update, :destroy, :finish_signup, :do_finish_signup] + invisible_captcha only: [:create], honeypot: :family_name, scope: :user + def new super do |user| user.use_redeemable_code = true if params[:use_redeemable_code].present? @@ -9,7 +11,7 @@ class Users::RegistrationsController < Devise::RegistrationsController def create build_resource(sign_up_params) - if resource.valid_with_captcha? + if resource.valid? super else render :new @@ -58,8 +60,7 @@ class Users::RegistrationsController < Devise::RegistrationsController def sign_up_params params[:user].delete(:redeemable_code) if params[:user].present? && params[:user][:redeemable_code].blank? params.require(:user).permit(:username, :email, :password, - :password_confirmation, :captcha, - :captcha_key, :terms_of_service, :locale, + :password_confirmation, :terms_of_service, :locale, :redeemable_code) end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1324fbdfa..9bd98aedd 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,8 +1,9 @@ class UsersController < ApplicationController - has_filters %w{proposals debates comments spending_proposals}, only: :show + has_filters %w{proposals debates budget_investments comments}, only: :show load_and_authorize_resource - helper_method :authorized_for_filter? + helper_method :author? + helper_method :author_or_admin? def show load_filtered_activity if valid_access? @@ -12,9 +13,9 @@ class UsersController < ApplicationController def set_activity_counts @activity_counts = HashWithIndifferentAccess.new( proposals: Proposal.where(author_id: @user.id).count, - debates: Debate.where(author_id: @user.id).count, - comments: Comment.not_as_admin_or_moderator.where(user_id: @user.id).count, - spending_proposals: SpendingProposal.where(author_id: @user.id).count) + debates: (Setting['feature.debates'] ? Debate.where(author_id: @user.id).count : 0), + budget_investments: (Setting['feature.budgets'] ? Budget::Investment.where(author_id: @user.id).count : 0), + comments: only_active_commentables.count) end def load_filtered_activity @@ -22,8 +23,8 @@ class UsersController < ApplicationController case params[:filter] when "proposals" then load_proposals when "debates" then load_debates + when "budget_investments" then load_budget_investments when "comments" then load_comments - when "spending_proposals" then load_spending_proposals if author_or_admin? else load_available_activity end end @@ -32,15 +33,15 @@ class UsersController < ApplicationController if @activity_counts[:proposals] > 0 load_proposals @current_filter = "proposals" - elsif @activity_counts[:debates] > 0 + elsif @activity_counts[:debates] > 0 load_debates @current_filter = "debates" + elsif @activity_counts[:budget_investments] > 0 + load_budget_investments + @current_filter = "budget_investments" elsif @activity_counts[:comments] > 0 load_comments @current_filter = "comments" - elsif @activity_counts[:spending_proposals] > 0 && author_or_admin? - load_spending_proposals - @current_filter = "spending_proposals" end end @@ -53,26 +54,42 @@ class UsersController < ApplicationController end def load_comments - @comments = Comment.not_as_admin_or_moderator.where(user_id: @user.id).includes(:commentable).order(created_at: :desc).page(params[:page]) + @comments = only_active_commentables.includes(:commentable).order(created_at: :desc).page(params[:page]) end - def load_spending_proposals - @spending_proposals = SpendingProposal.where(author_id: @user.id).order(created_at: :desc).page(params[:page]) + def load_budget_investments + @budget_investments = Budget::Investment.where(author_id: @user.id).order(created_at: :desc).page(params[:page]) end def valid_access? @user.public_activity || authorized_current_user? end + def author? + @author ||= current_user && (current_user == @user) + end + def author_or_admin? - @author_or_admin ||= current_user && (current_user == @user || current_user.administrator?) + @author_or_admin ||= current_user && (author? || current_user.administrator?) end def authorized_current_user? @authorized_current_user ||= current_user && (current_user == @user || current_user.moderator? || current_user.administrator?) end - def authorized_for_filter?(filter) - filter == "spending_proposals" ? author_or_admin? : true + def all_user_comments + Comment.not_as_admin_or_moderator.where(user_id: @user.id) end + + def only_active_commentables + disabled_commentables = [] + disabled_commentables << "Debate" unless Setting['feature.debates'] + disabled_commentables << "Budget::Investment" unless Setting['feature.budgets'] + if disabled_commentables.present? + all_user_comments.where("commentable_type NOT IN (?)", disabled_commentables) + else + all_user_comments + end + end + end diff --git a/app/controllers/valuation/budget_investments_controller.rb b/app/controllers/valuation/budget_investments_controller.rb new file mode 100644 index 000000000..ae747464e --- /dev/null +++ b/app/controllers/valuation/budget_investments_controller.rb @@ -0,0 +1,85 @@ +class Valuation::BudgetInvestmentsController < Valuation::BaseController + include FeatureFlags + feature_flag :budgets + + before_action :restrict_access_to_assigned_items, only: [:show, :edit, :valuate] + before_action :load_budget + before_action :load_investment, only: [:show, :edit, :valuate] + + has_filters %w{valuating valuation_finished}, only: :index + + load_and_authorize_resource :investment, class: "Budget::Investment" + + def index + @heading_filters = heading_filters + if current_user.valuator? && @budget.present? + @investments = @budget.investments.scoped_filter(params_for_current_valuator, @current_filter).order(cached_votes_up: :desc).page(params[:page]) + else + @investments = Budget::Investment.none.page(params[:page]) + end + end + + def valuate + if valid_price_params? && @investment.update(valuation_params) + + if @investment.unfeasible_email_pending? + @investment.send_unfeasible_email + end + + Activity.log(current_user, :valuate, @investment) + redirect_to valuation_budget_budget_investment_path(@budget, @investment), notice: t('valuation.budget_investments.notice.valuate') + else + render action: :edit + end + end + + private + + def load_budget + @budget = Budget.find(params[:budget_id]) + end + + def load_investment + @investment = @budget.investments.find params[:id] + end + + def heading_filters + investments = @budget.investments.by_valuator(current_user.valuator.try(:id)).valuation_open.select(:heading_id).all.to_a + + [ { name: t('valuation.budget_investments.index.headings_filter_all'), + id: nil, + pending_count: investments.size + } + ] + Budget::Heading.where(id: investments.map(&:heading_id).uniq).order(name: :asc).collect do |h| + { name: h.name, + id: h.id, + pending_count: investments.count{|x| x.heading_id == h.id} + } + end + end + + def params_for_current_valuator + Budget::Investment.filter_params(params).merge({valuator_id: current_user.valuator.id, budget_id: @budget.id}) + end + + def valuation_params + params.require(:budget_investment).permit(:price, :price_first_year, :price_explanation, :feasibility, :unfeasibility_explanation, :duration, :valuation_finished, :internal_comments) + end + + def restrict_access_to_assigned_items + raise ActionController::RoutingError.new('Not Found') unless current_user.administrator? || Budget::ValuatorAssignment.exists?(investment_id: params[:id], valuator_id: current_user.valuator.id) + end + + def valid_price_params? + if /\D/.match params[:budget_investment][:price] + @investment.errors.add(:price, I18n.t('budgets.investments.wrong_price_format')) + end + + if /\D/.match params[:budget_investment][:price_first_year] + @investment.errors.add(:price_first_year, I18n.t('budgets.investments.wrong_price_format')) + end + + @investment.errors.empty? + end + +end diff --git a/app/controllers/valuation/budgets_controller.rb b/app/controllers/valuation/budgets_controller.rb new file mode 100644 index 000000000..744b0d4bf --- /dev/null +++ b/app/controllers/valuation/budgets_controller.rb @@ -0,0 +1,17 @@ +class Valuation::BudgetsController < Valuation::BaseController + include FeatureFlags + feature_flag :budgets + + load_and_authorize_resource + + def index + @budgets = @budgets.current.order(created_at: :desc).page(params[:page]) + @investments_with_valuation_open = {} + @budgets.each do |b| + @investments_with_valuation_open[b.id] = b.investments + .by_valuator(current_user.valuator.try(:id)) + .valuation_open + .count + end + end +end diff --git a/app/controllers/valuation/spending_proposals_controller.rb b/app/controllers/valuation/spending_proposals_controller.rb index c5f822b40..623c9ac46 100644 --- a/app/controllers/valuation/spending_proposals_controller.rb +++ b/app/controllers/valuation/spending_proposals_controller.rb @@ -9,8 +9,9 @@ class Valuation::SpendingProposalsController < Valuation::BaseController load_and_authorize_resource def index + @geozone_filters = geozone_filters if current_user.valuator? - @spending_proposals = SpendingProposal.scoped_filter(params_for_current_valuator, @current_filter).order(created_at: :desc).page(params[:page]) + @spending_proposals = SpendingProposal.scoped_filter(params_for_current_valuator, @current_filter).order(cached_votes_up: :desc).page(params[:page]) else @spending_proposals = SpendingProposal.none.page(params[:page]) end @@ -31,6 +32,25 @@ class Valuation::SpendingProposalsController < Valuation::BaseController private + def geozone_filters + spending_proposals = SpendingProposal.by_valuator(current_user.valuator.try(:id)).valuation_open.all.to_a + + [ { name: t('valuation.spending_proposals.index.geozone_filter_all'), + id: nil, + pending_count: spending_proposals.size + }, + { name: t('geozones.none'), + id: 'all', + pending_count: spending_proposals.count{|x| x.geozone_id.nil?} + } + ] + Geozone.all.order(name: :asc).collect do |g| + { name: g.name, + id: g.id, + pending_count: spending_proposals.count{|x| x.geozone_id == g.id} + } + end.select{ |x| x[:pending_count] > 0 } + end + def valuation_params params[:spending_proposal][:feasible] = nil if params[:spending_proposal][:feasible] == 'nil' @@ -38,7 +58,7 @@ class Valuation::SpendingProposalsController < Valuation::BaseController end def params_for_current_valuator - params.merge({valuator_id: current_user.valuator.id}) + params.merge({valuator_id: current_user.valuator.id}) end def restrict_access_to_assigned_items @@ -57,4 +77,4 @@ class Valuation::SpendingProposalsController < Valuation::BaseController @spending_proposal.errors.empty? end -end +end \ No newline at end of file diff --git a/app/controllers/verification/email_controller.rb b/app/controllers/verification/email_controller.rb index 9f8fd9f8a..1774197d9 100644 --- a/app/controllers/verification/email_controller.rb +++ b/app/controllers/verification/email_controller.rb @@ -6,7 +6,7 @@ class Verification::EmailController < ApplicationController def show if Verification::Email.find(current_user, params[:email_verification_token]) - current_user.update(verified_at: Time.now) + current_user.update(verified_at: Time.current) redirect_to account_path, notice: t('verification.email.show.flash.success') else redirect_to verified_user_path, alert: t('verification.email.show.alert.failure') diff --git a/app/controllers/verification/letter_controller.rb b/app/controllers/verification/letter_controller.rb index bf5033b32..0e726a683 100644 --- a/app/controllers/verification/letter_controller.rb +++ b/app/controllers/verification/letter_controller.rb @@ -29,7 +29,7 @@ class Verification::LetterController < ApplicationController def update @letter = Verification::Letter.new(letter_params.merge(user: current_user, verify: true)) if @letter.valid? - current_user.update(verified_at: Time.now) + current_user.update(verified_at: Time.current) redirect_to account_path, notice: t('verification.letter.update.flash.success') else Lock.increase_tries(@letter.user) if @letter.user @@ -56,5 +56,4 @@ class Verification::LetterController < ApplicationController end end - end diff --git a/app/controllers/verification/sms_controller.rb b/app/controllers/verification/sms_controller.rb index 1cb5b2d8e..f06af5e6a 100644 --- a/app/controllers/verification/sms_controller.rb +++ b/app/controllers/verification/sms_controller.rb @@ -31,7 +31,7 @@ class Verification::SmsController < ApplicationController ahoy.track(:level_2_user, user_id: current_user.id) rescue nil if VerifiedUser.phone?(current_user) - current_user.update(verified_at: Time.now) + current_user.update(verified_at: Time.current) end redirect_to_next_path @@ -69,4 +69,4 @@ class Verification::SmsController < ApplicationController end end -end \ No newline at end of file +end diff --git a/app/controllers/verification/verified_user_controller.rb b/app/controllers/verification/verified_user_controller.rb index 23045e451..e637bb232 100644 --- a/app/controllers/verification/verified_user_controller.rb +++ b/app/controllers/verification/verified_user_controller.rb @@ -9,6 +9,7 @@ class Verification::VerifiedUserController < ApplicationController end private + def user_data_present? return false if @verified_users.blank? diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb index fc43f5357..d0c650fa3 100644 --- a/app/controllers/welcome_controller.rb +++ b/app/controllers/welcome_controller.rb @@ -16,5 +16,4 @@ class WelcomeController < ApplicationController redirect_to verification_path if signed_in? end - end diff --git a/app/helpers/accessibility_helper.rb b/app/helpers/accessibility_helper.rb new file mode 100644 index 000000000..a224f9a9b --- /dev/null +++ b/app/helpers/accessibility_helper.rb @@ -0,0 +1,7 @@ +module AccessibilityHelper + + def css_for_aria_hidden(reason) + reason.present? ? "true" : "" + end + +end diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 515a54deb..3f0d4db4b 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -4,8 +4,40 @@ module AdminHelper render "/#{namespace}/menu" end + def namespaced_root_path + "/#{namespace}" + end + + def namespaced_header_title + t("#{namespace}.header.title") + end + + def menu_tags? + ["tags"].include? controller_name + end + + def menu_moderated_content? + ["proposals", "debates", "comments", "users"].include? controller_name + end + + def menu_budget? + ["spending_proposals"].include? controller_name + end + + def menu_polls? + ["polls", "questions", "officers", "booths", "officer_assignments", "booth_assignments", "recounts", "results"].include? controller_name + end + + def menu_profiles? + ["organizations", "officials", "moderators", "valuators", "managers"].include? controller_name + end + + def menu_banners? + ["banners"].include? controller_name + end + def official_level_options - options = [["",0]] + options = [["", 0]] (1..5).each do |i| options << [[t("admin.officials.level_#{i}"), setting["official_level_#{i}_name"]].compact.join(': '), i] end @@ -16,10 +48,14 @@ module AdminHelper Administrator.all.order('users.username asc').includes(:user).collect { |v| [ v.name, v.id ] } end + def admin_submit_action(resource) + resource.persisted? ? "edit" : "new" + end + private def namespace - controller.class.parent.name.downcase + controller.class.parent.name.downcase.gsub("::", "/") end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c5f2e6aca..6a040ec69 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -35,4 +35,28 @@ module ApplicationHelper } Redcarpet::Markdown.new(renderer, extensions).render(text).html_safe end + + def author_of?(authorable, user) + return false if authorable.blank? || user.blank? + authorable.author_id == user.id + end + + def back_link_to(destination_path) + destination = destination_path || :back + link_to destination, class: "back" do + "".html_safe + t("shared.back") + end + end + + def image_path_for(filename) + SiteCustomization::Image.image_path_for(filename) || filename + end + + def content_block(name, locale) + SiteCustomization::ContentBlock.block_for(name, locale) + end + + def format_price(number) + number_to_currency(number, precision: 0, locale: I18n.default_locale) + end end diff --git a/app/helpers/ballots_helper.rb b/app/helpers/ballots_helper.rb new file mode 100644 index 000000000..ece1be9fa --- /dev/null +++ b/app/helpers/ballots_helper.rb @@ -0,0 +1,7 @@ +module BallotsHelper + + def progress_bar_width(amount_available, amount_spent) + (amount_spent/amount_available.to_f * 100).to_s + "%" + end + +end \ No newline at end of file diff --git a/app/helpers/banners_helper.rb b/app/helpers/banners_helper.rb new file mode 100644 index 000000000..b8b5864c9 --- /dev/null +++ b/app/helpers/banners_helper.rb @@ -0,0 +1,7 @@ +module BannersHelper + + def has_banners? + @banners.present? && @banners.count > 0 + end + +end \ No newline at end of file diff --git a/app/helpers/budget_headings_helper.rb b/app/helpers/budget_headings_helper.rb new file mode 100644 index 000000000..3fa1eac89 --- /dev/null +++ b/app/helpers/budget_headings_helper.rb @@ -0,0 +1,9 @@ +module BudgetHeadingsHelper + + def budget_heading_select_options(budget) + budget.headings.order_by_group_name.map do |heading| + [heading.name_scoped_by_group, heading.id] + end + end + +end diff --git a/app/helpers/budgets_helper.rb b/app/helpers/budgets_helper.rb new file mode 100644 index 000000000..3a07f0393 --- /dev/null +++ b/app/helpers/budgets_helper.rb @@ -0,0 +1,45 @@ +module BudgetsHelper + + def budget_phases_select_options + Budget::PHASES.map { |ph| [ t("budgets.phase.#{ph}"), ph ] } + end + + def budget_currency_symbol_select_options + Budget::CURRENCY_SYMBOLS.map { |cs| [ cs, cs ] } + end + + def namespaced_budget_investment_path(investment, options={}) + case namespace + when "management/budgets" + management_budget_investment_path(investment.budget, investment, options) + else + budget_investment_path(investment.budget, investment, options) + end + end + + def namespaced_budget_investment_vote_path(investment, options={}) + case namespace + when "management/budgets" + vote_management_budget_investment_path(investment.budget, investment, options) + else + vote_budget_investment_path(investment.budget, investment, options) + end + end + + def display_budget_countdown?(budget) + budget.balloting? + end + + def css_for_ballot_heading(heading) + return '' unless current_ballot.present? + current_ballot.has_lines_in_heading?(heading) ? 'active' : '' + end + + def current_ballot + Budget::Ballot.where(user: current_user, budget: @budget).first + end + + def investment_tags_select_options + Budget::Investment.tags_on(:valuation).order(:name).select(:name).distinct + end +end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index 9d9d08075..df6e57b61 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -20,6 +20,16 @@ module CommentsHelper end end + def commentable_path(comment) + if comment.commentable_type == "Budget::Investment" + budget_investment_path(comment.commentable.budget_id, comment.commentable) + elsif comment.commentable_type == "Poll::Question" + question_path(comment.commentable) + else + comment.commentable + end + end + def user_level_class(comment) if comment.as_administrator? "is-admin" @@ -39,4 +49,5 @@ module CommentsHelper "" # Default not author class end end -end + +end \ No newline at end of file diff --git a/app/helpers/debates_helper.rb b/app/helpers/debates_helper.rb new file mode 100644 index 000000000..8db989f61 --- /dev/null +++ b/app/helpers/debates_helper.rb @@ -0,0 +1,7 @@ +module DebatesHelper + + def has_featured? + Debate.all.featured.count > 0 + end + +end \ No newline at end of file diff --git a/app/helpers/embed_videos_helper.rb b/app/helpers/embed_videos_helper.rb index b53aeef21..22b1d878f 100644 --- a/app/helpers/embed_videos_helper.rb +++ b/app/helpers/embed_videos_helper.rb @@ -1,30 +1,31 @@ module EmbedVideosHelper def embedded_video_code - link = @proposal.video_url + link = @proposal.video_url + title = t('proposals.show.embed_video_title', proposal: @proposal.title) if link.match(/vimeo.*/) server = "Vimeo" elsif link.match(/youtu*.*/) server = "YouTube" - end - + end + if server == "Vimeo" - regExp = /vimeo.*(staffpicks\/|channels\/|videos\/|video\/|\/)([^#\&\?]*).*/ + reg_exp = /vimeo.*(staffpicks\/|channels\/|videos\/|video\/|\/)([^#\&\?]*).*/ src = "https://player.vimeo.com/video/" elsif server == "YouTube" - regExp = /youtu.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ - src = "https://www.youtube.com/embed/" + reg_exp = /youtu.*(be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/ + src = "https://www.youtube.com/embed/" end - - if regExp - match = link.match(regExp) + + if reg_exp + match = link.match(reg_exp) end if match and match[2] - '' - else + '' + else '' end - end + end end \ No newline at end of file diff --git a/app/helpers/flags_helper.rb b/app/helpers/flags_helper.rb index b5ba67f41..715937c0f 100644 --- a/app/helpers/flags_helper.rb +++ b/app/helpers/flags_helper.rb @@ -26,4 +26,4 @@ module FlagsHelper end end -end +end \ No newline at end of file diff --git a/app/helpers/geozones_helper.rb b/app/helpers/geozones_helper.rb index bfc5f9105..ce03e0579 100644 --- a/app/helpers/geozones_helper.rb +++ b/app/helpers/geozones_helper.rb @@ -8,4 +8,9 @@ module GeozonesHelper Geozone.all.order(name: :asc).collect { |g| [ g.name, g.id ] } end + def geozone_name_from_id(g_id) + @all_geozones ||= Geozone.all.collect{ |g| [ g.id, g.name ] }.to_h + @all_geozones[g_id] || t("geozones.none") + end + end diff --git a/app/helpers/layouts_helper.rb b/app/helpers/layouts_helper.rb new file mode 100644 index 000000000..c92f57898 --- /dev/null +++ b/app/helpers/layouts_helper.rb @@ -0,0 +1,12 @@ +module LayoutsHelper + + def layout_menu_link_to(text, path, is_active, options) + if is_active + content_tag(:span, t('shared.you_are_in'), class: 'sr-only') + ' ' + + link_to(text, path, options.merge(class: "active")) + else + link_to(text, path, options) + end + end + +end diff --git a/app/helpers/locales_helper.rb b/app/helpers/locales_helper.rb new file mode 100644 index 000000000..717be9e9b --- /dev/null +++ b/app/helpers/locales_helper.rb @@ -0,0 +1,8 @@ +module LocalesHelper + + def name_for_locale(locale) + default = I18n.t("locale", locale: locale) + I18n.backend.translate(locale, "i18n.language.name", default: default) + end + +end \ No newline at end of file diff --git a/app/helpers/mailer_helper.rb b/app/helpers/mailer_helper.rb index ad6f042af..e541eebb6 100644 --- a/app/helpers/mailer_helper.rb +++ b/app/helpers/mailer_helper.rb @@ -3,6 +3,7 @@ module MailerHelper def commentable_url(commentable) return debate_url(commentable) if commentable.is_a?(Debate) return proposal_url(commentable) if commentable.is_a?(Proposal) + return budget_investment_url(commentable.budget_id, commentable) if commentable.is_a?(Budget::Investment) end end \ No newline at end of file diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 281163380..7342393a7 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -1,6 +1,2 @@ module NotificationsHelper - - def notification_action(notification) - notification.notifiable_type == "Comment" ? "replies_to" : "comments_on" - end end diff --git a/app/helpers/officers_helper.rb b/app/helpers/officers_helper.rb new file mode 100644 index 000000000..f29bcf728 --- /dev/null +++ b/app/helpers/officers_helper.rb @@ -0,0 +1,7 @@ +module OfficersHelper + + def officer_label(officer) + truncate([officer.name, officer.email].compact.join(' - '), length: 100) + end + +end \ No newline at end of file diff --git a/app/helpers/officing_helper.rb b/app/helpers/officing_helper.rb new file mode 100644 index 000000000..2cfebf832 --- /dev/null +++ b/app/helpers/officing_helper.rb @@ -0,0 +1,36 @@ +module OfficingHelper + + def officer_assignments_select_options(officer_assignments) + options = [] + officer_assignments.each do |oa| + options << ["#{oa.booth_assignment.booth.name}: #{l(oa.date.to_date, format: :long)}", oa.id] + end + options_for_select(options) + end + + def booths_for_officer_select_options(officer_assignments) + options = [] + officer_assignments.each do |oa| + options << ["#{oa.booth_assignment.booth.name}", oa.id] + end + options.sort! {|x,y| x[0]<=>y[0]} + options_for_select(options, params[:oa]) + end + + def recount_to_compare_with_final_recount(final_recount) + recount = final_recount.booth_assignment.recounts.select {|r| r.date == final_recount.date}.first + recount.present? ? recount.count : "-" + end + + def system_recount_to_compare_with_final_recount(final_recount) + final_recount.booth_assignment.voters.select {|v| v.created_at.to_date == final_recount.date}.size + end + + def answer_result_value(question_id, answer_index) + return nil if params.blank? + return nil if params[:questions].blank? + return nil if params[:questions][question_id.to_s].blank? + params[:questions][question_id.to_s][answer_index.to_s] + end + +end \ No newline at end of file diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb deleted file mode 100644 index 08d5588ca..000000000 --- a/app/helpers/orders_helper.rb +++ /dev/null @@ -1,7 +0,0 @@ -module OrdersHelper - - def valid_orders - @valid_orders.reject { |order| order =='relevance' && params[:search].blank? } - end - -end \ No newline at end of file diff --git a/app/helpers/poll_final_recounts_helper.rb b/app/helpers/poll_final_recounts_helper.rb new file mode 100644 index 000000000..e196cb65b --- /dev/null +++ b/app/helpers/poll_final_recounts_helper.rb @@ -0,0 +1,7 @@ +module PollFinalRecountsHelper + + def final_recount_for_date(final_recounts, date) + final_recounts.select {|f| f.date == date}.first + end + +end \ No newline at end of file diff --git a/app/helpers/poll_recounts_helper.rb b/app/helpers/poll_recounts_helper.rb new file mode 100644 index 000000000..c47402163 --- /dev/null +++ b/app/helpers/poll_recounts_helper.rb @@ -0,0 +1,15 @@ +module PollRecountsHelper + + def recount_for_date(recounts, date) + recounts.select {|r| r.date == date}.first + end + + def booth_assignment_sum_recounts(ba) + ba.recounts.any? ? ba.recounts.to_a.sum(&:count) : nil + end + + def booth_assignment_sum_final_recounts(ba) + ba.final_recounts.any? ? ba.final_recounts.to_a.sum(&:count) :nil + end + +end \ No newline at end of file diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb new file mode 100644 index 000000000..5425a6b52 --- /dev/null +++ b/app/helpers/polls_helper.rb @@ -0,0 +1,49 @@ +module PollsHelper + + def poll_select_options(include_all=nil) + options = @polls.collect {|poll| + [poll.name, current_path_with_query_params(poll_id: poll.id)] + } + options << all_polls if include_all + options_for_select(options, request.fullpath) + end + + def all_polls + [I18n.t("polls.all"), admin_questions_path] + end + + def poll_dates(poll) + if poll.starts_at.blank? || poll.ends_at.blank? + I18n.t("polls.no_dates") + else + I18n.t("polls.dates", open_at: l(poll.starts_at.to_date), closed_at: l(poll.ends_at.to_date)) + end + end + + def poll_dates_select_options(poll) + options = [] + (poll.starts_at.to_date..poll.ends_at.to_date).each do |date| + options << [l(date, format: :long), l(date)] + end + options_for_select(options, params[:d]) + end + + def poll_final_recount_option(poll) + final_date = poll.ends_at.to_date + 1.day + options_for_select([[I18n.t("polls.final_date"), l(final_date)]]) + end + + def poll_booths_select_options(poll) + options = [] + poll.booths.each do |booth| + options << [booth_name_with_location(booth), booth.id] + end + options_for_select(options) + end + + def booth_name_with_location(booth) + location = booth.location.blank? ? "" : " (#{booth.location})" + booth.name + location + end + +end \ No newline at end of file diff --git a/app/helpers/proposals_helper.rb b/app/helpers/proposals_helper.rb index f01abc053..578cd1d8e 100644 --- a/app/helpers/proposals_helper.rb +++ b/app/helpers/proposals_helper.rb @@ -2,19 +2,19 @@ module ProposalsHelper def progress_bar_percentage(proposal) case proposal.cached_votes_up - when 0 then 0 - when 1..Proposal.votes_needed_for_success then (proposal.total_votes.to_f * 100 / Proposal.votes_needed_for_success).floor - else 100 + when 0 then 0 + when 1..Proposal.votes_needed_for_success then (proposal.total_votes.to_f * 100 / Proposal.votes_needed_for_success).floor + else 100 end end def supports_percentage(proposal) percentage = (proposal.total_votes.to_f * 100 / Proposal.votes_needed_for_success) case percentage - when 0 then "0%" - when 0..(0.1) then "0.1%" - when (0.1)..100 then number_to_percentage(percentage, strip_insignificant_zeros: true, precision: 1) - else "100%" + when 0 then "0%" + when 0..(0.1) then "0.1%" + when (0.1)..100 then number_to_percentage(percentage, strip_insignificant_zeros: true, precision: 1) + else "100%" end end @@ -28,4 +28,8 @@ module ProposalsHelper end end + def retire_proposals_options + Proposal::RETIRE_OPTIONS.collect { |option| [ t("proposals.retire_options.#{option}"), option ] } + end + end \ No newline at end of file diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 3d30ce101..ebde2a954 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,13 +1,8 @@ module SearchHelper def official_level_search_options - options_for_select([ - [t("shared.advanced_search.author_type_1"), 1], - [t("shared.advanced_search.author_type_2"), 2], - [t("shared.advanced_search.author_type_3"), 3], - [t("shared.advanced_search.author_type_4"), 4], - [t("shared.advanced_search.author_type_5"), 5]], - params[:advanced_search].try(:[], :official_level)) + options_for_select((1..5).map{ |i| [setting["official_level_#{i}_name"], i] }, + params[:advanced_search].try(:[], :official_level)) end def date_range_options @@ -28,4 +23,4 @@ module SearchHelper params[:advanced_search].try(:[], :date_max).present? end -end \ No newline at end of file +end diff --git a/app/helpers/settings_helper.rb b/app/helpers/settings_helper.rb index cd90a8a39..ad844e29f 100644 --- a/app/helpers/settings_helper.rb +++ b/app/helpers/settings_helper.rb @@ -8,4 +8,4 @@ module SettingsHelper @all_settings ||= Hash[ Setting.all.map{|s| [s.key, s.value.presence]} ] end -end +end \ No newline at end of file diff --git a/app/helpers/signature_sheets_helper.rb b/app/helpers/signature_sheets_helper.rb new file mode 100644 index 000000000..8e75dccfd --- /dev/null +++ b/app/helpers/signature_sheets_helper.rb @@ -0,0 +1,8 @@ +module SignatureSheetsHelper + + def signable_options + [[t("activerecord.models.proposal", count: 1), Proposal], + [t("activerecord.models.budget/investment", count: 1), Budget::Investment]] + end + +end \ No newline at end of file diff --git a/app/helpers/spending_proposals_helper.rb b/app/helpers/spending_proposals_helper.rb index 43ef7311a..26026952f 100644 --- a/app/helpers/spending_proposals_helper.rb +++ b/app/helpers/spending_proposals_helper.rb @@ -3,4 +3,23 @@ module SpendingProposalsHelper def spending_proposal_tags_select_options ActsAsTaggableOn::Tag.spending_proposal_tags.pluck(:name) end + + def namespaced_spending_proposal_path(spending_proposal, options={}) + @namespace_spending_proposal_path ||= namespace + case @namespace_spending_proposal_path + when "management" + management_spending_proposal_path(spending_proposal, options) + else + spending_proposal_path(spending_proposal, options) + end + end + + def spending_proposal_count_for_geozone(scope, geozone, second_scope) + if geozone.present? + geozone.spending_proposals.send(scope).send(second_scope).count + else + SpendingProposal.where(geozone: nil).send(scope).send(second_scope).count + end + end + end \ No newline at end of file diff --git a/app/helpers/stats_helper.rb b/app/helpers/stats_helper.rb index 097711b88..d8eff0ac2 100644 --- a/app/helpers/stats_helper.rb +++ b/app/helpers/stats_helper.rb @@ -20,4 +20,12 @@ module StatsHelper opt[:data][:graph] = admin_api_stats_path(spending_proposals: true) content_tag :div, "", opt end + + def budget_investments_chart_tag(opt={}) + events = events.join(',') if events.is_a? Array + opt[:data] ||= {} + opt[:data][:graph] = admin_api_stats_path(budget_investments: true) + content_tag :div, "", opt + end + end diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index da7824622..4cabae278 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -1,11 +1,27 @@ module TagsHelper - def taggable_path(taggable_type, tag_name) + def taggables_path(taggable_type, tag_name) case taggable_type when 'debate' debates_path(search: tag_name) when 'proposal' proposals_path(search: tag_name) + when 'budget/investment' + budget_investments_path(@budget, search: tag_name) + else + '#' + end + end + + def taggable_path(taggable) + taggable_type = taggable.class.name.underscore + case taggable_type + when 'debate' + debate_path(taggable) + when 'proposal' + proposal_path(taggable) + when 'budget/investment' + budget_investment_path(taggable.budget_id, taggable) else '#' end diff --git a/app/helpers/tracks_helper.rb b/app/helpers/tracks_helper.rb new file mode 100644 index 000000000..557d71802 --- /dev/null +++ b/app/helpers/tracks_helper.rb @@ -0,0 +1,14 @@ +module TracksHelper + + def track_event(data={}) + track_data = "" + prefix = " data-track-event-" + data.each do |key, value| + track_data = track_data + prefix + key.to_s + '=' + value + " " + end + content_for :track_event do + track_data + end + end + +end \ No newline at end of file diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 10da73712..9c13e8ef8 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -30,6 +30,8 @@ module UsersHelper t("users.show.deleted_proposal") when "Debate" t("users.show.deleted_debate") + when "Budget::Investment" + t("users.show.deleted_budget_investment") else t("users.show.deleted") end diff --git a/app/helpers/valuation_helper.rb b/app/helpers/valuation_helper.rb index 9e3a054c1..ded5fa0ed 100644 --- a/app/helpers/valuation_helper.rb +++ b/app/helpers/valuation_helper.rb @@ -2,23 +2,23 @@ module ValuationHelper def valuator_select_options(valuator=nil) if valuator.present? - Valuator.where.not(id: valuator.id).order('users.username asc').includes(:user).collect { |v| [ v.name, v.id ] }.prepend([valuator.name, valuator.id]) + Valuator.where.not(id: valuator.id).order("description ASC").order("users.email ASC").includes(:user).collect { |v| [ v.description_or_email, v.id ] }.prepend([valuator.description_or_email, valuator.id]) else - Valuator.all.order('users.username asc').includes(:user).collect { |v| [ v.name, v.id ] } + Valuator.all.order("description ASC").order("users.email ASC").includes(:user).collect { |v| [ v.description_or_email, v.id ] } end end def assigned_valuators_info(valuators) case valuators.size when 0 - t("valuation.spending_proposals.index.no_valuators_assigned") + t("valuation.budget_investments.index.no_valuators_assigned") when 1 - "".html_safe + + "".html_safe + valuators.first.name + "".html_safe else "".html_safe + - t('valuation.spending_proposals.index.valuators_assigned', count: valuators.size) + + t('valuation.budget_investments.index.valuators_assigned', count: valuators.size) + "".html_safe end end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index c1f3e4bb6..ea1d4fa40 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,5 +1,5 @@ class ApplicationMailer < ActionMailer::Base helper :settings - default from: "participacion@madrid.es" + default from: "#{Setting['mailer_from_name']} <#{Setting['mailer_from_address']}>" layout 'mailer' end diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb index 82abbb205..7e8970c61 100644 --- a/app/mailers/mailer.rb +++ b/app/mailers/mailer.rb @@ -42,6 +42,73 @@ class Mailer < ApplicationMailer end end + def direct_message_for_receiver(direct_message) + @direct_message = direct_message + @receiver = @direct_message.receiver + + with_user(@receiver) do + mail(to: @receiver.email, subject: t('mailers.direct_message_for_receiver.subject')) + end + end + + def direct_message_for_sender(direct_message) + @direct_message = direct_message + @sender = @direct_message.sender + + with_user(@sender) do + mail(to: @sender.email, subject: t('mailers.direct_message_for_sender.subject')) + end + end + + def proposal_notification_digest(user, notifications) + @notifications = notifications + + with_user(user) do + mail(to: user.email, subject: t('mailers.proposal_notification_digest.title', org_name: Setting['org_name'])) + end + end + + def user_invite(email) + I18n.with_locale(I18n.default_locale) do + mail(to: email, subject: t('mailers.user_invite.subject', org_name: Setting["org_name"])) + end + end + + def budget_investment_created(investment) + @investment = investment + + with_user(@investment.author) do + mail(to: @investment.author.email, subject: t('mailers.budget_investment_created.subject')) + end + end + + def budget_investment_unfeasible(investment) + @investment = investment + @author = investment.author + + with_user(@author) do + mail(to: @author.email, subject: t('mailers.budget_investment_unfeasible.subject', code: @investment.code)) + end + end + + def budget_investment_selected(investment) + @investment = investment + @author = investment.author + + with_user(@author) do + mail(to: @author.email, subject: t('mailers.budget_investment_selected.subject', code: @investment.code)) + end + end + + def budget_investment_unselected(investment) + @investment = investment + @author = investment.author + + with_user(@author) do + mail(to: @author.email, subject: t('mailers.budget_investment_unselected.subject', code: @investment.code)) + end + end + private def with_user(user, &block) diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 1b440795f..8df52e6eb 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -4,7 +4,6 @@ module Abilities def initialize(user) self.merge Abilities::Moderation.new(user) - self.merge Abilities::Valuator.new(user) can :restore, Comment cannot :restore, Comment, hidden_at: nil @@ -30,14 +29,41 @@ module Abilities can :confirm_hide, User cannot :confirm_hide, User, hidden_at: nil - can :comment_as_administrator, [Debate, Comment, Proposal] + can :mark_featured, Debate + can :unmark_featured, Debate + + can :comment_as_administrator, [Debate, Comment, Proposal, Poll::Question, Budget::Investment] can [:search, :create, :index, :destroy], ::Moderator - can [:search, :create, :index], ::Valuator + can [:search, :create, :index, :summary], ::Valuator + can [:search, :create, :index, :destroy], ::Manager can :manage, Annotation - can [:read, :update, :destroy], SpendingProposal + can [:read, :update, :valuate, :destroy, :summary], SpendingProposal + + can [:index, :read, :new, :create, :update, :destroy], Budget + can [:read, :create, :update, :destroy], Budget::Group + can [:read, :create, :update, :destroy], Budget::Heading + can [:hide, :update, :toggle_selection], Budget::Investment + can :valuate, Budget::Investment + can :create, Budget::ValuatorAssignment + + can [:search, :edit, :update, :create, :index, :destroy], Banner + + can [:index, :create, :edit, :update, :destroy], Geozone + + can [:read, :create, :update, :destroy, :add_question, :remove_question, :search_booths, :search_questions, :search_officers], Poll + can [:read, :create, :update, :destroy], Poll::Booth + can [:search, :create, :index, :destroy], ::Poll::Officer + can [:create, :destroy], ::Poll::BoothAssignment + can [:create, :destroy], ::Poll::OfficerAssignment + can [:read, :create, :update], Poll::Question + can :destroy, Poll::Question # , comments_count: 0, votes_up: 0 + + can :manage, SiteCustomization::Page + can :manage, SiteCustomization::Image + can :manage, SiteCustomization::ContentBlock end end end diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 5bb43725b..6a8ef594c 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -16,8 +16,7 @@ module Abilities can :update, Proposal do |proposal| proposal.editable_by?(user) end - - can :read, SpendingProposal + can [:retire_form, :retire], Proposal, author_id: user.id can :create, Comment can :create, Debate @@ -45,11 +44,27 @@ module Abilities can :vote_featured, Proposal can :vote, SpendingProposal can :create, SpendingProposal + + can :create, Budget::Investment, budget: { phase: "accepting" } + can :destroy, Budget::Investment, budget: { phase: ["accepting", "reviewing"] }, author_id: user.id + can :vote, Budget::Investment, budget: { phase: "selecting" } + can [:show, :create], Budget::Ballot, budget: { phase: "balloting" } + can [:create, :destroy], Budget::Ballot::Line, budget: { phase: "balloting" } + + can :create, DirectMessage + can :show, DirectMessage, sender_id: user.id + can :answer, Poll do |poll| + poll.answerable_by?(user) + end + can :answer, Poll::Question do |question| + question.answerable_by?(user) + end end + can [:create, :show], ProposalNotification, proposal: { author_id: user.id } + can :create, Annotation can [:update, :destroy], Annotation, user_id: user.id - end end end diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 21e142c05..c2b6cd6d7 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -6,10 +6,18 @@ module Abilities can [:read, :map], Debate can [:read, :map, :summary], Proposal can :read, Comment + can :read, Poll + can :read, Poll::Question + can [:read, :welcome], Budget + can :read, Budget::Investment can :read, SpendingProposal can :read, Legislation can :read, User can [:search, :read], Annotation + can [:read], Budget + can [:read], Budget::Group + can [:read, :print], Budget::Investment + can :new, DirectMessage end end end diff --git a/app/models/abilities/moderator.rb b/app/models/abilities/moderator.rb index f6c5c5004..5740e302e 100644 --- a/app/models/abilities/moderator.rb +++ b/app/models/abilities/moderator.rb @@ -5,7 +5,7 @@ module Abilities def initialize(user) self.merge Abilities::Moderation.new(user) - can :comment_as_moderator, [Debate, Comment, Proposal] + can :comment_as_moderator, [Debate, Comment, Proposal, Budget::Investment, Poll::Question] end end end diff --git a/app/models/abilities/valuator.rb b/app/models/abilities/valuator.rb index 15add866a..614869665 100644 --- a/app/models/abilities/valuator.rb +++ b/app/models/abilities/valuator.rb @@ -3,7 +3,10 @@ module Abilities include CanCan::Ability def initialize(user) + valuator = user.valuator can [:read, :update, :valuate], SpendingProposal + can [:read, :update, :valuate], Budget::Investment, id: valuator.investment_ids + cannot [:update, :valuate], Budget::Investment, budget: { phase: 'finished' } end end -end \ No newline at end of file +end diff --git a/app/models/activity.rb b/app/models/activity.rb index 047ccb7dd..0fc35ad11 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -2,7 +2,7 @@ class Activity < ActiveRecord::Base belongs_to :actionable, -> { with_hidden }, polymorphic: true belongs_to :user, -> { with_hidden } - VALID_ACTIONS = %w( hide block restore ) + VALID_ACTIONS = %w( hide block restore valuate ) validates :action, inclusion: {in: VALID_ACTIONS} @@ -10,6 +10,7 @@ class Activity < ActiveRecord::Base scope :on_debates, -> { where(actionable_type: 'Debate') } scope :on_users, -> { where(actionable_type: 'User') } scope :on_comments, -> { where(actionable_type: 'Comment') } + scope :on_budget_investments, -> { where(actionable_type: 'Budget::Investment') } scope :for_render, -> { includes(user: [:moderator, :administrator]).includes(:actionable) } def self.log(user, action, actionable) diff --git a/app/models/banner.rb b/app/models/banner.rb new file mode 100644 index 000000000..c4f2295e6 --- /dev/null +++ b/app/models/banner.rb @@ -0,0 +1,20 @@ +class Banner < ActiveRecord::Base + + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + validates :title, presence: true, + length: { minimum: 2 } + validates :description, presence: true + validates :target_url, presence: true + validates :style, presence: true + validates :image, presence: true + validates :post_started_at, presence: true + validates :post_ended_at, presence: true + + scope :with_active, -> {where("post_started_at <= ?", Time.current). + where("post_ended_at >= ?", Time.current) } + + scope :with_inactive,-> {where("post_started_at > ? or post_ended_at < ?", Time.current, Time.current) } + +end diff --git a/app/models/budget.rb b/app/models/budget.rb new file mode 100644 index 000000000..a5932921c --- /dev/null +++ b/app/models/budget.rb @@ -0,0 +1,138 @@ +class Budget < ActiveRecord::Base + + include Measurable + + PHASES = %w(accepting reviewing selecting valuating balloting reviewing_ballots finished).freeze + CURRENCY_SYMBOLS = %w(€ $ £ ¥).freeze + + validates :name, presence: true + validates :phase, inclusion: { in: PHASES } + validates :currency_symbol, presence: true + + has_many :investments, dependent: :destroy + has_many :ballots, dependent: :destroy + has_many :groups, dependent: :destroy + has_many :headings, through: :groups + + before_validation :sanitize_descriptions + + scope :on_hold, -> { where(phase: %w(reviewing valuating reviewing_ballots")) } + scope :accepting, -> { where(phase: "accepting") } + scope :reviewing, -> { where(phase: "reviewing") } + scope :selecting, -> { where(phase: "selecting") } + scope :valuating, -> { where(phase: "valuating") } + scope :balloting, -> { where(phase: "balloting") } + scope :reviewing_ballots, -> { where(phase: "reviewing_ballots") } + scope :finished, -> { where(phase: "finished") } + + scope :current, -> { where.not(phase: "finished") } + + def description + self.send("description_#{self.phase}").try(:html_safe) + end + + def self.description_max_length + 2000 + end + + def self.title_max_length + 80 + end + + def accepting? + phase == "accepting" + end + + def reviewing? + phase == "reviewing" + end + + def selecting? + phase == "selecting" + end + + def valuating? + phase == "valuating" + end + + def balloting? + phase == "balloting" + end + + def reviewing_ballots? + phase == "reviewing_ballots" + end + + def finished? + phase == "finished" + end + + def balloting_or_later? + balloting? || reviewing_ballots? || finished? + end + + def on_hold? + reviewing? || valuating? || reviewing_ballots? + end + + def current? + !finished? + end + + def heading_price(heading) + heading_ids.include?(heading.id) ? heading.price : -1 + end + + def translated_phase + I18n.t "budgets.phase.#{phase}" + end + + def formatted_amount(amount) + ActionController::Base.helpers.number_to_currency(amount, + precision: 0, + locale: I18n.default_locale, + unit: currency_symbol) + end + + def formatted_heading_price(heading) + formatted_amount(heading_price(heading)) + end + + def formatted_heading_amount_spent(heading) + formatted_amount(amount_spent(heading)) + end + + def investments_orders + case phase + when 'accepting', 'reviewing' + %w{random} + when 'balloting', 'reviewing_ballots' + %w{random price} + else + %w{random confidence_score} + end + end + + def email_selected + investments.selected.each do |investment| + Mailer.budget_investment_selected(investment).deliver_later + end + end + + def email_unselected + investments.unselected.each do |investment| + Mailer.budget_investment_unselected(investment).deliver_later + end + end + + private + + def sanitize_descriptions + s = WYSIWYGSanitizer.new + PHASES.each do |phase| + sanitized = s.sanitize(self.send("description_#{phase}")) + self.send("description_#{phase}=", sanitized) + end + end +end + diff --git a/app/models/budget/ballot.rb b/app/models/budget/ballot.rb new file mode 100644 index 000000000..83e799d6f --- /dev/null +++ b/app/models/budget/ballot.rb @@ -0,0 +1,74 @@ +class Budget + class Ballot < ActiveRecord::Base + belongs_to :user + belongs_to :budget + + has_many :lines, dependent: :destroy + has_many :investments, through: :lines + has_many :groups, -> { uniq }, through: :lines + has_many :headings, -> { uniq }, through: :groups + + def add_investment(investment) + lines.create(investment: investment).persisted? + end + + def total_amount_spent + investments.sum(:price).to_i + end + + def amount_spent(heading) + investments.by_heading(heading.id).sum(:price).to_i + end + + def formatted_amount_spent(heading) + budget.formatted_amount(amount_spent(heading)) + end + + def amount_available(heading) + budget.heading_price(heading) - amount_spent(heading) + end + + def formatted_amount_available(heading) + budget.formatted_amount(amount_available(heading)) + end + + def has_lines_in_group?(group) + self.groups.include?(group) + end + + def wrong_budget?(heading) + heading.budget_id != budget_id + end + + def different_heading_assigned?(heading) + other_heading_ids = heading.group.heading_ids - [heading.id] + lines.where(heading_id: other_heading_ids).exists? + end + + def valid_heading?(heading) + !wrong_budget?(heading) && !different_heading_assigned?(heading) + end + + def has_lines_with_no_heading? + investments.no_heading.count > 0 + end + + def has_lines_with_heading? + self.heading_id.present? + end + + def has_lines_in_heading?(heading) + investments.by_heading(heading.id).any? + end + + def has_investment?(investment) + self.investment_ids.include?(investment.id) + end + + def heading_for_group(group) + return nil unless has_lines_in_group?(group) + self.investments.where(group: group).first.heading + end + + end +end diff --git a/app/models/budget/ballot/line.rb b/app/models/budget/ballot/line.rb new file mode 100644 index 000000000..175e9886d --- /dev/null +++ b/app/models/budget/ballot/line.rb @@ -0,0 +1,41 @@ +class Budget + class Ballot + class Line < ActiveRecord::Base + belongs_to :ballot + belongs_to :investment, counter_cache: :ballot_lines_count + belongs_to :heading + belongs_to :group + belongs_to :budget + + validates :ballot_id, :investment_id, :heading_id, :group_id, :budget_id, presence: true + + validate :check_selected + validate :check_sufficient_funds + validate :check_valid_heading + + scope :by_investment, -> (investment_id) { where(investment_id: investment_id) } + + before_validation :set_denormalized_ids + + def check_sufficient_funds + errors.add(:money, "insufficient funds") if ballot.amount_available(investment.heading) < investment.price.to_i + end + + def check_valid_heading + errors.add(:heading, "This heading's budget is invalid, or a heading on the same group was already selected") unless ballot.valid_heading?(self.heading) + end + + def check_selected + errors.add(:investment, "unselected investment") unless investment.selected? + end + + private + + def set_denormalized_ids + self.heading_id ||= self.investment.try(:heading_id) + self.group_id ||= self.investment.try(:group_id) + self.budget_id ||= self.investment.try(:budget_id) + end + end + end +end diff --git a/app/models/budget/group.rb b/app/models/budget/group.rb new file mode 100644 index 000000000..dd7910950 --- /dev/null +++ b/app/models/budget/group.rb @@ -0,0 +1,10 @@ +class Budget + class Group < ActiveRecord::Base + belongs_to :budget + + has_many :headings, dependent: :destroy + + validates :budget_id, presence: true + validates :name, presence: true + end +end \ No newline at end of file diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb new file mode 100644 index 000000000..a81308947 --- /dev/null +++ b/app/models/budget/heading.rb @@ -0,0 +1,20 @@ +class Budget + class Heading < ActiveRecord::Base + belongs_to :group + + has_many :investments + + validates :group_id, presence: true + validates :name, presence: true + validates :price, presence: true + + delegate :budget, :budget_id, to: :group, allow_nil: true + + scope :order_by_group_name, -> { includes(:group).order('budget_groups.name', 'budget_headings.name') } + + def name_scoped_by_group + "#{group.name}: #{name}" + end + + end +end diff --git a/app/models/budget/investment.rb b/app/models/budget/investment.rb new file mode 100644 index 000000000..56a50deec --- /dev/null +++ b/app/models/budget/investment.rb @@ -0,0 +1,255 @@ +class Budget + class Investment < ActiveRecord::Base + + include Measurable + include Sanitizable + include Taggable + include Searchable + include Reclassification + + acts_as_votable + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :heading + belongs_to :group + belongs_to :budget + belongs_to :administrator + + has_many :valuator_assignments, dependent: :destroy + has_many :valuators, through: :valuator_assignments + has_many :comments, as: :commentable + + validates :title, presence: true + validates :author, presence: true + validates :description, presence: true + validates :heading_id, presence: true + validates_presence_of :unfeasibility_explanation, if: :unfeasibility_explanation_required? + validates_presence_of :price, if: :price_required? + + validates :title, length: { in: 4..Budget::Investment.title_max_length } + validates :description, length: { maximum: Budget::Investment.description_max_length } + validates :terms_of_service, acceptance: { allow_nil: false }, on: :create + + scope :sort_by_confidence_score, -> { reorder(confidence_score: :desc, id: :desc) } + scope :sort_by_ballots, -> { reorder(ballot_lines_count: :desc, id: :desc) } + scope :sort_by_price, -> { reorder(price: :desc, confidence_score: :desc, id: :desc) } + scope :sort_by_random, -> { reorder("RANDOM()") } + + scope :valuation_open, -> { where(valuation_finished: false) } + scope :without_admin, -> { valuation_open.where(administrator_id: nil) } + scope :managed, -> { valuation_open.where(valuator_assignments_count: 0).where("administrator_id IS NOT ?", nil) } + scope :valuating, -> { valuation_open.where("valuator_assignments_count > 0 AND valuation_finished = ?", false) } + scope :valuation_finished, -> { where(valuation_finished: true) } + scope :valuation_finished_feasible, -> { where(valuation_finished: true, feasibility: "feasible") } + scope :feasible, -> { where(feasibility: "feasible") } + scope :unfeasible, -> { where(feasibility: "unfeasible") } + scope :not_unfeasible, -> { where.not(feasibility: "unfeasible") } + scope :undecided, -> { where(feasibility: "undecided") } + scope :with_supports, -> { where('cached_votes_up > 0') } + scope :selected, -> { feasible.where(selected: true) } + scope :unselected, -> { not_unfeasible.where(selected: false) } + scope :last_week, -> { where("created_at >= ?", 7.days.ago)} + + scope :by_group, -> (group_id) { where(group_id: group_id) } + scope :by_heading, -> (heading_id) { where(heading_id: heading_id) } + scope :by_admin, -> (admin_id) { where(administrator_id: admin_id) } + scope :by_tag, -> (tag_name) { tagged_with(tag_name) } + scope :by_valuator, -> (valuator_id) { where("budget_valuator_assignments.valuator_id = ?", valuator_id).joins(:valuator_assignments) } + + scope :for_render, -> { includes(:heading) } + + before_save :calculate_confidence_score + before_validation :set_responsible_name + before_validation :set_denormalized_ids + + def self.filter_params(params) + params.select{|x,_| %w{heading_id group_id administrator_id tag_name valuator_id}.include? x.to_s } + end + + def self.scoped_filter(params, current_filter) + results = Investment.where(budget_id: params[:budget_id]) + results = results.where(group_id: params[:group_id]) if params[:group_id].present? + results = results.by_heading(params[:heading_id]) if params[:heading_id].present? + results = results.by_admin(params[:administrator_id]) if params[:administrator_id].present? + results = results.by_tag(params[:tag_name]) if params[:tag_name].present? + results = results.by_valuator(params[:valuator_id]) if params[:valuator_id].present? + results = results.send(current_filter) if current_filter.present? + results.includes(:heading, :group, :budget, administrator: :user, valuators: :user) + end + + def searchable_values + { title => 'A', + author.username => 'B', + heading.try(:name) => 'B', + tag_list.join(' ') => 'B', + description => 'C' + } + end + + def self.search(terms) + self.pg_search(terms) + end + + def self.by_heading(heading) + where(heading_id: heading == 'all' ? nil : heading.presence) + end + + def undecided? + feasibility == "undecided" + end + + def feasible? + feasibility == "feasible" + end + + def unfeasible? + feasibility == "unfeasible" + end + + def unfeasibility_explanation_required? + unfeasible? && valuation_finished? + end + + def price_required? + feasible? && valuation_finished? + end + + def unfeasible_email_pending? + unfeasible_email_sent_at.blank? && unfeasible? && valuation_finished? + end + + def total_votes + cached_votes_up + physical_votes + end + + def code + "#{created_at.strftime('%Y')}-#{id}" + (administrator.present? ? "-A#{administrator.id}" : "") + end + + def send_unfeasible_email + Mailer.budget_investment_unfeasible(self).deliver_later + update(unfeasible_email_sent_at: Time.current) + end + + def reason_for_not_being_selectable_by(user) + return permission_problem(user) if permission_problem?(user) + return :different_heading_assigned unless valid_heading?(user) + + return :no_selecting_allowed unless budget.selecting? + end + + def reason_for_not_being_ballotable_by(user, ballot) + return permission_problem(user) if permission_problem?(user) + return :not_selected unless selected? + return :no_ballots_allowed unless budget.balloting? + return :different_heading_assigned unless ballot.valid_heading?(heading) + return :not_enough_money if ballot.present? && !enough_money?(ballot) + end + + def permission_problem(user) + return :not_logged_in unless user + return :organization if user.organization? + return :not_verified unless user.can?(:vote, Budget::Investment) + return nil + end + + def permission_problem?(user) + permission_problem(user).present? + end + + def selectable_by?(user) + reason_for_not_being_selectable_by(user).blank? + end + + def valid_heading?(user) + !different_heading_assigned?(user) + end + + def different_heading_assigned?(user) + other_heading_ids = group.heading_ids - [heading.id] + voted_in?(other_heading_ids, user) + end + + def voted_in?(heading_ids, user) + heading_ids.include? heading_voted_by_user?(user) + end + + def heading_voted_by_user?(user) + user.votes.for_budget_investments(budget.investments.where(group: group)). + votables.map(&:heading_id).first + end + + def ballotable_by?(user) + reason_for_not_being_ballotable_by(user).blank? + end + + def enough_money?(ballot) + available_money = ballot.amount_available(self.heading) + price.to_i <= available_money + end + + def register_selection(user) + vote_by(voter: user, vote: 'yes') if selectable_by?(user) + end + + def calculate_confidence_score + self.confidence_score = ScoreCalculator.confidence_score(total_votes, total_votes) + end + + def set_responsible_name + self.responsible_name = author.try(:document_number) if author.try(:document_number).present? + end + + def should_show_aside? + (budget.selecting? && !unfeasible?) || + (budget.balloting? && feasible?) || + (budget.valuating? && !unfeasible?) + end + + def should_show_votes? + budget.selecting? + end + + def should_show_vote_count? + budget.valuating? + end + + def should_show_ballots? + budget.balloting? && selected? + end + + def should_show_price? + feasible? && + selected? && + (budget.reviewing_ballots? || budget.finished?) + end + + def should_show_price_info? + feasible? && + price_explanation.present? && + (budget.balloting? || budget.reviewing_ballots? || budget.finished?) + end + + def formatted_price + budget.formatted_amount(price) + end + + def self.apply_filters_and_search(budget, params, current_filter=nil) + investments = all + investments = investments.send(current_filter) if current_filter.present? + investments = investments.by_heading(params[:heading_id]) if params[:heading_id].present? + investments = investments.search(params[:search]) if params[:search].present? + investments + end + + private + + def set_denormalized_ids + self.group_id = self.heading.try(:group_id) if self.heading_id_changed? + self.budget_id ||= self.heading.try(:group).try(:budget_id) + end + + end +end diff --git a/app/models/budget/reclassification.rb b/app/models/budget/reclassification.rb new file mode 100644 index 000000000..f8d1db7fd --- /dev/null +++ b/app/models/budget/reclassification.rb @@ -0,0 +1,50 @@ +class Budget + module Reclassification + extend ActiveSupport::Concern + + included do + after_save :check_for_reclassification + end + + def check_for_reclassification + if heading_changed? + log_heading_change + store_reclassified_votes("heading_changed") + remove_reclassified_votes + elsif marked_as_unfeasible? + store_reclassified_votes("unfeasible") + remove_reclassified_votes + end + end + + def heading_changed? + budget.balloting? && heading_id_changed? + end + + def marked_as_unfeasible? + budget.balloting? && feasibility_changed? && unfeasible? + end + + def log_heading_change + update_column(:previous_heading_id, heading_id_was) + end + + def store_reclassified_votes(reason) + ballot_lines_for_investment.each do |line| + attrs = { user: line.ballot.user, + investment: self, + reason: reason } + Budget::ReclassifiedVote.create!(attrs) + end + end + + def remove_reclassified_votes + ballot_lines_for_investment.destroy_all + end + + def ballot_lines_for_investment + Budget::Ballot::Line.by_investment(self.id) + end + + end +end \ No newline at end of file diff --git a/app/models/budget/reclassified_vote.rb b/app/models/budget/reclassified_vote.rb new file mode 100644 index 000000000..0eb44d8cc --- /dev/null +++ b/app/models/budget/reclassified_vote.rb @@ -0,0 +1,12 @@ +class Budget + class ReclassifiedVote < ActiveRecord::Base + REASONS = %w(heading_changed unfeasible) + + belongs_to :user + belongs_to :investment + + validates :user, presence: true + validates :investment, presence: true + validates :reason, inclusion: {in: REASONS, allow_nil: false} + end +end \ No newline at end of file diff --git a/app/models/budget/result.rb b/app/models/budget/result.rb new file mode 100644 index 000000000..f29bc72cc --- /dev/null +++ b/app/models/budget/result.rb @@ -0,0 +1,55 @@ +class Budget + class Result + + attr_accessor :budget, :heading, :money_spent, :current_investment + + def initialize(budget, heading) + @budget = budget + @heading = heading + end + + def calculate_winners + reset_winners + investments.each do |investment| + @current_investment = investment + if inside_budget? + set_winner + end + end + end + + def investments + heading.investments.selected.sort_by_ballots + end + + def inside_budget? + available_budget >= @current_investment.price + end + + def available_budget + total_budget - money_spent + end + + def total_budget + heading.price + end + + def money_spent + @money_spent ||= 0 + end + + def reset_winners + investments.update_all(winner: false) + end + + def set_winner + @money_spent += @current_investment.price + @current_investment.update(winner: true) + end + + def winners + investments.where(winner: true) + end + + end +end \ No newline at end of file diff --git a/app/models/budget/valuator_assignment.rb b/app/models/budget/valuator_assignment.rb new file mode 100644 index 000000000..18ef73812 --- /dev/null +++ b/app/models/budget/valuator_assignment.rb @@ -0,0 +1,6 @@ +class Budget + class ValuatorAssignment < ActiveRecord::Base + belongs_to :valuator, counter_cache: :budget_investments_count + belongs_to :investment, counter_cache: true + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb index 47beb5050..cd84a3578 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -10,7 +10,8 @@ class Comment < ActiveRecord::Base validates :body, presence: true validates :user, presence: true - validates_inclusion_of :commentable_type, in: ["Debate", "Proposal"] + + validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Budget::Investment", "Poll::Question"] validate :validate_body_length @@ -24,8 +25,8 @@ class Comment < ActiveRecord::Base scope :not_as_admin_or_moderator, -> { where("administrator_id IS NULL").where("moderator_id IS NULL")} scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } - scope :sort_by_most_voted , -> { order(confidence_score: :desc, created_at: :desc) } - scope :sort_descendants_by_most_voted , -> { order(confidence_score: :desc, created_at: :asc) } + scope :sort_by_most_voted, -> { order(confidence_score: :desc, created_at: :desc) } + scope :sort_descendants_by_most_voted, -> { order(confidence_score: :desc, created_at: :asc) } scope :sort_by_newest, -> { order(created_at: :desc) } scope :sort_descendants_by_newest, -> { order(created_at: :desc) } @@ -95,7 +96,7 @@ class Comment < ActiveRecord::Base end def self.body_max_length - Setting['comments_body_max_length'].to_i + Setting['comments_body_max_length'].to_i end def calculate_confidence_score diff --git a/app/models/concerns/flaggable.rb b/app/models/concerns/flaggable.rb index a111562b3..c3125c440 100644 --- a/app/models/concerns/flaggable.rb +++ b/app/models/concerns/flaggable.rb @@ -13,7 +13,7 @@ module Flaggable end def ignore_flag - update(ignored_flag_at: Time.now) + update(ignored_flag_at: Time.current) end end diff --git a/app/models/concerns/sanitizable.rb b/app/models/concerns/sanitizable.rb index 261eccbf2..a4520ba2f 100644 --- a/app/models/concerns/sanitizable.rb +++ b/app/models/concerns/sanitizable.rb @@ -6,6 +6,10 @@ module Sanitizable before_validation :sanitize_tag_list end + def description + super.try :html_safe + end + protected def sanitize_description diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb index 4d717959e..147a37fbc 100644 --- a/app/models/concerns/searchable.rb +++ b/app/models/concerns/searchable.rb @@ -12,7 +12,7 @@ module Searchable }, ignoring: :accents, ranked_by: '(:tsearch)', - order_within_rank: "#{self.table_name}.cached_votes_up DESC" + order_within_rank: (self.column_names.include?('cached_votes_up') ? "#{self.table_name}.cached_votes_up DESC" : nil) } end diff --git a/app/models/concerns/verification.rb b/app/models/concerns/verification.rb index 3520e5b31..4eb933204 100644 --- a/app/models/concerns/verification.rb +++ b/app/models/concerns/verification.rb @@ -54,6 +54,17 @@ module Verification !verification_sms_sent? end + def user_type + case + when level_three_verified? + :level_3_user + when level_two_verified? + :level_2_user + else + :level_1_user + end + end + def sms_code_not_confirmed? !sms_verified? end diff --git a/app/models/custom/.keep b/app/models/custom/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/models/custom/verification/residence.rb b/app/models/custom/verification/residence.rb new file mode 100644 index 000000000..1cbc6f7ab --- /dev/null +++ b/app/models/custom/verification/residence.rb @@ -0,0 +1,29 @@ + +require_dependency Rails.root.join('app', 'models', 'verification', 'residence').to_s + +class Verification::Residence + + validate :postal_code_in_madrid + validate :residence_in_madrid + + def postal_code_in_madrid + errors.add(:postal_code, I18n.t('verification.residence.new.error_not_allowed_postal_code')) unless valid_postal_code? + end + + def residence_in_madrid + return if errors.any? + + unless residency_valid? + errors.add(:residence_in_madrid, false) + store_failed_attempt + Lock.increase_tries(user) + end + end + + private + + def valid_postal_code? + postal_code =~ /^280/ + end + +end diff --git a/app/models/debate.rb b/app/models/debate.rb index ceec9946b..de29c5864 100644 --- a/app/models/debate.rb +++ b/app/models/debate.rb @@ -8,7 +8,6 @@ class Debate < ActiveRecord::Base include Searchable include Filterable - apply_simple_captcha acts_as_votable acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases @@ -29,14 +28,15 @@ class Debate < ActiveRecord::Base before_save :calculate_hot_score, :calculate_confidence_score scope :for_render, -> { includes(:tags) } - scope :sort_by_hot_score , -> { reorder(hot_score: :desc) } + scope :sort_by_hot_score, -> { reorder(hot_score: :desc) } scope :sort_by_confidence_score, -> { reorder(confidence_score: :desc) } scope :sort_by_created_at, -> { reorder(created_at: :desc) } scope :sort_by_most_commented, -> { reorder(comments_count: :desc) } scope :sort_by_random, -> { reorder("RANDOM()") } scope :sort_by_relevance, -> { all } scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } - scope :last_week, -> { where("created_at >= ?", 7.days.ago)} + scope :last_week, -> { where("created_at >= ?", 7.days.ago)} + scope :featured, -> { where("featured_at is not null")} # Ahoy setup visitable # Ahoy will automatically assign visit_id on create @@ -59,10 +59,6 @@ class Debate < ActiveRecord::Base "#{id}-#{title}".parameterize end - def description - super.try :html_safe - end - def likes cached_votes_up end @@ -132,4 +128,8 @@ class Debate < ActiveRecord::Base self.tags.each{ |t| t.increment_custom_counter_for('Debate') } end + def featured? + self.featured_at.present? + end + end diff --git a/app/models/direct_message.rb b/app/models/direct_message.rb new file mode 100644 index 000000000..476194aea --- /dev/null +++ b/app/models/direct_message.rb @@ -0,0 +1,22 @@ +class DirectMessage < ActiveRecord::Base + belongs_to :sender, class_name: 'User', foreign_key: 'sender_id' + belongs_to :receiver, class_name: 'User', foreign_key: 'receiver_id' + + validates :title, presence: true + validates :body, presence: true + validates :sender, presence: true + validates :receiver, presence: true + validate :max_per_day + + scope :today, lambda { where('DATE(created_at) = ?', Date.today) } + + def max_per_day + return if errors.any? + max = Setting[:direct_message_max_per_day] + + if sender.direct_messages_sent.today.count >= max.to_i + errors.add(:title, I18n.t('activerecord.errors.models.direct_message.attributes.max_per_day.invalid')) + end + end + +end diff --git a/app/models/failed_census_call.rb b/app/models/failed_census_call.rb index ac792d7b7..b7d60e63a 100644 --- a/app/models/failed_census_call.rb +++ b/app/models/failed_census_call.rb @@ -1,3 +1,4 @@ class FailedCensusCall < ActiveRecord::Base belongs_to :user, counter_cache: true + belongs_to :poll_officer, class_name: 'Poll::Officer', counter_cache: true end diff --git a/app/models/geozone.rb b/app/models/geozone.rb index ee612f355..824879ec6 100644 --- a/app/models/geozone.rb +++ b/app/models/geozone.rb @@ -1,7 +1,21 @@ class Geozone < ActiveRecord::Base + has_many :proposals + has_many :spending_proposals + has_many :debates + has_many :users validates :name, presence: true def self.names Geozone.pluck(:name) end + + def self.city + where(name: 'city').first + end + + def safe_to_destroy? + Geozone.reflect_on_all_associations(:has_many).all? do |association| + association.klass.where(geozone: self).empty? + end + end end diff --git a/app/models/lock.rb b/app/models/lock.rb index 3c043de79..c0d5fae39 100644 --- a/app/models/lock.rb +++ b/app/models/lock.rb @@ -4,7 +4,7 @@ class Lock < ActiveRecord::Base before_save :set_locked_until def locked? - locked_until > Time.now + locked_until > Time.current end def set_locked_until @@ -12,7 +12,7 @@ class Lock < ActiveRecord::Base end def lock_time - Time.now + (2**tries).minutes + Time.current + (2**tries).minutes end def too_many_tries? diff --git a/app/models/manager.rb b/app/models/manager.rb new file mode 100644 index 000000000..d9c1aff07 --- /dev/null +++ b/app/models/manager.rb @@ -0,0 +1,6 @@ +class Manager < ActiveRecord::Base + belongs_to :user, touch: true + delegate :name, :email, :name_and_email, to: :user + + validates :user_id, presence: true, uniqueness: true +end diff --git a/app/models/notification.rb b/app/models/notification.rb index 1cb500ccf..e993e55f8 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -2,9 +2,11 @@ class Notification < ActiveRecord::Base belongs_to :user, counter_cache: true belongs_to :notifiable, polymorphic: true - scope :unread, -> { all } - scope :recent, -> { order(id: :desc) } - scope :for_render, -> { includes(:notifiable) } + scope :unread, -> { all } + scope :recent, -> { order(id: :desc) } + scope :not_emailed, -> { where(emailed_at: nil) } + scope :for_render, -> { includes(:notifiable) } + def timestamp notifiable.created_at @@ -21,4 +23,31 @@ class Notification < ActiveRecord::Base Notification.create!(user_id: user_id, notifiable: notifiable) end end + + def notifiable_title + case notifiable.class.name + when "ProposalNotification" + notifiable.proposal.title + when "Comment" + notifiable.commentable.title + else + notifiable.title + end + end + + def notifiable_action + case notifiable_type + when "ProposalNotification" + "proposal_notification" + when "Comment" + "replies_to" + else + "comments_on" + end + end + + def linkable_resource + notifiable.is_a?(ProposalNotification) ? notifiable.proposal : notifiable + end + end \ No newline at end of file diff --git a/app/models/officing/residence.rb b/app/models/officing/residence.rb new file mode 100644 index 000000000..6343fc5f7 --- /dev/null +++ b/app/models/officing/residence.rb @@ -0,0 +1,126 @@ +class Officing::Residence + include ActiveModel::Model + include ActiveModel::Validations::Callbacks + + attr_accessor :user, :officer, :document_number, :document_type, :year_of_birth + + before_validation :call_census_api + + validates_presence_of :document_number + validates_presence_of :document_type + validates_presence_of :year_of_birth + + validate :allowed_age + validate :residence_in_madrid + + def initialize(attrs={}) + super + clean_document_number + end + + def save + return false unless valid? + + if user_exists? + self.user = find_user_by_document + self.user.update(verified_at: Time.current) + else + user_params = { + document_number: document_number, + document_type: document_type, + geozone: self.geozone, + date_of_birth: date_of_birth.to_datetime, + gender: gender, + residence_verified_at: Time.current, + verified_at: Time.current, + erased_at: Time.current, + password: random_password, + terms_of_service: '1', + email: nil + } + self.user = User.create!(user_params) + end + end + + def store_failed_census_call + FailedCensusCall.create({ + user: user, + document_number: document_number, + document_type: document_type, + year_of_birth: year_of_birth, + poll_officer: officer + }) + + end + + def user_exists? + find_user_by_document.present? + end + + def find_user_by_document + User.where(document_number: document_number, + document_type: document_type).first + end + + def residence_in_madrid + return if errors.any? + + unless residency_valid? + store_failed_census_call + errors.add(:residence_in_madrid, false) + end + end + + def allowed_age + return if errors[:year_of_birth].any? + return unless @census_api_response.valid? + + unless allowed_age? + errors.add(:year_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) + end + end + + def allowed_age? + Age.in_years(date_of_birth) >= User.minimum_required_age + end + + def geozone + Geozone.where(census_code: district_code).first + end + + def district_code + @census_api_response.district_code + end + + def gender + @census_api_response.gender + end + + def date_of_birth + @census_api_response.date_of_birth + end + + private + + def call_census_api + @census_api_response = CensusApi.new.call(document_type, document_number) + end + + def residency_valid? + @census_api_response.valid? && + @census_api_response.date_of_birth.year.to_s == year_of_birth.to_s + end + + def census_year_of_birth + @census_api_response.date_of_birth.year + end + + def clean_document_number + self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase unless self.document_number.blank? + end + + def random_password + (0...20).map { ('a'..'z').to_a[rand(26)] }.join + end + +end diff --git a/app/models/organization.rb b/app/models/organization.rb index 069afc27f..74fd16111 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -14,11 +14,11 @@ class Organization < ActiveRecord::Base scope :rejected, -> { where.not(rejected_at: nil).where("(organizations.verified_at IS NULL or organizations.verified_at < rejected_at)") } def verify - update(verified_at: Time.now) + update(verified_at: Time.current) end def reject - update(rejected_at: Time.now) + update(rejected_at: Time.current) end def verified? diff --git a/app/models/poll.rb b/app/models/poll.rb new file mode 100644 index 000000000..c6be3073a --- /dev/null +++ b/app/models/poll.rb @@ -0,0 +1,65 @@ +class Poll < ActiveRecord::Base + has_many :booth_assignments, class_name: "Poll::BoothAssignment" + has_many :booths, through: :booth_assignments + has_many :partial_results, through: :booth_assignments + has_many :white_results, through: :booth_assignments + has_many :null_results, through: :booth_assignments + has_many :voters + has_many :officer_assignments, through: :booth_assignments + has_many :officers, through: :officer_assignments + has_many :questions + + has_and_belongs_to_many :geozones + + validates :name, presence: true + + validate :date_range + + scope :current, -> { where('starts_at <= ? and ? <= ends_at', Time.current, Time.current) } + scope :incoming, -> { where('? < starts_at', Time.current) } + scope :expired, -> { where('ends_at < ?', Time.current) } + scope :published, -> { where('published = ?', true) } + scope :by_geozone_id, ->(geozone_id) { where(geozones: {id: geozone_id}.joins(:geozones)) } + + scope :sort_for_list, -> { order(:geozone_restricted, :starts_at, :name) } + + def current?(timestamp = DateTime.current) + starts_at <= timestamp && timestamp <= ends_at + end + + def incoming?(timestamp = DateTime.current) + timestamp < starts_at + end + + def expired?(timestamp = DateTime.current) + ends_at < timestamp + end + + def answerable_by?(user) + user.present? && + user.level_two_or_three_verified? && + current? && + (!geozone_restricted || geozone_ids.include?(user.geozone_id)) + end + + def self.answerable_by(user) + return none if user.nil? || user.unverified? + current.joins('LEFT JOIN "geozones_polls" ON "geozones_polls"."poll_id" = "polls"."id"') + .where('geozone_restricted = ? OR geozones_polls.geozone_id = ?', false, user.geozone_id) + end + + def votable_by?(user) + !document_has_voted?(user.document_number, user.document_type) + end + + def document_has_voted?(document_number, document_type) + voters.where(document_number: document_number, document_type: document_type).exists? + end + + def date_range + unless starts_at.present? && ends_at.present? && starts_at <= ends_at + errors.add(:starts_at, I18n.t('errors.messages.invalid_date_range')) + end + end + +end diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb new file mode 100644 index 000000000..52fb11469 --- /dev/null +++ b/app/models/poll/answer.rb @@ -0,0 +1,19 @@ +class Poll::Answer < ActiveRecord::Base + + belongs_to :question, -> { with_hidden } + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + + delegate :poll, :poll_id, to: :question + + validates :question, presence: true + validates :author, presence: true + validates :answer, presence: true + validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + scope :by_question, -> (question_id) { where(question_id: question_id) } + + def record_voter_participation + Poll::Voter.create!(user: author, poll: poll) + end +end \ No newline at end of file diff --git a/app/models/poll/booth.rb b/app/models/poll/booth.rb new file mode 100644 index 000000000..c7fb63efc --- /dev/null +++ b/app/models/poll/booth.rb @@ -0,0 +1,13 @@ +class Poll + class Booth < ActiveRecord::Base + has_many :booth_assignments, class_name: "Poll::BoothAssignment" + has_many :polls, through: :booth_assignments + + validates :name, presence: true, uniqueness: true + + def self.search(terms) + return Booth.none if terms.blank? + Booth.where("name ILIKE ? OR location ILIKE ?", "%#{terms}%", "%#{terms}%") + end + end +end \ No newline at end of file diff --git a/app/models/poll/booth_assignment.rb b/app/models/poll/booth_assignment.rb new file mode 100644 index 000000000..0519fffa6 --- /dev/null +++ b/app/models/poll/booth_assignment.rb @@ -0,0 +1,15 @@ +class Poll + class BoothAssignment < ActiveRecord::Base + belongs_to :booth + belongs_to :poll + + has_many :officer_assignments, class_name: "Poll::OfficerAssignment", dependent: :destroy + has_many :recounts, class_name: "Poll::Recount", dependent: :destroy + has_many :final_recounts, class_name: "Poll::FinalRecount", dependent: :destroy + has_many :officers, through: :officer_assignments + has_many :voters + has_many :partial_results + has_many :white_results + has_many :null_results + end +end diff --git a/app/models/poll/final_recount.rb b/app/models/poll/final_recount.rb new file mode 100644 index 000000000..6ebf5eede --- /dev/null +++ b/app/models/poll/final_recount.rb @@ -0,0 +1,19 @@ +class Poll + class FinalRecount < ActiveRecord::Base + belongs_to :booth_assignment, class_name: "Poll::BoothAssignment" + belongs_to :officer_assignment, class_name: "Poll::OfficerAssignment" + + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :count, presence: true, numericality: {only_integer: true} + + before_save :update_logs + + def update_logs + if self.count_changed? && self.count_was.present? + self.count_log += ":#{self.count_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + end + end + end +end \ No newline at end of file diff --git a/app/models/poll/null_result.rb b/app/models/poll/null_result.rb new file mode 100644 index 000000000..222432c7f --- /dev/null +++ b/app/models/poll/null_result.rb @@ -0,0 +1,23 @@ +class Poll::NullResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :author, presence: true + validates :origin, inclusion: {in: VALID_ORIGINS} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/poll/officer.rb b/app/models/poll/officer.rb new file mode 100644 index 000000000..bf4c73c36 --- /dev/null +++ b/app/models/poll/officer.rb @@ -0,0 +1,26 @@ +class Poll + class Officer < ActiveRecord::Base + belongs_to :user + has_many :officer_assignments, class_name: "Poll::OfficerAssignment" + has_many :failed_census_calls, foreign_key: :poll_officer_id + + validates :user_id, presence: true, uniqueness: true + + delegate :name, :email, to: :user + + def voting_days_assigned_polls + officer_assignments.voting_days.includes(booth_assignment: :poll). + map(&:booth_assignment). + map(&:poll).uniq.compact. + sort {|x, y| y.ends_at <=> x.ends_at} + end + + def final_days_assigned_polls + officer_assignments.final.includes(booth_assignment: :poll). + map(&:booth_assignment). + map(&:poll).uniq.compact. + sort {|x, y| y.ends_at <=> x.ends_at} + end + + end +end diff --git a/app/models/poll/officer_assignment.rb b/app/models/poll/officer_assignment.rb new file mode 100644 index 000000000..cd4f53266 --- /dev/null +++ b/app/models/poll/officer_assignment.rb @@ -0,0 +1,25 @@ +class Poll + class OfficerAssignment < ActiveRecord::Base + belongs_to :officer + belongs_to :booth_assignment + has_one :recount + has_many :final_recounts + has_many :partial_results + has_many :voters + + validates :officer_id, presence: true + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: { scope: [:officer_id, :booth_assignment_id] } + + delegate :poll_id, :booth_id, to: :booth_assignment + + scope :voting_days, -> { where(final: false) } + scope :final, -> { where(final: true) } + + before_create :log_user_data + + def log_user_data + self.user_data_log = "#{officer.user_id} - #{officer.user.name_and_email}" + end + end +end diff --git a/app/models/poll/partial_result.rb b/app/models/poll/partial_result.rb new file mode 100644 index 000000000..e42589a03 --- /dev/null +++ b/app/models/poll/partial_result.rb @@ -0,0 +1,28 @@ +class Poll::PartialResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + belongs_to :question, -> { with_hidden } + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :question, presence: true + validates :author, presence: true + validates :answer, presence: true + validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }} + validates :origin, inclusion: {in: VALID_ORIGINS} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + scope :by_question, -> (question_id) { where(question_id: question_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb new file mode 100644 index 000000000..dd4eae3bc --- /dev/null +++ b/app/models/poll/question.rb @@ -0,0 +1,70 @@ +class Poll::Question < ActiveRecord::Base + include Measurable + include Searchable + + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :poll + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + + has_many :comments, as: :commentable + has_many :answers + has_many :partial_results + belongs_to :proposal + + validates :title, presence: true + validates :author, presence: true + + validates :title, length: { minimum: 4 } + validates :description, length: { maximum: Poll::Question.description_max_length } + + scope :by_poll_id, ->(poll_id) { where(poll_id: poll_id) } + + scope :sort_for_list, -> { order('poll_questions.proposal_id IS NULL', :created_at)} + scope :for_render, -> { includes(:author, :proposal) } + + def self.search(params) + results = self.all + results = results.by_poll_id(params[:poll_id]) if params[:poll_id].present? + results = results.pg_search(params[:search]) if params[:search].present? + results + end + + def searchable_values + { title => 'A', + proposal.try(:title) => 'A', + description => 'B', + author.username => 'C', + author_visible_name => 'C' } + end + + def description + super.try :html_safe + end + + def valid_answers + (super.try(:split, ',').compact || []).map(&:strip) + end + + def copy_attributes_from_proposal(proposal) + if proposal.present? + self.author = proposal.author + self.author_visible_name = proposal.author.name + self.proposal_id = proposal.id + self.title = proposal.title + self.description = proposal.description + self.valid_answers = I18n.t('poll_questions.default_valid_answers') + end + end + + def answerable_by?(user) + poll.answerable_by?(user) + end + + def self.answerable_by(user) + return none if user.nil? || user.unverified? + where(poll_id: Poll.answerable_by(user).pluck(:id)) + end + +end diff --git a/app/models/poll/recount.rb b/app/models/poll/recount.rb new file mode 100644 index 000000000..b4e28583e --- /dev/null +++ b/app/models/poll/recount.rb @@ -0,0 +1,20 @@ +class Poll + class Recount < ActiveRecord::Base + belongs_to :booth_assignment, class_name: "Poll::BoothAssignment" + belongs_to :officer_assignment, class_name: "Poll::OfficerAssignment" + + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :officer_assignment_id, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :count, presence: true, numericality: {only_integer: true} + + before_save :update_logs + + def update_logs + if self.count_changed? && self.count_was.present? + self.count_log += ":#{self.count_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + end + end + end +end \ No newline at end of file diff --git a/app/models/poll/voter.rb b/app/models/poll/voter.rb new file mode 100644 index 000000000..8fe612151 --- /dev/null +++ b/app/models/poll/voter.rb @@ -0,0 +1,59 @@ +class Poll + class Voter < ActiveRecord::Base + belongs_to :poll + belongs_to :user + belongs_to :geozone + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :poll_id, presence: true + validates :user_id, presence: true + + validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted } + + before_validation :set_demographic_info, :set_document_info + + def set_demographic_info + return unless user.present? + + self.gender = user.gender + self.age = user.age + self.geozone = user.geozone + end + + def set_document_info + return unless user.present? + + self.document_type = user.document_type + self.document_number = user.document_number + end + + private + + def in_census? + census_api_response.valid? + end + + def census_api_response + @census_api_response ||= CensusApi.new.call(document_type, document_number) + end + + def fill_stats_fields + if in_census? + self.gender = census_api_response.gender + self.geozone_id = Geozone.select(:id).where(census_code: census_api_response.district_code).first.try(:id) + self.age = voter_age(census_api_response.date_of_birth) + end + end + + def voter_age(dob) + if dob.blank? + nil + else + now = Time.now.utc.to_date + now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1) + end + end + + end +end \ No newline at end of file diff --git a/app/models/poll/white_result.rb b/app/models/poll/white_result.rb new file mode 100644 index 000000000..5b0aa4966 --- /dev/null +++ b/app/models/poll/white_result.rb @@ -0,0 +1,23 @@ +class Poll::WhiteResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :author, presence: true + validates :origin, inclusion: {in: VALID_ORIGINS} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/proposal.rb b/app/models/proposal.rb index f437fa134..0abde3584 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -7,14 +7,16 @@ class Proposal < ActiveRecord::Base include Searchable include Filterable - apply_simple_captcha acts_as_votable acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases + RETIRE_OPTIONS = %w(duplicated started unfeasible done other) + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' belongs_to :geozone has_many :comments, as: :commentable + has_many :proposal_notifications validates :title, presence: true validates :question, presence: true @@ -26,6 +28,7 @@ class Proposal < ActiveRecord::Base validates :description, length: { maximum: Proposal.description_max_length } validates :question, length: { in: 10..Proposal.question_max_length } validates :responsible_name, length: { in: 6..Proposal.responsible_name_max_length } + validates :retired_reason, inclusion: {in: RETIRE_OPTIONS, allow_nil: true} validates :terms_of_service, acceptance: { allow_nil: false }, on: :create @@ -41,7 +44,13 @@ class Proposal < ActiveRecord::Base scope :sort_by_random, -> { reorder("RANDOM()") } scope :sort_by_relevance, -> { all } scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) } + scope :sort_by_archival_date, -> { archived.sort_by_confidence_score } + scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago) } + scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago) } scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)} + scope :retired, -> { where.not(retired_at: nil) } + scope :not_retired, -> { where(retired_at: nil) } + scope :successful, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success) } def to_param "#{id}-#{title}".parameterize @@ -85,12 +94,12 @@ class Proposal < ActiveRecord::Base summary end - def description - super.try :html_safe + def total_votes + cached_votes_up end - def total_votes - cached_votes_up + physical_votes + def voters + User.active.where(id: votes_for.voters) end def editable? @@ -105,8 +114,12 @@ class Proposal < ActiveRecord::Base user && user.level_two_or_three_verified? end + def retired? + retired_at.present? + end + def register_vote(user, vote_value) - if votable_by?(user) + if votable_by?(user) && !archived? vote_by(voter: user, vote: vote_value) end end @@ -142,6 +155,18 @@ class Proposal < ActiveRecord::Base Setting['votes_for_proposal_success'].to_i end + def successful? + total_votes >= Proposal.votes_needed_for_success + end + + def archived? + self.created_at <= Setting["months_to_archive_proposals"].to_i.months.ago + end + + def notifications + proposal_notifications + end + protected def set_responsible_name diff --git a/app/models/proposal_notification.rb b/app/models/proposal_notification.rb new file mode 100644 index 000000000..60912d887 --- /dev/null +++ b/app/models/proposal_notification.rb @@ -0,0 +1,17 @@ +class ProposalNotification < ActiveRecord::Base + belongs_to :author, class_name: 'User', foreign_key: 'author_id' + belongs_to :proposal + + validates :title, presence: true + validates :body, presence: true + validates :proposal, presence: true + validate :minimum_interval + + def minimum_interval + return true if proposal.try(:notifications).blank? + if proposal.notifications.last.created_at > (Time.current - Setting[:proposal_notification_minimum_interval_in_days].to_i.days).to_datetime + errors.add(:title, I18n.t('activerecord.errors.models.proposal_notification.attributes.minimum_interval.invalid', interval: Setting[:proposal_notification_minimum_interval_in_days])) + end + end + +end diff --git a/app/models/setting.rb b/app/models/setting.rb index 40659ed74..9010abba5 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -2,6 +2,20 @@ class Setting < ActiveRecord::Base validates :key, presence: true, uniqueness: true default_scope { order(id: :asc) } + scope :banner_style, -> { where("key ilike ?", "banner-style.%")} + scope :banner_img, -> { where("key ilike ?", "banner-img.%")} + + def type + if feature_flag? + 'feature' + elsif banner_style? + 'banner-style' + elsif banner_img? + 'banner-img' + else + 'common' + end + end def feature_flag? key.start_with?('feature.') @@ -11,6 +25,14 @@ class Setting < ActiveRecord::Base feature_flag? && value.present? end + def banner_style? + key.start_with?('banner-style.') + end + + def banner_img? + key.start_with?('banner-img.') + end + class << self def [](key) where(key: key).pluck(:value).first.presence diff --git a/app/models/signature.rb b/app/models/signature.rb new file mode 100644 index 000000000..543965aed --- /dev/null +++ b/app/models/signature.rb @@ -0,0 +1,97 @@ +class Signature < ActiveRecord::Base + belongs_to :signature_sheet + belongs_to :user + + validates :document_number, presence: true + validates :signature_sheet, presence: true + + scope :verified, -> { where(verified: true) } + scope :unverified, -> { where(verified: false) } + + delegate :signable, to: :signature_sheet + + before_validation :clean_document_number + + def verify + if user_exists? + assign_vote_to_user + mark_as_verified + elsif in_census? + create_user + assign_vote_to_user + mark_as_verified + end + end + + def assign_vote_to_user + set_user + if signable.is_a? Budget::Investment + signable.vote_by(voter: user, vote: 'yes') if [nil, :no_selecting_allowed].include?(signable.reason_for_not_being_selectable_by(user)) + else + signable.register_vote(user, "yes") + end + assign_signature_to_vote + end + + def assign_signature_to_vote + vote = Vote.where(votable: signable, voter: user).first + vote.update(signature: self) if vote + end + + def user_exists? + User.where(document_number: document_number).any? + end + + def create_user + user_params = { + document_number: document_number, + created_from_signature: true, + verified_at: Time.current, + erased_at: Time.current, + password: random_password, + terms_of_service: '1', + email: nil, + date_of_birth: @census_api_response.date_of_birth, + gender: @census_api_response.gender, + geozone: Geozone.where(census_code: @census_api_response.district_code).first + } + User.create!(user_params) + end + + def clean_document_number + return if self.document_number.blank? + self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase + end + + def random_password + (0...20).map { ('a'..'z').to_a[rand(26)] }.join + end + + def in_census? + document_types.detect do |document_type| + response = CensusApi.new.call(document_type, document_number) + if response.valid? + @census_api_response = response + true + else + false + end + end + + @census_api_response.present? + end + + def set_user + user = User.where(document_number: document_number).first + update(user: user) + end + + def mark_as_verified + update(verified: true) + end + + def document_types + %w(1 2 3 4) + end + +end \ No newline at end of file diff --git a/app/models/signature_sheet.rb b/app/models/signature_sheet.rb new file mode 100644 index 000000000..1434143ac --- /dev/null +++ b/app/models/signature_sheet.rb @@ -0,0 +1,38 @@ +class SignatureSheet < ActiveRecord::Base + belongs_to :signable, polymorphic: true + belongs_to :author, class_name: 'User', foreign_key: 'author_id' + + VALID_SIGNABLES = %w( Proposal Budget::Investment SpendingProposal ) + + has_many :signatures + + validates :author, presence: true + validates :signable_type, inclusion: {in: VALID_SIGNABLES} + validates :document_numbers, presence: true + validates :signable, presence: true + validate :signable_found + + def name + "#{signable_name} #{signable_id}" + end + + def signable_name + I18n.t("activerecord.models.#{signable_type.underscore}", count: 1) + end + + def verify_signatures + parsed_document_numbers.each do |document_number| + signature = self.signatures.where(document_number: document_number).first_or_create + signature.verify + end + update(processed: true) + end + + def parsed_document_numbers + document_numbers.split(/\r\n|\n|[,]/).collect {|d| d.gsub(/\s+/, '') } + end + + def signable_found + errors.add(:signable_id, :not_found) if errors.messages[:signable].present? + end +end \ No newline at end of file diff --git a/app/models/site_customization.rb b/app/models/site_customization.rb new file mode 100644 index 000000000..e5d2f2137 --- /dev/null +++ b/app/models/site_customization.rb @@ -0,0 +1,5 @@ +module SiteCustomization + def self.table_name_prefix + 'site_customization_' + end +end diff --git a/app/models/site_customization/content_block.rb b/app/models/site_customization/content_block.rb new file mode 100644 index 000000000..c08beb52e --- /dev/null +++ b/app/models/site_customization/content_block.rb @@ -0,0 +1,11 @@ +class SiteCustomization::ContentBlock < ActiveRecord::Base + VALID_BLOCKS = %w(top_links footer) + + validates :locale, presence: true, inclusion: { in: I18n.available_locales.map(&:to_s) } + validates :name, presence: true, uniqueness: { scope: :locale }, inclusion: { in: VALID_BLOCKS } + + def self.block_for(name, locale) + locale ||= I18n.default_locale + find_by(name: name, locale: locale).try(:body) + end +end diff --git a/app/models/site_customization/image.rb b/app/models/site_customization/image.rb new file mode 100644 index 000000000..2230a96ce --- /dev/null +++ b/app/models/site_customization/image.rb @@ -0,0 +1,48 @@ +class SiteCustomization::Image < ActiveRecord::Base + VALID_IMAGES = { + "icon_home" => [330, 240], + "logo_header" => [80, 80], + "social-media-icon" => [200, 200], + "apple-touch-icon-200" => [200, 200] + } + + has_attached_file :image + + validates :name, presence: true, uniqueness: true, inclusion: { in: VALID_IMAGES.keys } + validates_attachment_content_type :image, :content_type => ["image/png"] + validate :check_image + + def self.all_images + VALID_IMAGES.keys.map do |image_name| + find_by(name: image_name) || create!(name: image_name.to_s) + end + end + + def self.image_path_for(filename) + image_name = filename.split(".").first + + if i = find_by(name: image_name) + i.image.exists? ? i.image.url : nil + end + end + + def required_width + VALID_IMAGES[name].try(:first) + end + + def required_height + VALID_IMAGES[name].try(:second) + end + + private + + def check_image + return unless image? + + dimensions = Paperclip::Geometry.from_file(image.queued_for_write[:original].path) + + errors.add(:image, :image_width, required_width: required_width) unless dimensions.width == required_width + errors.add(:image, :image_height, required_height: required_height) unless dimensions.height == required_height + end + +end diff --git a/app/models/site_customization/page.rb b/app/models/site_customization/page.rb new file mode 100644 index 000000000..c2a9b1467 --- /dev/null +++ b/app/models/site_customization/page.rb @@ -0,0 +1,16 @@ +class SiteCustomization::Page < ActiveRecord::Base + VALID_STATUSES = %w(draft published) + + validates :slug, presence: true, + uniqueness: { case_sensitive: false }, + format: { with: /\A[0-9a-zA-Z\-_]*\Z/, message: :slug_format } + validates :title, presence: true + validates :status, presence: true, inclusion: { in: VALID_STATUSES } + + scope :published, -> { where(status: 'published').order('id DESC') } + scope :with_more_info_flag, -> { where(status: 'published', more_info_flag: true).order('id ASC') } + + def url + "/#{slug}" + end +end diff --git a/app/models/spending_proposal.rb b/app/models/spending_proposal.rb index f11dc71c3..223e9adfe 100644 --- a/app/models/spending_proposal.rb +++ b/app/models/spending_proposal.rb @@ -4,7 +4,6 @@ class SpendingProposal < ActiveRecord::Base include Taggable include Searchable - apply_simple_captcha acts_as_votable belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' @@ -16,6 +15,7 @@ class SpendingProposal < ActiveRecord::Base validates :title, presence: true validates :author, presence: true validates :description, presence: true + validates_presence_of :feasible_explanation, if: :feasible_explanation_required? validates :title, length: { in: 4..SpendingProposal.title_max_length } validates :description, length: { maximum: SpendingProposal.description_max_length } @@ -29,6 +29,7 @@ class SpendingProposal < ActiveRecord::Base scope :feasible, -> { where(feasible: true) } scope :unfeasible, -> { where(feasible: false) } scope :not_unfeasible, -> { where("feasible IS ? OR feasible = ?", nil, true) } + scope :with_supports, -> { where('cached_votes_up > 0') } scope :by_admin, -> (admin) { where(administrator_id: admin.presence) } scope :by_tag, -> (tag_name) { tagged_with(tag_name) } @@ -36,6 +37,8 @@ class SpendingProposal < ActiveRecord::Base scope :for_render, -> { includes(:geozone) } + before_validation :set_responsible_name + def description super.try :html_safe end @@ -97,8 +100,12 @@ class SpendingProposal < ActiveRecord::Base valuation_finished end + def feasible_explanation_required? + valuation_finished? && unfeasible? + end + def total_votes - cached_votes_up + cached_votes_up + physical_votes end def code @@ -107,11 +114,19 @@ class SpendingProposal < ActiveRecord::Base def send_unfeasible_email Mailer.unfeasible_spending_proposal(self).deliver_later - update(unfeasible_email_sent_at: Time.now) + update(unfeasible_email_sent_at: Time.current) + end + + def reason_for_not_being_votable_by(user) + return :not_voting_allowed if Setting["feature.spending_proposal_features.voting_allowed"].blank? + return :not_logged_in unless user + return :not_verified unless user.can?(:vote, SpendingProposal) + return :unfeasible if unfeasible? + return :organization if user.organization? end def votable_by?(user) - user && user.level_two_or_three_verified? + reason_for_not_being_votable_by(user).blank? end def register_vote(user, vote_value) @@ -120,4 +135,16 @@ class SpendingProposal < ActiveRecord::Base end end + def set_responsible_name + self.responsible_name = author.try(:document_number) if author.try(:document_number).present? + end + + def self.finished_and_feasible + valuation_finished.feasible + end + + def self.finished_and_unfeasible + valuation_finished.unfeasible + end + end diff --git a/app/models/tag_cloud.rb b/app/models/tag_cloud.rb index f3ea655f0..107ecbf1a 100644 --- a/app/models/tag_cloud.rb +++ b/app/models/tag_cloud.rb @@ -32,7 +32,7 @@ class TagCloud end def table_name - resource_model.to_s.downcase.pluralize + resource_model.to_s.downcase.pluralize.gsub("::", "/") end end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb index bc4b0f120..c3038c88a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,9 +2,8 @@ class User < ActiveRecord::Base include Verification - apply_simple_captcha - devise :database_authenticatable, :registerable, :confirmable, - :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :async + devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, + :trackable, :validatable, :omniauthable, :async, :password_expirable, :secure_validatable acts_as_voter acts_as_paranoid column: :hidden_at @@ -13,20 +12,25 @@ class User < ActiveRecord::Base has_one :administrator has_one :moderator has_one :valuator + has_one :manager + has_one :poll_officer, class_name: "Poll::Officer" has_one :organization has_one :lock has_many :flags has_many :identities, dependent: :destroy has_many :debates, -> { with_hidden }, foreign_key: :author_id has_many :proposals, -> { with_hidden }, foreign_key: :author_id + has_many :budget_investments, -> { with_hidden }, foreign_key: :author_id, class_name: 'Budget::Investment' has_many :comments, -> { with_hidden } has_many :spending_proposals, foreign_key: :author_id has_many :failed_census_calls has_many :notifications + has_many :direct_messages_sent, class_name: 'DirectMessage', foreign_key: :sender_id + has_many :direct_messages_received, class_name: 'DirectMessage', foreign_key: :receiver_id belongs_to :geozone validates :username, presence: true, if: :username_required? - validates :username, uniqueness: true, if: :username_required? + validates :username, uniqueness: { scope: :registering_with_oauth }, if: :username_required? validates :document_number, uniqueness: { scope: :document_type }, allow_nil: true validate :validate_username_length @@ -50,6 +54,9 @@ class User < ActiveRecord::Base scope :officials, -> { where("official_level > 0") } scope :for_render, -> { includes(:organization) } scope :by_document, -> (document_type, document_number) { where(document_type: document_type, document_number: document_number) } + scope :email_digest, -> { where(email_digest: true) } + scope :active, -> { where(erased_at: nil) } + scope :erased, -> { where.not(erased_at: nil) } before_validation :clean_document_number @@ -65,7 +72,7 @@ class User < ActiveRecord::Base oauth_email: oauth_email, password: Devise.friendly_token[0,20], terms_of_service: '1', - confirmed_at: oauth_email_confirmed ? DateTime.now : nil + confirmed_at: oauth_email_confirmed ? DateTime.current : nil ) end @@ -88,11 +95,20 @@ class User < ActiveRecord::Base voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } end + def budget_investment_votes(budget_investments) + voted = votes.for_budget_investments(budget_investments) + voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } + end + def comment_flags(comments) comment_flags = flags.for_comments(comments) comment_flags.each_with_object({}){ |f, h| h[f.flaggable_id] = true } end + def voted_in_group?(group) + votes.for_budget_investments(Budget::Investment.where(group: group)).exists? + end + def administrator? administrator.present? end @@ -105,6 +121,14 @@ class User < ActiveRecord::Base valuator.present? end + def manager? + manager.present? + end + + def poll_officer? + poll_officer.present? + end + def organization? organization.present? end @@ -126,6 +150,16 @@ class User < ActiveRecord::Base update official_position: nil, official_level: 0 end + def has_official_email? + domain = Setting['email_domain_for_officials'] + !email.blank? && ( (email.end_with? "@#{domain}") || (email.end_with? ".#{domain}") ) + end + + def display_official_position_badge? + return true if official_level > 1 + official_position_badge? && official_level == 1 + end + def block debates_ids = Debate.where(author_id: id).pluck(:id) comments_ids = Comment.where(user_id: id).pluck(:id) @@ -140,12 +174,11 @@ class User < ActiveRecord::Base def erase(erase_reason = nil) self.update( - erased_at: Time.now, + erased_at: Time.current, erase_reason: erase_reason, username: nil, email: nil, unconfirmed_email: nil, - document_number: nil, phone_number: nil, encrypted_password: "", confirmation_token: nil, @@ -154,12 +187,29 @@ class User < ActiveRecord::Base confirmed_phone: nil, unconfirmed_phone: nil ) + self.identities.destroy_all end def erased? erased_at.present? end + def take_votes_if_erased_document(document_number, document_type) + erased_user = User.erased.where(document_number: document_number).where(document_type: document_type).first + if erased_user.present? + self.take_votes_from(erased_user) + erased_user.update(document_number: nil, document_type: nil) + end + end + + def take_votes_from(other_user) + return if other_user.blank? + Poll::Voter.where(user_id: other_user.id).update_all(user_id: self.id) + Budget::Ballot.where(user_id: other_user.id).update_all(user_id: self.id) + Vote.where("voter_id = ? AND voter_type = ?", other_user.id, "User").update_all(voter_id: self.id) + self.update(former_users_data_log: "#{self.former_users_data_log} | id: #{other_user.id} - #{Time.current.strftime('%Y-%m-%d %H:%M:%S')}") + end + def locked? Lock.find_or_create_by(user: self).locked? end @@ -172,6 +222,10 @@ class User < ActiveRecord::Base @@username_max_length ||= self.columns.find { |c| c.name == 'username' }.limit || 60 end + def self.minimum_required_age + (Setting['min_age_to_participate'] || 16).to_i + end + def show_welcome_screen? sign_in_count == 1 && unverified? && !organization && !administrator? end @@ -182,16 +236,11 @@ class User < ActiveRecord::Base end def username_required? - !organization? && !erased? && !registering_with_oauth + !organization? && !erased? end def email_required? - !erased? && !registering_with_oauth - end - - def has_official_email? - domain = Setting['email_domain_for_officials'] - !email.blank? && ( (email.end_with? "@#{domain}") || (email.end_with? ".#{domain}") ) + !erased? end def locale @@ -214,15 +263,29 @@ class User < ActiveRecord::Base "#{name} (#{email})" end - def save_requiring_finish_signup - self.update(registering_with_oauth: true) + def age + Age.in_years(date_of_birth) end - def save_requiring_finish_signup_without_email - self.update(registering_with_oauth: true, email: nil) + def save_requiring_finish_signup + begin + self.registering_with_oauth = true + self.save(validate: false) + # Devise puts unique constraints for the email the db, so we must detect & handle that + rescue ActiveRecord::RecordNotUnique + self.email = nil + self.save(validate: false) + end + true end + def ability + @ability ||= Ability.new(self) + end + delegate :can?, :cannot?, to: :ability + private + def clean_document_number self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase unless self.document_number.blank? end diff --git a/app/models/valuation_assignment.rb b/app/models/valuation_assignment.rb index 8f50d9082..a0505a8aa 100644 --- a/app/models/valuation_assignment.rb +++ b/app/models/valuation_assignment.rb @@ -1,4 +1,4 @@ class ValuationAssignment < ActiveRecord::Base - belongs_to :valuator + belongs_to :valuator, counter_cache: :spending_proposals_count belongs_to :spending_proposal, counter_cache: true end diff --git a/app/models/valuator.rb b/app/models/valuator.rb index c7cb6e4a8..5df6ea030 100644 --- a/app/models/valuator.rb +++ b/app/models/valuator.rb @@ -4,10 +4,16 @@ class Valuator < ActiveRecord::Base has_many :valuation_assignments, dependent: :destroy has_many :spending_proposals, through: :valuation_assignments + has_many :valuator_assignments, dependent: :destroy, class_name: 'Budget::ValuatorAssignment' + has_many :investments, through: :valuator_assignments, class_name: 'Budget::Investment' validates :user_id, presence: true, uniqueness: true def description_or_email description.present? ? description : email end + + def description_or_name + description.present? ? description : name + end end diff --git a/app/models/verification/letter.rb b/app/models/verification/letter.rb index 0beb7c8d8..c746e73e4 100644 --- a/app/models/verification/letter.rb +++ b/app/models/verification/letter.rb @@ -17,7 +17,7 @@ class Verification::Letter end def letter_requested! - user.update(letter_requested_at: Time.now, letter_verification_code: generate_verification_code) + user.update(letter_requested_at: Time.current, letter_verification_code: generate_verification_code) end def validate_existing_user diff --git a/app/models/verification/management/document.rb b/app/models/verification/management/document.rb index ee9e5462d..420dcf49c 100644 --- a/app/models/verification/management/document.rb +++ b/app/models/verification/management/document.rb @@ -10,7 +10,7 @@ class Verification::Management::Document delegate :username, :email, to: :user, allow_nil: true def user - @user = User.by_document(document_type, document_number).first + @user = User.active.by_document(document_type, document_number).first end def user? @@ -23,7 +23,7 @@ class Verification::Management::Document end def valid_age?(response) - if under_sixteen?(response) + if under_age?(response) errors.add(:age, true) return false else @@ -31,8 +31,8 @@ class Verification::Management::Document end end - def under_sixteen?(response) - 16.years.ago < string_to_date(response.date_of_birth) + def under_age?(response) + response.date_of_birth.blank? || Age.in_years(response.date_of_birth) < User.minimum_required_age end def verified? @@ -40,7 +40,7 @@ class Verification::Management::Document end def verify - user.update(verified_at: Time.now) if user? + user.update(verified_at: Time.current) if user? end -end \ No newline at end of file +end diff --git a/app/models/verification/management/email.rb b/app/models/verification/management/email.rb index 33282b569..de13d1ab4 100644 --- a/app/models/verification/management/email.rb +++ b/app/models/verification/management/email.rb @@ -27,8 +27,8 @@ class Verification::Management::Email user.update(document_type: document_type, document_number: document_number, - residence_verified_at: Time.now, - level_two_verified_at: Time.now, + residence_verified_at: Time.current, + level_two_verified_at: Time.current, email_verification_token: plain_token) Mailer.email_verification(user, email, encrypted_token, document_type, document_number).deliver_later diff --git a/app/models/verification/residence.rb b/app/models/verification/residence.rb index 0c1ed5f9a..ea000677f 100644 --- a/app/models/verification/residence.rb +++ b/app/models/verification/residence.rb @@ -16,8 +16,6 @@ class Verification::Residence validate :allowed_age validate :document_number_uniqueness - validate :postal_code_in_madrid - validate :residence_in_madrid def initialize(attrs={}) self.date_of_birth = parse_date('date_of_birth', attrs) @@ -28,33 +26,24 @@ class Verification::Residence def save return false unless valid? + + user.take_votes_if_erased_document(document_number, document_type) + user.update(document_number: document_number, document_type: document_type, geozone: self.geozone, - residence_verified_at: Time.now) + date_of_birth: date_of_birth.to_datetime, + gender: gender, + residence_verified_at: Time.current) end def allowed_age return if errors[:date_of_birth].any? - errors.add(:date_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) unless self.date_of_birth <= 16.years.ago + errors.add(:date_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) unless Age.in_years(self.date_of_birth) >= User.minimum_required_age end def document_number_uniqueness - errors.add(:document_number, I18n.t('errors.messages.taken')) if User.where(document_number: document_number).any? - end - - def postal_code_in_madrid - errors.add(:postal_code, I18n.t('verification.residence.new.error_not_allowed_postal_code')) unless valid_postal_code? - end - - def residence_in_madrid - return if errors.any? - - unless residency_valid? - errors.add(:residence_in_madrid, false) - store_failed_attempt - Lock.increase_tries(user) - end + errors.add(:document_number, I18n.t('errors.messages.taken')) if User.active.where(document_number: document_number).any? end def store_failed_attempt @@ -75,6 +64,10 @@ class Verification::Residence @census_api_response.district_code end + def gender + @census_api_response.gender + end + private def call_census_api @@ -84,15 +77,11 @@ class Verification::Residence def residency_valid? @census_api_response.valid? && @census_api_response.postal_code == postal_code && - @census_api_response.date_of_birth == date_to_string(date_of_birth) + @census_api_response.date_of_birth == date_of_birth end def clean_document_number self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase unless self.document_number.blank? end - def valid_postal_code? - postal_code =~ /^280/ - end - end diff --git a/app/models/verification/sms.rb b/app/models/verification/sms.rb index ba484c4f3..1a013f1d8 100644 --- a/app/models/verification/sms.rb +++ b/app/models/verification/sms.rb @@ -4,14 +4,9 @@ class Verification::Sms attr_accessor :user, :phone, :confirmation_code validates_presence_of :phone - validates :phone, length: { is: 9 } - validate :spanish_phone + validates :phone, format: { with: /\A[\d \+]+\z/ } validate :uniqness_phone - def spanish_phone - errors.add(:phone, :invalid) unless phone.start_with?('6', '7') - end - def uniqness_phone errors.add(:phone, :taken) if User.where(confirmed_phone: phone).any? end @@ -40,4 +35,4 @@ class Verification::Sms def generate_confirmation_code rand.to_s[2..5] end -end \ No newline at end of file +end diff --git a/app/views/account/show.html.erb b/app/views/account/show.html.erb index f0d2b6abc..247aa6024 100644 --- a/app/views/account/show.html.erb +++ b/app/views/account/show.html.erb @@ -34,7 +34,9 @@
<%= f.label :public_activity do %> <%= f.check_box :public_activity, title: t('account.show.public_activity_label'), label: false %> - <%= t("account.show.public_activity_label") %> + + <%= t("account.show.public_activity_label") %> + <% end %>
@@ -43,24 +45,61 @@
<%= f.label :email_on_comment do %> <%= f.check_box :email_on_comment, title: t('account.show.email_on_comment_label'), label: false %> - <%= t("account.show.email_on_comment_label") %> + + <%= t("account.show.email_on_comment_label") %> + <% end %>
<%= f.label :email_on_comment_reply do %> <%= f.check_box :email_on_comment_reply, title: t('account.show.email_on_comment_reply_label'), label: false %> - <%= t("account.show.email_on_comment_reply_label") %> + + <%= t("account.show.email_on_comment_reply_label") %> + <% end %>
<%= f.label :email_newsletter_subscribed do %> <%= f.check_box :newsletter, title: t('account.show.subscription_to_website_newsletter_label'), label: false %> - <%= t("account.show.subscription_to_website_newsletter_label") %> + + <%= t("account.show.subscription_to_website_newsletter_label") %> + <% end %>
+
+ <%= f.label :email_digest do %> + <%= f.check_box :email_digest, title: t('account.show.email_digest_label'), label: false %> + + <%= t("account.show.email_digest_label") %> + + <% end %> +
+ +
+ <%= f.label :email_on_direct_message do %> + <%= f.check_box :email_on_direct_message, title: t('account.show.email_on_direct_message_label'), label: false %> + + <%= t("account.show.email_on_direct_message_label") %> + + <% end %> +
+ + <% if @account.official_level == 1 %> +
+ <%= f.label :official_position_badge do %> + <%= f.check_box :official_position_badge, + title: t('account.show.official_position_badge_label'), + label: false %> + + <%= t("account.show.official_position_badge_label") %> + + <% end %> +
+ <% end %> + <%= f.submit t("account.show.save_changes_submit"), class: "button" %> diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index f435c5511..0b9929f46 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -1,109 +1,166 @@ - + diff --git a/app/views/admin/activity/show.html.erb b/app/views/admin/activity/show.html.erb index 0f422cfa8..92ff3e26a 100644 --- a/app/views/admin/activity/show.html.erb +++ b/app/views/admin/activity/show.html.erb @@ -8,7 +8,7 @@ <%= t("admin.activity.show.type") %> <%= t("admin.activity.show.action") %> - + <%= t("admin.activity.show.content") %> <%= t("admin.activity.show.by") %> <% @activity.each do |activity| %> diff --git a/app/views/admin/banners/_errors.html.erb b/app/views/admin/banners/_errors.html.erb new file mode 100644 index 000000000..e50720971 --- /dev/null +++ b/app/views/admin/banners/_errors.html.erb @@ -0,0 +1,15 @@ + +<% if @banner.errors.any? %> + +
+ + + + <%= @banner.errors.count %> + <%= t("admin.banners.errors.form.error", count: @banner.errors.count) %> + +
+ +<% end %> \ No newline at end of file diff --git a/app/views/admin/banners/_form.html.erb b/app/views/admin/banners/_form.html.erb new file mode 100644 index 000000000..9c8d20612 --- /dev/null +++ b/app/views/admin/banners/_form.html.erb @@ -0,0 +1,76 @@ +<%= form_for [:admin, @banner] do |f| %> + + <%= render 'errors' %> + +
+
+ <%= f.label :style, t("admin.banners.banner.style") %> + <%= f.select :style, options_for_select(@banner_styles, @banner.style),:include_blank => '-', + label: false,placeholder: t("admin.banners.banner.style") %> +
+
+ <%= f.label :image, t("admin.banners.banner.image") %> + <%= f.select :image, options_for_select(@banner_imgs, @banner.image),:include_blank => '-', + label: false, placeholder: t("admin.banners.banner.image") %> +
+ <% date_started_at = @banner.post_started_at.present? ? I18n.localize(@banner.post_started_at) : "" %> +
+ <%= f.label :post_started_at, t("admin.banners.banner.post_started_at") %> + <%= f.text_field :post_started_at, + label: false, + placeholder: t("admin.banners.banner.post_started_at"), + value: date_started_at, + class: "js-calendar-full", + id: "post_started_at" %> +
+ <% date_ended_at = @banner.post_ended_at.present? ? I18n.localize(@banner.post_ended_at) : ""%> +
+ <%= f.label :post_ended_at, t("admin.banners.banner.post_ended_at") %> + <%= f.text_field :post_ended_at, + label: false, + placeholder: t("admin.banners.banner.post_ended_at"), + value: date_ended_at, + class: "js-calendar-full", + id: "post_ended_at" %> +
+
+ +
+
+ <%= f.label :title, t("admin.banners.banner.title") %> + <%= f.text_field :title, placeholder: t("admin.banners.banner.title"), label: false, + data: {js_banner_title: "js_banner_title"} %> +
+ +
+ <%= f.label :target_url, t("admin.banners.banner.target_url") %> + <%= f.text_field :target_url, + label: false, + placeholder: t("admin.banners.banner.target_url") %> +
+
+ +
+
+ <%= f.label :description, t("admin.banners.banner.description") %> + <%= f.text_field :description, + label: false, + data: {js_banner_description: "js_banner_description"}, + placeholder: t("admin.banners.banner.description") %> +
+
+ +
+
+ <%= f.submit(class: "button expanded", value: t("admin.banners.edit.form.submit_button")) %> +
+
+
+
+ <%= link_to @banner.target_url do %> +

<%= @banner.title %>

+

<%= @banner.description %>

+ <% end %> +
+
+<% end %> \ No newline at end of file diff --git a/app/views/admin/banners/edit.html.erb b/app/views/admin/banners/edit.html.erb new file mode 100644 index 000000000..17cb11405 --- /dev/null +++ b/app/views/admin/banners/edit.html.erb @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/app/views/admin/banners/index.html.erb b/app/views/admin/banners/index.html.erb new file mode 100644 index 000000000..11f2ef6ab --- /dev/null +++ b/app/views/admin/banners/index.html.erb @@ -0,0 +1,54 @@ +<%= link_to t("admin.banners.index.create"), + new_admin_banner_path, class: "button success float-right" %> + +

<%= t("admin.banners.index.title") %>

+ +<%= render 'shared/filter_subnav', i18n_namespace: "admin.banners.index" %> + +

<%= page_entries_info @banners %>

+ +<% @banners.each do |banner| %> + + + + + + + + + + + + + + + + + + + + + +
<%= t("admin.banners.banner.post_started_at")%><%= t("admin.banners.banner.post_ended_at")%><%= t("admin.actions.actions")%>
<%= banner.post_started_at %><%= banner.post_ended_at %> +
+ <%= link_to t("admin.banners.index.edit"), edit_admin_banner_path(banner), + class: 'button hollow expanded' %> +
+
+ <%= link_to t("admin.banners.index.delete"), admin_banner_path(banner), + method: :delete, + class: 'button hollow alert expanded' %> +
+
<%= t("admin.banners.index.preview") %>
+
+
+ <%= link_to banner.target_url do %> +

<%= banner.title %>

+

<%= banner.description %>

+ <% end %> +
+
+
+<% end %> + +<%= paginate @banners %> \ No newline at end of file diff --git a/app/views/admin/banners/new.html.erb b/app/views/admin/banners/new.html.erb new file mode 100644 index 000000000..dc5eb4ca3 --- /dev/null +++ b/app/views/admin/banners/new.html.erb @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/app/views/admin/budget_groups/create.js.erb b/app/views/admin/budget_groups/create.js.erb new file mode 100644 index 000000000..cb926a7c6 --- /dev/null +++ b/app/views/admin/budget_groups/create.js.erb @@ -0,0 +1,2 @@ +$("#<%= dom_id(@budget) %>_groups").html('<%= j render("admin/budgets/groups", groups: @groups) %>'); +App.Forms.toggleLink(); \ No newline at end of file diff --git a/app/views/admin/budget_headings/create.js.erb b/app/views/admin/budget_headings/create.js.erb new file mode 100644 index 000000000..5d8eefb2d --- /dev/null +++ b/app/views/admin/budget_headings/create.js.erb @@ -0,0 +1,2 @@ +$("#<%= dom_id(@budget_group) %>").html('<%= j render("admin/budgets/group", group: @budget_group, headings: @headings) %>'); +App.Forms.toggleLink(); \ No newline at end of file diff --git a/app/views/admin/budget_investments/_investments.html.erb b/app/views/admin/budget_investments/_investments.html.erb new file mode 100644 index 000000000..475b582f5 --- /dev/null +++ b/app/views/admin/budget_investments/_investments.html.erb @@ -0,0 +1,81 @@ +

<%= page_entries_info @investments %>

+ + + + + + + + + + + + + + + + + <% @investments.each do |investment| %> + + + + + + + + + + + + <% end %> +
<%= t("admin.budget_investments.index.table_id") %><%= t("admin.budget_investments.index.table_title") %><%= t("admin.budget_investments.index.table_supports") %><%= t("admin.budget_investments.index.table_admin") %><%= t("admin.budget_investments.index.table_valuator") %><%= t("admin.budget_investments.index.table_geozone") %><%= t("admin.budget_investments.index.table_feasibility") %><%= t("admin.budget_investments.index.table_valuation_finished") %><%= t("admin.budget_investments.index.table_selection") %>
+ <%= investment.id %> + + <%= link_to investment.title, admin_budget_budget_investment_path(budget_id: @budget.id, id: investment.id, params: Budget::Investment.filter_params(params)) %> + + <%= investment.total_votes %> + + <% if investment.administrator.present? %> + <%= investment.administrator.name %> + <% else %> + <%= t("admin.budget_investments.index.no_admin_assigned") %> + <% end %> + + <% if investment.valuators.size == 0 %> + <%= t("admin.budget_investments.index.no_valuators_assigned") %> + <% else %> + <%= investment.valuators.collect(&:description_or_name).join(', ') %> + <% end %> + + <%= investment.heading.name %> + + <%= t("admin.budget_investments.index.feasibility.#{investment.feasibility}", + price: investment.formatted_price) + %> + + <%= investment.valuation_finished? ? t('shared.yes'): t('shared.no') %> + + <% if investment.selected? %> + <%= link_to toggle_selection_admin_budget_budget_investment_path(@budget, + investment, + filter: params[:filter], + page: params[:page]), + method: :patch, + remote: true, + class: "button small expanded" do %> + <%= t("admin.budget_investments.index.selected") %> + <% end %> + <% elsif investment.feasible? && investment.valuation_finished? %> + <%= link_to toggle_selection_admin_budget_budget_investment_path(@budget, + investment, + filter: params[:filter], + page: params[:page]), + method: :patch, + remote: true, + class: "button small hollow expanded" do %> + <%= t("admin.budget_investments.index.select") %> + <% end %> + <% end %> +
+ +<%= paginate @investments %> diff --git a/app/views/admin/budget_investments/_written_by_author.html.erb b/app/views/admin/budget_investments/_written_by_author.html.erb new file mode 100644 index 000000000..b2c46c6f5 --- /dev/null +++ b/app/views/admin/budget_investments/_written_by_author.html.erb @@ -0,0 +1,36 @@ +
+ <%= t "admin.budget_investments.show.info", budget_name: @budget.name, group_name: @investment.group.name, id: @investment.id %> +
+ +
+

<%= @investment.title %>

+ +
+
+

: <%= @investment.group.name %>"> + <%= t("admin.budget_investments.show.heading") %>: + <%= @investment.heading.name %> +

+
+ +
+

+ <%= t("admin.budget_investments.show.by") %>: + <%= link_to @investment.author.name, admin_user_path(@investment.author) %> +

+
+ +
+

+ <%= t("admin.budget_investments.show.sent") %>: + <%= l @investment.created_at, format: :datetime %> +

+
+ +
+ +<% if @investment.external_url.present? %> +

<%= text_with_links @investment.external_url %> 

+<% end %> + +<%= safe_html_with_links @investment.description %> diff --git a/app/views/admin/budget_investments/edit.html.erb b/app/views/admin/budget_investments/edit.html.erb new file mode 100644 index 000000000..17dd88665 --- /dev/null +++ b/app/views/admin/budget_investments/edit.html.erb @@ -0,0 +1,70 @@ +<%= link_to admin_budget_budget_investment_path(@budget, @investment, Budget::Investment.filter_params(params)), class: 'back' do %> + <%= t("shared.back") %> +<% end %> + +<%= form_for @investment, + url: admin_budget_budget_investment_path(@budget, @investment) do |f| %> + + <% Budget::Investment.filter_params(params).each do |filter_name, filter_value| %> + <%= hidden_field_tag filter_name, filter_value %> + <% end %> + +
+
+ <%= f.text_field :title, maxlength: Budget::Investment.title_max_length %> +
+ +
+ <%= f.cktext_area :description, maxlength: Budget::Investment.description_max_length, ckeditor: { language: I18n.locale } %> +
+ +
+ <%= f.text_field :external_url %> +
+ +
+ <%= f.select :heading_id, budget_heading_select_options(@budget), include_blank: t("admin.budget_investments.edit.select_heading") %> +
+
+ +

<%= t("admin.budget_investments.edit.classification") %>

+ +
+ +
+ <%= f.select(:administrator_id, + @admins.collect{ |a| [a.name_and_email, a.id ] }, + { include_blank: t("admin.budget_investments.edit.undefined") }) %> +
+ + +
+ <%= f.label :tag_list, t("admin.budget_investments.edit.tags") %> +
+ <% @tags.each do |tag| %> + <%= tag.name %> + <% end %> +
+ <%= f.text_field :valuation_tag_list, + value: @investment.tag_list_on(:valuation).sort.join(','), + label: false, + placeholder: t("admin.budget_investments.edit.tags_placeholder"), + class: 'js-tag-list' %> +
+ +
+ <%= f.label :valuator_ids, t("admin.budget_investments.edit.assigned_valuators") %> + + <%= f.collection_check_boxes :valuator_ids, @valuators, :id, :email do |b| %> + <%= b.label(title: valuator_label(b.object)) { b.check_box + truncate(b.object.description_or_email, length: 60) } %> + <% end %> +
+
+ +

+ <%= f.submit(class: "button", value: t("admin.budget_investments.edit.submit_button")) %> +

+<% end %> + +
+<%# render 'valuation/budget_investments/written_by_valuators' %> diff --git a/app/views/admin/budget_investments/index.html.erb b/app/views/admin/budget_investments/index.html.erb new file mode 100644 index 000000000..beb5eb856 --- /dev/null +++ b/app/views/admin/budget_investments/index.html.erb @@ -0,0 +1,44 @@ +

<%= @budget.name %> - <%= t("admin.budget_investments.index.title") %>

+ +
+ <%= form_tag admin_budget_budget_investments_path(budget: @budget), method: :get, enforce_utf8: false do %> +
+ <%= select_tag :administrator_id, + options_for_select(admin_select_options, params[:administrator_id]), + { prompt: t("admin.budget_investments.index.administrator_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
+ +
+ <%= select_tag :valuator_id, + options_for_select(valuator_select_options, params[:valuator_id]), + { prompt: t("admin.budget_investments.index.valuator_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
+ +
+ <%= select_tag :heading_id, + options_for_select(budget_heading_select_options(@budget), params[:heading_id]), + { prompt: t("admin.budget_investments.index.heading_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
+ +
+ <%= select_tag :tag_name, + options_for_select(investment_tags_select_options, params[:tag_name]), + { prompt: t("admin.budget_investments.index.tags_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
+ <% end %> +
+ +<%= render '/shared/filter_subnav', i18n_namespace: "admin.budget_investments.index" %> + +
+ <%= render '/admin/budget_investments/investments' %> +
+ diff --git a/app/views/admin/budget_investments/show.html.erb b/app/views/admin/budget_investments/show.html.erb new file mode 100644 index 000000000..a58110c94 --- /dev/null +++ b/app/views/admin/budget_investments/show.html.erb @@ -0,0 +1,49 @@ +<%= link_to admin_budget_budget_investments_path(Budget::Investment.filter_params(params)), data: {no_turbolink: true} do %> + <%= t("shared.back") %> +<% end %> + +<%= render 'written_by_author' %> + +<%= link_to t("admin.budget_investments.show.edit"), + edit_admin_budget_budget_investment_path(@budget, @investment, + Budget::Investment.filter_params(params)) %> + +
+ +

<%= t("admin.budget_investments.show.classification") %>

+ +

<%= t("admin.budget_investments.show.assigned_admin") %>: + <%= @investment.administrator.try(:name_and_email) || t("admin.budget_investments.show.undefined") %> +

+ +

+ <%= t("admin.budget_investments.show.tags") %>: + + <%= @investment.tags_on(:valuation).pluck(:name).join(', ') %> +

+ +

+ <%= t("admin.budget_investments.show.assigned_valuators") %>: + <% if @investment.valuators.any? %> + <%= @investment.valuators.collect(&:name_and_email).join(', ') %> + <% else %> + <%= t("admin.budget_investments.show.undefined") %> + <% end %> +

+ +

+ <%= link_to t("admin.budget_investments.show.edit_classification"), + edit_admin_budget_budget_investment_path(@budget, @investment, + {anchor: 'classification'}.merge(Budget::Investment.filter_params(params))) %> +

+ +
+ +

<%= t("admin.budget_investments.show.dossier") %>

+ +<%= render 'valuation/budget_investments/written_by_valuators' %> + +

+ <%= link_to t("admin.budget_investments.show.edit_dossier"), edit_valuation_budget_budget_investment_path(@budget, @investment) %> +

+ diff --git a/app/views/admin/budget_investments/toggle_selection.js.erb b/app/views/admin/budget_investments/toggle_selection.js.erb new file mode 100644 index 000000000..dc3a8d67a --- /dev/null +++ b/app/views/admin/budget_investments/toggle_selection.js.erb @@ -0,0 +1 @@ +$("#investments").html('<%= j render("admin/budget_investments/investments") %>'); diff --git a/app/views/admin/budgets/_form.html.erb b/app/views/admin/budgets/_form.html.erb new file mode 100644 index 000000000..5d323b45c --- /dev/null +++ b/app/views/admin/budgets/_form.html.erb @@ -0,0 +1,20 @@ +<%= form_for [:admin, @budget] do |f| %> + + <%= f.text_field :name, maxlength: Budget.title_max_length %> + + <% Budget::PHASES.each do |phase| %> +
+ <%= f.cktext_area "description_#{phase}", maxlength: Budget.description_max_length, ckeditor: { language: I18n.locale } %> +
+ <% end %> + +
+
+ <%= f.select :phase, budget_phases_select_options %> +
+
+ <%= f.select :currency_symbol, budget_currency_symbol_select_options %> +
+
+ <%= f.submit nil, class: "button success" %> +<% end %> diff --git a/app/views/admin/budgets/_group.html.erb b/app/views/admin/budgets/_group.html.erb new file mode 100644 index 000000000..6193fe287 --- /dev/null +++ b/app/views/admin/budgets/_group.html.erb @@ -0,0 +1,67 @@ + + + + + + + <% if headings.blank? %> + + + + + + <% else %> + + + + + + + <% end %> + + + + + + + + <% headings.each do |heading| %> + + + + + <% end %> + + +
+ <%= group.name %> + <%= link_to t("admin.budgets.form.add_heading"), "#", class: "button float-right js-toggle-link", data: { "toggle-selector" => "#group-#{group.id}-new-heading-form" } %> +
+
+ <%= t("admin.budgets.form.no_heading") %> +
+
<%= t("admin.budgets.form.table_heading") %><%= t("admin.budgets.form.table_amount") %>
+ <%= heading.name %> + + <%= heading.price %> +
diff --git a/app/views/admin/budgets/_groups.html.erb b/app/views/admin/budgets/_groups.html.erb new file mode 100644 index 000000000..537659fec --- /dev/null +++ b/app/views/admin/budgets/_groups.html.erb @@ -0,0 +1,32 @@ +

<%= t('admin.budgets.show.groups', count: groups.count) %>

+<% if groups.blank? %> +
+ <%= t("admin.budgets.form.no_groups") %> + <%= link_to t("admin.budgets.form.add_group"), "#", + class: "js-toggle-link", + data: { "toggle-selector" => "#new-group-form" } %> +
+<% else %> + <%= link_to t("admin.budgets.form.add_group"), "#", class: "button float-right js-toggle-link", data: { "toggle-selector" => "#new-group-form" } %> +<% end %> + +<%= form_for [:admin, @budget, Budget::Group.new], html: {id: "new-group-form", style: "display:none"}, remote: true do |f| %> +
+ + + + <%= f.text_field :name, + label: false, + maxlength: 50, + placeholder: t("admin.budgets.form.group") %> +
+ <%= f.submit t("admin.budgets.form.create_group"), class: "button success" %> +
+
+<% end %> + +<% groups.each do |group| %> +
+ <%= render "admin/budgets/group", group: group, headings: group.headings %> +
+<% end %> diff --git a/app/views/admin/budgets/edit.html.erb b/app/views/admin/budgets/edit.html.erb new file mode 100644 index 000000000..6c54cc75b --- /dev/null +++ b/app/views/admin/budgets/edit.html.erb @@ -0,0 +1,5 @@ +<%= back_link_to admin_budgets_path %> + +

<%= t("admin.budgets.edit.title") %>

+ +<%= render '/admin/budgets/form' %> diff --git a/app/views/admin/budgets/index.html.erb b/app/views/admin/budgets/index.html.erb new file mode 100644 index 000000000..87aaa8c66 --- /dev/null +++ b/app/views/admin/budgets/index.html.erb @@ -0,0 +1,44 @@ +

<%= t("admin.budgets.index.title") %>

+ +<%= link_to t("admin.budgets.index.new_link"), + new_admin_budget_path, + class: "button float-right margin-right" %> + +<%= render 'shared/filter_subnav', i18n_namespace: "admin.budgets.index" %> + +

<%= page_entries_info @budgets %>

+ + + + + + + + + + + + + <% @budgets.each do |budget| %> + + + + + + + + <% end %> + +
<%= t("admin.budgets.index.table_name") %><%= t("admin.budgets.index.table_phase") %><%= t("admin.budgets.index.table_investments") %><%= t("admin.budgets.index.table_edit_groups") %><%= t("admin.budgets.index.table_edit_budget") %>
+ <%= budget.name %> + + <%= t("budgets.phase.#{budget.phase}") %> + + <%= link_to t("admin.budgets.index.budget_investments"), admin_budget_budget_investments_path(budget_id: budget.id) %> + + <%= link_to t("admin.budgets.index.edit_groups"), admin_budget_path(budget) %> + + <%= link_to t("admin.budgets.index.edit_budget"), edit_admin_budget_path(budget) %> +
+ +<%= paginate @budgets %> \ No newline at end of file diff --git a/app/views/admin/budgets/new.html.erb b/app/views/admin/budgets/new.html.erb new file mode 100644 index 000000000..3fa0fac89 --- /dev/null +++ b/app/views/admin/budgets/new.html.erb @@ -0,0 +1,7 @@ +
+
+

<%= t("admin.budgets.new.title") %>

+ + <%= render '/admin/budgets/form' %> +
+
diff --git a/app/views/admin/budgets/show.html.erb b/app/views/admin/budgets/show.html.erb new file mode 100644 index 000000000..6c133ab9f --- /dev/null +++ b/app/views/admin/budgets/show.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_budgets_path %> + +

<%= @budget.name %>

+ +
+ <%= render "groups", groups: @budget.groups %> +
diff --git a/app/views/admin/comments/index.html.erb b/app/views/admin/comments/index.html.erb index af436c7c4..75493857c 100644 --- a/app/views/admin/comments/index.html.erb +++ b/app/views/admin/comments/index.html.erb @@ -4,34 +4,32 @@

<%= page_entries_info @comments %>

-
    + <% @comments.each do |comment| %> -
  • -
    -
    - <%= text_with_links comment.body %>
    - <% if comment.commentable.hidden? %> - (<%= t("admin.comments.index.hidden_#{comment.commentable_type.downcase}") %>: <%= comment.commentable.title %>) - <% else %> - <%= link_to comment.commentable.title, comment.commentable %> - <% end %> -
    -
    - <%= link_to t("admin.actions.restore"), - restore_admin_comment_path(comment, request.query_parameters), +
  • + + + <% end %> - +
    + <%= text_with_links comment.body %>
    + <% if comment.commentable.hidden? %> + (<%= t("admin.comments.index.hidden_#{comment.commentable_type.downcase}") %>: <%= comment.commentable.title %>) + <% else %> + <%= link_to comment.commentable.title, comment.commentable %> + <% end %> +
    + <%= link_to t("admin.actions.restore"), + restore_admin_comment_path(comment, request.query_parameters), + method: :put, + data: { confirm: t("admin.actions.confirm") }, + class: "button hollow on-hover-block" %> + <% unless comment.confirmed_hide? %> + <%= link_to t("admin.actions.confirm_hide"), + confirm_hide_admin_comment_path(comment, request.query_parameters), method: :put, - data: { confirm: t("admin.actions.confirm") }, - class: "button small success float-right" %> - <% unless comment.confirmed_hide? %> - <%= link_to t("admin.actions.confirm_hide"), - confirm_hide_admin_comment_path(comment, request.query_parameters), - method: :put, - class: "button small warning float-right" %> - <% end %> - - - + class: "button hollow warning on-hover-block" %> + <% end %> +
    <%= paginate @comments %> diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index da64cba15..2a2ed1c56 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -1,51 +1,99 @@ -
    -

    <%= t("admin.dashboard.index.title") %>

    +<%= link_to admin_settings_path, class: "button float-right" do %> + + <%= t("admin.menu.settings") %> +<% end %> -

    Desde aquí puedes administrar el sistema, a través de las siguientes acciones:

    +<%= link_to admin_stats_path, class: "button float-right" do %> + + <%= t("admin.menu.stats") %> +<% end %> -

    Temas de debate

    -

    Los temas (también llamadas tags, o etiquetas) de debate son palabras que definen los usuarios al crear debates, para catalogarlos (ej: sanidad, movilidad, arganzuela, ...). Aquí se pueden eliminar temas inapropiados, o marcarlos para ser propuestos al crear debates (cada usuario puede definir los que quiera, pero se le sugieren algunos que nos parecen útiles como catalogación por defecto; aquí se puede cambiar cuáles se sugieren)

    +

    <%= t("admin.dashboard.index.title") %>

    -

    Propuestas/Debates/Comentarios ocultos

    -

    Cuando un moderador o un administrador oculta una Propuesta/Debate/Comentario aparecerá en esta lista. De esta forma los administradores pueden revisar que se ha ocultado el elemento adecuado.

    +

    Desde aquí puedes administrar el sistema, a través de las siguientes acciones:

    -
      -
    • Al pulsar Confirmar se acepta el que se haya ocultado, se considera que se ha hecho correctamente.
    • -
    • Al pulsar Volver a mostrar se revierte la acción de ocultar y vuelve a ser una Propuesta/Debate/Comentario visible, en el caso de que se considere - que ha sido una acción errónea el haberlo ocultado.
    • +
      +
        + +
      • + Temas de debate +
        +

        Los temas (también llamadas tags, o etiquetas) de debate son palabras que definen los usuarios al crear debates, para catalogarlos (ej: sanidad, movilidad, arganzuela, ...). Aquí se pueden eliminar temas inapropiados, o marcarlos para ser propuestos al crear debates (cada usuario puede definir los que quiera, pero se le sugieren algunos que nos parecen útiles como catalogación por defecto; aquí se puede cambiar cuáles se sugieren)

        +
        +
      • + +
      • + Propuestas/Debates/Comentarios ocultos +
        +

        Cuando un moderador o un administrador oculta una Propuesta/Debate/Comentario aparecerá en esta lista. De esta forma los administradores pueden revisar que se ha ocultado el elemento adecuado.

        + +
          +
        • Al pulsar Confirmar se acepta el que se haya ocultado, se considera que se ha hecho correctamente.
        • +
        • Al pulsar Volver a mostrar se revierte la acción de ocultar y vuelve a ser una Propuesta/Debate/Comentario visible, en el caso de que se considere + que ha sido una acción errónea el haberlo ocultado.
        • +
        + +

        Para facilitar la gestión, arriba encontramos un filtro con las secciones: "pendientes" (los elementos sobre los que todavía no se ha pulsado "confirmar" o "volver a mostrar", que deberían ser revisados todavía), "confirmados" y "todos".

        + +

        Es recomendable revisar regularmente la sección "pendientes".

        +
        +
      • + +
      • + Usuarios bloqueados +
        +

        Cuando un moderador o un administrador bloquea a un usuario aparecerá en esta lista. Al bloquear a un usuario, éste deja de poder utilizarlo para ninguna acción de la web. Los administradores pueden desbloquearlos pulsando el botón al lado del nombre del usuario en la lista.

        +
        +
      • + +
      • + Organizaciones +
        +

        En la web hay dos tipos de usuarios: individuales y organizaciones. Cualquier persona puede crear usuarios de un tipo o de otro en la propia web. Los usuarios de organizaciones pueden ser verificados por parte de los administradores, confirmando que quien gestiona el usuario efectivamente representa a esa organización. Una vez se haya realizado el proceso de verificación, por el proceso externo a la web que se haya definido para ello, se pulsa el botón "Verificar" para confimarlo; lo que hará que al lado del nombre de la organización aparezca una etiqueta señalando que es una organización verificada.

        + +

        En caso de que el proceso de verificación haya sido negativo, se pulsa el botón "Rechazar". Para editar alguno de los datos de la organización, se pulsa el botón "Editar".

        + +

        En caso de que el proceso de verificación haya sido negativo, se pulsa el botón "Rechazar". Para editar alguno de los datos de la organización, se pulsa el botón "Editar".

        + +

        Las organizaciones que no aparecen en la lista pueden ser encontradas para actuar sobre ellas por medio del buscador en la parte superior. Para facilitar la gestión, arriba + encontramos un filtro con las secciones: "pendientes" (las organizaciones que todavía no han sido verificadas o rechazadas), "verificadas", "rechazadas" y "todas".

        +

        Es recomendable revisar regularmente la sección "pendientes".

        +
        +
      • + +
      • + Cargos Públicos +
        +

        En la web, los usuarios individuales pueden ser usuarios normales, o cargos públicos. Estos últimos se diferencian de los primeros únicamente en que al lado de sus nombres aparece una etiqueta que les identifica, y cambia ligeramente el estilo de sus comentarios. Esto permite que los usuarios les identifiquen más fácilmente. Al lado de cada usuario vemos la identificación que aparece en su etiqueta, y su nivel (la manera que internamente usa la web para diferenciar entre un tipo de cargos y otros). Pulsando el botón "Editar" al lado del usuario, se puede modificar su información. Los cargos públicos que no aparecen en la lista pueden ser encontrados para actuar sobre ellos por medio del buscador en la parte superior.

        +
        +
      • + +
      • + Moderadores +
        +

        Mediante el buscador de la parte superior se pueden buscar usuarios, para activarlos o desactivarlos como moderadores de la web. Los moderadores al acceder a la web con su usuario ven en la parte superior una nueva sección llamada "Moderar"

        +
        +
      • + +
      • + Actividad de moderadores +
        +

        En esta sección se va guardando todas las acciones que realizan los moderadores o los administradores respecto a la moderación: ocultar/mostrar Propuestas/Debates/Comentarios y bloquear usuarios. En la columna "Acción" comprobamos si la acción corresponde con ocultar o con volver a mostrar (restaurar) elementos o con bloquear usuarios. En las demás columnas tenemos el tipo de elemento, el contenido del elemento y el moderador o administrador que ha realizado la acción. Esta sección permite que los administradores detecten comportamientos irregulares por parte de moderadores específicos y que por lo tanto puedan corregirlos.

        +
        +
      • + +
      • + Configuración Global +
        +

        Opciones generales de configuración del sistema.

        +
        +
      • + +
      • + Estadísticas +
        +

        Estadísticas generales del sistema.

        +
        +
      - -

      Para facilitar la gestión, arriba encontramos un filtro con las secciones: "pendientes" (los elementos sobre los que todavía no se ha pulsado "confirmar" o "volver a mostrar", que deberían ser revisados todavía), "confirmados" y "todos".

      - -

      Es recomendable revisar regularmente la sección "pendientes".

      - -

      Usuarios bloqueados

      -

      Cuando un moderador o un administrador bloquea a un usuario aparecerá en esta lista. Al bloquear a un usuario, éste deja de poder utilizarlo para ninguna acción de la web. Los administradores pueden desbloquearlos pulsando el botón al lado del nombre del usuario en la lista.

      - -

      Organizaciones

      -

      En la web hay dos tipos de usuarios: individuales y organizaciones. Cualquier persona puede crear usuarios de un tipo o de otro en la propia web. Los usuarios de organizaciones pueden ser verificados por parte de los administradores, confirmando que quien gestiona el usuario efectivamente representa a esa organización. Una vez se haya realizado el proceso de verificación, por el proceso externo a la web que se haya definido para ello, se pulsa el botón "Verificar" para confimarlo; lo que hará que al lado del nombre de la organización aparezca una etiqueta señalando que es una organización verificada.

      - -

      En caso de que el proceso de verificación haya sido negativo, se pulsa el botón "Rechazar". Para editar alguno de los datos de la organización, se pulsa el botón "Editar".

      - -

      Las organizaciones que no aparecen en la lista pueden ser encontradas para actuar sobre ellas por medio del buscador en la parte superior. Para facilitar la gestión, arriba - encontramos un filtro con las secciones: "pendientes" (las organizaciones que todavía no han sido verificadas o rechazadas), "verificadas", "rechazadas" y "todas".

      -

      Es recomendable revisar regularmente la sección "pendientes".

      - -

      Cargos Públicos

      -

      En la web, los usuarios individuales pueden ser usuarios normales, o cargos públicos. Estos últimos se diferencian de los primeros únicamente en que al lado de sus nombres aparece una etiqueta que les identifica, y cambia ligeramente el estilo de sus comentarios. Esto permite que los usuarios les identifiquen más fácilmente. Al lado de cada usuario vemos la identificación que aparece en su etiqueta, y su nivel (la manera que internamente usa la web para diferenciar entre un tipo de cargos y otros). Pulsando el botón "Editar" al lado del usuario, se puede modificar su información. Los cargos públicos que no aparecen en la lista pueden ser encontrados para actuar sobre ellos por medio del buscador en la parte superior.

      - -

      Moderadores

      - -

      Mediante el buscador de la parte superior se pueden buscar usuarios, para activarlos o desactivarlos como moderadores de la web. Los moderadores al acceder a la web con su usuario ven en la parte - superior una nueva sección llamada "Moderar"

      - -

      Actividad de moderadores

      - -

      En esta sección se va guardando todas las acciones que realizan los moderadores o los administradores respecto a la moderación: ocultar/mostrar Propuestas/Debates/Comentarios y bloquear usuarios. En la columna "Acción" comprobamos si la acción corresponde con ocultar o con volver a mostrar (restaurar) elementos o con bloquear usuarios. En las demás columnas tenemos el tipo de elemento, el contenido del elemento y el moderador o administrador que ha realizado la acción. Esta sección permite que los administradores detecten comportamientos irregulares por parte de moderadores específicos y que por lo tanto puedan corregirlos.

      - -

      Configuración Global

      -

      Opciones generales de configuración del sistema.

      - -

      Estadísticas

      -

      Estadísticas generales del sistema.

      diff --git a/app/views/admin/debates/index.html.erb b/app/views/admin/debates/index.html.erb index b755e70a1..987aa6e72 100644 --- a/app/views/admin/debates/index.html.erb +++ b/app/views/admin/debates/index.html.erb @@ -14,19 +14,17 @@ <%= debate.description %>
    - + <%= link_to t("admin.actions.restore"), restore_admin_debate_path(debate, request.query_parameters), method: :put, data: { confirm: t("admin.actions.confirm") }, - class: "button small success no-margin" %> - - + class: "button hollow on-hover" %> <% unless debate.confirmed_hide? %> <%= link_to t("admin.actions.confirm_hide"), confirm_hide_admin_debate_path(debate, request.query_parameters), method: :put, - class: "button small warning float-right" %> + class: "button hollow warning on-hover" %> <% end %> diff --git a/app/views/admin/geozones/_errors.html.erb b/app/views/admin/geozones/_errors.html.erb new file mode 100644 index 000000000..d0f3b849f --- /dev/null +++ b/app/views/admin/geozones/_errors.html.erb @@ -0,0 +1,15 @@ + +<% if @geozone.errors.any? %> + +
    + + + + <%= @geozone.errors.count %> + <%= t("admin.geozones.errors.form.error", count: @geozone.errors.count) %> + +
    + +<% end %> diff --git a/app/views/admin/geozones/_form.html.erb b/app/views/admin/geozones/_form.html.erb new file mode 100644 index 000000000..e92b98c0b --- /dev/null +++ b/app/views/admin/geozones/_form.html.erb @@ -0,0 +1,29 @@ +<%= form_for [:admin, @geozone] do |f| %> + + <%= render 'errors' %> + +
    +
    + <%= f.label :name, t("admin.geozones.geozone.name") %> + <%= f.text_field :name, label: false %> +
    +
    + <%= f.label :html_map_coordinates, t("admin.geozones.geozone.coordinates") %> + <%= f.text_field :html_map_coordinates, label: false %> +
    +
    + <%= f.label :external_code, t("admin.geozones.geozone.external_code") %> + <%= f.text_field :external_code, label: false %> +
    +
    + <%= f.label :census_code, t("admin.geozones.geozone.census_code") %> + <%= f.text_field :census_code, label: false %> +
    +
    + +
    +
    + <%= f.submit(class: "button expanded", value: t("admin.geozones.edit.form.submit_button")) %> +
    +
    +<% end %> diff --git a/app/views/admin/geozones/edit.html.erb b/app/views/admin/geozones/edit.html.erb new file mode 100644 index 000000000..b6b8c3fd9 --- /dev/null +++ b/app/views/admin/geozones/edit.html.erb @@ -0,0 +1,13 @@ +
    + +
    + <%= link_to admin_geozones_path, class: "back" do %> + + <%= t("admin.geozones.edit.back") %> + <% end %> + +

    <%= t("admin.geozones.edit.editing") %>

    + + <%= render "form" %> +
    +
    diff --git a/app/views/admin/geozones/index.html.erb b/app/views/admin/geozones/index.html.erb new file mode 100644 index 000000000..5a524e46e --- /dev/null +++ b/app/views/admin/geozones/index.html.erb @@ -0,0 +1,33 @@ +<%= link_to t("admin.geozones.index.create"), + new_admin_geozone_path, class: "button success float-right" %> + +

    <%= t("admin.geozones.index.title") %>

    + + + + + + + + + + + + + + <% @geozones.each do |geozone| %> + + + + + + + + + <% end %> + +
    <%= t("admin.geozones.geozone.name") %><%= t("admin.geozones.geozone.external_code") %><%= t("admin.geozones.geozone.census_code") %><%= t("admin.geozones.geozone.coordinates") %>
    <%= geozone.name %><%= geozone.external_code %><%= geozone.census_code %><%= geozone.html_map_coordinates %> + <%= link_to t("admin.geozones.index.edit"), edit_admin_geozone_path(geozone), class: 'edit-banner button hollow' %> + + <%= link_to t("admin.geozones.index.delete"), admin_geozone_path(geozone), method: :delete, class: 'button hollow alert' %> +
    diff --git a/app/views/admin/geozones/new.html.erb b/app/views/admin/geozones/new.html.erb new file mode 100644 index 000000000..0d5080337 --- /dev/null +++ b/app/views/admin/geozones/new.html.erb @@ -0,0 +1,13 @@ +
    + +
    + <%= link_to admin_geozones_path, class: "back" do %> + + <%= t("admin.geozones.new.back") %> + <% end %> + +

    <%= t("admin.geozones.new.creating") %>

    + + <%= render "form" %> +
    +
    diff --git a/app/views/admin/managers/_manager.html.erb b/app/views/admin/managers/_manager.html.erb new file mode 100644 index 000000000..3e709eb60 --- /dev/null +++ b/app/views/admin/managers/_manager.html.erb @@ -0,0 +1,24 @@ +
    + + + + + + +
    + <%= manager.name %> + + <%= manager.email %> + + <% if manager.persisted? %> + <%= link_to t('admin.managers.manager.delete'), + admin_manager_path(manager), + method: :delete, + class: "button hollow alert" + %> + <% else %> + <%= link_to t('admin.managers.manager.add'),{ controller: "admin/managers", action: :create, user_id: manager.user_id }, + method: :post, class: "button success" %> + <% end %> +
    +
    diff --git a/app/views/admin/managers/index.html.erb b/app/views/admin/managers/index.html.erb new file mode 100644 index 000000000..d479ba303 --- /dev/null +++ b/app/views/admin/managers/index.html.erb @@ -0,0 +1,43 @@ +

    <%= t("admin.managers.index.title") %>

    + +
    + <%= form_tag search_admin_managers_path, method: :get, remote: true do %> +
    + <%= text_field_tag :email, '', placeholder: t('admin.managers.search.email_placeholder') %> +
    +
    + <%= submit_tag t('admin.managers.search.search'), class: 'button' %> +
    + <% end %> +
    + +
    + +

    <%= page_entries_info @managers %>

    + + + <% @managers.each do |manager| %> + + + + + + <% end %> +
    + <%= manager.name %> + + <%= manager.email %> + + <% if manager.persisted? %> + <%= link_to t('admin.managers.manager.delete'), + admin_manager_path(manager), + method: :delete, + class: "button hollow alert" + %> + <% else %> + <%= link_to t('admin.managers.manager.add'),{ controller: "admin/managers", action: :create, user_id: manager.user_id }, + method: :post, class: "button success" %> + <% end %> +
    + +<%= paginate @managers %> diff --git a/app/views/admin/managers/search.js.erb b/app/views/admin/managers/search.js.erb new file mode 100644 index 000000000..e54b84293 --- /dev/null +++ b/app/views/admin/managers/search.js.erb @@ -0,0 +1 @@ +$("#search-result").html("<%= j render 'manager', manager: @manager %>"); diff --git a/app/views/admin/managers/user_not_found.js.erb b/app/views/admin/managers/user_not_found.js.erb new file mode 100644 index 000000000..53e9d7e20 --- /dev/null +++ b/app/views/admin/managers/user_not_found.js.erb @@ -0,0 +1 @@ +$("#search-result").html("
    <%= j t('admin.managers.search.user_not_found') %>
    "); diff --git a/app/views/admin/moderators/_moderator.html.erb b/app/views/admin/moderators/_moderator.html.erb index 0f01b47b9..57a29d2e8 100644 --- a/app/views/admin/moderators/_moderator.html.erb +++ b/app/views/admin/moderators/_moderator.html.erb @@ -1,13 +1,26 @@ -<%= moderator.name %> - •  -<%= moderator.email %> -<% if moderator.persisted? %> - <%= link_to t('admin.moderators.moderator.delete'), - admin_moderator_path(moderator), - method: :delete, - class: "button small alert float-right" - %> -<% else %> - <%= link_to t('admin.moderators.moderator.add'),{ controller: "admin/moderators", action: :create, user_id: moderator.user_id }, - method: :post, class: "button small success float-right" %> -<% end %> +
    + + + + + + + + +
    + <%= moderator.name %> + + <%= moderator.email %> + + <% if moderator.persisted? %> + <%= link_to t('admin.moderators.moderator.delete'), + admin_moderator_path(moderator), + method: :delete, + class: "button hollow alert" %> + <% else %> + <%= link_to t('admin.moderators.moderator.add'),{ controller: "admin/moderators", action: :create, user_id: moderator.user_id }, + method: :post, + class: "button success" %> + <% end %> +
    +
    diff --git a/app/views/admin/moderators/index.html.erb b/app/views/admin/moderators/index.html.erb index 7c39f3234..0fc8703fd 100644 --- a/app/views/admin/moderators/index.html.erb +++ b/app/views/admin/moderators/index.html.erb @@ -1,24 +1,48 @@ -

    <%= t("admin.moderators.index.title") %>

    +<%= link_to t('admin.menu.activity'), admin_activity_path, class: "button hollow float-right" %> + +

    <%= t("admin.moderators.index.title") %>

    +
    -
    - <%= form_tag search_admin_moderators_path, method: :get, remote: true do %> -
    - <%= text_field_tag :email, '', placeholder: t('admin.moderators.search.email_placeholder') %> -
    -
    - <%= submit_tag t('admin.moderators.search.search'), class: 'button' %> -
    - <% end %> -
    -
    -
    -

    <%= page_entries_info @moderators %>

    -
      - <% @moderators.each do |moderator| %> -
    • - <%= render 'moderator', moderator: moderator %> -
    • + <%= form_tag search_admin_moderators_path, method: :get, remote: true do %> +
      + <%= text_field_tag :email, '', placeholder: t('admin.moderators.search.email_placeholder') %> +
      +
      + <%= submit_tag t('admin.moderators.search.search'), class: 'button' %> +
      <% end %> -
    + + +
    + +

    <%= page_entries_info @moderators %>

    + + + <% @moderators.each do |moderator| %> + + + + + + <% end %> +
    + <%= moderator.name %> + + <%= moderator.email %> + + <% if moderator.persisted? %> + <%= link_to t('admin.moderators.moderator.delete'), + admin_moderator_path(moderator), + method: :delete, + class: "button hollow alert" + %> + <% else %> + <%= link_to t('admin.moderators.moderator.add'), + { controller: "admin/moderators", action: :create, + user_id: moderator.user_id }, + method: :post, + class: "button success" %> + <% end %> +
    <%= paginate @moderators %> diff --git a/app/views/admin/moderators/search.js.erb b/app/views/admin/moderators/search.js.erb index a6ac48206..887d8aa3a 100644 --- a/app/views/admin/moderators/search.js.erb +++ b/app/views/admin/moderators/search.js.erb @@ -1 +1 @@ -$("#search-result").html("
    <%= j render 'moderator', moderator: @moderator %>
    "); +$("#search-result").html("<%= j render 'moderator', moderator: @moderator %>"); diff --git a/app/views/admin/moderators/user_not_found.js.erb b/app/views/admin/moderators/user_not_found.js.erb index a3f86936c..a8573236c 100644 --- a/app/views/admin/moderators/user_not_found.js.erb +++ b/app/views/admin/moderators/user_not_found.js.erb @@ -1 +1 @@ -$("#search-result").html("
    <%= j t('admin.moderators.search.user_not_found') %>
    "); +$("#search-result").html("
    <%= j t('admin.moderators.search.user_not_found') %>
    "); diff --git a/app/views/admin/officials/edit.html.erb b/app/views/admin/officials/edit.html.erb index defbaa483..10ec9b23f 100644 --- a/app/views/admin/officials/edit.html.erb +++ b/app/views/admin/officials/edit.html.erb @@ -1,15 +1,20 @@ -

    - <%= t("admin.officials.edit.title") %> - <%= @user.name %> (<%= @user.email %>) -

    +<%= render 'shared/back_link' %> -<%= form_for @user, url: admin_official_path(@user) do |f| %> - <%= f.text_field :official_position %> - <%= f.select :official_level, official_level_options %> - <%= link_to t("admin.officials.edit.back"), admin_officials_path, class: "button small secondary" %> - <%= f.submit %> +
    - <% if @user.official? %> - <%= link_to t("admin.officials.edit.destroy"), admin_official_path(@user), method: :delete, class: 'delete' %> +
    <%= @user.name %> (<%= @user.email %>)
    + +

    <%= t("admin.officials.edit.title") %>

    + +
    +
    + <%= form_for @user, url: admin_official_path(@user) do |f| %> + <%= f.text_field :official_position %> + <%= f.select :official_level, official_level_options %> + <% if @user.official? %> + <%= link_to t("admin.officials.edit.destroy"), admin_official_path(@user), method: :delete, class: 'button hollow alert' %> + <% end %> + <%= f.submit class: "button hollow" %> <% end %> -<% end %> +
    +
    diff --git a/app/views/admin/officials/index.html.erb b/app/views/admin/officials/index.html.erb index ea0e8dccf..078d5bbbc 100644 --- a/app/views/admin/officials/index.html.erb +++ b/app/views/admin/officials/index.html.erb @@ -4,20 +4,24 @@

    <%= page_entries_info @officials %>

    -
      -<% @officials.each do |official| %> -
    • - <%= link_to official.name, edit_admin_official_path(official) %> -  •  - <%= t("admin.officials.level_#{official.official_level}") %> -  •  - - <%= official.official_position %> - - - <%= link_to official.official? ? t("admin.officials.search.edit_official") : t("admin.officials.search.make_official"), edit_admin_official_path(official), class: "button small float-right" %> -
    • -<% end %> -
    + + <% @officials.each do |official| %> + + + + + + <% end %> +
    + <%= link_to official.name, edit_admin_official_path(official) %> + + + <%= official.official_position %> + + (<%= t("admin.officials.level_#{official.official_level}") %>) + + <%= link_to official.official? ? t("admin.officials.search.edit_official") : t("admin.officials.search.make_official"), + edit_admin_official_path(official), class: "button hollow" %> +
    <%= paginate @officials %> diff --git a/app/views/admin/officials/search.html.erb b/app/views/admin/officials/search.html.erb index c7a7dc2a6..f2d90c143 100644 --- a/app/views/admin/officials/search.html.erb +++ b/app/views/admin/officials/search.html.erb @@ -4,18 +4,22 @@

    <%= page_entries_info @users %>

    -
      + <% @users.each do |user| %> -
    • - <%= link_to user.name, edit_admin_official_path(user) %> -  •  - - <%= user.official_position %> - -  •  - <%= t("admin.officials.level_#{user.official_level}") %> - - <%= link_to user.official? ? t("admin.officials.search.edit_official") : t("admin.officials.search.make_official"), edit_admin_official_path(user), class: "button small float-right" %> -
    • + + + + + <% end %> - +
      + <%= link_to user.name, edit_admin_official_path(user) %> + + + <%= user.official_position %> + + (<%= t("admin.officials.level_#{user.official_level}") %>) + + <%= link_to user.official? ? t("admin.officials.search.edit_official") : t("admin.officials.search.make_official"), + edit_admin_official_path(user), class: "button hollow" %> +
      diff --git a/app/views/admin/organizations/index.html.erb b/app/views/admin/organizations/index.html.erb index ec834eebf..0ddee4247 100644 --- a/app/views/admin/organizations/index.html.erb +++ b/app/views/admin/organizations/index.html.erb @@ -22,12 +22,12 @@ <% @organizations.each do |organization| %> <% hidden += 1 and next if organization.user.nil? || organization.user.hidden? %> - <%= organization.name %> - <%= organization.email %> - <%= organization.phone_number %> - <%= organization.responsible_name %> +

      <%= organization.name %>

      +

      <%= organization.email %>

      +

      <%= organization.phone_number %>

      +

      <%= organization.responsible_name %>

      <% if organization.verified? %> - + <%= t("admin.organizations.index.verified") %> @@ -36,27 +36,32 @@ <%= link_to t("admin.organizations.index.verify"), verify_admin_organization_path(organization, request.query_parameters), - method: :put, class: "button small success" + method: :put, class: "button success expanded" %> <% end %> <% if organization.rejected? %> - + <%= t("admin.organizations.index.rejected") %> <% end %> <% if can? :reject, organization %> - <%= link_to t("admin.organizations.index.reject"), + + <%= link_to t("admin.organizations.index.reject"), reject_admin_organization_path(organization, request.query_parameters), - method: :put, class: "button small alert" - %> + method: :put, class: "button hollow alert expanded" + %> <% end %> <% end %> -<%= t("admin.organizations.index.hidden_count", count: hidden) if hidden > 0 %> +<% if hidden > 0 %> +
      + <%= t("admin.organizations.index.hidden_count_html", count: hidden) %> +
      +<% end %> <%= paginate @organizations %> diff --git a/app/views/admin/organizations/search.html.erb b/app/views/admin/organizations/search.html.erb index 9e90975ab..5b5071c5b 100644 --- a/app/views/admin/organizations/search.html.erb +++ b/app/views/admin/organizations/search.html.erb @@ -18,12 +18,12 @@ <% @organizations.each do |organization| %> - - - - + + + + <% if organization.verified? %> - @@ -32,12 +32,12 @@ <% end %> <% if organization.rejected? %> - @@ -45,7 +45,7 @@ <% if can? :reject, organization %> <% end %> diff --git a/app/views/admin/poll/_menu.html.erb b/app/views/admin/poll/_menu.html.erb new file mode 100644 index 000000000..08efd87dc --- /dev/null +++ b/app/views/admin/poll/_menu.html.erb @@ -0,0 +1 @@ +<%= render "admin/menu" %> \ No newline at end of file diff --git a/app/views/admin/poll/booth_assignments/_search_booths.html.erb b/app/views/admin/poll/booth_assignments/_search_booths.html.erb new file mode 100644 index 000000000..e96e334c7 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/_search_booths.html.erb @@ -0,0 +1,16 @@ +
      +
      + <%= form_tag(search_booths_admin_poll_booth_assignments_path(@poll), method: :get, remote: true) do |f| %> +
      + <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.booths_search.placeholder"), id: "search-booths" %> +
      + <%= submit_tag t("admin.shared.booths_search.button"), class: "button" %> +
      +
      + <% end %> +
      +
      + +
      diff --git a/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb b/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb new file mode 100644 index 000000000..3fa7fc080 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb @@ -0,0 +1,39 @@ +<% if @booths.blank? %> +
      + <%= t('admin.shared.no_search_results') %> +
      +<% else %> +

      <%= t('admin.shared.search_results') %>

      +<% end %> + +<% if @booths.any? %> +
      <%= organization.name %><%= organization.email %><%= organization.phone_number %><%= organization.responsible_name %>

      <%= organization.name %>

      <%= organization.email %>

      <%= organization.phone_number %>

      <%= organization.responsible_name %>

      + <%= t("admin.organizations.index.verified") %> <%= link_to t("admin.organizations.index.verify"), verify_admin_organization_path(organization, request.query_parameters), - method: :put, class: "button small success" + method: :put, class: "button success expanded" %> + <%= t("admin.organizations.index.rejected") %> <%= link_to t("admin.organizations.index.reject"), reject_admin_organization_path(organization, request.query_parameters), - method: :put, class: "button small alert" + method: :put, class: "button hollow alert expanded" %>
      + + + + + + + + + <% @booths.each do |booth| %> + + + + + + <% end %> + +
      <%= t("admin.poll_booth_assignments.index.table_name") %><%= t("admin.poll_booth_assignments.index.table_location") %><%= t("admin.poll_booth_assignments.index.table_assignment") %>
      + <%= booth.name %> + + <%= booth.location %> + + <% unless @poll.booth_ids.include?(booth.id) %> + <%= link_to t("admin.poll_booth_assignments.index.add_booth"), + admin_poll_booth_assignments_path(@poll, booth_id: booth.id), + method: :post, + class: "button hollow" %> + <% end %> +
      +<% end %> diff --git a/app/views/admin/poll/booth_assignments/index.html.erb b/app/views/admin/poll/booth_assignments/index.html.erb new file mode 100644 index 000000000..f14e59ef1 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/index.html.erb @@ -0,0 +1,43 @@ +<%= render "/admin/poll/polls/poll_header" %> +
      + <%= render "/admin/poll/polls/subnav" %> + <%= render "search_booths" %> + +

      <%= t("admin.poll_booth_assignments.index.booths_title") %>

      + + <% if @booth_assignments.empty? %> +
      + <%= t("admin.poll_booth_assignments.index.no_booths") %> +
      + <% else %> + + + + + + + + <% @booth_assignments.each do |booth_assignment| %> + + + + + + <% end %> + +
      <%= t("admin.poll_booth_assignments.index.table_name") %><%= t("admin.poll_booth_assignments.index.table_location") %><%= t("admin.poll_booth_assignments.index.table_assignment") %>
      + + <%= link_to booth_assignment.booth.name, admin_poll_booth_assignment_path(@poll, booth_assignment) %> + + + <%= booth_assignment.booth.location %> + + <%= link_to t("admin.poll_booth_assignments.index.remove_booth"), + admin_poll_booth_assignment_path(@poll, booth_assignment), + method: :delete, + class: "button hollow alert" %> +
      + + <%= paginate @booth_assignments %> + <% end %> +
      diff --git a/app/views/admin/poll/booth_assignments/search_booths.js.erb b/app/views/admin/poll/booth_assignments/search_booths.js.erb new file mode 100644 index 000000000..72fd96f68 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/search_booths.js.erb @@ -0,0 +1 @@ +$("#search-booths-results").html("<%= j render 'search_booths_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/booth_assignments/show.html.erb b/app/views/admin/poll/booth_assignments/show.html.erb new file mode 100644 index 000000000..519b36202 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/show.html.erb @@ -0,0 +1,87 @@ +<%= link_to admin_poll_booth_assignments_path(@poll) do %> + + <%= @poll.name %> +<% end %> + +

      <%= @booth_assignment.booth.name %>

      + +<% if @booth_assignment.booth.location.present? %> +

      + <%= t("admin.poll_booth_assignments.show.location") %>: + <%= @booth_assignment.booth.location %> +

      +<% end %> + +
      +
        +
      • + <%= link_to t("admin.poll_booth_assignments.show.officers"), "#tab-officers" %> +
      • +
      • + <%= link_to t("admin.poll_booth_assignments.show.recounts"), "#tab-recounts" %> +
      • +
      + +
      + <% if @booth_assignment.officers.empty? %> +
      + <%= t("admin.poll_booth_assignments.show.no_officers") %> +
      + <% else %> +

      <%= t("admin.poll_booth_assignments.show.officers_list") %>

      + + + + <% @booth_assignment.officers.uniq.each do |officer| %> + + + + + <% end %> + +
      <%= link_to officer.name, by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id) %><%= officer.email %>
      + <% end %> +
      + +
      + <% if @booth_assignment.recounts.empty? %> +
      + <%= t("admin.poll_booth_assignments.show.no_recounts") %> +
      + <% else %> +

      <%= t("admin.poll_booth_assignments.show.recounts_list") %>

      + + + + + + + + + + + <% (@poll.starts_at.to_date..@poll.ends_at.to_date).each do |voting_date| %> + <% recount = recount_for_date(@booth_assignment.recounts, voting_date) %> + <% final_recount = final_recount_for_date(@booth_assignment.final_recounts, voting_date) %> + <% system_count = @voters_by_date[voting_date].present? ? @voters_by_date[voting_date].size : 0 %> + + + <% if recount.present? %> + + + <% else %> + + <% end %> + <% if final_recount.present? %> + + <% else %> + + <% end %> + + + <% end %> + +
      <%= t("admin.poll_booth_assignments.show.date") %><%= t("admin.poll_booth_assignments.show.count_by_officer") %><%= t("admin.poll_booth_assignments.show.count_final") %><%= t("admin.poll_booth_assignments.show.count_by_system") %>
      <%= l voting_date %><%= recount.count %> - <%= final_recount.count %> - <%= system_count %>
      + <% end %> +
      +
      diff --git a/app/views/admin/poll/booths/_booth.html.erb b/app/views/admin/poll/booths/_booth.html.erb new file mode 100644 index 000000000..5732400a8 --- /dev/null +++ b/app/views/admin/poll/booths/_booth.html.erb @@ -0,0 +1,13 @@ + + + <%= booth.name %> + + + <%= booth.location %> + + + <%= link_to t("admin.actions.edit"), + edit_admin_booth_path(booth), + class: "button hollow" %> + + \ No newline at end of file diff --git a/app/views/admin/poll/booths/_form.html.erb b/app/views/admin/poll/booths/_form.html.erb new file mode 100644 index 000000000..31d60c0f0 --- /dev/null +++ b/app/views/admin/poll/booths/_form.html.erb @@ -0,0 +1,20 @@ +
      +
      + <%= f.text_field :name, + placeholder: t('admin.booths.new.name'), + label: t("admin.booths.new.name") %> +
      + +
      + <%= f.text_field :location, + placeholder: t("admin.booths.new.location"), + label: t("admin.booths.new.location") %> +
      +
      + +
      +
      + <%= f.submit t("admin.booths.#{admin_submit_action(@booth)}.submit_button"), + class: "button success expanded" %> +
      +
      \ No newline at end of file diff --git a/app/views/admin/poll/booths/edit.html.erb b/app/views/admin/poll/booths/edit.html.erb new file mode 100644 index 000000000..66bfa8355 --- /dev/null +++ b/app/views/admin/poll/booths/edit.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_booths_path %> + +

      <%= t("admin.booths.edit.title") %>

      + +<%= form_for @booth, url: admin_booth_path(@booth) do |f| %> + <%= render "form", f: f %> +<% end %> diff --git a/app/views/admin/poll/booths/index.html.erb b/app/views/admin/poll/booths/index.html.erb new file mode 100644 index 000000000..9618aec59 --- /dev/null +++ b/app/views/admin/poll/booths/index.html.erb @@ -0,0 +1,28 @@ +

      <%= t("admin.booths.index.title") %>

      + +<%= link_to t("admin.booths.index.add_booth"), new_admin_booth_path, + class: "button success float-right" %> + +<% if @booths.empty? %> +
      + <%= t("admin.booths.index.no_booths") %> +
      +<% end %> + +<% if @booths.any? %> +

      <%= page_entries_info @booths %>

      + + + + + + + + <% @booths.each do |booth| %> + <%= render partial: "booth", locals: { booth: booth } %> + <% end %> + +
      <%= t("admin.booths.index.name") %><%= t("admin.booths.index.location") %> 
      + + <%= paginate @booths %> +<% end %> diff --git a/app/views/admin/poll/booths/new.html.erb b/app/views/admin/poll/booths/new.html.erb new file mode 100644 index 000000000..cf9581fa3 --- /dev/null +++ b/app/views/admin/poll/booths/new.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_booths_path %> + +

      <%= t("admin.booths.new.title") %>

      + +<%= form_for @booth, url: admin_booths_path(@booth) do |f| %> + <%= render "form", f: f %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/booths/show.html.erb b/app/views/admin/poll/booths/show.html.erb new file mode 100644 index 000000000..bd9355f69 --- /dev/null +++ b/app/views/admin/poll/booths/show.html.erb @@ -0,0 +1,12 @@ +<%= back_link_to admin_booths_path %> + +
      + +

      + <%= @booth.name %> +

      + +

      + <%= t("admin.booths.show.location") %>: + <%= @booth.location %> +

      diff --git a/app/views/admin/poll/officer_assignments/_search_officers.html.erb b/app/views/admin/poll/officer_assignments/_search_officers.html.erb new file mode 100644 index 000000000..7ed39f10d --- /dev/null +++ b/app/views/admin/poll/officer_assignments/_search_officers.html.erb @@ -0,0 +1,16 @@ +
      +
      + <%= form_tag(search_officers_admin_poll_officer_assignments_path(@poll), method: :get, remote: true) do |f| %> +
      + <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_officers_search.placeholder"), id: "search-officers" %> +
      + <%= submit_tag t("admin.shared.poll_officers_search.button"), class: "button" %> +
      +
      + <% end %> +
      +
      + +
      diff --git a/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb b/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb new file mode 100644 index 000000000..f665c6c0b --- /dev/null +++ b/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb @@ -0,0 +1,42 @@ +<% if @officers.blank? %> +
      + <%= t('admin.shared.no_search_results') %> +
      +<% else %> +

      <%= t('admin.shared.search_results') %>

      +<% end %> + +<% if @officers.any? %> + + + + + + + + + + <% @officers.each do |user| %> + + + + + + <% end %> + +
      <%= t("admin.poll_officer_assignments.index.table_name") %><%= t("admin.poll_officer_assignments.index.table_email") %><%= t("admin.polls.show.table_assignment") %>
      + <%= user.name %> + + <%= user.email %> + + <% if @poll.officer_ids.include?(user.poll_officer.id) %> + <%= link_to t("admin.poll_officer_assignments.index.edit_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: user.poll_officer.id), + class: "button hollow alert" %> + <% else %> + <%= link_to t("admin.poll_officer_assignments.index.add_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: user.poll_officer.id), + class: "button hollow" %> + <% end %> +
      +<% end %> diff --git a/app/views/admin/poll/officer_assignments/by_officer.html.erb b/app/views/admin/poll/officer_assignments/by_officer.html.erb new file mode 100644 index 000000000..205692535 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/by_officer.html.erb @@ -0,0 +1,127 @@ +<%= link_to admin_poll_officer_assignments_path(@poll) do %> + + <%= @poll.name %> +<% end %> + +

      <%= @officer.name %> - <%= @officer.email %>

      + +<%= form_tag(admin_poll_officer_assignments_path(@poll), {id: "officer_assignment_form"}) do %> +
      + <%= t("admin.poll_officer_assignments.by_officer.new_assignment") %> +
      + + <%= select_tag :date, + poll_dates_select_options(@poll) + poll_final_recount_option(@poll), + { prompt: t("admin.poll_officer_assignments.by_officer.select_date"), + label: false } %> +
      + +
      + + <%= select_tag :booth_id, + poll_booths_select_options(@poll), + { prompt: t("admin.poll_officer_assignments.by_officer.select_booth"), + label: false } %> +
      + +
      + <%= hidden_field_tag :officer_id, @officer.id %> + <%= hidden_field_tag :poll_id, @poll.id %> + <%= submit_tag t("admin.poll_officer_assignments.by_officer.add_assignment"), + class: "button expanded hollow margin-top" %> +
      +
      +<% end %> + + +<% if @officer_assignments.empty? %> +
      + <%= t("admin.poll_officer_assignments.by_officer.no_assignments") %> +
      +<% else %> +

      <%= t("admin.poll_officer_assignments.by_officer.assignments") %>

      + + + + + + + + + + <% @officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
      <%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.assignment") %>
      <%= officer_assignment.final? ? t('polls.final_date') : l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <%= link_to t("admin.poll_officer_assignments.by_officer.remove_assignment"), + admin_poll_officer_assignment_path(@poll, officer_assignment), + method: :delete, + class: "button hollow alert" %> +
      +<% end %> + +<% voting_days_officer_assignments = @officer_assignments.select{|oa| oa.final == false} %> +<% if voting_days_officer_assignments.any? %> +

      <%= t("admin.poll_officer_assignments.by_officer.recounts") %>

      + + + + + + + + + + <% voting_days_officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
      <%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.recount") %>
      <%= l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <% if officer_assignment.recount.present? %> + <%= officer_assignment.recount.count %> + <% else %> + - + <% end %> +
      +<% end %> + +<% final_officer_assignments = @officer_assignments.select{|oa| oa.final == true} %> +<% if final_officer_assignments.any? %> +

      <%= t("admin.poll_officer_assignments.by_officer.final_recounts") %>

      + + + + + + + + + + <% final_officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
      <%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.final_recount") %>
      <%= l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <% if officer_assignment.final_recounts.any? %> + <%= officer_assignment.final_recounts.to_a.sum(&:count) %> + <% else %> + - + <% end %> +
      +<% end %> + + + diff --git a/app/views/admin/poll/officer_assignments/index.html.erb b/app/views/admin/poll/officer_assignments/index.html.erb new file mode 100644 index 000000000..f6e75c602 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/index.html.erb @@ -0,0 +1,43 @@ +<%= render "/admin/poll/polls/poll_header" %> +
      + <%= render "/admin/poll/polls/subnav" %> + + <%= render "search_officers" %> + +

      <%= t("admin.poll_officer_assignments.index.officers_title") %>

      + + <% if @officers.empty? %> +
      + <%= t("admin.poll_officer_assignments.index.no_officers") %> +
      + <% else %> + + + + + + + + <% @officers.each do |officer| %> + + + + + + <% end %> + +
      <%= t("admin.poll_officer_assignments.index.table_name") %><%= t("admin.poll_officer_assignments.index.table_email") %><%= t("admin.actions.actions") %>
      + + <%= link_to officer.name, by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id) %> + + + <%= officer.email %> + + <%= link_to t("admin.poll_officer_assignments.index.edit_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id), + class: "button hollow" %> +
      + + <%= paginate @officers %> + <% end %> +
      \ No newline at end of file diff --git a/app/views/admin/poll/officer_assignments/search_officers.js.erb b/app/views/admin/poll/officer_assignments/search_officers.js.erb new file mode 100644 index 000000000..ba621d8f7 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/search_officers.js.erb @@ -0,0 +1 @@ +$("#search-officers-results").html("<%= j render 'search_officers_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/officers/_officer.html.erb b/app/views/admin/poll/officers/_officer.html.erb new file mode 100644 index 000000000..80434f385 --- /dev/null +++ b/app/views/admin/poll/officers/_officer.html.erb @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + +
      <%= t('admin.poll_officers.officer.name') %><%= t('admin.poll_officers.officer.email') %>
      + <%= officer.name %> + + <%= officer.email %> + + <% if officer.persisted? %> + <%= link_to t('admin.poll_officers.officer.delete'), + admin_poll_officer_path(officer), + method: :delete, + class: "button hollow alert" %> + <% else %> + <%= link_to t('admin.poll_officers.officer.add'),{ controller: "admin/poll/officers", action: :create, user_id: officer.user_id }, + method: :post, + class: "button success" %> + <% end %> +
      diff --git a/app/views/admin/poll/officers/_search.html.erb b/app/views/admin/poll/officers/_search.html.erb new file mode 100644 index 000000000..dda22dee2 --- /dev/null +++ b/app/views/admin/poll/officers/_search.html.erb @@ -0,0 +1,9 @@ +<%= form_tag search_admin_officers_path, method: :get, remote: true do %> +
      + <%= text_field_tag :email, '', + placeholder: t("admin.poll_officers.search.email_placeholder") %> +
      + <%= submit_tag t("admin.poll_officers.search.search"), class: "button" %> +
      +
      +<% end %> diff --git a/app/views/admin/poll/officers/edit.html.erb b/app/views/admin/poll/officers/edit.html.erb new file mode 100644 index 000000000..5c64d3aeb --- /dev/null +++ b/app/views/admin/poll/officers/edit.html.erb @@ -0,0 +1 @@ +officer edit \ No newline at end of file diff --git a/app/views/admin/poll/officers/index.html.erb b/app/views/admin/poll/officers/index.html.erb new file mode 100644 index 000000000..fd9619167 --- /dev/null +++ b/app/views/admin/poll/officers/index.html.erb @@ -0,0 +1,53 @@ +

      <%= t("admin.poll_officers.index.title") %>

      + +
      +
      + <%= render 'search' %> +
      +
      + +
      + +

      + <%= page_entries_info @officers, entry_name: t('admin.poll_officers.officer.entry_name') %> +

      + +<% if @officers.any? %> + + + + + + + + + <% @officers.each do |officer| %> + + + + + + <% end %> + +
      <%= t('admin.poll_officers.officer.name') %><%= t('admin.poll_officers.officer.email') %>
      + <%= officer.name %> + + <%= officer.email %> + + <% if officer.persisted? %> + <%= link_to t('admin.poll_officers.officer.delete'), + admin_officer_path(officer), + method: :delete, + class: "button hollow alert" + %> + <% else %> + <%= link_to t('admin.poll_officers.officer.add'), + { controller: "admin/poll/officers", action: :create, + user_id: officer.user_id }, + method: :post, + class: "button success" %> + <% end %> +
      + + <%= paginate @officers %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/officers/search.js.erb b/app/views/admin/poll/officers/search.js.erb new file mode 100644 index 000000000..bd259f7fb --- /dev/null +++ b/app/views/admin/poll/officers/search.js.erb @@ -0,0 +1 @@ +$("#search-result").html("<%= j render 'officer', officer: @officer %>"); diff --git a/app/views/admin/poll/officers/show.html.erb b/app/views/admin/poll/officers/show.html.erb new file mode 100644 index 000000000..fc702276e --- /dev/null +++ b/app/views/admin/poll/officers/show.html.erb @@ -0,0 +1 @@ +officer show \ No newline at end of file diff --git a/app/views/admin/poll/officers/user_not_found.js.erb b/app/views/admin/poll/officers/user_not_found.js.erb new file mode 100644 index 000000000..a6444dc61 --- /dev/null +++ b/app/views/admin/poll/officers/user_not_found.js.erb @@ -0,0 +1 @@ +$("#search-result").html("
      <%= j t('admin.poll_officers.search.user_not_found') %>
      "); diff --git a/app/views/admin/poll/polls/_form.html.erb b/app/views/admin/poll/polls/_form.html.erb new file mode 100644 index 000000000..0b99ea9f5 --- /dev/null +++ b/app/views/admin/poll/polls/_form.html.erb @@ -0,0 +1,46 @@ +<%= form_for [:admin, @poll] do |f| %> +
      +
      + <%= f.text_field :name %> +
      +
      + +
      +
      + <%= f.text_field :starts_at, + value: @poll.starts_at.present? ? l(@poll.starts_at.to_date) : nil, + class: "js-calendar-full" %> +
      + +
      + <%= f.text_field :ends_at, + value: @poll.ends_at.present? ? l(@poll.ends_at.to_date) : nil, + class: "js-calendar-full" %> +
      +
      + +
      +
      + <%= f.check_box :geozone_restricted, data: { checkbox_toggle: "#geozones" } %> +
      +
      + +
      +
      + <%= f.collection_check_boxes(:geozone_ids, @geozones, :id, :name) do |b| %> +
      + <%= b.label do %> + <%= b.check_box + b.text %> + <% end %> +
      + <% end %> +
      +
      + +
      +
      + <%= f.submit t("admin.polls.#{admin_submit_action(@poll)}.submit_button"), + class: "button success expanded" %> +
      +
      +<% end %> diff --git a/app/views/admin/poll/polls/_poll.html.erb b/app/views/admin/poll/polls/_poll.html.erb new file mode 100644 index 000000000..b65ce3e71 --- /dev/null +++ b/app/views/admin/poll/polls/_poll.html.erb @@ -0,0 +1,18 @@ + + + + <%= link_to poll.name, admin_poll_path(poll) %> + + + + <%= l poll.starts_at.to_date %> - <%= l poll.ends_at.to_date %> + + + <%= link_to t("admin.actions.edit"), + edit_admin_poll_path(poll), + class: "button hollow" %> + <%= link_to t("admin.actions.configure"), + admin_poll_path(poll), + class: "button hollow" %> + + \ No newline at end of file diff --git a/app/views/admin/poll/polls/_poll_header.html.erb b/app/views/admin/poll/polls/_poll_header.html.erb new file mode 100644 index 000000000..a88dd03ee --- /dev/null +++ b/app/views/admin/poll/polls/_poll_header.html.erb @@ -0,0 +1,17 @@ +<%= link_to t("admin.actions.edit"), + edit_admin_poll_path(@poll), + class: "button hollow float-right" %> + +

      + <%= @poll.name %> +

      +
      + + (<%= l @poll.starts_at.to_date %> - <%= l @poll.ends_at.to_date %>) + +<% if @poll.geozone_restricted %> +  •  + + <%= @poll.geozones.pluck(:name).to_sentence %> + +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/_questions.html.erb b/app/views/admin/poll/polls/_questions.html.erb new file mode 100644 index 000000000..e41d2017e --- /dev/null +++ b/app/views/admin/poll/polls/_questions.html.erb @@ -0,0 +1,31 @@ +

      <%= t("admin.polls.show.questions_title") %>

      + +<% if @poll.questions.empty? %> +
      + <%= t('admin.polls.show.no_questions') %> +
      +<% else %> + + + + + + + + <% @poll.questions.each do |question| %> + + + + + <% end %> +
      <%= t('admin.polls.show.table_title') %><%= t('admin.polls.show.table_assignment') %>
      + + <%= link_to question.title, admin_question_path(question) %> + + + <%= link_to t('admin.polls.show.remove_question'), + remove_question_admin_poll_path(poll_id: @poll.id, question_id: question.id), + class: "button hollow alert", + method: :patch %> +
      +<% end %> diff --git a/app/views/admin/poll/polls/_search_questions.html.erb b/app/views/admin/poll/polls/_search_questions.html.erb new file mode 100644 index 000000000..659cdcd37 --- /dev/null +++ b/app/views/admin/poll/polls/_search_questions.html.erb @@ -0,0 +1,17 @@ +
      +
      + <%= form_tag(search_questions_admin_poll_path(@poll), method: :get, remote: true) do |f| %> +
      + <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_questions_search.placeholder"), id: "search-questions" %> + +
      + <%= submit_tag t("admin.shared.poll_questions_search.button"), class: "button" %> +
      +
      + <% end %> +
      +
      + +
      diff --git a/app/views/admin/poll/polls/_search_questions_results.html.erb b/app/views/admin/poll/polls/_search_questions_results.html.erb new file mode 100644 index 000000000..f6f11e1f1 --- /dev/null +++ b/app/views/admin/poll/polls/_search_questions_results.html.erb @@ -0,0 +1,33 @@ +<% if @questions.blank? %> +
      + <%= t('admin.shared.no_search_results') %> +
      +<% else %> +

      <%= t('admin.shared.search_results') %>

      +<% end %> + +<% if @questions.any? %> + + + + + + + + + <% @questions.each do |question| %> + + + + + <% end %> + +
      <%= t("admin.polls.show.table_name") %><%= t("admin.polls.show.table_assignment") %>
      + <%= question.title %> + + <%= link_to t("admin.polls.show.add_question"), + add_question_admin_poll_path(poll_id: @poll.id, question_id: question.id), + method: :patch, + class: "button hollow" %> +
      +<% end %> diff --git a/app/views/admin/poll/polls/_subnav.html.erb b/app/views/admin/poll/polls/_subnav.html.erb new file mode 100644 index 000000000..249ff407c --- /dev/null +++ b/app/views/admin/poll/polls/_subnav.html.erb @@ -0,0 +1,63 @@ + diff --git a/app/views/admin/poll/polls/edit.html.erb b/app/views/admin/poll/polls/edit.html.erb new file mode 100644 index 000000000..cf131203f --- /dev/null +++ b/app/views/admin/poll/polls/edit.html.erb @@ -0,0 +1,5 @@ +<%= render 'shared/back_link' %> + +

      <%= t("admin.polls.edit.title") %>

      + +<%= render "form" %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/index.html.erb b/app/views/admin/poll/polls/index.html.erb new file mode 100644 index 000000000..5bd310e54 --- /dev/null +++ b/app/views/admin/poll/polls/index.html.erb @@ -0,0 +1,22 @@ +

      <%= t("admin.polls.index.title") %>

      + +<%= link_to t("admin.polls.index.create"), + new_admin_poll_path, + class: "button success float-right" %> + +<% if @polls.any? %> + + + + + + + + <%= render @polls %> + +
      <%= t("admin.polls.index.name") %><%= t("admin.polls.index.dates") %><%= t("admin.actions.actions") %>
      +<% else %> +
      + <%= t("admin.polls.index.no_polls") %> +
      +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/new.html.erb b/app/views/admin/poll/polls/new.html.erb new file mode 100644 index 000000000..ba1ae7260 --- /dev/null +++ b/app/views/admin/poll/polls/new.html.erb @@ -0,0 +1,5 @@ +<%= render 'shared/back_link' %> + +

      <%= t("admin.polls.new.title") %>

      + +<%= render "form" %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/search_questions.js.erb b/app/views/admin/poll/polls/search_questions.js.erb new file mode 100644 index 000000000..05f5c5167 --- /dev/null +++ b/app/views/admin/poll/polls/search_questions.js.erb @@ -0,0 +1 @@ +$("#search-questions-results").html("<%= j render 'search_questions_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/polls/show.html.erb b/app/views/admin/poll/polls/show.html.erb new file mode 100644 index 000000000..93ce52e47 --- /dev/null +++ b/app/views/admin/poll/polls/show.html.erb @@ -0,0 +1,8 @@ +<%= render "poll_header" %> + +
      + <%= render "subnav" %> + + <%= render "search_questions" %> + <%= render "questions" %> +
      diff --git a/app/views/admin/poll/questions/_filter.html.erb b/app/views/admin/poll/questions/_filter.html.erb new file mode 100644 index 000000000..0fd800067 --- /dev/null +++ b/app/views/admin/poll/questions/_filter.html.erb @@ -0,0 +1,7 @@ +<%= form_tag '', method: :get do %> + <%= label_tag :poll_id, t("admin.questions.index.filter_poll") %> + <%= select_tag "poll_id", + poll_select_options(true), + prompt: t("admin.questions.index.select_poll"), + class: "js-location-changer" %> +<% end %> diff --git a/app/views/admin/poll/questions/_filter_subnav.html.erb b/app/views/admin/poll/questions/_filter_subnav.html.erb new file mode 100644 index 000000000..7f641d390 --- /dev/null +++ b/app/views/admin/poll/questions/_filter_subnav.html.erb @@ -0,0 +1,12 @@ +
        +
      • + <%= link_to "#tab-questions" do %> + <%= t("admin.questions.index.questions_tab") %> + <% end %> +
      • +
      • + <%= link_to "#tab-successful-proposals" do %> + <%= t("admin.questions.index.successful_proposals_tab") %> + <% end %> +
      • +
      diff --git a/app/views/admin/poll/questions/_form.html.erb b/app/views/admin/poll/questions/_form.html.erb new file mode 100644 index 000000000..0da36913a --- /dev/null +++ b/app/views/admin/poll/questions/_form.html.erb @@ -0,0 +1,37 @@ +<%= form_for(@question, url: form_url) do |f| %> + + <%= render 'shared/errors', resource: @question %> + + <%= f.hidden_field :proposal_id %> + +
      + +
      +
      + <%= f.select :poll_id, + options_for_select(Poll.pluck(:name, :id)), + prompt: t("admin.questions.index.select_poll"), + label: t("admin.questions.new.poll_label") %> +
      + + <%= f.text_field :title, maxlength: Poll::Question.title_max_length %> + + <%= f.label :valid_answers %> +

      <%= t("admin.questions.new.valid_answers_note") %>

      + <%= f.text_field :valid_answers, label: false %> + +
      + <%= f.cktext_area :description, + maxlength: Poll::Question.description_max_length, + ckeditor: { language: I18n.locale } %> +
      + +
      +
      + <%= f.submit(class: "button expanded", value: t("shared.save")) %> +
      +
      +
      +
      + +<% end %> diff --git a/app/views/admin/poll/questions/_questions.html.erb b/app/views/admin/poll/questions/_questions.html.erb new file mode 100644 index 000000000..c142ad084 --- /dev/null +++ b/app/views/admin/poll/questions/_questions.html.erb @@ -0,0 +1,31 @@ +
      + <%= render 'filter' %> +
      + +<% if @questions.count == 0 %> +
      + <%= t('admin.questions.index.no_questions') %> +
      +<% else %> + + + + + + + + + <% @questions.each do |question| %> + + + + + <% end %> + +
      <%= t('admin.questions.index.table_question') %><%= t("admin.actions.actions") %>
      <%= link_to question.title, admin_question_path(question) %> + <%= link_to t('shared.edit'), edit_admin_question_path(question), class: "button hollow" %> + <%= link_to t('shared.delete'), admin_question_path(question), class: "button hollow alert", method: :delete %> +
      + + <%= paginate @questions %> +<% end %> diff --git a/app/views/admin/poll/questions/_search.html.erb b/app/views/admin/poll/questions/_search.html.erb new file mode 100644 index 000000000..e8a2c52c5 --- /dev/null +++ b/app/views/admin/poll/questions/_search.html.erb @@ -0,0 +1,10 @@ +<%= form_tag(admin_questions_path, method: :get) do |f| %> +
      + <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_questions_search.placeholder") %> +
      + <%= submit_tag t("admin.shared.poll_questions_search.button"), class: "button" %> +
      +
      +<% end %> diff --git a/app/views/admin/poll/questions/_successful_proposals.html.erb b/app/views/admin/poll/questions/_successful_proposals.html.erb new file mode 100644 index 000000000..2b192bccd --- /dev/null +++ b/app/views/admin/poll/questions/_successful_proposals.html.erb @@ -0,0 +1,26 @@ + + + + + + + + + <% @proposals.each do |proposal| %> + + + + + <% end %> + +
      <%= t('admin.questions.index.table_proposal') %><%= t("admin.actions.actions") %>
      + <%= link_to proposal.title, proposal_path(proposal) %> +

      + <%= proposal.summary %>
      + <%= proposal.question %> +

      +
      + <%= link_to t("admin.questions.index.create_question"), + new_admin_question_path(proposal_id: proposal.id), + class: "button hollow" %> +
      diff --git a/app/views/admin/poll/questions/edit.html.erb b/app/views/admin/poll/questions/edit.html.erb new file mode 100644 index 000000000..6c4d24adf --- /dev/null +++ b/app/views/admin/poll/questions/edit.html.erb @@ -0,0 +1,5 @@ +<%= render "shared/back_link" %> + +

      <%= t("admin.questions.edit.title") %>

      + +<%= render "form", form_url: admin_question_path(@question) %> \ No newline at end of file diff --git a/app/views/admin/poll/questions/index.html.erb b/app/views/admin/poll/questions/index.html.erb new file mode 100644 index 000000000..f45497af0 --- /dev/null +++ b/app/views/admin/poll/questions/index.html.erb @@ -0,0 +1,22 @@ +

      <%= t('admin.questions.index.title') %>

      + +<%= link_to t('admin.questions.index.create'), new_admin_question_path, + class: "button success float-right" %> + +
      +
      + <%= render 'search' %> +
      +
      + +
      + <%= render "filter_subnav" %> + +
      + <%= render "questions" %> +
      + +
      + <%= render "successful_proposals" %> +
      +
      diff --git a/app/views/admin/poll/questions/new.html.erb b/app/views/admin/poll/questions/new.html.erb new file mode 100644 index 000000000..830492d6d --- /dev/null +++ b/app/views/admin/poll/questions/new.html.erb @@ -0,0 +1,5 @@ +<%= render "shared/back_link" %> + +

      <%= t("admin.questions.new.title") %>

      + +<%= render "form", form_url: admin_questions_path %> \ No newline at end of file diff --git a/app/views/admin/poll/questions/show.html.erb b/app/views/admin/poll/questions/show.html.erb new file mode 100644 index 000000000..a8f25f2bd --- /dev/null +++ b/app/views/admin/poll/questions/show.html.erb @@ -0,0 +1,46 @@ +<%= render "shared/back_link" %> + +<%= link_to t('shared.edit'), edit_admin_question_path(@question), + class: "button hollow float-right" %> + +
      + +
      +
      + <%= t("admin.questions.show.title") %> +

      <%= @question.title %>

      + + <% if @question.proposal.present? %> +

      + <%= t("admin.questions.show.proposal") %> +
      + <%= link_to @question.proposal.title, proposal_path(@question.proposal) %> +

      + <% end %> + +

      + <%= t("admin.questions.show.author") %> +
      + <%= link_to @question.author.name, user_path(@question.author) %> +

      + +

      + <%= t("admin.questions.show.valid_answers") %> +

      + + <% @question.valid_answers.each do |answer| %> + + <%= answer %> + + <% end %> + +

      + <%= t("admin.questions.show.description") %> +
      + <%= @question.description %> +

      + + + <%= link_to t("admin.questions.show.preview"), question_path(@question) %> +
      +
      diff --git a/app/views/admin/poll/recounts/index.html.erb b/app/views/admin/poll/recounts/index.html.erb new file mode 100644 index 000000000..98055f553 --- /dev/null +++ b/app/views/admin/poll/recounts/index.html.erb @@ -0,0 +1,58 @@ +<%= render "/admin/poll/polls/poll_header" %> +
      + <%= render "/admin/poll/polls/subnav" %> + +

      <%= t("admin.recounts.index.title") %>

      + + <% if @booth_assignments.empty? %> +
      + <%= t("admin.recounts.index.no_recounts") %> +
      + <% else %> + + + + + + + + + <% @booth_assignments.each do |booth_assignment| %> + <% recount = booth_assignment_sum_recounts(booth_assignment) %> + <% final_recount = booth_assignment_sum_final_recounts(booth_assignment) %> + <% system_count = booth_assignment.voters.size %> + + + + + + + <% end %> + +
      <%= t("admin.recounts.index.table_booth_name") %><%= t("admin.recounts.index.table_recounts") %><%= t("admin.recounts.index.table_final_recount") %><%= t("admin.recounts.index.table_system_count") %>
      + + <%= link_to booth_assignment.booth.name, admin_poll_booth_assignment_path(@poll, booth_assignment, anchor: 'tab-recounts') %> + + + <% if recount.present? %> + <%= recount %> + <% else %> + - + <% end %> + + <% if final_recount.present? %> + <%= final_recount %> + <% else %> + - + <% end %> + + <% if system_count.present? %> + <%= system_count %> + <% else %> + 0 + <% end %> +
      + + <%= paginate @booth_assignments %> + <% end %> +
      \ No newline at end of file diff --git a/app/views/admin/poll/results/index.html.erb b/app/views/admin/poll/results/index.html.erb new file mode 100644 index 000000000..9d3a6abef --- /dev/null +++ b/app/views/admin/poll/results/index.html.erb @@ -0,0 +1,50 @@ +<%= render "/admin/poll/polls/poll_header" %> +
      + <%= render "/admin/poll/polls/subnav" %> + +

      <%= t("admin.results.index.title") %>

      + + <% if @partial_results.empty? %> +
      + <%= t("admin.results.index.no_results") %> +
      + <% else %> + + + + + + + + + + + + +
      <%= t("admin.results.index.table_whites") %><%= t("admin.results.index.table_nulls") %>
      <%= @poll.white_results.sum(:amount) %><%= @poll.null_results.sum(:amount) %>
      + + + <% by_question = @partial_results.group_by(&:question_id) %> + <% @poll.questions.each do |question| %> +

      <%= question.title %>

      + + + + + + + + + <% question.valid_answers.each_with_index do |answer, i| %> + <% by_answer = by_question[question.id].present? ? by_question[question.id].group_by(&:answer) : {} %> + + + + + <% end %> + +
      <%= t("admin.results.index.table_answer") %><%= t("admin.results.index.table_votes") %>
      <%= answer %><%= by_answer[answer].present? ? by_answer[answer].sum(&:amount) : 0 %>
      + <% end %> + + <% end %> +
      \ No newline at end of file diff --git a/app/views/admin/proposals/index.html.erb b/app/views/admin/proposals/index.html.erb index 8df15af87..d929843b1 100644 --- a/app/views/admin/proposals/index.html.erb +++ b/app/views/admin/proposals/index.html.erb @@ -22,19 +22,17 @@

      <%= proposal.question %>

      - + <%= link_to t("admin.actions.restore"), restore_admin_proposal_path(proposal, request.query_parameters), method: :put, data: { confirm: t("admin.actions.confirm") }, - class: "button small success no-margin" %> - - + class: "button hollow on-hover-block" %> <% unless proposal.confirmed_hide? %> <%= link_to t("admin.actions.confirm_hide"), confirm_hide_admin_proposal_path(proposal, request.query_parameters), method: :put, - class: "button small warning float-right" %> + class: "button hollow warning on-hover-block" %> <% end %> diff --git a/app/views/admin/settings/index.html.erb b/app/views/admin/settings/index.html.erb index b18a1eb39..14c89b776 100644 --- a/app/views/admin/settings/index.html.erb +++ b/app/views/admin/settings/index.html.erb @@ -1,34 +1,97 @@

      <%= t("admin.settings.index.title") %>

      -
        - <% @settings.each do |setting| %> -
      • - <%= t("settings.#{setting.key}") %> - - <%= form_for(setting, url: admin_setting_path(setting), html: { id: "edit_#{dom_id(setting)}"}) do |f| %> - <%= f.text_area :value, label: false, id: dom_id(setting), lines: 1 %> - <%= f.submit(t('admin.settings.index.update_setting'), class: "button small success") %> - <% end %> -
      • - <% end %> -
      + + + <% @settings.each do |setting| %> + + + + + <% end %> + +
      + <%= t("settings.#{setting.key}") %> + + <%= form_for(setting, url: admin_setting_path(setting), html: { id: "edit_#{dom_id(setting)}"}) do |f| %> +
      + <%= f.text_area :value, label: false, id: dom_id(setting), lines: 1 %> +
      +
      + <%= f.submit(t('admin.settings.index.update_setting'), class: "button hollow expanded") %> +
      + <% end %> +

      <%= t("admin.settings.index.feature_flags") %>

      -
        - <% @feature_flags.each do |feature_flag| %> -
      • - <%= t("settings.#{feature_flag.key}") %> + + + <% @feature_flags.each do |feature_flag| %> + + -
        - <%= feature_flag.enabled? ? t("admin.settings.index.features.enabled") : t("admin.settings.index.features.disabled") %> -
        +
        - <%= form_for(feature_flag, url: admin_setting_path(feature_flag), html: { id: "edit_#{dom_id(feature_flag)}"}) do |f| %> + + + <% end %> + +
        + <%= t("settings.#{feature_flag.key}") %> + + <%= feature_flag.enabled? ? t("admin.settings.index.features.enabled") : t("admin.settings.index.features.disabled") %> + + <%= form_for(feature_flag, url: admin_setting_path(feature_flag), html: { id: "edit_#{dom_id(feature_flag)}"}) do |f| %> - <%= f.hidden_field :value, id: dom_id(feature_flag), value: (feature_flag.enabled? ? "" : "active") %> - <%= f.submit(t("admin.settings.index.features.#{feature_flag.enabled? ? 'disable' : 'enable'}"), class: "button small #{feature_flag.enabled? ? 'warning' : 'success'}", data: {confirm: t("admin.actions.confirm")}) %> + <%= f.hidden_field :value, id: dom_id(feature_flag), value: (feature_flag.enabled? ? "" : "active") %> + <%= f.submit(t("admin.settings.index.features.#{feature_flag.enabled? ? 'disable' : 'enable'}"), + class: "button expanded #{feature_flag.enabled? ? 'hollow alert' : 'success'}", + data: {confirm: t("admin.actions.confirm")}) %> + <% end %> +
        + +<% if @banner_styles.present? %> +

        <%= t("admin.settings.index.banners") %>

        + + + + <% @banner_styles.each do |setting| %> + + + + + <% end %> - - <% end %> - + +
        + <%= t("settings.#{setting.key}") %> + + <%= form_for(setting, url: admin_setting_path(setting), html: { id: "edit_#{dom_id(setting)}"}) do |f| %> + <%= f.text_area :value, label: false, id: dom_id(setting), lines: 1 %> + <%= f.submit(t('admin.settings.index.update_setting'), class: "button hollow") %> + <% end %> +
        +<% end %> + +<% if @banner_imgs.present?%> +

        <%= t("admin.settings.index.banner_imgs") %>

        + + + + <% @banner_imgs.each do |setting| %> + + + + + + <% end %> + +
        + <%= t("settings.#{setting.key}") %> + + <%= form_for(setting, url: admin_setting_path(setting), html: { id: "edit_#{dom_id(setting)}"}) do |f| %> + <%= f.text_area :value, label: false, id: dom_id(setting), lines: 1 %> + <%= f.submit(t('admin.settings.index.update_setting'), class: "button small success") %> + <% end %> +
        +<% end %> diff --git a/app/views/admin/shared/_budget_investment_search.html.erb b/app/views/admin/shared/_budget_investment_search.html.erb new file mode 100644 index 000000000..f29a3a0d1 --- /dev/null +++ b/app/views/admin/shared/_budget_investment_search.html.erb @@ -0,0 +1,26 @@ +<%= form_for(Budget::Investment.new, url: url, as: :budget_investment, method: :get) do |f| %> +
        +
        + <%= text_field_tag :search, "" %> +
        +
        + <%= select_tag :heading_id, + options_for_select(budget_heading_select_options(@budget), + params[:heading_id]), + include_blank: true + %> +
        +
        +
        +
        + <%= check_box_tag :unfeasible, "1", params[:unfeasible].present? %> + <%= t("admin.budget_investments.search_unfeasible") %> +
        +
        + +
        +
        + <%= f.submit t("shared.search"), class: "button" %> +
        +
        +<% end %> diff --git a/app/views/admin/shared/_proposal_search.html.erb b/app/views/admin/shared/_proposal_search.html.erb index 8fae3b543..895b6ea55 100644 --- a/app/views/admin/shared/_proposal_search.html.erb +++ b/app/views/admin/shared/_proposal_search.html.erb @@ -4,7 +4,7 @@ <%= text_field_tag :search, "", placeholder: t("admin.shared.proposal_search.placeholder") %>
        - <%= f.submit t("admin.shared.proposal_search.button"), class: "button success" %> + <%= f.submit t("admin.shared.proposal_search.button"), class: "button" %>
        <% end %> \ No newline at end of file diff --git a/app/views/admin/shared/_spending_proposal_search.html.erb b/app/views/admin/shared/_spending_proposal_search.html.erb new file mode 100644 index 000000000..cfb7e45f9 --- /dev/null +++ b/app/views/admin/shared/_spending_proposal_search.html.erb @@ -0,0 +1,10 @@ +<%= form_for(SpendingProposal.new, url: url, as: :spending_proposal, method: :get) do |f| %> +
        +
        + <%= text_field_tag :search, "", placeholder: t("admin.shared.spending_proposal_search.placeholder") %> +
        +
        + <%= f.submit t("admin.shared.spending_proposal_search.button"), class: "button" %> +
        +
        +<% end %> \ No newline at end of file diff --git a/app/views/admin/signature_sheets/index.html.erb b/app/views/admin/signature_sheets/index.html.erb new file mode 100644 index 000000000..74241b5b0 --- /dev/null +++ b/app/views/admin/signature_sheets/index.html.erb @@ -0,0 +1,31 @@ +

        <%= t("admin.signature_sheets.index.title") %>

        + +<%= link_to t("admin.signature_sheets.index.new"), new_admin_signature_sheet_path, + class: "button success float-right" %> + +<% if @signature_sheets.any? %> + + + + + + + <% @signature_sheets.each do |signature_sheet| %> + + + + + + <% end %> +
        <%= t("admin.signature_sheets.name") %><%= t("admin.signature_sheets.author") %><%= t("admin.signature_sheets.created_at") %>
        + <%= link_to signature_sheet.name, [:admin, signature_sheet] %> + + <%= signature_sheet.author.name %> + + <%= l(signature_sheet.created_at, format: :short) %> +
        +<% else %> +
        + <%= t("admin.signature_sheets.no_signature_sheets") %> +
        +<% end %> diff --git a/app/views/admin/signature_sheets/new.html.erb b/app/views/admin/signature_sheets/new.html.erb new file mode 100644 index 000000000..f471e1ea5 --- /dev/null +++ b/app/views/admin/signature_sheets/new.html.erb @@ -0,0 +1,22 @@ +<%= render 'shared/back_link' %> + +

        <%= t("admin.signature_sheets.new.title") %>

        + +<%= form_for [:admin, @signature_sheet] do |f| %> + <%= render 'shared/errors', + resource: @signature_sheet %> + +
        + <%= f.select :signable_type, signable_options %> +
        + +
        + <%= f.text_field :signable_id %> +
        + + <%= f.label :document_numbers %> +

        <%= t("admin.signature_sheets.new.document_numbers_note") %>

        + <%= f.text_area :document_numbers, rows: "6", label: false %> + + <%= f.submit(class: "button", value: t("admin.signature_sheets.new.submit")) %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/signature_sheets/show.html.erb b/app/views/admin/signature_sheets/show.html.erb new file mode 100644 index 000000000..0c2536b34 --- /dev/null +++ b/app/views/admin/signature_sheets/show.html.erb @@ -0,0 +1,44 @@ +

        <%= @signature_sheet.name %>

        + +
        + <%= t("admin.signature_sheets.show.created_at") %> + <%= l(@signature_sheet.created_at, format: :short) %> +  •  + <%= t("admin.signature_sheets.show.author") %> + <%= @signature_sheet.author.name %> +
        + +

        + <%= t("admin.signature_sheets.show.document_count") %> + <%= @signature_sheet.signatures.count %> +

        + +
        +

        <%= t("admin.signature_sheets.show.documents") %>

        + <%= simple_format @signature_sheet.document_numbers %> +
        + + +
        + + <%= t("admin.signature_sheets.show.verified", + count: @signature_sheet.signatures.verified.count ) %> + +
        + +
        +

        + + <%= t("admin.signature_sheets.show.unverified", + count: @signature_sheet.signatures.unverified.count ) %> + <%= t("admin.signature_sheets.show.unverified_error") %> + +

        + <%= @signature_sheet.signatures.unverified.map(&:document_number).join(", ") %> +
        + +<% unless @signature_sheet.processed? %> +
        + <%= t("admin.signature_sheets.show.loading") %> +
        +<% end %> \ No newline at end of file diff --git a/app/views/admin/site_customization/content_blocks/_form.html.erb b/app/views/admin/site_customization/content_blocks/_form.html.erb new file mode 100644 index 000000000..ae9d684d4 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/_form.html.erb @@ -0,0 +1,48 @@ +<%= form_for [:admin, @content_block], html: {class: "edit_page", data: {watch_changes: true}} do |f| %> + + <% if @content_block.errors.any? %> + +
        + + + + <%= @content_block.errors.count %> + <%= t("admin.site_customization.content_blocks.errors.form.error", count: @content_block.errors.count) %> + +
        + + <% end %> +
        +
        + <%= f.label :name %> + <%= f.select :name, SiteCustomization::ContentBlock::VALID_BLOCKS, label: false %> +
        +
        + <%= f.label :locale %> + <%= f.select :locale, I18n.available_locales, label: false %> +
        + +
        + <%= f.label :body %> + <%= f.text_area :body, label: false, rows: 10 %> +
        + <%= f.submit class: "button success expanded" %> +
        +
        +
        + +<% end %> + +

        <%= t('.content_blocks_information') %>

        + +

        <%= t('.content_block_about') %>

        + +

        <%= t('.content_block_top_links_html') %>

        + +<li><a href="http://site1.com">Site 1</a></li>
        +<li><a href="http://site2.com">Site 2</a></li>
        +<li><a href="http://site3.com">Site 3</a></li>
        + +

        <%= t('.content_block_footer_html') %>

        diff --git a/app/views/admin/site_customization/content_blocks/edit.html.erb b/app/views/admin/site_customization/content_blocks/edit.html.erb new file mode 100644 index 000000000..9c2694449 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/edit.html.erb @@ -0,0 +1,14 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.content_blocks") %> - <%= @content_block.name %> (<%= @content_block.locale %>) +<% end %> + +<%= back_link_to admin_site_customization_content_blocks_path %> + +<%= button_to t("admin.site_customization.content_blocks.index.delete"), admin_site_customization_content_block_path(@content_block), method: :delete, class: "button hollow alert float-right small" %> + +
        +
        +

        <%= t("admin.site_customization.content_blocks.edit.title") %>

        + <%= render 'form' %> +
        +
        diff --git a/app/views/admin/site_customization/content_blocks/index.html.erb b/app/views/admin/site_customization/content_blocks/index.html.erb new file mode 100644 index 000000000..e9fc664d1 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/index.html.erb @@ -0,0 +1,30 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.content_blocks") %> +<% end %> + +<%= link_to t("admin.site_customization.content_blocks.index.create"), new_admin_site_customization_content_block_path, class: "button float-right" %> + +

        <%= t("admin.site_customization.content_blocks.index.title") %>

        + + + + + + + + + + + <% @content_blocks.each do |content_block| %> + + + + + + <% end %> + +
        <%= t("admin.site_customization.content_blocks.content_block.name") %><%= t("admin.site_customization.content_blocks.content_block.body") %><%= t("admin.actions.actions") %>
        <%= link_to "#{content_block.name} (#{content_block.locale})", edit_admin_site_customization_content_block_path(content_block) %><%= content_block.body.html_safe %> + <%= button_to t("admin.site_customization.content_blocks.index.delete"), + admin_site_customization_content_block_path(content_block), + method: :delete, class: "button hollow alert" %> +
        diff --git a/app/views/admin/site_customization/content_blocks/new.html.erb b/app/views/admin/site_customization/content_blocks/new.html.erb new file mode 100644 index 000000000..6a5a1e0b3 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/new.html.erb @@ -0,0 +1,12 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.content_blocks") %> - <%= t("admin.site_customization.content_blocks.new.title") %> +<% end %> + +<%= back_link_to admin_site_customization_content_blocks_path %> + +
        +
        +

        <%= t("admin.site_customization.content_blocks.new.title") %>

        + <%= render 'form' %> +
        +
        diff --git a/app/views/admin/site_customization/images/index.html.erb b/app/views/admin/site_customization/images/index.html.erb new file mode 100644 index 000000000..95b9e18a7 --- /dev/null +++ b/app/views/admin/site_customization/images/index.html.erb @@ -0,0 +1,31 @@ +

        <%= t("admin.site_customization.images.index.title") %>

        + + + + + + + + + + <% @images.each do |image| %> + + + + + <% end %> + +
        <%= t("admin.site_customization.images.index.image") %><%= t("admin.actions.actions") %>
        + <%= image.name %> (<%= image.required_width %>x<%= image.required_height %>) + + <%= form_for([:admin, image], html: { id: "edit_#{dom_id(image)}"}) do |f| %> +
        + <%= image_tag image.image.url if image.image.exists? %> + <%= f.file_field :image, label: false %> +
        +
        + <%= f.submit(t('admin.site_customization.images.index.update'), class: "button hollow") %> + <%= link_to t('admin.site_customization.images.index.delete'), admin_site_customization_image_path(image), method: :delete, class: "button hollow alert" if image.image.exists? %> +
        + <% end %> +
        diff --git a/app/views/admin/site_customization/pages/_form.html.erb b/app/views/admin/site_customization/pages/_form.html.erb new file mode 100644 index 000000000..2c0d38ba6 --- /dev/null +++ b/app/views/admin/site_customization/pages/_form.html.erb @@ -0,0 +1,59 @@ +<%= form_for [:admin, @page], html: {class: "edit_page", data: {watch_changes: true}} do |f| %> + + <% if @page.errors.any? %> +
        + + + + <%= @page.errors.count %> + <%= t("admin.site_customization.pages.errors.form.error", count: @page.errors.count) %> + +
        + <% end %> + +
        +
        +
        +
        +
        +

        <%= t("admin.site_customization.pages.form.options") %>

        + <%= f.check_box :more_info_flag, class: "small" %> + <%= f.check_box :print_content_flag %> +
        +
        + <%= f.label :status %> + <% ::SiteCustomization::Page::VALID_STATUSES.each do |status| %> + <%= f.radio_button :status, status, label: false %> + <%= f.label "status_#{status}", t("admin.site_customization.pages.page.status_#{status}") %> +
        + <% end %> +
        +
        + <%= f.label :slug %> + <%= f.text_field :slug, label: false, size: 80, maxlength: 80 %> +
        +
        + +
        +
        +
        + + <%= f.label :title %> + <%= f.text_field :title, label: false %> + + <%= f.label :subtitle %> + <%= f.text_field :subtitle, label: false, size: 80, maxlength: 80 %> + +
        + <%= f.label :content %> + <%= f.cktext_area :content, label: false, cols: 80, rows: 10, ckeditor: { language: I18n.locale } %> +
        + +
        + <%= f.submit class: "button success expanded" %> +
        +
        +
        +<% end %> diff --git a/app/views/admin/site_customization/pages/edit.html.erb b/app/views/admin/site_customization/pages/edit.html.erb new file mode 100644 index 000000000..9a2fc92ab --- /dev/null +++ b/app/views/admin/site_customization/pages/edit.html.erb @@ -0,0 +1,14 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.pages") %> - <%= @page.title %> +<% end %> + +<%= back_link_to admin_site_customization_pages_path %> + +<%= button_to t("admin.site_customization.pages.index.delete"), admin_site_customization_page_path(@page), method: :delete, class: "button hollow alert small float-right" %> + +
        +
        +

        <%= t("admin.site_customization.pages.edit.title", page_title: @page.title) %>

        + <%= render 'form' %> +
        +
        diff --git a/app/views/admin/site_customization/pages/index.html.erb b/app/views/admin/site_customization/pages/index.html.erb new file mode 100644 index 000000000..f81631640 --- /dev/null +++ b/app/views/admin/site_customization/pages/index.html.erb @@ -0,0 +1,46 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.pages") %> +<% end %> + +<%= link_to t("admin.site_customization.pages.index.create"), new_admin_site_customization_page_path, class: "button float-right" %> +

        <%= t("admin.site_customization.pages.index.title") %>

        + +

        <%= page_entries_info @pages %>

        + + + + + + + + + + + + + <% @pages.each do |page| %> + + + + + + + + + <% end %> + +
        <%= t("admin.site_customization.pages.page.title") %><%= t("admin.site_customization.pages.page.created_at") %><%= t("admin.site_customization.pages.page.updated_at") %><%= t("admin.site_customization.pages.page.status") %><%= t("admin.actions.actions") %>
        + <%= link_to page.title, edit_admin_site_customization_page_path(page) %> + <%= I18n.l page.created_at, format: :short %><%= I18n.l page.created_at, format: :short %><%= t("admin.site_customization.pages.page.status_#{page.status}") %> + + <% if page.status == "published" %> + <%= link_to t("admin.site_customization.pages.index.see_page"), page.url, target: "_blank" %> + <% else %> + <%= t("admin.site_customization.pages.index.see_page") %> + <% end %> + + + <%= link_to t("admin.site_customization.pages.index.delete"), admin_site_customization_page_path(page), method: :delete, class: "delete" %> +
        + +<%= paginate @pages %> diff --git a/app/views/admin/site_customization/pages/new.html.erb b/app/views/admin/site_customization/pages/new.html.erb new file mode 100644 index 000000000..2d555c5b8 --- /dev/null +++ b/app/views/admin/site_customization/pages/new.html.erb @@ -0,0 +1,12 @@ +<% provide :title do %> + Admin - <%= t("admin.menu.site_customization.pages") %> - <%= t("admin.site_customization.pages.new.title") %> +<% end %> + +<%= back_link_to admin_site_customization_pages_path %> + +
        +
        +

        <%= t("admin.site_customization.pages.new.title") %>

        + <%= render 'form' %> +
        +
        diff --git a/app/views/admin/spending_proposals/_summary_table.html.erb b/app/views/admin/spending_proposals/_summary_table.html.erb new file mode 100644 index 000000000..c0c127ee7 --- /dev/null +++ b/app/views/admin/spending_proposals/_summary_table.html.erb @@ -0,0 +1,35 @@ + + + + + + + + + + <% spending_proposals.each do |geozone, price| %> + + + + + + + + + + <% end %> +
        <%= t("admin.spending_proposals.summary.geozone_name") %><%= t("admin.spending_proposals.summary.finished_and_feasible_count") %><%= t("admin.spending_proposals.summary.finished_and_unfeasible_count") %><%= t("admin.spending_proposals.summary.finished_count") %><%= t("admin.spending_proposals.summary.in_evaluation_count") %><%= t("admin.spending_proposals.summary.total_count") %><%= t("admin.spending_proposals.summary.cost_for_geozone") %>
        + <%= geozone.present? ? geozone.name : t("geozones.none") %> + + <%= spending_proposal_count_for_geozone("finished_and_feasible", geozone, second_scope) %> + + <%= spending_proposal_count_for_geozone("finished_and_unfeasible", geozone, second_scope) %> + + <%= spending_proposal_count_for_geozone("valuation_finished", geozone, second_scope) %> + + <%= spending_proposal_count_for_geozone("valuating", geozone, second_scope) %> + + <%= spending_proposal_count_for_geozone("all", geozone, second_scope) %> + + <%= number_to_currency(price) %> +
        \ No newline at end of file diff --git a/app/views/admin/spending_proposals/edit.html.erb b/app/views/admin/spending_proposals/edit.html.erb index 59180eb16..1fd86da59 100644 --- a/app/views/admin/spending_proposals/edit.html.erb +++ b/app/views/admin/spending_proposals/edit.html.erb @@ -2,43 +2,71 @@ <%= t("admin.spending_proposals.show.back") %> <% end %> -<%= render 'written_by_author' %> - -

        <%= t("admin.spending_proposals.edit.classification") %>

        - <%= form_for @spending_proposal, url: admin_spending_proposal_path(@spending_proposal) do |f| %> <% SpendingProposal.filter_params(params).each do |filter_name, filter_value| %> - <%= hidden_field_tag filter_name, filter_value %> + <%= hidden_field_tag filter_name, filter_value %> <% end %> - <%= f.select(:administrator_id, - @admins.collect{ |a| [a.name_and_email, a.id ] }, - { include_blank: t("admin.spending_proposals.edit.undefined") }, - class: "small-12 medium-6") %> +
        +
        + <%= f.text_field :title, maxlength: SpendingProposal.title_max_length %> +
        - <%= f.label :tag_list, t("admin.spending_proposals.edit.tags") %> -
        - <% @tags.each do |tag| %> - <%= tag.name %> - <% end %> +
        + <%= f.cktext_area :description, maxlength: SpendingProposal.description_max_length, ckeditor: { language: I18n.locale } %> +
        + +
        + <%= f.text_field :external_url %> +
        + +
        + <%= f.select :geozone_id, geozone_select_options, include_blank: t("geozones.none") %> +
        + +
        + <%= f.text_field :association_name, placeholder: t("spending_proposals.form.association_name") %> +
        - <%= f.text_field :tag_list, value: @spending_proposal.tag_list.to_s, - label: false, - placeholder: t("admin.spending_proposals.edit.tags_placeholder"), - class: 'js-tag-list' %> - <%= f.label :valuator_ids, t("admin.spending_proposals.edit.assigned_valuators") %> +

        <%= t("admin.spending_proposals.edit.classification") %>

        - <%= f.collection_check_boxes :valuator_ids, @valuators, :id, :email do |b| %> - <%= b.label(title: valuator_label(b.object)) { b.check_box + truncate(b.object.description_or_email, length: 60) } %> - <% end %> +
        + +
        + <%= f.select(:administrator_id, + @admins.collect{ |a| [a.name_and_email, a.id ] }, + { include_blank: t("admin.spending_proposals.edit.undefined") }) %> +
        + + +
        + <%= f.label :tag_list, t("admin.spending_proposals.edit.tags") %> +
        + <% @tags.each do |tag| %> + <%= tag.name %> + <% end %> +
        + <%= f.text_field :tag_list, value: @spending_proposal.tag_list.to_s, + label: false, + placeholder: t("admin.spending_proposals.edit.tags_placeholder"), + class: 'js-tag-list' %> +
        + +
        + <%= f.label :valuator_ids, t("admin.spending_proposals.edit.assigned_valuators") %> + + <%= f.collection_check_boxes :valuator_ids, @valuators, :id, :email do |b| %> + <%= b.label(title: valuator_label(b.object)) { b.check_box + truncate(b.object.description_or_email, length: 60) } %> + <% end %> +
        +

        <%= f.submit(class: "button", value: t("admin.spending_proposals.edit.submit_button")) %>

        - <% end %>
        diff --git a/app/views/admin/spending_proposals/index.html.erb b/app/views/admin/spending_proposals/index.html.erb index f50dea628..a1fe52c1b 100644 --- a/app/views/admin/spending_proposals/index.html.erb +++ b/app/views/admin/spending_proposals/index.html.erb @@ -1,22 +1,40 @@ -

        <%= t("admin.spending_proposals.index.title") %>

        +<%= link_to t("admin.spending_proposals.index.summary_link"), + summary_admin_spending_proposals_path, + class: "button float-right" %> + +<%= link_to t("admin.spending_proposals.index.valuator_summary_link"), + summary_admin_valuators_path, + class: "button float-right margin-right" %> + +

        <%= t("admin.spending_proposals.index.title") %>

        <%= form_tag admin_spending_proposals_path, method: :get, enforce_utf8: false do %> -
        +
        <%= select_tag :administrator_id, options_for_select(admin_select_options, params[:administrator_id]), { prompt: t("admin.spending_proposals.index.administrator_filter_all"), label: false, class: "js-submit-on-change" } %>
        -
        + +
        + <%= select_tag :valuator_id, + options_for_select(valuator_select_options, params[:valuator_id]), + { prompt: t("admin.spending_proposals.index.valuator_filter_all"), + label: false, + class: "js-submit-on-change" } %> +
        + +
        <%= select_tag :geozone_id, options_for_select(geozone_select_options.unshift([t("geozones.none"), "all"]), params[:geozone_id]), { prompt: t("admin.spending_proposals.index.geozone_filter_all"), label: false, class: "js-submit-on-change" } %>
        -
        + +
        <%= select_tag :tag_name, options_for_select(spending_proposal_tags_select_options, params[:tag_name]), { prompt: t("admin.spending_proposals.index.tags_filter_all"), @@ -50,12 +68,15 @@ <% if spending_proposal.valuators.size == 0 %> <%= t("admin.spending_proposals.index.no_valuators_assigned") %> <% else %> - <%= spending_proposal.valuators.collect(&:name).join(', ') %> + <%= spending_proposal.valuators.collect(&:description_or_name).join(', ') %> <% end %> <%= geozone_name(spending_proposal) %> + + <%= t("admin.spending_proposals.index.feasibility.#{spending_proposal.feasibility}", price: spending_proposal.price) %> + <% end %> diff --git a/app/views/admin/spending_proposals/show.html.erb b/app/views/admin/spending_proposals/show.html.erb index bbcf4b4fc..64313e030 100644 --- a/app/views/admin/spending_proposals/show.html.erb +++ b/app/views/admin/spending_proposals/show.html.erb @@ -1,16 +1,16 @@ -<%= link_to admin_spending_proposals_path(SpendingProposal.filter_params(params)) do %> +<%= link_to admin_spending_proposals_path(SpendingProposal.filter_params(params)), data: {no_turbolink: true} do %> <%= t("admin.spending_proposals.show.back") %> <% end %> <%= render 'written_by_author' %> -

        <%= t("admin.spending_proposals.show.classification") %>

        +<%= link_to t("admin.spending_proposals.show.edit"), + edit_admin_spending_proposal_path(@spending_proposal, + SpendingProposal.filter_params(params)) %> -

        - <%= link_to t("admin.spending_proposals.show.edit_classification"), - edit_admin_spending_proposal_path(@spending_proposal, - {anchor: 'form'}.merge(SpendingProposal.filter_params(params))) %> -

        +
        + +

        <%= t("admin.spending_proposals.show.classification") %>

        <%= t("admin.spending_proposals.show.assigned_admin") %>: <%= @spending_proposal.administrator.try(:name_and_email) || t("admin.spending_proposals.show.undefined") %> @@ -31,12 +31,19 @@ <% end %>

        +

        + <%= link_to t("admin.spending_proposals.show.edit_classification"), + edit_admin_spending_proposal_path(@spending_proposal, + {anchor: 'classification'}.merge(SpendingProposal.filter_params(params))) %> +

        +

        <%= t("admin.spending_proposals.show.dossier") %>

        +<%= render 'valuation/spending_proposals/written_by_valuators' %> +

        <%= link_to t("admin.spending_proposals.show.edit_dossier"), edit_valuation_spending_proposal_path(@spending_proposal) %>

        -<%= render 'valuation/spending_proposals/written_by_valuators' %> diff --git a/app/views/admin/spending_proposals/summary.html.erb b/app/views/admin/spending_proposals/summary.html.erb new file mode 100644 index 000000000..39fd2c014 --- /dev/null +++ b/app/views/admin/spending_proposals/summary.html.erb @@ -0,0 +1,18 @@ +<%= link_to admin_spending_proposals_path, class: "back" do %> + + <%= t("shared.back") %> +<% end %> + +

        <%= t("admin.spending_proposals.summary.title") %>

        +
        + <%= render 'summary_table', + spending_proposals: @spending_proposals, + second_scope: 'all' %> +
        + +
        +

        <%= t("admin.spending_proposals.summary.title_proposals_with_supports") %>

        + <%= render 'summary_table', + spending_proposals: @spending_proposals_with_supports, + second_scope: 'with_supports' %> +
        \ No newline at end of file diff --git a/app/views/admin/stats/direct_messages.html.erb b/app/views/admin/stats/direct_messages.html.erb new file mode 100644 index 000000000..b87e2b3e3 --- /dev/null +++ b/app/views/admin/stats/direct_messages.html.erb @@ -0,0 +1,21 @@ +<%= render 'shared/back_link' %> + +

        <%= t("admin.stats.direct_messages.title")%>

        + +
        +
        +
        + +
        + +
        +

        + <%= t("admin.stats.direct_messages.users_who_have_sent_message") %>
        + <%= @users_who_have_sent_message %> +

        +
        +
        +
        diff --git a/app/views/admin/stats/proposal_notifications.html.erb b/app/views/admin/stats/proposal_notifications.html.erb new file mode 100644 index 000000000..51130424d --- /dev/null +++ b/app/views/admin/stats/proposal_notifications.html.erb @@ -0,0 +1,41 @@ +<%= render 'shared/back_link' %> + +

        <%= t("admin.stats.proposal_notifications.title")%>

        + +
        +
        +
        + +
        + +
        +

        + <%= t("admin.stats.proposal_notifications.proposals_with_notifications") %>
        + + <%= @proposals_with_notifications %> + +

        +
        +
        +
        + + + + <% @proposal_notifications.each do |notification| %> + + + + <% end %> + +
        +

        + <%= notification.title %> + <%= link_to notification.proposal.title, proposal_path(notification.proposal) %> +

        +

        <%= notification.body %>

        +
        diff --git a/app/views/admin/stats/show.html.erb b/app/views/admin/stats/show.html.erb index 1966cd586..137f3ce81 100644 --- a/app/views/admin/stats/show.html.erb +++ b/app/views/admin/stats/show.html.erb @@ -1,10 +1,21 @@ <% content_for :head do %> <%= javascript_include_tag "stat_graphs", 'data-turbolinks-track' => true %> <% end %> -
        +
        -

        <%= t "admin.stats.show.stats_title" %>

        +

        <%= t "admin.stats.show.stats_title" %>

        + +
        + <%= link_to t("admin.stats.show.direct_messages"), + direct_messages_admin_stats_path, class: "button hollow" %> + <%= link_to t("admin.stats.show.proposal_notifications"), + proposal_notifications_admin_stats_path, class: "button hollow" %> + <%= link_to t("admin.stats.show.incomplete_verifications"), + admin_verifications_path, class: "button hollow" %> +
        + +
        @@ -25,6 +36,16 @@ <%= t "admin.stats.show.summary.comments" %>
        <%= number_with_delimiter(@comments) %>

        + <% if feature?(:budgets) %> +

        + <%= t "admin.stats.show.summary.budgets" %>
        + <%= number_with_delimiter(@budgets) %> +

        + + <% end %>
        @@ -76,14 +97,23 @@ <%= t "admin.stats.show.summary.user_level_three" %>
        <%= number_with_delimiter(@user_level_three) %>

        + +

        + <%= t "admin.stats.show.summary.verified_users_who_didnt_vote_proposals" %>
        + + <%=number_with_delimiter(@user_ids_who_didnt_vote_proposals)%> + +

        -
        - -
        + <% if feature?(:spending_proposals) %> +
        + +
        + <% end %>
        @@ -98,17 +128,19 @@ <% end %>
        -
        -

        <%= t "admin.stats.show.spending_proposals_title" %>

        - <%= spending_proposals_chart_tag id: "spending_proposals" %> -
        + <% if feature?(:spending_proposals) %> +
        +

        <%= t "admin.stats.show.spending_proposals_title" %>

        + <%= spending_proposals_chart_tag id: "spending_proposals" %> +
        + <% end %> -
        - <% @event_types.each do |event, count| %> -

        <%= event.titleize %> (<%= count %>)

        - <%= events_chart_tag event %> - <% end %> -
        + <% if feature?(:budgets) %> +
        +

        <%= t "admin.stats.show.budgets_title" %>

        + <%= budget_investments_chart_tag id: "budget_investments" %> +
        + <% end %>
        diff --git a/app/views/admin/tags/index.html.erb b/app/views/admin/tags/index.html.erb index 5e5ca5c99..c1eda4b30 100644 --- a/app/views/admin/tags/index.html.erb +++ b/app/views/admin/tags/index.html.erb @@ -22,29 +22,29 @@

        <%= t("admin.tags.index.title") %>: <%= page_entries_info @tags %>

        -
          + <% @tags.each do |tag| %> -
        • - <%= tag.name %> +
        • + + <% end %> - +
          + <%= form_for(tag, + url: admin_tag_path(tag), + as: :tag, + html: { id: "edit_tag_#{tag.id}", class: "text-right"}) do |f| %> - <%= form_for(tag, - url: admin_tag_path(tag), - as: :tag, - html: { id: "edit_tag_#{tag.id}", class: "text-right"}) do |f| %> + + <%= tag.name %> + <%= f.label "featured_#{tag.id}" do %> + <%= f.check_box :featured, title: t('admin.tags.mark_as_featured'), label: false, id: "tag_featured_#{tag.id}", class: "inline-block" %> + <%= t("admin.tags.mark_as_featured") %> + <% end %> + - - <%= f.label "featured_#{tag.id}" do %> - <%= f.check_box :featured, title: t('admin.tags.mark_as_featured'), label: false, id: "tag_featured_#{tag.id}", class: "inline-block" %> - <%= t("admin.tags.mark_as_featured") %> - <% end %> - - - <%= f.submit(t("admin.tags.update"), class: "button small success") %> - - <%= link_to t("admin.tags.destroy"), admin_tag_path(tag), method: :delete, class: "delete" %> - <% end %> - + <%= f.submit(t("admin.tags.update"), class: "button hollow on-hover") %> + <%= link_to t("admin.tags.destroy"), admin_tag_path(tag), method: :delete, class: "button hollow alert on-hover" %> + <% end %> +
          <%= paginate @tags %> diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb index a575bdef2..336443055 100644 --- a/app/views/admin/users/index.html.erb +++ b/app/views/admin/users/index.html.erb @@ -4,25 +4,29 @@

          <%= page_entries_info @users %>

          -
            + <% @users.each do |user| %> -
          • - <%= link_to user.name, admin_user_path(user) %> +
          • + - <%= link_to t("admin.actions.restore"), - restore_admin_user_path(user, request.query_parameters), - method: :put, - data: { confirm: t("admin.actions.confirm") }, - class: "button small success float-right" %> - <% unless user.confirmed_hide? %> - <%= link_to t("admin.actions.confirm_hide"), - confirm_hide_admin_user_path(user, request.query_parameters), - method: :put, - class: "button small warning float-right" %> - <% end %> - + + <% end %> - +
            +

            <%= link_to user.name, admin_user_path(user) %>

            +
            + <%= link_to t("admin.actions.restore"), + restore_admin_user_path(user, request.query_parameters), + method: :put, + data: { confirm: t("admin.actions.confirm") }, + class: "button hollow on-hover" %> + <% unless user.confirmed_hide? %> + <%= link_to t("admin.actions.confirm_hide"), + confirm_hide_admin_user_path(user, request.query_parameters), + method: :put, + class: "button hollow warning on-hover" %> + <% end %> +
            <%= paginate @users %> diff --git a/app/views/admin/users/show.html.erb b/app/views/admin/users/show.html.erb index 7fba928b3..1a6ab9795 100644 --- a/app/views/admin/users/show.html.erb +++ b/app/views/admin/users/show.html.erb @@ -1,3 +1,5 @@ +<%= render 'shared/back_link' %> +

            <%= t("admin.users.show.title", user: @user.name) %>

            @@ -5,37 +7,33 @@ <%= t("admin.users.show.registered_at") %> <%= @user.confirmed_at %> | <%= t("admin.users.show.hidden_at") %> <%= @user.hidden_at %>

            -

            - <%= link_to t("admin.users.show.back"), admin_users_path, - class: "button small secondary" %> -

            <% if @debates.present? %>

            <%= page_entries_info @debates %>

            <% end %> -
              -<% @debates.each do |debate| %> -
            • - <%= link_to debate.title, debate_path(debate) %> -
            • -<% end %> -
            + + <% @debates.each do |debate| %> + + + + <% end %> +
            + <%= link_to debate.title, debate_path(debate) %> +
            <% if @comments.present? %>

            <%= page_entries_info @comments %>

            <% end %> -
              -<% @comments.each do |comment| %> -
            • -
              -
              - <%= text_with_links comment.body %> -
              -
              -
            • -<% end %> -
            + + <% @comments.each do |comment| %> + + + + <% end %> +
            + <%= text_with_links comment.body %> +
            <%= paginate [@debates, @comments].sort_by {|x| x.size}.last %> diff --git a/app/views/admin/valuators/_valuator.html.erb b/app/views/admin/valuators/_valuator.html.erb index 0c64fcef0..e5971391b 100644 --- a/app/views/admin/valuators/_valuator.html.erb +++ b/app/views/admin/valuators/_valuator.html.erb @@ -1,12 +1,16 @@ -

            <%= t('admin.valuators.valuator.user_found') %>:

            -
            -
            -

            - <%= valuator.name %> -  •  - <%= valuator.email %> -

            -
            +
            +

            <%= t('admin.valuators.valuator.user_found') %>

            + + + + + + +
            + <%= valuator.name %> + + <%= valuator.email %> +
            <% unless @valuator.persisted? %> <%= form_for @valuator, url: admin_valuators_path do |f| %> @@ -21,4 +25,4 @@
            <% end %> <% end %> -
            \ No newline at end of file +
        diff --git a/app/views/admin/valuators/index.html.erb b/app/views/admin/valuators/index.html.erb index f3abe5e03..30132e66f 100644 --- a/app/views/admin/valuators/index.html.erb +++ b/app/views/admin/valuators/index.html.erb @@ -1,30 +1,36 @@

        <%= t("admin.valuators.index.title") %>

        +
        -
        - <%= form_tag search_admin_valuators_path, method: :get, remote: true do %> -
        - <%= text_field_tag :email, '', placeholder: t('admin.valuators.search.email_placeholder') %> -
        -
        - <%= submit_tag t('admin.valuators.search.search'), class: 'button radius' %> -
        - <% end %> -
        -
        -
        -

        <%= page_entries_info @valuators %>

        -
          - <% @valuators.each do |valuator| %> -
        • - <%= valuator.name %> -  •  - <%= valuator.email %> - <% if valuator.description.present? %> -  •  - <%= valuator.description %> - <% end %> -
        • + <%= form_tag search_admin_valuators_path, method: :get, remote: true do %> +
          + <%= text_field_tag :email, '', placeholder: t('admin.valuators.search.email_placeholder') %> +
          +
          + <%= submit_tag t('admin.valuators.search.search'), class: 'button radius' %> +
          <% end %> -
        +
        + +
        + +

        <%= page_entries_info @valuators %>

        + + + <% @valuators.each do |valuator| %> + + + + + + <% end %> +
        + <%= valuator.name %> + + <%= valuator.email %> + + <% if valuator.description.present? %> + <%= valuator.description %> + <% end %> +
        <%= paginate @valuators %> diff --git a/app/views/admin/valuators/summary.html.erb b/app/views/admin/valuators/summary.html.erb new file mode 100644 index 000000000..81cbc54d4 --- /dev/null +++ b/app/views/admin/valuators/summary.html.erb @@ -0,0 +1,39 @@ +<%= render 'shared/back_link' %> + +

        <%= t("admin.valuators.summary.title") %>

        + + + + + + + + + + + <% @valuators.each do |valuator| %> + + + + + + + + + + <% end %> +
        <%= t("admin.valuators.summary.valuator_name") %><%= t("admin.valuators.summary.finished_and_feasible_count") %><%= t("admin.valuators.summary.finished_and_unfeasible_count") %><%= t("admin.valuators.summary.finished_count") %><%= t("admin.valuators.summary.in_evaluation_count") %><%= t("admin.valuators.summary.total_count") %><%= t("admin.valuators.summary.cost") %>
        + <%= valuator.description_or_email %> + + <%= valuator.spending_proposals.finished_and_feasible.count %> + + <%= valuator.spending_proposals.finished_and_unfeasible.count %> + + <%= valuator.spending_proposals.valuation_finished.count %> + + <%= valuator.spending_proposals.valuating.count %> + + <%= valuator.spending_proposals.count %> + + <%= number_to_currency(valuator.spending_proposals.sum(:price)) %> +
        \ No newline at end of file diff --git a/app/views/admin/valuators/user_not_found.js.erb b/app/views/admin/valuators/user_not_found.js.erb index 1425577f4..af3f83a3f 100644 --- a/app/views/admin/valuators/user_not_found.js.erb +++ b/app/views/admin/valuators/user_not_found.js.erb @@ -1 +1 @@ -$("#search-result").html("

        <%= j t('admin.valuators.search.user_not_found') %>

        "); +$("#search-result").html("
        <%= j t('admin.valuators.search.user_not_found') %>
        "); diff --git a/app/views/admin/verifications/index.html.erb b/app/views/admin/verifications/index.html.erb index 429972ba1..97cca3a4e 100644 --- a/app/views/admin/verifications/index.html.erb +++ b/app/views/admin/verifications/index.html.erb @@ -4,13 +4,17 @@

        <%= page_entries_info @users %>

        -
          -<% @users.each do |user| %> -
        • - <%= link_to user.name, admin_user_path(user) %> - <%= render "pending_step", user: user %> -
        • -<% end %> -
        + + <% @users.each do |user| %> + + + + + <% end %> +
        + <%= link_to user.name, admin_user_path(user) %> + + <%= render "pending_step", user: user %> +
        <%= paginate @users %> \ No newline at end of file diff --git a/app/views/budgets/ballot/_ballot.html.erb b/app/views/budgets/ballot/_ballot.html.erb new file mode 100644 index 000000000..51f2750e9 --- /dev/null +++ b/app/views/budgets/ballot/_ballot.html.erb @@ -0,0 +1,69 @@ +
        +
        + <%= back_link_to @ballot_referer %> + +

        <%= t("budgets.ballots.show.title") %>

        + +
        +

        + <%= t("budgets.ballots.show.voted_html", + count: @ballot.investments.count) %> +

        +

        + + <%= t("budgets.ballots.show.voted_info_html") %> + +

        +
        +
        +
        + +
        + <% ballot_groups = @ballot.groups.order(name: :asc) %> + <% ballot_groups.each do |group| %> +
        +
        +
        +

        + <%= group.name %> - <%= @ballot.heading_for_group(group).name %> +

        + <%= link_to t("budgets.ballots.show.remaining", + amount: @ballot.formatted_amount_available(@ballot.heading_for_group(group))).html_safe, + budget_group_path(@budget, group) %> +
        + <% if @ballot.has_lines_in_group?(group) %> +

        + <%= t("budgets.ballots.show.amount_spent") %> + + <%= @ballot.formatted_amount_spent(@ballot.heading_for_group(group)) %> + +

        + <% else %> +

        + <%= t("budgets.ballots.show.zero") %>
        +

        + <% end %> + +
          + <%= render partial: 'budgets/ballot/investment', + collection: @ballot.investments.by_group(group.id) %> +
        +
        +
        + <% end %> + + <% no_balloted_groups = @budget.groups.order(name: :asc) - ballot_groups %> + <% no_balloted_groups.each do |group| %> +
        +
        +
        +

        + <%= group.name %> +

        + <%= link_to t("budgets.ballots.show.no_balloted_group_yet"), budget_group_path(@budget, group) %> +
        +
        +
        + <% end %> + +
        diff --git a/app/views/budgets/ballot/_investment.html.erb b/app/views/budgets/ballot/_investment.html.erb new file mode 100644 index 000000000..7f2bafcb8 --- /dev/null +++ b/app/views/budgets/ballot/_investment.html.erb @@ -0,0 +1,14 @@ +
      • + <%= link_to investment.title, budget_investment_path(@budget, investment) %> + <%= investment.formatted_price %> + + <% if @budget.balloting? %> + <%= link_to budget_ballot_line_path(@budget, id: investment.id), + title: t('budgets.ballots.show.remove'), + class: "remove-investment-project", + method: :delete, + remote: true do %> + + <% end %> + <% end %> +
      • diff --git a/app/views/budgets/ballot/_investment_for_sidebar.html.erb b/app/views/budgets/ballot/_investment_for_sidebar.html.erb new file mode 100644 index 000000000..5dc288aaa --- /dev/null +++ b/app/views/budgets/ballot/_investment_for_sidebar.html.erb @@ -0,0 +1,16 @@ +
      • + <%= investment.title %> + <%= investment.formatted_price %> + + <% if @budget.balloting? %> + <%= link_to budget_ballot_line_url(id: investment.id, + investments_ids: investment_ids), + title: t('budgets.ballots.show.remove'), + class: "remove-investment-project", + method: :delete, + remote: true do %> + <%= t('budgets.ballots.show.remove') %> + + <% end %> + <% end %> +
      • diff --git a/app/views/budgets/ballot/_progress_bar.html.erb b/app/views/budgets/ballot/_progress_bar.html.erb new file mode 100644 index 000000000..ec9d28457 --- /dev/null +++ b/app/views/budgets/ballot/_progress_bar.html.erb @@ -0,0 +1,29 @@ + + <%= @budget.formatted_heading_price(@heading) %> + + +
        +
        +
        +
        + +
        + +

        + <%= @ballot.formatted_amount_spent(@heading) %> + + <%= t("budgets.progress_bar.available") %> + <%= @ballot.formatted_amount_available(@heading) %> + +

        +
        +
        diff --git a/app/views/budgets/ballot/lines/_refresh_ballots.js.erb b/app/views/budgets/ballot/lines/_refresh_ballots.js.erb new file mode 100644 index 000000000..f81560d96 --- /dev/null +++ b/app/views/budgets/ballot/lines/_refresh_ballots.js.erb @@ -0,0 +1,8 @@ +<% if @investments.present? %> + <% @investments.each do |investment| %> + $("#<%= dom_id(investment) %>_ballot").html('<%= j render("/budgets/investments/ballot", + investment: investment, + investment_ids: investment_ids, + ballot: ballot) %>'); + <% end %> +<% end %> diff --git a/app/views/budgets/ballot/lines/create.js.erb b/app/views/budgets/ballot/lines/create.js.erb new file mode 100644 index 000000000..b1c9f76be --- /dev/null +++ b/app/views/budgets/ballot/lines/create.js.erb @@ -0,0 +1,11 @@ +$("#progress_bar").html('<%= j render("/budgets/ballot/progress_bar", ballot: @ballot) %>'); +$("#sidebar").html('<%= j render("/budgets/investments/sidebar") %>'); +$("#<%= dom_id(@investment) %>_ballot").html('<%= j render("/budgets/investments/ballot", + investment: @investment, + investment_ids: @investment_ids, + ballot: @ballot) %>'); + +<%= render 'refresh_ballots', + investment: @investment, + investment_ids: @investment_ids, + ballot: @ballot %> diff --git a/app/views/budgets/ballot/lines/destroy.js.erb b/app/views/budgets/ballot/lines/destroy.js.erb new file mode 100644 index 000000000..82c303047 --- /dev/null +++ b/app/views/budgets/ballot/lines/destroy.js.erb @@ -0,0 +1,12 @@ +$("#progress_bar").html('<%= j render("budgets/ballot/progress_bar", ballot: @ballot) %>'); +$("#sidebar").html('<%= j render("budgets/investments/sidebar") %>'); +$("#ballot").html('<%= j render("budgets/ballot/ballot") %>') + +$("#<%= dom_id(@investment) %>_ballot").html('<%= j render("/budgets/investments/ballot", + investment: @investment, + investment_ids: @investment_ids, + ballot: @ballot) %>'); +<%= render 'refresh_ballots', + investment: @investment, + investment_ids: @investment_ids, + ballot: @ballot %> diff --git a/app/views/budgets/ballot/lines/new.js.erb b/app/views/budgets/ballot/lines/new.js.erb new file mode 100644 index 000000000..2e8254866 --- /dev/null +++ b/app/views/budgets/ballot/lines/new.js.erb @@ -0,0 +1,2 @@ +$("#<%= dom_id(@spending_proposal) %>_ballot").html('<%= j render("spending_proposals/ballot", spending_proposal: @spending_proposal) %>'); +$(".no-supports-allowed").show(); \ No newline at end of file diff --git a/app/views/budgets/ballot/show.html.erb b/app/views/budgets/ballot/show.html.erb new file mode 100644 index 000000000..b4aaa2d46 --- /dev/null +++ b/app/views/budgets/ballot/show.html.erb @@ -0,0 +1,3 @@ +
        + <%= render "budgets/ballot/ballot" %> +
        diff --git a/app/views/budgets/groups/show.html.erb b/app/views/budgets/groups/show.html.erb new file mode 100644 index 000000000..a5850481d --- /dev/null +++ b/app/views/budgets/groups/show.html.erb @@ -0,0 +1,74 @@ +<% content_for :canonical do %> + <%= render "shared/canonical", href: budget_group_url(filter: @current_filter) %> +<% end %> + +
        +
        +
        + <%= back_link_to budget_path(@budget) %> +

        <%= t("budgets.groups.show.title") %>

        +
        +
        +
        + +<% if @current_filter == "unfeasible" %> +
        +
        +

        <%= t("budgets.groups.show.unfeasible_title") %>

        +
        +
        +<% elsif @current_filter == "unselected" %> +
        +
        +

        <%= t("budgets.groups.show.unselected_title") %>

        +
        +
        +<% end %> + +
        +
        +
        + <% @group.headings.each_slice(7) do |slice| %> +
        + <% slice.each do |heading| %> + + <%= link_to heading.name, + budget_investments_path(heading_id: heading.id, + filter: @current_filter), + data: { no_turbolink: true } %>
        +
        + <% end %> +
        + <% end %> +
        +
        + +
        + <%= image_tag "map.jpg" %> +
        +
        + +<% if @budget.balloting_or_later? %> + <% unless @current_filter == "unfeasible" %> +
        +
        + + <%= link_to t("budgets.groups.show.unfeasible"), + budget_path(@budget, filter: "unfeasible") %> + +
        +
        + <% end %> + + <% unless @current_filter == "unselected" %> +
        +
        + + <%= link_to t("budgets.groups.show.unselected"), + budget_path(@budget, filter: "unselected") %> + +
        +
        + <% end %> +<% end %> diff --git a/app/views/budgets/index.html.erb b/app/views/budgets/index.html.erb new file mode 100644 index 000000000..9f5ab0ab2 --- /dev/null +++ b/app/views/budgets/index.html.erb @@ -0,0 +1,36 @@ +<% content_for :canonical do %> + <%= render "shared/canonical", href: budgets_url %> +<% end %> + +
        +
        +
        +

        <%= t('budgets.index.title') %>

        +
        +
        +
        + +
        +
        + + + + + + + + + <% @budgets.each do |budget| %> + + + + + <% end %> + +
        <%= Budget.human_attribute_name(:name) %><%= Budget.human_attribute_name(:phase) %>
        + <%= link_to budget.name, budget %> + + <%= budget.translated_phase %> +
        +
        +
        diff --git a/app/views/budgets/investments/_ballot.html.erb b/app/views/budgets/investments/_ballot.html.erb new file mode 100644 index 000000000..0ad43ab43 --- /dev/null +++ b/app/views/budgets/investments/_ballot.html.erb @@ -0,0 +1,59 @@ +<% reason = investment.reason_for_not_being_ballotable_by(current_user, ballot) %> +
        + <% if ballot.has_investment?(investment) %> + +
        + "> + +

        + <%= investment.formatted_price %> +

        + <% if investment.should_show_ballots? %> + <%= link_to t('budgets.ballots.show.remove'), + budget_ballot_line_path(id: investment.id, + budget_id: investment.budget_id, + investments_ids: investment_ids), + class: "delete small expanded", + method: :delete, + remote: true %> + <% end %> +
        + + <% else %> + +
        +

        + <%= investment.formatted_price %> +

        + <% if investment.should_show_ballots? %> + <%= link_to t("budgets.investments.investment.add"), + budget_ballot_lines_url(investment_id: investment.id, + budget_id: investment.budget_id, + investments_ids: investment_ids), + class: "button button-support small expanded", + title: t('budgets.investments.investment.support_title'), + method: :post, + remote: true %> + <% end %> +
        + + <% end %> + + <% if reason.present? && !ballot.has_investment?(investment) %> + + + + <% end %> +
        diff --git a/app/views/budgets/investments/_categories.html.erb b/app/views/budgets/investments/_categories.html.erb new file mode 100644 index 000000000..ad628b1b1 --- /dev/null +++ b/app/views/budgets/investments/_categories.html.erb @@ -0,0 +1,11 @@ + + +
        + +
          + <% @categories.each do |category| %> +
        • + <%= link_to category.name, + budget_investments_path(@budget, search: category.name) %>
        • + <% end %> +
        \ No newline at end of file diff --git a/app/views/budgets/investments/_feasibility_link.html.erb b/app/views/budgets/investments/_feasibility_link.html.erb new file mode 100644 index 000000000..118a02e7f --- /dev/null +++ b/app/views/budgets/investments/_feasibility_link.html.erb @@ -0,0 +1,11 @@ + + +
        + +<% if params[:unfeasible].present? %> + <%= link_to t("budgets.investments.index.sidebar.feasible"), + budget_investments_path(@budget, heading_id: @heading, unfeasible: nil) %> +<% else %> + <%= link_to t("budgets.investments.index.sidebar.unfeasible"), + budget_investments_path(@budget, heading_id: @heading, unfeasible: 1) %> +<% end %> \ No newline at end of file diff --git a/app/views/budgets/investments/_form.html.erb b/app/views/budgets/investments/_form.html.erb new file mode 100644 index 000000000..12f91f486 --- /dev/null +++ b/app/views/budgets/investments/_form.html.erb @@ -0,0 +1,69 @@ +<%= form_for(@investment, url: form_url, method: :post) do |f| %> + <%= render 'shared/errors', resource: @investment %> + +
        +
        + <%= f.select :heading_id, budget_heading_select_options(@budget), {include_blank: true, } %> +
        + +
        + <%= f.text_field :title, maxlength: SpendingProposal.title_max_length %> +
        + + <%= f.invisible_captcha :subtitle %> + +
        + <%= f.cktext_area :description, maxlength: SpendingProposal.description_max_length, ckeditor: { language: I18n.locale } %> +
        + +
        + <%= f.text_field :external_url %> +
        + +
        + <%= f.text_field :location %> +
        + +
        + <%= f.text_field :organization_name %> +
        + +
        + <%= f.label :tag_list, t("budgets.investments.form.tags_label") %> +

        <%= t("budgets.investments.form.tags_instructions") %>

        + +
        + <%= f.label :category_tag_list, t("budgets.investments.form.tag_category_label") %> + <% @categories.each do |tag| %> + <%= tag.name %> + <% end %> +
        + +
        + <%= f.text_field :tag_list, value: @investment.tag_list.to_s, + label: false, + placeholder: t("budgets.investments.form.tags_placeholder"), + class: 'js-tag-list' %> +
        + + + <% unless current_user.manager? %> + +
        + <%= f.label :terms_of_service do %> + <%= f.check_box :terms_of_service, title: t('form.accept_terms_title'), label: false %> + + <%= t("form.accept_terms", + policy: link_to(t("form.policy"), "/privacy", target: "blank"), + conditions: link_to(t("form.conditions"), "/conditions", target: "blank")).html_safe %> + + <% end %> +
        + + <% end %> + +
        + <%= f.submit(nil, class: "button expanded") %> +
        +
        +<% end %> diff --git a/app/views/budgets/investments/_header.html.erb b/app/views/budgets/investments/_header.html.erb new file mode 100644 index 000000000..2ed01d989 --- /dev/null +++ b/app/views/budgets/investments/_header.html.erb @@ -0,0 +1,66 @@ +<% if @heading.present? %> +
        +
        + +
        +
        + <% if @heading.group.headings.count == 1 %> + <%= back_link_to budget_path(@budget, unfeasible: params[:unfeasible]) %> + <% else %> + <%= back_link_to budget_group_path(@budget, + @heading.group, + unfeasible: params[:unfeasible]) %> + <% end %> + <% if can? :show, @ballot %> + <%= link_to t("budgets.investments.header.check_ballot"), + budget_ballot_path(@budget), + class: "button float-right" %> + <% end %> +
        +
        + +
        +
        + <% if can? :show, @ballot %> + + <% if @ballot.valid_heading?(@heading) %> +
        +

        <%= @heading.name %>

        +
        + <%= render 'budgets/ballot/progress_bar' %> +
        +
        + <% else %> +

        <%= @heading.name %>

        +
        +
        + <%= t("budgets.investments.header.different_heading_assigned_html", + heading_link: link_to( + @assigned_heading.name, + budget_investments_path(@budget, heading_id: @assigned_heading.try(:id))) + ) %> +
        +
        + <% end %> + <% else %> +

        <%= @heading.name %>

        +

        <%= @budget.formatted_heading_price(@heading) %>

        + <% end %> +
        +
        +
        +
        +<% else %> +
        +
        +
        + <%= back_link_to budget_path(@budget) %> + +

        <%= t('budgets.investments.index.title') %>

        +
        +
        +
        +<% end %> diff --git a/app/views/budgets/investments/_investment.html.erb b/app/views/budgets/investments/_investment.html.erb new file mode 100644 index 000000000..3155853cd --- /dev/null +++ b/app/views/budgets/investments/_investment.html.erb @@ -0,0 +1,88 @@ +
        +
        +
        + +
        +
        + + <% cache [locale_and_user_status(investment), 'index', investment, investment.author] do %> + <%= t("budgets.investments.investment.title") %> + +

        <%= link_to investment.title, namespaced_budget_investment_path(investment) %>

        +

        + + <%= l investment.created_at.to_date %> + + <% if investment.author.hidden? || investment.author.erased? %> +  •  + + <%= t("budgets.investments.show.author_deleted") %> + + <% else %> +  •  + + <%= investment.author.name %> + + <% if investment.author.official? %> +  •  + + <%= investment.author.official_position %> + + <% end %> + <% end %> + +  •  + <%= investment.heading.name %> +

        +
        +

        <%= investment.description %>

        +
        +
        + <%= render "shared/tags", taggable: investment, limit: 5 %> + <% end %> +
        +
        + + <% unless investment.unfeasible? %> + + <% if investment.should_show_votes? %> +
        + <%= render partial: '/budgets/investments/votes', locals: { + investment: investment, + investment_votes: investment_votes, + vote_url: namespaced_budget_investment_vote_path(investment, value: 'yes') + } %> +
        + <% elsif investment.should_show_vote_count? %> +
        +
        + + <%= t("budgets.investments.investment.supports", + count: investment.total_votes) %> + +
        +
        + <% elsif investment.should_show_ballots? %> +
        + <%= render partial: '/budgets/investments/ballot', locals: { + investment: investment, + investment_ids: investment_ids, + ballot: ballot + } %> +
        + <% elsif investment.should_show_price? %> +
        +

        + <%= investment.formatted_price %> +

        +
        + <% end %> + + <% end %> +
        +
        +
        diff --git a/app/views/budgets/investments/_investment_show.html.erb b/app/views/budgets/investments/_investment_show.html.erb new file mode 100644 index 000000000..f9a371585 --- /dev/null +++ b/app/views/budgets/investments/_investment_show.html.erb @@ -0,0 +1,122 @@ +
        + +
        +
        + <%= back_link_to budget_investments_path(investment.budget) %> + +

        <%= investment.title %>

        + +
        + <%= render '/shared/author_info', resource: investment %> + +  •  + <%= l investment.created_at.to_date %> +  •  + <%= investment.heading.name %> +
        + +
        +

        + <%= t("budgets.investments.show.code_html", code: investment.id) %> +

        + + <% if investment.location.present? %> +

        + <%= t("budgets.investments.show.location_html", location: investment.location) %> +

        + <% end %> + + <% if investment.organization_name.present? %> +

        + <%= t("budgets.investments.show.organization_name_html", name: investment.organization_name) %> +

        + <% end %> + + <%= render 'shared/tags', taggable: investment %> + + <%= safe_html_with_links investment.description.html_safe %> + + <% if investment.external_url.present? %> + + <% end %> + + <% if investment.unfeasible? && investment.unfeasibility_explanation.present? %> +

        <%= t('budgets.investments.show.unfeasibility_explanation') %>

        +

        <%= investment.unfeasibility_explanation %>

        + <% end %> + + <% if investment.should_show_price_info? %> +

        <%= t('budgets.investments.show.price_explanation') %>

        +

        <%= investment.price_explanation %>

        + <% end %> +
        + + +
        +
        diff --git a/app/views/budgets/investments/_sidebar.html.erb b/app/views/budgets/investments/_sidebar.html.erb new file mode 100644 index 000000000..06d3b9d81 --- /dev/null +++ b/app/views/budgets/investments/_sidebar.html.erb @@ -0,0 +1,62 @@ +
        + +<% if can?(:create, Budget::Investment.new(budget: @budget)) %> + <% if current_user && current_user.level_two_or_three_verified? %> + <%= link_to t("budgets.investments.index.sidebar.create"), + new_budget_investment_path(budget_id: @budget.id), class: "button budget expanded" %> + <% else %> +
        + <%= t("budgets.investments.index.sidebar.verified_only", + verify: link_to(t("budgets.investments.index.sidebar.verify_account"), + verification_path)).html_safe %> +
        + <% end %> +<% end %> + +<% if @heading && can?(:show, @ballot) %> +

        + <%= t("budgets.investments.index.sidebar.voted_info", + link: link_to(t("budgets.investments.index.sidebar.voted_info_link"), + budget_ballot_path(@budget))).html_safe %> +

        +<% end %> + +<%= render "shared/tag_cloud", taggable: 'budget/investment' %> +<%= render 'budgets/investments/categories' %> + +<% if @heading && can?(:show, @ballot) %> + + + + + + <% if @ballot.investments.by_heading(@heading.id).count > 0 %> +

        + + <%= t("budgets.investments.index.sidebar.voted_html", + count: @ballot.investments.by_heading(@heading.id).count, + amount_spent: @ballot.formatted_amount_spent(@heading)) %> + +

        + <% elsif @assigned_heading.present? %> +

        <%= t("budgets.investments.index.sidebar.different_heading_assigned_html", + heading_link: link_to( + @assigned_heading.name, + budget_investments_path(@budget, heading_id: @assigned_heading.try(:id))) + ) %>

        + <% else %> +

        <%= t("budgets.investments.index.sidebar.zero") %>

        + <% end %> + +
          + <% if @heading %> + <% @ballot.investments.by_heading(@heading.id).each do |investment| %> + <%= render 'budgets/ballot/investment_for_sidebar', + investment: investment, + investment_ids: @investment_ids %> + <% end %> + <% end %> +
        +<% end %> diff --git a/app/views/budgets/investments/_votes.html.erb b/app/views/budgets/investments/_votes.html.erb new file mode 100644 index 000000000..c2870d76c --- /dev/null +++ b/app/views/budgets/investments/_votes.html.erb @@ -0,0 +1,46 @@ +<% reason = investment.reason_for_not_being_selectable_by(current_user) %> +<% voting_allowed = true unless reason.presence == :not_voting_allowed %> +<% user_voted_for = voted_for?(investment_votes, investment) %> + +
        + + + <%= t("budgets.investments.investment.supports", count: investment.total_votes) %> + + +
        + <% if user_voted_for %> +
        + <%= t("budgets.investments.investment.already_supported") %> +
        + <% elsif investment.should_show_votes? %> + <%= link_to vote_url, + class: "button button-support small expanded", + title: t('budgets.investments.investment.support_title'), + method: "post", + remote: (current_user && current_user.voted_in_group?(investment.group) ? true : false), + data: (current_user && current_user.voted_in_group?(investment.group) ? nil : { confirm: t('budgets.investments.investment.confirm_group')} ), + "aria-hidden" => css_for_aria_hidden(reason) do %> + <%= t("budgets.investments.investment.give_support") %> + <% end %> + <% end %> +
        + + <% if reason.present? && !user_voted_for %> + + <% end %> + + <% if user_voted_for && setting['twitter_handle'] %> + + <% end %> +
        diff --git a/app/views/budgets/investments/index.html.erb b/app/views/budgets/investments/index.html.erb new file mode 100644 index 000000000..4c6871826 --- /dev/null +++ b/app/views/budgets/investments/index.html.erb @@ -0,0 +1,67 @@ +<% provide :title do %><%= t('budgets.investments.index.title') %><% end %> +<% content_for :header_addon do %> + <%= render "shared/search_form", + search_path: budget_investments_path(budget_id: @budget.id, page: 1), + i18n_namespace: "budgets.investments.index.search_form" %> +<% end %> +<% content_for :canonical do %> + <% if @heading.present? %> + <%= render "shared/canonical", href: budget_investments_url(@budget, heading_id: @heading.id, filter: @current_filter) %> + <% else %> + <%= render "shared/canonical", href: budget_investments_url(@budget) %> + <% end %> +<% end %> + +
        + + <% unless params[:search].present? %> + <%= render '/budgets/investments/header' %> + <% end %> + +
        +
        + +
        + + <% if @current_filter == "unfeasible" %> +

        <%= t("budgets.investments.index.unfeasible") %>: <%= @heading.name %>

        +
        + <%= t("budgets.investments.index.unfeasible_text", + definitions: link_to(t("budgets.investments.index.unfeasible_text_definitions"), "https://decide.madrid.es/participatory_budget_info#20")).html_safe %> +
        + <% else %> + <%= content_tag(:h2, t("budgets.investments.index.by_heading", heading: @heading.name)) if @heading.present? %> + <% end %> + + <% if params[:search].present? %> +

        + <%= page_entries_info @investments %> + <%= t("budgets.investments.index.search_results", count: @investments.size, search_term: params[:search]) %> +

        + <% end %> +
        + + <%= render('shared/order_links', i18n_namespace: "budgets.investments.index") unless @current_filter == "unfeasible" %> + + <% @investments.each do |investment| %> + <%= render partial: '/budgets/investments/investment', locals: { + investment: investment, + investment_ids: @investment_ids, + investment_votes: @investment_votes, + ballot: @ballot + } %> + <% end %> + + <%= paginate @investments %> +
        + +
        + +
        + +
        +
        diff --git a/app/views/budgets/investments/new.html.erb b/app/views/budgets/investments/new.html.erb new file mode 100644 index 000000000..457fbd550 --- /dev/null +++ b/app/views/budgets/investments/new.html.erb @@ -0,0 +1,7 @@ +
        +
        +

        <%= t("management.budget_investments.create") %>

        + + <%= render '/budgets/investments/form', form_url: budget_investments_path(@budget) %> +
        +
        diff --git a/app/views/budgets/investments/show.html.erb b/app/views/budgets/investments/show.html.erb new file mode 100644 index 000000000..73861697e --- /dev/null +++ b/app/views/budgets/investments/show.html.erb @@ -0,0 +1,13 @@ +<% provide :title do %><%= @investment.title %><% end %> +<% content_for :canonical do %> + <%= render "shared/canonical", href: budget_investment_url %> +<% end %> + +<%= render partial: '/budgets/investments/investment_show', locals: { + investment: @investment, + investment_ids: @investment_ids, + investment_votes: @investment_votes, + ballot: @ballot +} %> + +<%= render partial: '/comments/comment_tree', locals: { comment_tree: @comment_tree, comment_flags: @comment_flags } %> diff --git a/app/views/budgets/investments/vote.js.erb b/app/views/budgets/investments/vote.js.erb new file mode 100644 index 000000000..56248ba68 --- /dev/null +++ b/app/views/budgets/investments/vote.js.erb @@ -0,0 +1,4 @@ +$("#<%= dom_id(@investment) %>_votes").html('<%= j render("/budgets/investments/votes", + investment: @investment, + investment_votes: @investment_votes, + vote_url: namespaced_budget_investment_vote_path(@investment, value: 'yes')) %>'); diff --git a/app/views/budgets/results/_results_table.html.erb b/app/views/budgets/results/_results_table.html.erb new file mode 100644 index 000000000..9024899b6 --- /dev/null +++ b/app/views/budgets/results/_results_table.html.erb @@ -0,0 +1,72 @@ +
        +

        + <%= heading.name %> +

        + + <%= link_to t("budgets.results.show_all_link"), "#", + class: "js-toggle-link button hollow margin-bottom float-right", + data: {'toggle-selector' => '.js-discarded', + 'toggle-text' => t("budgets.results.hide_discarded_link")} %> + + + + + + + + + + + + + <% amount_available = heading.price %> + <% @result.investments.each do |investment| %> + <% if investment.winner? %> + + <% else %> + + <% end %> + + + + + + <% end %> + +
        + <%= t("budgets.results.spending_proposal") %> + + <%= t("budgets.results.ballot_lines_count") %> + + <%= t("budgets.results.price") %> + + <%= format_price(heading.price) %>
        + <%= t("budgets.results.amount_available") %> +
        +
        diff --git a/app/views/budgets/results/show.html.erb b/app/views/budgets/results/show.html.erb new file mode 100644 index 000000000..d9253b785 --- /dev/null +++ b/app/views/budgets/results/show.html.erb @@ -0,0 +1,38 @@ +<% provide :title, t("budgets.results.page_title", budget: @budget.name) %> +<% content_for :canonical do %> + <%= render "shared/canonical", href: budget_results_url(@budget, heading_id: @result.heading) %> +<% end %> + +
        +
        +
        + <%= back_link_to budget_path(@budget) %> + +

        + <%= @budget.name %>
        + <%= t("budgets.results.heading") %> +

        +
        +
        +
        + +
        +
        + +
        + + <%= render 'results_table', heading: @result.heading %> +
        diff --git a/app/views/budgets/show.html.erb b/app/views/budgets/show.html.erb new file mode 100644 index 000000000..814488d9d --- /dev/null +++ b/app/views/budgets/show.html.erb @@ -0,0 +1,105 @@ +<% content_for :canonical do %> + <%= render "shared/canonical", href: budget_url(@budget, filter: @current_filter) %> +<% end %> + +
        +
        +
        + <%= back_link_to budgets_path %> + +

        <%= @budget.name %>

        + + <%= safe_html_with_links @budget.description %> +
        +
        +

        + <%= t('budgets.show.phase') %> +

        +

        <%= t("budgets.phase.#{@budget.phase}") %>

        + + <% if @budget.accepting? %> + <% if current_user %> + <% if current_user.level_two_or_three_verified? %> + <%= link_to t("budgets.investments.index.sidebar.create"), new_budget_investment_path(@budget), class: "button margin-top expanded" %> + <% else %> +
        + <%= t("budgets.investments.index.sidebar.verified_only", + verify: link_to(t("budgets.investments.index.sidebar.verify_account"), verification_path)).html_safe %> +
        + <% end %> + <% else %> +
        + <%= t("budgets.investments.index.sidebar.not_logged_in", + sign_in: link_to(t("budgets.investments.index.sidebar.sign_in"), new_user_session_path), + sign_up: link_to(t("budgets.investments.index.sidebar.sign_up"), new_user_registration_path)).html_safe %> +
        + <% end %> + <% end %> + + <% if @budget.finished? %> + <%= link_to t("budgets.show.see_results"), + budget_results_path(@budget, heading_id: @budget.headings.first), + class: "button margin-top expanded" %> + <% end %> +
        +
        +
        + +
        +
        + <% if @current_filter == "unfeasible" %> +

        <%= t("budgets.show.unfeasible_title") %>

        + <% elsif @current_filter == "unselected" %> +

        <%= t("budgets.show.unselected_title") %>

        + <% end %> + + + + + + <% @budget.groups.each do |group| %> + + + + <% end %> + +
        <%= t('budgets.show.group') %>
        + <% if group.headings.count == 1 %> + <%= link_to group.name, + budget_investments_path(@budget, + heading_id: group.headings.first.id, + filter: @current_filter), + data: { no_turbolink: true } %> + <% else %> + <%= link_to group.name, + budget_group_path(@budget, group, + filter: @current_filter) %> + <% end %> +
        +
        +
        +
        + +<% if @budget.balloting_or_later? %> + <% unless @current_filter == "unfeasible" %> +
        +
        + + <%= link_to t("budgets.show.unfeasible"), + budget_path(@budget, filter: "unfeasible") %> + +
        +
        + <% end %> + + <% unless @current_filter == "unselected" %> +
        +
        + + <%= link_to t("budgets.show.unselected"), + budget_path(@budget, filter: "unselected") %> + +
        +
        + <% end %> +<% end %> diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb index 2561c9e4d..4960baba8 100644 --- a/app/views/comments/_comment.html.erb +++ b/app/views/comments/_comment.html.erb @@ -1,103 +1,106 @@ -<% cache [locale_and_user_status(comment), comment, commentable_cache_key(comment.commentable), comment.author, (@comment_flags[comment.id] if @comment_flags)] do %> -
        -
        - +<% comment_flags ||= @comment_flags %> +<% cache [locale_and_user_status(comment), comment, commentable_cache_key(comment.commentable), comment.author, (comment_flags[comment.id] if comment_flags)] do %> +
          +
        • <% if comment.hidden? || comment.user.hidden? %> <% if comment.children.size > 0 %> -
          +

          <%= t("comments.comment.deleted") %>

          <% end %> <% else %> <% if comment.as_administrator? %> - <%= image_tag("admin_avatar.png", size: 32, class: "admin-avatar float-left") %> + <%= image_tag("avatar_admin.png", size: 32, class: "admin-avatar float-left") %> <% elsif comment.as_moderator? %> - <%= image_tag("moderator_avatar.png", size: 32, class: "moderator-avatar float-left") %> + <%= image_tag("avatar_moderator.png", size: 32, class: "moderator-avatar float-left") %> <% else %> <% if comment.user.hidden? || comment.user.erased? %> <% elsif comment.user.organization? %> - <%= image_tag("collective_avatar.png", size: 32, class: "avatar float-left") %> + <%= image_tag("avatar_collective.png", size: 32, class: "avatar float-left") %> <% else %> <%= avatar_image(comment.user, seed: comment.user_id, size: 32, class: "float-left") %> <% end %> <% end %> -
          -
          +
          - <% if comment.as_administrator? %> - <%= t("comments.comment.admin") %> #<%= comment.administrator_id%> - <% elsif comment.as_moderator? %> - <%= t("comments.comment.moderator") %> #<%= comment.moderator_id%> + <% if comment.as_administrator? %> + <%= t("comments.comment.admin") %> #<%= comment.administrator_id%> + <% elsif comment.as_moderator? %> + <%= t("comments.comment.moderator") %> #<%= comment.moderator_id%> + <% else %> + + <% if comment.user.hidden? || comment.user.erased? %> + <%= t("comments.comment.user_deleted") %> <% else %> - - <% if comment.user.hidden? || comment.user.erased? %> - <%= t("comments.comment.user_deleted") %> - <% else %> - <%= link_to comment.user.name, user_path(comment.user) %> - <% if comment.user.official? %> -  •  - - <%= comment.user.official_position %> - - <% end %> - <% end %> - <% if comment.user.verified_organization? %> + <%= link_to comment.user.name, user_path(comment.user) %> + <% if comment.user.display_official_position_badge? %>  •  - - <%= t("shared.collective") %> + + <%= comment.user.official_position %> <% end %> - <% if comment.user_id == comment.commentable.author_id %> -  •  - - <%= t("comments.comment.author") %> - - <% end %> - + <% end %> + <% if comment.user.verified_organization? %> +  •  + + <%= t("shared.collective") %> + + <% end %> + <% if comment.user_id == comment.commentable.author_id %> +  •  + + <%= t("comments.comment.author") %> + <% end %> -  •  + <% end %> + +  • <%= l comment.created_at.to_datetime, format: :datetime %> +
          + +
          + <%= simple_format text_with_links(comment.body), {}, sanitize: false %> +
          + +
          +
          + <%= render 'comments/votes', comment: comment %>
          -
          - <%= simple_format text_with_links(comment.body), {}, sanitize: false %> -
          - -
          - - <%= render 'comments/votes', comment: comment %> - - - <% if comment.children.size > 0 %> - <%= link_to "", class: "js-toggle-children", data: {'id': "#{dom_id(comment)}"} do %> - <%= t("comments.comment.responses", count: comment.children.size) %> - <% end %> - <% else %> - <%= t("comments.comment.responses", count: 0) %> + <% if comment.children.size > 0 %> + <%= link_to "#{dom_id(comment)}", class: "js-toggle-children relative", data: {'id': "#{dom_id(comment)}"} do %> + + <%= t("shared.hide") %> + <%= t("comments.comment.responses", count: comment.children.size) %> <% end %> + <% else %> + <%= t("comments.comment.responses", count: 0) %> + <% end %> - <% if user_signed_in? %> -  |  - <%= link_to(comment_link_text(comment), "", - class: "js-add-comment-link", data: {'id': dom_id(comment)}) %> + <% if user_signed_in? %> +  |  + <%= link_to(comment_link_text(comment), "", + class: "js-add-comment-link", data: {'id': dom_id(comment)}) %> - <%= render 'comments/actions', comment: comment %> + <%= render 'comments/actions', comment: comment %> - <%= render 'comments/form', {commentable: comment.commentable, parent_id: comment.id, toggeable: true} %> - <% end %> -
          + <%= render 'comments/form', {commentable: comment.commentable, parent_id: comment.id, toggeable: true} %> + <% end %>
          <% end %> - -
          +
        • +
        • +
            <% child_comments_of(comment).each do |child| %> - <%= render 'comments/comment', comment: child %> +
          • + <%= render 'comments/comment', comment: child %> +
          • <% end %> -
        -
        - +
      + +
    <% end %> diff --git a/app/views/comments/_comment_tree.html.erb b/app/views/comments/_comment_tree.html.erb new file mode 100644 index 000000000..0581809fb --- /dev/null +++ b/app/views/comments/_comment_tree.html.erb @@ -0,0 +1,34 @@ +<% commentable = comment_tree.commentable %> +<% current_order = comment_tree.order %> + +<% cache [locale_and_user_status, current_order, commentable_cache_key(commentable), comment_tree.comments, comment_tree.comment_authors, commentable.comments_count, comment_flags] do %> +
    +
    +
    +

    + <%= t("debates.show.comments_title") %> + (<%= commentable.comments_count %>) +

    + + <%= render 'shared/wide_order_selector', i18n_namespace: "comments" %> + + <% if user_signed_in? %> + <%= render 'comments/form', {commentable: commentable, parent_id: nil, toggeable: false} %> + <% else %> +
    + +
    + <%= t("debates.show.login_to_comment", + signin: link_to(t("votes.signin"), new_user_session_path), + signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %> +
    + <% end %> + + <% comment_tree.root_comments.each do |comment| %> + <%= render 'comments/comment', {comment: comment, comment_flags: comment_flags} %> + <% end %> + <%= paginate comment_tree.root_comments %> +
    +
    +
    +<% end %> diff --git a/app/views/comments/_commentable_tree.html.erb b/app/views/comments/_commentable_tree.html.erb new file mode 100644 index 000000000..6970e64f4 --- /dev/null +++ b/app/views/comments/_commentable_tree.html.erb @@ -0,0 +1,31 @@ +<% cache [locale_and_user_status, @current_order, commentable_cache_key(@investment), @comment_tree.comments, @comment_tree.comment_authors, @investment.comments_count, @comment_flags] do %> +
    +
    +
    +

    + <%= t("debates.show.comments_title") %> + (<%= @investment.comments_count %>) +

    + + <%= render 'shared/wide_order_selector', i18n_namespace: "comments" %> + + <% if user_signed_in? %> + <%= render 'comments/form', {commentable: @investment, parent_id: nil, toggeable: false} %> + <% else %> +
    + +
    + <%= t("debates.show.login_to_comment", + signin: link_to(t("votes.signin"), new_user_session_path), + signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %> +
    + <% end %> + + <% @comment_tree.root_comments.each do |comment| %> + <%= render 'comments/comment', comment: comment %> + <% end %> + <%= paginate @comment_tree.root_comments %> +
    +
    +
    +<% end %> diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb index 601e45a47..e3399c383 100644 --- a/app/views/comments/_form.html.erb +++ b/app/views/comments/_form.html.erb @@ -1,6 +1,6 @@ <% cache [locale_and_user_status, parent_id, commentable_cache_key(commentable)] do %> <% css_id = parent_or_commentable_dom_id(parent_id, commentable) %> -
    > +
    class="comment-form"> <%= form_for Comment.new, remote: true do |f| %> <%= label_tag "comment-body-#{css_id}", t("comments.form.leave_comment") %> <%= f.text_area :body, id: "comment-body-#{css_id}", maxlength: Comment.body_max_length, label: false %> diff --git a/app/views/comments/_votes.html.erb b/app/views/comments/_votes.html.erb index 20fc2d1cf..93af5b2dd 100644 --- a/app/views/comments/_votes.html.erb +++ b/app/views/comments/_votes.html.erb @@ -6,11 +6,17 @@ <% if can?(:vote, comment) %> <%= link_to vote_comment_path(comment, value: 'yes'), - method: "post", remote: true do %> - + method: "post", remote: true, title: t('votes.agree') do %> + + <%= t('votes.agree') %> + <% end %> <% else %> - + <%= link_to new_user_session_path do %> + + <%= t('votes.agree') %> + + <% end %> <% end %> <%= comment.total_likes %> @@ -18,45 +24,63 @@ <% if can?(:vote, comment) %> <%= link_to vote_comment_path(comment, value: 'no'), - method: "post", remote: true do %> - + method: "post", remote: true, title: t('votes.disagree') do %> + + <%= t('votes.disagree') %> + <% end %> <% else %> - + + <%= t('votes.disagree') %> + <% end %> <%= comment.total_dislikes %> <% elsif !user_signed_in? %> -
    +
    <%= t('comments.comment.votes', count: comment.total_votes) %>  |  <% if can?(:vote, comment) %> <%= link_to vote_comment_path(comment, value: 'yes'), - method: "post", remote: true do %> - + method: "post", remote: true, title: t('votes.agree') do %> + + <%= t('votes.agree') %> + <% end %> <% else %> - + <%= link_to new_user_session_path do %> + + <%= t('votes.agree') %> + + <% end %> <% end %> <%= comment.total_likes %> + <% if can?(:vote, comment) %> <%= link_to vote_comment_path(comment, value: 'no'), - method: "post", remote: true do %> - + method: "post", remote: true, title: t('votes.disagree') do %> + + <%= t('votes.disagree') %> + <% end %> <% else %> - + <%= link_to new_user_session_path do %> + + <%= t('votes.disagree') %> + + <% end %> <% end %> <%= comment.total_dislikes %> -
    - + + -
    +
    <%= render 'debates/votes', debate: debate %>
    diff --git a/app/views/debates/_featured_debates.html.erb b/app/views/debates/_featured_debates.html.erb new file mode 100644 index 000000000..577a6da77 --- /dev/null +++ b/app/views/debates/_featured_debates.html.erb @@ -0,0 +1,13 @@ + diff --git a/app/views/debates/_featured_debates_static.html.erb b/app/views/debates/_featured_debates_static.html.erb deleted file mode 100644 index 8f7d0b68d..000000000 --- a/app/views/debates/_featured_debates_static.html.erb +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/app/views/debates/_form.html.erb b/app/views/debates/_form.html.erb index be5c92306..c88045f26 100644 --- a/app/views/debates/_form.html.erb +++ b/app/views/debates/_form.html.erb @@ -1,5 +1,7 @@ <%= form_for(@debate) do |f| %> + <%= render 'shared/errors', resource: @debate %> +
    <%= f.label :title, t("debates.form.debate_title") %> @@ -11,6 +13,8 @@ <%= f.cktext_area :description, maxlength: Debate.description_max_length, ckeditor: { language: I18n.locale }, label: false %>
    + <%= f.invisible_captcha :subtitle %> +
    <%= f.label :tag_list, t("debates.form.tags_label") %>

    <%= t("debates.form.tags_instructions") %>

    @@ -32,10 +36,6 @@ <% end %>
    -
    - <%= f.simple_captcha input_html: { required: false } %> -
    -
    <%= f.submit(class: "button", value: t("debates.#{action_name}.form.submit_button")) %>
    diff --git a/app/views/debates/_votes.html.erb b/app/views/debates/_votes.html.erb index a4fd722ee..9bc3ca555 100644 --- a/app/views/debates/_votes.html.erb +++ b/app/views/debates/_votes.html.erb @@ -1,19 +1,41 @@ <% voted_classes = css_classes_for_vote(@debate_votes, debate) %>
    - <%= link_to vote_debate_path(debate, value: 'yes'), - class: "like #{voted_classes[:in_favor]}", title: t('votes.agree'), method: "post", remote: true do %> - - <%= votes_percentage('likes', debate) %> + <% if user_signed_in? %> + <%= link_to vote_debate_path(debate, value: 'yes'), + class: "like #{voted_classes[:in_favor]}", title: t('votes.agree'), method: "post", remote: true do %> + + <%= t('votes.agree') %> + + <%= votes_percentage('likes', debate) %> + <% end %> + <% else %> + <% end %>
    - <%= link_to vote_debate_path(debate, value: 'no'), class: "unlike #{voted_classes[:against]}", title: t('votes.disagree'), method: "post", remote: true do %> - - <%= votes_percentage('dislikes', debate) %> + <% if user_signed_in? %> + <%= link_to vote_debate_path(debate, value: 'no'), class: "unlike #{voted_classes[:against]}", title: t('votes.disagree'), method: "post", remote: true do %> + + <%= t('votes.disagree') %> + + <%= votes_percentage('dislikes', debate) %> + <% end %> + <% else %> +
    + + <%= t('votes.disagree') %> + + <%= votes_percentage('dislikes', debate) %> +
    <% end %>
    @@ -22,23 +44,25 @@ <% if user_signed_in? && current_user.organization? %> - + <% elsif user_signed_in? && !debate.votable_by?(current_user)%> - + <% elsif !user_signed_in? %> - +
    + +
    <% end %>
    diff --git a/app/views/debates/edit.html.erb b/app/views/debates/edit.html.erb index 34a7f70df..13a2aa625 100644 --- a/app/views/debates/edit.html.erb +++ b/app/views/debates/edit.html.erb @@ -1,10 +1,7 @@ -
    +
    - <%= link_to debates_path, class: "back" do %> - - <%= t("debates.edit.back_link") %> - <% end %> + <%= render "shared/back_link" %>
    <%= link_to t("debates.edit.show_link"), @debate %> diff --git a/app/views/debates/index.html.erb b/app/views/debates/index.html.erb index 6488b5844..b7796f0ba 100644 --- a/app/views/debates/index.html.erb +++ b/app/views/debates/index.html.erb @@ -4,9 +4,14 @@ search_path: debates_path(page: 1), i18n_namespace: "debates.index.search_form" %> <% end %> +<% content_for :canonical do %> + <%= render "shared/canonical", href: debates_url %> +<% end %>
    -
    +

    <%= t("shared.outline.debates") %>

    + +
    @@ -25,8 +30,16 @@ <% end %>
    - <% unless @tag_filter || @search_terms %> - <%= render "featured_debates_static" %> + <% if has_banners? %> + <%= render "shared/banner" %> + <% end %> + + <% if @proposal_successful_exists %> + <%= render "proposals/successful_banner" %> + <% end %> + + <% unless @tag_filter || @search_terms || !has_featured? || @proposal_successful_exists %> + <%= render "featured_debates" %> <% end %> <%= render "shared/advanced_search", search_path: debates_path(page: 1) %> diff --git a/app/views/debates/new.html.erb b/app/views/debates/new.html.erb index 34821a4fd..ccd11bec2 100644 --- a/app/views/debates/new.html.erb +++ b/app/views/debates/new.html.erb @@ -1,15 +1,13 @@ -
    +
    - <%= link_to debates_path, class: "back" do %> - - <%= t("debates.new.back_link") %> - <% end %> + <%= render "shared/back_link" %> +

    <%= t("debates.new.start_new") %>

    <%= t("debates.new.info", info_link: link_to(t("debates.new.info_link"), new_proposal_path )).html_safe %> - <%= link_to "/more_information", title: t('shared.target_blank_html'), target: "_blank" do %> + <%= link_to more_info_path, title: t('shared.target_blank_html'), target: "_blank" do %> <%= t("debates.new.more_info") %> <% end %>
    diff --git a/app/views/debates/show.html.erb b/app/views/debates/show.html.erb index b0aa58ce4..7ca7b0e35 100644 --- a/app/views/debates/show.html.erb +++ b/app/views/debates/show.html.erb @@ -1,12 +1,14 @@ <% provide :title do %><%= @debate.title %><% end %> +<% content_for :canonical do %> + <%= render "shared/canonical", href: debate_url(@debate) %> +<% end %> + <% cache [locale_and_user_status(@debate), @debate, @debate.author, Flag.flagged?(current_user, @debate), @debate_votes] do %> -
    +
    - <%= link_to :back, class: "back" do %> - - <%= t("debates.show.back_link") %> - <% end %> + <%= render "shared/back_link" %> + <% if current_user && @debate.editable_by?(current_user) %> <%= link_to edit_debate_path(@debate), class: 'edit-debate button success small float-right' do %> @@ -46,24 +48,23 @@
    -
    +
    <% end %> <%= render "comments" %> diff --git a/app/views/devise/_omniauth_form.html.erb b/app/views/devise/_omniauth_form.html.erb index 52ca029be..f2c567259 100644 --- a/app/views/devise/_omniauth_form.html.erb +++ b/app/views/devise/_omniauth_form.html.erb @@ -1,35 +1,78 @@ <% if feature?(:twitter_login) || feature?(:facebook_login) || feature?(:google_login) %> <% if current_page?(new_user_session_path) %> +
    +
    +
    + <%= t("omniauth.info_sign_in") %> +
    +
    - <% if feature? :twitter_login %> - <%= link_to t("omniauth.twitter.sign_in"), user_omniauth_authorize_path(:twitter), class: "button-twitter button expanded" %> - <% end %> + <% if feature? :twitter_login %> +
    + <%= link_to t("omniauth.twitter.name"), user_omniauth_authorize_path(:twitter), + title: t("omniauth.twitter.sign_in"), + class: "button-twitter button expanded" %> +
    + <% end %> - <% if feature? :facebook_login %> - <%= link_to t("omniauth.facebook.sign_in"), user_omniauth_authorize_path(:facebook), class: "button-facebook button expanded" %> - <% end %> + <% if feature? :facebook_login %> +
    + <%= link_to t("omniauth.facebook.name"), user_omniauth_authorize_path(:facebook), + title: t("omniauth.facebook.sign_in"), + class: "button-facebook button expanded" %> +
    + <% end %> - <% if feature? :google_login %> - <%= link_to t("omniauth.google_oauth2.sign_in"), user_omniauth_authorize_path(:google_oauth2), class: "button-google button expanded" %> - <% end %> + <% if feature? :google_login %> +
    + <%= link_to t("omniauth.google_oauth2.name"), user_omniauth_authorize_path(:google_oauth2), + title: t("omniauth.google_oauth2.sign_in"), + class: "button-google button expanded" %> +
    + <% end %> + +
    + <%= t("omniauth.or_fill") %> +
    +
    -
    <% elsif current_page?(new_user_registration_path) %> +
    +
    +
    + <%= t("omniauth.info_sign_up") %> +
    +
    - <% if feature? :twitter_login %> - <%= link_to t("omniauth.twitter.sign_up"), user_omniauth_authorize_path(:twitter), class: "button-twitter button expanded" %> - <% end %> + <% if feature? :twitter_login %> +
    + <%= link_to t("omniauth.twitter.name"), user_omniauth_authorize_path(:twitter), + title: t("omniauth.twitter.sign_up"), + class: "button-twitter button expanded" %> +
    + <% end %> - <% if feature? :facebook_login %> - <%= link_to t("omniauth.facebook.sign_up"), user_omniauth_authorize_path(:facebook), class: "button-facebook button expanded" %> - <% end %> + <% if feature? :facebook_login %> +
    + <%= link_to t("omniauth.facebook.name"), user_omniauth_authorize_path(:facebook), + title: t("omniauth.facebook.sign_up"), + class: "button-facebook button expanded" %> +
    + <% end %> - <% if feature? :google_login %> - <%= link_to t("omniauth.google_oauth2.sign_up"), user_omniauth_authorize_path(:google_oauth2), class: "button-google button expanded" %> - <% end %> + <% if feature? :google_login %> +
    + <%= link_to t("omniauth.google_oauth2.name"), user_omniauth_authorize_path(:google_oauth2), + title: t("omniauth.google_oauth2.sign_up"), + class: "button-google button expanded" %> +
    + <% end %> -
    +
    + <%= t("omniauth.or_fill") %> +
    +
    <% end %> <% end %> diff --git a/app/views/devise/confirmations/new.html.erb b/app/views/devise/confirmations/new.html.erb index 88016f5ce..51f6cf1eb 100644 --- a/app/views/devise/confirmations/new.html.erb +++ b/app/views/devise/confirmations/new.html.erb @@ -15,8 +15,11 @@ <%= hidden_field_tag :confirmation_token,@confirmation_token %> - <%= f.submit(t("devise_views.confirmations.new.submit"), class: "button expanded") %> +
    + <%= f.submit(t("devise_views.confirmations.new.submit"), class: "button expanded") %> +
    +
    <% end %> <%= render "devise/shared/links" %> diff --git a/app/views/devise/menu/_login_items.html.erb b/app/views/devise/menu/_login_items.html.erb index 55665708a..5d493131d 100644 --- a/app/views/devise/menu/_login_items.html.erb +++ b/app/views/devise/menu/_login_items.html.erb @@ -1,6 +1,7 @@ <% if user_signed_in? %>
  • - <%= link_to notifications_path, class: "notifications", accesskey: "n" do %> + <%= link_to notifications_path, rel: "nofollow", class: "notifications" do %> + <%= t("layouts.header.notifications") %> <% if current_user.notifications_count > 0 %>
  • - <%= link_to(t("layouts.header.my_activity_link"), user_path(current_user), accesskey: "a") %> + <%= link_to t("layouts.header.my_activity_link"), + user_path(current_user), rel: "nofollow" %>
  • - <%= link_to(t("layouts.header.my_account_link"), account_path, accesskey: "m") %> + <%= link_to t("layouts.header.my_account_link"), + account_path, rel: "nofollow" %>
  • - <%= link_to(t("devise_views.menu.login_items.logout"), destroy_user_session_path, method: :delete) %> + <%= link_to t("devise_views.menu.login_items.logout"), + destroy_user_session_path, rel: "nofollow", method: :delete %>
  • <% else %>
  • - <%= link_to(t("devise_views.menu.login_items.login"), new_user_session_path) %> + <%= link_to t("devise_views.menu.login_items.login"), + new_user_session_path, rel: "nofollow" %>
  • - <%= link_to(t("devise_views.menu.login_items.signup"), new_user_registration_path, class: "button") %> + <%= link_to t("devise_views.menu.login_items.signup"), + new_user_registration_path, rel: "nofollow", class: "button" %>
  • <% end %> diff --git a/app/views/devise/password_expired/show.html.erb b/app/views/devise/password_expired/show.html.erb new file mode 100644 index 000000000..33dc82f71 --- /dev/null +++ b/app/views/devise/password_expired/show.html.erb @@ -0,0 +1,13 @@ +

    <%= t("devise.password_expired.expire_password") %>

    + +<%= form_for(resource, :as => resource_name, :url => [resource_name, :password_expired], :html => { :method => :put }) do |f| %> + + <%= f.password_field :current_password %>

    + + <%= f.label t("devise.password_expired.new_password") %> + <%= f.password_field :password, label: false %>

    + + <%= f.password_field :password_confirmation %>

    + +

    <%= f.submit t("devise.password_expired.change_password") %>

    +<% end %> \ No newline at end of file diff --git a/app/views/devise/passwords/new.html.erb b/app/views/devise/passwords/new.html.erb index 59608c148..b06c9ff25 100644 --- a/app/views/devise/passwords/new.html.erb +++ b/app/views/devise/passwords/new.html.erb @@ -7,8 +7,11 @@
    <%= f.email_field :email, autofocus: true, placeholder: t("devise_views.passwords.new.email_label") %> - <%= f.submit t("devise_views.passwords.new.send_submit"), class: "button expanded" %> +
    + <%= f.submit t("devise_views.passwords.new.send_submit"), class: "button expanded" %> +
    +
    <% end %> <%= render "devise/shared/links" %> diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 2abd9aebd..910135853 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -10,30 +10,28 @@ <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
    -
    - <%= f.email_field :email, autofocus: true, placeholder: t("devise_views.sessions.new.email_label"), tabindex: "1" %> +
    + <%= f.email_field :email, autofocus: true, placeholder: t("devise_views.sessions.new.email_label") %>
    -
    - <%= link_to t("devise_views.shared.links.new_password"), new_password_path(resource_name), class: "float-right", tabindex: "3" %> - <%= f.password_field :password, autocomplete: "off", placeholder: t("devise_views.sessions.new.password_label"), tabindex: "2" %> +
    + <%= f.password_field :password, autocomplete: "off", placeholder: t("devise_views.sessions.new.password_label") %> + <%= link_to t("devise_views.shared.links.new_password"), new_password_path(resource_name), class: "float-right" %>
    <% if devise_mapping.rememberable? -%> -
    +
    <%= f.label :remember_me do %> - <%= f.check_box :remember_me, title: t('devise_views.sessions.new.remember_me'), label: false, tabindex: "4" %> + <%= f.check_box :remember_me, title: t('devise_views.sessions.new.remember_me'), label: false %> <%= t("devise_views.sessions.new.remember_me") %> <% end %>
    <% end -%> -
    +
    <%= f.submit(t("devise_views.sessions.new.submit"), class: "button expanded") %>
    <% end %> <%= render "devise/shared/links" %> - - diff --git a/app/views/direct_messages/new.html.erb b/app/views/direct_messages/new.html.erb new file mode 100644 index 000000000..3413f26f0 --- /dev/null +++ b/app/views/direct_messages/new.html.erb @@ -0,0 +1,47 @@ +
    +
    + <%= render 'shared/back_link' %> + +

    + <%= t("users.direct_messages.new.title", receiver: @receiver.name) %> +

    + + <% if not current_user %> +
    +

    + <%= t("users.direct_messages.new.authenticate", + signin: link_to(t("users.direct_messages.new.signin"), new_user_session_path), + signup: link_to(t("users.direct_messages.new.signup"), new_user_registration_path)).html_safe %> +

    +
    + <% elsif not @receiver.email_on_direct_message? %> +
    +

    + <%= t("users.direct_messages.new.direct_messages_bloqued") %> +

    +
    + <% elsif can? :create, @direct_message %> + <%= form_for [@receiver, @direct_message] do |f| %> + <%= render "shared/errors", resource: @direct_message %> + + <%= f.label :title, t("users.direct_messages.new.title_label") %> + <%= f.text_field :title, label: false %> + + <%= f.label :body, t("users.direct_messages.new.body_label") %> + <%= f.text_area :body, label: false, rows: "3" %> + +
    + <%= f.submit t("users.direct_messages.new.submit_button"), class: "button expanded" %> +
    + <% end %> + <% else %> +
    +

    + <%= t("users.direct_messages.new.verified_only", + verify_account: link_to( t("users.direct_messages.new.verify_account"), + verification_path )).html_safe %> +

    +
    + <% end %> +
    +
    diff --git a/app/views/direct_messages/show.html.erb b/app/views/direct_messages/show.html.erb new file mode 100644 index 000000000..8d0f62d84 --- /dev/null +++ b/app/views/direct_messages/show.html.erb @@ -0,0 +1,18 @@ +
    +
    + <%= link_to user_path(@direct_message.receiver), class: "back" do %> + + <%= t("shared.back") %> + <% end %> + +
    + +
    + <%= t("users.direct_messages.show.receiver", + receiver: @direct_message.receiver.name) %> +
    + +

    <%= @direct_message.title %>

    +

    <%= simple_format text_with_links(@direct_message.body), {}, sanitize: false %>

    +
    +
    diff --git a/app/views/kaminari/_paginator.html.erb b/app/views/kaminari/_paginator.html.erb index f129b1b9d..de9e89c93 100644 --- a/app/views/kaminari/_paginator.html.erb +++ b/app/views/kaminari/_paginator.html.erb @@ -1,21 +1,19 @@ <%= paginator.render do -%>
    - + <%= next_page_tag unless current_page.last? %> + <%= last_page_tag unless current_page.last? %> +
<% end -%> diff --git a/app/views/layouts/_admin_header.html.erb b/app/views/layouts/_admin_header.html.erb index 9d4943ca2..8597b41de 100644 --- a/app/views/layouts/_admin_header.html.erb +++ b/app/views/layouts/_admin_header.html.erb @@ -1,14 +1,16 @@
- + -
+
- <%= link_to Setting['org_name'], root_path, class: "logo show-for-small-only" %> + <%= link_to setting['org_name'], namespaced_root_path, class: "logo show-for-small-only" %> @@ -17,11 +19,13 @@
- <%= link_to root_path, class: "hide-for-small-only" do %> - <%= image_tag('header_logo_madrid.png', class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> - <%= Setting['org_name'] %> -  | <%= t("admin.dashboard.index.title") %> - <% end %> +

+ <%= link_to namespaced_root_path, class: "hide-for-small-only" do %> + <%= image_tag(image_path_for('logo_header.png'), class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> + <%= setting['org_name'] %> +  | <%= namespaced_header_title %> + <% end %> +

@@ -34,4 +38,4 @@
-
\ No newline at end of file + diff --git a/app/views/layouts/_flash.html.erb b/app/views/layouts/_flash.html.erb new file mode 100644 index 000000000..9f55b449a --- /dev/null +++ b/app/views/layouts/_flash.html.erb @@ -0,0 +1,12 @@ +<% flash.each do |flash_key, flash_message| %> +
+
+ +
+ <%= flash_message %> +
+
+
+<% end %> diff --git a/app/views/layouts/_footer.html.erb b/app/views/layouts/_footer.html.erb index da6074706..ed6027618 100644 --- a/app/views/layouts/_footer.html.erb +++ b/app/views/layouts/_footer.html.erb @@ -1,5 +1,5 @@
-
+

<%= link_to t("layouts.header.open_gov", open: "#{t('layouts.header.open')}").html_safe %> @@ -7,33 +7,27 @@

<%= t("layouts.footer.description", - open_source: link_to(t("layouts.footer.open_source"), t("layouts.footer.open_source_url"), target: "blank"), - consul: link_to(t("layouts.footer.consul"), t("layouts.footer.consul_url"), target: "blank")).html_safe + open_source: link_to(t("layouts.footer.open_source"), t("layouts.footer.open_source_url"), target: "blank", rel: "nofollow"), + consul: link_to(t("layouts.footer.consul"), t("layouts.footer.consul_url"), target: "blank", rel: "nofollow")).html_safe %> <%= t("layouts.footer.contact_us") %> - <%= link_to t("layouts.footer.faq"), page_path('faq') %> + <%= link_to t("layouts.footer.faq"), faq_path %>

@@ -41,13 +35,12 @@
-

- <%= t("layouts.footer.copyright", year: Time.now.year) %> |  - <%= link_to t("layouts.footer.more_info"), page_path('more_information') %> |  - <%= link_to t("layouts.footer.privacy"), page_path('privacy') %> |  - <%= link_to t("layouts.footer.conditions"), page_path('conditions') %> |  - <%= link_to t("layouts.footer.accessibility"), page_path('accessibility') %> -

+ <%= t("layouts.footer.copyright", year: Time.current.year) %> | +
    +
  • <%= link_to t("layouts.footer.privacy"), page_path('privacy') %> |
  • +
  • <%= link_to t("layouts.footer.conditions"), page_path('conditions') %> |
  • +
  • <%= link_to t("layouts.footer.accessibility"), page_path('accessibility') %>
  • +
+ +
+ <%= raw content_block("footer", I18n.locale) %> +
diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index d791fe7b9..a3162bb8c 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -1,11 +1,18 @@
- <%= render "shared/top_links" %> +
- <%= link_to Setting['org_name'], root_path, class: "logo show-for-small-only" %> + <%= link_to setting['org_name'], root_path, class: "logo show-for-small-only" %> @@ -14,14 +21,9 @@
- <%= link_to root_path, class: "hide-for-small-only", accesskey: "/" do %> - <%= image_tag('header_logo_madrid.png', class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> - <% if opendata_page? %> - <%= t("layouts.header.open_gov", open: "#{t('layouts.header.open')}") %> | - <%= t("layouts.header.open_data") %> - <% else %> - <%= setting['org_name'] %> - <% end %> + <%= link_to root_path, class: "hide-for-small-only", accesskey: "0" do %> + <%= image_tag(image_path_for('logo_header.png'), class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> + <%= setting['org_name'] %> <% end %>
@@ -34,8 +36,8 @@
@@ -44,13 +46,13 @@
-