Den is the private cloud vault for your reminders, calendars and to-dos.

feat(init): initialize repo with codez (#1)

* feat: init aspire app

* chore: drop kube

* feat(ci): github actions init

* feat: jwt refresh flow + token updates

* fix: logic error

* feat: jwk init

* feat: init security subsystem

* feat: making jwks work finally

* feat: init react frontend

* feat: integrate vite with apphost

* feat: begin integrating with auth svc

* feat: scaffolding the features out

* fix: allow dynamic secrets id

* feat: fuck jwks back off, screw that, im overcomplicating things.

* feat: make login work

* fix: clean up

* feat: login -> home redirect

* fix: resolve account ui issues, add logout button

* feat: budgeting init

* feat: codeowners

authored by Hayden and committed by GitHub ab57a3d0 589920d2

Changed files
+12988
.aspire
.github
src
Den.Api
Den.AppHost
Den.Application
Den.Client.Web
Den.Domain
Den.Infrastructure
Den.MigrationService
Den.ServiceDefaults
+3
.aspire/settings.json
··· 1 + { 2 + "appHostPath": "../src/Den.AppHost/Den.AppHost.csproj" 3 + }
+382
.editorconfig
··· 1 + root = true 2 + 3 + # All files 4 + [*] 5 + end_of_line = lf 6 + indent_style = space 7 + 8 + # Xml files 9 + [*.xml] 10 + indent_size = 2 11 + 12 + # C# files 13 + [*.cs] 14 + 15 + #### Core EditorConfig Options #### 16 + 17 + # Indentation and spacing 18 + indent_size = 4 19 + tab_width = 4 20 + 21 + # New line preferences 22 + insert_final_newline = false 23 + 24 + #### .NET Coding Conventions #### 25 + [*.{cs,vb}] 26 + 27 + # Logging 28 + dotnet_diagnostic.CA1873.severity = none 29 + 30 + # Organize usings 31 + dotnet_separate_import_directive_groups = true 32 + dotnet_sort_system_directives_first = true 33 + file_header_template = unset 34 + 35 + # this. and Me. preferences 36 + dotnet_style_qualification_for_event = false:silent 37 + dotnet_style_qualification_for_field = false:silent 38 + dotnet_style_qualification_for_method = false:silent 39 + dotnet_style_qualification_for_property = false:silent 40 + 41 + # Language keywords vs BCL types preferences 42 + dotnet_style_predefined_type_for_locals_parameters_members = true:silent 43 + dotnet_style_predefined_type_for_member_access = true:silent 44 + 45 + # Parentheses preferences 46 + dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 47 + dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 48 + dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 49 + dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 50 + 51 + # Modifier preferences 52 + dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 53 + 54 + # Expression-level preferences 55 + dotnet_style_coalesce_expression = true:suggestion 56 + dotnet_style_collection_initializer = true:suggestion 57 + dotnet_style_explicit_tuple_names = true:suggestion 58 + dotnet_style_namespace_match_folder = true:suggestion 59 + dotnet_style_null_propagation = true:suggestion 60 + dotnet_style_object_initializer = true:suggestion 61 + dotnet_style_operator_placement_when_wrapping = beginning_of_line 62 + dotnet_style_prefer_auto_properties = true:suggestion 63 + dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion 64 + dotnet_style_prefer_compound_assignment = true:suggestion 65 + dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion 66 + dotnet_style_prefer_conditional_expression_over_return = true:suggestion 67 + dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion 68 + dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 69 + dotnet_style_prefer_inferred_tuple_names = true:suggestion 70 + dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 71 + dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 72 + dotnet_style_prefer_simplified_interpolation = true:suggestion 73 + 74 + # Field preferences 75 + dotnet_style_readonly_field = true:warning 76 + 77 + # Parameter preferences 78 + dotnet_code_quality_unused_parameters = all:suggestion 79 + 80 + # Suppression preferences 81 + dotnet_remove_unnecessary_suppression_exclusions = none 82 + 83 + #### C# Coding Conventions #### 84 + [*.cs] 85 + 86 + # var preferences 87 + csharp_style_var_elsewhere = false:silent 88 + csharp_style_var_for_built_in_types = false:silent 89 + csharp_style_var_when_type_is_apparent = false:silent 90 + 91 + # Expression-bodied members 92 + csharp_style_expression_bodied_accessors = true:silent 93 + csharp_style_expression_bodied_constructors = false:silent 94 + csharp_style_expression_bodied_indexers = true:silent 95 + csharp_style_expression_bodied_lambdas = true:suggestion 96 + csharp_style_expression_bodied_local_functions = false:silent 97 + csharp_style_expression_bodied_methods = false:silent 98 + csharp_style_expression_bodied_operators = false:silent 99 + csharp_style_expression_bodied_properties = true:silent 100 + 101 + # Pattern matching preferences 102 + csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 103 + csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 104 + csharp_style_prefer_extended_property_pattern = true:suggestion 105 + csharp_style_prefer_not_pattern = true:suggestion 106 + csharp_style_prefer_pattern_matching = true:silent 107 + csharp_style_prefer_switch_expression = true:suggestion 108 + 109 + # Null-checking preferences 110 + csharp_style_conditional_delegate_call = true:suggestion 111 + 112 + # Modifier preferences 113 + csharp_prefer_static_anonymous_function = true:suggestion 114 + csharp_prefer_static_local_function = true:warning 115 + csharp_preferred_modifier_order = public,private,protected,internal,file,const,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion 116 + csharp_style_prefer_readonly_struct = true:suggestion 117 + csharp_style_prefer_readonly_struct_member = true:suggestion 118 + 119 + # Code-block preferences 120 + csharp_prefer_braces = true:silent 121 + csharp_prefer_simple_using_statement = true:suggestion 122 + csharp_style_namespace_declarations = file_scoped:suggestion 123 + csharp_style_prefer_method_group_conversion = true:silent 124 + csharp_style_prefer_primary_constructors = true:suggestion 125 + csharp_style_prefer_top_level_statements = true:silent 126 + 127 + # Expression-level preferences 128 + csharp_prefer_simple_default_expression = true:suggestion 129 + csharp_style_deconstructed_variable_declaration = true:suggestion 130 + csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 131 + csharp_style_inlined_variable_declaration = true:suggestion 132 + csharp_style_prefer_index_operator = true:suggestion 133 + csharp_style_prefer_local_over_anonymous_function = true:suggestion 134 + csharp_style_prefer_null_check_over_type_check = true:suggestion 135 + csharp_style_prefer_range_operator = true:suggestion 136 + csharp_style_prefer_tuple_swap = true:suggestion 137 + csharp_style_prefer_utf8_string_literals = true:suggestion 138 + csharp_style_throw_expression = true:suggestion 139 + csharp_style_unused_value_assignment_preference = discard_variable:suggestion 140 + csharp_style_unused_value_expression_statement_preference = discard_variable:silent 141 + 142 + # 'using' directive preferences 143 + csharp_using_directive_placement = outside_namespace:silent 144 + 145 + #### C# Formatting Rules #### 146 + 147 + # New line preferences 148 + csharp_new_line_before_catch = true 149 + csharp_new_line_before_else = true 150 + csharp_new_line_before_finally = true 151 + csharp_new_line_before_members_in_anonymous_types = true 152 + csharp_new_line_before_members_in_object_initializers = true 153 + csharp_new_line_before_open_brace = all 154 + csharp_new_line_between_query_expression_clauses = true 155 + 156 + # Indentation preferences 157 + csharp_indent_block_contents = true 158 + csharp_indent_braces = false 159 + csharp_indent_case_contents = true 160 + csharp_indent_case_contents_when_block = true 161 + csharp_indent_labels = one_less_than_current 162 + csharp_indent_switch_labels = true 163 + 164 + # Space preferences 165 + csharp_space_after_cast = false 166 + csharp_space_after_colon_in_inheritance_clause = true 167 + csharp_space_after_comma = true 168 + csharp_space_after_dot = false 169 + csharp_space_after_keywords_in_control_flow_statements = true 170 + csharp_space_after_semicolon_in_for_statement = true 171 + csharp_space_around_binary_operators = before_and_after 172 + csharp_space_around_declaration_statements = false 173 + csharp_space_before_colon_in_inheritance_clause = true 174 + csharp_space_before_comma = false 175 + csharp_space_before_dot = false 176 + csharp_space_before_open_square_brackets = false 177 + csharp_space_before_semicolon_in_for_statement = false 178 + csharp_space_between_empty_square_brackets = false 179 + csharp_space_between_method_call_empty_parameter_list_parentheses = false 180 + csharp_space_between_method_call_name_and_opening_parenthesis = false 181 + csharp_space_between_method_call_parameter_list_parentheses = false 182 + csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 183 + csharp_space_between_method_declaration_name_and_open_parenthesis = false 184 + csharp_space_between_method_declaration_parameter_list_parentheses = false 185 + csharp_space_between_parentheses = false 186 + csharp_space_between_square_brackets = false 187 + 188 + # Wrapping preferences 189 + csharp_preserve_single_line_blocks = true 190 + csharp_preserve_single_line_statements = true 191 + 192 + #### Naming styles #### 193 + [*.{cs,vb}] 194 + 195 + # Naming rules 196 + 197 + dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion 198 + dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces 199 + dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase 200 + 201 + dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion 202 + dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces 203 + dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase 204 + 205 + dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion 206 + dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters 207 + dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase 208 + 209 + dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion 210 + dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods 211 + dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase 212 + 213 + dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion 214 + dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties 215 + dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase 216 + 217 + dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion 218 + dotnet_naming_rule.events_should_be_pascalcase.symbols = events 219 + dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase 220 + 221 + dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion 222 + dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables 223 + dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase 224 + 225 + dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion 226 + dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants 227 + dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase 228 + 229 + dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion 230 + dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters 231 + dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase 232 + 233 + dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion 234 + dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields 235 + dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase 236 + 237 + dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion 238 + dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields 239 + dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase 240 + 241 + dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion 242 + dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields 243 + dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase 244 + 245 + dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion 246 + dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields 247 + dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase 248 + 249 + dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion 250 + dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields 251 + dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase 252 + 253 + dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion 254 + dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields 255 + dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase 256 + 257 + dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion 258 + dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields 259 + dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase 260 + 261 + dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion 262 + dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums 263 + dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase 264 + 265 + dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion 266 + dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions 267 + dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase 268 + 269 + dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion 270 + dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members 271 + dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase 272 + 273 + # Symbol specifications 274 + 275 + dotnet_naming_symbols.interfaces.applicable_kinds = interface 276 + dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 277 + dotnet_naming_symbols.interfaces.required_modifiers = 278 + 279 + dotnet_naming_symbols.enums.applicable_kinds = enum 280 + dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 281 + dotnet_naming_symbols.enums.required_modifiers = 282 + 283 + dotnet_naming_symbols.events.applicable_kinds = event 284 + dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 285 + dotnet_naming_symbols.events.required_modifiers = 286 + 287 + dotnet_naming_symbols.methods.applicable_kinds = method 288 + dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 289 + dotnet_naming_symbols.methods.required_modifiers = 290 + 291 + dotnet_naming_symbols.properties.applicable_kinds = property 292 + dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 293 + dotnet_naming_symbols.properties.required_modifiers = 294 + 295 + dotnet_naming_symbols.public_fields.applicable_kinds = field 296 + dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal 297 + dotnet_naming_symbols.public_fields.required_modifiers = 298 + 299 + dotnet_naming_symbols.private_fields.applicable_kinds = field 300 + dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected 301 + dotnet_naming_symbols.private_fields.required_modifiers = 302 + 303 + dotnet_naming_symbols.private_static_fields.applicable_kinds = field 304 + dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected 305 + dotnet_naming_symbols.private_static_fields.required_modifiers = static 306 + 307 + dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum 308 + dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 309 + dotnet_naming_symbols.types_and_namespaces.required_modifiers = 310 + 311 + dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 312 + dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 313 + dotnet_naming_symbols.non_field_members.required_modifiers = 314 + 315 + dotnet_naming_symbols.type_parameters.applicable_kinds = namespace 316 + dotnet_naming_symbols.type_parameters.applicable_accessibilities = * 317 + dotnet_naming_symbols.type_parameters.required_modifiers = 318 + 319 + dotnet_naming_symbols.private_constant_fields.applicable_kinds = field 320 + dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected 321 + dotnet_naming_symbols.private_constant_fields.required_modifiers = const 322 + 323 + dotnet_naming_symbols.local_variables.applicable_kinds = local 324 + dotnet_naming_symbols.local_variables.applicable_accessibilities = local 325 + dotnet_naming_symbols.local_variables.required_modifiers = 326 + 327 + dotnet_naming_symbols.local_constants.applicable_kinds = local 328 + dotnet_naming_symbols.local_constants.applicable_accessibilities = local 329 + dotnet_naming_symbols.local_constants.required_modifiers = const 330 + 331 + dotnet_naming_symbols.parameters.applicable_kinds = parameter 332 + dotnet_naming_symbols.parameters.applicable_accessibilities = * 333 + dotnet_naming_symbols.parameters.required_modifiers = 334 + 335 + dotnet_naming_symbols.public_constant_fields.applicable_kinds = field 336 + dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal 337 + dotnet_naming_symbols.public_constant_fields.required_modifiers = const 338 + 339 + dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field 340 + dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal 341 + dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static 342 + 343 + dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field 344 + dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected 345 + dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static 346 + 347 + dotnet_naming_symbols.local_functions.applicable_kinds = local_function 348 + dotnet_naming_symbols.local_functions.applicable_accessibilities = * 349 + dotnet_naming_symbols.local_functions.required_modifiers = 350 + 351 + # Naming styles 352 + 353 + dotnet_naming_style.pascalcase.required_prefix = 354 + dotnet_naming_style.pascalcase.required_suffix = 355 + dotnet_naming_style.pascalcase.word_separator = 356 + dotnet_naming_style.pascalcase.capitalization = pascal_case 357 + 358 + dotnet_naming_style.ipascalcase.required_prefix = I 359 + dotnet_naming_style.ipascalcase.required_suffix = 360 + dotnet_naming_style.ipascalcase.word_separator = 361 + dotnet_naming_style.ipascalcase.capitalization = pascal_case 362 + 363 + dotnet_naming_style.tpascalcase.required_prefix = T 364 + dotnet_naming_style.tpascalcase.required_suffix = 365 + dotnet_naming_style.tpascalcase.word_separator = 366 + dotnet_naming_style.tpascalcase.capitalization = pascal_case 367 + 368 + dotnet_naming_style._camelcase.required_prefix = _ 369 + dotnet_naming_style._camelcase.required_suffix = 370 + dotnet_naming_style._camelcase.word_separator = 371 + dotnet_naming_style._camelcase.capitalization = camel_case 372 + 373 + dotnet_naming_style.camelcase.required_prefix = 374 + dotnet_naming_style.camelcase.required_suffix = 375 + dotnet_naming_style.camelcase.word_separator = 376 + dotnet_naming_style.camelcase.capitalization = camel_case 377 + 378 + dotnet_naming_style.s_camelcase.required_prefix = s_ 379 + dotnet_naming_style.s_camelcase.required_suffix = 380 + dotnet_naming_style.s_camelcase.word_separator = 381 + dotnet_naming_style.s_camelcase.capitalization = camel_case 382 +
+1
.github/CODEOWNERS
··· 1 + * @roostmoe/sig-den
+26
.github/workflows/dotnet.yaml
··· 1 + --- 2 + name: .NET CI 3 + 4 + on: 5 + push: 6 + branches: [main] 7 + paths-ignore: 8 + - '**.md' 9 + - 'src/Den.Client/**' 10 + - 'tests/Den.Client.UnitTests/**' 11 + 12 + pull_request: 13 + paths-ignore: 14 + - '**.md' 15 + - 'src/Den.Client/**' 16 + - 'tests/Den.Client.UnitTests/**' 17 + 18 + jobs: 19 + build: 20 + name: Build 21 + runs-on: ubuntu-latest 22 + steps: 23 + - uses: actions/checkout@v5 24 + - uses: actions/setup-dotnet@v5 25 + - run: dotnet build den.slnx 26 + - run: dotnet test den.slnx
+487
.gitignore
··· 1 + ## Ignore Visual Studio temporary files, build results, and 2 + ## files generated by popular Visual Studio add-ons. 3 + ## 4 + ## Get latest from `dotnet new gitignore` 5 + 6 + # dotenv files 7 + .env 8 + 9 + # User-specific files 10 + *.rsuser 11 + *.suo 12 + *.user 13 + *.userosscache 14 + *.sln.docstates 15 + 16 + # User-specific files (MonoDevelop/Xamarin Studio) 17 + *.userprefs 18 + 19 + # Mono auto generated files 20 + mono_crash.* 21 + 22 + # Build results 23 + [Dd]ebug/ 24 + [Dd]ebugPublic/ 25 + [Rr]elease/ 26 + [Rr]eleases/ 27 + x64/ 28 + x86/ 29 + [Ww][Ii][Nn]32/ 30 + [Aa][Rr][Mm]/ 31 + [Aa][Rr][Mm]64/ 32 + bld/ 33 + [Bb]in/ 34 + [Oo]bj/ 35 + [Ll]og/ 36 + [Ll]ogs/ 37 + 38 + # Visual Studio 2015/2017 cache/options directory 39 + .vs/ 40 + # Uncomment if you have tasks that create the project's static files in wwwroot 41 + #wwwroot/ 42 + 43 + # Visual Studio 2017 auto generated files 44 + Generated\ Files/ 45 + 46 + # MSTest test Results 47 + [Tt]est[Rr]esult*/ 48 + [Bb]uild[Ll]og.* 49 + 50 + # NUnit 51 + *.VisualState.xml 52 + TestResult.xml 53 + nunit-*.xml 54 + 55 + # Build Results of an ATL Project 56 + [Dd]ebugPS/ 57 + [Rr]eleasePS/ 58 + dlldata.c 59 + 60 + # Benchmark Results 61 + BenchmarkDotNet.Artifacts/ 62 + 63 + # .NET 64 + project.lock.json 65 + project.fragment.lock.json 66 + artifacts/ 67 + 68 + # Tye 69 + .tye/ 70 + 71 + # ASP.NET Scaffolding 72 + ScaffoldingReadMe.txt 73 + 74 + # StyleCop 75 + StyleCopReport.xml 76 + 77 + # Files built by Visual Studio 78 + *_i.c 79 + *_p.c 80 + *_h.h 81 + *.ilk 82 + *.meta 83 + *.obj 84 + *.iobj 85 + *.pch 86 + *.pdb 87 + *.ipdb 88 + *.pgc 89 + *.pgd 90 + *.rsp 91 + *.sbr 92 + *.tlb 93 + *.tli 94 + *.tlh 95 + *.tmp 96 + *.tmp_proj 97 + *_wpftmp.csproj 98 + *.log 99 + *.tlog 100 + *.vspscc 101 + *.vssscc 102 + .builds 103 + *.pidb 104 + *.svclog 105 + *.scc 106 + 107 + # Chutzpah Test files 108 + _Chutzpah* 109 + 110 + # Visual C++ cache files 111 + ipch/ 112 + *.aps 113 + *.ncb 114 + *.opendb 115 + *.opensdf 116 + *.sdf 117 + *.cachefile 118 + *.VC.db 119 + *.VC.VC.opendb 120 + 121 + # Visual Studio profiler 122 + *.psess 123 + *.vsp 124 + *.vspx 125 + *.sap 126 + 127 + # Visual Studio Trace Files 128 + *.e2e 129 + 130 + # TFS 2012 Local Workspace 131 + $tf/ 132 + 133 + # Guidance Automation Toolkit 134 + *.gpState 135 + 136 + # ReSharper is a .NET coding add-in 137 + _ReSharper*/ 138 + *.[Rr]e[Ss]harper 139 + *.DotSettings.user 140 + 141 + # TeamCity is a build add-in 142 + _TeamCity* 143 + 144 + # DotCover is a Code Coverage Tool 145 + *.dotCover 146 + 147 + # AxoCover is a Code Coverage Tool 148 + .axoCover/* 149 + !.axoCover/settings.json 150 + 151 + # Coverlet is a free, cross platform Code Coverage Tool 152 + coverage*.json 153 + coverage*.xml 154 + coverage*.info 155 + 156 + # Visual Studio code coverage results 157 + *.coverage 158 + *.coveragexml 159 + 160 + # NCrunch 161 + _NCrunch_* 162 + .*crunch*.local.xml 163 + nCrunchTemp_* 164 + 165 + # MightyMoose 166 + *.mm.* 167 + AutoTest.Net/ 168 + 169 + # Web workbench (sass) 170 + .sass-cache/ 171 + 172 + # Installshield output folder 173 + [Ee]xpress/ 174 + 175 + # DocProject is a documentation generator add-in 176 + DocProject/buildhelp/ 177 + DocProject/Help/*.HxT 178 + DocProject/Help/*.HxC 179 + DocProject/Help/*.hhc 180 + DocProject/Help/*.hhk 181 + DocProject/Help/*.hhp 182 + DocProject/Help/Html2 183 + DocProject/Help/html 184 + 185 + # Click-Once directory 186 + publish/ 187 + 188 + # Publish Web Output 189 + *.[Pp]ublish.xml 190 + *.azurePubxml 191 + # Note: Comment the next line if you want to checkin your web deploy settings, 192 + # but database connection strings (with potential passwords) will be unencrypted 193 + *.pubxml 194 + *.publishproj 195 + 196 + # Microsoft Azure Web App publish settings. Comment the next line if you want to 197 + # checkin your Azure Web App publish settings, but sensitive information contained 198 + # in these scripts will be unencrypted 199 + PublishScripts/ 200 + 201 + # NuGet Packages 202 + *.nupkg 203 + # NuGet Symbol Packages 204 + *.snupkg 205 + # The packages folder can be ignored because of Package Restore 206 + **/[Pp]ackages/* 207 + # except build/, which is used as an MSBuild target. 208 + !**/[Pp]ackages/build/ 209 + # Uncomment if necessary however generally it will be regenerated when needed 210 + #!**/[Pp]ackages/repositories.config 211 + # NuGet v3's project.json files produces more ignorable files 212 + *.nuget.props 213 + *.nuget.targets 214 + 215 + # Microsoft Azure Build Output 216 + csx/ 217 + *.build.csdef 218 + 219 + # Microsoft Azure Emulator 220 + ecf/ 221 + rcf/ 222 + 223 + # Windows Store app package directories and files 224 + AppPackages/ 225 + BundleArtifacts/ 226 + Package.StoreAssociation.xml 227 + _pkginfo.txt 228 + *.appx 229 + *.appxbundle 230 + *.appxupload 231 + 232 + # Visual Studio cache files 233 + # files ending in .cache can be ignored 234 + *.[Cc]ache 235 + # but keep track of directories ending in .cache 236 + !?*.[Cc]ache/ 237 + 238 + # Others 239 + ClientBin/ 240 + ~$* 241 + *~ 242 + *.dbmdl 243 + *.dbproj.schemaview 244 + *.jfm 245 + *.pfx 246 + *.publishsettings 247 + orleans.codegen.cs 248 + 249 + # Including strong name files can present a security risk 250 + # (https://github.com/github/gitignore/pull/2483#issue-259490424) 251 + #*.snk 252 + 253 + # Since there are multiple workflows, uncomment next line to ignore bower_components 254 + # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 255 + #bower_components/ 256 + 257 + # RIA/Silverlight projects 258 + Generated_Code/ 259 + 260 + # Backup & report files from converting an old project file 261 + # to a newer Visual Studio version. Backup files are not needed, 262 + # because we have git ;-) 263 + _UpgradeReport_Files/ 264 + Backup*/ 265 + UpgradeLog*.XML 266 + UpgradeLog*.htm 267 + ServiceFabricBackup/ 268 + *.rptproj.bak 269 + 270 + # SQL Server files 271 + *.mdf 272 + *.ldf 273 + *.ndf 274 + 275 + # Business Intelligence projects 276 + *.rdl.data 277 + *.bim.layout 278 + *.bim_*.settings 279 + *.rptproj.rsuser 280 + *- [Bb]ackup.rdl 281 + *- [Bb]ackup ([0-9]).rdl 282 + *- [Bb]ackup ([0-9][0-9]).rdl 283 + 284 + # Microsoft Fakes 285 + FakesAssemblies/ 286 + 287 + # GhostDoc plugin setting file 288 + *.GhostDoc.xml 289 + 290 + # Node.js Tools for Visual Studio 291 + .ntvs_analysis.dat 292 + node_modules/ 293 + 294 + # Visual Studio 6 build log 295 + *.plg 296 + 297 + # Visual Studio 6 workspace options file 298 + *.opt 299 + 300 + # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 301 + *.vbw 302 + 303 + # Visual Studio 6 auto-generated project file (contains which files were open etc.) 304 + *.vbp 305 + 306 + # Visual Studio 6 workspace and project file (working project files containing files to include in project) 307 + *.dsw 308 + *.dsp 309 + 310 + # Visual Studio 6 technical files 311 + *.ncb 312 + *.aps 313 + 314 + # Visual Studio LightSwitch build output 315 + **/*.HTMLClient/GeneratedArtifacts 316 + **/*.DesktopClient/GeneratedArtifacts 317 + **/*.DesktopClient/ModelManifest.xml 318 + **/*.Server/GeneratedArtifacts 319 + **/*.Server/ModelManifest.xml 320 + _Pvt_Extensions 321 + 322 + # Paket dependency manager 323 + .paket/paket.exe 324 + paket-files/ 325 + 326 + # FAKE - F# Make 327 + .fake/ 328 + 329 + # CodeRush personal settings 330 + .cr/personal 331 + 332 + # Python Tools for Visual Studio (PTVS) 333 + __pycache__/ 334 + *.pyc 335 + 336 + # Cake - Uncomment if you are using it 337 + # tools/** 338 + # !tools/packages.config 339 + 340 + # Tabs Studio 341 + *.tss 342 + 343 + # Telerik's JustMock configuration file 344 + *.jmconfig 345 + 346 + # BizTalk build output 347 + *.btp.cs 348 + *.btm.cs 349 + *.odx.cs 350 + *.xsd.cs 351 + 352 + # OpenCover UI analysis results 353 + OpenCover/ 354 + 355 + # Azure Stream Analytics local run output 356 + ASALocalRun/ 357 + 358 + # MSBuild Binary and Structured Log 359 + *.binlog 360 + 361 + # NVidia Nsight GPU debugger configuration file 362 + *.nvuser 363 + 364 + # MFractors (Xamarin productivity tool) working folder 365 + .mfractor/ 366 + 367 + # Local History for Visual Studio 368 + .localhistory/ 369 + 370 + # Visual Studio History (VSHistory) files 371 + .vshistory/ 372 + 373 + # BeatPulse healthcheck temp database 374 + healthchecksdb 375 + 376 + # Backup folder for Package Reference Convert tool in Visual Studio 2017 377 + MigrationBackup/ 378 + 379 + # Ionide (cross platform F# VS Code tools) working folder 380 + .ionide/ 381 + 382 + # Fody - auto-generated XML schema 383 + FodyWeavers.xsd 384 + 385 + # VS Code files for those working on multiple tools 386 + .vscode/* 387 + !.vscode/settings.json 388 + !.vscode/tasks.json 389 + !.vscode/launch.json 390 + !.vscode/extensions.json 391 + *.code-workspace 392 + 393 + # Local History for Visual Studio Code 394 + .history/ 395 + 396 + # Windows Installer files from build outputs 397 + *.cab 398 + *.msi 399 + *.msix 400 + *.msm 401 + *.msp 402 + 403 + # JetBrains Rider 404 + *.sln.iml 405 + .idea/ 406 + 407 + ## 408 + ## Visual studio for Mac 409 + ## 410 + 411 + 412 + # globs 413 + Makefile.in 414 + *.userprefs 415 + *.usertasks 416 + config.make 417 + config.status 418 + aclocal.m4 419 + install-sh 420 + autom4te.cache/ 421 + *.tar.gz 422 + tarballs/ 423 + test-results/ 424 + 425 + # Mac bundle stuff 426 + *.dmg 427 + *.app 428 + 429 + # content below from: https://github.com/github/gitignore/blob/main/Global/macOS.gitignore 430 + # General 431 + .DS_Store 432 + .AppleDouble 433 + .LSOverride 434 + 435 + # Icon must end with two \r 436 + Icon 437 + 438 + 439 + # Thumbnails 440 + ._* 441 + 442 + # Files that might appear in the root of a volume 443 + .DocumentRevisions-V100 444 + .fseventsd 445 + .Spotlight-V100 446 + .TemporaryItems 447 + .Trashes 448 + .VolumeIcon.icns 449 + .com.apple.timemachine.donotpresent 450 + 451 + # Directories potentially created on remote AFP share 452 + .AppleDB 453 + .AppleDesktop 454 + Network Trash Folder 455 + Temporary Items 456 + .apdisk 457 + 458 + # content below from: https://github.com/github/gitignore/blob/main/Global/Windows.gitignore 459 + # Windows thumbnail cache files 460 + Thumbs.db 461 + ehthumbs.db 462 + ehthumbs_vista.db 463 + 464 + # Dump file 465 + *.stackdump 466 + 467 + # Folder config file 468 + [Dd]esktop.ini 469 + 470 + # Recycle Bin used on file shares 471 + $RECYCLE.BIN/ 472 + 473 + # Windows Installer files 474 + *.cab 475 + *.msi 476 + *.msix 477 + *.msm 478 + *.msp 479 + 480 + # Windows shortcuts 481 + *.lnk 482 + 483 + # Vim temporary swap files 484 + *.swp 485 + 486 + # .NET Aspire output files 487 + aspire-output/
+46
Directory.Packages.props
··· 1 + <Project> 2 + <PropertyGroup> 3 + <!-- Enable central package management, https://learn.microsoft.com/en-us/nuget/consume-packages/Central-Package-Management --> 4 + <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> 5 + <!-- Version references --> 6 + <AspireVersion>13.0.0</AspireVersion> 7 + <OtelVersion>1.12.0</OtelVersion> 8 + </PropertyGroup> 9 + <ItemGroup> 10 + <!-- Aspire & ASP.NET --> 11 + <PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireVersion)" /> 12 + <PackageVersion Include="Aspire.Hosting.JavaScript" Version="13.0.0" /> 13 + <PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="$(AspireVersion)" /> 14 + <PackageVersion Include="Aspire.Hosting.Yarp" Version="$(AspireVersion)" /> 15 + <PackageVersion Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.5.2" /> 16 + <PackageVersion Include="CommunityToolkit.Aspire.Hosting.Bun" Version="9.9.0" /> 17 + <PackageVersion Include="FluentValidation" Version="12.1.0" /> 18 + <PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.1" /> 19 + <PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.9" /> 20 + <PackageVersion Include="Microsoft.EntityFrameworkCore.Abstractions" Version="9.0.9" /> 21 + <PackageVersion Include="Microsoft.EntityFrameworkCore.Analyzers" Version="9.0.9" /> 22 + <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9"> 23 + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> 24 + <PrivateAssets>all</PrivateAssets> 25 + </PackageVersion> 26 + <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.9" /> 27 + <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.9" /> 28 + <PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" /> 29 + <PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.9" /> 30 + <PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" /> 31 + <PackageVersion Include="Yarp.ReverseProxy" Version="2.2.0" /> 32 + <PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.9.0" /> 33 + <PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="9.5.2" /> 34 + <PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.9" /> 35 + <PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" /> 36 + <!-- Telemetry --> 37 + <PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="$(OtelVersion)" /> 38 + <PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="$(OtelVersion)" /> 39 + <PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="$(OtelVersion)" /> 40 + <PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="$(OtelVersion)" /> 41 + <PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="$(OtelVersion)" /> 42 + <!-- Auth --> 43 + <PackageVersion Include="BCrypt.Net-Next" Version="4.0.3" /> 44 + <PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.0" /> 45 + </ItemGroup> 46 + </Project>
+27
Justfile
··· 1 + set quiet := true 2 + 3 + src := justfile_directory() / "src" 4 + api := src / "Den.Api" 5 + apphost := src / "Den.AppHost" 6 + infra := src / "Den.Infrastructure" 7 + migrations := src / "Den.MigrationService" 8 + 9 + run *args: 10 + dotnet run --project {{ apphost }} {{ args }} 11 + 12 + makemigrations name: 13 + dotnet ef migrations add {{ name }} \ 14 + --project={{ infra }} \ 15 + --output-dir={{ infra / "Migrations" }} \ 16 + --startup-project={{ api }} 17 + 18 + dbshell: 19 + docker exec \ 20 + --interactive \ 21 + --tty \ 22 + --env "PGPASSWORD=$(cat ~/.microsoft/usersecrets/$(cat {{ apphost / "Den.AppHost.csproj" }} | grep Secrets | awk -F'>' '{ print $2; }' | awk -F'<' '{ print $1; }')/secrets.json | jq -r '.["Parameters:postgres-password"]')" \ 23 + $(docker ps | grep postgres- | awk '{ print $1 }') \ 24 + psql -U postgres postgresdb 25 + 26 + build name: 27 + dotnet build src/{{ name }}
+13
den.slnx
··· 1 + <Solution> 2 + <Folder Name="/src/"> 3 + <Project Path="src/Den.AppHost/Den.AppHost.csproj" /> 4 + <Project Path="src/Den.ServiceDefaults/Den.ServiceDefaults.csproj" /> 5 + 6 + <Project Path="src/Den.Domain/Den.Domain.csproj" /> 7 + <Project Path="src/Den.Application/Den.Application.csproj" /> 8 + <Project Path="src/Den.Infrastructure/Den.Infrastructure.csproj" /> 9 + 10 + <Project Path="src/Den.Api/Den.Api.csproj" /> 11 + <Project Path="src/Den.MigrationService/Den.MigrationService.csproj" /> 12 + </Folder> 13 + </Solution>
+5
global.json
··· 1 + { 2 + "sdk": { 3 + "version": "10.0.100" 4 + } 5 + }
+5
mise.toml
··· 1 + [tools] 2 + dotnet = "10.0.100" 3 + just = "1.43.0" 4 + "dotnet:dotnet-ef" = "9.0.10" 5 + "dotnet:Aspire.Cli" = "13.0.0"
+88
src/Den.Api/Controllers/AuthController.cs
··· 1 + using System.Security.Claims; 2 + using Den.Application.Auth; 3 + using Den.Infrastructure.Persistence; 4 + using Microsoft.AspNetCore.Authorization; 5 + using Microsoft.AspNetCore.Mvc; 6 + using Microsoft.EntityFrameworkCore; 7 + 8 + namespace Den.Api.Controllers; 9 + 10 + [ApiController] 11 + [Route("v1/auth")] 12 + public class AuthController( 13 + IAuthService authService, 14 + DenDbContext context, 15 + ILogger<AuthController> logger 16 + ) : ControllerBase 17 + { 18 + [HttpPost("signup")] 19 + [ProducesResponseType(StatusCodes.Status201Created)] 20 + [ProducesResponseType(StatusCodes.Status400BadRequest)] 21 + public async Task<ActionResult<AuthResponse>> Signup([FromBody] SignupRequest request) 22 + { 23 + try 24 + { 25 + var response = await authService.SignupAsync(request); 26 + return Ok(response); 27 + } 28 + catch (InvalidOperationException ex) 29 + { 30 + return BadRequest(new { error = ex.Message }); 31 + } 32 + } 33 + 34 + [HttpPost("login")] 35 + [ProducesResponseType(StatusCodes.Status200OK)] 36 + [ProducesResponseType(StatusCodes.Status401Unauthorized)] 37 + public async Task<ActionResult<AuthResponse>> Login([FromBody] LoginRequest request) 38 + { 39 + var response = await authService.LoginAsync(request); 40 + return response switch 41 + { 42 + null => Unauthorized(new { error = "invalid credentials" }), 43 + _ => Ok(response), 44 + }; 45 + } 46 + 47 + [HttpPost("refresh")] 48 + [ProducesResponseType(StatusCodes.Status200OK)] 49 + [ProducesResponseType(StatusCodes.Status401Unauthorized)] 50 + public async Task<ActionResult<AuthResponse>> Refresh([FromBody] RefreshRequest request) 51 + { 52 + var response = await authService.RefreshAsync(request); 53 + return response switch 54 + { 55 + null => Unauthorized(new { error = "invalid refresh token" }), 56 + _ => Ok(response), 57 + }; 58 + } 59 + 60 + [Authorize] 61 + [HttpGet("me")] 62 + [ProducesResponseType(StatusCodes.Status200OK)] 63 + [ProducesResponseType(StatusCodes.Status401Unauthorized)] 64 + public async Task<ActionResult<MeResponse>> GetMe() 65 + { 66 + var userId = User.FindFirstValue(ClaimTypes.NameIdentifier); 67 + if (userId is null) 68 + { 69 + logger.LogDebug("Invalid token: {Reason}", new { Reason = "Token has no subject ID" }); 70 + return Unauthorized(new { error = "invalid token" }); 71 + } 72 + 73 + var user = await context.Users.FirstOrDefaultAsync(u => u.Id == Guid.Parse(userId)); 74 + 75 + if (user is null) 76 + { 77 + return Unauthorized(new { error = "user not found" }); 78 + } 79 + 80 + return Ok(new MeResponse( 81 + Id: user.Id.ToString(), 82 + Username: user.Username, 83 + DisplayName: user.DisplayName, 84 + Email: user.Email, 85 + Role: user.Role.ToString() 86 + )); 87 + } 88 + }
+123
src/Den.Api/Controllers/Budgets/BudgetsController.cs
··· 1 + using System.Security.Claims; 2 + using Den.Application.Budgets; 3 + using FluentValidation; 4 + using Microsoft.AspNetCore.Authorization; 5 + using Microsoft.AspNetCore.Mvc; 6 + 7 + namespace Den.Api.Controllers.Budgets; 8 + 9 + [ApiController] 10 + [Route("v1/budgets")] 11 + [Authorize] 12 + public class BudgetsController( 13 + IBudgetService budgetService, 14 + IValidator<CreateBudgetRequest> createValidator, 15 + IValidator<UpdateBudgetRequest> updateValidator, 16 + ILogger<BudgetsController> logger 17 + ) : ControllerBase 18 + { 19 + [HttpGet] 20 + [ProducesResponseType(StatusCodes.Status200OK)] 21 + [ProducesResponseType(StatusCodes.Status401Unauthorized)] 22 + public async Task<ActionResult<IEnumerable<BudgetResponse>>> GetAll() 23 + { 24 + var budgets = await budgetService.GetAllAsync(); 25 + return Ok(budgets); 26 + } 27 + 28 + [HttpGet("{id}")] 29 + [ProducesResponseType(StatusCodes.Status200OK)] 30 + [ProducesResponseType(StatusCodes.Status404NotFound)] 31 + public async Task<ActionResult<BudgetResponse>> GetById(Guid id) 32 + { 33 + var budget = await budgetService.GetByIdAsync(id); 34 + return budget switch 35 + { 36 + null => NotFound(new { error = "budget not found" }), 37 + _ => Ok(budget) 38 + }; 39 + } 40 + 41 + [HttpPost] 42 + [Authorize(Policy = "EditorOrHigher")] 43 + [ProducesResponseType(StatusCodes.Status201Created)] 44 + [ProducesResponseType(StatusCodes.Status400BadRequest)] 45 + [ProducesResponseType(StatusCodes.Status401Unauthorized)] 46 + [ProducesResponseType(StatusCodes.Status403Forbidden)] 47 + public async Task<ActionResult<BudgetResponse>> Create([FromBody] CreateBudgetRequest request) 48 + { 49 + var userId = GetUserId(); 50 + if (userId is null) 51 + { 52 + return Unauthorized(new { error = "invalid token" }); 53 + } 54 + 55 + var validationResult = await createValidator.ValidateAsync(request); 56 + if (!validationResult.IsValid) 57 + { 58 + return BadRequest(new { errors = validationResult.Errors.Select(e => e.ErrorMessage) }); 59 + } 60 + 61 + var budget = await budgetService.CreateAsync(request, userId.Value); 62 + return CreatedAtAction(nameof(GetById), new { id = budget.Id }, budget); 63 + } 64 + 65 + [HttpPut("{id}")] 66 + [Authorize(Policy = "EditorOrHigher")] 67 + [ProducesResponseType(StatusCodes.Status200OK)] 68 + [ProducesResponseType(StatusCodes.Status400BadRequest)] 69 + [ProducesResponseType(StatusCodes.Status401Unauthorized)] 70 + [ProducesResponseType(StatusCodes.Status403Forbidden)] 71 + [ProducesResponseType(StatusCodes.Status404NotFound)] 72 + public async Task<ActionResult<BudgetResponse>> Update(Guid id, [FromBody] UpdateBudgetRequest request) 73 + { 74 + var userId = GetUserId(); 75 + if (userId is null) 76 + { 77 + return Unauthorized(new { error = "invalid token" }); 78 + } 79 + 80 + var validationResult = await updateValidator.ValidateAsync(request); 81 + if (!validationResult.IsValid) 82 + { 83 + return BadRequest(new { errors = validationResult.Errors.Select(e => e.ErrorMessage) }); 84 + } 85 + 86 + var budget = await budgetService.UpdateAsync(id, request, userId.Value); 87 + return budget switch 88 + { 89 + null => NotFound(new { error = "budget not found" }), 90 + _ => Ok(budget) 91 + }; 92 + } 93 + 94 + [HttpDelete("{id}")] 95 + [Authorize(Policy = "EditorOrHigher")] 96 + [ProducesResponseType(StatusCodes.Status204NoContent)] 97 + [ProducesResponseType(StatusCodes.Status401Unauthorized)] 98 + [ProducesResponseType(StatusCodes.Status403Forbidden)] 99 + [ProducesResponseType(StatusCodes.Status404NotFound)] 100 + public async Task<IActionResult> Delete(Guid id) 101 + { 102 + var userId = GetUserId(); 103 + if (userId is null) 104 + { 105 + return Unauthorized(new { error = "invalid token" }); 106 + } 107 + 108 + var deleted = await budgetService.DeleteAsync(id, userId.Value); 109 + return deleted ? NoContent() : NotFound(new { error = "budget not found" }); 110 + } 111 + 112 + private Guid? GetUserId() 113 + { 114 + var userIdClaim = User.FindFirstValue(ClaimTypes.NameIdentifier); 115 + if (userIdClaim is null) 116 + { 117 + logger.LogDebug("Invalid token: {Reason}", new { Reason = "Token has no subject ID" }); 118 + return null; 119 + } 120 + 121 + return Guid.TryParse(userIdClaim, out var userId) ? userId : null; 122 + } 123 + }
+22
src/Den.Api/Den.Api.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk.Web"> 2 + 3 + <ItemGroup> 4 + <ProjectReference Include="..\Den.Application\Den.Application.csproj" /> 5 + <ProjectReference Include="..\Den.Infrastructure\Den.Infrastructure.csproj" /> 6 + <ProjectReference Include="..\Den.ServiceDefaults\Den.ServiceDefaults.csproj" /> 7 + </ItemGroup> 8 + 9 + <ItemGroup> 10 + <PackageReference Include="FluentValidation.AspNetCore" /> 11 + <PackageReference Include="Microsoft.AspNetCore.OpenApi" /> 12 + <PackageReference Include="Microsoft.EntityFrameworkCore" /> 13 + <PackageReference Include="Microsoft.EntityFrameworkCore.Design" /> 14 + </ItemGroup> 15 + 16 + <PropertyGroup> 17 + <TargetFramework>net10.0</TargetFramework> 18 + <Nullable>enable</Nullable> 19 + <ImplicitUsings>enable</ImplicitUsings> 20 + </PropertyGroup> 21 + 22 + </Project>
+67
src/Den.Api/Program.cs
··· 1 + using System.Text; 2 + using Den.Application.Auth; 3 + using Den.Application.Budgets; 4 + using Den.Infrastructure.Auth; 5 + using Den.Infrastructure.Budgets; 6 + using Den.Infrastructure.Persistence; 7 + using FluentValidation; 8 + using Microsoft.AspNetCore.Authentication.JwtBearer; 9 + using Microsoft.AspNetCore.DataProtection; 10 + using Microsoft.IdentityModel.Tokens; 11 + 12 + var builder = WebApplication.CreateBuilder(args); 13 + 14 + builder.AddServiceDefaults(); 15 + 16 + builder.AddNpgsqlDbContext<DenDbContext>(connectionName: "postgresdb"); 17 + 18 + builder.Services.AddDataProtection().SetApplicationName("den-auth"); 19 + 20 + var jwtConfig = builder.Configuration.GetSection("Jwt").Get<JwtSettings>() 21 + ?? throw new InvalidOperationException("Missing JWT config"); 22 + 23 + builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("Jwt")); 24 + builder.Services.AddScoped<IAuthService, AuthService>(); 25 + builder.Services.AddScoped<IBudgetService, BudgetService>(); 26 + 27 + builder.Services.AddValidatorsFromAssemblyContaining<CreateBudgetRequestValidator>(); 28 + 29 + builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) 30 + .AddJwtBearer(options => 31 + { 32 + if (builder.Environment.IsDevelopment()) options.RequireHttpsMetadata = false; 33 + 34 + options.TokenValidationParameters = new TokenValidationParameters 35 + { 36 + ValidateIssuer = true, 37 + ValidateAudience = true, 38 + ValidateLifetime = true, 39 + ValidateIssuerSigningKey = true, 40 + ValidIssuer = jwtConfig.Issuer, 41 + ValidAudience = jwtConfig.Audience, 42 + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.Secret)) 43 + }; 44 + }); 45 + 46 + builder.Services.AddAuthorization(options => 47 + { 48 + options.AddPolicy("EditorOrHigher", policy => 49 + policy.RequireAssertion(context => 50 + context.User.HasClaim(c => c.Type == System.Security.Claims.ClaimTypes.Role && 51 + (c.Value == "EDITOR" || c.Value == "ADMIN")))); 52 + }); 53 + 54 + builder.Services.AddControllers(); 55 + 56 + var app = builder.Build(); 57 + 58 + app.UseHttpsRedirection(); 59 + 60 + app.UseAuthentication(); 61 + app.UseAuthorization(); 62 + 63 + app.MapControllers(); 64 + 65 + app.MapDefaultEndpoints(); 66 + 67 + app.Run();
+23
src/Den.Api/Properties/launchSettings.json
··· 1 + { 2 + "$schema": "https://json.schemastore.org/launchsettings.json", 3 + "profiles": { 4 + "http": { 5 + "commandName": "Project", 6 + "dotnetRunMessages": true, 7 + "launchBrowser": false, 8 + "applicationUrl": "http://localhost:5226", 9 + "environmentVariables": { 10 + "ASPNETCORE_ENVIRONMENT": "Development" 11 + } 12 + }, 13 + "https": { 14 + "commandName": "Project", 15 + "dotnetRunMessages": true, 16 + "launchBrowser": false, 17 + "applicationUrl": "https://localhost:7165;http://localhost:5226", 18 + "environmentVariables": { 19 + "ASPNETCORE_ENVIRONMENT": "Development" 20 + } 21 + } 22 + } 23 + }
+13
src/Den.Api/appsettings.Development.json
··· 1 + { 2 + "Logging": { 3 + "LogLevel": { 4 + "Default": "Debug", 5 + "Microsoft.AspNetCore": "Warning" 6 + } 7 + }, 8 + "Jwt": { 9 + "Issuer": "den", 10 + "Audience": "den", 11 + "Secret": "a-string-secret-at-least-256-bits-long" 12 + } 13 + }
+9
src/Den.Api/appsettings.json
··· 1 + { 2 + "Logging": { 3 + "LogLevel": { 4 + "Default": "Information", 5 + "Microsoft.AspNetCore": "Warning" 6 + } 7 + }, 8 + "AllowedHosts": "*" 9 + }
+36
src/Den.AppHost/AppHost.cs
··· 1 + using Aspire.Hosting.Yarp.Transforms; 2 + 3 + using Microsoft.Extensions.Configuration; 4 + 5 + var builder = DistributedApplication.CreateBuilder(args); 6 + 7 + var postgres = builder.AddPostgres("postgres") 8 + .WithHostPort(5432) 9 + .WithDataVolume(isReadOnly: false); 10 + 11 + var postgresDb = postgres.AddDatabase("postgresdb"); 12 + 13 + var migrations = builder.AddProject<Projects.Den_MigrationService>("migrations") 14 + .WithReference(postgresDb) 15 + .WaitFor(postgresDb); 16 + 17 + var api = builder.AddProject<Projects.Den_Api>("api") 18 + .WithReference(postgresDb) 19 + .WithReference(migrations) 20 + .WaitForCompletion(migrations); 21 + 22 + var webClient = builder.AddViteApp("client-web", "../Den.Client.Web") 23 + .WithBun() 24 + .WithIconName("globe") 25 + .WithExternalHttpEndpoints(); 26 + 27 + var gateway = builder.AddYarp("gateway") 28 + .WithHostPort(builder.Configuration.GetValue<int>("Port")) 29 + .WithConfiguration(yarp => 30 + { 31 + yarp.AddRoute(webClient); 32 + yarp.AddRoute("/api/{**catch-all}", api) 33 + .WithTransformPathRemovePrefix("/api"); 34 + }); 35 + 36 + builder.Build().Run();
+24
src/Den.AppHost/Den.AppHost.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + 3 + <Sdk Name="Aspire.AppHost.Sdk" Version="9.5.2" /> 4 + 5 + <PropertyGroup> 6 + <OutputType>Exe</OutputType> 7 + <TargetFramework>net10.0</TargetFramework> 8 + <ImplicitUsings>enable</ImplicitUsings> 9 + <Nullable>enable</Nullable> 10 + <UserSecretsId>b90624cd-d891-40c3-a2af-074ad778dab5</UserSecretsId> 11 + </PropertyGroup> 12 + 13 + <ItemGroup> 14 + <PackageReference Include="Aspire.Hosting.AppHost" /> 15 + <PackageReference Include="Aspire.Hosting.JavaScript" /> 16 + <PackageReference Include="Aspire.Hosting.PostgreSQL" /> 17 + <PackageReference Include="Aspire.Hosting.Yarp" /> 18 + <PackageReference Include="CommunityToolkit.Aspire.Hosting.Bun" /> 19 + 20 + <ProjectReference Include="../Den.MigrationService/Den.MigrationService.csproj" /> 21 + <ProjectReference Include="../Den.Api/Den.Api.csproj" /> 22 + </ItemGroup> 23 + 24 + </Project>
+115
src/Den.AppHost/Extensions.cs
··· 1 + using Aspire.Hosting.JavaScript; 2 + 3 + #pragma warning disable IDE0130 // Namespace does not match folder structure 4 + namespace Aspire.Hosting; 5 + #pragma warning restore IDE0130 // Namespace does not match folder structure 6 + 7 + public static class Extensions { 8 + public static IResourceBuilder<TResource> WithBun<TResource>(this IResourceBuilder<TResource> resource, bool install = true, string[]? installArgs = null) 9 + where TResource : JavaScriptAppResource 10 + { 11 + ArgumentNullException.ThrowIfNull(resource, nameof(resource)); 12 + string workingDirectory = resource.Resource.WorkingDirectory; 13 + bool flag = File.Exists(Path.Combine(workingDirectory, "bun.lock")); 14 + installArgs ??= GetDefaultBunInstallArgs(resource, flag); 15 + 16 + string text = "package.json"; 17 + if (flag) 18 + { 19 + text += " bun.lock"; 20 + } 21 + 22 + IResourceBuilder<TResource> resourceBuilder = resource.WithAnnotation(new JavaScriptPackageManagerAnnotation("bun", "run") 23 + { 24 + PackageFilesPatterns = 25 + { 26 + new CopyFilePattern(text, "./") 27 + }, 28 + CommandSeparator = null 29 + }); 30 + 31 + string text2 = "install"; 32 + string[] array = installArgs; 33 + int num = 0; 34 + string[] array2 = new string[1 + array.Length]; 35 + array2[num] = text2; 36 + num++; 37 + ReadOnlySpan<string> readOnlySpan = new(array); 38 + readOnlySpan.CopyTo(new Span<string>(array2).Slice(num, readOnlySpan.Length)); 39 + num += readOnlySpan.Length; 40 + resourceBuilder.WithAnnotation(new JavaScriptInstallCommandAnnotation(array2)); 41 + AddInstaller(resource, install); 42 + return resource; 43 + } 44 + 45 + private static string[] GetDefaultBunInstallArgs(IResourceBuilder<JavaScriptAppResource> resource, bool hasBunLock) => 46 + resource.ApplicationBuilder.ExecutionContext.IsPublishMode && hasBunLock 47 + ? ["--frozen-lockfile"] 48 + : []; 49 + 50 + private static void AddInstaller<TResource>(IResourceBuilder<TResource> resource, bool install) where TResource : JavaScriptAppResource 51 + { 52 + // Only install packages if in run mode 53 + if (resource.ApplicationBuilder.ExecutionContext.IsRunMode) 54 + { 55 + // Check if the installer resource already exists 56 + var installerName = $"{resource.Resource.Name}-installer"; 57 + resource.ApplicationBuilder.TryCreateResourceBuilder<JavaScriptInstallerResource>(installerName, out var existingResource); 58 + 59 + if (!install) 60 + { 61 + if (existingResource != null) 62 + { 63 + // Remove existing installer resource if install is false 64 + resource.ApplicationBuilder.Resources.Remove(existingResource.Resource); 65 + resource.Resource.Annotations.OfType<WaitAnnotation>() 66 + .Where(w => w.Resource == existingResource.Resource) 67 + .ToList() 68 + .ForEach(w => resource.Resource.Annotations.Remove(w)); 69 + resource.Resource.Annotations.OfType<JavaScriptPackageInstallerAnnotation>() 70 + .ToList() 71 + .ForEach(a => resource.Resource.Annotations.Remove(a)); 72 + } 73 + else 74 + { 75 + // No installer needed 76 + } 77 + return; 78 + } 79 + 80 + if (existingResource is not null) 81 + { 82 + // Installer already exists 83 + return; 84 + } 85 + 86 + var installer = new JavaScriptInstallerResource(installerName, resource.Resource.WorkingDirectory); 87 + var installerBuilder = resource.ApplicationBuilder.AddResource(installer) 88 + .WithParentRelationship(resource.Resource) 89 + .ExcludeFromManifest(); 90 + 91 + resource.ApplicationBuilder.Eventing.Subscribe<BeforeStartEvent>((_, _) => 92 + { 93 + // set the installer's working directory to match the resource's working directory 94 + // and set the install command and args based on the resource's annotations 95 + if (!resource.Resource.TryGetLastAnnotation<JavaScriptPackageManagerAnnotation>(out var packageManager) || 96 + !resource.Resource.TryGetLastAnnotation<JavaScriptInstallCommandAnnotation>(out var installCommand)) 97 + { 98 + throw new InvalidOperationException("JavaScriptPackageManagerAnnotation and JavaScriptInstallCommandAnnotation are required when installing packages."); 99 + } 100 + 101 + installerBuilder 102 + .WithCommand(packageManager.ExecutableName) 103 + .WithWorkingDirectory(resource.Resource.WorkingDirectory) 104 + .WithArgs(installCommand.Args); 105 + 106 + return Task.CompletedTask; 107 + }); 108 + 109 + // Make the parent resource wait for the installer to complete 110 + resource.WaitForCompletion(installerBuilder); 111 + 112 + resource.WithAnnotation(new JavaScriptPackageInstallerAnnotation(installer)); 113 + } 114 + } 115 + }
+29
src/Den.AppHost/Properties/launchSettings.json
··· 1 + { 2 + "$schema": "https://json.schemastore.org/launchsettings.json", 3 + "profiles": { 4 + "https": { 5 + "commandName": "Project", 6 + "dotnetRunMessages": true, 7 + "launchBrowser": true, 8 + "applicationUrl": "https://localhost:17084;http://localhost:15058", 9 + "environmentVariables": { 10 + "ASPNETCORE_ENVIRONMENT": "Development", 11 + "DOTNET_ENVIRONMENT": "Development", 12 + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21190", 13 + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22113" 14 + } 15 + }, 16 + "http": { 17 + "commandName": "Project", 18 + "dotnetRunMessages": true, 19 + "launchBrowser": true, 20 + "applicationUrl": "http://localhost:15058", 21 + "environmentVariables": { 22 + "ASPNETCORE_ENVIRONMENT": "Development", 23 + "DOTNET_ENVIRONMENT": "Development", 24 + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19169", 25 + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20068" 26 + } 27 + } 28 + } 29 + }
+8
src/Den.AppHost/appsettings.Development.json
··· 1 + { 2 + "Logging": { 3 + "LogLevel": { 4 + "Default": "Information", 5 + "Microsoft.AspNetCore": "Warning" 6 + } 7 + } 8 + }
+10
src/Den.AppHost/appsettings.json
··· 1 + { 2 + "Logging": { 3 + "LogLevel": { 4 + "Default": "Information", 5 + "Microsoft.AspNetCore": "Warning", 6 + "Aspire.Hosting.Dcp": "Warning" 7 + } 8 + }, 9 + "Port": 8080 10 + }
+8
src/Den.Application/Auth/AuthResponse.cs
··· 1 + namespace Den.Application.Auth; 2 + 3 + public record AuthResponse( 4 + string AccessToken, 5 + string RefreshToken, 6 + DateTime ExpiresIn, 7 + string TokenType = "Bearer" 8 + );
+8
src/Den.Application/Auth/IAuthService.cs
··· 1 + namespace Den.Application.Auth; 2 + 3 + public interface IAuthService 4 + { 5 + Task<AuthResponse> SignupAsync(SignupRequest request); 6 + Task<AuthResponse?> LoginAsync(LoginRequest request); 7 + Task<RefreshResponse?> RefreshAsync(RefreshRequest request); 8 + }
+8
src/Den.Application/Auth/JwtSettings.cs
··· 1 + namespace Den.Application.Auth; 2 + 3 + public class JwtSettings 4 + { 5 + public required string Issuer { get; set; } 6 + public required string Audience { get; set; } 7 + public required string Secret { get; set; } 8 + }
+6
src/Den.Application/Auth/LoginRequest.cs
··· 1 + namespace Den.Application.Auth; 2 + 3 + public record LoginRequest( 4 + string Username, 5 + string Password 6 + );
+9
src/Den.Application/Auth/MeResponse.cs
··· 1 + namespace Den.Application.Auth; 2 + 3 + public record MeResponse( 4 + string Id, 5 + string Username, 6 + string DisplayName, 7 + string Email, 8 + string Role 9 + );
+5
src/Den.Application/Auth/RefreshRequest.cs
··· 1 + namespace Den.Application.Auth; 2 + 3 + public record RefreshRequest( 4 + string RefreshToken 5 + );
+6
src/Den.Application/Auth/RefreshResponse.cs
··· 1 + namespace Den.Application.Auth; 2 + 3 + public record RefreshResponse( 4 + string AccessToken, 5 + string TokenType = "Bearer" 6 + );
+8
src/Den.Application/Auth/SignupRequest.cs
··· 1 + namespace Den.Application.Auth; 2 + 3 + public record SignupRequest( 4 + string Username, 5 + string DisplayName, 6 + string Email, 7 + string Password 8 + );
+13
src/Den.Application/Budgets/BudgetResponse.cs
··· 1 + namespace Den.Application.Budgets; 2 + 3 + public record BudgetResponse( 4 + string Id, 5 + string DisplayName, 6 + string Description, 7 + string Period, 8 + int Total, 9 + string Currency, 10 + string OwnerId, 11 + DateTime CreatedAt, 12 + DateTime UpdatedAt 13 + );
+9
src/Den.Application/Budgets/CreateBudgetRequest.cs
··· 1 + namespace Den.Application.Budgets; 2 + 3 + public record CreateBudgetRequest( 4 + string DisplayName, 5 + string Description, 6 + string Period, 7 + int Total, 8 + string Currency 9 + );
+38
src/Den.Application/Budgets/CreateBudgetRequestValidator.cs
··· 1 + using Den.Domain.Entities; 2 + using FluentValidation; 3 + 4 + namespace Den.Application.Budgets; 5 + 6 + public class CreateBudgetRequestValidator : AbstractValidator<CreateBudgetRequest> 7 + { 8 + public CreateBudgetRequestValidator() 9 + { 10 + RuleFor(x => x.DisplayName) 11 + .NotEmpty().WithMessage("display name is required") 12 + .MaximumLength(100).WithMessage("display name must not exceed 100 characters"); 13 + 14 + RuleFor(x => x.Description) 15 + .MaximumLength(500).WithMessage("description must not exceed 500 characters"); 16 + 17 + RuleFor(x => x.Period) 18 + .NotEmpty().WithMessage("period is required") 19 + .Must(BeValidPeriod).WithMessage("period must be one of: Weekly, Monthly, Quarterly, Yearly"); 20 + 21 + RuleFor(x => x.Total) 22 + .GreaterThan(0).WithMessage("total must be greater than 0"); 23 + 24 + RuleFor(x => x.Currency) 25 + .NotEmpty().WithMessage("currency is required") 26 + .Must(BeValidCurrency).WithMessage("currency must be one of: GBP, EUR, USD, COP"); 27 + } 28 + 29 + private static bool BeValidPeriod(string period) 30 + { 31 + return Enum.TryParse<BudgetPeriod>(period, ignoreCase: true, out _); 32 + } 33 + 34 + private static bool BeValidCurrency(string currency) 35 + { 36 + return Enum.TryParse<BudgetCurrency>(currency, ignoreCase: true, out _); 37 + } 38 + }
+10
src/Den.Application/Budgets/IBudgetService.cs
··· 1 + namespace Den.Application.Budgets; 2 + 3 + public interface IBudgetService 4 + { 5 + Task<BudgetResponse?> GetByIdAsync(Guid id); 6 + Task<IEnumerable<BudgetResponse>> GetAllAsync(); 7 + Task<BudgetResponse> CreateAsync(CreateBudgetRequest request, Guid userId); 8 + Task<BudgetResponse?> UpdateAsync(Guid id, UpdateBudgetRequest request, Guid userId); 9 + Task<bool> DeleteAsync(Guid id, Guid userId); 10 + }
+9
src/Den.Application/Budgets/UpdateBudgetRequest.cs
··· 1 + namespace Den.Application.Budgets; 2 + 3 + public record UpdateBudgetRequest( 4 + string? DisplayName, 5 + string? Description, 6 + string? Period, 7 + int? Total, 8 + string? Currency 9 + );
+40
src/Den.Application/Budgets/UpdateBudgetRequestValidator.cs
··· 1 + using Den.Domain.Entities; 2 + using FluentValidation; 3 + 4 + namespace Den.Application.Budgets; 5 + 6 + public class UpdateBudgetRequestValidator : AbstractValidator<UpdateBudgetRequest> 7 + { 8 + public UpdateBudgetRequestValidator() 9 + { 10 + RuleFor(x => x.DisplayName) 11 + .MaximumLength(100).WithMessage("display name must not exceed 100 characters") 12 + .When(x => x.DisplayName is not null); 13 + 14 + RuleFor(x => x.Description) 15 + .MaximumLength(500).WithMessage("description must not exceed 500 characters") 16 + .When(x => x.Description is not null); 17 + 18 + RuleFor(x => x.Period) 19 + .Must(BeValidPeriod).WithMessage("period must be one of: Weekly, Monthly, Quarterly, Yearly") 20 + .When(x => x.Period is not null); 21 + 22 + RuleFor(x => x.Total) 23 + .GreaterThan(0).WithMessage("total must be greater than 0") 24 + .When(x => x.Total is not null); 25 + 26 + RuleFor(x => x.Currency) 27 + .Must(BeValidCurrency).WithMessage("currency must be one of: GBP, EUR, USD, COP") 28 + .When(x => x.Currency is not null); 29 + } 30 + 31 + private static bool BeValidPeriod(string? period) 32 + { 33 + return period is null || Enum.TryParse<BudgetPeriod>(period, ignoreCase: true, out _); 34 + } 35 + 36 + private static bool BeValidCurrency(string? currency) 37 + { 38 + return currency is null || Enum.TryParse<BudgetCurrency>(currency, ignoreCase: true, out _); 39 + } 40 + }
+18
src/Den.Application/Den.Application.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + 3 + <ItemGroup> 4 + <ProjectReference Include="..\Den.Domain\Den.Domain.csproj" /> 5 + </ItemGroup> 6 + 7 + <ItemGroup> 8 + <PackageReference Include="FluentValidation" /> 9 + <PackageReference Include="Microsoft.IdentityModel.Tokens" /> 10 + </ItemGroup> 11 + 12 + <PropertyGroup> 13 + <TargetFramework>net10.0</TargetFramework> 14 + <ImplicitUsings>enable</ImplicitUsings> 15 + <Nullable>enable</Nullable> 16 + </PropertyGroup> 17 + 18 + </Project>
+24
src/Den.Client.Web/.gitignore
··· 1 + # Logs 2 + logs 3 + *.log 4 + npm-debug.log* 5 + yarn-debug.log* 6 + yarn-error.log* 7 + pnpm-debug.log* 8 + lerna-debug.log* 9 + 10 + node_modules 11 + dist 12 + dist-ssr 13 + *.local 14 + 15 + # Editor directories and files 16 + .vscode/* 17 + !.vscode/extensions.json 18 + .idea 19 + .DS_Store 20 + *.suo 21 + *.ntvs* 22 + *.njsproj 23 + *.sln 24 + *.sw?
+1177
src/Den.Client.Web/bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "@roostmoe/den-client-web", 7 + "dependencies": { 8 + "@radix-ui/react-avatar": "^1.1.11", 9 + "@radix-ui/react-checkbox": "^1.3.3", 10 + "@radix-ui/react-collapsible": "^1.1.12", 11 + "@radix-ui/react-dialog": "^1.1.15", 12 + "@radix-ui/react-dropdown-menu": "^2.1.16", 13 + "@radix-ui/react-label": "^2.1.8", 14 + "@radix-ui/react-select": "^2.2.6", 15 + "@radix-ui/react-separator": "^1.1.8", 16 + "@radix-ui/react-slot": "^1.2.4", 17 + "@radix-ui/react-tooltip": "^1.2.8", 18 + "@tabler/icons-react": "^3.35.0", 19 + "@tailwindcss/vite": "^4.1.17", 20 + "@tanstack/react-form": "^1.25.0", 21 + "@tanstack/react-query": "^5.90.10", 22 + "@tanstack/react-query-devtools": "^5.90.2", 23 + "@tanstack/react-router": "^1.136.8", 24 + "@tanstack/react-router-devtools": "^1.136.8", 25 + "@tanstack/react-table": "^8.21.3", 26 + "@tanstack/router-plugin": "^1.136.8", 27 + "axios": "^1.13.2", 28 + "class-variance-authority": "^0.7.1", 29 + "clsx": "^2.1.1", 30 + "cmdk": "^1.1.1", 31 + "i18next": "^25.6.2", 32 + "lucide-react": "^0.554.0", 33 + "next-themes": "^0.4.6", 34 + "react": "^19.2.0", 35 + "react-dom": "^19.2.0", 36 + "react-i18next": "^16.3.3", 37 + "recharts": "2.15.4", 38 + "sonner": "^2.0.7", 39 + "tailwind-merge": "^3.4.0", 40 + "tailwindcss": "^4.1.17", 41 + "zod": "^4.1.12", 42 + }, 43 + "devDependencies": { 44 + "@eslint/js": "^9.39.1", 45 + "@hey-api/client-axios": "^0.9.1", 46 + "@hey-api/openapi-ts": "0.87.5", 47 + "@types/node": "^24.10.0", 48 + "@types/react": "^19.2.2", 49 + "@types/react-dom": "^19.2.2", 50 + "@vitejs/plugin-react": "^5.1.0", 51 + "babel-plugin-react-compiler": "^1.0.0", 52 + "eslint": "^9.39.1", 53 + "eslint-plugin-react-hooks": "^7.0.1", 54 + "eslint-plugin-react-refresh": "^0.4.24", 55 + "globals": "^16.5.0", 56 + "tw-animate-css": "^1.4.0", 57 + "typescript": "~5.9.3", 58 + "typescript-eslint": "^8.46.3", 59 + "vite": "npm:rolldown-vite@7.2.2", 60 + }, 61 + }, 62 + }, 63 + "overrides": { 64 + "vite": "npm:rolldown-vite@7.2.2", 65 + }, 66 + "packages": { 67 + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], 68 + 69 + "@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], 70 + 71 + "@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], 72 + 73 + "@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], 74 + 75 + "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], 76 + 77 + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], 78 + 79 + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ=="], 80 + 81 + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], 82 + 83 + "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="], 84 + 85 + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], 86 + 87 + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="], 88 + 89 + "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="], 90 + 91 + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="], 92 + 93 + "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.27.1", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA=="], 94 + 95 + "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="], 96 + 97 + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], 98 + 99 + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], 100 + 101 + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], 102 + 103 + "@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="], 104 + 105 + "@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], 106 + 107 + "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="], 108 + 109 + "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="], 110 + 111 + "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw=="], 112 + 113 + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], 114 + 115 + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], 116 + 117 + "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA=="], 118 + 119 + "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="], 120 + 121 + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], 122 + 123 + "@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], 124 + 125 + "@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="], 126 + 127 + "@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], 128 + 129 + "@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], 130 + 131 + "@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], 132 + 133 + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], 134 + 135 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], 136 + 137 + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], 138 + 139 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], 140 + 141 + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], 142 + 143 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], 144 + 145 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], 146 + 147 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], 148 + 149 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], 150 + 151 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], 152 + 153 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], 154 + 155 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], 156 + 157 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], 158 + 159 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], 160 + 161 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], 162 + 163 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], 164 + 165 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], 166 + 167 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], 168 + 169 + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], 170 + 171 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], 172 + 173 + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], 174 + 175 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], 176 + 177 + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], 178 + 179 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], 180 + 181 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], 182 + 183 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], 184 + 185 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], 186 + 187 + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], 188 + 189 + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], 190 + 191 + "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], 192 + 193 + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], 194 + 195 + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], 196 + 197 + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], 198 + 199 + "@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="], 200 + 201 + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], 202 + 203 + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], 204 + 205 + "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], 206 + 207 + "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], 208 + 209 + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.6", "", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="], 210 + 211 + "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], 212 + 213 + "@hey-api/client-axios": ["@hey-api/client-axios@0.9.1", "", { "peerDependencies": { "@hey-api/openapi-ts": "< 2", "axios": ">= 1.0.0 < 2" } }, "sha512-fvpOdnEz6tu5T2+IMNZW3g9mAZwaXavqpsvtapEZNtYxyYtQ+lQs9wJn/VPhZEvdXAXu8HPTCRpmfa0t1aRATA=="], 214 + 215 + "@hey-api/codegen-core": ["@hey-api/codegen-core@0.3.3", "", { "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-vArVDtrvdzFewu1hnjUm4jX1NBITlSCeO81EdWq676MxQbyxsGcDPAgohaSA+Wvr4HjPSvsg2/1s2zYxUtXebg=="], 216 + 217 + "@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.2.1", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0", "lodash": "^4.17.21" } }, "sha512-inPeksRLq+j3ArnuGOzQPQE//YrhezQG0+9Y9yizScBN2qatJ78fIByhEgKdNAbtguDCn4RPxmEhcrePwHxs4A=="], 218 + 219 + "@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.87.5", "", { "dependencies": { "@hey-api/codegen-core": "^0.3.3", "@hey-api/json-schema-ref-parser": "1.2.1", "ansi-colors": "4.1.3", "c12": "3.3.2", "color-support": "1.1.3", "commander": "14.0.1", "open": "10.2.0", "semver": "7.7.2" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-WtmBCfbRKzsz578haTe1bh7FJWbqGyPmZ4dD8bynvQjQ6qtXk7mmI/3Nmev70Hl41VHoEoQNEL+BulfwJiJO/g=="], 220 + 221 + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], 222 + 223 + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], 224 + 225 + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], 226 + 227 + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], 228 + 229 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 230 + 231 + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], 232 + 233 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 234 + 235 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 236 + 237 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 238 + 239 + "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="], 240 + 241 + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], 242 + 243 + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], 244 + 245 + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], 246 + 247 + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], 248 + 249 + "@oxc-project/runtime": ["@oxc-project/runtime@0.96.0", "", {}, "sha512-34lh4o9CcSw09Hx6fKihPu85+m+4pmDlkXwJrLvN5nMq5JrcGhhihVM415zDqT8j8IixO1PYYdQZRN4SwQCncg=="], 250 + 251 + "@oxc-project/types": ["@oxc-project/types@0.96.0", "", {}, "sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw=="], 252 + 253 + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], 254 + 255 + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], 256 + 257 + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], 258 + 259 + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="], 260 + 261 + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], 262 + 263 + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], 264 + 265 + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], 266 + 267 + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], 268 + 269 + "@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="], 270 + 271 + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], 272 + 273 + "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], 274 + 275 + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], 276 + 277 + "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], 278 + 279 + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], 280 + 281 + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], 282 + 283 + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], 284 + 285 + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="], 286 + 287 + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], 288 + 289 + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], 290 + 291 + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], 292 + 293 + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], 294 + 295 + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], 296 + 297 + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], 298 + 299 + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], 300 + 301 + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], 302 + 303 + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], 304 + 305 + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], 306 + 307 + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], 308 + 309 + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], 310 + 311 + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], 312 + 313 + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], 314 + 315 + "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], 316 + 317 + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], 318 + 319 + "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], 320 + 321 + "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], 322 + 323 + "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], 324 + 325 + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], 326 + 327 + "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], 328 + 329 + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.47", "", { "os": "android", "cpu": "arm64" }, "sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw=="], 330 + 331 + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.47", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw=="], 332 + 333 + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.47", "", { "os": "darwin", "cpu": "x64" }, "sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ=="], 334 + 335 + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.47", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA=="], 336 + 337 + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.47", "", { "os": "linux", "cpu": "arm" }, "sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA=="], 338 + 339 + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.47", "", { "os": "linux", "cpu": "arm64" }, "sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA=="], 340 + 341 + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.47", "", { "os": "linux", "cpu": "arm64" }, "sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ=="], 342 + 343 + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.47", "", { "os": "linux", "cpu": "x64" }, "sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ=="], 344 + 345 + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.47", "", { "os": "linux", "cpu": "x64" }, "sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ=="], 346 + 347 + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.47", "", { "os": "none", "cpu": "arm64" }, "sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w=="], 348 + 349 + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.47", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.7" }, "cpu": "none" }, "sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ=="], 350 + 351 + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.47", "", { "os": "win32", "cpu": "arm64" }, "sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w=="], 352 + 353 + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.47", "", { "os": "win32", "cpu": "ia32" }, "sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw=="], 354 + 355 + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.47", "", { "os": "win32", "cpu": "x64" }, "sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg=="], 356 + 357 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.47", "", {}, "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw=="], 358 + 359 + "@tabler/icons": ["@tabler/icons@3.35.0", "", {}, "sha512-yYXe+gJ56xlZFiXwV9zVoe3FWCGuZ/D7/G4ZIlDtGxSx5CGQK110wrnT29gUj52kEZoxqF7oURTk97GQxELOFQ=="], 360 + 361 + "@tabler/icons-react": ["@tabler/icons-react@3.35.0", "", { "dependencies": { "@tabler/icons": "3.35.0" }, "peerDependencies": { "react": ">= 16" } }, "sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g=="], 362 + 363 + "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="], 364 + 365 + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="], 366 + 367 + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="], 368 + 369 + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="], 370 + 371 + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="], 372 + 373 + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="], 374 + 375 + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="], 376 + 377 + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="], 378 + 379 + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="], 380 + 381 + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="], 382 + 383 + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="], 384 + 385 + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="], 386 + 387 + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="], 388 + 389 + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="], 390 + 391 + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.17", "", { "dependencies": { "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "tailwindcss": "4.1.17" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA=="], 392 + 393 + "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.3.5", "", {}, "sha512-RL1f5ZlfZMpghrCIdzl6mLOFLTuhqmPNblZgBaeKfdtk5rfbjykurv+VfYydOFXj0vxVIoA2d/zT7xfD7Ph8fw=="], 394 + 395 + "@tanstack/form-core": ["@tanstack/form-core@1.25.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.3.5", "@tanstack/pacer": "^0.15.3", "@tanstack/store": "^0.7.7" } }, "sha512-OEWW2uTOFMyRmHrVEiPOn+J27ekQ/vXwRAJt9kD8U8vCt8CmpClj989OOGGSBSVJtDNxGUcWyKF8gYznnqIyaw=="], 396 + 397 + "@tanstack/history": ["@tanstack/history@1.133.28", "", {}, "sha512-B7+x7eP2FFvi3fgd3rNH9o/Eixt+pp0zCIdGhnQbAJjFrlwIKGjGnwyJjhWJ5fMQlGks/E2LdDTqEV4W9Plx7g=="], 398 + 399 + "@tanstack/pacer": ["@tanstack/pacer@0.15.4", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.3.2", "@tanstack/store": "^0.7.5" } }, "sha512-vGY+CWsFZeac3dELgB6UZ4c7OacwsLb8hvL2gLS6hTgy8Fl0Bm/aLokHaeDIP+q9F9HUZTnp360z9uv78eg8pg=="], 400 + 401 + "@tanstack/query-core": ["@tanstack/query-core@5.90.10", "", {}, "sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ=="], 402 + 403 + "@tanstack/query-devtools": ["@tanstack/query-devtools@5.90.1", "", {}, "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ=="], 404 + 405 + "@tanstack/react-form": ["@tanstack/react-form@1.25.0", "", { "dependencies": { "@tanstack/form-core": "1.25.0", "@tanstack/react-store": "^0.7.7" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-SjKpBkjegNVW9WU+qlO8+/+kSbSEwo2zwHnrQz/yOnnJRhtdgubUt50LfeUtdzkMsbbptQ5MSZrXH03kidQjyw=="], 406 + 407 + "@tanstack/react-query": ["@tanstack/react-query@5.90.10", "", { "dependencies": { "@tanstack/query-core": "5.90.10" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw=="], 408 + 409 + "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.90.2", "", { "dependencies": { "@tanstack/query-devtools": "5.90.1" }, "peerDependencies": { "@tanstack/react-query": "^5.90.2", "react": "^18 || ^19" } }, "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ=="], 410 + 411 + "@tanstack/react-router": ["@tanstack/react-router@1.136.8", "", { "dependencies": { "@tanstack/history": "1.133.28", "@tanstack/react-store": "^0.8.0", "@tanstack/router-core": "1.136.8", "isbot": "^5.1.22", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" }, "peerDependencies": { "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" } }, "sha512-m9aJvQaAHSehPld5fBQX4K/e91S+JHGweJyBLH/+/fmHQnusgB+roeEwyn74Nag74sT4ErA5GwGYKE8ZLH5h3g=="], 412 + 413 + "@tanstack/react-router-devtools": ["@tanstack/react-router-devtools@1.136.8", "", { "dependencies": { "@tanstack/router-devtools-core": "1.136.8", "vite": "^7.1.7" }, "peerDependencies": { "@tanstack/react-router": "^1.136.8", "@tanstack/router-core": "^1.136.8", "react": ">=18.0.0 || >=19.0.0", "react-dom": ">=18.0.0 || >=19.0.0" }, "optionalPeers": ["@tanstack/router-core"] }, "sha512-doM/BexWfKnS8z16Ll+91/Js3msd71q70Dw5rtkX0cPUccxyRskQyHPOMH4YlMr+Pwnc7XABtkc/nZxrOP9/fQ=="], 414 + 415 + "@tanstack/react-store": ["@tanstack/react-store@0.7.7", "", { "dependencies": { "@tanstack/store": "0.7.7", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg=="], 416 + 417 + "@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="], 418 + 419 + "@tanstack/router-core": ["@tanstack/router-core@1.136.8", "", { "dependencies": { "@tanstack/history": "1.133.28", "@tanstack/store": "^0.8.0", "cookie-es": "^2.0.0", "seroval": "^1.3.2", "seroval-plugins": "^1.3.2", "tiny-invariant": "^1.3.3", "tiny-warning": "^1.0.3" } }, "sha512-qPBWbInoi9CNtcjjKaaWVjoZoE5EiM5q6KVT0qVIQq2yAI4jyR1cWqyGMZp9tWNpFrlrUoG6kDvX9GRlstORJw=="], 420 + 421 + "@tanstack/router-devtools-core": ["@tanstack/router-devtools-core@1.136.8", "", { "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", "tiny-invariant": "^1.3.3", "vite": "^7.1.7" }, "peerDependencies": { "@tanstack/router-core": "^1.136.8", "csstype": "^3.0.10", "solid-js": ">=1.9.5" }, "optionalPeers": ["csstype"] }, "sha512-3HM5OrxcMotm/G75+EtDYqWetKeedHHBZbUvrmL7o7QV6cGc5BcYJy6HV6+d8oLOFRdrjp3MZipf00XoFdnZBQ=="], 422 + 423 + "@tanstack/router-generator": ["@tanstack/router-generator@1.136.8", "", { "dependencies": { "@tanstack/router-core": "1.136.8", "@tanstack/router-utils": "1.133.19", "@tanstack/virtual-file-routes": "1.133.19", "prettier": "^3.5.0", "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", "zod": "^3.24.2" } }, "sha512-Zd5Zj7db6JPR7tDo0po9ktH9u6nIRqVWMeawzllMcoDNqtsmvxecJBcbiRlm/855ZiQgtQRJ4dAwAgOc3r1VOw=="], 424 + 425 + "@tanstack/router-plugin": ["@tanstack/router-plugin@1.136.8", "", { "dependencies": { "@babel/core": "^7.27.7", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/template": "^7.27.2", "@babel/traverse": "^7.27.7", "@babel/types": "^7.27.7", "@tanstack/router-core": "1.136.8", "@tanstack/router-generator": "1.136.8", "@tanstack/router-utils": "1.133.19", "@tanstack/virtual-file-routes": "1.133.19", "babel-dead-code-elimination": "^1.0.10", "chokidar": "^3.6.0", "unplugin": "^2.1.2", "zod": "^3.24.2" }, "peerDependencies": { "@rsbuild/core": ">=1.0.2", "@tanstack/react-router": "^1.136.8", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0", "vite-plugin-solid": "^2.11.10", "webpack": ">=5.92.0" }, "optionalPeers": ["@rsbuild/core", "@tanstack/react-router", "vite", "vite-plugin-solid", "webpack"] }, "sha512-7YmbYMUYgnH/G6tE6BzP+zTwymTwC0ESS0TzMrME6hybvZpSvaaQaaVkPoxqy0+onHWG5h1szB+NZo6JWBnX2A=="], 426 + 427 + "@tanstack/router-utils": ["@tanstack/router-utils@1.133.19", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/parser": "^7.27.5", "@babel/preset-typescript": "^7.27.1", "ansis": "^4.1.0", "diff": "^8.0.2", "pathe": "^2.0.3", "tinyglobby": "^0.2.15" } }, "sha512-WEp5D2gPxvlLDRXwD/fV7RXjYtqaqJNXKB/L6OyZEbT+9BG/Ib2d7oG9GSUZNNMGPGYAlhBUOi3xutySsk6rxA=="], 428 + 429 + "@tanstack/store": ["@tanstack/store@0.7.7", "", {}, "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ=="], 430 + 431 + "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], 432 + 433 + "@tanstack/virtual-file-routes": ["@tanstack/virtual-file-routes@1.133.19", "", {}, "sha512-IKwZENsK7owmW1Lm5FhuHegY/SyQ8KqtL/7mTSnzoKJgfzhrrf9qwKB1rmkKkt+svUuy/Zw3uVEpZtUzQruWtA=="], 434 + 435 + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 436 + 437 + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], 438 + 439 + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], 440 + 441 + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], 442 + 443 + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], 444 + 445 + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], 446 + 447 + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], 448 + 449 + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], 450 + 451 + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], 452 + 453 + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], 454 + 455 + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], 456 + 457 + "@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="], 458 + 459 + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], 460 + 461 + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], 462 + 463 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 464 + 465 + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], 466 + 467 + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], 468 + 469 + "@types/react": ["@types/react@19.2.5", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw=="], 470 + 471 + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], 472 + 473 + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.4", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/type-utils": "8.46.4", "@typescript-eslint/utils": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.4", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg=="], 474 + 475 + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.4", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w=="], 476 + 477 + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.4", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.4", "@typescript-eslint/types": "^8.46.4", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ=="], 478 + 479 + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4" } }, "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA=="], 480 + 481 + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.4", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A=="], 482 + 483 + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/utils": "8.46.4", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ=="], 484 + 485 + "@typescript-eslint/types": ["@typescript-eslint/types@8.46.4", "", {}, "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w=="], 486 + 487 + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.4", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.4", "@typescript-eslint/tsconfig-utils": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/visitor-keys": "8.46.4", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA=="], 488 + 489 + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.46.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/types": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg=="], 490 + 491 + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.4", "", { "dependencies": { "@typescript-eslint/types": "8.46.4", "eslint-visitor-keys": "^4.2.1" } }, "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw=="], 492 + 493 + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.1", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.47", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA=="], 494 + 495 + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], 496 + 497 + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], 498 + 499 + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], 500 + 501 + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], 502 + 503 + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], 504 + 505 + "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], 506 + 507 + "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], 508 + 509 + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 510 + 511 + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], 512 + 513 + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], 514 + 515 + "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], 516 + 517 + "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], 518 + 519 + "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.10", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA=="], 520 + 521 + "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="], 522 + 523 + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 524 + 525 + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.28", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ=="], 526 + 527 + "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], 528 + 529 + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], 530 + 531 + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 532 + 533 + "browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="], 534 + 535 + "bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="], 536 + 537 + "c12": ["c12@3.3.2", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-QkikB2X5voO1okL3QsES0N690Sn/K9WokXqUsDQsWy5SnYb+psYQFGA10iy1bZHj3fjISKsI67Q90gruvWWM3A=="], 538 + 539 + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], 540 + 541 + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], 542 + 543 + "caniuse-lite": ["caniuse-lite@1.0.30001755", "", {}, "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA=="], 544 + 545 + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 546 + 547 + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], 548 + 549 + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], 550 + 551 + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], 552 + 553 + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], 554 + 555 + "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], 556 + 557 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 558 + 559 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 560 + 561 + "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], 562 + 563 + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], 564 + 565 + "commander": ["commander@14.0.1", "", {}, "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A=="], 566 + 567 + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], 568 + 569 + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], 570 + 571 + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], 572 + 573 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 574 + 575 + "cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="], 576 + 577 + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], 578 + 579 + "csstype": ["csstype@3.2.2", "", {}, "sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g=="], 580 + 581 + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], 582 + 583 + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], 584 + 585 + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], 586 + 587 + "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="], 588 + 589 + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], 590 + 591 + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], 592 + 593 + "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], 594 + 595 + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], 596 + 597 + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], 598 + 599 + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], 600 + 601 + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], 602 + 603 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 604 + 605 + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], 606 + 607 + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], 608 + 609 + "default-browser": ["default-browser@5.4.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg=="], 610 + 611 + "default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="], 612 + 613 + "define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="], 614 + 615 + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], 616 + 617 + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], 618 + 619 + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], 620 + 621 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 622 + 623 + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], 624 + 625 + "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], 626 + 627 + "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], 628 + 629 + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], 630 + 631 + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], 632 + 633 + "electron-to-chromium": ["electron-to-chromium@1.5.254", "", {}, "sha512-DcUsWpVhv9svsKRxnSCZ86SjD+sp32SGidNB37KpqXJncp1mfUgKbHvBomE89WJDbfVKw1mdv5+ikrvd43r+Bg=="], 634 + 635 + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], 636 + 637 + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 638 + 639 + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], 640 + 641 + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 642 + 643 + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], 644 + 645 + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], 646 + 647 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 648 + 649 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 650 + 651 + "eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="], 652 + 653 + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], 654 + 655 + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="], 656 + 657 + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], 658 + 659 + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], 660 + 661 + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], 662 + 663 + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], 664 + 665 + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], 666 + 667 + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], 668 + 669 + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], 670 + 671 + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], 672 + 673 + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], 674 + 675 + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], 676 + 677 + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], 678 + 679 + "fast-equals": ["fast-equals@5.3.3", "", {}, "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw=="], 680 + 681 + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], 682 + 683 + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], 684 + 685 + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], 686 + 687 + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], 688 + 689 + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 690 + 691 + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], 692 + 693 + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 694 + 695 + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 696 + 697 + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 698 + 699 + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], 700 + 701 + "follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="], 702 + 703 + "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], 704 + 705 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 706 + 707 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 708 + 709 + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 710 + 711 + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], 712 + 713 + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], 714 + 715 + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], 716 + 717 + "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], 718 + 719 + "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], 720 + 721 + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], 722 + 723 + "globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], 724 + 725 + "goober": ["goober@2.1.18", "", { "peerDependencies": { "csstype": "^3.0.10" } }, "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw=="], 726 + 727 + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], 728 + 729 + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 730 + 731 + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], 732 + 733 + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 734 + 735 + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 736 + 737 + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], 738 + 739 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 740 + 741 + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], 742 + 743 + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], 744 + 745 + "html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg=="], 746 + 747 + "i18next": ["i18next@25.6.2", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-0GawNyVUw0yvJoOEBq1VHMAsqdM23XrHkMtl2gKEjviJQSLVXsrPqsoYAxBEugW5AB96I2pZkwRxyl8WZVoWdw=="], 748 + 749 + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], 750 + 751 + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], 752 + 753 + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], 754 + 755 + "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], 756 + 757 + "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], 758 + 759 + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], 760 + 761 + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 762 + 763 + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], 764 + 765 + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], 766 + 767 + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], 768 + 769 + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], 770 + 771 + "isbot": ["isbot@5.1.32", "", {}, "sha512-VNfjM73zz2IBZmdShMfAUg10prm6t7HFUQmNAEOAVS4YH92ZrZcvkMcGX6cIgBJAzWDzPent/EeAtYEHNPNPBQ=="], 772 + 773 + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], 774 + 775 + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], 776 + 777 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 778 + 779 + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], 780 + 781 + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], 782 + 783 + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], 784 + 785 + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], 786 + 787 + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], 788 + 789 + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], 790 + 791 + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], 792 + 793 + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], 794 + 795 + "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], 796 + 797 + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], 798 + 799 + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], 800 + 801 + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], 802 + 803 + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], 804 + 805 + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], 806 + 807 + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], 808 + 809 + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], 810 + 811 + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], 812 + 813 + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], 814 + 815 + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], 816 + 817 + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], 818 + 819 + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], 820 + 821 + "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], 822 + 823 + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], 824 + 825 + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], 826 + 827 + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 828 + 829 + "lucide-react": ["lucide-react@0.554.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA=="], 830 + 831 + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], 832 + 833 + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 834 + 835 + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], 836 + 837 + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], 838 + 839 + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 840 + 841 + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 842 + 843 + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], 844 + 845 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 846 + 847 + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 848 + 849 + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], 850 + 851 + "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], 852 + 853 + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], 854 + 855 + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], 856 + 857 + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], 858 + 859 + "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], 860 + 861 + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], 862 + 863 + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], 864 + 865 + "open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], 866 + 867 + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], 868 + 869 + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], 870 + 871 + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], 872 + 873 + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], 874 + 875 + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], 876 + 877 + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 878 + 879 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 880 + 881 + "perfect-debounce": ["perfect-debounce@2.0.0", "", {}, "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow=="], 882 + 883 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 884 + 885 + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 886 + 887 + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], 888 + 889 + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], 890 + 891 + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], 892 + 893 + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], 894 + 895 + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], 896 + 897 + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], 898 + 899 + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 900 + 901 + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], 902 + 903 + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], 904 + 905 + "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], 906 + 907 + "react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="], 908 + 909 + "react-i18next": ["react-i18next@16.3.3", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 25.6.2", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-IaY2W+ueVd/fe7H6Wj2S4bTuLNChnajFUlZFfCTrTHWzGcOrUHlVzW55oXRSl+J51U8Onn6EvIhQ+Bar9FUcjw=="], 910 + 911 + "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], 912 + 913 + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], 914 + 915 + "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], 916 + 917 + "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], 918 + 919 + "react-smooth": ["react-smooth@4.0.4", "", { "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q=="], 920 + 921 + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], 922 + 923 + "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], 924 + 925 + "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], 926 + 927 + "recast": ["recast@0.23.11", "", { "dependencies": { "ast-types": "^0.16.1", "esprima": "~4.0.0", "source-map": "~0.6.1", "tiny-invariant": "^1.3.3", "tslib": "^2.0.1" } }, "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA=="], 928 + 929 + "recharts": ["recharts@2.15.4", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw=="], 930 + 931 + "recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="], 932 + 933 + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], 934 + 935 + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 936 + 937 + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], 938 + 939 + "rolldown": ["rolldown@1.0.0-beta.47", "", { "dependencies": { "@oxc-project/types": "=0.96.0", "@rolldown/pluginutils": "1.0.0-beta.47" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.47", "@rolldown/binding-darwin-arm64": "1.0.0-beta.47", "@rolldown/binding-darwin-x64": "1.0.0-beta.47", "@rolldown/binding-freebsd-x64": "1.0.0-beta.47", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.47", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.47", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.47", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.47", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.47", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.47", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.47", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.47", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.47", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.47" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg=="], 940 + 941 + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], 942 + 943 + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], 944 + 945 + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], 946 + 947 + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], 948 + 949 + "seroval": ["seroval@1.4.0", "", {}, "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg=="], 950 + 951 + "seroval-plugins": ["seroval-plugins@1.4.0", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-zir1aWzoiax6pbBVjoYVd0O1QQXgIL3eVGBMsBsNmM8Ukq90yGaWlfx0AB9dTS8GPqrOrbXn79vmItCUP9U3BQ=="], 952 + 953 + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], 954 + 955 + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], 956 + 957 + "solid-js": ["solid-js@1.9.10", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew=="], 958 + 959 + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], 960 + 961 + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], 962 + 963 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 964 + 965 + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], 966 + 967 + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 968 + 969 + "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], 970 + 971 + "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], 972 + 973 + "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], 974 + 975 + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], 976 + 977 + "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], 978 + 979 + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], 980 + 981 + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 982 + 983 + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], 984 + 985 + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], 986 + 987 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 988 + 989 + "tsx": ["tsx@4.20.6", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg=="], 990 + 991 + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], 992 + 993 + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], 994 + 995 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 996 + 997 + "typescript-eslint": ["typescript-eslint@8.46.4", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.4", "@typescript-eslint/parser": "8.46.4", "@typescript-eslint/typescript-estree": "8.46.4", "@typescript-eslint/utils": "8.46.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg=="], 998 + 999 + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 1000 + 1001 + "unplugin": ["unplugin@2.3.10", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw=="], 1002 + 1003 + "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], 1004 + 1005 + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], 1006 + 1007 + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], 1008 + 1009 + "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], 1010 + 1011 + "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], 1012 + 1013 + "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], 1014 + 1015 + "vite": ["rolldown-vite@7.2.2", "", { "dependencies": { "@oxc-project/runtime": "0.96.0", "fdir": "^6.5.0", "lightningcss": "^1.30.2", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.47", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.25.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Fl3ZdmJhDMJGcqrr342pPVrhugXdOcuNBRBauz4S7QGSRXbQy7y8q5QYJtgkcrG8XjY0EENSZeTk58c3m20FxA=="], 1016 + 1017 + "void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="], 1018 + 1019 + "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], 1020 + 1021 + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 1022 + 1023 + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], 1024 + 1025 + "wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="], 1026 + 1027 + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 1028 + 1029 + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], 1030 + 1031 + "zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], 1032 + 1033 + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], 1034 + 1035 + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 1036 + 1037 + "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 1038 + 1039 + "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 1040 + 1041 + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], 1042 + 1043 + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], 1044 + 1045 + "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1046 + 1047 + "@radix-ui/react-checkbox/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1048 + 1049 + "@radix-ui/react-checkbox/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1050 + 1051 + "@radix-ui/react-collapsible/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1052 + 1053 + "@radix-ui/react-collapsible/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1054 + 1055 + "@radix-ui/react-collection/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1056 + 1057 + "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1058 + 1059 + "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1060 + 1061 + "@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1062 + 1063 + "@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1064 + 1065 + "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1066 + 1067 + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1068 + 1069 + "@radix-ui/react-dropdown-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1070 + 1071 + "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1072 + 1073 + "@radix-ui/react-focus-scope/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1074 + 1075 + "@radix-ui/react-menu/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1076 + 1077 + "@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1078 + 1079 + "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1080 + 1081 + "@radix-ui/react-popper/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1082 + 1083 + "@radix-ui/react-popper/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1084 + 1085 + "@radix-ui/react-portal/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1086 + 1087 + "@radix-ui/react-roving-focus/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1088 + 1089 + "@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1090 + 1091 + "@radix-ui/react-select/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1092 + 1093 + "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1094 + 1095 + "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1096 + 1097 + "@radix-ui/react-tooltip/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], 1098 + 1099 + "@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1100 + 1101 + "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1102 + 1103 + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], 1104 + 1105 + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="], 1106 + 1107 + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="], 1108 + 1109 + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], 1110 + 1111 + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], 1112 + 1113 + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 1114 + 1115 + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 1116 + 1117 + "@tanstack/react-router/@tanstack/react-store": ["@tanstack/react-store@0.8.0", "", { "dependencies": { "@tanstack/store": "0.8.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow=="], 1118 + 1119 + "@tanstack/router-core/@tanstack/store": ["@tanstack/store@0.8.0", "", {}, "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ=="], 1120 + 1121 + "@tanstack/router-generator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 1122 + 1123 + "@tanstack/router-plugin/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 1124 + 1125 + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 1126 + 1127 + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], 1128 + 1129 + "@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 1130 + 1131 + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 1132 + 1133 + "c12/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], 1134 + 1135 + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 1136 + 1137 + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], 1138 + 1139 + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 1140 + 1141 + "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], 1142 + 1143 + "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], 1144 + 1145 + "recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], 1146 + 1147 + "solid-js/seroval": ["seroval@1.3.2", "", {}, "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="], 1148 + 1149 + "solid-js/seroval-plugins": ["seroval-plugins@1.3.3", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w=="], 1150 + 1151 + "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1152 + 1153 + "@radix-ui/react-checkbox/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1154 + 1155 + "@radix-ui/react-collapsible/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1156 + 1157 + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1158 + 1159 + "@radix-ui/react-dropdown-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1160 + 1161 + "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1162 + 1163 + "@radix-ui/react-popper/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1164 + 1165 + "@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1166 + 1167 + "@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1168 + 1169 + "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], 1170 + 1171 + "@tanstack/react-router/@tanstack/react-store/@tanstack/store": ["@tanstack/store@0.8.0", "", {}, "sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ=="], 1172 + 1173 + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 1174 + 1175 + "c12/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], 1176 + } 1177 + }
+24
src/Den.Client.Web/components.json
··· 1 + { 2 + "$schema": "https://ui.shadcn.com/schema.json", 3 + "style": "new-york", 4 + "rsc": false, 5 + "tsx": true, 6 + "tailwind": { 7 + "config": "", 8 + "css": "src/index.css", 9 + "baseColor": "neutral", 10 + "cssVariables": true, 11 + "prefix": "" 12 + }, 13 + "iconLibrary": "lucide", 14 + "aliases": { 15 + "components": "@/components", 16 + "utils": "@/lib/utils", 17 + "ui": "@/components/ui", 18 + "lib": "@/lib", 19 + "hooks": "@/hooks" 20 + }, 21 + "registries": { 22 + "@blocks": "https://blocks.so/r/{name}.json" 23 + } 24 + }
+23
src/Den.Client.Web/eslint.config.js
··· 1 + import js from '@eslint/js' 2 + import globals from 'globals' 3 + import reactHooks from 'eslint-plugin-react-hooks' 4 + import reactRefresh from 'eslint-plugin-react-refresh' 5 + import tseslint from 'typescript-eslint' 6 + import { defineConfig, globalIgnores } from 'eslint/config' 7 + 8 + export default defineConfig([ 9 + globalIgnores(['dist']), 10 + { 11 + files: ['**/*.{ts,tsx}'], 12 + extends: [ 13 + js.configs.recommended, 14 + tseslint.configs.recommended, 15 + reactHooks.configs.flat.recommended, 16 + reactRefresh.configs.vite, 17 + ], 18 + languageOptions: { 19 + ecmaVersion: 2020, 20 + globals: globals.browser, 21 + }, 22 + }, 23 + ])
+13
src/Den.Client.Web/index.html
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>Den</title> 8 + </head> 9 + <body> 10 + <div id="root"></div> 11 + <script type="module" src="/src/main.tsx"></script> 12 + </body> 13 + </html>
+7
src/Den.Client.Web/openapi-ts.config.ts
··· 1 + import { defineConfig } from '@hey-api/openapi-ts'; 2 + 3 + export default defineConfig({ 4 + input: 'http://localhost:8080/api/openapi/v1.json', 5 + output: 'src/lib/state/client', 6 + plugins: ['@hey-api/client-axios', '@tanstack/react-query'], 7 + });
+70
src/Den.Client.Web/package.json
··· 1 + { 2 + "name": "@roostmoe/den-client-web", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite --host", 8 + "build": "tsc -b && vite build", 9 + "lint": "eslint .", 10 + "preview": "vite preview", 11 + "gen-client": "openapi-ts" 12 + }, 13 + "dependencies": { 14 + "@radix-ui/react-avatar": "^1.1.11", 15 + "@radix-ui/react-checkbox": "^1.3.3", 16 + "@radix-ui/react-collapsible": "^1.1.12", 17 + "@radix-ui/react-dialog": "^1.1.15", 18 + "@radix-ui/react-dropdown-menu": "^2.1.16", 19 + "@radix-ui/react-label": "^2.1.8", 20 + "@radix-ui/react-select": "^2.2.6", 21 + "@radix-ui/react-separator": "^1.1.8", 22 + "@radix-ui/react-slot": "^1.2.4", 23 + "@radix-ui/react-tooltip": "^1.2.8", 24 + "@tabler/icons-react": "^3.35.0", 25 + "@tailwindcss/vite": "^4.1.17", 26 + "@tanstack/react-form": "^1.25.0", 27 + "@tanstack/react-query": "^5.90.10", 28 + "@tanstack/react-query-devtools": "^5.90.2", 29 + "@tanstack/react-router": "^1.136.8", 30 + "@tanstack/react-router-devtools": "^1.136.8", 31 + "@tanstack/react-table": "^8.21.3", 32 + "@tanstack/router-plugin": "^1.136.8", 33 + "axios": "^1.13.2", 34 + "class-variance-authority": "^0.7.1", 35 + "clsx": "^2.1.1", 36 + "cmdk": "^1.1.1", 37 + "i18next": "^25.6.2", 38 + "lucide-react": "^0.554.0", 39 + "next-themes": "^0.4.6", 40 + "react": "^19.2.0", 41 + "react-dom": "^19.2.0", 42 + "react-i18next": "^16.3.3", 43 + "recharts": "2.15.4", 44 + "sonner": "^2.0.7", 45 + "tailwind-merge": "^3.4.0", 46 + "tailwindcss": "^4.1.17", 47 + "zod": "^4.1.12" 48 + }, 49 + "devDependencies": { 50 + "@eslint/js": "^9.39.1", 51 + "@hey-api/client-axios": "^0.9.1", 52 + "@hey-api/openapi-ts": "0.87.5", 53 + "@types/node": "^24.10.0", 54 + "@types/react": "^19.2.2", 55 + "@types/react-dom": "^19.2.2", 56 + "@vitejs/plugin-react": "^5.1.0", 57 + "babel-plugin-react-compiler": "^1.0.0", 58 + "eslint": "^9.39.1", 59 + "eslint-plugin-react-hooks": "^7.0.1", 60 + "eslint-plugin-react-refresh": "^0.4.24", 61 + "globals": "^16.5.0", 62 + "tw-animate-css": "^1.4.0", 63 + "typescript": "~5.9.3", 64 + "typescript-eslint": "^8.46.3", 65 + "vite": "npm:rolldown-vite@7.2.2" 66 + }, 67 + "overrides": { 68 + "vite": "npm:rolldown-vite@7.2.2" 69 + } 70 + }
+256
src/Den.Client.Web/src/components/app-sidebar.tsx
··· 1 + import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarHeader, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub, SidebarMenuSubItem } from "@/components/ui/sidebar"; 2 + import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; 3 + import { CalendarIcon, ChevronUpIcon, CirclePoundSterlingIcon, CogIcon, Home, HomeIcon, LightbulbIcon, ShoppingBasketIcon, User2Icon, UsersIcon, UtensilsIcon, type LucideIcon } from "lucide-react"; 4 + import { DropdownMenu, DropdownMenuItem, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator } from "@/components/ui/dropdown-menu"; 5 + import { Skeleton } from "@/components/ui/skeleton"; 6 + import { useMeQuery } from "@/lib/state/queries/auth"; 7 + import { t } from "i18next"; 8 + import { Link } from "@tanstack/react-router"; 9 + import { useAuth } from "@/lib/state/auth"; 10 + 11 + type MenuItem = { 12 + type: 'group'; 13 + title: string; 14 + children: MenuItem[]; 15 + } | { 16 + type: 'submenu'; 17 + title: string; 18 + icon?: LucideIcon; 19 + defaultOpen?: boolean; 20 + children: MenuItem[]; 21 + } | { 22 + type: 'link'; 23 + title: string; 24 + url: string; 25 + badge?: string | number; 26 + icon?: LucideIcon; 27 + }; 28 + 29 + const items = [ 30 + { 31 + type: 'group', 32 + title: t('menu.home.title'), 33 + children: [ 34 + { 35 + type: 'link', 36 + title: t('menu.home.dashboard'), 37 + url: '/', 38 + icon: Home 39 + } 40 + ], 41 + }, 42 + 43 + { 44 + type: 'group', 45 + title: t('menu.organisation.title'), 46 + children: [ 47 + { 48 + type: 'link', 49 + title: t('menu.organisation.groceries'), 50 + url: '#', 51 + icon: ShoppingBasketIcon, 52 + badge: 15, 53 + }, 54 + { 55 + type: 'link', 56 + title: t('menu.organisation.calendar'), 57 + url: '#', 58 + icon: CalendarIcon, 59 + badge: 3, 60 + }, 61 + { 62 + type: 'link', 63 + title: t('menu.organisation.reminders'), 64 + url: '#', 65 + icon: LightbulbIcon, 66 + badge: 10, 67 + }, 68 + { 69 + type: 'link', 70 + title: t('menu.organisation.recipes'), 71 + url: '#', 72 + icon: UtensilsIcon, 73 + badge: 56 74 + }, 75 + ], 76 + }, 77 + 78 + { 79 + type: 'group', 80 + title: t('menu.budgeting.title'), 81 + children: [ 82 + { 83 + type: 'submenu', 84 + title: t('menu.budgeting.budgets'), 85 + icon: CirclePoundSterlingIcon, 86 + children: [ 87 + { 88 + type: 'link', 89 + title: 'Household', 90 + url: '#', 91 + }, 92 + { 93 + type: 'link', 94 + title: 'Grocery', 95 + url: '#', 96 + }, 97 + { 98 + type: 'link', 99 + title: 'Bills & Services', 100 + url: '#', 101 + }, 102 + { 103 + type: 'link', 104 + title: 'Disposable Income', 105 + url: '#', 106 + }, 107 + { 108 + type: 'link', 109 + title: t('menu.budgeting.allBudgets'), 110 + url: '/budgets', 111 + }, 112 + ] 113 + }, 114 + ], 115 + }, 116 + 117 + { 118 + type: 'group', 119 + title: t('menu.admin.title'), 120 + children: [ 121 + { 122 + type: 'link', 123 + title: t('menu.admin.settings'), 124 + url: '#', 125 + icon: CogIcon, 126 + }, 127 + { 128 + type: 'link', 129 + title: t('menu.admin.users'), 130 + url: '#', 131 + icon: UsersIcon, 132 + }, 133 + ], 134 + }, 135 + ] as MenuItem[]; 136 + 137 + export const AppSidebar = () => { 138 + const { logout } = useAuth(); 139 + const { data, isLoading } = useMeQuery(); 140 + 141 + return ( 142 + <Sidebar> 143 + <SidebarHeader> 144 + <SidebarMenu> 145 + <SidebarMenuItem> 146 + <SidebarMenuButton size="lg"> 147 + <div className="flex items-center gap-3"> 148 + <div className="size-8 bg-primary flex items-center justify-center rounded-md"> 149 + <HomeIcon /> 150 + </div> 151 + <span>Den</span> 152 + </div> 153 + </SidebarMenuButton> 154 + </SidebarMenuItem> 155 + </SidebarMenu> 156 + </SidebarHeader> 157 + 158 + <SidebarContent> 159 + <SidebarIterator itemList={items} /> 160 + </SidebarContent> 161 + <SidebarFooter> 162 + <SidebarMenu> 163 + <SidebarMenuItem> 164 + {isLoading && !data 165 + ? (<Skeleton />) 166 + : ( 167 + <DropdownMenu> 168 + <DropdownMenuTrigger asChild> 169 + <SidebarMenuButton> 170 + <User2Icon /> {data?.username} 171 + <ChevronUpIcon className="ml-auto" /> 172 + </SidebarMenuButton> 173 + </DropdownMenuTrigger> 174 + <DropdownMenuContent side="top" className="w-[--radix-popper-anchor-width]"> 175 + <DropdownMenuLabel>Account</DropdownMenuLabel> 176 + <DropdownMenuItem> 177 + <span>Profile</span> 178 + </DropdownMenuItem> 179 + <DropdownMenuItem> 180 + <span>Settings</span> 181 + </DropdownMenuItem> 182 + <DropdownMenuSeparator /> 183 + <DropdownMenuItem 184 + className="cursor-pointer" 185 + onClick={e => { e.preventDefault(); logout() }} 186 + > 187 + <span>Log out</span> 188 + </DropdownMenuItem> 189 + </DropdownMenuContent> 190 + </DropdownMenu> 191 + ) 192 + } 193 + </SidebarMenuItem> 194 + </SidebarMenu> 195 + </SidebarFooter> 196 + </Sidebar> 197 + ); 198 + }; 199 + 200 + export const SidebarIterator = ({ parent, itemList }: { parent?: MenuItem, itemList: typeof items }) => { 201 + return itemList.map((item) => { 202 + switch (item.type) { 203 + case 'group': 204 + return ( 205 + <SidebarGroup key={item.title}> 206 + <SidebarGroupLabel>{item.title}</SidebarGroupLabel> 207 + <SidebarGroupContent> 208 + <SidebarMenu> 209 + <SidebarIterator parent={item} itemList={item.children} /> 210 + </SidebarMenu> 211 + </SidebarGroupContent> 212 + </SidebarGroup> 213 + ); 214 + 215 + case 'submenu': 216 + return ( 217 + <Collapsible key={item.title} defaultOpen={item.defaultOpen == null ? false : item.defaultOpen}> 218 + <SidebarMenuItem> 219 + <CollapsibleTrigger asChild> 220 + <SidebarMenuButton> 221 + {item.icon && <item.icon />} 222 + <span>{item.title}</span> 223 + </SidebarMenuButton> 224 + </CollapsibleTrigger> 225 + <CollapsibleContent> 226 + <SidebarMenuSub> 227 + <SidebarIterator parent={item} itemList={item.children} /> 228 + </SidebarMenuSub> 229 + </CollapsibleContent> 230 + </SidebarMenuItem> 231 + </Collapsible> 232 + ); 233 + 234 + case 'link': 235 + let MenuItemComponent = SidebarMenuItem; 236 + if (parent && parent.type == 'submenu') { 237 + MenuItemComponent = SidebarMenuSubItem; 238 + } 239 + 240 + return ( 241 + <MenuItemComponent key={item.title}> 242 + <SidebarMenuButton asChild> 243 + <Link {...item.url.startsWith('http') ? { href: item.url } : { to: item.url }}> 244 + {item.icon && <item.icon />} 245 + <span>{item.title}</span> 246 + </Link> 247 + </SidebarMenuButton> 248 + {item.badge && <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>} 249 + </MenuItemComponent> 250 + ); 251 + 252 + default: 253 + throw new Error('Invalid list item type.'); 254 + } 255 + }); 256 + };
+73
src/Den.Client.Web/src/components/theme-provider.tsx
··· 1 + import { createContext, useContext, useEffect, useState } from "react" 2 + 3 + type Theme = "dark" | "light" | "system" 4 + 5 + type ThemeProviderProps = { 6 + children: React.ReactNode 7 + defaultTheme?: Theme 8 + storageKey?: string 9 + } 10 + 11 + type ThemeProviderState = { 12 + theme: Theme 13 + setTheme: (theme: Theme) => void 14 + } 15 + 16 + const initialState: ThemeProviderState = { 17 + theme: "system", 18 + setTheme: () => null, 19 + } 20 + 21 + const ThemeProviderContext = createContext<ThemeProviderState>(initialState) 22 + 23 + export function ThemeProvider({ 24 + children, 25 + defaultTheme = "system", 26 + storageKey = "vite-ui-theme", 27 + ...props 28 + }: ThemeProviderProps) { 29 + const [theme, setTheme] = useState<Theme>( 30 + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme 31 + ) 32 + 33 + useEffect(() => { 34 + const root = window.document.documentElement 35 + 36 + root.classList.remove("light", "dark") 37 + 38 + if (theme === "system") { 39 + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") 40 + .matches 41 + ? "dark" 42 + : "light" 43 + 44 + root.classList.add(systemTheme) 45 + return 46 + } 47 + 48 + root.classList.add(theme) 49 + }, [theme]) 50 + 51 + const value = { 52 + theme, 53 + setTheme: (theme: Theme) => { 54 + localStorage.setItem(storageKey, theme) 55 + setTheme(theme) 56 + }, 57 + } 58 + 59 + return ( 60 + <ThemeProviderContext.Provider {...props} value={value}> 61 + {children} 62 + </ThemeProviderContext.Provider> 63 + ) 64 + } 65 + 66 + export const useTheme = () => { 67 + const context = useContext(ThemeProviderContext) 68 + 69 + if (context === undefined) 70 + throw new Error("useTheme must be used within a ThemeProvider") 71 + 72 + return context 73 + }
+44
src/Den.Client.Web/src/components/types.ts
··· 1 + import type { ElementType } from "react"; 2 + 3 + export interface NavItem { 4 + id: string; 5 + title: string; 6 + icon: ElementType; 7 + url?: string; 8 + isActive?: boolean; 9 + } 10 + 11 + export interface User { 12 + name: string; 13 + email: string; 14 + avatar: string; 15 + } 16 + 17 + export interface FavoriteItem { 18 + id: string; 19 + title: string; 20 + href: string; 21 + color: string; 22 + } 23 + 24 + export interface TeamItem { 25 + id: string; 26 + title: string; 27 + icon: ElementType; 28 + } 29 + 30 + export interface TopicItem { 31 + id: string; 32 + title: string; 33 + icon: ElementType; 34 + } 35 + 36 + export interface SidebarData { 37 + user: User; 38 + navMain: NavItem[]; 39 + navCollapsible: { 40 + favorites: FavoriteItem[]; 41 + teams: TeamItem[]; 42 + topics: TopicItem[]; 43 + }; 44 + }
+51
src/Den.Client.Web/src/components/ui/avatar.tsx
··· 1 + import * as React from "react" 2 + import * as AvatarPrimitive from "@radix-ui/react-avatar" 3 + 4 + import { cn } from "@/lib/utils" 5 + 6 + function Avatar({ 7 + className, 8 + ...props 9 + }: React.ComponentProps<typeof AvatarPrimitive.Root>) { 10 + return ( 11 + <AvatarPrimitive.Root 12 + data-slot="avatar" 13 + className={cn( 14 + "relative flex size-8 shrink-0 overflow-hidden rounded-full", 15 + className 16 + )} 17 + {...props} 18 + /> 19 + ) 20 + } 21 + 22 + function AvatarImage({ 23 + className, 24 + ...props 25 + }: React.ComponentProps<typeof AvatarPrimitive.Image>) { 26 + return ( 27 + <AvatarPrimitive.Image 28 + data-slot="avatar-image" 29 + className={cn("aspect-square size-full", className)} 30 + {...props} 31 + /> 32 + ) 33 + } 34 + 35 + function AvatarFallback({ 36 + className, 37 + ...props 38 + }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { 39 + return ( 40 + <AvatarPrimitive.Fallback 41 + data-slot="avatar-fallback" 42 + className={cn( 43 + "bg-muted flex size-full items-center justify-center rounded-full", 44 + className 45 + )} 46 + {...props} 47 + /> 48 + ) 49 + } 50 + 51 + export { Avatar, AvatarImage, AvatarFallback }
+109
src/Den.Client.Web/src/components/ui/breadcrumb.tsx
··· 1 + import * as React from "react" 2 + import { Slot } from "@radix-ui/react-slot" 3 + import { ChevronRight, MoreHorizontal } from "lucide-react" 4 + 5 + import { cn } from "@/lib/utils" 6 + 7 + function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { 8 + return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} /> 9 + } 10 + 11 + function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) { 12 + return ( 13 + <ol 14 + data-slot="breadcrumb-list" 15 + className={cn( 16 + "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5", 17 + className 18 + )} 19 + {...props} 20 + /> 21 + ) 22 + } 23 + 24 + function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) { 25 + return ( 26 + <li 27 + data-slot="breadcrumb-item" 28 + className={cn("inline-flex items-center gap-1.5", className)} 29 + {...props} 30 + /> 31 + ) 32 + } 33 + 34 + function BreadcrumbLink({ 35 + asChild, 36 + className, 37 + ...props 38 + }: React.ComponentProps<"a"> & { 39 + asChild?: boolean 40 + }) { 41 + const Comp = asChild ? Slot : "a" 42 + 43 + return ( 44 + <Comp 45 + data-slot="breadcrumb-link" 46 + className={cn("hover:text-foreground transition-colors", className)} 47 + {...props} 48 + /> 49 + ) 50 + } 51 + 52 + function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) { 53 + return ( 54 + <span 55 + data-slot="breadcrumb-page" 56 + role="link" 57 + aria-disabled="true" 58 + aria-current="page" 59 + className={cn("text-foreground font-normal", className)} 60 + {...props} 61 + /> 62 + ) 63 + } 64 + 65 + function BreadcrumbSeparator({ 66 + children, 67 + className, 68 + ...props 69 + }: React.ComponentProps<"li">) { 70 + return ( 71 + <li 72 + data-slot="breadcrumb-separator" 73 + role="presentation" 74 + aria-hidden="true" 75 + className={cn("[&>svg]:size-3.5", className)} 76 + {...props} 77 + > 78 + {children ?? <ChevronRight />} 79 + </li> 80 + ) 81 + } 82 + 83 + function BreadcrumbEllipsis({ 84 + className, 85 + ...props 86 + }: React.ComponentProps<"span">) { 87 + return ( 88 + <span 89 + data-slot="breadcrumb-ellipsis" 90 + role="presentation" 91 + aria-hidden="true" 92 + className={cn("flex size-9 items-center justify-center", className)} 93 + {...props} 94 + > 95 + <MoreHorizontal className="size-4" /> 96 + <span className="sr-only">More</span> 97 + </span> 98 + ) 99 + } 100 + 101 + export { 102 + Breadcrumb, 103 + BreadcrumbList, 104 + BreadcrumbItem, 105 + BreadcrumbLink, 106 + BreadcrumbPage, 107 + BreadcrumbSeparator, 108 + BreadcrumbEllipsis, 109 + }
+60
src/Den.Client.Web/src/components/ui/button.tsx
··· 1 + import * as React from "react" 2 + import { Slot } from "@radix-ui/react-slot" 3 + import { cva, type VariantProps } from "class-variance-authority" 4 + 5 + import { cn } from "@/lib/utils" 6 + 7 + const buttonVariants = cva( 8 + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 + { 10 + variants: { 11 + variant: { 12 + default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 + destructive: 14 + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 15 + outline: 16 + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 17 + secondary: 18 + "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 + ghost: 20 + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 21 + link: "text-primary underline-offset-4 hover:underline", 22 + }, 23 + size: { 24 + default: "h-9 px-4 py-2 has-[>svg]:px-3", 25 + sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 26 + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 27 + icon: "size-9", 28 + "icon-sm": "size-8", 29 + "icon-lg": "size-10", 30 + }, 31 + }, 32 + defaultVariants: { 33 + variant: "default", 34 + size: "default", 35 + }, 36 + } 37 + ) 38 + 39 + function Button({ 40 + className, 41 + variant, 42 + size, 43 + asChild = false, 44 + ...props 45 + }: React.ComponentProps<"button"> & 46 + VariantProps<typeof buttonVariants> & { 47 + asChild?: boolean 48 + }) { 49 + const Comp = asChild ? Slot : "button" 50 + 51 + return ( 52 + <Comp 53 + data-slot="button" 54 + className={cn(buttonVariants({ variant, size, className }))} 55 + {...props} 56 + /> 57 + ) 58 + } 59 + 60 + export { Button, buttonVariants }
+92
src/Den.Client.Web/src/components/ui/card.tsx
··· 1 + import * as React from "react" 2 + 3 + import { cn } from "@/lib/utils" 4 + 5 + function Card({ className, ...props }: React.ComponentProps<"div">) { 6 + return ( 7 + <div 8 + data-slot="card" 9 + className={cn( 10 + "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", 11 + className 12 + )} 13 + {...props} 14 + /> 15 + ) 16 + } 17 + 18 + function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 + return ( 20 + <div 21 + data-slot="card-header" 22 + className={cn( 23 + "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", 24 + className 25 + )} 26 + {...props} 27 + /> 28 + ) 29 + } 30 + 31 + function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 + return ( 33 + <div 34 + data-slot="card-title" 35 + className={cn("leading-none font-semibold", className)} 36 + {...props} 37 + /> 38 + ) 39 + } 40 + 41 + function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 + return ( 43 + <div 44 + data-slot="card-description" 45 + className={cn("text-muted-foreground text-sm", className)} 46 + {...props} 47 + /> 48 + ) 49 + } 50 + 51 + function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 + return ( 53 + <div 54 + data-slot="card-action" 55 + className={cn( 56 + "col-start-2 row-span-2 row-start-1 self-start justify-self-end", 57 + className 58 + )} 59 + {...props} 60 + /> 61 + ) 62 + } 63 + 64 + function CardContent({ className, ...props }: React.ComponentProps<"div">) { 65 + return ( 66 + <div 67 + data-slot="card-content" 68 + className={cn("px-6", className)} 69 + {...props} 70 + /> 71 + ) 72 + } 73 + 74 + function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 75 + return ( 76 + <div 77 + data-slot="card-footer" 78 + className={cn("flex items-center px-6 [.border-t]:pt-6", className)} 79 + {...props} 80 + /> 81 + ) 82 + } 83 + 84 + export { 85 + Card, 86 + CardHeader, 87 + CardFooter, 88 + CardTitle, 89 + CardAction, 90 + CardDescription, 91 + CardContent, 92 + }
+355
src/Den.Client.Web/src/components/ui/chart.tsx
··· 1 + import * as React from "react" 2 + import * as RechartsPrimitive from "recharts" 3 + 4 + import { cn } from "@/lib/utils" 5 + 6 + // Format: { THEME_NAME: CSS_SELECTOR } 7 + const THEMES = { light: "", dark: ".dark" } as const 8 + 9 + export type ChartConfig = { 10 + [k in string]: { 11 + label?: React.ReactNode 12 + icon?: React.ComponentType 13 + } & ( 14 + | { color?: string; theme?: never } 15 + | { color?: never; theme: Record<keyof typeof THEMES, string> } 16 + ) 17 + } 18 + 19 + type ChartContextProps = { 20 + config: ChartConfig 21 + } 22 + 23 + const ChartContext = React.createContext<ChartContextProps | null>(null) 24 + 25 + function useChart() { 26 + const context = React.useContext(ChartContext) 27 + 28 + if (!context) { 29 + throw new Error("useChart must be used within a <ChartContainer />") 30 + } 31 + 32 + return context 33 + } 34 + 35 + function ChartContainer({ 36 + id, 37 + className, 38 + children, 39 + config, 40 + ...props 41 + }: React.ComponentProps<"div"> & { 42 + config: ChartConfig 43 + children: React.ComponentProps< 44 + typeof RechartsPrimitive.ResponsiveContainer 45 + >["children"] 46 + }) { 47 + const uniqueId = React.useId() 48 + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}` 49 + 50 + return ( 51 + <ChartContext.Provider value={{ config }}> 52 + <div 53 + data-slot="chart" 54 + data-chart={chartId} 55 + className={cn( 56 + "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden", 57 + className 58 + )} 59 + {...props} 60 + > 61 + <ChartStyle id={chartId} config={config} /> 62 + <RechartsPrimitive.ResponsiveContainer> 63 + {children} 64 + </RechartsPrimitive.ResponsiveContainer> 65 + </div> 66 + </ChartContext.Provider> 67 + ) 68 + } 69 + 70 + const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { 71 + const colorConfig = Object.entries(config).filter( 72 + ([, config]) => config.theme || config.color 73 + ) 74 + 75 + if (!colorConfig.length) { 76 + return null 77 + } 78 + 79 + return ( 80 + <style 81 + dangerouslySetInnerHTML={{ 82 + __html: Object.entries(THEMES) 83 + .map( 84 + ([theme, prefix]) => ` 85 + ${prefix} [data-chart=${id}] { 86 + ${colorConfig 87 + .map(([key, itemConfig]) => { 88 + const color = 89 + itemConfig.theme?.[theme as keyof typeof itemConfig.theme] || 90 + itemConfig.color 91 + return color ? ` --color-${key}: ${color};` : null 92 + }) 93 + .join("\n")} 94 + } 95 + ` 96 + ) 97 + .join("\n"), 98 + }} 99 + /> 100 + ) 101 + } 102 + 103 + const ChartTooltip = RechartsPrimitive.Tooltip 104 + 105 + function ChartTooltipContent({ 106 + active, 107 + payload, 108 + className, 109 + indicator = "dot", 110 + hideLabel = false, 111 + hideIndicator = false, 112 + label, 113 + labelFormatter, 114 + labelClassName, 115 + formatter, 116 + color, 117 + nameKey, 118 + labelKey, 119 + }: React.ComponentProps<typeof RechartsPrimitive.Tooltip> & 120 + React.ComponentProps<"div"> & { 121 + hideLabel?: boolean 122 + hideIndicator?: boolean 123 + indicator?: "line" | "dot" | "dashed" 124 + nameKey?: string 125 + labelKey?: string 126 + }) { 127 + const { config } = useChart() 128 + 129 + const tooltipLabel = React.useMemo(() => { 130 + if (hideLabel || !payload?.length) { 131 + return null 132 + } 133 + 134 + const [item] = payload 135 + const key = `${labelKey || item?.dataKey || item?.name || "value"}` 136 + const itemConfig = getPayloadConfigFromPayload(config, item, key) 137 + const value = 138 + !labelKey && typeof label === "string" 139 + ? config[label as keyof typeof config]?.label || label 140 + : itemConfig?.label 141 + 142 + if (labelFormatter) { 143 + return ( 144 + <div className={cn("font-medium", labelClassName)}> 145 + {labelFormatter(value, payload)} 146 + </div> 147 + ) 148 + } 149 + 150 + if (!value) { 151 + return null 152 + } 153 + 154 + return <div className={cn("font-medium", labelClassName)}>{value}</div> 155 + }, [ 156 + label, 157 + labelFormatter, 158 + payload, 159 + hideLabel, 160 + labelClassName, 161 + config, 162 + labelKey, 163 + ]) 164 + 165 + if (!active || !payload?.length) { 166 + return null 167 + } 168 + 169 + const nestLabel = payload.length === 1 && indicator !== "dot" 170 + 171 + return ( 172 + <div 173 + className={cn( 174 + "border-border/50 bg-background grid min-w-[8rem] items-start gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs shadow-xl", 175 + className 176 + )} 177 + > 178 + {!nestLabel ? tooltipLabel : null} 179 + <div className="grid gap-1.5"> 180 + {payload 181 + .filter((item) => item.type !== "none") 182 + .map((item, index) => { 183 + const key = `${nameKey || item.name || item.dataKey || "value"}` 184 + const itemConfig = getPayloadConfigFromPayload(config, item, key) 185 + const indicatorColor = color || item.payload.fill || item.color 186 + 187 + return ( 188 + <div 189 + key={item.dataKey} 190 + className={cn( 191 + "[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5", 192 + indicator === "dot" && "items-center" 193 + )} 194 + > 195 + {formatter && item?.value !== undefined && item.name ? ( 196 + formatter(item.value, item.name, item, index, item.payload) 197 + ) : ( 198 + <> 199 + {itemConfig?.icon ? ( 200 + <itemConfig.icon /> 201 + ) : ( 202 + !hideIndicator && ( 203 + <div 204 + className={cn( 205 + "shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", 206 + { 207 + "h-2.5 w-2.5": indicator === "dot", 208 + "w-1": indicator === "line", 209 + "w-0 border-[1.5px] border-dashed bg-transparent": 210 + indicator === "dashed", 211 + "my-0.5": nestLabel && indicator === "dashed", 212 + } 213 + )} 214 + style={ 215 + { 216 + "--color-bg": indicatorColor, 217 + "--color-border": indicatorColor, 218 + } as React.CSSProperties 219 + } 220 + /> 221 + ) 222 + )} 223 + <div 224 + className={cn( 225 + "flex flex-1 justify-between leading-none", 226 + nestLabel ? "items-end" : "items-center" 227 + )} 228 + > 229 + <div className="grid gap-1.5"> 230 + {nestLabel ? tooltipLabel : null} 231 + <span className="text-muted-foreground"> 232 + {itemConfig?.label || item.name} 233 + </span> 234 + </div> 235 + {item.value && ( 236 + <span className="text-foreground font-mono font-medium tabular-nums"> 237 + {item.value.toLocaleString()} 238 + </span> 239 + )} 240 + </div> 241 + </> 242 + )} 243 + </div> 244 + ) 245 + })} 246 + </div> 247 + </div> 248 + ) 249 + } 250 + 251 + const ChartLegend = RechartsPrimitive.Legend 252 + 253 + function ChartLegendContent({ 254 + className, 255 + hideIcon = false, 256 + payload, 257 + verticalAlign = "bottom", 258 + nameKey, 259 + }: React.ComponentProps<"div"> & 260 + Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & { 261 + hideIcon?: boolean 262 + nameKey?: string 263 + }) { 264 + const { config } = useChart() 265 + 266 + if (!payload?.length) { 267 + return null 268 + } 269 + 270 + return ( 271 + <div 272 + className={cn( 273 + "flex items-center justify-center gap-4", 274 + verticalAlign === "top" ? "pb-3" : "pt-3", 275 + className 276 + )} 277 + > 278 + {payload 279 + .filter((item) => item.type !== "none") 280 + .map((item) => { 281 + const key = `${nameKey || item.dataKey || "value"}` 282 + const itemConfig = getPayloadConfigFromPayload(config, item, key) 283 + 284 + return ( 285 + <div 286 + key={item.value} 287 + className={cn( 288 + "[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3" 289 + )} 290 + > 291 + {itemConfig?.icon && !hideIcon ? ( 292 + <itemConfig.icon /> 293 + ) : ( 294 + <div 295 + className="h-2 w-2 shrink-0 rounded-[2px]" 296 + style={{ 297 + backgroundColor: item.color, 298 + }} 299 + /> 300 + )} 301 + {itemConfig?.label} 302 + </div> 303 + ) 304 + })} 305 + </div> 306 + ) 307 + } 308 + 309 + // Helper to extract item config from a payload. 310 + function getPayloadConfigFromPayload( 311 + config: ChartConfig, 312 + payload: unknown, 313 + key: string 314 + ) { 315 + if (typeof payload !== "object" || payload === null) { 316 + return undefined 317 + } 318 + 319 + const payloadPayload = 320 + "payload" in payload && 321 + typeof payload.payload === "object" && 322 + payload.payload !== null 323 + ? payload.payload 324 + : undefined 325 + 326 + let configLabelKey: string = key 327 + 328 + if ( 329 + key in payload && 330 + typeof payload[key as keyof typeof payload] === "string" 331 + ) { 332 + configLabelKey = payload[key as keyof typeof payload] as string 333 + } else if ( 334 + payloadPayload && 335 + key in payloadPayload && 336 + typeof payloadPayload[key as keyof typeof payloadPayload] === "string" 337 + ) { 338 + configLabelKey = payloadPayload[ 339 + key as keyof typeof payloadPayload 340 + ] as string 341 + } 342 + 343 + return configLabelKey in config 344 + ? config[configLabelKey] 345 + : config[key as keyof typeof config] 346 + } 347 + 348 + export { 349 + ChartContainer, 350 + ChartTooltip, 351 + ChartTooltipContent, 352 + ChartLegend, 353 + ChartLegendContent, 354 + ChartStyle, 355 + }
+30
src/Den.Client.Web/src/components/ui/checkbox.tsx
··· 1 + import * as React from "react" 2 + import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 3 + import { CheckIcon } from "lucide-react" 4 + 5 + import { cn } from "@/lib/utils" 6 + 7 + function Checkbox({ 8 + className, 9 + ...props 10 + }: React.ComponentProps<typeof CheckboxPrimitive.Root>) { 11 + return ( 12 + <CheckboxPrimitive.Root 13 + data-slot="checkbox" 14 + className={cn( 15 + "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", 16 + className 17 + )} 18 + {...props} 19 + > 20 + <CheckboxPrimitive.Indicator 21 + data-slot="checkbox-indicator" 22 + className="grid place-content-center text-current transition-none" 23 + > 24 + <CheckIcon className="size-3.5" /> 25 + </CheckboxPrimitive.Indicator> 26 + </CheckboxPrimitive.Root> 27 + ) 28 + } 29 + 30 + export { Checkbox }
+33
src/Den.Client.Web/src/components/ui/collapsible.tsx
··· 1 + "use client" 2 + 3 + import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 + 5 + function Collapsible({ 6 + ...props 7 + }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) { 8 + return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} /> 9 + } 10 + 11 + function CollapsibleTrigger({ 12 + ...props 13 + }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) { 14 + return ( 15 + <CollapsiblePrimitive.CollapsibleTrigger 16 + data-slot="collapsible-trigger" 17 + {...props} 18 + /> 19 + ) 20 + } 21 + 22 + function CollapsibleContent({ 23 + ...props 24 + }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) { 25 + return ( 26 + <CollapsiblePrimitive.CollapsibleContent 27 + data-slot="collapsible-content" 28 + {...props} 29 + /> 30 + ) 31 + } 32 + 33 + export { Collapsible, CollapsibleTrigger, CollapsibleContent }
+184
src/Den.Client.Web/src/components/ui/command.tsx
··· 1 + "use client" 2 + 3 + import * as React from "react" 4 + import { Command as CommandPrimitive } from "cmdk" 5 + import { SearchIcon } from "lucide-react" 6 + 7 + import { cn } from "@/lib/utils" 8 + import { 9 + Dialog, 10 + DialogContent, 11 + DialogDescription, 12 + DialogHeader, 13 + DialogTitle, 14 + } from "@/components/ui/dialog" 15 + 16 + function Command({ 17 + className, 18 + ...props 19 + }: React.ComponentProps<typeof CommandPrimitive>) { 20 + return ( 21 + <CommandPrimitive 22 + data-slot="command" 23 + className={cn( 24 + "bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md", 25 + className 26 + )} 27 + {...props} 28 + /> 29 + ) 30 + } 31 + 32 + function CommandDialog({ 33 + title = "Command Palette", 34 + description = "Search for a command to run...", 35 + children, 36 + className, 37 + showCloseButton = true, 38 + ...props 39 + }: React.ComponentProps<typeof Dialog> & { 40 + title?: string 41 + description?: string 42 + className?: string 43 + showCloseButton?: boolean 44 + }) { 45 + return ( 46 + <Dialog {...props}> 47 + <DialogHeader className="sr-only"> 48 + <DialogTitle>{title}</DialogTitle> 49 + <DialogDescription>{description}</DialogDescription> 50 + </DialogHeader> 51 + <DialogContent 52 + className={cn("overflow-hidden p-0", className)} 53 + showCloseButton={showCloseButton} 54 + > 55 + <Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"> 56 + {children} 57 + </Command> 58 + </DialogContent> 59 + </Dialog> 60 + ) 61 + } 62 + 63 + function CommandInput({ 64 + className, 65 + ...props 66 + }: React.ComponentProps<typeof CommandPrimitive.Input>) { 67 + return ( 68 + <div 69 + data-slot="command-input-wrapper" 70 + className="flex h-9 items-center gap-2 border-b px-3" 71 + > 72 + <SearchIcon className="size-4 shrink-0 opacity-50" /> 73 + <CommandPrimitive.Input 74 + data-slot="command-input" 75 + className={cn( 76 + "placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50", 77 + className 78 + )} 79 + {...props} 80 + /> 81 + </div> 82 + ) 83 + } 84 + 85 + function CommandList({ 86 + className, 87 + ...props 88 + }: React.ComponentProps<typeof CommandPrimitive.List>) { 89 + return ( 90 + <CommandPrimitive.List 91 + data-slot="command-list" 92 + className={cn( 93 + "max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto", 94 + className 95 + )} 96 + {...props} 97 + /> 98 + ) 99 + } 100 + 101 + function CommandEmpty({ 102 + ...props 103 + }: React.ComponentProps<typeof CommandPrimitive.Empty>) { 104 + return ( 105 + <CommandPrimitive.Empty 106 + data-slot="command-empty" 107 + className="py-6 text-center text-sm" 108 + {...props} 109 + /> 110 + ) 111 + } 112 + 113 + function CommandGroup({ 114 + className, 115 + ...props 116 + }: React.ComponentProps<typeof CommandPrimitive.Group>) { 117 + return ( 118 + <CommandPrimitive.Group 119 + data-slot="command-group" 120 + className={cn( 121 + "text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium", 122 + className 123 + )} 124 + {...props} 125 + /> 126 + ) 127 + } 128 + 129 + function CommandSeparator({ 130 + className, 131 + ...props 132 + }: React.ComponentProps<typeof CommandPrimitive.Separator>) { 133 + return ( 134 + <CommandPrimitive.Separator 135 + data-slot="command-separator" 136 + className={cn("bg-border -mx-1 h-px", className)} 137 + {...props} 138 + /> 139 + ) 140 + } 141 + 142 + function CommandItem({ 143 + className, 144 + ...props 145 + }: React.ComponentProps<typeof CommandPrimitive.Item>) { 146 + return ( 147 + <CommandPrimitive.Item 148 + data-slot="command-item" 149 + className={cn( 150 + "data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 151 + className 152 + )} 153 + {...props} 154 + /> 155 + ) 156 + } 157 + 158 + function CommandShortcut({ 159 + className, 160 + ...props 161 + }: React.ComponentProps<"span">) { 162 + return ( 163 + <span 164 + data-slot="command-shortcut" 165 + className={cn( 166 + "text-muted-foreground ml-auto text-xs tracking-widest", 167 + className 168 + )} 169 + {...props} 170 + /> 171 + ) 172 + } 173 + 174 + export { 175 + Command, 176 + CommandDialog, 177 + CommandInput, 178 + CommandList, 179 + CommandEmpty, 180 + CommandGroup, 181 + CommandItem, 182 + CommandShortcut, 183 + CommandSeparator, 184 + }
+141
src/Den.Client.Web/src/components/ui/dialog.tsx
··· 1 + import * as React from "react" 2 + import * as DialogPrimitive from "@radix-ui/react-dialog" 3 + import { XIcon } from "lucide-react" 4 + 5 + import { cn } from "@/lib/utils" 6 + 7 + function Dialog({ 8 + ...props 9 + }: React.ComponentProps<typeof DialogPrimitive.Root>) { 10 + return <DialogPrimitive.Root data-slot="dialog" {...props} /> 11 + } 12 + 13 + function DialogTrigger({ 14 + ...props 15 + }: React.ComponentProps<typeof DialogPrimitive.Trigger>) { 16 + return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} /> 17 + } 18 + 19 + function DialogPortal({ 20 + ...props 21 + }: React.ComponentProps<typeof DialogPrimitive.Portal>) { 22 + return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} /> 23 + } 24 + 25 + function DialogClose({ 26 + ...props 27 + }: React.ComponentProps<typeof DialogPrimitive.Close>) { 28 + return <DialogPrimitive.Close data-slot="dialog-close" {...props} /> 29 + } 30 + 31 + function DialogOverlay({ 32 + className, 33 + ...props 34 + }: React.ComponentProps<typeof DialogPrimitive.Overlay>) { 35 + return ( 36 + <DialogPrimitive.Overlay 37 + data-slot="dialog-overlay" 38 + className={cn( 39 + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", 40 + className 41 + )} 42 + {...props} 43 + /> 44 + ) 45 + } 46 + 47 + function DialogContent({ 48 + className, 49 + children, 50 + showCloseButton = true, 51 + ...props 52 + }: React.ComponentProps<typeof DialogPrimitive.Content> & { 53 + showCloseButton?: boolean 54 + }) { 55 + return ( 56 + <DialogPortal data-slot="dialog-portal"> 57 + <DialogOverlay /> 58 + <DialogPrimitive.Content 59 + data-slot="dialog-content" 60 + className={cn( 61 + "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg", 62 + className 63 + )} 64 + {...props} 65 + > 66 + {children} 67 + {showCloseButton && ( 68 + <DialogPrimitive.Close 69 + data-slot="dialog-close" 70 + className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" 71 + > 72 + <XIcon /> 73 + <span className="sr-only">Close</span> 74 + </DialogPrimitive.Close> 75 + )} 76 + </DialogPrimitive.Content> 77 + </DialogPortal> 78 + ) 79 + } 80 + 81 + function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { 82 + return ( 83 + <div 84 + data-slot="dialog-header" 85 + className={cn("flex flex-col gap-2 text-center sm:text-left", className)} 86 + {...props} 87 + /> 88 + ) 89 + } 90 + 91 + function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { 92 + return ( 93 + <div 94 + data-slot="dialog-footer" 95 + className={cn( 96 + "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", 97 + className 98 + )} 99 + {...props} 100 + /> 101 + ) 102 + } 103 + 104 + function DialogTitle({ 105 + className, 106 + ...props 107 + }: React.ComponentProps<typeof DialogPrimitive.Title>) { 108 + return ( 109 + <DialogPrimitive.Title 110 + data-slot="dialog-title" 111 + className={cn("text-lg leading-none font-semibold", className)} 112 + {...props} 113 + /> 114 + ) 115 + } 116 + 117 + function DialogDescription({ 118 + className, 119 + ...props 120 + }: React.ComponentProps<typeof DialogPrimitive.Description>) { 121 + return ( 122 + <DialogPrimitive.Description 123 + data-slot="dialog-description" 124 + className={cn("text-muted-foreground text-sm", className)} 125 + {...props} 126 + /> 127 + ) 128 + } 129 + 130 + export { 131 + Dialog, 132 + DialogClose, 133 + DialogContent, 134 + DialogDescription, 135 + DialogFooter, 136 + DialogHeader, 137 + DialogOverlay, 138 + DialogPortal, 139 + DialogTitle, 140 + DialogTrigger, 141 + }
+257
src/Den.Client.Web/src/components/ui/dropdown-menu.tsx
··· 1 + "use client" 2 + 3 + import * as React from "react" 4 + import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 + import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" 6 + 7 + import { cn } from "@/lib/utils" 8 + 9 + function DropdownMenu({ 10 + ...props 11 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { 12 + return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} /> 13 + } 14 + 15 + function DropdownMenuPortal({ 16 + ...props 17 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) { 18 + return ( 19 + <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} /> 20 + ) 21 + } 22 + 23 + function DropdownMenuTrigger({ 24 + ...props 25 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { 26 + return ( 27 + <DropdownMenuPrimitive.Trigger 28 + data-slot="dropdown-menu-trigger" 29 + {...props} 30 + /> 31 + ) 32 + } 33 + 34 + function DropdownMenuContent({ 35 + className, 36 + sideOffset = 4, 37 + ...props 38 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) { 39 + return ( 40 + <DropdownMenuPrimitive.Portal> 41 + <DropdownMenuPrimitive.Content 42 + data-slot="dropdown-menu-content" 43 + sideOffset={sideOffset} 44 + className={cn( 45 + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md", 46 + className 47 + )} 48 + {...props} 49 + /> 50 + </DropdownMenuPrimitive.Portal> 51 + ) 52 + } 53 + 54 + function DropdownMenuGroup({ 55 + ...props 56 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { 57 + return ( 58 + <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} /> 59 + ) 60 + } 61 + 62 + function DropdownMenuItem({ 63 + className, 64 + inset, 65 + variant = "default", 66 + ...props 67 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & { 68 + inset?: boolean 69 + variant?: "default" | "destructive" 70 + }) { 71 + return ( 72 + <DropdownMenuPrimitive.Item 73 + data-slot="dropdown-menu-item" 74 + data-inset={inset} 75 + data-variant={variant} 76 + className={cn( 77 + "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 78 + className 79 + )} 80 + {...props} 81 + /> 82 + ) 83 + } 84 + 85 + function DropdownMenuCheckboxItem({ 86 + className, 87 + children, 88 + checked, 89 + ...props 90 + }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) { 91 + return ( 92 + <DropdownMenuPrimitive.CheckboxItem 93 + data-slot="dropdown-menu-checkbox-item" 94 + className={cn( 95 + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 96 + className 97 + )} 98 + checked={checked} 99 + {...props} 100 + > 101 + <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> 102 + <DropdownMenuPrimitive.ItemIndicator> 103 + <CheckIcon className="size-4" /> 104 + </DropdownMenuPrimitive.ItemIndicator> 105 + </span> 106 + {children} 107 + </DropdownMenuPrimitive.CheckboxItem> 108 + ) 109 + } 110 + 111 + function DropdownMenuRadioGroup({ 112 + ...props 113 + }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { 114 + return ( 115 + <DropdownMenuPrimitive.RadioGroup 116 + data-slot="dropdown-menu-radio-group" 117 + {...props} 118 + /> 119 + ) 120 + } 121 + 122 + function DropdownMenuRadioItem({ 123 + className, 124 + children, 125 + ...props 126 + }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) { 127 + return ( 128 + <DropdownMenuPrimitive.RadioItem 129 + data-slot="dropdown-menu-radio-item" 130 + className={cn( 131 + "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 132 + className 133 + )} 134 + {...props} 135 + > 136 + <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> 137 + <DropdownMenuPrimitive.ItemIndicator> 138 + <CircleIcon className="size-2 fill-current" /> 139 + </DropdownMenuPrimitive.ItemIndicator> 140 + </span> 141 + {children} 142 + </DropdownMenuPrimitive.RadioItem> 143 + ) 144 + } 145 + 146 + function DropdownMenuLabel({ 147 + className, 148 + inset, 149 + ...props 150 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { 151 + inset?: boolean 152 + }) { 153 + return ( 154 + <DropdownMenuPrimitive.Label 155 + data-slot="dropdown-menu-label" 156 + data-inset={inset} 157 + className={cn( 158 + "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", 159 + className 160 + )} 161 + {...props} 162 + /> 163 + ) 164 + } 165 + 166 + function DropdownMenuSeparator({ 167 + className, 168 + ...props 169 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) { 170 + return ( 171 + <DropdownMenuPrimitive.Separator 172 + data-slot="dropdown-menu-separator" 173 + className={cn("bg-border -mx-1 my-1 h-px", className)} 174 + {...props} 175 + /> 176 + ) 177 + } 178 + 179 + function DropdownMenuShortcut({ 180 + className, 181 + ...props 182 + }: React.ComponentProps<"span">) { 183 + return ( 184 + <span 185 + data-slot="dropdown-menu-shortcut" 186 + className={cn( 187 + "text-muted-foreground ml-auto text-xs tracking-widest", 188 + className 189 + )} 190 + {...props} 191 + /> 192 + ) 193 + } 194 + 195 + function DropdownMenuSub({ 196 + ...props 197 + }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { 198 + return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} /> 199 + } 200 + 201 + function DropdownMenuSubTrigger({ 202 + className, 203 + inset, 204 + children, 205 + ...props 206 + }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { 207 + inset?: boolean 208 + }) { 209 + return ( 210 + <DropdownMenuPrimitive.SubTrigger 211 + data-slot="dropdown-menu-sub-trigger" 212 + data-inset={inset} 213 + className={cn( 214 + "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 215 + className 216 + )} 217 + {...props} 218 + > 219 + {children} 220 + <ChevronRightIcon className="ml-auto size-4" /> 221 + </DropdownMenuPrimitive.SubTrigger> 222 + ) 223 + } 224 + 225 + function DropdownMenuSubContent({ 226 + className, 227 + ...props 228 + }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) { 229 + return ( 230 + <DropdownMenuPrimitive.SubContent 231 + data-slot="dropdown-menu-sub-content" 232 + className={cn( 233 + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg", 234 + className 235 + )} 236 + {...props} 237 + /> 238 + ) 239 + } 240 + 241 + export { 242 + DropdownMenu, 243 + DropdownMenuPortal, 244 + DropdownMenuTrigger, 245 + DropdownMenuContent, 246 + DropdownMenuGroup, 247 + DropdownMenuLabel, 248 + DropdownMenuItem, 249 + DropdownMenuCheckboxItem, 250 + DropdownMenuRadioGroup, 251 + DropdownMenuRadioItem, 252 + DropdownMenuSeparator, 253 + DropdownMenuShortcut, 254 + DropdownMenuSub, 255 + DropdownMenuSubTrigger, 256 + DropdownMenuSubContent, 257 + }
+104
src/Den.Client.Web/src/components/ui/empty.tsx
··· 1 + import { cva, type VariantProps } from "class-variance-authority" 2 + 3 + import { cn } from "@/lib/utils" 4 + 5 + function Empty({ className, ...props }: React.ComponentProps<"div">) { 6 + return ( 7 + <div 8 + data-slot="empty" 9 + className={cn( 10 + "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12", 11 + className 12 + )} 13 + {...props} 14 + /> 15 + ) 16 + } 17 + 18 + function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) { 19 + return ( 20 + <div 21 + data-slot="empty-header" 22 + className={cn( 23 + "flex max-w-sm flex-col items-center gap-2 text-center", 24 + className 25 + )} 26 + {...props} 27 + /> 28 + ) 29 + } 30 + 31 + const emptyMediaVariants = cva( 32 + "flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0", 33 + { 34 + variants: { 35 + variant: { 36 + default: "bg-transparent", 37 + icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6", 38 + }, 39 + }, 40 + defaultVariants: { 41 + variant: "default", 42 + }, 43 + } 44 + ) 45 + 46 + function EmptyMedia({ 47 + className, 48 + variant = "default", 49 + ...props 50 + }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) { 51 + return ( 52 + <div 53 + data-slot="empty-icon" 54 + data-variant={variant} 55 + className={cn(emptyMediaVariants({ variant, className }))} 56 + {...props} 57 + /> 58 + ) 59 + } 60 + 61 + function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) { 62 + return ( 63 + <div 64 + data-slot="empty-title" 65 + className={cn("text-lg font-medium tracking-tight", className)} 66 + {...props} 67 + /> 68 + ) 69 + } 70 + 71 + function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) { 72 + return ( 73 + <div 74 + data-slot="empty-description" 75 + className={cn( 76 + "text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4", 77 + className 78 + )} 79 + {...props} 80 + /> 81 + ) 82 + } 83 + 84 + function EmptyContent({ className, ...props }: React.ComponentProps<"div">) { 85 + return ( 86 + <div 87 + data-slot="empty-content" 88 + className={cn( 89 + "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance", 90 + className 91 + )} 92 + {...props} 93 + /> 94 + ) 95 + } 96 + 97 + export { 98 + Empty, 99 + EmptyHeader, 100 + EmptyTitle, 101 + EmptyDescription, 102 + EmptyContent, 103 + EmptyMedia, 104 + }
+246
src/Den.Client.Web/src/components/ui/field.tsx
··· 1 + import { useMemo } from "react" 2 + import { cva, type VariantProps } from "class-variance-authority" 3 + 4 + import { cn } from "@/lib/utils" 5 + import { Label } from "@/components/ui/label" 6 + import { Separator } from "@/components/ui/separator" 7 + 8 + function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) { 9 + return ( 10 + <fieldset 11 + data-slot="field-set" 12 + className={cn( 13 + "flex flex-col gap-6", 14 + "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", 15 + className 16 + )} 17 + {...props} 18 + /> 19 + ) 20 + } 21 + 22 + function FieldLegend({ 23 + className, 24 + variant = "legend", 25 + ...props 26 + }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) { 27 + return ( 28 + <legend 29 + data-slot="field-legend" 30 + data-variant={variant} 31 + className={cn( 32 + "mb-3 font-medium", 33 + "data-[variant=legend]:text-base", 34 + "data-[variant=label]:text-sm", 35 + className 36 + )} 37 + {...props} 38 + /> 39 + ) 40 + } 41 + 42 + function FieldGroup({ className, ...props }: React.ComponentProps<"div">) { 43 + return ( 44 + <div 45 + data-slot="field-group" 46 + className={cn( 47 + "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4", 48 + className 49 + )} 50 + {...props} 51 + /> 52 + ) 53 + } 54 + 55 + const fieldVariants = cva( 56 + "group/field flex w-full gap-3 data-[invalid=true]:text-destructive", 57 + { 58 + variants: { 59 + orientation: { 60 + vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"], 61 + horizontal: [ 62 + "flex-row items-center", 63 + "[&>[data-slot=field-label]]:flex-auto", 64 + "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", 65 + ], 66 + responsive: [ 67 + "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto", 68 + "@md/field-group:[&>[data-slot=field-label]]:flex-auto", 69 + "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px", 70 + ], 71 + }, 72 + }, 73 + defaultVariants: { 74 + orientation: "vertical", 75 + }, 76 + } 77 + ) 78 + 79 + function Field({ 80 + className, 81 + orientation = "vertical", 82 + ...props 83 + }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) { 84 + return ( 85 + <div 86 + role="group" 87 + data-slot="field" 88 + data-orientation={orientation} 89 + className={cn(fieldVariants({ orientation }), className)} 90 + {...props} 91 + /> 92 + ) 93 + } 94 + 95 + function FieldContent({ className, ...props }: React.ComponentProps<"div">) { 96 + return ( 97 + <div 98 + data-slot="field-content" 99 + className={cn( 100 + "group/field-content flex flex-1 flex-col gap-1.5 leading-snug", 101 + className 102 + )} 103 + {...props} 104 + /> 105 + ) 106 + } 107 + 108 + function FieldLabel({ 109 + className, 110 + ...props 111 + }: React.ComponentProps<typeof Label>) { 112 + return ( 113 + <Label 114 + data-slot="field-label" 115 + className={cn( 116 + "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50", 117 + "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4", 118 + "has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10", 119 + className 120 + )} 121 + {...props} 122 + /> 123 + ) 124 + } 125 + 126 + function FieldTitle({ className, ...props }: React.ComponentProps<"div">) { 127 + return ( 128 + <div 129 + data-slot="field-label" 130 + className={cn( 131 + "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50", 132 + className 133 + )} 134 + {...props} 135 + /> 136 + ) 137 + } 138 + 139 + function FieldDescription({ className, ...props }: React.ComponentProps<"p">) { 140 + return ( 141 + <p 142 + data-slot="field-description" 143 + className={cn( 144 + "text-muted-foreground text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance", 145 + "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5", 146 + "[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4", 147 + className 148 + )} 149 + {...props} 150 + /> 151 + ) 152 + } 153 + 154 + function FieldSeparator({ 155 + children, 156 + className, 157 + ...props 158 + }: React.ComponentProps<"div"> & { 159 + children?: React.ReactNode 160 + }) { 161 + return ( 162 + <div 163 + data-slot="field-separator" 164 + data-content={!!children} 165 + className={cn( 166 + "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", 167 + className 168 + )} 169 + {...props} 170 + > 171 + <Separator className="absolute inset-0 top-1/2" /> 172 + {children && ( 173 + <span 174 + className="bg-background text-muted-foreground relative mx-auto block w-fit px-2" 175 + data-slot="field-separator-content" 176 + > 177 + {children} 178 + </span> 179 + )} 180 + </div> 181 + ) 182 + } 183 + 184 + function FieldError({ 185 + className, 186 + children, 187 + errors, 188 + ...props 189 + }: React.ComponentProps<"div"> & { 190 + errors?: Array<{ message?: string } | undefined> 191 + }) { 192 + const content = useMemo(() => { 193 + if (children) { 194 + return children 195 + } 196 + 197 + if (!errors?.length) { 198 + return null 199 + } 200 + 201 + const uniqueErrors = [ 202 + ...new Map(errors.map((error) => [error?.message, error])).values(), 203 + ] 204 + 205 + if (uniqueErrors?.length == 1) { 206 + return uniqueErrors[0]?.message 207 + } 208 + 209 + return ( 210 + <ul className="ml-4 flex list-disc flex-col gap-1"> 211 + {uniqueErrors.map( 212 + (error, index) => 213 + error?.message && <li key={index}>{error.message}</li> 214 + )} 215 + </ul> 216 + ) 217 + }, [children, errors]) 218 + 219 + if (!content) { 220 + return null 221 + } 222 + 223 + return ( 224 + <div 225 + role="alert" 226 + data-slot="field-error" 227 + className={cn("text-destructive text-sm font-normal", className)} 228 + {...props} 229 + > 230 + {content} 231 + </div> 232 + ) 233 + } 234 + 235 + export { 236 + Field, 237 + FieldLabel, 238 + FieldDescription, 239 + FieldError, 240 + FieldGroup, 241 + FieldLegend, 242 + FieldSeparator, 243 + FieldSet, 244 + FieldContent, 245 + FieldTitle, 246 + }
+21
src/Den.Client.Web/src/components/ui/input.tsx
··· 1 + import * as React from "react" 2 + 3 + import { cn } from "@/lib/utils" 4 + 5 + function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 + return ( 7 + <input 8 + type={type} 9 + data-slot="input" 10 + className={cn( 11 + "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 12 + "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", 13 + "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 14 + className 15 + )} 16 + {...props} 17 + /> 18 + ) 19 + } 20 + 21 + export { Input }
+22
src/Den.Client.Web/src/components/ui/label.tsx
··· 1 + import * as React from "react" 2 + import * as LabelPrimitive from "@radix-ui/react-label" 3 + 4 + import { cn } from "@/lib/utils" 5 + 6 + function Label({ 7 + className, 8 + ...props 9 + }: React.ComponentProps<typeof LabelPrimitive.Root>) { 10 + return ( 11 + <LabelPrimitive.Root 12 + data-slot="label" 13 + className={cn( 14 + "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", 15 + className 16 + )} 17 + {...props} 18 + /> 19 + ) 20 + } 21 + 22 + export { Label }
+185
src/Den.Client.Web/src/components/ui/select.tsx
··· 1 + import * as React from "react" 2 + import * as SelectPrimitive from "@radix-ui/react-select" 3 + import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" 4 + 5 + import { cn } from "@/lib/utils" 6 + 7 + function Select({ 8 + ...props 9 + }: React.ComponentProps<typeof SelectPrimitive.Root>) { 10 + return <SelectPrimitive.Root data-slot="select" {...props} /> 11 + } 12 + 13 + function SelectGroup({ 14 + ...props 15 + }: React.ComponentProps<typeof SelectPrimitive.Group>) { 16 + return <SelectPrimitive.Group data-slot="select-group" {...props} /> 17 + } 18 + 19 + function SelectValue({ 20 + ...props 21 + }: React.ComponentProps<typeof SelectPrimitive.Value>) { 22 + return <SelectPrimitive.Value data-slot="select-value" {...props} /> 23 + } 24 + 25 + function SelectTrigger({ 26 + className, 27 + size = "default", 28 + children, 29 + ...props 30 + }: React.ComponentProps<typeof SelectPrimitive.Trigger> & { 31 + size?: "sm" | "default" 32 + }) { 33 + return ( 34 + <SelectPrimitive.Trigger 35 + data-slot="select-trigger" 36 + data-size={size} 37 + className={cn( 38 + "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", 39 + className 40 + )} 41 + {...props} 42 + > 43 + {children} 44 + <SelectPrimitive.Icon asChild> 45 + <ChevronDownIcon className="size-4 opacity-50" /> 46 + </SelectPrimitive.Icon> 47 + </SelectPrimitive.Trigger> 48 + ) 49 + } 50 + 51 + function SelectContent({ 52 + className, 53 + children, 54 + position = "popper", 55 + align = "center", 56 + ...props 57 + }: React.ComponentProps<typeof SelectPrimitive.Content>) { 58 + return ( 59 + <SelectPrimitive.Portal> 60 + <SelectPrimitive.Content 61 + data-slot="select-content" 62 + className={cn( 63 + "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", 64 + position === "popper" && 65 + "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", 66 + className 67 + )} 68 + position={position} 69 + align={align} 70 + {...props} 71 + > 72 + <SelectScrollUpButton /> 73 + <SelectPrimitive.Viewport 74 + className={cn( 75 + "p-1", 76 + position === "popper" && 77 + "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1" 78 + )} 79 + > 80 + {children} 81 + </SelectPrimitive.Viewport> 82 + <SelectScrollDownButton /> 83 + </SelectPrimitive.Content> 84 + </SelectPrimitive.Portal> 85 + ) 86 + } 87 + 88 + function SelectLabel({ 89 + className, 90 + ...props 91 + }: React.ComponentProps<typeof SelectPrimitive.Label>) { 92 + return ( 93 + <SelectPrimitive.Label 94 + data-slot="select-label" 95 + className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)} 96 + {...props} 97 + /> 98 + ) 99 + } 100 + 101 + function SelectItem({ 102 + className, 103 + children, 104 + ...props 105 + }: React.ComponentProps<typeof SelectPrimitive.Item>) { 106 + return ( 107 + <SelectPrimitive.Item 108 + data-slot="select-item" 109 + className={cn( 110 + "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", 111 + className 112 + )} 113 + {...props} 114 + > 115 + <span className="absolute right-2 flex size-3.5 items-center justify-center"> 116 + <SelectPrimitive.ItemIndicator> 117 + <CheckIcon className="size-4" /> 118 + </SelectPrimitive.ItemIndicator> 119 + </span> 120 + <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> 121 + </SelectPrimitive.Item> 122 + ) 123 + } 124 + 125 + function SelectSeparator({ 126 + className, 127 + ...props 128 + }: React.ComponentProps<typeof SelectPrimitive.Separator>) { 129 + return ( 130 + <SelectPrimitive.Separator 131 + data-slot="select-separator" 132 + className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)} 133 + {...props} 134 + /> 135 + ) 136 + } 137 + 138 + function SelectScrollUpButton({ 139 + className, 140 + ...props 141 + }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) { 142 + return ( 143 + <SelectPrimitive.ScrollUpButton 144 + data-slot="select-scroll-up-button" 145 + className={cn( 146 + "flex cursor-default items-center justify-center py-1", 147 + className 148 + )} 149 + {...props} 150 + > 151 + <ChevronUpIcon className="size-4" /> 152 + </SelectPrimitive.ScrollUpButton> 153 + ) 154 + } 155 + 156 + function SelectScrollDownButton({ 157 + className, 158 + ...props 159 + }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) { 160 + return ( 161 + <SelectPrimitive.ScrollDownButton 162 + data-slot="select-scroll-down-button" 163 + className={cn( 164 + "flex cursor-default items-center justify-center py-1", 165 + className 166 + )} 167 + {...props} 168 + > 169 + <ChevronDownIcon className="size-4" /> 170 + </SelectPrimitive.ScrollDownButton> 171 + ) 172 + } 173 + 174 + export { 175 + Select, 176 + SelectContent, 177 + SelectGroup, 178 + SelectItem, 179 + SelectLabel, 180 + SelectScrollDownButton, 181 + SelectScrollUpButton, 182 + SelectSeparator, 183 + SelectTrigger, 184 + SelectValue, 185 + }
+28
src/Den.Client.Web/src/components/ui/separator.tsx
··· 1 + "use client" 2 + 3 + import * as React from "react" 4 + import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 + 6 + import { cn } from "@/lib/utils" 7 + 8 + function Separator({ 9 + className, 10 + orientation = "horizontal", 11 + decorative = true, 12 + ...props 13 + }: React.ComponentProps<typeof SeparatorPrimitive.Root>) { 14 + return ( 15 + <SeparatorPrimitive.Root 16 + data-slot="separator" 17 + decorative={decorative} 18 + orientation={orientation} 19 + className={cn( 20 + "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px", 21 + className 22 + )} 23 + {...props} 24 + /> 25 + ) 26 + } 27 + 28 + export { Separator }
+139
src/Den.Client.Web/src/components/ui/sheet.tsx
··· 1 + "use client" 2 + 3 + import * as React from "react" 4 + import * as SheetPrimitive from "@radix-ui/react-dialog" 5 + import { XIcon } from "lucide-react" 6 + 7 + import { cn } from "@/lib/utils" 8 + 9 + function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) { 10 + return <SheetPrimitive.Root data-slot="sheet" {...props} /> 11 + } 12 + 13 + function SheetTrigger({ 14 + ...props 15 + }: React.ComponentProps<typeof SheetPrimitive.Trigger>) { 16 + return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} /> 17 + } 18 + 19 + function SheetClose({ 20 + ...props 21 + }: React.ComponentProps<typeof SheetPrimitive.Close>) { 22 + return <SheetPrimitive.Close data-slot="sheet-close" {...props} /> 23 + } 24 + 25 + function SheetPortal({ 26 + ...props 27 + }: React.ComponentProps<typeof SheetPrimitive.Portal>) { 28 + return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} /> 29 + } 30 + 31 + function SheetOverlay({ 32 + className, 33 + ...props 34 + }: React.ComponentProps<typeof SheetPrimitive.Overlay>) { 35 + return ( 36 + <SheetPrimitive.Overlay 37 + data-slot="sheet-overlay" 38 + className={cn( 39 + "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", 40 + className 41 + )} 42 + {...props} 43 + /> 44 + ) 45 + } 46 + 47 + function SheetContent({ 48 + className, 49 + children, 50 + side = "right", 51 + ...props 52 + }: React.ComponentProps<typeof SheetPrimitive.Content> & { 53 + side?: "top" | "right" | "bottom" | "left" 54 + }) { 55 + return ( 56 + <SheetPortal> 57 + <SheetOverlay /> 58 + <SheetPrimitive.Content 59 + data-slot="sheet-content" 60 + className={cn( 61 + "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", 62 + side === "right" && 63 + "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", 64 + side === "left" && 65 + "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm", 66 + side === "top" && 67 + "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b", 68 + side === "bottom" && 69 + "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t", 70 + className 71 + )} 72 + {...props} 73 + > 74 + {children} 75 + <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"> 76 + <XIcon className="size-4" /> 77 + <span className="sr-only">Close</span> 78 + </SheetPrimitive.Close> 79 + </SheetPrimitive.Content> 80 + </SheetPortal> 81 + ) 82 + } 83 + 84 + function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { 85 + return ( 86 + <div 87 + data-slot="sheet-header" 88 + className={cn("flex flex-col gap-1.5 p-4", className)} 89 + {...props} 90 + /> 91 + ) 92 + } 93 + 94 + function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { 95 + return ( 96 + <div 97 + data-slot="sheet-footer" 98 + className={cn("mt-auto flex flex-col gap-2 p-4", className)} 99 + {...props} 100 + /> 101 + ) 102 + } 103 + 104 + function SheetTitle({ 105 + className, 106 + ...props 107 + }: React.ComponentProps<typeof SheetPrimitive.Title>) { 108 + return ( 109 + <SheetPrimitive.Title 110 + data-slot="sheet-title" 111 + className={cn("text-foreground font-semibold", className)} 112 + {...props} 113 + /> 114 + ) 115 + } 116 + 117 + function SheetDescription({ 118 + className, 119 + ...props 120 + }: React.ComponentProps<typeof SheetPrimitive.Description>) { 121 + return ( 122 + <SheetPrimitive.Description 123 + data-slot="sheet-description" 124 + className={cn("text-muted-foreground text-sm", className)} 125 + {...props} 126 + /> 127 + ) 128 + } 129 + 130 + export { 131 + Sheet, 132 + SheetTrigger, 133 + SheetClose, 134 + SheetContent, 135 + SheetHeader, 136 + SheetFooter, 137 + SheetTitle, 138 + SheetDescription, 139 + }
+724
src/Den.Client.Web/src/components/ui/sidebar.tsx
··· 1 + import * as React from "react" 2 + import { Slot } from "@radix-ui/react-slot" 3 + import { cva, type VariantProps } from "class-variance-authority" 4 + import { PanelLeftIcon } from "lucide-react" 5 + 6 + import { useIsMobile } from "@/hooks/use-mobile" 7 + import { cn } from "@/lib/utils" 8 + import { Button } from "@/components/ui/button" 9 + import { Input } from "@/components/ui/input" 10 + import { Separator } from "@/components/ui/separator" 11 + import { 12 + Sheet, 13 + SheetContent, 14 + SheetDescription, 15 + SheetHeader, 16 + SheetTitle, 17 + } from "@/components/ui/sheet" 18 + import { Skeleton } from "@/components/ui/skeleton" 19 + import { 20 + Tooltip, 21 + TooltipContent, 22 + TooltipProvider, 23 + TooltipTrigger, 24 + } from "@/components/ui/tooltip" 25 + 26 + const SIDEBAR_COOKIE_NAME = "sidebar_state" 27 + const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7 28 + const SIDEBAR_WIDTH = "16rem" 29 + const SIDEBAR_WIDTH_MOBILE = "18rem" 30 + const SIDEBAR_WIDTH_ICON = "3rem" 31 + const SIDEBAR_KEYBOARD_SHORTCUT = "b" 32 + 33 + type SidebarContextProps = { 34 + state: "expanded" | "collapsed" 35 + open: boolean 36 + setOpen: (open: boolean) => void 37 + openMobile: boolean 38 + setOpenMobile: (open: boolean) => void 39 + isMobile: boolean 40 + toggleSidebar: () => void 41 + } 42 + 43 + const SidebarContext = React.createContext<SidebarContextProps | null>(null) 44 + 45 + function useSidebar() { 46 + const context = React.useContext(SidebarContext) 47 + if (!context) { 48 + throw new Error("useSidebar must be used within a SidebarProvider.") 49 + } 50 + 51 + return context 52 + } 53 + 54 + function SidebarProvider({ 55 + defaultOpen = true, 56 + open: openProp, 57 + onOpenChange: setOpenProp, 58 + className, 59 + style, 60 + children, 61 + ...props 62 + }: React.ComponentProps<"div"> & { 63 + defaultOpen?: boolean 64 + open?: boolean 65 + onOpenChange?: (open: boolean) => void 66 + }) { 67 + const isMobile = useIsMobile() 68 + const [openMobile, setOpenMobile] = React.useState(false) 69 + 70 + // This is the internal state of the sidebar. 71 + // We use openProp and setOpenProp for control from outside the component. 72 + const [_open, _setOpen] = React.useState(defaultOpen) 73 + const open = openProp ?? _open 74 + const setOpen = React.useCallback( 75 + (value: boolean | ((value: boolean) => boolean)) => { 76 + const openState = typeof value === "function" ? value(open) : value 77 + if (setOpenProp) { 78 + setOpenProp(openState) 79 + } else { 80 + _setOpen(openState) 81 + } 82 + 83 + // This sets the cookie to keep the sidebar state. 84 + document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}` 85 + }, 86 + [setOpenProp, open] 87 + ) 88 + 89 + // Helper to toggle the sidebar. 90 + const toggleSidebar = React.useCallback(() => { 91 + return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open) 92 + }, [isMobile, setOpen, setOpenMobile]) 93 + 94 + // Adds a keyboard shortcut to toggle the sidebar. 95 + React.useEffect(() => { 96 + const handleKeyDown = (event: KeyboardEvent) => { 97 + if ( 98 + event.key === SIDEBAR_KEYBOARD_SHORTCUT && 99 + (event.metaKey || event.ctrlKey) 100 + ) { 101 + event.preventDefault() 102 + toggleSidebar() 103 + } 104 + } 105 + 106 + window.addEventListener("keydown", handleKeyDown) 107 + return () => window.removeEventListener("keydown", handleKeyDown) 108 + }, [toggleSidebar]) 109 + 110 + // We add a state so that we can do data-state="expanded" or "collapsed". 111 + // This makes it easier to style the sidebar with Tailwind classes. 112 + const state = open ? "expanded" : "collapsed" 113 + 114 + const contextValue = React.useMemo<SidebarContextProps>( 115 + () => ({ 116 + state, 117 + open, 118 + setOpen, 119 + isMobile, 120 + openMobile, 121 + setOpenMobile, 122 + toggleSidebar, 123 + }), 124 + [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] 125 + ) 126 + 127 + return ( 128 + <SidebarContext.Provider value={contextValue}> 129 + <TooltipProvider delayDuration={0}> 130 + <div 131 + data-slot="sidebar-wrapper" 132 + style={ 133 + { 134 + "--sidebar-width": SIDEBAR_WIDTH, 135 + "--sidebar-width-icon": SIDEBAR_WIDTH_ICON, 136 + ...style, 137 + } as React.CSSProperties 138 + } 139 + className={cn( 140 + "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", 141 + className 142 + )} 143 + {...props} 144 + > 145 + {children} 146 + </div> 147 + </TooltipProvider> 148 + </SidebarContext.Provider> 149 + ) 150 + } 151 + 152 + function Sidebar({ 153 + side = "left", 154 + variant = "sidebar", 155 + collapsible = "offcanvas", 156 + className, 157 + children, 158 + ...props 159 + }: React.ComponentProps<"div"> & { 160 + side?: "left" | "right" 161 + variant?: "sidebar" | "floating" | "inset" 162 + collapsible?: "offcanvas" | "icon" | "none" 163 + }) { 164 + const { isMobile, state, openMobile, setOpenMobile } = useSidebar() 165 + 166 + if (collapsible === "none") { 167 + return ( 168 + <div 169 + data-slot="sidebar" 170 + className={cn( 171 + "bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", 172 + className 173 + )} 174 + {...props} 175 + > 176 + {children} 177 + </div> 178 + ) 179 + } 180 + 181 + if (isMobile) { 182 + return ( 183 + <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}> 184 + <SheetContent 185 + data-sidebar="sidebar" 186 + data-slot="sidebar" 187 + data-mobile="true" 188 + className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden" 189 + style={ 190 + { 191 + "--sidebar-width": SIDEBAR_WIDTH_MOBILE, 192 + } as React.CSSProperties 193 + } 194 + side={side} 195 + > 196 + <SheetHeader className="sr-only"> 197 + <SheetTitle>Sidebar</SheetTitle> 198 + <SheetDescription>Displays the mobile sidebar.</SheetDescription> 199 + </SheetHeader> 200 + <div className="flex h-full w-full flex-col">{children}</div> 201 + </SheetContent> 202 + </Sheet> 203 + ) 204 + } 205 + 206 + return ( 207 + <div 208 + className="group peer text-sidebar-foreground hidden md:block" 209 + data-state={state} 210 + data-collapsible={state === "collapsed" ? collapsible : ""} 211 + data-variant={variant} 212 + data-side={side} 213 + data-slot="sidebar" 214 + > 215 + {/* This is what handles the sidebar gap on desktop */} 216 + <div 217 + data-slot="sidebar-gap" 218 + className={cn( 219 + "relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear", 220 + "group-data-[collapsible=offcanvas]:w-0", 221 + "group-data-[side=right]:rotate-180", 222 + variant === "floating" || variant === "inset" 223 + ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]" 224 + : "group-data-[collapsible=icon]:w-(--sidebar-width-icon)" 225 + )} 226 + /> 227 + <div 228 + data-slot="sidebar-container" 229 + className={cn( 230 + "fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex", 231 + side === "left" 232 + ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]" 233 + : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]", 234 + // Adjust the padding for floating and inset variants. 235 + variant === "floating" || variant === "inset" 236 + ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]" 237 + : "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l", 238 + className 239 + )} 240 + {...props} 241 + > 242 + <div 243 + data-sidebar="sidebar" 244 + data-slot="sidebar-inner" 245 + className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm" 246 + > 247 + {children} 248 + </div> 249 + </div> 250 + </div> 251 + ) 252 + } 253 + 254 + function SidebarTrigger({ 255 + className, 256 + onClick, 257 + ...props 258 + }: React.ComponentProps<typeof Button>) { 259 + const { toggleSidebar } = useSidebar() 260 + 261 + return ( 262 + <Button 263 + data-sidebar="trigger" 264 + data-slot="sidebar-trigger" 265 + variant="ghost" 266 + size="icon" 267 + className={cn("size-7", className)} 268 + onClick={(event) => { 269 + onClick?.(event) 270 + toggleSidebar() 271 + }} 272 + {...props} 273 + > 274 + <PanelLeftIcon /> 275 + <span className="sr-only">Toggle Sidebar</span> 276 + </Button> 277 + ) 278 + } 279 + 280 + function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { 281 + const { toggleSidebar } = useSidebar() 282 + 283 + return ( 284 + <button 285 + data-sidebar="rail" 286 + data-slot="sidebar-rail" 287 + aria-label="Toggle Sidebar" 288 + tabIndex={-1} 289 + onClick={toggleSidebar} 290 + title="Toggle Sidebar" 291 + className={cn( 292 + "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex", 293 + "in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize", 294 + "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize", 295 + "hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full", 296 + "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2", 297 + "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2", 298 + className 299 + )} 300 + {...props} 301 + /> 302 + ) 303 + } 304 + 305 + function SidebarInset({ className, ...props }: React.ComponentProps<"main">) { 306 + return ( 307 + <main 308 + data-slot="sidebar-inset" 309 + className={cn( 310 + "bg-background relative flex w-full flex-1 flex-col", 311 + "md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2", 312 + className 313 + )} 314 + {...props} 315 + /> 316 + ) 317 + } 318 + 319 + function SidebarInput({ 320 + className, 321 + ...props 322 + }: React.ComponentProps<typeof Input>) { 323 + return ( 324 + <Input 325 + data-slot="sidebar-input" 326 + data-sidebar="input" 327 + className={cn("bg-background h-8 w-full shadow-none", className)} 328 + {...props} 329 + /> 330 + ) 331 + } 332 + 333 + function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) { 334 + return ( 335 + <div 336 + data-slot="sidebar-header" 337 + data-sidebar="header" 338 + className={cn("flex flex-col gap-2 p-2", className)} 339 + {...props} 340 + /> 341 + ) 342 + } 343 + 344 + function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) { 345 + return ( 346 + <div 347 + data-slot="sidebar-footer" 348 + data-sidebar="footer" 349 + className={cn("flex flex-col gap-2 p-2", className)} 350 + {...props} 351 + /> 352 + ) 353 + } 354 + 355 + function SidebarSeparator({ 356 + className, 357 + ...props 358 + }: React.ComponentProps<typeof Separator>) { 359 + return ( 360 + <Separator 361 + data-slot="sidebar-separator" 362 + data-sidebar="separator" 363 + className={cn("bg-sidebar-border mx-2 w-auto", className)} 364 + {...props} 365 + /> 366 + ) 367 + } 368 + 369 + function SidebarContent({ className, ...props }: React.ComponentProps<"div">) { 370 + return ( 371 + <div 372 + data-slot="sidebar-content" 373 + data-sidebar="content" 374 + className={cn( 375 + "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden", 376 + className 377 + )} 378 + {...props} 379 + /> 380 + ) 381 + } 382 + 383 + function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) { 384 + return ( 385 + <div 386 + data-slot="sidebar-group" 387 + data-sidebar="group" 388 + className={cn("relative flex w-full min-w-0 flex-col p-2", className)} 389 + {...props} 390 + /> 391 + ) 392 + } 393 + 394 + function SidebarGroupLabel({ 395 + className, 396 + asChild = false, 397 + ...props 398 + }: React.ComponentProps<"div"> & { asChild?: boolean }) { 399 + const Comp = asChild ? Slot : "div" 400 + 401 + return ( 402 + <Comp 403 + data-slot="sidebar-group-label" 404 + data-sidebar="group-label" 405 + className={cn( 406 + "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", 407 + "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", 408 + className 409 + )} 410 + {...props} 411 + /> 412 + ) 413 + } 414 + 415 + function SidebarGroupAction({ 416 + className, 417 + asChild = false, 418 + ...props 419 + }: React.ComponentProps<"button"> & { asChild?: boolean }) { 420 + const Comp = asChild ? Slot : "button" 421 + 422 + return ( 423 + <Comp 424 + data-slot="sidebar-group-action" 425 + data-sidebar="group-action" 426 + className={cn( 427 + "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", 428 + // Increases the hit area of the button on mobile. 429 + "after:absolute after:-inset-2 md:after:hidden", 430 + "group-data-[collapsible=icon]:hidden", 431 + className 432 + )} 433 + {...props} 434 + /> 435 + ) 436 + } 437 + 438 + function SidebarGroupContent({ 439 + className, 440 + ...props 441 + }: React.ComponentProps<"div">) { 442 + return ( 443 + <div 444 + data-slot="sidebar-group-content" 445 + data-sidebar="group-content" 446 + className={cn("w-full text-sm", className)} 447 + {...props} 448 + /> 449 + ) 450 + } 451 + 452 + function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) { 453 + return ( 454 + <ul 455 + data-slot="sidebar-menu" 456 + data-sidebar="menu" 457 + className={cn("flex w-full min-w-0 flex-col gap-1", className)} 458 + {...props} 459 + /> 460 + ) 461 + } 462 + 463 + function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { 464 + return ( 465 + <li 466 + data-slot="sidebar-menu-item" 467 + data-sidebar="menu-item" 468 + className={cn("group/menu-item relative", className)} 469 + {...props} 470 + /> 471 + ) 472 + } 473 + 474 + const sidebarMenuButtonVariants = cva( 475 + "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", 476 + { 477 + variants: { 478 + variant: { 479 + default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", 480 + outline: 481 + "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]", 482 + }, 483 + size: { 484 + default: "h-8 text-sm", 485 + sm: "h-7 text-xs", 486 + lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!", 487 + }, 488 + }, 489 + defaultVariants: { 490 + variant: "default", 491 + size: "default", 492 + }, 493 + } 494 + ) 495 + 496 + function SidebarMenuButton({ 497 + asChild = false, 498 + isActive = false, 499 + variant = "default", 500 + size = "default", 501 + tooltip, 502 + className, 503 + ...props 504 + }: React.ComponentProps<"button"> & { 505 + asChild?: boolean 506 + isActive?: boolean 507 + tooltip?: string | React.ComponentProps<typeof TooltipContent> 508 + } & VariantProps<typeof sidebarMenuButtonVariants>) { 509 + const Comp = asChild ? Slot : "button" 510 + const { isMobile, state } = useSidebar() 511 + 512 + const button = ( 513 + <Comp 514 + data-slot="sidebar-menu-button" 515 + data-sidebar="menu-button" 516 + data-size={size} 517 + data-active={isActive} 518 + className={cn(sidebarMenuButtonVariants({ variant, size }), className)} 519 + {...props} 520 + /> 521 + ) 522 + 523 + if (!tooltip) { 524 + return button 525 + } 526 + 527 + if (typeof tooltip === "string") { 528 + tooltip = { 529 + children: tooltip, 530 + } 531 + } 532 + 533 + return ( 534 + <Tooltip> 535 + <TooltipTrigger asChild>{button}</TooltipTrigger> 536 + <TooltipContent 537 + side="right" 538 + align="center" 539 + hidden={state !== "collapsed" || isMobile} 540 + {...tooltip} 541 + /> 542 + </Tooltip> 543 + ) 544 + } 545 + 546 + function SidebarMenuAction({ 547 + className, 548 + asChild = false, 549 + showOnHover = false, 550 + ...props 551 + }: React.ComponentProps<"button"> & { 552 + asChild?: boolean 553 + showOnHover?: boolean 554 + }) { 555 + const Comp = asChild ? Slot : "button" 556 + 557 + return ( 558 + <Comp 559 + data-slot="sidebar-menu-action" 560 + data-sidebar="menu-action" 561 + className={cn( 562 + "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", 563 + // Increases the hit area of the button on mobile. 564 + "after:absolute after:-inset-2 md:after:hidden", 565 + "peer-data-[size=sm]/menu-button:top-1", 566 + "peer-data-[size=default]/menu-button:top-1.5", 567 + "peer-data-[size=lg]/menu-button:top-2.5", 568 + "group-data-[collapsible=icon]:hidden", 569 + showOnHover && 570 + "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0", 571 + className 572 + )} 573 + {...props} 574 + /> 575 + ) 576 + } 577 + 578 + function SidebarMenuBadge({ 579 + className, 580 + ...props 581 + }: React.ComponentProps<"div">) { 582 + return ( 583 + <div 584 + data-slot="sidebar-menu-badge" 585 + data-sidebar="menu-badge" 586 + className={cn( 587 + "text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none", 588 + "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground", 589 + "peer-data-[size=sm]/menu-button:top-1", 590 + "peer-data-[size=default]/menu-button:top-1.5", 591 + "peer-data-[size=lg]/menu-button:top-2.5", 592 + "group-data-[collapsible=icon]:hidden", 593 + className 594 + )} 595 + {...props} 596 + /> 597 + ) 598 + } 599 + 600 + function SidebarMenuSkeleton({ 601 + className, 602 + showIcon = false, 603 + ...props 604 + }: React.ComponentProps<"div"> & { 605 + showIcon?: boolean 606 + }) { 607 + // Random width between 50 to 90%. 608 + const width = React.useMemo(() => { 609 + return `${Math.floor(Math.random() * 40) + 50}%` 610 + }, []) 611 + 612 + return ( 613 + <div 614 + data-slot="sidebar-menu-skeleton" 615 + data-sidebar="menu-skeleton" 616 + className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)} 617 + {...props} 618 + > 619 + {showIcon && ( 620 + <Skeleton 621 + className="size-4 rounded-md" 622 + data-sidebar="menu-skeleton-icon" 623 + /> 624 + )} 625 + <Skeleton 626 + className="h-4 max-w-(--skeleton-width) flex-1" 627 + data-sidebar="menu-skeleton-text" 628 + style={ 629 + { 630 + "--skeleton-width": width, 631 + } as React.CSSProperties 632 + } 633 + /> 634 + </div> 635 + ) 636 + } 637 + 638 + function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) { 639 + return ( 640 + <ul 641 + data-slot="sidebar-menu-sub" 642 + data-sidebar="menu-sub" 643 + className={cn( 644 + "border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5", 645 + "group-data-[collapsible=icon]:hidden", 646 + className 647 + )} 648 + {...props} 649 + /> 650 + ) 651 + } 652 + 653 + function SidebarMenuSubItem({ 654 + className, 655 + ...props 656 + }: React.ComponentProps<"li">) { 657 + return ( 658 + <li 659 + data-slot="sidebar-menu-sub-item" 660 + data-sidebar="menu-sub-item" 661 + className={cn("group/menu-sub-item relative", className)} 662 + {...props} 663 + /> 664 + ) 665 + } 666 + 667 + function SidebarMenuSubButton({ 668 + asChild = false, 669 + size = "md", 670 + isActive = false, 671 + className, 672 + ...props 673 + }: React.ComponentProps<"a"> & { 674 + asChild?: boolean 675 + size?: "sm" | "md" 676 + isActive?: boolean 677 + }) { 678 + const Comp = asChild ? Slot : "a" 679 + 680 + return ( 681 + <Comp 682 + data-slot="sidebar-menu-sub-button" 683 + data-sidebar="menu-sub-button" 684 + data-size={size} 685 + data-active={isActive} 686 + className={cn( 687 + "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0", 688 + "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground", 689 + size === "sm" && "text-xs", 690 + size === "md" && "text-sm", 691 + "group-data-[collapsible=icon]:hidden", 692 + className 693 + )} 694 + {...props} 695 + /> 696 + ) 697 + } 698 + 699 + export { 700 + Sidebar, 701 + SidebarContent, 702 + SidebarFooter, 703 + SidebarGroup, 704 + SidebarGroupAction, 705 + SidebarGroupContent, 706 + SidebarGroupLabel, 707 + SidebarHeader, 708 + SidebarInput, 709 + SidebarInset, 710 + SidebarMenu, 711 + SidebarMenuAction, 712 + SidebarMenuBadge, 713 + SidebarMenuButton, 714 + SidebarMenuItem, 715 + SidebarMenuSkeleton, 716 + SidebarMenuSub, 717 + SidebarMenuSubButton, 718 + SidebarMenuSubItem, 719 + SidebarProvider, 720 + SidebarRail, 721 + SidebarSeparator, 722 + SidebarTrigger, 723 + useSidebar, 724 + }
+13
src/Den.Client.Web/src/components/ui/skeleton.tsx
··· 1 + import { cn } from "@/lib/utils" 2 + 3 + function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 + return ( 5 + <div 6 + data-slot="skeleton" 7 + className={cn("bg-accent animate-pulse rounded-md", className)} 8 + {...props} 9 + /> 10 + ) 11 + } 12 + 13 + export { Skeleton }
+38
src/Den.Client.Web/src/components/ui/sonner.tsx
··· 1 + import { 2 + CircleCheckIcon, 3 + InfoIcon, 4 + Loader2Icon, 5 + OctagonXIcon, 6 + TriangleAlertIcon, 7 + } from "lucide-react" 8 + import { useTheme } from "next-themes" 9 + import { Toaster as Sonner, type ToasterProps } from "sonner" 10 + 11 + const Toaster = ({ ...props }: ToasterProps) => { 12 + const { theme = "system" } = useTheme() 13 + 14 + return ( 15 + <Sonner 16 + theme={theme as ToasterProps["theme"]} 17 + className="toaster group" 18 + icons={{ 19 + success: <CircleCheckIcon className="size-4" />, 20 + info: <InfoIcon className="size-4" />, 21 + warning: <TriangleAlertIcon className="size-4" />, 22 + error: <OctagonXIcon className="size-4" />, 23 + loading: <Loader2Icon className="size-4 animate-spin" />, 24 + }} 25 + style={ 26 + { 27 + "--normal-bg": "var(--popover)", 28 + "--normal-text": "var(--popover-foreground)", 29 + "--normal-border": "var(--border)", 30 + "--border-radius": "var(--radius)", 31 + } as React.CSSProperties 32 + } 33 + {...props} 34 + /> 35 + ) 36 + } 37 + 38 + export { Toaster }
+114
src/Den.Client.Web/src/components/ui/table.tsx
··· 1 + import * as React from "react" 2 + 3 + import { cn } from "@/lib/utils" 4 + 5 + function Table({ className, ...props }: React.ComponentProps<"table">) { 6 + return ( 7 + <div 8 + data-slot="table-container" 9 + className="relative w-full overflow-x-auto" 10 + > 11 + <table 12 + data-slot="table" 13 + className={cn("w-full caption-bottom text-sm", className)} 14 + {...props} 15 + /> 16 + </div> 17 + ) 18 + } 19 + 20 + function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { 21 + return ( 22 + <thead 23 + data-slot="table-header" 24 + className={cn("[&_tr]:border-b", className)} 25 + {...props} 26 + /> 27 + ) 28 + } 29 + 30 + function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { 31 + return ( 32 + <tbody 33 + data-slot="table-body" 34 + className={cn("[&_tr:last-child]:border-0", className)} 35 + {...props} 36 + /> 37 + ) 38 + } 39 + 40 + function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { 41 + return ( 42 + <tfoot 43 + data-slot="table-footer" 44 + className={cn( 45 + "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", 46 + className 47 + )} 48 + {...props} 49 + /> 50 + ) 51 + } 52 + 53 + function TableRow({ className, ...props }: React.ComponentProps<"tr">) { 54 + return ( 55 + <tr 56 + data-slot="table-row" 57 + className={cn( 58 + "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", 59 + className 60 + )} 61 + {...props} 62 + /> 63 + ) 64 + } 65 + 66 + function TableHead({ className, ...props }: React.ComponentProps<"th">) { 67 + return ( 68 + <th 69 + data-slot="table-head" 70 + className={cn( 71 + "text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", 72 + className 73 + )} 74 + {...props} 75 + /> 76 + ) 77 + } 78 + 79 + function TableCell({ className, ...props }: React.ComponentProps<"td">) { 80 + return ( 81 + <td 82 + data-slot="table-cell" 83 + className={cn( 84 + "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", 85 + className 86 + )} 87 + {...props} 88 + /> 89 + ) 90 + } 91 + 92 + function TableCaption({ 93 + className, 94 + ...props 95 + }: React.ComponentProps<"caption">) { 96 + return ( 97 + <caption 98 + data-slot="table-caption" 99 + className={cn("text-muted-foreground mt-4 text-sm", className)} 100 + {...props} 101 + /> 102 + ) 103 + } 104 + 105 + export { 106 + Table, 107 + TableHeader, 108 + TableBody, 109 + TableFooter, 110 + TableHead, 111 + TableRow, 112 + TableCell, 113 + TableCaption, 114 + }
+18
src/Den.Client.Web/src/components/ui/textarea.tsx
··· 1 + import * as React from "react" 2 + 3 + import { cn } from "@/lib/utils" 4 + 5 + function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 + return ( 7 + <textarea 8 + data-slot="textarea" 9 + className={cn( 10 + "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 11 + className 12 + )} 13 + {...props} 14 + /> 15 + ) 16 + } 17 + 18 + export { Textarea }
+59
src/Den.Client.Web/src/components/ui/tooltip.tsx
··· 1 + import * as React from "react" 2 + import * as TooltipPrimitive from "@radix-ui/react-tooltip" 3 + 4 + import { cn } from "@/lib/utils" 5 + 6 + function TooltipProvider({ 7 + delayDuration = 0, 8 + ...props 9 + }: React.ComponentProps<typeof TooltipPrimitive.Provider>) { 10 + return ( 11 + <TooltipPrimitive.Provider 12 + data-slot="tooltip-provider" 13 + delayDuration={delayDuration} 14 + {...props} 15 + /> 16 + ) 17 + } 18 + 19 + function Tooltip({ 20 + ...props 21 + }: React.ComponentProps<typeof TooltipPrimitive.Root>) { 22 + return ( 23 + <TooltipProvider> 24 + <TooltipPrimitive.Root data-slot="tooltip" {...props} /> 25 + </TooltipProvider> 26 + ) 27 + } 28 + 29 + function TooltipTrigger({ 30 + ...props 31 + }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { 32 + return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} /> 33 + } 34 + 35 + function TooltipContent({ 36 + className, 37 + sideOffset = 0, 38 + children, 39 + ...props 40 + }: React.ComponentProps<typeof TooltipPrimitive.Content>) { 41 + return ( 42 + <TooltipPrimitive.Portal> 43 + <TooltipPrimitive.Content 44 + data-slot="tooltip-content" 45 + sideOffset={sideOffset} 46 + className={cn( 47 + "bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance", 48 + className 49 + )} 50 + {...props} 51 + > 52 + {children} 53 + <TooltipPrimitive.Arrow className="bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" /> 54 + </TooltipPrimitive.Content> 55 + </TooltipPrimitive.Portal> 56 + ) 57 + } 58 + 59 + export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
+129
src/Den.Client.Web/src/components/views/auth/login-form.tsx
··· 1 + import { useTranslation } from 'react-i18next'; 2 + import { useForm } from '@tanstack/react-form'; 3 + import { z } from 'zod'; 4 + 5 + import { Button } from '@/components/ui/button'; 6 + import { Field, /*FieldDescription,*/ FieldError, FieldGroup } from '@/components/ui/field'; 7 + import { Input } from '@/components/ui/input'; 8 + import { Label } from '@/components/ui/label'; 9 + import { Checkbox } from '@/components/ui/checkbox'; 10 + /*import { Link } from '@tanstack/react-router';*/ 11 + import { useLoginMutation } from '@/lib/state/queries/auth'; 12 + 13 + const formSchema = z.object({ 14 + username: z.string().nonempty('Please enter your username.'), 15 + password: z.string().nonempty('Please enter your password.'), 16 + rememberMe: z.boolean(), 17 + }); 18 + 19 + export const LoginForm = () => { 20 + const { t } = useTranslation(); 21 + const { mutate } = useLoginMutation(); 22 + 23 + const form = useForm({ 24 + defaultValues: { 25 + username: '', 26 + password: '', 27 + rememberMe: false, 28 + }, 29 + validators: { 30 + onSubmit: formSchema, 31 + }, 32 + onSubmit: async ({ value }) => { 33 + mutate({ 34 + body: { 35 + username: value.username, 36 + password: value.password, 37 + } 38 + }); 39 + }, 40 + }); 41 + 42 + return ( 43 + <form 44 + id="login-form" 45 + onSubmit={e => { 46 + e.preventDefault(); 47 + form.handleSubmit(); 48 + }} 49 + > 50 + <FieldGroup> 51 + <form.Field name="username"> 52 + {(field) => { 53 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 54 + return ( 55 + <Field data-invalid={isInvalid}> 56 + <Label htmlFor="username">{t('login.form.usernameLabel')}</Label> 57 + <Input 58 + id="username" 59 + name={field.name} 60 + value={field.state.value} 61 + onBlur={field.handleBlur} 62 + onChange={e => field.handleChange(e.target.value)} 63 + autoFocus 64 + autoComplete="username" 65 + tabIndex={1} 66 + aria-invalid={isInvalid} 67 + /> 68 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 69 + </Field> 70 + ); 71 + }} 72 + </form.Field> 73 + 74 + <form.Field name="password"> 75 + {(field) => { 76 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 77 + return ( 78 + <Field data-invalid={isInvalid}> 79 + <Label htmlFor="password">{t('login.form.passwordLabel')}</Label> 80 + <Input 81 + id="password" 82 + type="password" 83 + name={field.name} 84 + value={field.state.value} 85 + onBlur={field.handleBlur} 86 + onChange={e => field.handleChange(e.target.value)} 87 + autoComplete="password" 88 + tabIndex={2} 89 + aria-invalid={isInvalid} 90 + /> 91 + {/* TODO */} 92 + {/* <FieldDescription> */} 93 + {/* <Link */} 94 + {/* to="/" */} 95 + {/* > */} 96 + {/* {t('login.form.forgotPassword')} */} 97 + {/* </Link> */} 98 + {/* </FieldDescription> */} 99 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 100 + </Field> 101 + ); 102 + }} 103 + </form.Field> 104 + 105 + <form.Field name="rememberMe"> 106 + {(field) => { 107 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 108 + return ( 109 + <Field data-invalid={isInvalid}> 110 + <div className="flex items-center gap-3"> 111 + <Checkbox 112 + id="rememberMe" 113 + checked={field.state.value} 114 + onCheckedChange={(checked) => field.handleChange(checked === true)} 115 + tabIndex={3} 116 + aria-invalid={isInvalid} 117 + /> 118 + <Label htmlFor="rememberMe">{t('login.form.rememberLabel')}</Label> 119 + </div> 120 + </Field> 121 + ); 122 + }} 123 + </form.Field> 124 + 125 + <Button type="submit" tabIndex={4}>{t('login.form.doLogin')}</Button> 126 + </FieldGroup> 127 + </form> 128 + ); 129 + };
+153
src/Den.Client.Web/src/components/views/auth/signup-form.tsx
··· 1 + import { useTranslation } from 'react-i18next'; 2 + import { useForm } from '@tanstack/react-form'; 3 + import { z } from 'zod'; 4 + 5 + import { Button } from '@/components/ui/button'; 6 + import { Field, FieldDescription, FieldError, FieldGroup } from '@/components/ui/field'; 7 + import { Input } from '@/components/ui/input'; 8 + import { Label } from '@/components/ui/label'; 9 + import { toast } from 'sonner'; 10 + 11 + const formSchema = z 12 + .object({ 13 + username: z.string().nonempty('Please enter your username.'), 14 + email: z.email().nonempty('Please enter your email.'), 15 + password: z.string().nonempty('Please enter your password.'), 16 + passwordConfirm: z.string().nonempty('Confirm your password.'), 17 + }) 18 + .superRefine(({ password, passwordConfirm }, ctx) => { 19 + if (passwordConfirm !== password) ctx.addIssue({ 20 + code: 'custom', 21 + message: 'The passwords did not match.', 22 + path: ['passwordConfirm'], 23 + }); 24 + }); 25 + 26 + export const SignupForm = () => { 27 + const { t } = useTranslation(); 28 + 29 + const form = useForm({ 30 + defaultValues: { 31 + username: '', 32 + email: '', 33 + password: '', 34 + passwordConfirm: '', 35 + }, 36 + validators: { 37 + onSubmit: formSchema, 38 + }, 39 + onSubmit: async ({ value }) => { 40 + toast.success('Successfully signed up!', { 41 + description: `Signed up as ${value.username} (${value.email}), password ${value.password}.`, 42 + }); 43 + }, 44 + }); 45 + 46 + return ( 47 + <form 48 + id="signup-form" 49 + onSubmit={e => { 50 + e.preventDefault(); 51 + form.handleSubmit(); 52 + }} 53 + > 54 + <FieldGroup> 55 + <form.Field name="username"> 56 + {(field) => { 57 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 58 + return ( 59 + <Field data-invalid={isInvalid}> 60 + <Label htmlFor="username">{t('signup.form.usernameLabel')}</Label> 61 + <Input 62 + id="username" 63 + name={field.name} 64 + value={field.state.value} 65 + onBlur={field.handleBlur} 66 + onChange={e => field.handleChange(e.target.value)} 67 + autoFocus 68 + autoComplete="username" 69 + tabIndex={0} 70 + aria-invalid={isInvalid} 71 + /> 72 + <FieldDescription>{t('signup.form.usernameDescription')}</FieldDescription> 73 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 74 + </Field> 75 + ); 76 + }} 77 + </form.Field> 78 + 79 + <form.Field name="email"> 80 + {(field) => { 81 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 82 + return ( 83 + <Field data-invalid={isInvalid}> 84 + <Label htmlFor="email">{t('signup.form.emailLabel')}</Label> 85 + <Input 86 + id="email" 87 + type="email" 88 + name={field.name} 89 + value={field.state.value} 90 + onBlur={field.handleBlur} 91 + onChange={e => field.handleChange(e.target.value)} 92 + autoComplete="email" 93 + tabIndex={1} 94 + aria-invalid={isInvalid} 95 + /> 96 + <FieldDescription>{t('signup.form.emailDescription')}</FieldDescription> 97 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 98 + </Field> 99 + ); 100 + }} 101 + </form.Field> 102 + 103 + <form.Field name="password"> 104 + {(field) => { 105 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 106 + return ( 107 + <Field data-invalid={isInvalid}> 108 + <Label htmlFor="password">{t('signup.form.passwordLabel')}</Label> 109 + <Input 110 + id="password" 111 + type="password" 112 + name={field.name} 113 + value={field.state.value} 114 + onBlur={field.handleBlur} 115 + onChange={e => field.handleChange(e.target.value)} 116 + autoComplete="new-password" 117 + tabIndex={2} 118 + aria-invalid={isInvalid} 119 + /> 120 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 121 + </Field> 122 + ); 123 + }} 124 + </form.Field> 125 + 126 + <form.Field name="passwordConfirm"> 127 + {(field) => { 128 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 129 + return ( 130 + <Field data-invalid={isInvalid}> 131 + <Label htmlFor="passwordConfirm">{t('signup.form.passwordConfirmLabel')}</Label> 132 + <Input 133 + id="passwordConfirm" 134 + type="password" 135 + name={field.name} 136 + value={field.state.value} 137 + onBlur={field.handleBlur} 138 + onChange={e => field.handleChange(e.target.value)} 139 + autoComplete="new-password" 140 + tabIndex={3} 141 + aria-invalid={isInvalid} 142 + /> 143 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 144 + </Field> 145 + ); 146 + }} 147 + </form.Field> 148 + 149 + <Button type="submit" tabIndex={4}>{t('signup.form.doSignup')}</Button> 150 + </FieldGroup> 151 + </form> 152 + ); 153 + };
+180
src/Den.Client.Web/src/components/views/budgets/create-form.tsx
··· 1 + import { Button } from "@/components/ui/button"; 2 + import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel, FieldLegend, FieldSeparator, FieldSet } from "@/components/ui/field"; 3 + import { Input } from "@/components/ui/input"; 4 + import { Select, SelectContent, SelectTrigger, SelectValue, SelectItem } from "@/components/ui/select"; 5 + import { Textarea } from "@/components/ui/textarea"; 6 + import { getV1BudgetsQueryKey } from "@/lib/state/client/@tanstack/react-query.gen"; 7 + import { useBudgetsCreateMutation } from "@/lib/state/queries/budgets"; 8 + import { useForm } from "@tanstack/react-form"; 9 + import { useQueryClient } from "@tanstack/react-query"; 10 + import { useRouter } from "@tanstack/react-router"; 11 + import { t } from "i18next"; 12 + import z from "zod"; 13 + 14 + const formSchema = z.object({ 15 + displayName: z.string().nonempty('Please enter your username.'), 16 + description: z.string().max(255), 17 + currency: z.enum(['USD', 'EUR', 'GBP', 'COP']), 18 + total: z.int().min(0), 19 + }); 20 + 21 + export const CreateBudgetForm = () => { 22 + const { mutateAsync } = useBudgetsCreateMutation(); 23 + const queryClient = useQueryClient(); 24 + const router = useRouter(); 25 + 26 + const form = useForm({ 27 + defaultValues: { 28 + displayName: '', 29 + description: '', 30 + currency: 'GBP', 31 + total: 0, 32 + }, 33 + validators: { 34 + onSubmit: formSchema, 35 + }, 36 + onSubmit: async ({ value }) => { 37 + await mutateAsync({ 38 + body: { 39 + displayName: value.displayName, 40 + description: value.description, 41 + currency: value.currency, 42 + total: value.total, 43 + period: 'Monthly', 44 + }, 45 + }); 46 + 47 + queryClient.invalidateQueries({ queryKey: getV1BudgetsQueryKey() }); 48 + 49 + router.navigate({ to: '/budgets' }); 50 + }, 51 + }); 52 + 53 + return ( 54 + <form 55 + id="create-budget-form" 56 + onSubmit={e => { 57 + e.preventDefault(); 58 + form.handleSubmit(); 59 + }} 60 + > 61 + <FieldGroup> 62 + <FieldSet> 63 + <form.Field name="displayName"> 64 + {field => { 65 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 66 + return ( 67 + <Field data-invalid={isInvalid}> 68 + <FieldLabel htmlFor="displayName"> 69 + {t('budgeting.create.form.displayNameLabel')} 70 + </FieldLabel> 71 + <Input 72 + id="displayName" 73 + name={field.name} 74 + value={field.state.value} 75 + onBlur={field.handleBlur} 76 + onChange={e => field.handleChange(e.target.value)} 77 + autoFocus 78 + tabIndex={1} 79 + aria-invalid={isInvalid} 80 + /> 81 + <FieldDescription>What should we call this budget?</FieldDescription> 82 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 83 + </Field> 84 + ); 85 + }} 86 + </form.Field> 87 + 88 + <form.Field name="description"> 89 + {field => { 90 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 91 + return ( 92 + <Field data-invalid={isInvalid}> 93 + <FieldLabel htmlFor="description"> 94 + {t('budgeting.create.form.descriptionLabel')} 95 + <span className="text-muted-foreground">{t('optional')}</span> 96 + </FieldLabel> 97 + <Textarea 98 + id="description" 99 + name={field.name} 100 + value={field.state.value} 101 + onBlur={field.handleBlur} 102 + onChange={e => field.handleChange(e.target.value)} 103 + tabIndex={2} 104 + aria-invalid={isInvalid} 105 + /> 106 + <FieldDescription>Is there any information you want to display alongside your budget?</FieldDescription> 107 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 108 + </Field> 109 + ); 110 + }} 111 + </form.Field> 112 + </FieldSet> 113 + 114 + <FieldSeparator /> 115 + 116 + <FieldSet> 117 + <FieldLegend>{t('budgeting.create.form.scope.title')}</FieldLegend> 118 + <FieldDescription>{t('budgeting.create.form.scope.subtitle')}</FieldDescription> 119 + 120 + <div className="grid grid-cols-1 md:grid-cols-[3fr_1fr] gap-4"> 121 + <form.Field name="total"> 122 + {field => { 123 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 124 + return ( 125 + <Field data-invalid={isInvalid}> 126 + <FieldLabel htmlFor="total">{t('budgeting.create.form.scope.totalLabel')}</FieldLabel> 127 + <Input 128 + id="total" 129 + type="number" 130 + name={field.name} 131 + value={field.state.value} 132 + onBlur={field.handleBlur} 133 + onChange={e => field.handleChange(parseFloat(e.target.value))} 134 + tabIndex={3} 135 + aria-invalid={isInvalid} 136 + /> 137 + <FieldDescription>How much will this budget let you spend?</FieldDescription> 138 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 139 + </Field> 140 + ); 141 + }} 142 + </form.Field> 143 + 144 + <form.Field name="currency"> 145 + {field => { 146 + const isInvalid = field.state.meta.isTouched && !field.state.meta.isValid; 147 + return ( 148 + <Field data-invalid={isInvalid}> 149 + <FieldLabel htmlFor="currency">{t('budgeting.create.form.scope.currencyLabel')}</FieldLabel> 150 + <Select 151 + defaultValue="GBP" 152 + onValueChange={value => field.handleChange(value)} 153 + > 154 + <SelectTrigger id="currency" tabIndex={4}> 155 + <SelectValue placeholder="ABC" /> 156 + </SelectTrigger> 157 + <SelectContent> 158 + <SelectItem value="USD">{t('budgeting.currencies.USD')}</SelectItem> 159 + <SelectItem value="EUR">{t('budgeting.currencies.EUR')}</SelectItem> 160 + <SelectItem value="GBP">{t('budgeting.currencies.GBP')}</SelectItem> 161 + <SelectItem value="COP">{t('budgeting.currencies.COP')}</SelectItem> 162 + </SelectContent> 163 + </Select> 164 + {isInvalid && <FieldError errors={field.state.meta.errors} />} 165 + </Field> 166 + ); 167 + }} 168 + </form.Field> 169 + </div> 170 + </FieldSet> 171 + <Field orientation="horizontal"> 172 + <Button type="submit" form="create-budget-form" disabled={form.state.isSubmitting}> 173 + {t('submit')} 174 + </Button> 175 + </Field> 176 + </FieldGroup> 177 + 178 + </form> 179 + ); 180 + };
+51
src/Den.Client.Web/src/components/views/budgets/list/budgets-list.tsx
··· 1 + import { Button } from "@/components/ui/button"; 2 + import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"; 3 + import { Skeleton } from "@/components/ui/skeleton"; 4 + import { useBudgetsListQuery } from "@/lib/state/queries/budgets"; 5 + import { AlertCircle, CirclePoundSterlingIcon } from "lucide-react"; 6 + import { BudgetsListTable } from "./table"; 7 + import { Link } from "@tanstack/react-router"; 8 + 9 + export const BudgetsList = () => { 10 + const { data, isLoading, isPending, isError } = useBudgetsListQuery(); 11 + 12 + if (isLoading || isPending) 13 + return ( 14 + <Skeleton className="w-[100px]" /> 15 + ); 16 + 17 + if (isError) { 18 + return <AlertCircle /> 19 + } 20 + 21 + if (data.length === 0) return <BudgetsListEmptyState />; 22 + 23 + return <BudgetsListTable data={data} />; 24 + }; 25 + 26 + export const BudgetsListEmptyState = () => { 27 + return ( 28 + <div className="flex flex-1 flex-col items-center justify-center gap-4"> 29 + 30 + <Empty> 31 + <EmptyHeader> 32 + <EmptyMedia variant="icon"> 33 + <CirclePoundSterlingIcon /> 34 + </EmptyMedia> 35 + <EmptyTitle>No budgets yet</EmptyTitle> 36 + <EmptyDescription>You haven't created any budgets yet. Get started by creating your first budget.</EmptyDescription> 37 + </EmptyHeader> 38 + <EmptyContent> 39 + <div className="flex gap-2"> 40 + <Button asChild> 41 + <Link to="/budgets/create"> 42 + Create Budget 43 + </Link> 44 + </Button> 45 + </div> 46 + </EmptyContent> 47 + </Empty> 48 + 49 + </div> 50 + ); 51 + };
+103
src/Den.Client.Web/src/components/views/budgets/list/table.tsx
··· 1 + import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; 2 + import type { BudgetResponse } from "@/lib/state/client"; 3 + 4 + import { useState } from 'react'; 5 + import { 6 + flexRender, 7 + getCoreRowModel, 8 + getFilteredRowModel, 9 + getPaginationRowModel, 10 + getSortedRowModel, 11 + useReactTable, 12 + type ColumnDef, 13 + type ColumnFiltersState, 14 + type SortingState, 15 + type VisibilityState 16 + } from '@tanstack/react-table'; 17 + import { Link } from '@tanstack/react-router'; 18 + import { Button } from '@/components/ui/button'; 19 + 20 + export const columns: ColumnDef<BudgetResponse>[] = [ 21 + { 22 + accessorKey: 'displayName', 23 + header: 'Display Name', 24 + cell: ({ row }) => ( 25 + <Button variant="link" size="sm" asChild> 26 + <Link to={`/budgets`}>{row.original.displayName}</Link> 27 + </Button> 28 + ), 29 + }, 30 + { 31 + accessorKey: 'description', 32 + header: 'Description', 33 + }, 34 + { 35 + header: 'Total amount', 36 + accessorFn: row => `${row.currency} ${row.total.toFixed(2)}`, 37 + }, 38 + ]; 39 + 40 + export const BudgetsListTable = ({ data }: { data: BudgetResponse[]; }) => { 41 + const [sorting, setSorting] = useState<SortingState>([]); 42 + const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); 43 + const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({}); 44 + 45 + const table = useReactTable({ 46 + data, 47 + columns, 48 + onSortingChange: setSorting, 49 + onColumnFiltersChange: setColumnFilters, 50 + getCoreRowModel: getCoreRowModel(), 51 + getPaginationRowModel: getPaginationRowModel(), 52 + getSortedRowModel: getSortedRowModel(), 53 + getFilteredRowModel: getFilteredRowModel(), 54 + onColumnVisibilityChange: setColumnVisibility, 55 + state: { 56 + sorting, 57 + columnFilters, 58 + columnVisibility, 59 + }, 60 + }); 61 + 62 + return ( 63 + <div className="w-full"> 64 + <div className="overflow-hidden rounded-md border"> 65 + <Table> 66 + <TableHeader> 67 + {table.getHeaderGroups().map((headerGroup) => ( 68 + <TableRow key={headerGroup.id}> 69 + {headerGroup.headers.map((header) => { 70 + return ( 71 + <TableHead key={header.id}> 72 + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} 73 + </TableHead> 74 + ); 75 + })} 76 + </TableRow> 77 + ))} 78 + </TableHeader> 79 + 80 + <TableBody> 81 + {table.getRowModel().rows.length ? ( 82 + table.getRowModel().rows.map((row) => ( 83 + <TableRow 84 + key={row.id} 85 + data-state={row.getIsSelected() && "selected"} 86 + > 87 + {row.getVisibleCells().map((cell) => ( 88 + <TableCell key={cell.id}> 89 + {flexRender( 90 + cell.column.columnDef.cell, 91 + cell.getContext() 92 + )} 93 + </TableCell> 94 + ))} 95 + </TableRow> 96 + )) 97 + ) : null} 98 + </TableBody> 99 + </Table> 100 + </div> 101 + </div> 102 + ); 103 + };
+116
src/Den.Client.Web/src/components/views/dashboard/budget-cards.tsx
··· 1 + import type { ChartConfig } from "@/components/ui/chart"; 2 + import { Card, CardContent } from '@/components/ui/card'; 3 + import { ChartContainer } from "@/components/ui/chart"; 4 + import { PolarAngleAxis, RadialBar, RadialBarChart } from "recharts"; 5 + import { Button } from '@/components/ui/button'; 6 + import { useTranslation } from "react-i18next"; 7 + 8 + const data = [ 9 + { 10 + name: "Household", 11 + progress: 25, 12 + budget: "£1,000", 13 + current: "£250", 14 + href: "#", 15 + fill: "var(--chart-1)", 16 + }, 17 + { 18 + name: "Groceries", 19 + progress: 55, 20 + budget: "£1,000", 21 + current: "£550", 22 + href: "#", 23 + fill: "var(--chart-2)", 24 + }, 25 + { 26 + name: "Bills & Services", 27 + progress: 85, 28 + budget: "£1,000", 29 + current: "£850", 30 + href: "#", 31 + fill: "var(--chart-3)", 32 + }, 33 + { 34 + name: "Disposable Income", 35 + progress: 70, 36 + budget: "£2,000", 37 + current: "£1,400", 38 + href: "#", 39 + fill: "var(--chart-4)", 40 + }, 41 + ]; 42 + 43 + const chartConfig = { 44 + progress: { 45 + label: "Progress", 46 + color: "var(--primary)", 47 + }, 48 + } satisfies ChartConfig; 49 + 50 + export const BudgetCards = () => { 51 + const { t } = useTranslation(); 52 + 53 + return ( 54 + <div className="flex items-center justify-center w-full"> 55 + <dl className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4 w-full"> 56 + {data.map((item) => ( 57 + <Card key={item.name} className="p-0 gap-0"> 58 + <CardContent className="p-4"> 59 + <div className="flex items-center space-x-3"> 60 + <div className="relative flex items-center justify-center"> 61 + <ChartContainer 62 + config={chartConfig} 63 + className="h-[80px] w-[80px]" 64 + > 65 + <RadialBarChart 66 + data={[item]} 67 + innerRadius={30} 68 + outerRadius={60} 69 + barSize={6} 70 + startAngle={90} 71 + endAngle={-270} 72 + > 73 + <PolarAngleAxis 74 + type="number" 75 + domain={[0, 100]} 76 + angleAxisId={0} 77 + tick={false} 78 + axisLine={false} 79 + /> 80 + <RadialBar 81 + dataKey="progress" 82 + background 83 + cornerRadius={10} 84 + fill={item.fill} 85 + angleAxisId={0} 86 + /> 87 + </RadialBarChart> 88 + </ChartContainer> 89 + <div className="absolute inset-0 flex items-center justify-center"> 90 + <span className="text-base font-medium text-foreground"> 91 + {item.progress}% 92 + </span> 93 + </div> 94 + </div> 95 + <div> 96 + <dd className="text-base font-medium text-foreground"> 97 + {item.current} / {item.budget} 98 + </dd> 99 + <dt className="text-sm text-muted-foreground"> 100 + {item.name} 101 + </dt> 102 + </div> 103 + 104 + <Button variant="outline" asChild className="ml-auto"> 105 + <a href={item.href}> 106 + {t('misc.doViewMore')} 107 + </a> 108 + </Button> 109 + </div> 110 + </CardContent> 111 + </Card> 112 + ))} 113 + </dl> 114 + </div> 115 + ); 116 + };
+61
src/Den.Client.Web/src/components/views/dashboard/summary-cards.tsx
··· 1 + import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 2 + import { CalendarIcon, ClapperboardIcon, LightbulbIcon, ShoppingBasketIcon } from "lucide-react"; 3 + import { useTranslation } from "react-i18next"; 4 + 5 + export const SummaryCards = () => { 6 + const { t } = useTranslation(); 7 + 8 + return ( 9 + <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> 10 + <Card> 11 + <CardHeader> 12 + <div className="flex items-center gap-4"> 13 + <ShoppingBasketIcon /> 14 + <CardTitle>{t('dashboard.groceriesCountTitle')}</CardTitle> 15 + </div> 16 + </CardHeader> 17 + <CardContent> 18 + <span className="text-3xl font-bold">14</span> 19 + </CardContent> 20 + </Card> 21 + 22 + <Card> 23 + <CardHeader> 24 + <div className="flex items-center gap-4"> 25 + <LightbulbIcon /> 26 + <CardTitle>{t('dashboard.remindersCountTitle')}</CardTitle> 27 + </div> 28 + </CardHeader> 29 + <CardContent> 30 + <span className="text-3xl font-bold">10</span> 31 + </CardContent> 32 + </Card> 33 + 34 + <Card> 35 + <CardHeader> 36 + <div className="flex items-center gap-4"> 37 + <CalendarIcon /> 38 + <CardTitle>{t('dashboard.calendarEventsTitle')}</CardTitle> 39 + </div> 40 + </CardHeader> 41 + <CardContent className="flex items-center gap-2"> 42 + <span className="text-3xl font-bold">3</span> 43 + <span className="text-sm">{t('dashboard.calendarEventsCountSubtext')}</span> 44 + </CardContent> 45 + </Card> 46 + 47 + <Card> 48 + <CardHeader> 49 + <div className="flex items-center gap-4"> 50 + <ClapperboardIcon /> 51 + <CardTitle>{t('dashboard.watchlistCountTitle')}</CardTitle> 52 + </div> 53 + </CardHeader> 54 + <CardContent className="flex items-end gap-2"> 55 + <span className="text-3xl font-bold">2</span> 56 + <span className="text-sm">{t('dashboard.watchlistCountSubtext')}</span> 57 + </CardContent> 58 + </Card> 59 + </div> 60 + ); 61 + };
+8
src/Den.Client.Web/src/env.d.ts
··· 1 + import { resources, defaultNS } from './i18n'; 2 + 3 + declare module 'i18next' { 4 + interface CustomTypeOptions { 5 + defaultNS: typeof defaultNS; 6 + resources: typeof resources["en"]; 7 + } 8 + }
+19
src/Den.Client.Web/src/hooks/use-mobile.ts
··· 1 + import * as React from "react" 2 + 3 + const MOBILE_BREAKPOINT = 768 4 + 5 + export function useIsMobile() { 6 + const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) 7 + 8 + React.useEffect(() => { 9 + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 + const onChange = () => { 11 + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 + } 13 + mql.addEventListener("change", onChange) 14 + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 + return () => mql.removeEventListener("change", onChange) 16 + }, []) 17 + 18 + return !!isMobile 19 + }
+136
src/Den.Client.Web/src/i18n.ts
··· 1 + import i18n from 'i18next'; 2 + import { initReactI18next } from 'react-i18next'; 3 + 4 + export const defaultNS = "main"; 5 + export const resources = { 6 + en: { 7 + main: { 8 + // App Strings 9 + appTitle: 'Den', 10 + 11 + // Generic Strings 12 + optional: '(optional)', 13 + submit: 'Submit', 14 + 15 + // Dashboard 16 + dashboard: { 17 + title: 'Dashboard', 18 + 19 + groceriesCountTitle: 'Groceries', 20 + remindersCountTitle: 'Reminders', 21 + 22 + calendarEventsTitle: 'Events', 23 + calendarEventsCountSubtext: 'event(s) today', 24 + 25 + watchlistCountTitle: 'Watchlist', 26 + watchlistCountSubtext: 'in the next 30 days', 27 + }, 28 + 29 + menu: { 30 + home: { 31 + title: 'Home', 32 + dashboard: 'Dashboard', 33 + }, 34 + organisation: { 35 + title: 'Organisation', 36 + groceries: 'Groceries', 37 + calendar: 'Calendar', 38 + reminders: 'Reminders', 39 + recipes: 'Recipes', 40 + }, 41 + budgeting: { 42 + title: 'Budgeting', 43 + budgets: 'Budgets', 44 + allBudgets: 'All Budgets', 45 + }, 46 + admin: { 47 + title: 'Admin', 48 + settings: 'Configuration', 49 + users: 'Users', 50 + }, 51 + userSettings: { 52 + title: 'Settings', 53 + doLogOut: 'Log out', 54 + }, 55 + }, 56 + 57 + budgeting: { 58 + budgets: { 59 + title: 'Budgets', 60 + description: 'Manage your budgets to keep track of your finances.', 61 + }, 62 + create: { 63 + title: 'Create new budget', 64 + description: 'Start tracking your finances against a budget!', 65 + doNewBudget: 'New budget', 66 + 67 + form: { 68 + displayNameLabel: 'Budget name', 69 + descriptionLabel: 'Description', 70 + scope: { 71 + title: 'Budget scope', 72 + subtitle: 'Configure the scope of your budget by setting the amount and in what currency it applies.', 73 + totalLabel: 'Total Amount', 74 + currencyLabel: 'Currency', 75 + }, 76 + }, 77 + }, 78 + currencies: { 79 + USD: 'USD ($)', 80 + GBP: 'GBP (£)', 81 + EUR: 'EUR (€)', 82 + COP: 'COP ($)', 83 + }, 84 + }, 85 + 86 + // Login Form 87 + login: { 88 + page: { title: 'Log in' }, 89 + form: { 90 + title: 'Log in to your account', 91 + subtitle: 'Enter your username and password below.', 92 + 93 + usernameLabel: 'Username', 94 + passwordLabel: 'Password', 95 + forgotPassword: 'Forgot your password?', 96 + rememberLabel: 'Remember Me?', 97 + doLogin: 'Log in', 98 + }, 99 + }, 100 + 101 + // Signup Form 102 + signup: { 103 + page: { title: 'Sign up' }, 104 + form: { 105 + title: 'Create an account', 106 + subtitle: 'Sign up for this Den instance.', 107 + usernameLabel: 'Username', 108 + usernameDescription: 'This is the username you use to log in to Den.', 109 + emailLabel: 'Email address', 110 + emailDescription: 'We never share your email with anyone.', 111 + passwordLabel: 'Password', 112 + passwordConfirmLabel: 'Confirm Password', 113 + doSignup: 'Sign up', 114 + }, 115 + }, 116 + 117 + misc: { 118 + doViewMore: 'View more', 119 + }, 120 + }, 121 + }, 122 + }; 123 + 124 + i18n 125 + .use(initReactI18next) 126 + .init({ 127 + lng: 'en', 128 + interpolation: { 129 + escapeValue: false, 130 + }, 131 + ns: ["main"], 132 + defaultNS, 133 + resources, 134 + }); 135 + 136 + export default i18n;
+120
src/Den.Client.Web/src/index.css
··· 1 + @import "tailwindcss"; 2 + @import "tw-animate-css"; 3 + 4 + @custom-variant dark (&:is(.dark *)); 5 + 6 + @theme inline { 7 + --radius-sm: calc(var(--radius) - 4px); 8 + --radius-md: calc(var(--radius) - 2px); 9 + --radius-lg: var(--radius); 10 + --radius-xl: calc(var(--radius) + 4px); 11 + --color-background: var(--background); 12 + --color-foreground: var(--foreground); 13 + --color-card: var(--card); 14 + --color-card-foreground: var(--card-foreground); 15 + --color-popover: var(--popover); 16 + --color-popover-foreground: var(--popover-foreground); 17 + --color-primary: var(--primary); 18 + --color-primary-foreground: var(--primary-foreground); 19 + --color-secondary: var(--secondary); 20 + --color-secondary-foreground: var(--secondary-foreground); 21 + --color-muted: var(--muted); 22 + --color-muted-foreground: var(--muted-foreground); 23 + --color-accent: var(--accent); 24 + --color-accent-foreground: var(--accent-foreground); 25 + --color-destructive: var(--destructive); 26 + --color-border: var(--border); 27 + --color-input: var(--input); 28 + --color-ring: var(--ring); 29 + --color-chart-1: var(--chart-1); 30 + --color-chart-2: var(--chart-2); 31 + --color-chart-3: var(--chart-3); 32 + --color-chart-4: var(--chart-4); 33 + --color-chart-5: var(--chart-5); 34 + --color-sidebar: var(--sidebar); 35 + --color-sidebar-foreground: var(--sidebar-foreground); 36 + --color-sidebar-primary: var(--sidebar-primary); 37 + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 38 + --color-sidebar-accent: var(--sidebar-accent); 39 + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 40 + --color-sidebar-border: var(--sidebar-border); 41 + --color-sidebar-ring: var(--sidebar-ring); 42 + } 43 + 44 + :root { 45 + --radius: 0.65rem; 46 + --background: oklch(1 0 0); 47 + --foreground: oklch(0.141 0.005 285.823); 48 + --card: oklch(1 0 0); 49 + --card-foreground: oklch(0.141 0.005 285.823); 50 + --popover: oklch(1 0 0); 51 + --popover-foreground: oklch(0.141 0.005 285.823); 52 + --primary: oklch(0.646 0.222 41.116); 53 + --primary-foreground: oklch(0.98 0.016 73.684); 54 + --secondary: oklch(0.967 0.001 286.375); 55 + --secondary-foreground: oklch(0.21 0.006 285.885); 56 + --muted: oklch(0.967 0.001 286.375); 57 + --muted-foreground: oklch(0.552 0.016 285.938); 58 + --accent: oklch(0.967 0.001 286.375); 59 + --accent-foreground: oklch(0.21 0.006 285.885); 60 + --destructive: oklch(0.577 0.245 27.325); 61 + --border: oklch(0.92 0.004 286.32); 62 + --input: oklch(0.92 0.004 286.32); 63 + --ring: oklch(0.75 0.183 55.934); 64 + --chart-1: oklch(0.837 0.128 66.29); 65 + --chart-2: oklch(0.705 0.213 47.604); 66 + --chart-3: oklch(0.646 0.222 41.116); 67 + --chart-4: oklch(0.553 0.195 38.402); 68 + --chart-5: oklch(0.47 0.157 37.304); 69 + --sidebar: oklch(0.985 0 0); 70 + --sidebar-foreground: oklch(0.141 0.005 285.823); 71 + --sidebar-primary: oklch(0.646 0.222 41.116); 72 + --sidebar-primary-foreground: oklch(0.98 0.016 73.684); 73 + --sidebar-accent: oklch(0.967 0.001 286.375); 74 + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); 75 + --sidebar-border: oklch(0.92 0.004 286.32); 76 + --sidebar-ring: oklch(0.75 0.183 55.934); 77 + } 78 + 79 + .dark { 80 + --background: oklch(0.141 0.005 285.823); 81 + --foreground: oklch(0.985 0 0); 82 + --card: oklch(0.21 0.006 285.885); 83 + --card-foreground: oklch(0.985 0 0); 84 + --popover: oklch(0.21 0.006 285.885); 85 + --popover-foreground: oklch(0.985 0 0); 86 + --primary: oklch(0.705 0.213 47.604); 87 + --primary-foreground: oklch(0.98 0.016 73.684); 88 + --secondary: oklch(0.274 0.006 286.033); 89 + --secondary-foreground: oklch(0.985 0 0); 90 + --muted: oklch(0.274 0.006 286.033); 91 + --muted-foreground: oklch(0.705 0.015 286.067); 92 + --accent: oklch(0.274 0.006 286.033); 93 + --accent-foreground: oklch(0.985 0 0); 94 + --destructive: oklch(0.704 0.191 22.216); 95 + --border: oklch(1 0 0 / 10%); 96 + --input: oklch(1 0 0 / 15%); 97 + --ring: oklch(0.408 0.123 38.172); 98 + --chart-1: oklch(0.837 0.128 66.29); 99 + --chart-2: oklch(0.705 0.213 47.604); 100 + --chart-3: oklch(0.646 0.222 41.116); 101 + --chart-4: oklch(0.553 0.195 38.402); 102 + --chart-5: oklch(0.47 0.157 37.304); 103 + --sidebar: oklch(0.21 0.006 285.885); 104 + --sidebar-foreground: oklch(0.985 0 0); 105 + --sidebar-primary: oklch(0.705 0.213 47.604); 106 + --sidebar-primary-foreground: oklch(0.98 0.016 73.684); 107 + --sidebar-accent: oklch(0.274 0.006 286.033); 108 + --sidebar-accent-foreground: oklch(0.985 0 0); 109 + --sidebar-border: oklch(1 0 0 / 10%); 110 + --sidebar-ring: oklch(0.408 0.123 38.172); 111 + } 112 + 113 + @layer base { 114 + * { 115 + @apply border-border outline-ring/50; 116 + } 117 + body { 118 + @apply bg-background text-foreground; 119 + } 120 + }
+104
src/Den.Client.Web/src/lib/state/auth.tsx
··· 1 + import { createContext, useCallback, useContext, useEffect, useState, type PropsWithChildren } from "react"; 2 + import { postV1AuthRefresh } from "./client"; 3 + import { client } from "./client/client.gen"; 4 + 5 + type AuthTokens = { 6 + accessToken: string; 7 + refreshToken: string; 8 + }; 9 + 10 + export type AuthContext = { 11 + tokens: AuthTokens | null; 12 + refresh: () => Promise<void>; 13 + login: (tokens: AuthTokens) => void; 14 + logout: () => void; 15 + isAuthenticated: boolean; 16 + }; 17 + 18 + export const authContext = createContext<AuthContext | undefined>(undefined); 19 + 20 + const ACCESS_TOKEN_KEY = 'den_access_token'; 21 + const REFRESH_TOKEN_KEY = 'den_refresh_token'; 22 + 23 + export const AuthProvider = ({ children }: PropsWithChildren) => { 24 + const [tokens, setTokens] = useState<AuthTokens | null>(() => { 25 + const accessToken = localStorage.getItem(ACCESS_TOKEN_KEY); 26 + const refreshToken = localStorage.getItem(REFRESH_TOKEN_KEY); 27 + return accessToken && refreshToken ? { accessToken, refreshToken } : null; 28 + }); 29 + 30 + useEffect(() => { 31 + if (tokens) { 32 + localStorage.setItem(ACCESS_TOKEN_KEY, tokens.accessToken); 33 + localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refreshToken); 34 + client.setConfig({ auth: tokens.accessToken }); 35 + } else { 36 + localStorage.removeItem(ACCESS_TOKEN_KEY); 37 + localStorage.removeItem(REFRESH_TOKEN_KEY); 38 + client.setConfig({ auth: undefined }); 39 + } 40 + }, [tokens]); 41 + 42 + const login = useCallback((newTokens: AuthTokens) => { 43 + setTokens(newTokens); 44 + }, []); 45 + 46 + const logout = useCallback(() => { 47 + setTokens(null); 48 + }, []); 49 + 50 + const refresh = useCallback(async () => { 51 + const currentRefreshToken = localStorage.getItem(REFRESH_TOKEN_KEY); 52 + if (!currentRefreshToken) return; 53 + 54 + try { 55 + const { data } = await postV1AuthRefresh({ 56 + body: { refreshToken: currentRefreshToken }, 57 + }); 58 + 59 + setTokens({ 60 + accessToken: data?.accessToken!!, 61 + refreshToken: currentRefreshToken, 62 + }); 63 + } catch (err) { 64 + // Refresh failed, log out user. 65 + logout(); 66 + } 67 + }, [logout]); 68 + 69 + // Refresh on mount if we have a refresh token 70 + useEffect(() => { 71 + if (tokens?.refreshToken) { 72 + refresh(); 73 + } 74 + }, []); 75 + 76 + // Periodic refresh 77 + useEffect(() => { 78 + if (!tokens?.refreshToken) return; 79 + 80 + const refreshInterval = setInterval(refresh, 2.5 * 60 * 1000 /* Every 2.5 minutes (a half token life-time) */); 81 + 82 + return () => clearInterval(refreshInterval); 83 + }, [tokens?.refreshToken, refresh]); 84 + 85 + return ( 86 + <authContext.Provider value={{ 87 + tokens, 88 + login, 89 + logout, 90 + refresh, 91 + isAuthenticated: !!tokens, 92 + }}> 93 + {children} 94 + </authContext.Provider> 95 + ) 96 + }; 97 + 98 + export const useAuth = () => { 99 + const ctx = useContext(authContext); 100 + if (!ctx) { 101 + throw new Error('useAuth() must be called within an <AuthProvider />'); 102 + } 103 + return ctx; 104 + };
+172
src/Den.Client.Web/src/lib/state/client/@tanstack/react-query.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { queryOptions, type UseMutationOptions } from '@tanstack/react-query'; 4 + import type { AxiosError } from 'axios'; 5 + 6 + import { client } from '../client.gen'; 7 + import { deleteV1BudgetsById, getV1AuthMe, getV1Budgets, getV1BudgetsById, type Options, postV1AuthLogin, postV1AuthRefresh, postV1AuthSignup, postV1Budgets, putV1BudgetsById } from '../sdk.gen'; 8 + import type { DeleteV1BudgetsByIdData, DeleteV1BudgetsByIdError, DeleteV1BudgetsByIdResponse, GetV1AuthMeData, GetV1AuthMeError, GetV1AuthMeResponse, GetV1BudgetsByIdData, GetV1BudgetsByIdError, GetV1BudgetsByIdResponse, GetV1BudgetsData, GetV1BudgetsError, GetV1BudgetsResponse, PostV1AuthLoginData, PostV1AuthLoginError, PostV1AuthLoginResponse, PostV1AuthRefreshData, PostV1AuthRefreshError, PostV1AuthRefreshResponse, PostV1AuthSignupData, PostV1AuthSignupError, PostV1AuthSignupResponse, PostV1BudgetsData, PostV1BudgetsError, PostV1BudgetsResponse, PutV1BudgetsByIdData, PutV1BudgetsByIdError, PutV1BudgetsByIdResponse } from '../types.gen'; 9 + 10 + export const postV1AuthSignupMutation = (options?: Partial<Options<PostV1AuthSignupData>>): UseMutationOptions<PostV1AuthSignupResponse, AxiosError<PostV1AuthSignupError>, Options<PostV1AuthSignupData>> => { 11 + const mutationOptions: UseMutationOptions<PostV1AuthSignupResponse, AxiosError<PostV1AuthSignupError>, Options<PostV1AuthSignupData>> = { 12 + mutationFn: async (fnOptions) => { 13 + const { data } = await postV1AuthSignup({ 14 + ...options, 15 + ...fnOptions, 16 + throwOnError: true 17 + }); 18 + return data; 19 + } 20 + }; 21 + return mutationOptions; 22 + }; 23 + 24 + export const postV1AuthLoginMutation = (options?: Partial<Options<PostV1AuthLoginData>>): UseMutationOptions<PostV1AuthLoginResponse, AxiosError<PostV1AuthLoginError>, Options<PostV1AuthLoginData>> => { 25 + const mutationOptions: UseMutationOptions<PostV1AuthLoginResponse, AxiosError<PostV1AuthLoginError>, Options<PostV1AuthLoginData>> = { 26 + mutationFn: async (fnOptions) => { 27 + const { data } = await postV1AuthLogin({ 28 + ...options, 29 + ...fnOptions, 30 + throwOnError: true 31 + }); 32 + return data; 33 + } 34 + }; 35 + return mutationOptions; 36 + }; 37 + 38 + export const postV1AuthRefreshMutation = (options?: Partial<Options<PostV1AuthRefreshData>>): UseMutationOptions<PostV1AuthRefreshResponse, AxiosError<PostV1AuthRefreshError>, Options<PostV1AuthRefreshData>> => { 39 + const mutationOptions: UseMutationOptions<PostV1AuthRefreshResponse, AxiosError<PostV1AuthRefreshError>, Options<PostV1AuthRefreshData>> = { 40 + mutationFn: async (fnOptions) => { 41 + const { data } = await postV1AuthRefresh({ 42 + ...options, 43 + ...fnOptions, 44 + throwOnError: true 45 + }); 46 + return data; 47 + } 48 + }; 49 + return mutationOptions; 50 + }; 51 + 52 + export type QueryKey<TOptions extends Options> = [ 53 + Pick<TOptions, 'baseURL' | 'body' | 'headers' | 'path' | 'query'> & { 54 + _id: string; 55 + _infinite?: boolean; 56 + tags?: ReadonlyArray<string>; 57 + } 58 + ]; 59 + 60 + const createQueryKey = <TOptions extends Options>(id: string, options?: TOptions, infinite?: boolean, tags?: ReadonlyArray<string>): [ 61 + QueryKey<TOptions>[0] 62 + ] => { 63 + const params: QueryKey<TOptions>[0] = { _id: id, baseURL: options?.baseURL || (options?.client ?? client).getConfig().baseURL } as QueryKey<TOptions>[0]; 64 + if (infinite) { 65 + params._infinite = infinite; 66 + } 67 + if (tags) { 68 + params.tags = tags; 69 + } 70 + if (options?.body) { 71 + params.body = options.body; 72 + } 73 + if (options?.headers) { 74 + params.headers = options.headers; 75 + } 76 + if (options?.path) { 77 + params.path = options.path; 78 + } 79 + if (options?.query) { 80 + params.query = options.query; 81 + } 82 + return [ 83 + params 84 + ]; 85 + }; 86 + 87 + export const getV1AuthMeQueryKey = (options?: Options<GetV1AuthMeData>) => createQueryKey("getV1AuthMe", options); 88 + 89 + export const getV1AuthMeOptions = (options?: Options<GetV1AuthMeData>) => queryOptions<GetV1AuthMeResponse, AxiosError<GetV1AuthMeError>, GetV1AuthMeResponse, ReturnType<typeof getV1AuthMeQueryKey>>({ 90 + queryFn: async ({ queryKey, signal }) => { 91 + const { data } = await getV1AuthMe({ 92 + ...options, 93 + ...queryKey[0], 94 + signal, 95 + throwOnError: true 96 + }); 97 + return data; 98 + }, 99 + queryKey: getV1AuthMeQueryKey(options) 100 + }); 101 + 102 + export const getV1BudgetsQueryKey = (options?: Options<GetV1BudgetsData>) => createQueryKey("getV1Budgets", options); 103 + 104 + export const getV1BudgetsOptions = (options?: Options<GetV1BudgetsData>) => queryOptions<GetV1BudgetsResponse, AxiosError<GetV1BudgetsError>, GetV1BudgetsResponse, ReturnType<typeof getV1BudgetsQueryKey>>({ 105 + queryFn: async ({ queryKey, signal }) => { 106 + const { data } = await getV1Budgets({ 107 + ...options, 108 + ...queryKey[0], 109 + signal, 110 + throwOnError: true 111 + }); 112 + return data; 113 + }, 114 + queryKey: getV1BudgetsQueryKey(options) 115 + }); 116 + 117 + export const postV1BudgetsMutation = (options?: Partial<Options<PostV1BudgetsData>>): UseMutationOptions<PostV1BudgetsResponse, AxiosError<PostV1BudgetsError>, Options<PostV1BudgetsData>> => { 118 + const mutationOptions: UseMutationOptions<PostV1BudgetsResponse, AxiosError<PostV1BudgetsError>, Options<PostV1BudgetsData>> = { 119 + mutationFn: async (fnOptions) => { 120 + const { data } = await postV1Budgets({ 121 + ...options, 122 + ...fnOptions, 123 + throwOnError: true 124 + }); 125 + return data; 126 + } 127 + }; 128 + return mutationOptions; 129 + }; 130 + 131 + export const deleteV1BudgetsByIdMutation = (options?: Partial<Options<DeleteV1BudgetsByIdData>>): UseMutationOptions<DeleteV1BudgetsByIdResponse, AxiosError<DeleteV1BudgetsByIdError>, Options<DeleteV1BudgetsByIdData>> => { 132 + const mutationOptions: UseMutationOptions<DeleteV1BudgetsByIdResponse, AxiosError<DeleteV1BudgetsByIdError>, Options<DeleteV1BudgetsByIdData>> = { 133 + mutationFn: async (fnOptions) => { 134 + const { data } = await deleteV1BudgetsById({ 135 + ...options, 136 + ...fnOptions, 137 + throwOnError: true 138 + }); 139 + return data; 140 + } 141 + }; 142 + return mutationOptions; 143 + }; 144 + 145 + export const getV1BudgetsByIdQueryKey = (options: Options<GetV1BudgetsByIdData>) => createQueryKey("getV1BudgetsById", options); 146 + 147 + export const getV1BudgetsByIdOptions = (options: Options<GetV1BudgetsByIdData>) => queryOptions<GetV1BudgetsByIdResponse, AxiosError<GetV1BudgetsByIdError>, GetV1BudgetsByIdResponse, ReturnType<typeof getV1BudgetsByIdQueryKey>>({ 148 + queryFn: async ({ queryKey, signal }) => { 149 + const { data } = await getV1BudgetsById({ 150 + ...options, 151 + ...queryKey[0], 152 + signal, 153 + throwOnError: true 154 + }); 155 + return data; 156 + }, 157 + queryKey: getV1BudgetsByIdQueryKey(options) 158 + }); 159 + 160 + export const putV1BudgetsByIdMutation = (options?: Partial<Options<PutV1BudgetsByIdData>>): UseMutationOptions<PutV1BudgetsByIdResponse, AxiosError<PutV1BudgetsByIdError>, Options<PutV1BudgetsByIdData>> => { 161 + const mutationOptions: UseMutationOptions<PutV1BudgetsByIdResponse, AxiosError<PutV1BudgetsByIdError>, Options<PutV1BudgetsByIdData>> = { 162 + mutationFn: async (fnOptions) => { 163 + const { data } = await putV1BudgetsById({ 164 + ...options, 165 + ...fnOptions, 166 + throwOnError: true 167 + }); 168 + return data; 169 + } 170 + }; 171 + return mutationOptions; 172 + };
+18
src/Den.Client.Web/src/lib/state/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { type ClientOptions, type Config, createClient, createConfig } from './client'; 4 + import type { ClientOptions as ClientOptions2 } from './types.gen'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions2>({ 17 + baseURL: 'https://host.docker.internal:7165/' 18 + }));
+163
src/Den.Client.Web/src/lib/state/client/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { AxiosError, AxiosInstance, RawAxiosRequestHeaders } from 'axios'; 4 + import axios from 'axios'; 5 + 6 + import { createSseClient } from '../core/serverSentEvents.gen'; 7 + import type { HttpMethod } from '../core/types.gen'; 8 + import { getValidRequestBody } from '../core/utils.gen'; 9 + import type { Client, Config, RequestOptions } from './types.gen'; 10 + import { 11 + buildUrl, 12 + createConfig, 13 + mergeConfigs, 14 + mergeHeaders, 15 + setAuthParams, 16 + } from './utils.gen'; 17 + 18 + export const createClient = (config: Config = {}): Client => { 19 + let _config = mergeConfigs(createConfig(), config); 20 + 21 + let instance: AxiosInstance; 22 + 23 + if (_config.axios && !('Axios' in _config.axios)) { 24 + instance = _config.axios; 25 + } else { 26 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 27 + const { auth, ...configWithoutAuth } = _config; 28 + instance = axios.create(configWithoutAuth); 29 + } 30 + 31 + const getConfig = (): Config => ({ ..._config }); 32 + 33 + const setConfig = (config: Config): Config => { 34 + _config = mergeConfigs(_config, config); 35 + instance.defaults = { 36 + ...instance.defaults, 37 + ..._config, 38 + // @ts-expect-error 39 + headers: mergeHeaders(instance.defaults.headers, _config.headers), 40 + }; 41 + return getConfig(); 42 + }; 43 + 44 + const beforeRequest = async (options: RequestOptions) => { 45 + const opts = { 46 + ..._config, 47 + ...options, 48 + axios: options.axios ?? _config.axios ?? instance, 49 + headers: mergeHeaders(_config.headers, options.headers), 50 + }; 51 + 52 + if (opts.security) { 53 + await setAuthParams({ 54 + ...opts, 55 + security: opts.security, 56 + }); 57 + } 58 + 59 + if (opts.requestValidator) { 60 + await opts.requestValidator(opts); 61 + } 62 + 63 + if (opts.body !== undefined && opts.bodySerializer) { 64 + opts.body = opts.bodySerializer(opts.body); 65 + } 66 + 67 + const url = buildUrl(opts); 68 + 69 + return { opts, url }; 70 + }; 71 + 72 + // @ts-expect-error 73 + const request: Client['request'] = async (options) => { 74 + // @ts-expect-error 75 + const { opts, url } = await beforeRequest(options); 76 + try { 77 + // assign Axios here for consistency with fetch 78 + const _axios = opts.axios!; 79 + // eslint-disable-next-line @typescript-eslint/no-unused-vars 80 + const { auth, ...optsWithoutAuth } = opts; 81 + const response = await _axios({ 82 + ...optsWithoutAuth, 83 + baseURL: '', // the baseURL is already included in `url` 84 + data: getValidRequestBody(opts), 85 + headers: opts.headers as RawAxiosRequestHeaders, 86 + // let `paramsSerializer()` handle query params if it exists 87 + params: opts.paramsSerializer ? opts.query : undefined, 88 + url, 89 + }); 90 + 91 + let { data } = response; 92 + 93 + if (opts.responseType === 'json') { 94 + if (opts.responseValidator) { 95 + await opts.responseValidator(data); 96 + } 97 + 98 + if (opts.responseTransformer) { 99 + data = await opts.responseTransformer(data); 100 + } 101 + } 102 + 103 + return { 104 + ...response, 105 + data: data ?? {}, 106 + }; 107 + } catch (error) { 108 + const e = error as AxiosError; 109 + if (opts.throwOnError) { 110 + throw e; 111 + } 112 + // @ts-expect-error 113 + e.error = e.response?.data ?? {}; 114 + return e; 115 + } 116 + }; 117 + 118 + const makeMethodFn = 119 + (method: Uppercase<HttpMethod>) => (options: RequestOptions) => 120 + request({ ...options, method }); 121 + 122 + const makeSseFn = 123 + (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => { 124 + const { opts, url } = await beforeRequest(options); 125 + return createSseClient({ 126 + ...opts, 127 + body: opts.body as BodyInit | null | undefined, 128 + headers: opts.headers as Record<string, string>, 129 + method, 130 + // @ts-expect-error 131 + signal: opts.signal, 132 + url, 133 + }); 134 + }; 135 + 136 + return { 137 + buildUrl, 138 + connect: makeMethodFn('CONNECT'), 139 + delete: makeMethodFn('DELETE'), 140 + get: makeMethodFn('GET'), 141 + getConfig, 142 + head: makeMethodFn('HEAD'), 143 + instance, 144 + options: makeMethodFn('OPTIONS'), 145 + patch: makeMethodFn('PATCH'), 146 + post: makeMethodFn('POST'), 147 + put: makeMethodFn('PUT'), 148 + request, 149 + setConfig, 150 + sse: { 151 + connect: makeSseFn('CONNECT'), 152 + delete: makeSseFn('DELETE'), 153 + get: makeSseFn('GET'), 154 + head: makeSseFn('HEAD'), 155 + options: makeSseFn('OPTIONS'), 156 + patch: makeSseFn('PATCH'), 157 + post: makeSseFn('POST'), 158 + put: makeSseFn('PUT'), 159 + trace: makeSseFn('TRACE'), 160 + }, 161 + trace: makeMethodFn('TRACE'), 162 + } as Client; 163 + };
+23
src/Den.Client.Web/src/lib/state/client/client/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type { Auth } from '../core/auth.gen'; 4 + export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; 5 + export { 6 + formDataBodySerializer, 7 + jsonBodySerializer, 8 + urlSearchParamsBodySerializer, 9 + } from '../core/bodySerializer.gen'; 10 + export { buildClientParams } from '../core/params.gen'; 11 + export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen'; 12 + export { createClient } from './client.gen'; 13 + export type { 14 + Client, 15 + ClientOptions, 16 + Config, 17 + CreateClientConfig, 18 + Options, 19 + RequestOptions, 20 + RequestResult, 21 + TDataShape, 22 + } from './types.gen'; 23 + export { createConfig } from './utils.gen';
+197
src/Den.Client.Web/src/lib/state/client/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { 4 + AxiosError, 5 + AxiosInstance, 6 + AxiosRequestHeaders, 7 + AxiosResponse, 8 + AxiosStatic, 9 + CreateAxiosDefaults, 10 + } from 'axios'; 11 + 12 + import type { Auth } from '../core/auth.gen'; 13 + import type { 14 + ServerSentEventsOptions, 15 + ServerSentEventsResult, 16 + } from '../core/serverSentEvents.gen'; 17 + import type { 18 + Client as CoreClient, 19 + Config as CoreConfig, 20 + } from '../core/types.gen'; 21 + 22 + export interface Config<T extends ClientOptions = ClientOptions> 23 + extends Omit<CreateAxiosDefaults, 'auth' | 'baseURL' | 'headers' | 'method'>, 24 + CoreConfig { 25 + /** 26 + * Axios implementation. You can use this option to provide either an 27 + * `AxiosStatic` or an `AxiosInstance`. 28 + * 29 + * @default axios 30 + */ 31 + axios?: AxiosStatic | AxiosInstance; 32 + /** 33 + * Base URL for all requests made by this client. 34 + */ 35 + baseURL?: T['baseURL']; 36 + /** 37 + * An object containing any HTTP headers that you want to pre-populate your 38 + * `Headers` object with. 39 + * 40 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 41 + */ 42 + headers?: 43 + | AxiosRequestHeaders 44 + | Record< 45 + string, 46 + | string 47 + | number 48 + | boolean 49 + | (string | number | boolean)[] 50 + | null 51 + | undefined 52 + | unknown 53 + >; 54 + /** 55 + * Throw an error instead of returning it in the response? 56 + * 57 + * @default false 58 + */ 59 + throwOnError?: T['throwOnError']; 60 + } 61 + 62 + export interface RequestOptions< 63 + TData = unknown, 64 + ThrowOnError extends boolean = boolean, 65 + Url extends string = string, 66 + > extends Config<{ 67 + throwOnError: ThrowOnError; 68 + }>, 69 + Pick< 70 + ServerSentEventsOptions<TData>, 71 + | 'onSseError' 72 + | 'onSseEvent' 73 + | 'sseDefaultRetryDelay' 74 + | 'sseMaxRetryAttempts' 75 + | 'sseMaxRetryDelay' 76 + > { 77 + /** 78 + * Any body that you want to add to your request. 79 + * 80 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 81 + */ 82 + body?: unknown; 83 + path?: Record<string, unknown>; 84 + query?: Record<string, unknown>; 85 + /** 86 + * Security mechanism(s) to use for the request. 87 + */ 88 + security?: ReadonlyArray<Auth>; 89 + url: Url; 90 + } 91 + 92 + export interface ClientOptions { 93 + baseURL?: string; 94 + throwOnError?: boolean; 95 + } 96 + 97 + export type RequestResult< 98 + TData = unknown, 99 + TError = unknown, 100 + ThrowOnError extends boolean = boolean, 101 + > = ThrowOnError extends true 102 + ? Promise< 103 + AxiosResponse< 104 + TData extends Record<string, unknown> ? TData[keyof TData] : TData 105 + > 106 + > 107 + : Promise< 108 + | (AxiosResponse< 109 + TData extends Record<string, unknown> ? TData[keyof TData] : TData 110 + > & { error: undefined }) 111 + | (AxiosError< 112 + TError extends Record<string, unknown> ? TError[keyof TError] : TError 113 + > & { 114 + data: undefined; 115 + error: TError extends Record<string, unknown> 116 + ? TError[keyof TError] 117 + : TError; 118 + }) 119 + >; 120 + 121 + type MethodFn = < 122 + TData = unknown, 123 + TError = unknown, 124 + ThrowOnError extends boolean = false, 125 + >( 126 + options: Omit<RequestOptions<TData, ThrowOnError>, 'method'>, 127 + ) => RequestResult<TData, TError, ThrowOnError>; 128 + 129 + type SseFn = < 130 + TData = unknown, 131 + TError = unknown, 132 + ThrowOnError extends boolean = false, 133 + >( 134 + options: Omit<RequestOptions<TData, ThrowOnError>, 'method'>, 135 + ) => Promise<ServerSentEventsResult<TData, TError>>; 136 + 137 + type RequestFn = < 138 + TData = unknown, 139 + TError = unknown, 140 + ThrowOnError extends boolean = false, 141 + >( 142 + options: Omit<RequestOptions<TData, ThrowOnError>, 'method'> & 143 + Pick<Required<RequestOptions<TData, ThrowOnError>>, 'method'>, 144 + ) => RequestResult<TData, TError, ThrowOnError>; 145 + 146 + type BuildUrlFn = < 147 + TData extends { 148 + body?: unknown; 149 + path?: Record<string, unknown>; 150 + query?: Record<string, unknown>; 151 + url: string; 152 + }, 153 + >( 154 + options: TData & Options<TData>, 155 + ) => string; 156 + 157 + export type Client = CoreClient< 158 + RequestFn, 159 + Config, 160 + MethodFn, 161 + BuildUrlFn, 162 + SseFn 163 + > & { 164 + instance: AxiosInstance; 165 + }; 166 + 167 + /** 168 + * The `createClientConfig()` function will be called on client initialization 169 + * and the returned object will become the client's initial configuration. 170 + * 171 + * You may want to initialize your client this way instead of calling 172 + * `setConfig()`. This is useful for example if you're using Next.js 173 + * to ensure your client always has the correct values. 174 + */ 175 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 176 + override?: Config<ClientOptions & T>, 177 + ) => Config<Required<ClientOptions> & T>; 178 + 179 + export interface TDataShape { 180 + body?: unknown; 181 + headers?: unknown; 182 + path?: unknown; 183 + query?: unknown; 184 + url: string; 185 + } 186 + 187 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 188 + 189 + export type Options< 190 + TData extends TDataShape = TDataShape, 191 + ThrowOnError extends boolean = boolean, 192 + TResponse = unknown, 193 + > = OmitKeys< 194 + RequestOptions<TResponse, ThrowOnError>, 195 + 'body' | 'path' | 'query' | 'url' 196 + > & 197 + ([TData] extends [never] ? unknown : Omit<TData, 'url'>);
+213
src/Den.Client.Web/src/lib/state/client/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { getAuthToken } from '../core/auth.gen'; 4 + import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; 5 + import { 6 + serializeArrayParam, 7 + serializeObjectParam, 8 + serializePrimitiveParam, 9 + } from '../core/pathSerializer.gen'; 10 + import { getUrl } from '../core/utils.gen'; 11 + import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; 12 + 13 + export const createQuerySerializer = <T = unknown>({ 14 + parameters = {}, 15 + ...args 16 + }: QuerySerializerOptions = {}) => { 17 + const querySerializer = (queryParams: T) => { 18 + const search: string[] = []; 19 + if (queryParams && typeof queryParams === 'object') { 20 + for (const name in queryParams) { 21 + const value = queryParams[name]; 22 + 23 + if (value === undefined || value === null) { 24 + continue; 25 + } 26 + 27 + const options = parameters[name] || args; 28 + 29 + if (Array.isArray(value)) { 30 + const serializedArray = serializeArrayParam({ 31 + allowReserved: options.allowReserved, 32 + explode: true, 33 + name, 34 + style: 'form', 35 + value, 36 + ...options.array, 37 + }); 38 + if (serializedArray) search.push(serializedArray); 39 + } else if (typeof value === 'object') { 40 + const serializedObject = serializeObjectParam({ 41 + allowReserved: options.allowReserved, 42 + explode: true, 43 + name, 44 + style: 'deepObject', 45 + value: value as Record<string, unknown>, 46 + ...options.object, 47 + }); 48 + if (serializedObject) search.push(serializedObject); 49 + } else { 50 + const serializedPrimitive = serializePrimitiveParam({ 51 + allowReserved: options.allowReserved, 52 + name, 53 + value: value as string, 54 + }); 55 + if (serializedPrimitive) search.push(serializedPrimitive); 56 + } 57 + } 58 + } 59 + return search.join('&'); 60 + }; 61 + return querySerializer; 62 + }; 63 + 64 + const checkForExistence = ( 65 + options: Pick<RequestOptions, 'auth' | 'query'> & { 66 + headers: Record<any, unknown>; 67 + }, 68 + name?: string, 69 + ): boolean => { 70 + if (!name) { 71 + return false; 72 + } 73 + if (name in options.headers || options.query?.[name]) { 74 + return true; 75 + } 76 + if ( 77 + 'Cookie' in options.headers && 78 + options.headers['Cookie'] && 79 + typeof options.headers['Cookie'] === 'string' 80 + ) { 81 + return options.headers['Cookie'].includes(`${name}=`); 82 + } 83 + return false; 84 + }; 85 + 86 + export const setAuthParams = async ({ 87 + security, 88 + ...options 89 + }: Pick<Required<RequestOptions>, 'security'> & 90 + Pick<RequestOptions, 'auth' | 'query'> & { 91 + headers: Record<any, unknown>; 92 + }) => { 93 + for (const auth of security) { 94 + if (checkForExistence(options, auth.name)) { 95 + continue; 96 + } 97 + const token = await getAuthToken(auth, options.auth); 98 + 99 + if (!token) { 100 + continue; 101 + } 102 + 103 + const name = auth.name ?? 'Authorization'; 104 + 105 + switch (auth.in) { 106 + case 'query': 107 + if (!options.query) { 108 + options.query = {}; 109 + } 110 + options.query[name] = token; 111 + break; 112 + case 'cookie': { 113 + const value = `${name}=${token}`; 114 + if ('Cookie' in options.headers && options.headers['Cookie']) { 115 + options.headers['Cookie'] = `${options.headers['Cookie']}; ${value}`; 116 + } else { 117 + options.headers['Cookie'] = value; 118 + } 119 + break; 120 + } 121 + case 'header': 122 + default: 123 + options.headers[name] = token; 124 + break; 125 + } 126 + } 127 + }; 128 + 129 + export const buildUrl: Client['buildUrl'] = (options) => { 130 + const instanceBaseUrl = options.axios?.defaults?.baseURL; 131 + 132 + const baseUrl = 133 + !!options.baseURL && typeof options.baseURL === 'string' 134 + ? options.baseURL 135 + : instanceBaseUrl; 136 + 137 + return getUrl({ 138 + baseUrl: baseUrl as string, 139 + path: options.path, 140 + // let `paramsSerializer()` handle query params if it exists 141 + query: !options.paramsSerializer ? options.query : undefined, 142 + querySerializer: 143 + typeof options.querySerializer === 'function' 144 + ? options.querySerializer 145 + : createQuerySerializer(options.querySerializer), 146 + url: options.url, 147 + }); 148 + }; 149 + 150 + export const mergeConfigs = (a: Config, b: Config): Config => { 151 + const config = { ...a, ...b }; 152 + config.headers = mergeHeaders(a.headers, b.headers); 153 + return config; 154 + }; 155 + 156 + /** 157 + * Special Axios headers keywords allowing to set headers by request method. 158 + */ 159 + export const axiosHeadersKeywords = [ 160 + 'common', 161 + 'delete', 162 + 'get', 163 + 'head', 164 + 'patch', 165 + 'post', 166 + 'put', 167 + ] as const; 168 + 169 + export const mergeHeaders = ( 170 + ...headers: Array<Required<Config>['headers'] | undefined> 171 + ): Record<any, unknown> => { 172 + const mergedHeaders: Record<any, unknown> = {}; 173 + for (const header of headers) { 174 + if (!header || typeof header !== 'object') { 175 + continue; 176 + } 177 + 178 + const iterator = Object.entries(header); 179 + 180 + for (const [key, value] of iterator) { 181 + if ( 182 + axiosHeadersKeywords.includes( 183 + key as (typeof axiosHeadersKeywords)[number], 184 + ) && 185 + typeof value === 'object' 186 + ) { 187 + mergedHeaders[key] = { 188 + ...(mergedHeaders[key] as Record<any, unknown>), 189 + ...value, 190 + }; 191 + } else if (value === null) { 192 + delete mergedHeaders[key]; 193 + } else if (Array.isArray(value)) { 194 + for (const v of value) { 195 + // @ts-expect-error 196 + mergedHeaders[key] = [...(mergedHeaders[key] ?? []), v as string]; 197 + } 198 + } else if (value !== undefined) { 199 + // assume object headers are meant to be JSON stringified, i.e. their 200 + // content value in OpenAPI specification is 'application/json' 201 + mergedHeaders[key] = 202 + typeof value === 'object' ? JSON.stringify(value) : (value as string); 203 + } 204 + } 205 + } 206 + return mergedHeaders; 207 + }; 208 + 209 + export const createConfig = <T extends ClientOptions = ClientOptions>( 210 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 211 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 212 + ...override, 213 + });
+42
src/Den.Client.Web/src/lib/state/client/core/auth.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type AuthToken = string | undefined; 4 + 5 + export interface Auth { 6 + /** 7 + * Which part of the request do we use to send the auth? 8 + * 9 + * @default 'header' 10 + */ 11 + in?: 'header' | 'query' | 'cookie'; 12 + /** 13 + * Header or query parameter name. 14 + * 15 + * @default 'Authorization' 16 + */ 17 + name?: string; 18 + scheme?: 'basic' | 'bearer'; 19 + type: 'apiKey' | 'http'; 20 + } 21 + 22 + export const getAuthToken = async ( 23 + auth: Auth, 24 + callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken, 25 + ): Promise<string | undefined> => { 26 + const token = 27 + typeof callback === 'function' ? await callback(auth) : callback; 28 + 29 + if (!token) { 30 + return; 31 + } 32 + 33 + if (auth.scheme === 'bearer') { 34 + return `Bearer ${token}`; 35 + } 36 + 37 + if (auth.scheme === 'basic') { 38 + return `Basic ${btoa(token)}`; 39 + } 40 + 41 + return token; 42 + };
+100
src/Den.Client.Web/src/lib/state/client/core/bodySerializer.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { 4 + ArrayStyle, 5 + ObjectStyle, 6 + SerializerOptions, 7 + } from './pathSerializer.gen'; 8 + 9 + export type QuerySerializer = (query: Record<string, unknown>) => string; 10 + 11 + export type BodySerializer = (body: any) => any; 12 + 13 + type QuerySerializerOptionsObject = { 14 + allowReserved?: boolean; 15 + array?: Partial<SerializerOptions<ArrayStyle>>; 16 + object?: Partial<SerializerOptions<ObjectStyle>>; 17 + }; 18 + 19 + export type QuerySerializerOptions = QuerySerializerOptionsObject & { 20 + /** 21 + * Per-parameter serialization overrides. When provided, these settings 22 + * override the global array/object settings for specific parameter names. 23 + */ 24 + parameters?: Record<string, QuerySerializerOptionsObject>; 25 + }; 26 + 27 + const serializeFormDataPair = ( 28 + data: FormData, 29 + key: string, 30 + value: unknown, 31 + ): void => { 32 + if (typeof value === 'string' || value instanceof Blob) { 33 + data.append(key, value); 34 + } else if (value instanceof Date) { 35 + data.append(key, value.toISOString()); 36 + } else { 37 + data.append(key, JSON.stringify(value)); 38 + } 39 + }; 40 + 41 + const serializeUrlSearchParamsPair = ( 42 + data: URLSearchParams, 43 + key: string, 44 + value: unknown, 45 + ): void => { 46 + if (typeof value === 'string') { 47 + data.append(key, value); 48 + } else { 49 + data.append(key, JSON.stringify(value)); 50 + } 51 + }; 52 + 53 + export const formDataBodySerializer = { 54 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 55 + body: T, 56 + ): FormData => { 57 + const data = new FormData(); 58 + 59 + Object.entries(body).forEach(([key, value]) => { 60 + if (value === undefined || value === null) { 61 + return; 62 + } 63 + if (Array.isArray(value)) { 64 + value.forEach((v) => serializeFormDataPair(data, key, v)); 65 + } else { 66 + serializeFormDataPair(data, key, value); 67 + } 68 + }); 69 + 70 + return data; 71 + }, 72 + }; 73 + 74 + export const jsonBodySerializer = { 75 + bodySerializer: <T>(body: T): string => 76 + JSON.stringify(body, (_key, value) => 77 + typeof value === 'bigint' ? value.toString() : value, 78 + ), 79 + }; 80 + 81 + export const urlSearchParamsBodySerializer = { 82 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 83 + body: T, 84 + ): string => { 85 + const data = new URLSearchParams(); 86 + 87 + Object.entries(body).forEach(([key, value]) => { 88 + if (value === undefined || value === null) { 89 + return; 90 + } 91 + if (Array.isArray(value)) { 92 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 93 + } else { 94 + serializeUrlSearchParamsPair(data, key, value); 95 + } 96 + }); 97 + 98 + return data.toString(); 99 + }, 100 + };
+176
src/Den.Client.Web/src/lib/state/client/core/params.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + type Slot = 'body' | 'headers' | 'path' | 'query'; 4 + 5 + export type Field = 6 + | { 7 + in: Exclude<Slot, 'body'>; 8 + /** 9 + * Field name. This is the name we want the user to see and use. 10 + */ 11 + key: string; 12 + /** 13 + * Field mapped name. This is the name we want to use in the request. 14 + * If omitted, we use the same value as `key`. 15 + */ 16 + map?: string; 17 + } 18 + | { 19 + in: Extract<Slot, 'body'>; 20 + /** 21 + * Key isn't required for bodies. 22 + */ 23 + key?: string; 24 + map?: string; 25 + } 26 + | { 27 + /** 28 + * Field name. This is the name we want the user to see and use. 29 + */ 30 + key: string; 31 + /** 32 + * Field mapped name. This is the name we want to use in the request. 33 + * If `in` is omitted, `map` aliases `key` to the transport layer. 34 + */ 35 + map: Slot; 36 + }; 37 + 38 + export interface Fields { 39 + allowExtra?: Partial<Record<Slot, boolean>>; 40 + args?: ReadonlyArray<Field>; 41 + } 42 + 43 + export type FieldsConfig = ReadonlyArray<Field | Fields>; 44 + 45 + const extraPrefixesMap: Record<string, Slot> = { 46 + $body_: 'body', 47 + $headers_: 'headers', 48 + $path_: 'path', 49 + $query_: 'query', 50 + }; 51 + const extraPrefixes = Object.entries(extraPrefixesMap); 52 + 53 + type KeyMap = Map< 54 + string, 55 + | { 56 + in: Slot; 57 + map?: string; 58 + } 59 + | { 60 + in?: never; 61 + map: Slot; 62 + } 63 + >; 64 + 65 + const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { 66 + if (!map) { 67 + map = new Map(); 68 + } 69 + 70 + for (const config of fields) { 71 + if ('in' in config) { 72 + if (config.key) { 73 + map.set(config.key, { 74 + in: config.in, 75 + map: config.map, 76 + }); 77 + } 78 + } else if ('key' in config) { 79 + map.set(config.key, { 80 + map: config.map, 81 + }); 82 + } else if (config.args) { 83 + buildKeyMap(config.args, map); 84 + } 85 + } 86 + 87 + return map; 88 + }; 89 + 90 + interface Params { 91 + body: unknown; 92 + headers: Record<string, unknown>; 93 + path: Record<string, unknown>; 94 + query: Record<string, unknown>; 95 + } 96 + 97 + const stripEmptySlots = (params: Params) => { 98 + for (const [slot, value] of Object.entries(params)) { 99 + if (value && typeof value === 'object' && !Object.keys(value).length) { 100 + delete params[slot as Slot]; 101 + } 102 + } 103 + }; 104 + 105 + export const buildClientParams = ( 106 + args: ReadonlyArray<unknown>, 107 + fields: FieldsConfig, 108 + ) => { 109 + const params: Params = { 110 + body: {}, 111 + headers: {}, 112 + path: {}, 113 + query: {}, 114 + }; 115 + 116 + const map = buildKeyMap(fields); 117 + 118 + let config: FieldsConfig[number] | undefined; 119 + 120 + for (const [index, arg] of args.entries()) { 121 + if (fields[index]) { 122 + config = fields[index]; 123 + } 124 + 125 + if (!config) { 126 + continue; 127 + } 128 + 129 + if ('in' in config) { 130 + if (config.key) { 131 + const field = map.get(config.key)!; 132 + const name = field.map || config.key; 133 + if (field.in) { 134 + (params[field.in] as Record<string, unknown>)[name] = arg; 135 + } 136 + } else { 137 + params.body = arg; 138 + } 139 + } else { 140 + for (const [key, value] of Object.entries(arg ?? {})) { 141 + const field = map.get(key); 142 + 143 + if (field) { 144 + if (field.in) { 145 + const name = field.map || key; 146 + (params[field.in] as Record<string, unknown>)[name] = value; 147 + } else { 148 + params[field.map] = value; 149 + } 150 + } else { 151 + const extra = extraPrefixes.find(([prefix]) => 152 + key.startsWith(prefix), 153 + ); 154 + 155 + if (extra) { 156 + const [prefix, slot] = extra; 157 + (params[slot] as Record<string, unknown>)[ 158 + key.slice(prefix.length) 159 + ] = value; 160 + } else if ('allowExtra' in config && config.allowExtra) { 161 + for (const [slot, allowed] of Object.entries(config.allowExtra)) { 162 + if (allowed) { 163 + (params[slot as Slot] as Record<string, unknown>)[key] = value; 164 + break; 165 + } 166 + } 167 + } 168 + } 169 + } 170 + } 171 + } 172 + 173 + stripEmptySlots(params); 174 + 175 + return params; 176 + };
+181
src/Den.Client.Web/src/lib/state/client/core/pathSerializer.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + interface SerializeOptions<T> 4 + extends SerializePrimitiveOptions, 5 + SerializerOptions<T> {} 6 + 7 + interface SerializePrimitiveOptions { 8 + allowReserved?: boolean; 9 + name: string; 10 + } 11 + 12 + export interface SerializerOptions<T> { 13 + /** 14 + * @default true 15 + */ 16 + explode: boolean; 17 + style: T; 18 + } 19 + 20 + export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 21 + export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 22 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 23 + export type ObjectStyle = 'form' | 'deepObject'; 24 + type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; 25 + 26 + interface SerializePrimitiveParam extends SerializePrimitiveOptions { 27 + value: string; 28 + } 29 + 30 + export const separatorArrayExplode = (style: ArraySeparatorStyle) => { 31 + switch (style) { 32 + case 'label': 33 + return '.'; 34 + case 'matrix': 35 + return ';'; 36 + case 'simple': 37 + return ','; 38 + default: 39 + return '&'; 40 + } 41 + }; 42 + 43 + export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { 44 + switch (style) { 45 + case 'form': 46 + return ','; 47 + case 'pipeDelimited': 48 + return '|'; 49 + case 'spaceDelimited': 50 + return '%20'; 51 + default: 52 + return ','; 53 + } 54 + }; 55 + 56 + export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { 57 + switch (style) { 58 + case 'label': 59 + return '.'; 60 + case 'matrix': 61 + return ';'; 62 + case 'simple': 63 + return ','; 64 + default: 65 + return '&'; 66 + } 67 + }; 68 + 69 + export const serializeArrayParam = ({ 70 + allowReserved, 71 + explode, 72 + name, 73 + style, 74 + value, 75 + }: SerializeOptions<ArraySeparatorStyle> & { 76 + value: unknown[]; 77 + }) => { 78 + if (!explode) { 79 + const joinedValues = ( 80 + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) 81 + ).join(separatorArrayNoExplode(style)); 82 + switch (style) { 83 + case 'label': 84 + return `.${joinedValues}`; 85 + case 'matrix': 86 + return `;${name}=${joinedValues}`; 87 + case 'simple': 88 + return joinedValues; 89 + default: 90 + return `${name}=${joinedValues}`; 91 + } 92 + } 93 + 94 + const separator = separatorArrayExplode(style); 95 + const joinedValues = value 96 + .map((v) => { 97 + if (style === 'label' || style === 'simple') { 98 + return allowReserved ? v : encodeURIComponent(v as string); 99 + } 100 + 101 + return serializePrimitiveParam({ 102 + allowReserved, 103 + name, 104 + value: v as string, 105 + }); 106 + }) 107 + .join(separator); 108 + return style === 'label' || style === 'matrix' 109 + ? separator + joinedValues 110 + : joinedValues; 111 + }; 112 + 113 + export const serializePrimitiveParam = ({ 114 + allowReserved, 115 + name, 116 + value, 117 + }: SerializePrimitiveParam) => { 118 + if (value === undefined || value === null) { 119 + return ''; 120 + } 121 + 122 + if (typeof value === 'object') { 123 + throw new Error( 124 + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', 125 + ); 126 + } 127 + 128 + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; 129 + }; 130 + 131 + export const serializeObjectParam = ({ 132 + allowReserved, 133 + explode, 134 + name, 135 + style, 136 + value, 137 + valueOnly, 138 + }: SerializeOptions<ObjectSeparatorStyle> & { 139 + value: Record<string, unknown> | Date; 140 + valueOnly?: boolean; 141 + }) => { 142 + if (value instanceof Date) { 143 + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; 144 + } 145 + 146 + if (style !== 'deepObject' && !explode) { 147 + let values: string[] = []; 148 + Object.entries(value).forEach(([key, v]) => { 149 + values = [ 150 + ...values, 151 + key, 152 + allowReserved ? (v as string) : encodeURIComponent(v as string), 153 + ]; 154 + }); 155 + const joinedValues = values.join(','); 156 + switch (style) { 157 + case 'form': 158 + return `${name}=${joinedValues}`; 159 + case 'label': 160 + return `.${joinedValues}`; 161 + case 'matrix': 162 + return `;${name}=${joinedValues}`; 163 + default: 164 + return joinedValues; 165 + } 166 + } 167 + 168 + const separator = separatorObjectExplode(style); 169 + const joinedValues = Object.entries(value) 170 + .map(([key, v]) => 171 + serializePrimitiveParam({ 172 + allowReserved, 173 + name: style === 'deepObject' ? `${name}[${key}]` : key, 174 + value: v as string, 175 + }), 176 + ) 177 + .join(separator); 178 + return style === 'label' || style === 'matrix' 179 + ? separator + joinedValues 180 + : joinedValues; 181 + };
+136
src/Den.Client.Web/src/lib/state/client/core/queryKeySerializer.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + /** 4 + * JSON-friendly union that mirrors what Pinia Colada can hash. 5 + */ 6 + export type JsonValue = 7 + | null 8 + | string 9 + | number 10 + | boolean 11 + | JsonValue[] 12 + | { [key: string]: JsonValue }; 13 + 14 + /** 15 + * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. 16 + */ 17 + export const queryKeyJsonReplacer = (_key: string, value: unknown) => { 18 + if ( 19 + value === undefined || 20 + typeof value === 'function' || 21 + typeof value === 'symbol' 22 + ) { 23 + return undefined; 24 + } 25 + if (typeof value === 'bigint') { 26 + return value.toString(); 27 + } 28 + if (value instanceof Date) { 29 + return value.toISOString(); 30 + } 31 + return value; 32 + }; 33 + 34 + /** 35 + * Safely stringifies a value and parses it back into a JsonValue. 36 + */ 37 + export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { 38 + try { 39 + const json = JSON.stringify(input, queryKeyJsonReplacer); 40 + if (json === undefined) { 41 + return undefined; 42 + } 43 + return JSON.parse(json) as JsonValue; 44 + } catch { 45 + return undefined; 46 + } 47 + }; 48 + 49 + /** 50 + * Detects plain objects (including objects with a null prototype). 51 + */ 52 + const isPlainObject = (value: unknown): value is Record<string, unknown> => { 53 + if (value === null || typeof value !== 'object') { 54 + return false; 55 + } 56 + const prototype = Object.getPrototypeOf(value as object); 57 + return prototype === Object.prototype || prototype === null; 58 + }; 59 + 60 + /** 61 + * Turns URLSearchParams into a sorted JSON object for deterministic keys. 62 + */ 63 + const serializeSearchParams = (params: URLSearchParams): JsonValue => { 64 + const entries = Array.from(params.entries()).sort(([a], [b]) => 65 + a.localeCompare(b), 66 + ); 67 + const result: Record<string, JsonValue> = {}; 68 + 69 + for (const [key, value] of entries) { 70 + const existing = result[key]; 71 + if (existing === undefined) { 72 + result[key] = value; 73 + continue; 74 + } 75 + 76 + if (Array.isArray(existing)) { 77 + (existing as string[]).push(value); 78 + } else { 79 + result[key] = [existing, value]; 80 + } 81 + } 82 + 83 + return result; 84 + }; 85 + 86 + /** 87 + * Normalizes any accepted value into a JSON-friendly shape for query keys. 88 + */ 89 + export const serializeQueryKeyValue = ( 90 + value: unknown, 91 + ): JsonValue | undefined => { 92 + if (value === null) { 93 + return null; 94 + } 95 + 96 + if ( 97 + typeof value === 'string' || 98 + typeof value === 'number' || 99 + typeof value === 'boolean' 100 + ) { 101 + return value; 102 + } 103 + 104 + if ( 105 + value === undefined || 106 + typeof value === 'function' || 107 + typeof value === 'symbol' 108 + ) { 109 + return undefined; 110 + } 111 + 112 + if (typeof value === 'bigint') { 113 + return value.toString(); 114 + } 115 + 116 + if (value instanceof Date) { 117 + return value.toISOString(); 118 + } 119 + 120 + if (Array.isArray(value)) { 121 + return stringifyToJsonValue(value); 122 + } 123 + 124 + if ( 125 + typeof URLSearchParams !== 'undefined' && 126 + value instanceof URLSearchParams 127 + ) { 128 + return serializeSearchParams(value); 129 + } 130 + 131 + if (isPlainObject(value)) { 132 + return stringifyToJsonValue(value); 133 + } 134 + 135 + return undefined; 136 + };
+264
src/Den.Client.Web/src/lib/state/client/core/serverSentEvents.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Config } from './types.gen'; 4 + 5 + export type ServerSentEventsOptions<TData = unknown> = Omit< 6 + RequestInit, 7 + 'method' 8 + > & 9 + Pick<Config, 'method' | 'responseTransformer' | 'responseValidator'> & { 10 + /** 11 + * Fetch API implementation. You can use this option to provide a custom 12 + * fetch instance. 13 + * 14 + * @default globalThis.fetch 15 + */ 16 + fetch?: typeof fetch; 17 + /** 18 + * Implementing clients can call request interceptors inside this hook. 19 + */ 20 + onRequest?: (url: string, init: RequestInit) => Promise<Request>; 21 + /** 22 + * Callback invoked when a network or parsing error occurs during streaming. 23 + * 24 + * This option applies only if the endpoint returns a stream of events. 25 + * 26 + * @param error The error that occurred. 27 + */ 28 + onSseError?: (error: unknown) => void; 29 + /** 30 + * Callback invoked when an event is streamed from the server. 31 + * 32 + * This option applies only if the endpoint returns a stream of events. 33 + * 34 + * @param event Event streamed from the server. 35 + * @returns Nothing (void). 36 + */ 37 + onSseEvent?: (event: StreamEvent<TData>) => void; 38 + serializedBody?: RequestInit['body']; 39 + /** 40 + * Default retry delay in milliseconds. 41 + * 42 + * This option applies only if the endpoint returns a stream of events. 43 + * 44 + * @default 3000 45 + */ 46 + sseDefaultRetryDelay?: number; 47 + /** 48 + * Maximum number of retry attempts before giving up. 49 + */ 50 + sseMaxRetryAttempts?: number; 51 + /** 52 + * Maximum retry delay in milliseconds. 53 + * 54 + * Applies only when exponential backoff is used. 55 + * 56 + * This option applies only if the endpoint returns a stream of events. 57 + * 58 + * @default 30000 59 + */ 60 + sseMaxRetryDelay?: number; 61 + /** 62 + * Optional sleep function for retry backoff. 63 + * 64 + * Defaults to using `setTimeout`. 65 + */ 66 + sseSleepFn?: (ms: number) => Promise<void>; 67 + url: string; 68 + }; 69 + 70 + export interface StreamEvent<TData = unknown> { 71 + data: TData; 72 + event?: string; 73 + id?: string; 74 + retry?: number; 75 + } 76 + 77 + export type ServerSentEventsResult< 78 + TData = unknown, 79 + TReturn = void, 80 + TNext = unknown, 81 + > = { 82 + stream: AsyncGenerator< 83 + TData extends Record<string, unknown> ? TData[keyof TData] : TData, 84 + TReturn, 85 + TNext 86 + >; 87 + }; 88 + 89 + export const createSseClient = <TData = unknown>({ 90 + onRequest, 91 + onSseError, 92 + onSseEvent, 93 + responseTransformer, 94 + responseValidator, 95 + sseDefaultRetryDelay, 96 + sseMaxRetryAttempts, 97 + sseMaxRetryDelay, 98 + sseSleepFn, 99 + url, 100 + ...options 101 + }: ServerSentEventsOptions): ServerSentEventsResult<TData> => { 102 + let lastEventId: string | undefined; 103 + 104 + const sleep = 105 + sseSleepFn ?? 106 + ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); 107 + 108 + const createStream = async function* () { 109 + let retryDelay: number = sseDefaultRetryDelay ?? 3000; 110 + let attempt = 0; 111 + const signal = options.signal ?? new AbortController().signal; 112 + 113 + while (true) { 114 + if (signal.aborted) break; 115 + 116 + attempt++; 117 + 118 + const headers = 119 + options.headers instanceof Headers 120 + ? options.headers 121 + : new Headers(options.headers as Record<string, string> | undefined); 122 + 123 + if (lastEventId !== undefined) { 124 + headers.set('Last-Event-ID', lastEventId); 125 + } 126 + 127 + try { 128 + const requestInit: RequestInit = { 129 + redirect: 'follow', 130 + ...options, 131 + body: options.serializedBody, 132 + headers, 133 + signal, 134 + }; 135 + let request = new Request(url, requestInit); 136 + if (onRequest) { 137 + request = await onRequest(url, requestInit); 138 + } 139 + // fetch must be assigned here, otherwise it would throw the error: 140 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 141 + const _fetch = options.fetch ?? globalThis.fetch; 142 + const response = await _fetch(request); 143 + 144 + if (!response.ok) 145 + throw new Error( 146 + `SSE failed: ${response.status} ${response.statusText}`, 147 + ); 148 + 149 + if (!response.body) throw new Error('No body in SSE response'); 150 + 151 + const reader = response.body 152 + .pipeThrough(new TextDecoderStream()) 153 + .getReader(); 154 + 155 + let buffer = ''; 156 + 157 + const abortHandler = () => { 158 + try { 159 + reader.cancel(); 160 + } catch { 161 + // noop 162 + } 163 + }; 164 + 165 + signal.addEventListener('abort', abortHandler); 166 + 167 + try { 168 + while (true) { 169 + const { done, value } = await reader.read(); 170 + if (done) break; 171 + buffer += value; 172 + 173 + const chunks = buffer.split('\n\n'); 174 + buffer = chunks.pop() ?? ''; 175 + 176 + for (const chunk of chunks) { 177 + const lines = chunk.split('\n'); 178 + const dataLines: Array<string> = []; 179 + let eventName: string | undefined; 180 + 181 + for (const line of lines) { 182 + if (line.startsWith('data:')) { 183 + dataLines.push(line.replace(/^data:\s*/, '')); 184 + } else if (line.startsWith('event:')) { 185 + eventName = line.replace(/^event:\s*/, ''); 186 + } else if (line.startsWith('id:')) { 187 + lastEventId = line.replace(/^id:\s*/, ''); 188 + } else if (line.startsWith('retry:')) { 189 + const parsed = Number.parseInt( 190 + line.replace(/^retry:\s*/, ''), 191 + 10, 192 + ); 193 + if (!Number.isNaN(parsed)) { 194 + retryDelay = parsed; 195 + } 196 + } 197 + } 198 + 199 + let data: unknown; 200 + let parsedJson = false; 201 + 202 + if (dataLines.length) { 203 + const rawData = dataLines.join('\n'); 204 + try { 205 + data = JSON.parse(rawData); 206 + parsedJson = true; 207 + } catch { 208 + data = rawData; 209 + } 210 + } 211 + 212 + if (parsedJson) { 213 + if (responseValidator) { 214 + await responseValidator(data); 215 + } 216 + 217 + if (responseTransformer) { 218 + data = await responseTransformer(data); 219 + } 220 + } 221 + 222 + onSseEvent?.({ 223 + data, 224 + event: eventName, 225 + id: lastEventId, 226 + retry: retryDelay, 227 + }); 228 + 229 + if (dataLines.length) { 230 + yield data as any; 231 + } 232 + } 233 + } 234 + } finally { 235 + signal.removeEventListener('abort', abortHandler); 236 + reader.releaseLock(); 237 + } 238 + 239 + break; // exit loop on normal completion 240 + } catch (error) { 241 + // connection failed or aborted; retry after delay 242 + onSseError?.(error); 243 + 244 + if ( 245 + sseMaxRetryAttempts !== undefined && 246 + attempt >= sseMaxRetryAttempts 247 + ) { 248 + break; // stop after firing error 249 + } 250 + 251 + // exponential backoff: double retry each attempt, cap at 30s 252 + const backoff = Math.min( 253 + retryDelay * 2 ** (attempt - 1), 254 + sseMaxRetryDelay ?? 30000, 255 + ); 256 + await sleep(backoff); 257 + } 258 + } 259 + }; 260 + 261 + const stream = createStream(); 262 + 263 + return { stream }; 264 + };
+118
src/Den.Client.Web/src/lib/state/client/core/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth, AuthToken } from './auth.gen'; 4 + import type { 5 + BodySerializer, 6 + QuerySerializer, 7 + QuerySerializerOptions, 8 + } from './bodySerializer.gen'; 9 + 10 + export type HttpMethod = 11 + | 'connect' 12 + | 'delete' 13 + | 'get' 14 + | 'head' 15 + | 'options' 16 + | 'patch' 17 + | 'post' 18 + | 'put' 19 + | 'trace'; 20 + 21 + export type Client< 22 + RequestFn = never, 23 + Config = unknown, 24 + MethodFn = never, 25 + BuildUrlFn = never, 26 + SseFn = never, 27 + > = { 28 + /** 29 + * Returns the final request URL. 30 + */ 31 + buildUrl: BuildUrlFn; 32 + getConfig: () => Config; 33 + request: RequestFn; 34 + setConfig: (config: Config) => Config; 35 + } & { 36 + [K in HttpMethod]: MethodFn; 37 + } & ([SseFn] extends [never] 38 + ? { sse?: never } 39 + : { sse: { [K in HttpMethod]: SseFn } }); 40 + 41 + export interface Config { 42 + /** 43 + * Auth token or a function returning auth token. The resolved value will be 44 + * added to the request payload as defined by its `security` array. 45 + */ 46 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 47 + /** 48 + * A function for serializing request body parameter. By default, 49 + * {@link JSON.stringify()} will be used. 50 + */ 51 + bodySerializer?: BodySerializer | null; 52 + /** 53 + * An object containing any HTTP headers that you want to pre-populate your 54 + * `Headers` object with. 55 + * 56 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 57 + */ 58 + headers?: 59 + | RequestInit['headers'] 60 + | Record< 61 + string, 62 + | string 63 + | number 64 + | boolean 65 + | (string | number | boolean)[] 66 + | null 67 + | undefined 68 + | unknown 69 + >; 70 + /** 71 + * The request method. 72 + * 73 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 74 + */ 75 + method?: Uppercase<HttpMethod>; 76 + /** 77 + * A function for serializing request query parameters. By default, arrays 78 + * will be exploded in form style, objects will be exploded in deepObject 79 + * style, and reserved characters are percent-encoded. 80 + * 81 + * This method will have no effect if the native `paramsSerializer()` Axios 82 + * API function is used. 83 + * 84 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 85 + */ 86 + querySerializer?: QuerySerializer | QuerySerializerOptions; 87 + /** 88 + * A function validating request data. This is useful if you want to ensure 89 + * the request conforms to the desired shape, so it can be safely sent to 90 + * the server. 91 + */ 92 + requestValidator?: (data: unknown) => Promise<unknown>; 93 + /** 94 + * A function transforming response data before it's returned. This is useful 95 + * for post-processing data, e.g. converting ISO strings into Date objects. 96 + */ 97 + responseTransformer?: (data: unknown) => Promise<unknown>; 98 + /** 99 + * A function validating response data. This is useful if you want to ensure 100 + * the response conforms to the desired shape, so it can be safely passed to 101 + * the transformers and returned to the user. 102 + */ 103 + responseValidator?: (data: unknown) => Promise<unknown>; 104 + } 105 + 106 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 107 + ? true 108 + : [T] extends [never | undefined] 109 + ? [undefined] extends [T] 110 + ? false 111 + : true 112 + : false; 113 + 114 + export type OmitNever<T extends Record<string, unknown>> = { 115 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true 116 + ? never 117 + : K]: T[K]; 118 + };
+143
src/Den.Client.Web/src/lib/state/client/core/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; 4 + import { 5 + type ArraySeparatorStyle, 6 + serializeArrayParam, 7 + serializeObjectParam, 8 + serializePrimitiveParam, 9 + } from './pathSerializer.gen'; 10 + 11 + export interface PathSerializer { 12 + path: Record<string, unknown>; 13 + url: string; 14 + } 15 + 16 + export const PATH_PARAM_RE = /\{[^{}]+\}/g; 17 + 18 + export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 19 + let url = _url; 20 + const matches = _url.match(PATH_PARAM_RE); 21 + if (matches) { 22 + for (const match of matches) { 23 + let explode = false; 24 + let name = match.substring(1, match.length - 1); 25 + let style: ArraySeparatorStyle = 'simple'; 26 + 27 + if (name.endsWith('*')) { 28 + explode = true; 29 + name = name.substring(0, name.length - 1); 30 + } 31 + 32 + if (name.startsWith('.')) { 33 + name = name.substring(1); 34 + style = 'label'; 35 + } else if (name.startsWith(';')) { 36 + name = name.substring(1); 37 + style = 'matrix'; 38 + } 39 + 40 + const value = path[name]; 41 + 42 + if (value === undefined || value === null) { 43 + continue; 44 + } 45 + 46 + if (Array.isArray(value)) { 47 + url = url.replace( 48 + match, 49 + serializeArrayParam({ explode, name, style, value }), 50 + ); 51 + continue; 52 + } 53 + 54 + if (typeof value === 'object') { 55 + url = url.replace( 56 + match, 57 + serializeObjectParam({ 58 + explode, 59 + name, 60 + style, 61 + value: value as Record<string, unknown>, 62 + valueOnly: true, 63 + }), 64 + ); 65 + continue; 66 + } 67 + 68 + if (style === 'matrix') { 69 + url = url.replace( 70 + match, 71 + `;${serializePrimitiveParam({ 72 + name, 73 + value: value as string, 74 + })}`, 75 + ); 76 + continue; 77 + } 78 + 79 + const replaceValue = encodeURIComponent( 80 + style === 'label' ? `.${value as string}` : (value as string), 81 + ); 82 + url = url.replace(match, replaceValue); 83 + } 84 + } 85 + return url; 86 + }; 87 + 88 + export const getUrl = ({ 89 + baseUrl, 90 + path, 91 + query, 92 + querySerializer, 93 + url: _url, 94 + }: { 95 + baseUrl?: string; 96 + path?: Record<string, unknown>; 97 + query?: Record<string, unknown>; 98 + querySerializer: QuerySerializer; 99 + url: string; 100 + }) => { 101 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 102 + let url = (baseUrl ?? '') + pathUrl; 103 + if (path) { 104 + url = defaultPathSerializer({ path, url }); 105 + } 106 + let search = query ? querySerializer(query) : ''; 107 + if (search.startsWith('?')) { 108 + search = search.substring(1); 109 + } 110 + if (search) { 111 + url += `?${search}`; 112 + } 113 + return url; 114 + }; 115 + 116 + export function getValidRequestBody(options: { 117 + body?: unknown; 118 + bodySerializer?: BodySerializer | null; 119 + serializedBody?: unknown; 120 + }) { 121 + const hasBody = options.body !== undefined; 122 + const isSerializedBody = hasBody && options.bodySerializer; 123 + 124 + if (isSerializedBody) { 125 + if ('serializedBody' in options) { 126 + const hasSerializedBody = 127 + options.serializedBody !== undefined && options.serializedBody !== ''; 128 + 129 + return hasSerializedBody ? options.serializedBody : null; 130 + } 131 + 132 + // not all clients implement a serializedBody property (i.e. client-axios) 133 + return options.body !== '' ? options.body : null; 134 + } 135 + 136 + // plain/text body 137 + if (hasBody) { 138 + return options.body; 139 + } 140 + 141 + // no body was provided 142 + return undefined; 143 + }
+4
src/Den.Client.Web/src/lib/state/client/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type * from './types.gen'; 4 + export * from './sdk.gen';
+146
src/Den.Client.Web/src/lib/state/client/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Options as Options2, TDataShape } from './client'; 4 + import { client } from './client.gen'; 5 + import type { DeleteV1BudgetsByIdData, DeleteV1BudgetsByIdErrors, DeleteV1BudgetsByIdResponses, GetV1AuthMeData, GetV1AuthMeErrors, GetV1AuthMeResponses, GetV1BudgetsByIdData, GetV1BudgetsByIdErrors, GetV1BudgetsByIdResponses, GetV1BudgetsData, GetV1BudgetsErrors, GetV1BudgetsResponses, PostV1AuthLoginData, PostV1AuthLoginErrors, PostV1AuthLoginResponses, PostV1AuthRefreshData, PostV1AuthRefreshErrors, PostV1AuthRefreshResponses, PostV1AuthSignupData, PostV1AuthSignupErrors, PostV1AuthSignupResponses, PostV1BudgetsData, PostV1BudgetsErrors, PostV1BudgetsResponses, PutV1BudgetsByIdData, PutV1BudgetsByIdErrors, PutV1BudgetsByIdResponses } from './types.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + export const postV1AuthSignup = <ThrowOnError extends boolean = false>(options: Options<PostV1AuthSignupData, ThrowOnError>) => { 22 + return (options.client ?? client).post<PostV1AuthSignupResponses, PostV1AuthSignupErrors, ThrowOnError>({ 23 + responseType: 'json', 24 + url: '/v1/auth/signup', 25 + ...options, 26 + headers: { 27 + 'Content-Type': 'application/json', 28 + ...options.headers 29 + } 30 + }); 31 + }; 32 + 33 + export const postV1AuthLogin = <ThrowOnError extends boolean = false>(options: Options<PostV1AuthLoginData, ThrowOnError>) => { 34 + return (options.client ?? client).post<PostV1AuthLoginResponses, PostV1AuthLoginErrors, ThrowOnError>({ 35 + responseType: 'json', 36 + url: '/v1/auth/login', 37 + ...options, 38 + headers: { 39 + 'Content-Type': 'application/json', 40 + ...options.headers 41 + } 42 + }); 43 + }; 44 + 45 + export const postV1AuthRefresh = <ThrowOnError extends boolean = false>(options: Options<PostV1AuthRefreshData, ThrowOnError>) => { 46 + return (options.client ?? client).post<PostV1AuthRefreshResponses, PostV1AuthRefreshErrors, ThrowOnError>({ 47 + responseType: 'json', 48 + url: '/v1/auth/refresh', 49 + ...options, 50 + headers: { 51 + 'Content-Type': 'application/json', 52 + ...options.headers 53 + } 54 + }); 55 + }; 56 + 57 + export const getV1AuthMe = <ThrowOnError extends boolean = false>(options?: Options<GetV1AuthMeData, ThrowOnError>) => { 58 + return (options?.client ?? client).get<GetV1AuthMeResponses, GetV1AuthMeErrors, ThrowOnError>({ 59 + responseType: 'json', 60 + security: [ 61 + { 62 + scheme: 'bearer', 63 + type: 'http' 64 + } 65 + ], 66 + url: '/v1/auth/me', 67 + ...options 68 + }); 69 + }; 70 + 71 + export const getV1Budgets = <ThrowOnError extends boolean = false>(options?: Options<GetV1BudgetsData, ThrowOnError>) => { 72 + return (options?.client ?? client).get<GetV1BudgetsResponses, GetV1BudgetsErrors, ThrowOnError>({ 73 + responseType: 'json', 74 + security: [ 75 + { 76 + scheme: 'bearer', 77 + type: 'http' 78 + } 79 + ], 80 + url: '/v1/budgets', 81 + ...options 82 + }); 83 + }; 84 + 85 + export const postV1Budgets = <ThrowOnError extends boolean = false>(options: Options<PostV1BudgetsData, ThrowOnError>) => { 86 + return (options.client ?? client).post<PostV1BudgetsResponses, PostV1BudgetsErrors, ThrowOnError>({ 87 + responseType: 'json', 88 + security: [ 89 + { 90 + scheme: 'bearer', 91 + type: 'http' 92 + } 93 + ], 94 + url: '/v1/budgets', 95 + ...options, 96 + headers: { 97 + 'Content-Type': 'application/json', 98 + ...options.headers 99 + } 100 + }); 101 + }; 102 + 103 + export const deleteV1BudgetsById = <ThrowOnError extends boolean = false>(options: Options<DeleteV1BudgetsByIdData, ThrowOnError>) => { 104 + return (options.client ?? client).delete<DeleteV1BudgetsByIdResponses, DeleteV1BudgetsByIdErrors, ThrowOnError>({ 105 + security: [ 106 + { 107 + scheme: 'bearer', 108 + type: 'http' 109 + } 110 + ], 111 + url: '/v1/budgets/{id}', 112 + ...options 113 + }); 114 + }; 115 + 116 + export const getV1BudgetsById = <ThrowOnError extends boolean = false>(options: Options<GetV1BudgetsByIdData, ThrowOnError>) => { 117 + return (options.client ?? client).get<GetV1BudgetsByIdResponses, GetV1BudgetsByIdErrors, ThrowOnError>({ 118 + responseType: 'json', 119 + security: [ 120 + { 121 + scheme: 'bearer', 122 + type: 'http' 123 + } 124 + ], 125 + url: '/v1/budgets/{id}', 126 + ...options 127 + }); 128 + }; 129 + 130 + export const putV1BudgetsById = <ThrowOnError extends boolean = false>(options: Options<PutV1BudgetsByIdData, ThrowOnError>) => { 131 + return (options.client ?? client).put<PutV1BudgetsByIdResponses, PutV1BudgetsByIdErrors, ThrowOnError>({ 132 + responseType: 'json', 133 + security: [ 134 + { 135 + scheme: 'bearer', 136 + type: 'http' 137 + } 138 + ], 139 + url: '/v1/budgets/{id}', 140 + ...options, 141 + headers: { 142 + 'Content-Type': 'application/json', 143 + ...options.headers 144 + } 145 + }); 146 + };
+331
src/Den.Client.Web/src/lib/state/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type ClientOptions = { 4 + baseURL: 'https://host.docker.internal:7165/' | (string & {}); 5 + }; 6 + 7 + export type AuthResponse = { 8 + accessToken: string; 9 + refreshToken: string; 10 + expiresIn: string; 11 + tokenType?: string; 12 + }; 13 + 14 + export type BudgetResponse = { 15 + id: string; 16 + displayName: string; 17 + description: string; 18 + period: string; 19 + total: number; 20 + currency: string; 21 + ownerId: string; 22 + createdAt: string; 23 + updatedAt: string; 24 + }; 25 + 26 + export type CreateBudgetRequest = { 27 + displayName: string; 28 + description: string; 29 + period: string; 30 + total: number; 31 + currency: string; 32 + }; 33 + 34 + export type LoginRequest = { 35 + username: string; 36 + password: string; 37 + }; 38 + 39 + export type MeResponse = { 40 + id: string; 41 + username: string; 42 + displayName: string; 43 + email: string; 44 + role: string; 45 + }; 46 + 47 + export type ProblemDetails = { 48 + type?: string | null; 49 + title?: string | null; 50 + status?: number | null; 51 + detail?: string | null; 52 + instance?: string | null; 53 + }; 54 + 55 + export type RefreshRequest = { 56 + refreshToken: string; 57 + }; 58 + 59 + export type SignupRequest = { 60 + username: string; 61 + displayName: string; 62 + email: string; 63 + password: string; 64 + }; 65 + 66 + export type UpdateBudgetRequest = { 67 + displayName: string | null; 68 + description: string | null; 69 + period: string | null; 70 + total: number | null; 71 + currency: string | null; 72 + }; 73 + 74 + export type PostV1AuthSignupData = { 75 + body: SignupRequest; 76 + path?: never; 77 + query?: never; 78 + url: '/v1/auth/signup'; 79 + }; 80 + 81 + export type PostV1AuthSignupErrors = { 82 + /** 83 + * Bad Request 84 + */ 85 + 400: ProblemDetails; 86 + }; 87 + 88 + export type PostV1AuthSignupError = PostV1AuthSignupErrors[keyof PostV1AuthSignupErrors]; 89 + 90 + export type PostV1AuthSignupResponses = { 91 + /** 92 + * Created 93 + */ 94 + 201: AuthResponse; 95 + }; 96 + 97 + export type PostV1AuthSignupResponse = PostV1AuthSignupResponses[keyof PostV1AuthSignupResponses]; 98 + 99 + export type PostV1AuthLoginData = { 100 + body: LoginRequest; 101 + path?: never; 102 + query?: never; 103 + url: '/v1/auth/login'; 104 + }; 105 + 106 + export type PostV1AuthLoginErrors = { 107 + /** 108 + * Unauthorized 109 + */ 110 + 401: ProblemDetails; 111 + }; 112 + 113 + export type PostV1AuthLoginError = PostV1AuthLoginErrors[keyof PostV1AuthLoginErrors]; 114 + 115 + export type PostV1AuthLoginResponses = { 116 + /** 117 + * OK 118 + */ 119 + 200: AuthResponse; 120 + }; 121 + 122 + export type PostV1AuthLoginResponse = PostV1AuthLoginResponses[keyof PostV1AuthLoginResponses]; 123 + 124 + export type PostV1AuthRefreshData = { 125 + body: RefreshRequest; 126 + path?: never; 127 + query?: never; 128 + url: '/v1/auth/refresh'; 129 + }; 130 + 131 + export type PostV1AuthRefreshErrors = { 132 + /** 133 + * Unauthorized 134 + */ 135 + 401: ProblemDetails; 136 + }; 137 + 138 + export type PostV1AuthRefreshError = PostV1AuthRefreshErrors[keyof PostV1AuthRefreshErrors]; 139 + 140 + export type PostV1AuthRefreshResponses = { 141 + /** 142 + * OK 143 + */ 144 + 200: AuthResponse; 145 + }; 146 + 147 + export type PostV1AuthRefreshResponse = PostV1AuthRefreshResponses[keyof PostV1AuthRefreshResponses]; 148 + 149 + export type GetV1AuthMeData = { 150 + body?: never; 151 + path?: never; 152 + query?: never; 153 + url: '/v1/auth/me'; 154 + }; 155 + 156 + export type GetV1AuthMeErrors = { 157 + /** 158 + * Unauthorized 159 + */ 160 + 401: ProblemDetails; 161 + }; 162 + 163 + export type GetV1AuthMeError = GetV1AuthMeErrors[keyof GetV1AuthMeErrors]; 164 + 165 + export type GetV1AuthMeResponses = { 166 + /** 167 + * OK 168 + */ 169 + 200: MeResponse; 170 + }; 171 + 172 + export type GetV1AuthMeResponse = GetV1AuthMeResponses[keyof GetV1AuthMeResponses]; 173 + 174 + export type GetV1BudgetsData = { 175 + body?: never; 176 + path?: never; 177 + query?: never; 178 + url: '/v1/budgets'; 179 + }; 180 + 181 + export type GetV1BudgetsErrors = { 182 + /** 183 + * Unauthorized 184 + */ 185 + 401: ProblemDetails; 186 + }; 187 + 188 + export type GetV1BudgetsError = GetV1BudgetsErrors[keyof GetV1BudgetsErrors]; 189 + 190 + export type GetV1BudgetsResponses = { 191 + /** 192 + * OK 193 + */ 194 + 200: Array<BudgetResponse>; 195 + }; 196 + 197 + export type GetV1BudgetsResponse = GetV1BudgetsResponses[keyof GetV1BudgetsResponses]; 198 + 199 + export type PostV1BudgetsData = { 200 + body: CreateBudgetRequest; 201 + path?: never; 202 + query?: never; 203 + url: '/v1/budgets'; 204 + }; 205 + 206 + export type PostV1BudgetsErrors = { 207 + /** 208 + * Bad Request 209 + */ 210 + 400: ProblemDetails; 211 + /** 212 + * Unauthorized 213 + */ 214 + 401: ProblemDetails; 215 + /** 216 + * Forbidden 217 + */ 218 + 403: ProblemDetails; 219 + }; 220 + 221 + export type PostV1BudgetsError = PostV1BudgetsErrors[keyof PostV1BudgetsErrors]; 222 + 223 + export type PostV1BudgetsResponses = { 224 + /** 225 + * Created 226 + */ 227 + 201: BudgetResponse; 228 + }; 229 + 230 + export type PostV1BudgetsResponse = PostV1BudgetsResponses[keyof PostV1BudgetsResponses]; 231 + 232 + export type DeleteV1BudgetsByIdData = { 233 + body?: never; 234 + path: { 235 + id: string; 236 + }; 237 + query?: never; 238 + url: '/v1/budgets/{id}'; 239 + }; 240 + 241 + export type DeleteV1BudgetsByIdErrors = { 242 + /** 243 + * Unauthorized 244 + */ 245 + 401: ProblemDetails; 246 + /** 247 + * Forbidden 248 + */ 249 + 403: ProblemDetails; 250 + /** 251 + * Not Found 252 + */ 253 + 404: ProblemDetails; 254 + }; 255 + 256 + export type DeleteV1BudgetsByIdError = DeleteV1BudgetsByIdErrors[keyof DeleteV1BudgetsByIdErrors]; 257 + 258 + export type DeleteV1BudgetsByIdResponses = { 259 + /** 260 + * No Content 261 + */ 262 + 204: void; 263 + }; 264 + 265 + export type DeleteV1BudgetsByIdResponse = DeleteV1BudgetsByIdResponses[keyof DeleteV1BudgetsByIdResponses]; 266 + 267 + export type GetV1BudgetsByIdData = { 268 + body?: never; 269 + path: { 270 + id: string; 271 + }; 272 + query?: never; 273 + url: '/v1/budgets/{id}'; 274 + }; 275 + 276 + export type GetV1BudgetsByIdErrors = { 277 + /** 278 + * Not Found 279 + */ 280 + 404: ProblemDetails; 281 + }; 282 + 283 + export type GetV1BudgetsByIdError = GetV1BudgetsByIdErrors[keyof GetV1BudgetsByIdErrors]; 284 + 285 + export type GetV1BudgetsByIdResponses = { 286 + /** 287 + * OK 288 + */ 289 + 200: BudgetResponse; 290 + }; 291 + 292 + export type GetV1BudgetsByIdResponse = GetV1BudgetsByIdResponses[keyof GetV1BudgetsByIdResponses]; 293 + 294 + export type PutV1BudgetsByIdData = { 295 + body: UpdateBudgetRequest; 296 + path: { 297 + id: string; 298 + }; 299 + query?: never; 300 + url: '/v1/budgets/{id}'; 301 + }; 302 + 303 + export type PutV1BudgetsByIdErrors = { 304 + /** 305 + * Bad Request 306 + */ 307 + 400: ProblemDetails; 308 + /** 309 + * Unauthorized 310 + */ 311 + 401: ProblemDetails; 312 + /** 313 + * Forbidden 314 + */ 315 + 403: ProblemDetails; 316 + /** 317 + * Not Found 318 + */ 319 + 404: ProblemDetails; 320 + }; 321 + 322 + export type PutV1BudgetsByIdError = PutV1BudgetsByIdErrors[keyof PutV1BudgetsByIdErrors]; 323 + 324 + export type PutV1BudgetsByIdResponses = { 325 + /** 326 + * OK 327 + */ 328 + 200: BudgetResponse; 329 + }; 330 + 331 + export type PutV1BudgetsByIdResponse = PutV1BudgetsByIdResponses[keyof PutV1BudgetsByIdResponses];
+28
src/Den.Client.Web/src/lib/state/queries/auth.ts
··· 1 + import { useMutation, useQuery } from "@tanstack/react-query" 2 + import { getV1AuthMeOptions, postV1AuthLoginMutation } from "../client/@tanstack/react-query.gen" 3 + import { useAuth } from "../auth"; 4 + 5 + export const useMeQuery = () => { 6 + const { tokens } = useAuth(); 7 + 8 + return useQuery({ 9 + ...getV1AuthMeOptions({ 10 + auth: tokens?.accessToken, 11 + }), 12 + enabled: !!tokens?.accessToken, 13 + }); 14 + }; 15 + 16 + export const useLoginMutation = () => { 17 + const { login } = useAuth(); 18 + 19 + return useMutation({ 20 + ...postV1AuthLoginMutation(), 21 + onSuccess: (data) => { 22 + login({ 23 + accessToken: data.accessToken, 24 + refreshToken: data.refreshToken, 25 + }); 26 + }, 27 + }); 28 + }
+14
src/Den.Client.Web/src/lib/state/queries/budgets.ts
··· 1 + import { useMutation, useQuery } from "@tanstack/react-query"; 2 + import { getV1BudgetsOptions, postV1BudgetsMutation } from "../client/@tanstack/react-query.gen"; 3 + 4 + export const useBudgetsListQuery = () => { 5 + return useQuery({ 6 + ...getV1BudgetsOptions(), 7 + }); 8 + }; 9 + 10 + export const useBudgetsCreateMutation = () => { 11 + return useMutation({ 12 + ...postV1BudgetsMutation(), 13 + }); 14 + };
+6
src/Den.Client.Web/src/lib/utils.ts
··· 1 + import { clsx, type ClassValue } from "clsx" 2 + import { twMerge } from "tailwind-merge" 3 + 4 + export function cn(...inputs: ClassValue[]) { 5 + return twMerge(clsx(inputs)) 6 + }
+61
src/Den.Client.Web/src/main.tsx
··· 1 + import { StrictMode, useEffect } from 'react'; 2 + import { createRoot } from 'react-dom/client'; 3 + import { createRouter, RouterProvider } from '@tanstack/react-router'; 4 + import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'; 5 + import { routeTree } from './routeTree.gen'; 6 + import './i18n' 7 + import './index.css' 8 + import { ThemeProvider } from './components/theme-provider'; 9 + import { Toaster } from '@/components/ui/sonner'; 10 + import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 11 + import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; 12 + import { client } from './lib/state/client/client.gen'; 13 + import { AuthProvider, useAuth } from './lib/state/auth'; 14 + 15 + const router = createRouter({ 16 + routeTree, 17 + context: { 18 + auth: undefined!, // will be set in InnerApp 19 + }, 20 + }); 21 + 22 + declare module '@tanstack/react-router' { 23 + interface Register { 24 + router: typeof router 25 + } 26 + } 27 + 28 + client.setConfig({ 29 + baseURL: `${window.origin}/api`, 30 + }); 31 + const queryClient = new QueryClient(); 32 + 33 + function InnerApp() { 34 + const auth = useAuth(); 35 + 36 + // invalidate router whenever auth state changes so beforeLoad hooks re-run 37 + useEffect(() => { 38 + router.invalidate(); 39 + }, [auth.isAuthenticated]); 40 + 41 + return ( 42 + <> 43 + <RouterProvider router={router} context={{ auth }} /> 44 + <TanStackRouterDevtools router={router} position="bottom-right" /> 45 + <Toaster /> 46 + </> 47 + ); 48 + } 49 + 50 + createRoot(document.getElementById('root')!).render( 51 + <StrictMode> 52 + <QueryClientProvider client={queryClient}> 53 + <AuthProvider> 54 + <ThemeProvider defaultTheme="dark"> 55 + <InnerApp /> 56 + </ThemeProvider> 57 + </AuthProvider> 58 + <ReactQueryDevtools /> 59 + </QueryClientProvider> 60 + </StrictMode>, 61 + )
+193
src/Den.Client.Web/src/routeTree.gen.ts
··· 1 + /* eslint-disable */ 2 + 3 + // @ts-nocheck 4 + 5 + // noinspection JSUnusedGlobalSymbols 6 + 7 + // This file was automatically generated by TanStack Router. 8 + // You should NOT make any changes in this file as it will be overwritten. 9 + // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 + 11 + import { Route as rootRouteImport } from './routes/__root' 12 + import { Route as authAuthRouteImport } from './routes/(auth)/_auth' 13 + import { Route as appHomeRouteImport } from './routes/(app)/_home' 14 + import { Route as appHomeIndexRouteImport } from './routes/(app)/_home.index' 15 + import { Route as appHomeBudgetsIndexRouteImport } from './routes/(app)/_home/budgets/index' 16 + import { Route as authAuthAuthSignupRouteImport } from './routes/(auth)/_auth.auth.signup' 17 + import { Route as authAuthAuthLoginRouteImport } from './routes/(auth)/_auth.auth.login' 18 + import { Route as appHomeBudgetsCreateRouteImport } from './routes/(app)/_home/budgets/create' 19 + 20 + const authAuthRoute = authAuthRouteImport.update({ 21 + id: '/(auth)/_auth', 22 + getParentRoute: () => rootRouteImport, 23 + } as any) 24 + const appHomeRoute = appHomeRouteImport.update({ 25 + id: '/(app)/_home', 26 + getParentRoute: () => rootRouteImport, 27 + } as any) 28 + const appHomeIndexRoute = appHomeIndexRouteImport.update({ 29 + id: '/', 30 + path: '/', 31 + getParentRoute: () => appHomeRoute, 32 + } as any) 33 + const appHomeBudgetsIndexRoute = appHomeBudgetsIndexRouteImport.update({ 34 + id: '/budgets/', 35 + path: '/budgets/', 36 + getParentRoute: () => appHomeRoute, 37 + } as any) 38 + const authAuthAuthSignupRoute = authAuthAuthSignupRouteImport.update({ 39 + id: '/auth/signup', 40 + path: '/auth/signup', 41 + getParentRoute: () => authAuthRoute, 42 + } as any) 43 + const authAuthAuthLoginRoute = authAuthAuthLoginRouteImport.update({ 44 + id: '/auth/login', 45 + path: '/auth/login', 46 + getParentRoute: () => authAuthRoute, 47 + } as any) 48 + const appHomeBudgetsCreateRoute = appHomeBudgetsCreateRouteImport.update({ 49 + id: '/budgets/create', 50 + path: '/budgets/create', 51 + getParentRoute: () => appHomeRoute, 52 + } as any) 53 + 54 + export interface FileRoutesByFullPath { 55 + '/': typeof appHomeIndexRoute 56 + '/budgets/create': typeof appHomeBudgetsCreateRoute 57 + '/auth/login': typeof authAuthAuthLoginRoute 58 + '/auth/signup': typeof authAuthAuthSignupRoute 59 + '/budgets': typeof appHomeBudgetsIndexRoute 60 + } 61 + export interface FileRoutesByTo { 62 + '/': typeof appHomeIndexRoute 63 + '/budgets/create': typeof appHomeBudgetsCreateRoute 64 + '/auth/login': typeof authAuthAuthLoginRoute 65 + '/auth/signup': typeof authAuthAuthSignupRoute 66 + '/budgets': typeof appHomeBudgetsIndexRoute 67 + } 68 + export interface FileRoutesById { 69 + __root__: typeof rootRouteImport 70 + '/(app)/_home': typeof appHomeRouteWithChildren 71 + '/(auth)/_auth': typeof authAuthRouteWithChildren 72 + '/(app)/_home/': typeof appHomeIndexRoute 73 + '/(app)/_home/budgets/create': typeof appHomeBudgetsCreateRoute 74 + '/(auth)/_auth/auth/login': typeof authAuthAuthLoginRoute 75 + '/(auth)/_auth/auth/signup': typeof authAuthAuthSignupRoute 76 + '/(app)/_home/budgets/': typeof appHomeBudgetsIndexRoute 77 + } 78 + export interface FileRouteTypes { 79 + fileRoutesByFullPath: FileRoutesByFullPath 80 + fullPaths: 81 + | '/' 82 + | '/budgets/create' 83 + | '/auth/login' 84 + | '/auth/signup' 85 + | '/budgets' 86 + fileRoutesByTo: FileRoutesByTo 87 + to: '/' | '/budgets/create' | '/auth/login' | '/auth/signup' | '/budgets' 88 + id: 89 + | '__root__' 90 + | '/(app)/_home' 91 + | '/(auth)/_auth' 92 + | '/(app)/_home/' 93 + | '/(app)/_home/budgets/create' 94 + | '/(auth)/_auth/auth/login' 95 + | '/(auth)/_auth/auth/signup' 96 + | '/(app)/_home/budgets/' 97 + fileRoutesById: FileRoutesById 98 + } 99 + export interface RootRouteChildren { 100 + appHomeRoute: typeof appHomeRouteWithChildren 101 + authAuthRoute: typeof authAuthRouteWithChildren 102 + } 103 + 104 + declare module '@tanstack/react-router' { 105 + interface FileRoutesByPath { 106 + '/(auth)/_auth': { 107 + id: '/(auth)/_auth' 108 + path: '' 109 + fullPath: '' 110 + preLoaderRoute: typeof authAuthRouteImport 111 + parentRoute: typeof rootRouteImport 112 + } 113 + '/(app)/_home': { 114 + id: '/(app)/_home' 115 + path: '' 116 + fullPath: '' 117 + preLoaderRoute: typeof appHomeRouteImport 118 + parentRoute: typeof rootRouteImport 119 + } 120 + '/(app)/_home/': { 121 + id: '/(app)/_home/' 122 + path: '/' 123 + fullPath: '/' 124 + preLoaderRoute: typeof appHomeIndexRouteImport 125 + parentRoute: typeof appHomeRoute 126 + } 127 + '/(app)/_home/budgets/': { 128 + id: '/(app)/_home/budgets/' 129 + path: '/budgets' 130 + fullPath: '/budgets' 131 + preLoaderRoute: typeof appHomeBudgetsIndexRouteImport 132 + parentRoute: typeof appHomeRoute 133 + } 134 + '/(auth)/_auth/auth/signup': { 135 + id: '/(auth)/_auth/auth/signup' 136 + path: '/auth/signup' 137 + fullPath: '/auth/signup' 138 + preLoaderRoute: typeof authAuthAuthSignupRouteImport 139 + parentRoute: typeof authAuthRoute 140 + } 141 + '/(auth)/_auth/auth/login': { 142 + id: '/(auth)/_auth/auth/login' 143 + path: '/auth/login' 144 + fullPath: '/auth/login' 145 + preLoaderRoute: typeof authAuthAuthLoginRouteImport 146 + parentRoute: typeof authAuthRoute 147 + } 148 + '/(app)/_home/budgets/create': { 149 + id: '/(app)/_home/budgets/create' 150 + path: '/budgets/create' 151 + fullPath: '/budgets/create' 152 + preLoaderRoute: typeof appHomeBudgetsCreateRouteImport 153 + parentRoute: typeof appHomeRoute 154 + } 155 + } 156 + } 157 + 158 + interface appHomeRouteChildren { 159 + appHomeIndexRoute: typeof appHomeIndexRoute 160 + appHomeBudgetsCreateRoute: typeof appHomeBudgetsCreateRoute 161 + appHomeBudgetsIndexRoute: typeof appHomeBudgetsIndexRoute 162 + } 163 + 164 + const appHomeRouteChildren: appHomeRouteChildren = { 165 + appHomeIndexRoute: appHomeIndexRoute, 166 + appHomeBudgetsCreateRoute: appHomeBudgetsCreateRoute, 167 + appHomeBudgetsIndexRoute: appHomeBudgetsIndexRoute, 168 + } 169 + 170 + const appHomeRouteWithChildren = 171 + appHomeRoute._addFileChildren(appHomeRouteChildren) 172 + 173 + interface authAuthRouteChildren { 174 + authAuthAuthLoginRoute: typeof authAuthAuthLoginRoute 175 + authAuthAuthSignupRoute: typeof authAuthAuthSignupRoute 176 + } 177 + 178 + const authAuthRouteChildren: authAuthRouteChildren = { 179 + authAuthAuthLoginRoute: authAuthAuthLoginRoute, 180 + authAuthAuthSignupRoute: authAuthAuthSignupRoute, 181 + } 182 + 183 + const authAuthRouteWithChildren = authAuthRoute._addFileChildren( 184 + authAuthRouteChildren, 185 + ) 186 + 187 + const rootRouteChildren: RootRouteChildren = { 188 + appHomeRoute: appHomeRouteWithChildren, 189 + authAuthRoute: authAuthRouteWithChildren, 190 + } 191 + export const routeTree = rootRouteImport 192 + ._addFileChildren(rootRouteChildren) 193 + ._addFileTypes<FileRouteTypes>()
+76
src/Den.Client.Web/src/routes/(app)/_home.index.tsx
··· 1 + import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from '@/components/ui/breadcrumb'; 2 + import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 3 + import { Separator } from '@/components/ui/separator'; 4 + import { SidebarTrigger } from '@/components/ui/sidebar'; 5 + import { createFileRoute, Link } from '@tanstack/react-router' 6 + import { useTranslation } from 'react-i18next'; 7 + import { BudgetCards } from '@/components/views/dashboard/budget-cards'; 8 + import { SummaryCards } from '@/components/views/dashboard/summary-cards'; 9 + 10 + export const Route = createFileRoute('/(app)/_home/')({ 11 + component: () => { 12 + const { t } = useTranslation(); 13 + 14 + return ( 15 + <> 16 + <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4"> 17 + <SidebarTrigger /> 18 + <Separator orientation="vertical" className="data-[orientation=vertical]:h-4" /> 19 + <Breadcrumb> 20 + <BreadcrumbList> 21 + <BreadcrumbItem className="hidden md:block"> 22 + <BreadcrumbLink asChild> 23 + <Link to="/">{t('dashboard.title')}</Link> 24 + </BreadcrumbLink> 25 + </BreadcrumbItem> 26 + </BreadcrumbList> 27 + </Breadcrumb> 28 + </header> 29 + 30 + <div className="flex flex-1 flex-col gap-4 p-4"> 31 + <SummaryCards /> 32 + <BudgetCards /> 33 + <Separator /> 34 + 35 + <div className="grid lg:grid-cols-2 gap-4"> 36 + <Card> 37 + <CardHeader> 38 + <CardTitle>Shopping list</CardTitle> 39 + </CardHeader> 40 + <CardContent> 41 + Table here 42 + </CardContent> 43 + </Card> 44 + 45 + <Card> 46 + <CardHeader> 47 + <CardTitle>Reminders</CardTitle> 48 + </CardHeader> 49 + <CardContent> 50 + Table here 51 + </CardContent> 52 + </Card> 53 + 54 + <Card> 55 + <CardHeader> 56 + <CardTitle>Calendar</CardTitle> 57 + </CardHeader> 58 + <CardContent> 59 + Table here 60 + </CardContent> 61 + </Card> 62 + 63 + <Card> 64 + <CardHeader> 65 + <CardTitle>Upcoming movies</CardTitle> 66 + </CardHeader> 67 + <CardContent> 68 + Table here 69 + </CardContent> 70 + </Card> 71 + </div> 72 + </div> 73 + </> 74 + ); 75 + } 76 + });
+28
src/Den.Client.Web/src/routes/(app)/_home.tsx
··· 1 + import { AppSidebar } from '@/components/app-sidebar'; 2 + import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'; 3 + import { createFileRoute, Outlet, redirect } from '@tanstack/react-router' 4 + 5 + export const Route = createFileRoute('/(app)/_home')({ 6 + beforeLoad: ({ context, location }) => { 7 + if (!context.auth.isAuthenticated) { 8 + throw redirect({ 9 + to: '/auth/login', 10 + search: { 11 + redirect: location.href, 12 + }, 13 + }) 14 + } 15 + }, 16 + component: RouteComponent, 17 + }) 18 + 19 + function RouteComponent() { 20 + return ( 21 + <SidebarProvider> 22 + <AppSidebar /> 23 + <SidebarInset> 24 + <Outlet /> 25 + </SidebarInset> 26 + </SidebarProvider> 27 + ); 28 + }
+53
src/Den.Client.Web/src/routes/(app)/_home/budgets/create.tsx
··· 1 + import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from '@/components/ui/breadcrumb'; 2 + import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; 3 + import { SidebarTrigger } from '@/components/ui/sidebar'; 4 + import { CreateBudgetForm } from '@/components/views/budgets/create-form'; 5 + import { Separator } from '@radix-ui/react-separator'; 6 + import { createFileRoute, Link } from '@tanstack/react-router' 7 + import { useTranslation } from 'react-i18next'; 8 + 9 + export const Route = createFileRoute('/(app)/_home/budgets/create')({ 10 + component: () => { 11 + const { t } = useTranslation(); 12 + 13 + return ( 14 + <> 15 + <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4"> 16 + <SidebarTrigger /> 17 + <Separator orientation="vertical" className="data-[orientation=vertical]:h-4" /> 18 + <Breadcrumb> 19 + <BreadcrumbList> 20 + <BreadcrumbItem className="hidden md:block"> 21 + <BreadcrumbLink asChild> 22 + <Link to="/budgets"> 23 + {t('menu.budgeting.title')} 24 + </Link> 25 + </BreadcrumbLink> 26 + </BreadcrumbItem> 27 + <BreadcrumbSeparator /> 28 + <BreadcrumbItem> 29 + <BreadcrumbPage> 30 + New Budget 31 + </BreadcrumbPage> 32 + </BreadcrumbItem> 33 + </BreadcrumbList> 34 + </Breadcrumb> 35 + </header> 36 + 37 + <div className="flex flex-1 justify-center p-4"> 38 + <div className="flex flex-col w-full max-w-2xl gap-6"> 39 + <Card> 40 + <CardHeader> 41 + <CardTitle>{t('budgeting.create.title')}</CardTitle> 42 + <CardDescription>{t('budgeting.create.description')}</CardDescription> 43 + </CardHeader> 44 + <CardContent> 45 + <CreateBudgetForm /> 46 + </CardContent> 47 + </Card> 48 + </div> 49 + </div> 50 + </> 51 + ); 52 + }, 53 + });
+57
src/Den.Client.Web/src/routes/(app)/_home/budgets/index.tsx
··· 1 + import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, BreadcrumbPage } from '@/components/ui/breadcrumb'; 2 + import { SidebarTrigger } from '@/components/ui/sidebar'; 3 + import { Separator } from '@/components/ui/separator'; 4 + import { createFileRoute, Link } from '@tanstack/react-router' 5 + import { BudgetsList } from '@/components/views/budgets/list/budgets-list'; 6 + import { useTranslation } from 'react-i18next'; 7 + import { Button } from '@/components/ui/button'; 8 + import { PlusIcon } from 'lucide-react'; 9 + 10 + export const Route = createFileRoute('/(app)/_home/budgets/')({ 11 + component: () => { 12 + const { t } = useTranslation(); 13 + 14 + return ( 15 + <> 16 + <header className="flex h-16 shrink-0 items-center gap-2 border-b px-4"> 17 + <SidebarTrigger /> 18 + <Separator orientation="vertical" className="data-[orientation=vertical]:h-4" /> 19 + <Breadcrumb> 20 + <BreadcrumbList> 21 + <BreadcrumbItem className="hidden md:block"> 22 + <BreadcrumbLink asChild> 23 + <Link to="/budgets">{t('menu.budgeting.title')}</Link> 24 + </BreadcrumbLink> 25 + </BreadcrumbItem> 26 + <BreadcrumbSeparator /> 27 + <BreadcrumbItem> 28 + <BreadcrumbPage> 29 + {t('menu.budgeting.allBudgets')} 30 + </BreadcrumbPage> 31 + </BreadcrumbItem> 32 + </BreadcrumbList> 33 + </Breadcrumb> 34 + </header> 35 + 36 + <div className="flex flex-1 flex-col gap-4 p-4"> 37 + <div className="flex justify-between items-center"> 38 + <div className="flex flex-col gap-2"> 39 + <h1 className="text-2xl font-semibold">{t('menu.budgeting.allBudgets')}</h1> 40 + <p className="text-muted-foreground">Track your finances using Den's Budgeting feature. Below are any of your configured budgets.</p> 41 + </div> 42 + <Button variant="outline" asChild> 43 + <Link to="/budgets/create"> 44 + <PlusIcon /> 45 + {t('budgeting.create.doNewBudget')} 46 + </Link> 47 + </Button> 48 + </div> 49 + 50 + <Separator /> 51 + 52 + <BudgetsList /> 53 + </div> 54 + </> 55 + ) 56 + }, 57 + });
+33
src/Den.Client.Web/src/routes/(auth)/_auth.auth.login.tsx
··· 1 + import { Button } from '@/components/ui/button'; 2 + import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; 3 + import { LoginForm } from '@/components/views/auth/login-form'; 4 + import { createFileRoute, Link } from '@tanstack/react-router'; 5 + import { ExternalLink } from 'lucide-react'; 6 + import { useTranslation } from 'react-i18next'; 7 + 8 + export const Route = createFileRoute('/(auth)/_auth/auth/login')({ 9 + component: () => { 10 + const { t } = useTranslation(); 11 + 12 + return ( 13 + <> 14 + <title>{`${t('login.page.title')} — ${t('appTitle')}`}</title> 15 + <Card> 16 + <CardHeader> 17 + <CardTitle>{t('login.form.title')}</CardTitle> 18 + <CardDescription>{t('login.form.subtitle')}</CardDescription> 19 + <CardAction> 20 + <Button variant="link" asChild> 21 + <Link to="/auth/signup">{t('signup.page.title')} <ExternalLink /></Link> 22 + </Button> 23 + </CardAction> 24 + </CardHeader> 25 + 26 + <CardContent> 27 + <LoginForm /> 28 + </CardContent> 29 + </Card> 30 + </> 31 + ); 32 + }, 33 + });
+33
src/Den.Client.Web/src/routes/(auth)/_auth.auth.signup.tsx
··· 1 + import { Button } from '@/components/ui/button'; 2 + import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardAction } from '@/components/ui/card'; 3 + import { SignupForm } from '@/components/views/auth/signup-form'; 4 + import { createFileRoute, Link } from '@tanstack/react-router' 5 + import { ExternalLink } from 'lucide-react'; 6 + import { useTranslation } from 'react-i18next'; 7 + 8 + export const Route = createFileRoute('/(auth)/_auth/auth/signup')({ 9 + component: () => { 10 + const { t } = useTranslation(); 11 + 12 + return ( 13 + <> 14 + <title>{`${t('signup.page.title')} — ${t('appTitle')}`}</title> 15 + <Card> 16 + <CardHeader> 17 + <CardTitle>{t('signup.form.title')}</CardTitle> 18 + <CardDescription>{t('signup.form.subtitle')}</CardDescription> 19 + <CardAction> 20 + <Button variant="link" asChild> 21 + <Link to="/auth/login">{t('login.page.title')} <ExternalLink /></Link> 22 + </Button> 23 + </CardAction> 24 + </CardHeader> 25 + 26 + <CardContent> 27 + <SignupForm /> 28 + </CardContent> 29 + </Card> 30 + </> 31 + ); 32 + }, 33 + });
+32
src/Den.Client.Web/src/routes/(auth)/_auth.tsx
··· 1 + import { createFileRoute, Outlet, redirect } from '@tanstack/react-router' 2 + import { useTranslation } from 'react-i18next'; 3 + import { z } from 'zod'; 4 + 5 + const authSearchSchema = z.object({ 6 + redirect: z.string().optional(), 7 + }); 8 + 9 + export const Route = createFileRoute('/(auth)/_auth')({ 10 + validateSearch: authSearchSchema, 11 + beforeLoad: ({ context, search }) => { 12 + if (context.auth.isAuthenticated) { 13 + throw redirect({ to: search.redirect || '/' }); 14 + } 15 + }, 16 + component: RouteComponent, 17 + }) 18 + 19 + function RouteComponent() { 20 + const { t } = useTranslation(); 21 + 22 + return ( 23 + <div className="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10"> 24 + <div className="flex w-full max-w-sm flex-col gap-6"> 25 + <a href="/" className="flex items-center gap-2 self-center font-medium"> 26 + {t('appTitle')} 27 + </a> 28 + <Outlet /> 29 + </div> 30 + </div> 31 + ); 32 + }
+18
src/Den.Client.Web/src/routes/__root.tsx
··· 1 + import { Outlet, createRootRouteWithContext } from '@tanstack/react-router' 2 + import type { AuthContext } from '@/lib/state/auth' 3 + 4 + interface RouterContext { 5 + auth: AuthContext 6 + } 7 + 8 + export const Route = createRootRouteWithContext<RouterContext>()({ 9 + component: RootComponent, 10 + }) 11 + 12 + function RootComponent() { 13 + return ( 14 + <> 15 + <Outlet /> 16 + </> 17 + ) 18 + }
+36
src/Den.Client.Web/tsconfig.app.json
··· 1 + { 2 + "compilerOptions": { 3 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 + "target": "ES2022", 5 + "useDefineForClassFields": true, 6 + "lib": ["ES2022", "DOM", "DOM.Iterable"], 7 + "module": "ESNext", 8 + "types": ["vite/client"], 9 + "skipLibCheck": true, 10 + 11 + /* Bundler mode */ 12 + "moduleResolution": "bundler", 13 + "allowImportingTsExtensions": true, 14 + "verbatimModuleSyntax": true, 15 + "moduleDetection": "force", 16 + "noEmit": true, 17 + "jsx": "react-jsx", 18 + 19 + /* Linting */ 20 + "strict": true, 21 + "noUnusedLocals": true, 22 + "noUnusedParameters": true, 23 + "erasableSyntaxOnly": true, 24 + "noFallthroughCasesInSwitch": true, 25 + "noUncheckedSideEffectImports": true, 26 + 27 + /* Aliasing */ 28 + "baseUrl": ".", 29 + "paths": { 30 + "@/*": [ 31 + "./src/*" 32 + ] 33 + } 34 + }, 35 + "include": ["src"] 36 + }
+14
src/Den.Client.Web/tsconfig.json
··· 1 + { 2 + "files": [], 3 + "references": [ 4 + { "path": "./tsconfig.app.json" }, 5 + { "path": "./tsconfig.node.json" } 6 + ], 7 + "compilerOptions": { 8 + /* Aliasing */ 9 + "baseUrl": ".", 10 + "paths": { 11 + "@/*": ["./src/*"] 12 + } 13 + } 14 + }
+34
src/Den.Client.Web/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 + "target": "ES2023", 5 + "lib": ["ES2023"], 6 + "module": "ESNext", 7 + "types": ["node"], 8 + "skipLibCheck": true, 9 + 10 + /* Bundler mode */ 11 + "moduleResolution": "bundler", 12 + "allowImportingTsExtensions": true, 13 + "verbatimModuleSyntax": true, 14 + "moduleDetection": "force", 15 + "noEmit": true, 16 + 17 + /* Linting */ 18 + "strict": true, 19 + "noUnusedLocals": true, 20 + "noUnusedParameters": true, 21 + "erasableSyntaxOnly": true, 22 + "noFallthroughCasesInSwitch": true, 23 + "noUncheckedSideEffectImports": true, 24 + 25 + /* Aliasing */ 26 + "baseUrl": ".", 27 + "paths": { 28 + "@/*": [ 29 + "./src/*" 30 + ] 31 + } 32 + }, 33 + "include": ["vite.config.ts"] 34 + }
+29
src/Den.Client.Web/vite.config.ts
··· 1 + import { defineConfig } from 'vite'; 2 + import react from '@vitejs/plugin-react'; 3 + import path from "path"; 4 + import tailwindcss from "@tailwindcss/vite"; 5 + import { tanstackRouter } from '@tanstack/router-plugin/vite'; 6 + 7 + // https://vite.dev/config/ 8 + export default defineConfig({ 9 + plugins: [ 10 + tanstackRouter({ 11 + target: 'react', 12 + autoCodeSplitting: true, 13 + }), 14 + tailwindcss(), 15 + react({ 16 + babel: { 17 + plugins: [['babel-plugin-react-compiler']], 18 + }, 19 + }), 20 + ], 21 + resolve: { 22 + alias: { 23 + '@': path.resolve(__dirname, './src'), 24 + }, 25 + }, 26 + server: { 27 + allowedHosts: ['host.docker.internal'] 28 + } 29 + });
+14
src/Den.Domain/Den.Domain.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + 3 + <PropertyGroup> 4 + <TargetFramework>net10.0</TargetFramework> 5 + <ImplicitUsings>enable</ImplicitUsings> 6 + <Nullable>enable</Nullable> 7 + </PropertyGroup> 8 + 9 + <ItemGroup> 10 + <PackageReference Include="Microsoft.IdentityModel.Tokens" /> 11 + <PackageReference Include="System.IdentityModel.Tokens.Jwt" /> 12 + </ItemGroup> 13 + 14 + </Project>
+72
src/Den.Domain/Entities/Budget.cs
··· 1 + namespace Den.Domain.Entities; 2 + 3 + public class Budget 4 + { 5 + public required Guid Id { get; set; } 6 + public required string DisplayName { get; set; } 7 + public required string Description { get; set; } = ""; 8 + public required BudgetPeriod Period { get; set; } 9 + 10 + // Recorded where the first digit is the lowest denominator of that currency 11 + // 12 + // Examples: 13 + // - 427.13GBP -> 42713 (42,713 pence) 14 + // - 30.10EUR -> 3010 (3,010 cents) 15 + // - 0.41USD -> 3010 (41 cents) 16 + // - 6000COP -> 6000 (6000 pesos) 17 + public required int Total { get; set; } 18 + public required BudgetCurrency Currency { get; set; } 19 + 20 + public required Guid OwnerId { get; set; } 21 + public required User Owner { get; set; } = null!; 22 + public DateTime CreatedAt { get; set; } 23 + public DateTime UpdatedAt { get; set; } 24 + 25 + public ICollection<BudgetPoint> BudgetPoints { get; } = []; 26 + public ICollection<BudgetSource> BudgetSources { get; } = []; 27 + } 28 + 29 + public enum BudgetCurrency 30 + { 31 + GBP, 32 + EUR, 33 + USD, 34 + COP 35 + } 36 + 37 + public enum BudgetPeriod 38 + { 39 + Weekly, 40 + Monthly, 41 + Quarterly, 42 + Yearly 43 + } 44 + 45 + public class BudgetSource 46 + { 47 + public required Guid Id { get; set; } 48 + public required string DisplayName { get; set; } 49 + public required BudgetProvider Provider { get; set; } 50 + 51 + public ICollection<Budget> Budgets { get; } = []; 52 + public ICollection<BudgetPoint> BudgetPoints { get; } = []; 53 + } 54 + 55 + public enum BudgetProvider 56 + { 57 + Bank_Starling 58 + } 59 + 60 + public class BudgetPoint 61 + { 62 + public required Guid Id { get; set; } 63 + public required string Reference { get; set; } 64 + public required int Value { get; set; } 65 + public DateTime Timestamp { get; set; } 66 + 67 + public required Guid BudgetId { get; set; } 68 + public required Budget Budget { get; set; } = null!; 69 + 70 + public required Guid BudgetSourceId { get; set; } 71 + public required BudgetSource BudgetSource { get; set; } = null!; 72 + }
+13
src/Den.Domain/Entities/Session.cs
··· 1 + namespace Den.Domain.Entities; 2 + 3 + public class Session 4 + { 5 + public required Guid Id { get; set; } 6 + public required string RefreshTokenHash { get; set; } 7 + public DateTime Expiry { get; set; } 8 + 9 + public string? DeviceInfo { get; set; } 10 + 11 + public Guid UserId { get; set; } 12 + public User User { get; set; } = null!; 13 + }
+34
src/Den.Domain/Entities/User.cs
··· 1 + namespace Den.Domain.Entities; 2 + 3 + public class User 4 + { 5 + public required Guid Id { get; set; } 6 + public required string Username { get; set; } 7 + public required string DisplayName { get; set; } 8 + public required string Email { get; set; } 9 + public required string PasswordHash { get; set; } 10 + public required UserRole Role { get; set; } 11 + 12 + public ICollection<Session> Sessions { get; } = []; 13 + public ICollection<Budget> Budgets { get; } = []; 14 + } 15 + 16 + public enum UserRole 17 + { 18 + /// <summary> 19 + /// A user with permission to view the contents of the system, but not edit. 20 + /// </summary> 21 + VIEWER, 22 + 23 + /// <summary> 24 + /// A user with 'standard' permissions within the service, allowing them 25 + /// read/write permissions on all resources. 26 + /// </summary> 27 + EDITOR, 28 + 29 + /// <summary> 30 + /// A user with complete administrative access, granting extra abilities to 31 + /// manage other users with and see system diagnostic information. 32 + /// </summary> 33 + ADMIN, 34 + }
+153
src/Den.Infrastructure/Auth/AuthService.cs
··· 1 + using System.IdentityModel.Tokens.Jwt; 2 + using System.Security.Claims; 3 + using System.Security.Cryptography; 4 + using System.Text; 5 + using Den.Application.Auth; 6 + using Den.Domain.Entities; 7 + using Den.Infrastructure.Persistence; 8 + 9 + using Microsoft.EntityFrameworkCore; 10 + using Microsoft.Extensions.Options; 11 + using Microsoft.IdentityModel.Tokens; 12 + 13 + namespace Den.Infrastructure.Auth; 14 + 15 + public class AuthService( 16 + DenDbContext context, 17 + IOptions<JwtSettings> jwtOptions 18 + ) : IAuthService 19 + { 20 + private readonly JwtSettings _jwtSettings = jwtOptions.Value; 21 + public async Task<AuthResponse> SignupAsync(SignupRequest request) 22 + { 23 + if (await context.Users.AnyAsync(u => u.Email == request.Email)) 24 + { 25 + throw new InvalidOperationException("email already in use"); 26 + } 27 + 28 + if (await context.Users.AnyAsync(u => u.Username == request.Username)) 29 + { 30 + throw new InvalidOperationException("username already taken"); 31 + } 32 + 33 + var passwordHash = HashPassword(request.Password); 34 + 35 + var user = new User 36 + { 37 + Id = Guid.NewGuid(), 38 + Username = request.Username, 39 + DisplayName = request.DisplayName, 40 + Email = request.Email, 41 + PasswordHash = passwordHash, 42 + Role = UserRole.VIEWER 43 + }; 44 + 45 + context.Users.Add(user); 46 + await context.SaveChangesAsync(); 47 + 48 + var (accessToken, refreshToken, session) = await GenerateTokenPair(user); 49 + return new AuthResponse(accessToken, refreshToken, session.Expiry); 50 + } 51 + 52 + public async Task<AuthResponse?> LoginAsync(LoginRequest request) 53 + { 54 + var user = await context.Users.FirstOrDefaultAsync(u => u.Username == request.Username); 55 + if (user is null || !VerifyPassword(request.Password, user.PasswordHash)) 56 + { 57 + return null; 58 + } 59 + 60 + var (accessToken, refreshToken, session) = await GenerateTokenPair(user); 61 + return new AuthResponse(accessToken, refreshToken, session.Expiry); 62 + } 63 + 64 + public async Task<RefreshResponse?> RefreshAsync(RefreshRequest request) 65 + { 66 + var session = await context.Sessions 67 + .Include(s => s.User) 68 + .FirstOrDefaultAsync(s => s.RefreshTokenHash == HashRefreshToken(request.RefreshToken)); 69 + if (session is null) 70 + { 71 + return null; 72 + } 73 + 74 + if (session.Expiry <= DateTime.UtcNow) 75 + { 76 + return null; 77 + } 78 + 79 + var accessToken = await GenerateAccessToken(session.User); 80 + return new RefreshResponse(accessToken); 81 + } 82 + 83 + private static string HashPassword(string password) 84 + { 85 + return BCrypt.Net.BCrypt.HashPassword(password); 86 + } 87 + 88 + private static bool VerifyPassword(string password, string hash) 89 + { 90 + return BCrypt.Net.BCrypt.Verify(password, hash); 91 + } 92 + 93 + private async Task<(string AccessToken, string RefreshToken, Session Session)> GenerateTokenPair(User user) 94 + { 95 + var tokenExpiry = DateTime.UtcNow.AddDays(7); 96 + var refreshToken = GenerateRefreshToken(); 97 + 98 + var session = new Session { 99 + Id = Guid.NewGuid(), 100 + RefreshTokenHash = HashRefreshToken(refreshToken), 101 + Expiry = tokenExpiry, 102 + UserId = user.Id, 103 + }; 104 + context.Sessions.Add(session); 105 + await context.SaveChangesAsync(); 106 + 107 + return (await GenerateAccessToken(user), refreshToken, session); 108 + } 109 + 110 + private async Task<string> GenerateAccessToken(User user) 111 + { 112 + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Secret)); 113 + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); 114 + 115 + var now = DateTime.UtcNow; 116 + var accessToken = new JwtSecurityToken( 117 + issuer: _jwtSettings.Issuer, 118 + audience: _jwtSettings.Audience, 119 + claims: [ 120 + new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), 121 + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), 122 + new Claim(ClaimTypes.Role, user.Role.ToString()), 123 + ], 124 + expires: now.AddMinutes(5), 125 + signingCredentials: creds, 126 + notBefore: now 127 + ); 128 + 129 + return new JwtSecurityTokenHandler().WriteToken(accessToken); 130 + } 131 + 132 + private static string GenerateRefreshToken() 133 + { 134 + var randomBytes = new byte[32]; 135 + using var rng = RandomNumberGenerator.Create(); 136 + rng.GetBytes(randomBytes); 137 + return Convert.ToBase64String(randomBytes); 138 + } 139 + 140 + public static string HashRefreshToken(string token) 141 + { 142 + var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(token)); 143 + return Convert.ToBase64String(hashBytes); 144 + } 145 + 146 + public static bool VerifyRefreshToken(string a, string b) 147 + { 148 + return CryptographicOperations.FixedTimeEquals( 149 + Encoding.UTF8.GetBytes(a), 150 + Encoding.UTF8.GetBytes(b) 151 + ); 152 + } 153 + }
+127
src/Den.Infrastructure/Budgets/BudgetService.cs
··· 1 + using Den.Application.Budgets; 2 + using Den.Domain.Entities; 3 + using Den.Infrastructure.Persistence; 4 + using Microsoft.EntityFrameworkCore; 5 + 6 + namespace Den.Infrastructure.Budgets; 7 + 8 + public class BudgetService(DenDbContext context) : IBudgetService 9 + { 10 + public async Task<BudgetResponse?> GetByIdAsync(Guid id) 11 + { 12 + var budget = await context.Budgets 13 + .FirstOrDefaultAsync(b => b.Id == id); 14 + 15 + return budget is null ? null : MapToResponse(budget); 16 + } 17 + 18 + public async Task<IEnumerable<BudgetResponse>> GetAllAsync() 19 + { 20 + var budgets = await context.Budgets 21 + .OrderByDescending(b => b.CreatedAt) 22 + .ToListAsync(); 23 + 24 + return budgets.Select(MapToResponse); 25 + } 26 + 27 + public async Task<BudgetResponse> CreateAsync(CreateBudgetRequest request, Guid userId) 28 + { 29 + var owner = await context.Users.FindAsync(userId); 30 + if (owner is null) 31 + { 32 + throw new InvalidOperationException("user not found"); 33 + } 34 + 35 + var budget = new Budget 36 + { 37 + Id = Guid.NewGuid(), 38 + DisplayName = request.DisplayName, 39 + Description = request.Description, 40 + Period = Enum.Parse<BudgetPeriod>(request.Period, ignoreCase: true), 41 + Total = request.Total, 42 + Currency = Enum.Parse<BudgetCurrency>(request.Currency, ignoreCase: true), 43 + OwnerId = userId, 44 + Owner = owner, 45 + CreatedAt = DateTime.UtcNow, 46 + UpdatedAt = DateTime.UtcNow 47 + }; 48 + 49 + context.Budgets.Add(budget); 50 + await context.SaveChangesAsync(); 51 + 52 + return MapToResponse(budget); 53 + } 54 + 55 + public async Task<BudgetResponse?> UpdateAsync(Guid id, UpdateBudgetRequest request, Guid userId) 56 + { 57 + var budget = await context.Budgets 58 + .FirstOrDefaultAsync(b => b.Id == id && b.OwnerId == userId); 59 + 60 + if (budget is null) 61 + { 62 + return null; 63 + } 64 + 65 + if (request.DisplayName is not null) 66 + { 67 + budget.DisplayName = request.DisplayName; 68 + } 69 + 70 + if (request.Description is not null) 71 + { 72 + budget.Description = request.Description; 73 + } 74 + 75 + if (request.Period is not null) 76 + { 77 + budget.Period = Enum.Parse<BudgetPeriod>(request.Period, ignoreCase: true); 78 + } 79 + 80 + if (request.Total is not null) 81 + { 82 + budget.Total = request.Total.Value; 83 + } 84 + 85 + if (request.Currency is not null) 86 + { 87 + budget.Currency = Enum.Parse<BudgetCurrency>(request.Currency, ignoreCase: true); 88 + } 89 + 90 + budget.UpdatedAt = DateTime.UtcNow; 91 + 92 + await context.SaveChangesAsync(); 93 + 94 + return MapToResponse(budget); 95 + } 96 + 97 + public async Task<bool> DeleteAsync(Guid id, Guid userId) 98 + { 99 + var budget = await context.Budgets 100 + .FirstOrDefaultAsync(b => b.Id == id && b.OwnerId == userId); 101 + 102 + if (budget is null) 103 + { 104 + return false; 105 + } 106 + 107 + context.Budgets.Remove(budget); 108 + await context.SaveChangesAsync(); 109 + 110 + return true; 111 + } 112 + 113 + private static BudgetResponse MapToResponse(Budget budget) 114 + { 115 + return new BudgetResponse( 116 + Id: budget.Id.ToString(), 117 + DisplayName: budget.DisplayName, 118 + Description: budget.Description, 119 + Period: budget.Period.ToString(), 120 + Total: budget.Total, 121 + Currency: budget.Currency.ToString(), 122 + OwnerId: budget.OwnerId.ToString(), 123 + CreatedAt: budget.CreatedAt, 124 + UpdatedAt: budget.UpdatedAt 125 + ); 126 + } 127 + }
+23
src/Den.Infrastructure/Den.Infrastructure.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + 3 + <PropertyGroup> 4 + <TargetFramework>net10.0</TargetFramework> 5 + <ImplicitUsings>enable</ImplicitUsings> 6 + <Nullable>enable</Nullable> 7 + </PropertyGroup> 8 + 9 + <ItemGroup> 10 + <PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" /> 11 + <PackageReference Include="BCrypt.Net-Next" /> 12 + <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" /> 13 + <PackageReference Include="Microsoft.EntityFrameworkCore.Design"> 14 + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> 15 + <PrivateAssets>all</PrivateAssets> 16 + </PackageReference> 17 + </ItemGroup> 18 + 19 + <ItemGroup> 20 + <ProjectReference Include="..\Den.Application\Den.Application.csproj" /> 21 + </ItemGroup> 22 + 23 + </Project>
+102
src/Den.Infrastructure/Migrations/20251112211324_Sessions.Designer.cs
··· 1 + // <auto-generated /> 2 + using System; 3 + using Den.Infrastructure.Persistence; 4 + using Microsoft.EntityFrameworkCore; 5 + using Microsoft.EntityFrameworkCore.Infrastructure; 6 + using Microsoft.EntityFrameworkCore.Migrations; 7 + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 9 + 10 + #nullable disable 11 + 12 + namespace Den.Infrastructure.Migrations 13 + { 14 + [DbContext(typeof(DenDbContext))] 15 + [Migration("20251112211324_Sessions")] 16 + partial class Sessions 17 + { 18 + /// <inheritdoc /> 19 + protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 + { 21 + #pragma warning disable 612, 618 22 + modelBuilder 23 + .HasAnnotation("ProductVersion", "9.0.9") 24 + .HasAnnotation("Relational:MaxIdentifierLength", 63); 25 + 26 + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); 27 + 28 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 29 + { 30 + b.Property<Guid>("Id") 31 + .ValueGeneratedOnAdd() 32 + .HasColumnType("uuid"); 33 + 34 + b.Property<string>("DeviceInfo") 35 + .HasColumnType("text"); 36 + 37 + b.Property<DateTime>("Expiry") 38 + .HasColumnType("timestamp with time zone"); 39 + 40 + b.Property<string>("RefreshTokenHash") 41 + .IsRequired() 42 + .HasColumnType("text"); 43 + 44 + b.Property<Guid>("UserId") 45 + .HasColumnType("uuid"); 46 + 47 + b.HasKey("Id"); 48 + 49 + b.HasIndex("UserId"); 50 + 51 + b.ToTable("Sessions"); 52 + }); 53 + 54 + modelBuilder.Entity("Den.Domain.Entities.User", b => 55 + { 56 + b.Property<Guid>("Id") 57 + .ValueGeneratedOnAdd() 58 + .HasColumnType("uuid"); 59 + 60 + b.Property<string>("DisplayName") 61 + .IsRequired() 62 + .HasColumnType("text"); 63 + 64 + b.Property<string>("Email") 65 + .IsRequired() 66 + .HasColumnType("text"); 67 + 68 + b.Property<string>("PasswordHash") 69 + .IsRequired() 70 + .HasColumnType("text"); 71 + 72 + b.Property<int>("Role") 73 + .HasColumnType("integer"); 74 + 75 + b.Property<string>("Username") 76 + .IsRequired() 77 + .HasColumnType("text"); 78 + 79 + b.HasKey("Id"); 80 + 81 + b.ToTable("Users"); 82 + }); 83 + 84 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 85 + { 86 + b.HasOne("Den.Domain.Entities.User", "User") 87 + .WithMany("Sessions") 88 + .HasForeignKey("UserId") 89 + .OnDelete(DeleteBehavior.Cascade) 90 + .IsRequired(); 91 + 92 + b.Navigation("User"); 93 + }); 94 + 95 + modelBuilder.Entity("Den.Domain.Entities.User", b => 96 + { 97 + b.Navigation("Sessions"); 98 + }); 99 + #pragma warning restore 612, 618 100 + } 101 + } 102 + }
+67
src/Den.Infrastructure/Migrations/20251112211324_Sessions.cs
··· 1 + using System; 2 + using Microsoft.EntityFrameworkCore.Migrations; 3 + 4 + #nullable disable 5 + 6 + namespace Den.Infrastructure.Migrations 7 + { 8 + /// <inheritdoc /> 9 + public partial class Sessions : Migration 10 + { 11 + /// <inheritdoc /> 12 + protected override void Up(MigrationBuilder migrationBuilder) 13 + { 14 + migrationBuilder.CreateTable( 15 + name: "Users", 16 + columns: table => new 17 + { 18 + Id = table.Column<Guid>(type: "uuid", nullable: false), 19 + Username = table.Column<string>(type: "text", nullable: false), 20 + DisplayName = table.Column<string>(type: "text", nullable: false), 21 + Email = table.Column<string>(type: "text", nullable: false), 22 + PasswordHash = table.Column<string>(type: "text", nullable: false), 23 + Role = table.Column<int>(type: "integer", nullable: false) 24 + }, 25 + constraints: table => 26 + { 27 + table.PrimaryKey("PK_Users", x => x.Id); 28 + }); 29 + 30 + migrationBuilder.CreateTable( 31 + name: "Sessions", 32 + columns: table => new 33 + { 34 + Id = table.Column<Guid>(type: "uuid", nullable: false), 35 + RefreshTokenHash = table.Column<string>(type: "text", nullable: false), 36 + Expiry = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), 37 + DeviceInfo = table.Column<string>(type: "text", nullable: true), 38 + UserId = table.Column<Guid>(type: "uuid", nullable: false) 39 + }, 40 + constraints: table => 41 + { 42 + table.PrimaryKey("PK_Sessions", x => x.Id); 43 + table.ForeignKey( 44 + name: "FK_Sessions_Users_UserId", 45 + column: x => x.UserId, 46 + principalTable: "Users", 47 + principalColumn: "Id", 48 + onDelete: ReferentialAction.Cascade); 49 + }); 50 + 51 + migrationBuilder.CreateIndex( 52 + name: "IX_Sessions_UserId", 53 + table: "Sessions", 54 + column: "UserId"); 55 + } 56 + 57 + /// <inheritdoc /> 58 + protected override void Down(MigrationBuilder migrationBuilder) 59 + { 60 + migrationBuilder.DropTable( 61 + name: "Sessions"); 62 + 63 + migrationBuilder.DropTable( 64 + name: "Users"); 65 + } 66 + } 67 + }
+154
src/Den.Infrastructure/Migrations/20251113003534_SecurityKeys.Designer.cs
··· 1 + // <auto-generated /> 2 + using System; 3 + using Den.Infrastructure.Persistence; 4 + using Microsoft.EntityFrameworkCore; 5 + using Microsoft.EntityFrameworkCore.Infrastructure; 6 + using Microsoft.EntityFrameworkCore.Migrations; 7 + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 9 + 10 + #nullable disable 11 + 12 + namespace Den.Infrastructure.Migrations 13 + { 14 + [DbContext(typeof(DenDbContext))] 15 + [Migration("20251113003534_SecurityKeys")] 16 + partial class SecurityKeys 17 + { 18 + /// <inheritdoc /> 19 + protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 + { 21 + #pragma warning disable 612, 618 22 + modelBuilder 23 + .HasAnnotation("ProductVersion", "9.0.9") 24 + .HasAnnotation("Relational:MaxIdentifierLength", 63); 25 + 26 + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); 27 + 28 + modelBuilder.Entity("Den.Domain.Entities.SecurityKey", b => 29 + { 30 + b.Property<Guid>("Id") 31 + .ValueGeneratedOnAdd() 32 + .HasColumnType("uuid"); 33 + 34 + b.Property<DateTime>("CreatedAt") 35 + .HasColumnType("timestamp with time zone"); 36 + 37 + b.Property<string>("EncryptedPrivateKey") 38 + .IsRequired() 39 + .HasColumnType("text"); 40 + 41 + b.Property<DateTime?>("ExpiresAt") 42 + .HasColumnType("timestamp with time zone"); 43 + 44 + b.Property<string>("HashAlgorithm") 45 + .HasMaxLength(16) 46 + .HasColumnType("character varying(16)"); 47 + 48 + b.Property<bool>("IsActive") 49 + .HasColumnType("boolean"); 50 + 51 + b.Property<bool>("IsRevoked") 52 + .HasColumnType("boolean"); 53 + 54 + b.Property<string>("KeyId") 55 + .IsRequired() 56 + .HasMaxLength(64) 57 + .HasColumnType("character varying(64)"); 58 + 59 + b.Property<string>("KeyType") 60 + .IsRequired() 61 + .HasMaxLength(16) 62 + .HasColumnType("character varying(16)"); 63 + 64 + b.Property<string>("PublicJwkJson") 65 + .HasColumnType("text"); 66 + 67 + b.Property<DateTime?>("RevokedAt") 68 + .HasColumnType("timestamp with time zone"); 69 + 70 + b.HasKey("Id"); 71 + 72 + b.HasIndex("KeyId") 73 + .IsUnique(); 74 + 75 + b.HasIndex("IsActive", "IsRevoked", "ExpiresAt"); 76 + 77 + b.ToTable("SecurityKeys"); 78 + }); 79 + 80 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 81 + { 82 + b.Property<Guid>("Id") 83 + .ValueGeneratedOnAdd() 84 + .HasColumnType("uuid"); 85 + 86 + b.Property<string>("DeviceInfo") 87 + .HasColumnType("text"); 88 + 89 + b.Property<DateTime>("Expiry") 90 + .HasColumnType("timestamp with time zone"); 91 + 92 + b.Property<string>("RefreshTokenHash") 93 + .IsRequired() 94 + .HasColumnType("text"); 95 + 96 + b.Property<Guid>("UserId") 97 + .HasColumnType("uuid"); 98 + 99 + b.HasKey("Id"); 100 + 101 + b.HasIndex("UserId"); 102 + 103 + b.ToTable("Sessions"); 104 + }); 105 + 106 + modelBuilder.Entity("Den.Domain.Entities.User", b => 107 + { 108 + b.Property<Guid>("Id") 109 + .ValueGeneratedOnAdd() 110 + .HasColumnType("uuid"); 111 + 112 + b.Property<string>("DisplayName") 113 + .IsRequired() 114 + .HasColumnType("text"); 115 + 116 + b.Property<string>("Email") 117 + .IsRequired() 118 + .HasColumnType("text"); 119 + 120 + b.Property<string>("PasswordHash") 121 + .IsRequired() 122 + .HasColumnType("text"); 123 + 124 + b.Property<int>("Role") 125 + .HasColumnType("integer"); 126 + 127 + b.Property<string>("Username") 128 + .IsRequired() 129 + .HasColumnType("text"); 130 + 131 + b.HasKey("Id"); 132 + 133 + b.ToTable("Users"); 134 + }); 135 + 136 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 137 + { 138 + b.HasOne("Den.Domain.Entities.User", "User") 139 + .WithMany("Sessions") 140 + .HasForeignKey("UserId") 141 + .OnDelete(DeleteBehavior.Cascade) 142 + .IsRequired(); 143 + 144 + b.Navigation("User"); 145 + }); 146 + 147 + modelBuilder.Entity("Den.Domain.Entities.User", b => 148 + { 149 + b.Navigation("Sessions"); 150 + }); 151 + #pragma warning restore 612, 618 152 + } 153 + } 154 + }
+54
src/Den.Infrastructure/Migrations/20251113003534_SecurityKeys.cs
··· 1 + using System; 2 + using Microsoft.EntityFrameworkCore.Migrations; 3 + 4 + #nullable disable 5 + 6 + namespace Den.Infrastructure.Migrations 7 + { 8 + /// <inheritdoc /> 9 + public partial class SecurityKeys : Migration 10 + { 11 + /// <inheritdoc /> 12 + protected override void Up(MigrationBuilder migrationBuilder) 13 + { 14 + migrationBuilder.CreateTable( 15 + name: "SecurityKeys", 16 + columns: table => new 17 + { 18 + Id = table.Column<Guid>(type: "uuid", nullable: false), 19 + KeyId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false), 20 + CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), 21 + ExpiresAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), 22 + IsActive = table.Column<bool>(type: "boolean", nullable: false), 23 + IsRevoked = table.Column<bool>(type: "boolean", nullable: false), 24 + RevokedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), 25 + EncryptedPrivateKey = table.Column<string>(type: "text", nullable: false), 26 + KeyType = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: false), 27 + HashAlgorithm = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: true), 28 + PublicJwkJson = table.Column<string>(type: "text", nullable: true) 29 + }, 30 + constraints: table => 31 + { 32 + table.PrimaryKey("PK_SecurityKeys", x => x.Id); 33 + }); 34 + 35 + migrationBuilder.CreateIndex( 36 + name: "IX_SecurityKeys_IsActive_IsRevoked_ExpiresAt", 37 + table: "SecurityKeys", 38 + columns: new[] { "IsActive", "IsRevoked", "ExpiresAt" }); 39 + 40 + migrationBuilder.CreateIndex( 41 + name: "IX_SecurityKeys_KeyId", 42 + table: "SecurityKeys", 43 + column: "KeyId", 44 + unique: true); 45 + } 46 + 47 + /// <inheritdoc /> 48 + protected override void Down(MigrationBuilder migrationBuilder) 49 + { 50 + migrationBuilder.DropTable( 51 + name: "SecurityKeys"); 52 + } 53 + } 54 + }
+159
src/Den.Infrastructure/Migrations/20251113015059_SecurityKeysUpdates.Designer.cs
··· 1 + // <auto-generated /> 2 + using System; 3 + using Den.Infrastructure.Persistence; 4 + using Microsoft.EntityFrameworkCore; 5 + using Microsoft.EntityFrameworkCore.Infrastructure; 6 + using Microsoft.EntityFrameworkCore.Migrations; 7 + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 9 + 10 + #nullable disable 11 + 12 + namespace Den.Infrastructure.Migrations 13 + { 14 + [DbContext(typeof(DenDbContext))] 15 + [Migration("20251113015059_SecurityKeysUpdates")] 16 + partial class SecurityKeysUpdates 17 + { 18 + /// <inheritdoc /> 19 + protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 + { 21 + #pragma warning disable 612, 618 22 + modelBuilder 23 + .HasAnnotation("ProductVersion", "9.0.9") 24 + .HasAnnotation("Relational:MaxIdentifierLength", 63); 25 + 26 + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); 27 + 28 + modelBuilder.Entity("Den.Domain.Entities.SecurityKey", b => 29 + { 30 + b.Property<Guid>("Id") 31 + .ValueGeneratedOnAdd() 32 + .HasColumnType("uuid"); 33 + 34 + b.Property<DateTime>("CreatedAt") 35 + .HasColumnType("timestamp with time zone"); 36 + 37 + b.Property<string>("EncryptedPrivateKey") 38 + .IsRequired() 39 + .HasColumnType("text"); 40 + 41 + b.Property<DateTime?>("ExpiresAt") 42 + .HasColumnType("timestamp with time zone"); 43 + 44 + b.Property<string>("HashAlgorithm") 45 + .HasMaxLength(16) 46 + .HasColumnType("character varying(16)"); 47 + 48 + b.Property<bool>("IsActive") 49 + .HasColumnType("boolean"); 50 + 51 + b.Property<bool>("IsRevoked") 52 + .HasColumnType("boolean"); 53 + 54 + b.Property<string>("KeyId") 55 + .IsRequired() 56 + .HasMaxLength(64) 57 + .HasColumnType("character varying(64)"); 58 + 59 + b.Property<string>("KeyType") 60 + .IsRequired() 61 + .HasMaxLength(16) 62 + .HasColumnType("character varying(16)"); 63 + 64 + b.Property<string>("PublicJwkJson") 65 + .HasColumnType("text"); 66 + 67 + b.Property<DateTime?>("RevokedAt") 68 + .HasColumnType("timestamp with time zone"); 69 + 70 + b.Property<string>("Usage") 71 + .IsRequired() 72 + .HasMaxLength(16) 73 + .HasColumnType("character varying(16)"); 74 + 75 + b.HasKey("Id"); 76 + 77 + b.HasIndex("KeyId") 78 + .IsUnique(); 79 + 80 + b.HasIndex("IsActive", "IsRevoked", "ExpiresAt", "Usage"); 81 + 82 + b.ToTable("SecurityKeys"); 83 + }); 84 + 85 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 86 + { 87 + b.Property<Guid>("Id") 88 + .ValueGeneratedOnAdd() 89 + .HasColumnType("uuid"); 90 + 91 + b.Property<string>("DeviceInfo") 92 + .HasColumnType("text"); 93 + 94 + b.Property<DateTime>("Expiry") 95 + .HasColumnType("timestamp with time zone"); 96 + 97 + b.Property<string>("RefreshTokenHash") 98 + .IsRequired() 99 + .HasColumnType("text"); 100 + 101 + b.Property<Guid>("UserId") 102 + .HasColumnType("uuid"); 103 + 104 + b.HasKey("Id"); 105 + 106 + b.HasIndex("UserId"); 107 + 108 + b.ToTable("Sessions"); 109 + }); 110 + 111 + modelBuilder.Entity("Den.Domain.Entities.User", b => 112 + { 113 + b.Property<Guid>("Id") 114 + .ValueGeneratedOnAdd() 115 + .HasColumnType("uuid"); 116 + 117 + b.Property<string>("DisplayName") 118 + .IsRequired() 119 + .HasColumnType("text"); 120 + 121 + b.Property<string>("Email") 122 + .IsRequired() 123 + .HasColumnType("text"); 124 + 125 + b.Property<string>("PasswordHash") 126 + .IsRequired() 127 + .HasColumnType("text"); 128 + 129 + b.Property<int>("Role") 130 + .HasColumnType("integer"); 131 + 132 + b.Property<string>("Username") 133 + .IsRequired() 134 + .HasColumnType("text"); 135 + 136 + b.HasKey("Id"); 137 + 138 + b.ToTable("Users"); 139 + }); 140 + 141 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 142 + { 143 + b.HasOne("Den.Domain.Entities.User", "User") 144 + .WithMany("Sessions") 145 + .HasForeignKey("UserId") 146 + .OnDelete(DeleteBehavior.Cascade) 147 + .IsRequired(); 148 + 149 + b.Navigation("User"); 150 + }); 151 + 152 + modelBuilder.Entity("Den.Domain.Entities.User", b => 153 + { 154 + b.Navigation("Sessions"); 155 + }); 156 + #pragma warning restore 612, 618 157 + } 158 + } 159 + }
+48
src/Den.Infrastructure/Migrations/20251113015059_SecurityKeysUpdates.cs
··· 1 + using Microsoft.EntityFrameworkCore.Migrations; 2 + 3 + #nullable disable 4 + 5 + namespace Den.Infrastructure.Migrations 6 + { 7 + /// <inheritdoc /> 8 + public partial class SecurityKeysUpdates : Migration 9 + { 10 + /// <inheritdoc /> 11 + protected override void Up(MigrationBuilder migrationBuilder) 12 + { 13 + migrationBuilder.DropIndex( 14 + name: "IX_SecurityKeys_IsActive_IsRevoked_ExpiresAt", 15 + table: "SecurityKeys"); 16 + 17 + migrationBuilder.AddColumn<string>( 18 + name: "Usage", 19 + table: "SecurityKeys", 20 + type: "character varying(16)", 21 + maxLength: 16, 22 + nullable: false, 23 + defaultValue: ""); 24 + 25 + migrationBuilder.CreateIndex( 26 + name: "IX_SecurityKeys_IsActive_IsRevoked_ExpiresAt_Usage", 27 + table: "SecurityKeys", 28 + columns: new[] { "IsActive", "IsRevoked", "ExpiresAt", "Usage" }); 29 + } 30 + 31 + /// <inheritdoc /> 32 + protected override void Down(MigrationBuilder migrationBuilder) 33 + { 34 + migrationBuilder.DropIndex( 35 + name: "IX_SecurityKeys_IsActive_IsRevoked_ExpiresAt_Usage", 36 + table: "SecurityKeys"); 37 + 38 + migrationBuilder.DropColumn( 39 + name: "Usage", 40 + table: "SecurityKeys"); 41 + 42 + migrationBuilder.CreateIndex( 43 + name: "IX_SecurityKeys_IsActive_IsRevoked_ExpiresAt", 44 + table: "SecurityKeys", 45 + columns: new[] { "IsActive", "IsRevoked", "ExpiresAt" }); 46 + } 47 + } 48 + }
+102
src/Den.Infrastructure/Migrations/20251117223956_RemoveSecurityKeys.Designer.cs
··· 1 + // <auto-generated /> 2 + using System; 3 + using Den.Infrastructure.Persistence; 4 + using Microsoft.EntityFrameworkCore; 5 + using Microsoft.EntityFrameworkCore.Infrastructure; 6 + using Microsoft.EntityFrameworkCore.Migrations; 7 + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 9 + 10 + #nullable disable 11 + 12 + namespace Den.Infrastructure.Migrations 13 + { 14 + [DbContext(typeof(DenDbContext))] 15 + [Migration("20251117223956_RemoveSecurityKeys")] 16 + partial class RemoveSecurityKeys 17 + { 18 + /// <inheritdoc /> 19 + protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 + { 21 + #pragma warning disable 612, 618 22 + modelBuilder 23 + .HasAnnotation("ProductVersion", "9.0.9") 24 + .HasAnnotation("Relational:MaxIdentifierLength", 63); 25 + 26 + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); 27 + 28 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 29 + { 30 + b.Property<Guid>("Id") 31 + .ValueGeneratedOnAdd() 32 + .HasColumnType("uuid"); 33 + 34 + b.Property<string>("DeviceInfo") 35 + .HasColumnType("text"); 36 + 37 + b.Property<DateTime>("Expiry") 38 + .HasColumnType("timestamp with time zone"); 39 + 40 + b.Property<string>("RefreshTokenHash") 41 + .IsRequired() 42 + .HasColumnType("text"); 43 + 44 + b.Property<Guid>("UserId") 45 + .HasColumnType("uuid"); 46 + 47 + b.HasKey("Id"); 48 + 49 + b.HasIndex("UserId"); 50 + 51 + b.ToTable("Sessions"); 52 + }); 53 + 54 + modelBuilder.Entity("Den.Domain.Entities.User", b => 55 + { 56 + b.Property<Guid>("Id") 57 + .ValueGeneratedOnAdd() 58 + .HasColumnType("uuid"); 59 + 60 + b.Property<string>("DisplayName") 61 + .IsRequired() 62 + .HasColumnType("text"); 63 + 64 + b.Property<string>("Email") 65 + .IsRequired() 66 + .HasColumnType("text"); 67 + 68 + b.Property<string>("PasswordHash") 69 + .IsRequired() 70 + .HasColumnType("text"); 71 + 72 + b.Property<int>("Role") 73 + .HasColumnType("integer"); 74 + 75 + b.Property<string>("Username") 76 + .IsRequired() 77 + .HasColumnType("text"); 78 + 79 + b.HasKey("Id"); 80 + 81 + b.ToTable("Users"); 82 + }); 83 + 84 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 85 + { 86 + b.HasOne("Den.Domain.Entities.User", "User") 87 + .WithMany("Sessions") 88 + .HasForeignKey("UserId") 89 + .OnDelete(DeleteBehavior.Cascade) 90 + .IsRequired(); 91 + 92 + b.Navigation("User"); 93 + }); 94 + 95 + modelBuilder.Entity("Den.Domain.Entities.User", b => 96 + { 97 + b.Navigation("Sessions"); 98 + }); 99 + #pragma warning restore 612, 618 100 + } 101 + } 102 + }
+55
src/Den.Infrastructure/Migrations/20251117223956_RemoveSecurityKeys.cs
··· 1 + using System; 2 + using Microsoft.EntityFrameworkCore.Migrations; 3 + 4 + #nullable disable 5 + 6 + namespace Den.Infrastructure.Migrations 7 + { 8 + /// <inheritdoc /> 9 + public partial class RemoveSecurityKeys : Migration 10 + { 11 + /// <inheritdoc /> 12 + protected override void Up(MigrationBuilder migrationBuilder) 13 + { 14 + migrationBuilder.DropTable( 15 + name: "SecurityKeys"); 16 + } 17 + 18 + /// <inheritdoc /> 19 + protected override void Down(MigrationBuilder migrationBuilder) 20 + { 21 + migrationBuilder.CreateTable( 22 + name: "SecurityKeys", 23 + columns: table => new 24 + { 25 + Id = table.Column<Guid>(type: "uuid", nullable: false), 26 + CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), 27 + EncryptedPrivateKey = table.Column<string>(type: "text", nullable: false), 28 + ExpiresAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), 29 + HashAlgorithm = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: true), 30 + IsActive = table.Column<bool>(type: "boolean", nullable: false), 31 + IsRevoked = table.Column<bool>(type: "boolean", nullable: false), 32 + KeyId = table.Column<string>(type: "character varying(64)", maxLength: 64, nullable: false), 33 + KeyType = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: false), 34 + PublicJwkJson = table.Column<string>(type: "text", nullable: true), 35 + RevokedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true), 36 + Usage = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: false) 37 + }, 38 + constraints: table => 39 + { 40 + table.PrimaryKey("PK_SecurityKeys", x => x.Id); 41 + }); 42 + 43 + migrationBuilder.CreateIndex( 44 + name: "IX_SecurityKeys_IsActive_IsRevoked_ExpiresAt_Usage", 45 + table: "SecurityKeys", 46 + columns: new[] { "IsActive", "IsRevoked", "ExpiresAt", "Usage" }); 47 + 48 + migrationBuilder.CreateIndex( 49 + name: "IX_SecurityKeys_KeyId", 50 + table: "SecurityKeys", 51 + column: "KeyId", 52 + unique: true); 53 + } 54 + } 55 + }
+262
src/Den.Infrastructure/Migrations/20251118200110_AddBudgetTimestamps.Designer.cs
··· 1 + // <auto-generated /> 2 + using System; 3 + using Den.Infrastructure.Persistence; 4 + using Microsoft.EntityFrameworkCore; 5 + using Microsoft.EntityFrameworkCore.Infrastructure; 6 + using Microsoft.EntityFrameworkCore.Migrations; 7 + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 8 + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 9 + 10 + #nullable disable 11 + 12 + namespace Den.Infrastructure.Migrations 13 + { 14 + [DbContext(typeof(DenDbContext))] 15 + [Migration("20251118200110_AddBudgetTimestamps")] 16 + partial class AddBudgetTimestamps 17 + { 18 + /// <inheritdoc /> 19 + protected override void BuildTargetModel(ModelBuilder modelBuilder) 20 + { 21 + #pragma warning disable 612, 618 22 + modelBuilder 23 + .HasAnnotation("ProductVersion", "9.0.9") 24 + .HasAnnotation("Relational:MaxIdentifierLength", 63); 25 + 26 + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); 27 + 28 + modelBuilder.Entity("BudgetBudgetSource", b => 29 + { 30 + b.Property<Guid>("BudgetSourcesId") 31 + .HasColumnType("uuid"); 32 + 33 + b.Property<Guid>("BudgetsId") 34 + .HasColumnType("uuid"); 35 + 36 + b.HasKey("BudgetSourcesId", "BudgetsId"); 37 + 38 + b.HasIndex("BudgetsId"); 39 + 40 + b.ToTable("BudgetBudgetSource"); 41 + }); 42 + 43 + modelBuilder.Entity("Den.Domain.Entities.Budget", b => 44 + { 45 + b.Property<Guid>("Id") 46 + .ValueGeneratedOnAdd() 47 + .HasColumnType("uuid"); 48 + 49 + b.Property<DateTime>("CreatedAt") 50 + .HasColumnType("timestamp with time zone"); 51 + 52 + b.Property<int>("Currency") 53 + .HasColumnType("integer"); 54 + 55 + b.Property<string>("Description") 56 + .IsRequired() 57 + .HasColumnType("text"); 58 + 59 + b.Property<string>("DisplayName") 60 + .IsRequired() 61 + .HasColumnType("text"); 62 + 63 + b.Property<Guid>("OwnerId") 64 + .HasColumnType("uuid"); 65 + 66 + b.Property<int>("Period") 67 + .HasColumnType("integer"); 68 + 69 + b.Property<int>("Total") 70 + .HasColumnType("integer"); 71 + 72 + b.Property<DateTime>("UpdatedAt") 73 + .HasColumnType("timestamp with time zone"); 74 + 75 + b.HasKey("Id"); 76 + 77 + b.HasIndex("OwnerId"); 78 + 79 + b.ToTable("Budgets"); 80 + }); 81 + 82 + modelBuilder.Entity("Den.Domain.Entities.BudgetPoint", b => 83 + { 84 + b.Property<Guid>("Id") 85 + .ValueGeneratedOnAdd() 86 + .HasColumnType("uuid"); 87 + 88 + b.Property<Guid>("BudgetId") 89 + .HasColumnType("uuid"); 90 + 91 + b.Property<Guid>("BudgetSourceId") 92 + .HasColumnType("uuid"); 93 + 94 + b.Property<string>("Reference") 95 + .IsRequired() 96 + .HasColumnType("text"); 97 + 98 + b.Property<DateTime>("Timestamp") 99 + .HasColumnType("timestamp with time zone"); 100 + 101 + b.Property<int>("Value") 102 + .HasColumnType("integer"); 103 + 104 + b.HasKey("Id"); 105 + 106 + b.HasIndex("BudgetId"); 107 + 108 + b.HasIndex("BudgetSourceId"); 109 + 110 + b.ToTable("BudgetPoints"); 111 + }); 112 + 113 + modelBuilder.Entity("Den.Domain.Entities.BudgetSource", b => 114 + { 115 + b.Property<Guid>("Id") 116 + .ValueGeneratedOnAdd() 117 + .HasColumnType("uuid"); 118 + 119 + b.Property<string>("DisplayName") 120 + .IsRequired() 121 + .HasColumnType("text"); 122 + 123 + b.Property<int>("Provider") 124 + .HasColumnType("integer"); 125 + 126 + b.HasKey("Id"); 127 + 128 + b.ToTable("BudgetSources"); 129 + }); 130 + 131 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 132 + { 133 + b.Property<Guid>("Id") 134 + .ValueGeneratedOnAdd() 135 + .HasColumnType("uuid"); 136 + 137 + b.Property<string>("DeviceInfo") 138 + .HasColumnType("text"); 139 + 140 + b.Property<DateTime>("Expiry") 141 + .HasColumnType("timestamp with time zone"); 142 + 143 + b.Property<string>("RefreshTokenHash") 144 + .IsRequired() 145 + .HasColumnType("text"); 146 + 147 + b.Property<Guid>("UserId") 148 + .HasColumnType("uuid"); 149 + 150 + b.HasKey("Id"); 151 + 152 + b.HasIndex("UserId"); 153 + 154 + b.ToTable("Sessions"); 155 + }); 156 + 157 + modelBuilder.Entity("Den.Domain.Entities.User", b => 158 + { 159 + b.Property<Guid>("Id") 160 + .ValueGeneratedOnAdd() 161 + .HasColumnType("uuid"); 162 + 163 + b.Property<string>("DisplayName") 164 + .IsRequired() 165 + .HasColumnType("text"); 166 + 167 + b.Property<string>("Email") 168 + .IsRequired() 169 + .HasColumnType("text"); 170 + 171 + b.Property<string>("PasswordHash") 172 + .IsRequired() 173 + .HasColumnType("text"); 174 + 175 + b.Property<int>("Role") 176 + .HasColumnType("integer"); 177 + 178 + b.Property<string>("Username") 179 + .IsRequired() 180 + .HasColumnType("text"); 181 + 182 + b.HasKey("Id"); 183 + 184 + b.ToTable("Users"); 185 + }); 186 + 187 + modelBuilder.Entity("BudgetBudgetSource", b => 188 + { 189 + b.HasOne("Den.Domain.Entities.BudgetSource", null) 190 + .WithMany() 191 + .HasForeignKey("BudgetSourcesId") 192 + .OnDelete(DeleteBehavior.Cascade) 193 + .IsRequired(); 194 + 195 + b.HasOne("Den.Domain.Entities.Budget", null) 196 + .WithMany() 197 + .HasForeignKey("BudgetsId") 198 + .OnDelete(DeleteBehavior.Cascade) 199 + .IsRequired(); 200 + }); 201 + 202 + modelBuilder.Entity("Den.Domain.Entities.Budget", b => 203 + { 204 + b.HasOne("Den.Domain.Entities.User", "Owner") 205 + .WithMany("Budgets") 206 + .HasForeignKey("OwnerId") 207 + .OnDelete(DeleteBehavior.Cascade) 208 + .IsRequired(); 209 + 210 + b.Navigation("Owner"); 211 + }); 212 + 213 + modelBuilder.Entity("Den.Domain.Entities.BudgetPoint", b => 214 + { 215 + b.HasOne("Den.Domain.Entities.Budget", "Budget") 216 + .WithMany("BudgetPoints") 217 + .HasForeignKey("BudgetId") 218 + .OnDelete(DeleteBehavior.Cascade) 219 + .IsRequired(); 220 + 221 + b.HasOne("Den.Domain.Entities.BudgetSource", "BudgetSource") 222 + .WithMany("BudgetPoints") 223 + .HasForeignKey("BudgetSourceId") 224 + .OnDelete(DeleteBehavior.Cascade) 225 + .IsRequired(); 226 + 227 + b.Navigation("Budget"); 228 + 229 + b.Navigation("BudgetSource"); 230 + }); 231 + 232 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 233 + { 234 + b.HasOne("Den.Domain.Entities.User", "User") 235 + .WithMany("Sessions") 236 + .HasForeignKey("UserId") 237 + .OnDelete(DeleteBehavior.Cascade) 238 + .IsRequired(); 239 + 240 + b.Navigation("User"); 241 + }); 242 + 243 + modelBuilder.Entity("Den.Domain.Entities.Budget", b => 244 + { 245 + b.Navigation("BudgetPoints"); 246 + }); 247 + 248 + modelBuilder.Entity("Den.Domain.Entities.BudgetSource", b => 249 + { 250 + b.Navigation("BudgetPoints"); 251 + }); 252 + 253 + modelBuilder.Entity("Den.Domain.Entities.User", b => 254 + { 255 + b.Navigation("Budgets"); 256 + 257 + b.Navigation("Sessions"); 258 + }); 259 + #pragma warning restore 612, 618 260 + } 261 + } 262 + }
+141
src/Den.Infrastructure/Migrations/20251118200110_AddBudgetTimestamps.cs
··· 1 + using System; 2 + using Microsoft.EntityFrameworkCore.Migrations; 3 + 4 + #nullable disable 5 + 6 + namespace Den.Infrastructure.Migrations 7 + { 8 + /// <inheritdoc /> 9 + public partial class AddBudgetTimestamps : Migration 10 + { 11 + /// <inheritdoc /> 12 + protected override void Up(MigrationBuilder migrationBuilder) 13 + { 14 + migrationBuilder.CreateTable( 15 + name: "Budgets", 16 + columns: table => new 17 + { 18 + Id = table.Column<Guid>(type: "uuid", nullable: false), 19 + DisplayName = table.Column<string>(type: "text", nullable: false), 20 + Description = table.Column<string>(type: "text", nullable: false), 21 + Period = table.Column<int>(type: "integer", nullable: false), 22 + Total = table.Column<int>(type: "integer", nullable: false), 23 + Currency = table.Column<int>(type: "integer", nullable: false), 24 + OwnerId = table.Column<Guid>(type: "uuid", nullable: false), 25 + CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), 26 + UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false) 27 + }, 28 + constraints: table => 29 + { 30 + table.PrimaryKey("PK_Budgets", x => x.Id); 31 + table.ForeignKey( 32 + name: "FK_Budgets_Users_OwnerId", 33 + column: x => x.OwnerId, 34 + principalTable: "Users", 35 + principalColumn: "Id", 36 + onDelete: ReferentialAction.Cascade); 37 + }); 38 + 39 + migrationBuilder.CreateTable( 40 + name: "BudgetSources", 41 + columns: table => new 42 + { 43 + Id = table.Column<Guid>(type: "uuid", nullable: false), 44 + DisplayName = table.Column<string>(type: "text", nullable: false), 45 + Provider = table.Column<int>(type: "integer", nullable: false) 46 + }, 47 + constraints: table => 48 + { 49 + table.PrimaryKey("PK_BudgetSources", x => x.Id); 50 + }); 51 + 52 + migrationBuilder.CreateTable( 53 + name: "BudgetBudgetSource", 54 + columns: table => new 55 + { 56 + BudgetSourcesId = table.Column<Guid>(type: "uuid", nullable: false), 57 + BudgetsId = table.Column<Guid>(type: "uuid", nullable: false) 58 + }, 59 + constraints: table => 60 + { 61 + table.PrimaryKey("PK_BudgetBudgetSource", x => new { x.BudgetSourcesId, x.BudgetsId }); 62 + table.ForeignKey( 63 + name: "FK_BudgetBudgetSource_BudgetSources_BudgetSourcesId", 64 + column: x => x.BudgetSourcesId, 65 + principalTable: "BudgetSources", 66 + principalColumn: "Id", 67 + onDelete: ReferentialAction.Cascade); 68 + table.ForeignKey( 69 + name: "FK_BudgetBudgetSource_Budgets_BudgetsId", 70 + column: x => x.BudgetsId, 71 + principalTable: "Budgets", 72 + principalColumn: "Id", 73 + onDelete: ReferentialAction.Cascade); 74 + }); 75 + 76 + migrationBuilder.CreateTable( 77 + name: "BudgetPoints", 78 + columns: table => new 79 + { 80 + Id = table.Column<Guid>(type: "uuid", nullable: false), 81 + Reference = table.Column<string>(type: "text", nullable: false), 82 + Value = table.Column<int>(type: "integer", nullable: false), 83 + Timestamp = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), 84 + BudgetId = table.Column<Guid>(type: "uuid", nullable: false), 85 + BudgetSourceId = table.Column<Guid>(type: "uuid", nullable: false) 86 + }, 87 + constraints: table => 88 + { 89 + table.PrimaryKey("PK_BudgetPoints", x => x.Id); 90 + table.ForeignKey( 91 + name: "FK_BudgetPoints_BudgetSources_BudgetSourceId", 92 + column: x => x.BudgetSourceId, 93 + principalTable: "BudgetSources", 94 + principalColumn: "Id", 95 + onDelete: ReferentialAction.Cascade); 96 + table.ForeignKey( 97 + name: "FK_BudgetPoints_Budgets_BudgetId", 98 + column: x => x.BudgetId, 99 + principalTable: "Budgets", 100 + principalColumn: "Id", 101 + onDelete: ReferentialAction.Cascade); 102 + }); 103 + 104 + migrationBuilder.CreateIndex( 105 + name: "IX_BudgetBudgetSource_BudgetsId", 106 + table: "BudgetBudgetSource", 107 + column: "BudgetsId"); 108 + 109 + migrationBuilder.CreateIndex( 110 + name: "IX_BudgetPoints_BudgetId", 111 + table: "BudgetPoints", 112 + column: "BudgetId"); 113 + 114 + migrationBuilder.CreateIndex( 115 + name: "IX_BudgetPoints_BudgetSourceId", 116 + table: "BudgetPoints", 117 + column: "BudgetSourceId"); 118 + 119 + migrationBuilder.CreateIndex( 120 + name: "IX_Budgets_OwnerId", 121 + table: "Budgets", 122 + column: "OwnerId"); 123 + } 124 + 125 + /// <inheritdoc /> 126 + protected override void Down(MigrationBuilder migrationBuilder) 127 + { 128 + migrationBuilder.DropTable( 129 + name: "BudgetBudgetSource"); 130 + 131 + migrationBuilder.DropTable( 132 + name: "BudgetPoints"); 133 + 134 + migrationBuilder.DropTable( 135 + name: "BudgetSources"); 136 + 137 + migrationBuilder.DropTable( 138 + name: "Budgets"); 139 + } 140 + } 141 + }
+259
src/Den.Infrastructure/Migrations/AuthContextModelSnapshot.cs
··· 1 + // <auto-generated /> 2 + using System; 3 + using Den.Infrastructure.Persistence; 4 + using Microsoft.EntityFrameworkCore; 5 + using Microsoft.EntityFrameworkCore.Infrastructure; 6 + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 7 + using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; 8 + 9 + #nullable disable 10 + 11 + namespace Den.Infrastructure.Migrations 12 + { 13 + [DbContext(typeof(DenDbContext))] 14 + partial class AuthContextModelSnapshot : ModelSnapshot 15 + { 16 + protected override void BuildModel(ModelBuilder modelBuilder) 17 + { 18 + #pragma warning disable 612, 618 19 + modelBuilder 20 + .HasAnnotation("ProductVersion", "9.0.9") 21 + .HasAnnotation("Relational:MaxIdentifierLength", 63); 22 + 23 + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); 24 + 25 + modelBuilder.Entity("BudgetBudgetSource", b => 26 + { 27 + b.Property<Guid>("BudgetSourcesId") 28 + .HasColumnType("uuid"); 29 + 30 + b.Property<Guid>("BudgetsId") 31 + .HasColumnType("uuid"); 32 + 33 + b.HasKey("BudgetSourcesId", "BudgetsId"); 34 + 35 + b.HasIndex("BudgetsId"); 36 + 37 + b.ToTable("BudgetBudgetSource"); 38 + }); 39 + 40 + modelBuilder.Entity("Den.Domain.Entities.Budget", b => 41 + { 42 + b.Property<Guid>("Id") 43 + .ValueGeneratedOnAdd() 44 + .HasColumnType("uuid"); 45 + 46 + b.Property<DateTime>("CreatedAt") 47 + .HasColumnType("timestamp with time zone"); 48 + 49 + b.Property<int>("Currency") 50 + .HasColumnType("integer"); 51 + 52 + b.Property<string>("Description") 53 + .IsRequired() 54 + .HasColumnType("text"); 55 + 56 + b.Property<string>("DisplayName") 57 + .IsRequired() 58 + .HasColumnType("text"); 59 + 60 + b.Property<Guid>("OwnerId") 61 + .HasColumnType("uuid"); 62 + 63 + b.Property<int>("Period") 64 + .HasColumnType("integer"); 65 + 66 + b.Property<int>("Total") 67 + .HasColumnType("integer"); 68 + 69 + b.Property<DateTime>("UpdatedAt") 70 + .HasColumnType("timestamp with time zone"); 71 + 72 + b.HasKey("Id"); 73 + 74 + b.HasIndex("OwnerId"); 75 + 76 + b.ToTable("Budgets"); 77 + }); 78 + 79 + modelBuilder.Entity("Den.Domain.Entities.BudgetPoint", b => 80 + { 81 + b.Property<Guid>("Id") 82 + .ValueGeneratedOnAdd() 83 + .HasColumnType("uuid"); 84 + 85 + b.Property<Guid>("BudgetId") 86 + .HasColumnType("uuid"); 87 + 88 + b.Property<Guid>("BudgetSourceId") 89 + .HasColumnType("uuid"); 90 + 91 + b.Property<string>("Reference") 92 + .IsRequired() 93 + .HasColumnType("text"); 94 + 95 + b.Property<DateTime>("Timestamp") 96 + .HasColumnType("timestamp with time zone"); 97 + 98 + b.Property<int>("Value") 99 + .HasColumnType("integer"); 100 + 101 + b.HasKey("Id"); 102 + 103 + b.HasIndex("BudgetId"); 104 + 105 + b.HasIndex("BudgetSourceId"); 106 + 107 + b.ToTable("BudgetPoints"); 108 + }); 109 + 110 + modelBuilder.Entity("Den.Domain.Entities.BudgetSource", b => 111 + { 112 + b.Property<Guid>("Id") 113 + .ValueGeneratedOnAdd() 114 + .HasColumnType("uuid"); 115 + 116 + b.Property<string>("DisplayName") 117 + .IsRequired() 118 + .HasColumnType("text"); 119 + 120 + b.Property<int>("Provider") 121 + .HasColumnType("integer"); 122 + 123 + b.HasKey("Id"); 124 + 125 + b.ToTable("BudgetSources"); 126 + }); 127 + 128 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 129 + { 130 + b.Property<Guid>("Id") 131 + .ValueGeneratedOnAdd() 132 + .HasColumnType("uuid"); 133 + 134 + b.Property<string>("DeviceInfo") 135 + .HasColumnType("text"); 136 + 137 + b.Property<DateTime>("Expiry") 138 + .HasColumnType("timestamp with time zone"); 139 + 140 + b.Property<string>("RefreshTokenHash") 141 + .IsRequired() 142 + .HasColumnType("text"); 143 + 144 + b.Property<Guid>("UserId") 145 + .HasColumnType("uuid"); 146 + 147 + b.HasKey("Id"); 148 + 149 + b.HasIndex("UserId"); 150 + 151 + b.ToTable("Sessions"); 152 + }); 153 + 154 + modelBuilder.Entity("Den.Domain.Entities.User", b => 155 + { 156 + b.Property<Guid>("Id") 157 + .ValueGeneratedOnAdd() 158 + .HasColumnType("uuid"); 159 + 160 + b.Property<string>("DisplayName") 161 + .IsRequired() 162 + .HasColumnType("text"); 163 + 164 + b.Property<string>("Email") 165 + .IsRequired() 166 + .HasColumnType("text"); 167 + 168 + b.Property<string>("PasswordHash") 169 + .IsRequired() 170 + .HasColumnType("text"); 171 + 172 + b.Property<int>("Role") 173 + .HasColumnType("integer"); 174 + 175 + b.Property<string>("Username") 176 + .IsRequired() 177 + .HasColumnType("text"); 178 + 179 + b.HasKey("Id"); 180 + 181 + b.ToTable("Users"); 182 + }); 183 + 184 + modelBuilder.Entity("BudgetBudgetSource", b => 185 + { 186 + b.HasOne("Den.Domain.Entities.BudgetSource", null) 187 + .WithMany() 188 + .HasForeignKey("BudgetSourcesId") 189 + .OnDelete(DeleteBehavior.Cascade) 190 + .IsRequired(); 191 + 192 + b.HasOne("Den.Domain.Entities.Budget", null) 193 + .WithMany() 194 + .HasForeignKey("BudgetsId") 195 + .OnDelete(DeleteBehavior.Cascade) 196 + .IsRequired(); 197 + }); 198 + 199 + modelBuilder.Entity("Den.Domain.Entities.Budget", b => 200 + { 201 + b.HasOne("Den.Domain.Entities.User", "Owner") 202 + .WithMany("Budgets") 203 + .HasForeignKey("OwnerId") 204 + .OnDelete(DeleteBehavior.Cascade) 205 + .IsRequired(); 206 + 207 + b.Navigation("Owner"); 208 + }); 209 + 210 + modelBuilder.Entity("Den.Domain.Entities.BudgetPoint", b => 211 + { 212 + b.HasOne("Den.Domain.Entities.Budget", "Budget") 213 + .WithMany("BudgetPoints") 214 + .HasForeignKey("BudgetId") 215 + .OnDelete(DeleteBehavior.Cascade) 216 + .IsRequired(); 217 + 218 + b.HasOne("Den.Domain.Entities.BudgetSource", "BudgetSource") 219 + .WithMany("BudgetPoints") 220 + .HasForeignKey("BudgetSourceId") 221 + .OnDelete(DeleteBehavior.Cascade) 222 + .IsRequired(); 223 + 224 + b.Navigation("Budget"); 225 + 226 + b.Navigation("BudgetSource"); 227 + }); 228 + 229 + modelBuilder.Entity("Den.Domain.Entities.Session", b => 230 + { 231 + b.HasOne("Den.Domain.Entities.User", "User") 232 + .WithMany("Sessions") 233 + .HasForeignKey("UserId") 234 + .OnDelete(DeleteBehavior.Cascade) 235 + .IsRequired(); 236 + 237 + b.Navigation("User"); 238 + }); 239 + 240 + modelBuilder.Entity("Den.Domain.Entities.Budget", b => 241 + { 242 + b.Navigation("BudgetPoints"); 243 + }); 244 + 245 + modelBuilder.Entity("Den.Domain.Entities.BudgetSource", b => 246 + { 247 + b.Navigation("BudgetPoints"); 248 + }); 249 + 250 + modelBuilder.Entity("Den.Domain.Entities.User", b => 251 + { 252 + b.Navigation("Budgets"); 253 + 254 + b.Navigation("Sessions"); 255 + }); 256 + #pragma warning restore 612, 618 257 + } 258 + } 259 + }
+13
src/Den.Infrastructure/Persistence/DenDbContext.cs
··· 1 + using Den.Domain.Entities; 2 + using Microsoft.EntityFrameworkCore; 3 + 4 + namespace Den.Infrastructure.Persistence; 5 + 6 + public class DenDbContext(DbContextOptions<DenDbContext> options) : DbContext(options) 7 + { 8 + public DbSet<User> Users => Set<User>(); 9 + public DbSet<Session> Sessions => Set<Session>(); 10 + public DbSet<Budget> Budgets => Set<Budget>(); 11 + public DbSet<BudgetPoint> BudgetPoints => Set<BudgetPoint>(); 12 + public DbSet<BudgetSource> BudgetSources => Set<BudgetSource>(); 13 + }
+21
src/Den.MigrationService/Den.MigrationService.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk.Worker"> 2 + 3 + <PropertyGroup> 4 + <TargetFramework>net10.0</TargetFramework> 5 + <Nullable>enable</Nullable> 6 + <ImplicitUsings>enable</ImplicitUsings> 7 + <UserSecretsId>dotnet-Den.MigrationService-3de7b5fb-d8ff-4990-a075-8f696fad1b9e</UserSecretsId> 8 + </PropertyGroup> 9 + 10 + <ItemGroup> 11 + <PackageReference Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" /> 12 + <PackageReference Include="Microsoft.EntityFrameworkCore.Design"> 13 + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> 14 + <PrivateAssets>all</PrivateAssets> 15 + </PackageReference> 16 + <PackageReference Include="Microsoft.Extensions.Hosting" /> 17 + 18 + <ProjectReference Include="..\Den.Infrastructure\Den.Infrastructure.csproj" /> 19 + <ProjectReference Include="..\Den.ServiceDefaults\Den.ServiceDefaults.csproj" /> 20 + </ItemGroup> 21 + </Project>
+10
src/Den.MigrationService/Program.cs
··· 1 + using Den.Infrastructure.Persistence; 2 + using Den.MigrationService; 3 + 4 + var builder = Host.CreateApplicationBuilder(args); 5 + builder.AddServiceDefaults(isApi: false); 6 + builder.Services.AddHostedService<Worker>(); 7 + builder.AddNpgsqlDbContext<DenDbContext>(connectionName: "postgresdb"); 8 + 9 + var host = builder.Build(); 10 + host.Run();
+12
src/Den.MigrationService/Properties/launchSettings.json
··· 1 + { 2 + "$schema": "https://json.schemastore.org/launchsettings.json", 3 + "profiles": { 4 + "Den.MigrationService": { 5 + "commandName": "Project", 6 + "dotnetRunMessages": true, 7 + "environmentVariables": { 8 + "DOTNET_ENVIRONMENT": "Development" 9 + } 10 + } 11 + } 12 + }
+41
src/Den.MigrationService/Worker.cs
··· 1 + using System.Diagnostics; 2 + 3 + using Den.Infrastructure.Persistence; 4 + 5 + using Microsoft.EntityFrameworkCore; 6 + 7 + namespace Den.MigrationService; 8 + 9 + public class Worker( 10 + IServiceProvider serviceProvider, 11 + IHostApplicationLifetime hostApplicationLifetime 12 + ) : BackgroundService 13 + { 14 + public const string ActivitySourceName = "Migrations"; 15 + private static readonly ActivitySource ActivitySource = new(ActivitySourceName); 16 + 17 + protected override async Task ExecuteAsync(CancellationToken stoppingToken) 18 + { 19 + using var activity = ActivitySource.StartActivity("Migrating database", ActivityKind.Client); 20 + 21 + try 22 + { 23 + using var scope = serviceProvider.CreateScope(); 24 + var dbContext = scope.ServiceProvider.GetRequiredService<DenDbContext>(); 25 + await RunMigrationAsync(dbContext, stoppingToken); 26 + } 27 + catch (Exception ex) 28 + { 29 + activity?.AddException(ex); 30 + throw; 31 + } 32 + 33 + hostApplicationLifetime.StopApplication(); 34 + } 35 + 36 + private static async Task RunMigrationAsync(DbContext dbContext, CancellationToken cancellationToken) 37 + { 38 + var strategy = dbContext.Database.CreateExecutionStrategy(); 39 + await strategy.ExecuteAsync(async () => await dbContext.Database.MigrateAsync(cancellationToken)); 40 + } 41 + }
+8
src/Den.MigrationService/appsettings.Development.json
··· 1 + { 2 + "Logging": { 3 + "LogLevel": { 4 + "Default": "Information", 5 + "Microsoft.Hosting.Lifetime": "Information" 6 + } 7 + } 8 + }
+8
src/Den.MigrationService/appsettings.json
··· 1 + { 2 + "Logging": { 3 + "LogLevel": { 4 + "Default": "Information", 5 + "Microsoft.Hosting.Lifetime": "Information" 6 + } 7 + } 8 + }
+23
src/Den.ServiceDefaults/Den.ServiceDefaults.csproj
··· 1 + <Project Sdk="Microsoft.NET.Sdk"> 2 + 3 + <PropertyGroup> 4 + <TargetFramework>net10.0</TargetFramework> 5 + <ImplicitUsings>enable</ImplicitUsings> 6 + <Nullable>enable</Nullable> 7 + <IsAspireSharedProject>true</IsAspireSharedProject> 8 + </PropertyGroup> 9 + 10 + <ItemGroup> 11 + <FrameworkReference Include="Microsoft.AspNetCore.App" /> 12 + <PackageReference Include="Microsoft.AspNetCore.OpenApi" /> 13 + 14 + <PackageReference Include="Microsoft.Extensions.Http.Resilience" /> 15 + <PackageReference Include="Microsoft.Extensions.ServiceDiscovery" /> 16 + <PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" /> 17 + <PackageReference Include="OpenTelemetry.Extensions.Hosting" /> 18 + <PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" /> 19 + <PackageReference Include="OpenTelemetry.Instrumentation.Http" /> 20 + <PackageReference Include="OpenTelemetry.Instrumentation.Runtime" /> 21 + </ItemGroup> 22 + 23 + </Project>
+170
src/Den.ServiceDefaults/Extensions.cs
··· 1 + using Microsoft.AspNetCore.Authorization; 2 + using Microsoft.AspNetCore.Builder; 3 + using Microsoft.AspNetCore.Diagnostics.HealthChecks; 4 + using Microsoft.Extensions.DependencyInjection; 5 + using Microsoft.Extensions.Diagnostics.HealthChecks; 6 + using Microsoft.Extensions.Logging; 7 + using Microsoft.Extensions.ServiceDiscovery; 8 + using Microsoft.OpenApi.Models; 9 + using OpenTelemetry; 10 + using OpenTelemetry.Metrics; 11 + using OpenTelemetry.Trace; 12 + 13 + #pragma warning disable IDE0130 // Namespace does not match folder structure 14 + namespace Microsoft.Extensions.Hosting; 15 + #pragma warning restore IDE0130 // Namespace does not match folder structure 16 + 17 + // Adds common Aspire services: service discovery, resilience, health checks, and OpenTelemetry. 18 + // This project should be referenced by each service project in your solution. 19 + // To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults 20 + public static class Extensions 21 + { 22 + private const string HealthEndpointPath = "/healthz"; 23 + private const string AlivenessEndpointPath = "/livez"; 24 + 25 + public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder, bool isApi = true) where TBuilder : IHostApplicationBuilder 26 + { 27 + builder.ConfigureOpenTelemetry(); 28 + 29 + builder.AddDefaultHealthChecks(); 30 + 31 + builder.Services.AddServiceDiscovery(); 32 + 33 + if (isApi) 34 + { 35 + builder.Services.AddOpenApi(options => 36 + { 37 + options.AddDocumentTransformer((document, context, cancellationToken) => 38 + { 39 + document.Components ??= new(); 40 + document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>(); 41 + document.Components.SecuritySchemes["Bearer"] = new() 42 + { 43 + Type = SecuritySchemeType.Http, 44 + Scheme = "bearer", 45 + BearerFormat = "JWT", 46 + Description = "JWT Authorization header using the Bearer scheme" 47 + }; 48 + 49 + return Task.CompletedTask; 50 + }); 51 + 52 + options.AddOperationTransformer((operation, context, cancellationToken) => 53 + { 54 + var metadata = context.Description.ActionDescriptor.EndpointMetadata; 55 + 56 + var hasAuthorize = metadata.OfType<AuthorizeAttribute>().Any(); 57 + var hasAllowAnonymous = metadata.OfType<AllowAnonymousAttribute>().Any(); 58 + 59 + if (hasAuthorize && !hasAllowAnonymous) 60 + { 61 + operation.Security = new List<OpenApiSecurityRequirement> 62 + { 63 + new() 64 + { 65 + [new OpenApiSecurityScheme 66 + { 67 + Reference = new OpenApiReference 68 + { 69 + Type = ReferenceType.SecurityScheme, 70 + Id = "Bearer" 71 + } 72 + }] = Array.Empty<string>() 73 + } 74 + }; 75 + } 76 + 77 + return Task.CompletedTask; 78 + }); 79 + }); 80 + } 81 + 82 + builder.Services.ConfigureHttpClientDefaults(http => 83 + { 84 + // Turn on resilience by default 85 + http.AddStandardResilienceHandler(); 86 + 87 + // Turn on service discovery by default 88 + http.AddServiceDiscovery(); 89 + }); 90 + 91 + builder.Services.Configure<ServiceDiscoveryOptions>(options => 92 + options.AllowedSchemes = ["https"]); 93 + 94 + return builder; 95 + } 96 + 97 + public static TBuilder ConfigureOpenTelemetry<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder 98 + { 99 + builder.Logging.AddOpenTelemetry(logging => 100 + { 101 + logging.IncludeFormattedMessage = true; 102 + logging.IncludeScopes = true; 103 + }); 104 + 105 + builder.Services.AddOpenTelemetry() 106 + .WithMetrics(metrics => 107 + metrics.AddAspNetCoreInstrumentation() 108 + .AddHttpClientInstrumentation() 109 + .AddRuntimeInstrumentation() 110 + ) 111 + .WithTracing(tracing => 112 + tracing.AddSource(builder.Environment.ApplicationName) 113 + .AddAspNetCoreInstrumentation(tracing => 114 + // Exclude health check requests from tracing 115 + tracing.Filter = context => 116 + !context.Request.Path.StartsWithSegments(HealthEndpointPath) 117 + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) 118 + ) 119 + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) 120 + //.AddGrpcClientInstrumentation() 121 + .AddHttpClientInstrumentation() 122 + ); 123 + 124 + builder.AddOpenTelemetryExporters(); 125 + 126 + return builder; 127 + } 128 + 129 + private static TBuilder AddOpenTelemetryExporters<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder 130 + { 131 + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); 132 + 133 + if (useOtlpExporter) 134 + { 135 + builder.Services.AddOpenTelemetry().UseOtlpExporter(); 136 + } 137 + 138 + return builder; 139 + } 140 + 141 + public static TBuilder AddDefaultHealthChecks<TBuilder>(this TBuilder builder) where TBuilder : IHostApplicationBuilder 142 + { 143 + builder.Services.AddHealthChecks() 144 + // Add a default liveness check to ensure app is responsive 145 + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); 146 + 147 + return builder; 148 + } 149 + 150 + public static WebApplication MapDefaultEndpoints(this WebApplication app) 151 + { 152 + // Adding health checks endpoints to applications in non-development environments has security implications. 153 + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. 154 + if (app.Environment.IsDevelopment()) 155 + { 156 + app.MapOpenApi(); 157 + 158 + // All health checks must pass for app to be considered ready to accept traffic after starting 159 + app.MapHealthChecks(HealthEndpointPath); 160 + 161 + // Only health checks tagged with the "live" tag must pass for app to be considered alive 162 + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions 163 + { 164 + Predicate = r => r.Tags.Contains("live") 165 + }); 166 + } 167 + 168 + return app; 169 + } 170 + }