diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..05e5a39
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,485 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = crlf
+indent_size = 4
+indent_style = space
+insert_final_newline = false
+max_line_length = 120
+tab_width = 4
+ij_continuation_indent_size = 4
+ij_formatter_off_tag = @formatter:off
+ij_formatter_on_tag = @formatter:on
+ij_formatter_tags_enabled = true
+ij_smart_tabs = false
+ij_visual_guides = none
+ij_wrap_on_typing = false
+
+[*.css]
+ij_css_align_closing_brace_with_properties = false
+ij_css_blank_lines_around_nested_selector = 1
+ij_css_blank_lines_between_blocks = 1
+ij_css_block_comment_add_space = false
+ij_css_brace_placement = end_of_line
+ij_css_enforce_quotes_on_format = false
+ij_css_hex_color_long_format = false
+ij_css_hex_color_lower_case = false
+ij_css_hex_color_short_format = false
+ij_css_hex_color_upper_case = false
+ij_css_keep_blank_lines_in_code = 2
+ij_css_keep_indents_on_empty_lines = false
+ij_css_keep_single_line_blocks = false
+ij_css_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
+ij_css_space_after_colon = true
+ij_css_space_before_opening_brace = true
+ij_css_use_double_quotes = true
+ij_css_value_alignment = do_not_align
+
+[*.sass]
+indent_size = 2
+ij_sass_align_closing_brace_with_properties = false
+ij_sass_blank_lines_around_nested_selector = 1
+ij_sass_blank_lines_between_blocks = 1
+ij_sass_brace_placement = 0
+ij_sass_enforce_quotes_on_format = false
+ij_sass_hex_color_long_format = false
+ij_sass_hex_color_lower_case = false
+ij_sass_hex_color_short_format = false
+ij_sass_hex_color_upper_case = false
+ij_sass_keep_blank_lines_in_code = 2
+ij_sass_keep_indents_on_empty_lines = false
+ij_sass_keep_single_line_blocks = false
+ij_sass_line_comment_add_space = false
+ij_sass_line_comment_at_first_column = false
+ij_sass_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
+ij_sass_space_after_colon = true
+ij_sass_space_before_opening_brace = true
+ij_sass_use_double_quotes = true
+ij_sass_value_alignment = 0
+
+[*.scss]
+indent_size = 2
+ij_scss_align_closing_brace_with_properties = false
+ij_scss_blank_lines_around_nested_selector = 1
+ij_scss_blank_lines_between_blocks = 1
+ij_scss_block_comment_add_space = false
+ij_scss_brace_placement = 0
+ij_scss_enforce_quotes_on_format = false
+ij_scss_hex_color_long_format = false
+ij_scss_hex_color_lower_case = false
+ij_scss_hex_color_short_format = false
+ij_scss_hex_color_upper_case = false
+ij_scss_keep_blank_lines_in_code = 2
+ij_scss_keep_indents_on_empty_lines = false
+ij_scss_keep_single_line_blocks = false
+ij_scss_line_comment_add_space = false
+ij_scss_line_comment_at_first_column = false
+ij_scss_properties_order = font, font-family, font-size, font-weight, font-style, font-variant, font-size-adjust, font-stretch, line-height, position, z-index, top, right, bottom, left, display, visibility, float, clear, overflow, overflow-x, overflow-y, clip, zoom, align-content, align-items, align-self, flex, flex-flow, flex-basis, flex-direction, flex-grow, flex-shrink, flex-wrap, justify-content, order, box-sizing, width, min-width, max-width, height, min-height, max-height, margin, margin-top, margin-right, margin-bottom, margin-left, padding, padding-top, padding-right, padding-bottom, padding-left, table-layout, empty-cells, caption-side, border-spacing, border-collapse, list-style, list-style-position, list-style-type, list-style-image, content, quotes, counter-reset, counter-increment, resize, cursor, user-select, nav-index, nav-up, nav-right, nav-down, nav-left, transition, transition-delay, transition-timing-function, transition-duration, transition-property, transform, transform-origin, animation, animation-name, animation-duration, animation-play-state, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, text-align, text-align-last, vertical-align, white-space, text-decoration, text-emphasis, text-emphasis-color, text-emphasis-style, text-emphasis-position, text-indent, text-justify, letter-spacing, word-spacing, text-outline, text-transform, text-wrap, text-overflow, text-overflow-ellipsis, text-overflow-mode, word-wrap, word-break, tab-size, hyphens, pointer-events, opacity, color, border, border-width, border-style, border-color, border-top, border-top-width, border-top-style, border-top-color, border-right, border-right-width, border-right-style, border-right-color, border-bottom, border-bottom-width, border-bottom-style, border-bottom-color, border-left, border-left-width, border-left-style, border-left-color, border-radius, border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius, border-image, border-image-source, border-image-slice, border-image-width, border-image-outset, border-image-repeat, outline, outline-width, outline-style, outline-color, outline-offset, background, background-color, background-image, background-repeat, background-attachment, background-position, background-position-x, background-position-y, background-clip, background-origin, background-size, box-decoration-break, box-shadow, text-shadow
+ij_scss_space_after_colon = true
+ij_scss_space_before_opening_brace = true
+ij_scss_use_double_quotes = true
+ij_scss_value_alignment = 0
+
+[{*.ats,*.cts,*.mts,*.ts}]
+ij_continuation_indent_size = 4
+ij_typescript_align_imports = false
+ij_typescript_align_multiline_array_initializer_expression = false
+ij_typescript_align_multiline_binary_operation = false
+ij_typescript_align_multiline_chained_methods = false
+ij_typescript_align_multiline_extends_list = false
+ij_typescript_align_multiline_for = true
+ij_typescript_align_multiline_parameters = true
+ij_typescript_align_multiline_parameters_in_calls = false
+ij_typescript_align_multiline_ternary_operation = false
+ij_typescript_align_object_properties = 0
+ij_typescript_align_union_types = false
+ij_typescript_align_var_statements = 0
+ij_typescript_array_initializer_new_line_after_left_brace = false
+ij_typescript_array_initializer_right_brace_on_new_line = false
+ij_typescript_array_initializer_wrap = off
+ij_typescript_assignment_wrap = off
+ij_typescript_binary_operation_sign_on_next_line = false
+ij_typescript_binary_operation_wrap = off
+ij_typescript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
+ij_typescript_blank_lines_after_imports = 1
+ij_typescript_blank_lines_around_class = 1
+ij_typescript_blank_lines_around_field = 0
+ij_typescript_blank_lines_around_field_in_interface = 0
+ij_typescript_blank_lines_around_function = 1
+ij_typescript_blank_lines_around_method = 1
+ij_typescript_blank_lines_around_method_in_interface = 1
+ij_typescript_block_brace_style = end_of_line
+ij_typescript_block_comment_add_space = false
+ij_typescript_block_comment_at_first_column = true
+ij_typescript_call_parameters_new_line_after_left_paren = false
+ij_typescript_call_parameters_right_paren_on_new_line = false
+ij_typescript_call_parameters_wrap = off
+ij_typescript_catch_on_new_line = false
+ij_typescript_chained_call_dot_on_new_line = true
+ij_typescript_class_brace_style = end_of_line
+ij_typescript_comma_on_new_line = false
+ij_typescript_do_while_brace_force = never
+ij_typescript_else_on_new_line = false
+ij_typescript_enforce_trailing_comma = keep
+ij_typescript_enum_constants_wrap = on_every_item
+ij_typescript_extends_keyword_wrap = off
+ij_typescript_extends_list_wrap = off
+ij_typescript_field_prefix = _
+ij_typescript_file_name_style = relaxed
+ij_typescript_finally_on_new_line = false
+ij_typescript_for_brace_force = never
+ij_typescript_for_statement_new_line_after_left_paren = false
+ij_typescript_for_statement_right_paren_on_new_line = false
+ij_typescript_for_statement_wrap = off
+ij_typescript_force_quote_style = false
+ij_typescript_force_semicolon_style = false
+ij_typescript_function_expression_brace_style = end_of_line
+ij_typescript_if_brace_force = never
+ij_typescript_import_merge_members = global
+ij_typescript_import_prefer_absolute_path = global
+ij_typescript_import_sort_members = true
+ij_typescript_import_sort_module_name = false
+ij_typescript_import_use_node_resolution = true
+ij_typescript_imports_wrap = on_every_item
+ij_typescript_indent_case_from_switch = true
+ij_typescript_indent_chained_calls = true
+ij_typescript_indent_package_children = 0
+ij_typescript_jsdoc_include_types = false
+ij_typescript_jsx_attribute_value = braces
+ij_typescript_keep_blank_lines_in_code = 2
+ij_typescript_keep_first_column_comment = true
+ij_typescript_keep_indents_on_empty_lines = false
+ij_typescript_keep_line_breaks = true
+ij_typescript_keep_simple_blocks_in_one_line = false
+ij_typescript_keep_simple_methods_in_one_line = false
+ij_typescript_line_comment_add_space = true
+ij_typescript_line_comment_at_first_column = false
+ij_typescript_method_brace_style = end_of_line
+ij_typescript_method_call_chain_wrap = off
+ij_typescript_method_parameters_new_line_after_left_paren = false
+ij_typescript_method_parameters_right_paren_on_new_line = false
+ij_typescript_method_parameters_wrap = off
+ij_typescript_object_literal_wrap = on_every_item
+ij_typescript_object_types_wrap = on_every_item
+ij_typescript_parentheses_expression_new_line_after_left_paren = false
+ij_typescript_parentheses_expression_right_paren_on_new_line = false
+ij_typescript_place_assignment_sign_on_next_line = false
+ij_typescript_prefer_as_type_cast = false
+ij_typescript_prefer_explicit_types_function_expression_returns = false
+ij_typescript_prefer_explicit_types_function_returns = false
+ij_typescript_prefer_explicit_types_vars_fields = false
+ij_typescript_prefer_parameters_wrap = false
+ij_typescript_reformat_c_style_comments = false
+ij_typescript_space_after_colon = true
+ij_typescript_space_after_comma = true
+ij_typescript_space_after_dots_in_rest_parameter = false
+ij_typescript_space_after_generator_mult = true
+ij_typescript_space_after_property_colon = true
+ij_typescript_space_after_quest = true
+ij_typescript_space_after_type_colon = true
+ij_typescript_space_after_unary_not = false
+ij_typescript_space_before_async_arrow_lparen = true
+ij_typescript_space_before_catch_keyword = true
+ij_typescript_space_before_catch_left_brace = true
+ij_typescript_space_before_catch_parentheses = true
+ij_typescript_space_before_class_lbrace = true
+ij_typescript_space_before_class_left_brace = true
+ij_typescript_space_before_colon = true
+ij_typescript_space_before_comma = false
+ij_typescript_space_before_do_left_brace = true
+ij_typescript_space_before_else_keyword = true
+ij_typescript_space_before_else_left_brace = true
+ij_typescript_space_before_finally_keyword = true
+ij_typescript_space_before_finally_left_brace = true
+ij_typescript_space_before_for_left_brace = true
+ij_typescript_space_before_for_parentheses = true
+ij_typescript_space_before_for_semicolon = false
+ij_typescript_space_before_function_left_parenth = true
+ij_typescript_space_before_generator_mult = false
+ij_typescript_space_before_if_left_brace = true
+ij_typescript_space_before_if_parentheses = true
+ij_typescript_space_before_method_call_parentheses = false
+ij_typescript_space_before_method_left_brace = true
+ij_typescript_space_before_method_parentheses = false
+ij_typescript_space_before_property_colon = false
+ij_typescript_space_before_quest = true
+ij_typescript_space_before_switch_left_brace = true
+ij_typescript_space_before_switch_parentheses = true
+ij_typescript_space_before_try_left_brace = true
+ij_typescript_space_before_type_colon = false
+ij_typescript_space_before_unary_not = false
+ij_typescript_space_before_while_keyword = true
+ij_typescript_space_before_while_left_brace = true
+ij_typescript_space_before_while_parentheses = true
+ij_typescript_spaces_around_additive_operators = true
+ij_typescript_spaces_around_arrow_function_operator = true
+ij_typescript_spaces_around_assignment_operators = true
+ij_typescript_spaces_around_bitwise_operators = true
+ij_typescript_spaces_around_equality_operators = true
+ij_typescript_spaces_around_logical_operators = true
+ij_typescript_spaces_around_multiplicative_operators = true
+ij_typescript_spaces_around_relational_operators = true
+ij_typescript_spaces_around_shift_operators = true
+ij_typescript_spaces_around_unary_operator = false
+ij_typescript_spaces_within_array_initializer_brackets = false
+ij_typescript_spaces_within_brackets = false
+ij_typescript_spaces_within_catch_parentheses = false
+ij_typescript_spaces_within_for_parentheses = false
+ij_typescript_spaces_within_if_parentheses = false
+ij_typescript_spaces_within_imports = false
+ij_typescript_spaces_within_interpolation_expressions = false
+ij_typescript_spaces_within_method_call_parentheses = false
+ij_typescript_spaces_within_method_parentheses = false
+ij_typescript_spaces_within_object_literal_braces = false
+ij_typescript_spaces_within_object_type_braces = true
+ij_typescript_spaces_within_parentheses = false
+ij_typescript_spaces_within_switch_parentheses = false
+ij_typescript_spaces_within_type_assertion = false
+ij_typescript_spaces_within_union_types = true
+ij_typescript_spaces_within_while_parentheses = false
+ij_typescript_special_else_if_treatment = true
+ij_typescript_ternary_operation_signs_on_next_line = false
+ij_typescript_ternary_operation_wrap = off
+ij_typescript_union_types_wrap = on_every_item
+ij_typescript_use_chained_calls_group_indents = false
+ij_typescript_use_double_quotes = true
+ij_typescript_use_explicit_js_extension = auto
+ij_typescript_use_path_mapping = always
+ij_typescript_use_public_modifier = false
+ij_typescript_use_semicolon_after_statement = true
+ij_typescript_var_declaration_wrap = normal
+ij_typescript_while_brace_force = never
+ij_typescript_while_on_new_line = false
+ij_typescript_wrap_comments = false
+
+[{*.cjs,*.js}]
+ij_continuation_indent_size = 4
+ij_javascript_align_imports = false
+ij_javascript_align_multiline_array_initializer_expression = false
+ij_javascript_align_multiline_binary_operation = false
+ij_javascript_align_multiline_chained_methods = false
+ij_javascript_align_multiline_extends_list = false
+ij_javascript_align_multiline_for = true
+ij_javascript_align_multiline_parameters = true
+ij_javascript_align_multiline_parameters_in_calls = false
+ij_javascript_align_multiline_ternary_operation = false
+ij_javascript_align_object_properties = 0
+ij_javascript_align_union_types = false
+ij_javascript_align_var_statements = 0
+ij_javascript_array_initializer_new_line_after_left_brace = false
+ij_javascript_array_initializer_right_brace_on_new_line = false
+ij_javascript_array_initializer_wrap = off
+ij_javascript_assignment_wrap = off
+ij_javascript_binary_operation_sign_on_next_line = false
+ij_javascript_binary_operation_wrap = off
+ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/**
+ij_javascript_blank_lines_after_imports = 1
+ij_javascript_blank_lines_around_class = 1
+ij_javascript_blank_lines_around_field = 0
+ij_javascript_blank_lines_around_function = 1
+ij_javascript_blank_lines_around_method = 1
+ij_javascript_block_brace_style = end_of_line
+ij_javascript_block_comment_add_space = false
+ij_javascript_block_comment_at_first_column = true
+ij_javascript_call_parameters_new_line_after_left_paren = false
+ij_javascript_call_parameters_right_paren_on_new_line = false
+ij_javascript_call_parameters_wrap = off
+ij_javascript_catch_on_new_line = false
+ij_javascript_chained_call_dot_on_new_line = true
+ij_javascript_class_brace_style = end_of_line
+ij_javascript_comma_on_new_line = false
+ij_javascript_do_while_brace_force = never
+ij_javascript_else_on_new_line = false
+ij_javascript_enforce_trailing_comma = keep
+ij_javascript_extends_keyword_wrap = off
+ij_javascript_extends_list_wrap = off
+ij_javascript_field_prefix = _
+ij_javascript_file_name_style = relaxed
+ij_javascript_finally_on_new_line = false
+ij_javascript_for_brace_force = never
+ij_javascript_for_statement_new_line_after_left_paren = false
+ij_javascript_for_statement_right_paren_on_new_line = false
+ij_javascript_for_statement_wrap = off
+ij_javascript_force_quote_style = false
+ij_javascript_force_semicolon_style = false
+ij_javascript_function_expression_brace_style = end_of_line
+ij_javascript_if_brace_force = never
+ij_javascript_import_merge_members = global
+ij_javascript_import_prefer_absolute_path = global
+ij_javascript_import_sort_members = true
+ij_javascript_import_sort_module_name = false
+ij_javascript_import_use_node_resolution = true
+ij_javascript_imports_wrap = on_every_item
+ij_javascript_indent_case_from_switch = true
+ij_javascript_indent_chained_calls = true
+ij_javascript_indent_package_children = 0
+ij_javascript_jsx_attribute_value = braces
+ij_javascript_keep_blank_lines_in_code = 2
+ij_javascript_keep_first_column_comment = true
+ij_javascript_keep_indents_on_empty_lines = false
+ij_javascript_keep_line_breaks = true
+ij_javascript_keep_simple_blocks_in_one_line = false
+ij_javascript_keep_simple_methods_in_one_line = false
+ij_javascript_line_comment_add_space = true
+ij_javascript_line_comment_at_first_column = false
+ij_javascript_method_brace_style = end_of_line
+ij_javascript_method_call_chain_wrap = off
+ij_javascript_method_parameters_new_line_after_left_paren = false
+ij_javascript_method_parameters_right_paren_on_new_line = false
+ij_javascript_method_parameters_wrap = off
+ij_javascript_object_literal_wrap = on_every_item
+ij_javascript_object_types_wrap = on_every_item
+ij_javascript_parentheses_expression_new_line_after_left_paren = false
+ij_javascript_parentheses_expression_right_paren_on_new_line = false
+ij_javascript_place_assignment_sign_on_next_line = false
+ij_javascript_prefer_as_type_cast = false
+ij_javascript_prefer_explicit_types_function_expression_returns = false
+ij_javascript_prefer_explicit_types_function_returns = false
+ij_javascript_prefer_explicit_types_vars_fields = false
+ij_javascript_prefer_parameters_wrap = false
+ij_javascript_reformat_c_style_comments = false
+ij_javascript_space_after_colon = true
+ij_javascript_space_after_comma = true
+ij_javascript_space_after_dots_in_rest_parameter = false
+ij_javascript_space_after_generator_mult = true
+ij_javascript_space_after_property_colon = true
+ij_javascript_space_after_quest = true
+ij_javascript_space_after_type_colon = true
+ij_javascript_space_after_unary_not = false
+ij_javascript_space_before_async_arrow_lparen = true
+ij_javascript_space_before_catch_keyword = true
+ij_javascript_space_before_catch_left_brace = true
+ij_javascript_space_before_catch_parentheses = true
+ij_javascript_space_before_class_lbrace = true
+ij_javascript_space_before_class_left_brace = true
+ij_javascript_space_before_colon = true
+ij_javascript_space_before_comma = false
+ij_javascript_space_before_do_left_brace = true
+ij_javascript_space_before_else_keyword = true
+ij_javascript_space_before_else_left_brace = true
+ij_javascript_space_before_finally_keyword = true
+ij_javascript_space_before_finally_left_brace = true
+ij_javascript_space_before_for_left_brace = true
+ij_javascript_space_before_for_parentheses = true
+ij_javascript_space_before_for_semicolon = false
+ij_javascript_space_before_function_left_parenth = true
+ij_javascript_space_before_generator_mult = false
+ij_javascript_space_before_if_left_brace = true
+ij_javascript_space_before_if_parentheses = true
+ij_javascript_space_before_method_call_parentheses = false
+ij_javascript_space_before_method_left_brace = true
+ij_javascript_space_before_method_parentheses = false
+ij_javascript_space_before_property_colon = false
+ij_javascript_space_before_quest = true
+ij_javascript_space_before_switch_left_brace = true
+ij_javascript_space_before_switch_parentheses = true
+ij_javascript_space_before_try_left_brace = true
+ij_javascript_space_before_type_colon = false
+ij_javascript_space_before_unary_not = false
+ij_javascript_space_before_while_keyword = true
+ij_javascript_space_before_while_left_brace = true
+ij_javascript_space_before_while_parentheses = true
+ij_javascript_spaces_around_additive_operators = true
+ij_javascript_spaces_around_arrow_function_operator = true
+ij_javascript_spaces_around_assignment_operators = true
+ij_javascript_spaces_around_bitwise_operators = true
+ij_javascript_spaces_around_equality_operators = true
+ij_javascript_spaces_around_logical_operators = true
+ij_javascript_spaces_around_multiplicative_operators = true
+ij_javascript_spaces_around_relational_operators = true
+ij_javascript_spaces_around_shift_operators = true
+ij_javascript_spaces_around_unary_operator = false
+ij_javascript_spaces_within_array_initializer_brackets = false
+ij_javascript_spaces_within_brackets = false
+ij_javascript_spaces_within_catch_parentheses = false
+ij_javascript_spaces_within_for_parentheses = false
+ij_javascript_spaces_within_if_parentheses = false
+ij_javascript_spaces_within_imports = false
+ij_javascript_spaces_within_interpolation_expressions = false
+ij_javascript_spaces_within_method_call_parentheses = false
+ij_javascript_spaces_within_method_parentheses = false
+ij_javascript_spaces_within_object_literal_braces = false
+ij_javascript_spaces_within_object_type_braces = true
+ij_javascript_spaces_within_parentheses = false
+ij_javascript_spaces_within_switch_parentheses = false
+ij_javascript_spaces_within_type_assertion = false
+ij_javascript_spaces_within_union_types = true
+ij_javascript_spaces_within_while_parentheses = false
+ij_javascript_special_else_if_treatment = true
+ij_javascript_ternary_operation_signs_on_next_line = false
+ij_javascript_ternary_operation_wrap = off
+ij_javascript_union_types_wrap = on_every_item
+ij_javascript_use_chained_calls_group_indents = false
+ij_javascript_use_double_quotes = true
+ij_javascript_use_explicit_js_extension = auto
+ij_javascript_use_path_mapping = always
+ij_javascript_use_public_modifier = false
+ij_javascript_use_semicolon_after_statement = true
+ij_javascript_var_declaration_wrap = normal
+ij_javascript_while_brace_force = never
+ij_javascript_while_on_new_line = false
+ij_javascript_wrap_comments = false
+
+[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.prettierrc,.stylelintrc,bowerrc,jest.config}]
+indent_size = 2
+ij_json_array_wrapping = split_into_lines
+ij_json_keep_blank_lines_in_code = 0
+ij_json_keep_indents_on_empty_lines = false
+ij_json_keep_line_breaks = true
+ij_json_keep_trailing_comma = false
+ij_json_object_wrapping = split_into_lines
+ij_json_property_alignment = do_not_align
+ij_json_space_after_colon = true
+ij_json_space_after_comma = true
+ij_json_space_before_colon = false
+ij_json_space_before_comma = false
+ij_json_spaces_within_braces = false
+ij_json_spaces_within_brackets = false
+ij_json_wrap_long_lines = false
+
+[{*.htm,*.html,*.ng,*.sht,*.shtm,*.shtml}]
+ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3
+ij_html_align_attributes = true
+ij_html_align_text = false
+ij_html_attribute_wrap = normal
+ij_html_block_comment_add_space = false
+ij_html_block_comment_at_first_column = true
+ij_html_do_not_align_children_of_min_lines = 0
+ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p
+ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot
+ij_html_enforce_quotes = false
+ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var
+ij_html_keep_blank_lines = 2
+ij_html_keep_indents_on_empty_lines = false
+ij_html_keep_line_breaks = true
+ij_html_keep_line_breaks_in_text = true
+ij_html_keep_whitespaces = false
+ij_html_keep_whitespaces_inside = span, pre, textarea
+ij_html_line_comment_at_first_column = true
+ij_html_new_line_after_last_attribute = never
+ij_html_new_line_before_first_attribute = never
+ij_html_quote_style = double
+ij_html_remove_new_line_before_tags = br
+ij_html_space_after_tag_name = false
+ij_html_space_around_equality_in_attribute = false
+ij_html_space_inside_empty_tag = false
+ij_html_text_wrap = normal
+
+[{*.markdown,*.md}]
+ij_markdown_force_one_space_after_blockquote_symbol = true
+ij_markdown_force_one_space_after_header_symbol = true
+ij_markdown_force_one_space_after_list_bullet = true
+ij_markdown_force_one_space_between_words = true
+ij_markdown_format_tables = true
+ij_markdown_insert_quote_arrows_on_wrap = true
+ij_markdown_keep_indents_on_empty_lines = false
+ij_markdown_keep_line_breaks_inside_text_blocks = true
+ij_markdown_max_lines_around_block_elements = 1
+ij_markdown_max_lines_around_header = 1
+ij_markdown_max_lines_between_paragraphs = 1
+ij_markdown_min_lines_around_block_elements = 1
+ij_markdown_min_lines_around_header = 1
+ij_markdown_min_lines_between_paragraphs = 1
+ij_markdown_wrap_text_if_long = true
+ij_markdown_wrap_text_inside_blockquotes = true
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..bffb357
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..78b977c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+.next
+.env.local
+.idea
+package-lock.json
\ No newline at end of file
diff --git a/example.env.local b/example.env.local
new file mode 100644
index 0000000..078bd0d
--- /dev/null
+++ b/example.env.local
@@ -0,0 +1,11 @@
+NEXT_PUBLIC_API_NAME = "CRM Dashboard"
+NEXT_PUBLIC_API_VERSION = "0.0.1"
+NEXT_PUBLIC_DEFAULT_LANGUAGE = "fa"
+NEXT_PUBLIC_DEFAULT_DIRECTION = "rtl"
+
+NEXT_PUBLIC_PRIMARY_MAIN = "#1b4332"
+NEXT_PUBLIC_SECONDARY_MAIN = "#800e13"
+
+NEXT_PUBLIC_BASE_URL = "https://crm.witel.ir"
+
+NODE_ENV = "development"
\ No newline at end of file
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..fcd02aa
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "paths": {
+ "@/*": [
+ "./src/*"
+ ],
+ "&/*": [
+ "./public/*"
+ ]
+ }
+ }
+}
diff --git a/next.config.js b/next.config.js
new file mode 100644
index 0000000..a288e34
--- /dev/null
+++ b/next.config.js
@@ -0,0 +1,12 @@
+/** @type {import('next').NextConfig} */
+
+const nextConfig = {
+ reactStrictMode: true,
+ i18n: {
+ defaultLocale: 'fa',
+ locales: ['fa'],
+ localeDetection: false,
+ }
+}
+
+module.exports = nextConfig
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..7c15808
--- /dev/null
+++ b/package.json
@@ -0,0 +1,54 @@
+{
+ "name": "dashboard",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "publish-3002": "next start -p 3002",
+ "lint": "next lint",
+ "update": "run-script-os",
+ "update:win32": "echo 'ok'",
+ "update:linux": "rm -rf .next/ && npm i && npm run build && sudo systemctl restart crm.service"
+ },
+ "dependencies": {
+ "@emotion/react": "^11.10.6",
+ "@emotion/server": "^11.10.0",
+ "@emotion/styled": "^11.10.6",
+ "@mui/icons-material": "^5.11.16",
+ "@mui/material": "^5.12.0",
+ "@mui/x-date-pickers": "^6.9.2",
+ "axios": "^1.4.0",
+ "colord": "^2.9.3",
+ "date-fns-jalali": "^2.13.0-0",
+ "dayjs": "^1.11.9",
+ "eslint": "8.36.0",
+ "formik": "^2.2.9",
+ "fs-extra": "^11.1.1",
+ "image-resize": "^1.3.2",
+ "image-to-file-converter": "^1.0.6",
+ "jalali-moment": "^3.3.11",
+ "material-react-table": "^1.11.1",
+ "moment": "^2.29.4",
+ "moment-jalaali": "^0.10.0",
+ "next": "^13.3.0",
+ "next-intl": "^2.13.1",
+ "next-useragent": "^2.8.0",
+ "nextjs-progressbar": "^0.0.16",
+ "prop-types": "^15.8.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-toastify": "^9.1.3",
+ "sass": "^1.62.0",
+ "stylis": "^4.1.3",
+ "stylis-plugin-rtl": "^2.1.1",
+ "swr": "^2.1.5",
+ "yup": "^1.1.1"
+ },
+ "devDependencies": {
+ "@faker-js/faker": "^7.6.0",
+ "eslint-config-next": "^13.3.0",
+ "run-script-os": "^1.1.6"
+ }
+}
diff --git a/public/fontiran.scss b/public/fontiran.scss
new file mode 100644
index 0000000..86d585d
--- /dev/null
+++ b/public/fontiran.scss
@@ -0,0 +1,169 @@
+/**
+*
+* Name: IRAN Sans-Serif Font
+* Version: 5.0
+* Author: Moslem Ebrahimi (moslemebrahimi.com)
+* Created on: Dec 25, 2012
+* Updated on: Sep 01, 2017
+* Website: http://fontiran.com
+* Copyright: Commercial/Proprietary Software
+--------------------------------------------------------------------------------------
+فونت های ایران سن سریف یک نرم افزار مالکیتی محسوب می شود. جهت آگاهی از قوانین استفاده از این فونت ها لطفا به وب سایت (فونت ایران دات کام) مراجعه نمایید
+--------------------------------------------------------------------------------------
+IRAN Sans-serif fonts are considered a proprietary software. To gain information about the laws regarding the use of these fonts, please visit www.fontiran.com
+--------------------------------------------------------------------------------------
+This set of fonts are used in this project under the license: (.....)
+--------------------------------------------------------------------------------------
+*
+**/
+@font-face {
+ font-family: IRANSansFaNum;
+ font-style: normal;
+ font-weight: 900;
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_Black.eot');
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_Black.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb(FaNum)_Black.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb(FaNum)_Black.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb(FaNum)_Black.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSansFaNum;
+ font-style: normal;
+ font-weight: bold;
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_Bold.eot');
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb(FaNum)_Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSansFaNum;
+ font-style: normal;
+ font-weight: 500;
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_Medium.eot');
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_Medium.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb(FaNum)_Medium.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSansFaNum;
+ font-style: normal;
+ font-weight: 300;
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_Light.eot');
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_Light.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb(FaNum)_Light.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb(FaNum)_Light.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb(FaNum)_Light.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSansFaNum;
+ font-style: normal;
+ font-weight: 200;
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot');
+ src: url('./fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSansFaNum;
+ font-style: normal;
+ font-weight: normal;
+ src: url('./fonts/eot/IRANSansWeb(FaNum).eot');
+ src: url('./fonts/eot/IRANSansWeb(FaNum).eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb(FaNum).woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb(FaNum).woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb(FaNum).ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSans;
+ font-style: normal;
+ font-weight: 900;
+ src: url('./fonts/eot/IRANSansWeb_Black.eot');
+ src: url('./fonts/eot/IRANSansWeb_Black.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb_Black.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb_Black.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb_Black.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSans;
+ font-style: normal;
+ font-weight: bold;
+ src: url('./fonts/eot/IRANSansWeb_Bold.eot');
+ src: url('./fonts/eot/IRANSansWeb_Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb_Bold.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb_Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb_Bold.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSans;
+ font-style: normal;
+ font-weight: 500;
+ src: url('./fonts/eot/IRANSansWeb_Medium.eot');
+ src: url('./fonts/eot/IRANSansWeb_Medium.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb_Medium.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb_Medium.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb_Medium.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSans;
+ font-style: normal;
+ font-weight: 300;
+ src: url('./fonts/eot/IRANSansWeb_Light.eot');
+ src: url('./fonts/eot/IRANSansWeb_Light.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb_Light.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb_Light.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb_Light.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSans;
+ font-style: normal;
+ font-weight: 200;
+ src: url('./fonts/eot/IRANSansWeb_UltraLight.eot');
+ src: url('./fonts/eot/IRANSansWeb_UltraLight.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb_UltraLight.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb_UltraLight.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb_UltraLight.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: IRANSans;
+ font-style: normal;
+ font-weight: normal;
+ src: url('./fonts/eot/IRANSansWeb.eot');
+ src: url('./fonts/eot/IRANSansWeb.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff2/IRANSansWeb.woff2') format('woff2'), /* FF39+,Chrome36+, Opera24+*/
+ url('./fonts/woff/IRANSansWeb.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/IRANSansWeb.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: Parastoo;
+ font-style: normal;
+ font-weight: normal;
+ src: url('./fonts/eot/Parastoo.eot');
+ src: url('./fonts/eot/Parastoo.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff/Parastoo.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/Parastoo.ttf') format('truetype');
+}
+
+@font-face {
+ font-family: Parastoo;
+ font-style: normal;
+ font-weight: bold;
+ src: url('./fonts/eot/Parastoo-Bold.eot');
+ src: url('./fonts/eot/Parastoo-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-8 */
+ url('./fonts/woff/Parastoo-Bold.woff') format('woff'), /* FF3.6+, IE9, Chrome6+, Saf5.1+*/
+ url('./fonts/ttf/Parastoo-Bold.ttf') format('truetype');
+}
diff --git a/public/fonts/eot/IRANSansWeb(FaNum).eot b/public/fonts/eot/IRANSansWeb(FaNum).eot
new file mode 100644
index 0000000..5a6fdd0
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum).eot differ
diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_Black.eot b/public/fonts/eot/IRANSansWeb(FaNum)_Black.eot
new file mode 100644
index 0000000..a012bc6
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_Black.eot differ
diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_Bold.eot b/public/fonts/eot/IRANSansWeb(FaNum)_Bold.eot
new file mode 100644
index 0000000..82525e5
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_Bold.eot differ
diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_Light.eot b/public/fonts/eot/IRANSansWeb(FaNum)_Light.eot
new file mode 100644
index 0000000..f899008
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_Light.eot differ
diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_Medium.eot b/public/fonts/eot/IRANSansWeb(FaNum)_Medium.eot
new file mode 100644
index 0000000..117c2c8
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_Medium.eot differ
diff --git a/public/fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot b/public/fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot
new file mode 100644
index 0000000..9cd9b66
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb(FaNum)_UltraLight.eot differ
diff --git a/public/fonts/eot/IRANSansWeb.eot b/public/fonts/eot/IRANSansWeb.eot
new file mode 100644
index 0000000..553eec8
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb.eot differ
diff --git a/public/fonts/eot/IRANSansWeb_Black.eot b/public/fonts/eot/IRANSansWeb_Black.eot
new file mode 100644
index 0000000..ee7c6a0
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_Black.eot differ
diff --git a/public/fonts/eot/IRANSansWeb_Bold.eot b/public/fonts/eot/IRANSansWeb_Bold.eot
new file mode 100644
index 0000000..746a6e6
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_Bold.eot differ
diff --git a/public/fonts/eot/IRANSansWeb_Light.eot b/public/fonts/eot/IRANSansWeb_Light.eot
new file mode 100644
index 0000000..f79a681
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_Light.eot differ
diff --git a/public/fonts/eot/IRANSansWeb_Medium.eot b/public/fonts/eot/IRANSansWeb_Medium.eot
new file mode 100644
index 0000000..8b31d6b
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_Medium.eot differ
diff --git a/public/fonts/eot/IRANSansWeb_UltraLight.eot b/public/fonts/eot/IRANSansWeb_UltraLight.eot
new file mode 100644
index 0000000..fd4b205
Binary files /dev/null and b/public/fonts/eot/IRANSansWeb_UltraLight.eot differ
diff --git a/public/fonts/eot/Parastoo-Bold.eot b/public/fonts/eot/Parastoo-Bold.eot
new file mode 100644
index 0000000..4dc70ba
Binary files /dev/null and b/public/fonts/eot/Parastoo-Bold.eot differ
diff --git a/public/fonts/eot/Parastoo.eot b/public/fonts/eot/Parastoo.eot
new file mode 100644
index 0000000..fd15eda
Binary files /dev/null and b/public/fonts/eot/Parastoo.eot differ
diff --git a/public/fonts/ttf/IRANSansWeb(FaNum).ttf b/public/fonts/ttf/IRANSansWeb(FaNum).ttf
new file mode 100644
index 0000000..cd74265
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum).ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_Black.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_Black.ttf
new file mode 100644
index 0000000..0f84dbf
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_Black.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf
new file mode 100644
index 0000000..6fa2412
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_Bold.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_Light.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_Light.ttf
new file mode 100644
index 0000000..d9000c9
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_Light.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf
new file mode 100644
index 0000000..e20001c
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_Medium.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf b/public/fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf
new file mode 100644
index 0000000..0072606
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb(FaNum)_UltraLight.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb.ttf b/public/fonts/ttf/IRANSansWeb.ttf
new file mode 100644
index 0000000..7416c53
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb_Black.ttf b/public/fonts/ttf/IRANSansWeb_Black.ttf
new file mode 100644
index 0000000..df4928f
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_Black.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb_Bold.ttf b/public/fonts/ttf/IRANSansWeb_Bold.ttf
new file mode 100644
index 0000000..158fbaa
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_Bold.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb_Light.ttf b/public/fonts/ttf/IRANSansWeb_Light.ttf
new file mode 100644
index 0000000..3060c60
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_Light.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb_Medium.ttf b/public/fonts/ttf/IRANSansWeb_Medium.ttf
new file mode 100644
index 0000000..6dd6c5d
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_Medium.ttf differ
diff --git a/public/fonts/ttf/IRANSansWeb_UltraLight.ttf b/public/fonts/ttf/IRANSansWeb_UltraLight.ttf
new file mode 100644
index 0000000..7ebe1bf
Binary files /dev/null and b/public/fonts/ttf/IRANSansWeb_UltraLight.ttf differ
diff --git a/public/fonts/ttf/Parastoo-Bold.ttf b/public/fonts/ttf/Parastoo-Bold.ttf
new file mode 100644
index 0000000..5ef27c9
Binary files /dev/null and b/public/fonts/ttf/Parastoo-Bold.ttf differ
diff --git a/public/fonts/ttf/Parastoo.ttf b/public/fonts/ttf/Parastoo.ttf
new file mode 100644
index 0000000..f52889c
Binary files /dev/null and b/public/fonts/ttf/Parastoo.ttf differ
diff --git a/public/fonts/woff/IRANSansWeb(FaNum).woff b/public/fonts/woff/IRANSansWeb(FaNum).woff
new file mode 100644
index 0000000..e34dfc4
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum).woff differ
diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_Black.woff b/public/fonts/woff/IRANSansWeb(FaNum)_Black.woff
new file mode 100644
index 0000000..bbbca77
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_Black.woff differ
diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_Bold.woff b/public/fonts/woff/IRANSansWeb(FaNum)_Bold.woff
new file mode 100644
index 0000000..05d933e
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_Bold.woff differ
diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_Light.woff b/public/fonts/woff/IRANSansWeb(FaNum)_Light.woff
new file mode 100644
index 0000000..fe80d47
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_Light.woff differ
diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_Medium.woff b/public/fonts/woff/IRANSansWeb(FaNum)_Medium.woff
new file mode 100644
index 0000000..f13a870
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_Medium.woff differ
diff --git a/public/fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff b/public/fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff
new file mode 100644
index 0000000..9c653a4
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb(FaNum)_UltraLight.woff differ
diff --git a/public/fonts/woff/IRANSansWeb.woff b/public/fonts/woff/IRANSansWeb.woff
new file mode 100644
index 0000000..abb63f2
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb.woff differ
diff --git a/public/fonts/woff/IRANSansWeb_Black.woff b/public/fonts/woff/IRANSansWeb_Black.woff
new file mode 100644
index 0000000..12c8a6e
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_Black.woff differ
diff --git a/public/fonts/woff/IRANSansWeb_Bold.woff b/public/fonts/woff/IRANSansWeb_Bold.woff
new file mode 100644
index 0000000..801c55d
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_Bold.woff differ
diff --git a/public/fonts/woff/IRANSansWeb_Light.woff b/public/fonts/woff/IRANSansWeb_Light.woff
new file mode 100644
index 0000000..99b8d5d
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_Light.woff differ
diff --git a/public/fonts/woff/IRANSansWeb_Medium.woff b/public/fonts/woff/IRANSansWeb_Medium.woff
new file mode 100644
index 0000000..322c396
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_Medium.woff differ
diff --git a/public/fonts/woff/IRANSansWeb_UltraLight.woff b/public/fonts/woff/IRANSansWeb_UltraLight.woff
new file mode 100644
index 0000000..31c21ca
Binary files /dev/null and b/public/fonts/woff/IRANSansWeb_UltraLight.woff differ
diff --git a/public/fonts/woff/Parastoo-Bold.woff b/public/fonts/woff/Parastoo-Bold.woff
new file mode 100644
index 0000000..c6c05ff
Binary files /dev/null and b/public/fonts/woff/Parastoo-Bold.woff differ
diff --git a/public/fonts/woff/Parastoo.woff b/public/fonts/woff/Parastoo.woff
new file mode 100644
index 0000000..e5efc4d
Binary files /dev/null and b/public/fonts/woff/Parastoo.woff differ
diff --git a/public/fonts/woff2/IRANSansWeb(FaNum).woff2 b/public/fonts/woff2/IRANSansWeb(FaNum).woff2
new file mode 100644
index 0000000..30a377d
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum).woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_Black.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_Black.woff2
new file mode 100644
index 0000000..0b15441
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_Black.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2
new file mode 100644
index 0000000..f2353b2
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_Bold.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_Light.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_Light.woff2
new file mode 100644
index 0000000..562d663
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_Light.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2
new file mode 100644
index 0000000..a69f4d0
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_Medium.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2 b/public/fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2
new file mode 100644
index 0000000..1114f71
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb(FaNum)_UltraLight.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb.woff2 b/public/fonts/woff2/IRANSansWeb.woff2
new file mode 100644
index 0000000..21bbba5
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb_Black.woff2 b/public/fonts/woff2/IRANSansWeb_Black.woff2
new file mode 100644
index 0000000..da34aad
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_Black.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb_Bold.woff2 b/public/fonts/woff2/IRANSansWeb_Bold.woff2
new file mode 100644
index 0000000..dd1436c
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_Bold.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb_Light.woff2 b/public/fonts/woff2/IRANSansWeb_Light.woff2
new file mode 100644
index 0000000..16dc9f1
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_Light.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb_Medium.woff2 b/public/fonts/woff2/IRANSansWeb_Medium.woff2
new file mode 100644
index 0000000..5fb39d4
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_Medium.woff2 differ
diff --git a/public/fonts/woff2/IRANSansWeb_UltraLight.woff2 b/public/fonts/woff2/IRANSansWeb_UltraLight.woff2
new file mode 100644
index 0000000..eeeec11
Binary files /dev/null and b/public/fonts/woff2/IRANSansWeb_UltraLight.woff2 differ
diff --git a/public/icons/favicon.png b/public/icons/favicon.png
new file mode 100644
index 0000000..c82bf9a
Binary files /dev/null and b/public/icons/favicon.png differ
diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json
new file mode 100644
index 0000000..02db7a1
--- /dev/null
+++ b/public/locales/fa/app.json
@@ -0,0 +1,120 @@
+{
+ "app_name": "سامانه CRM",
+ "app_short_name": "سامانه CRM",
+ "dashboard": "داشبورد",
+ "first_page": "خوش آمدید",
+ "login": "ورود",
+ "login_expert": "ورود کارشناس",
+ "expert": "کارشناس",
+ "last_updated_at": "اخرین بروز رسانی",
+ "filter_mode": "حالت فیلتر",
+ "date_picker_label": "فیلتر بر اساس تاریخ",
+ "lessThan": "کوچکتر از",
+ "greaterThan": "بزرگتر از",
+ "between": "میان",
+ "online_message": "شما به اینترنت وصل هستید",
+ "offline_message": "اتصال شما به اینترنت قطع شده است",
+ "header": {
+ "open_profile": "پروفایل",
+ "edit_profile": " پروفایل",
+ "change_password": "تغییر رمز عبور",
+ "logout": "خروج"
+ },
+ "Titles": {
+ "title_login_page": "صفحه ورود",
+ "title_login_expert_page": "ورود کارشناس",
+ "title_custom_404": "صفحه یافت نشد",
+ "title_custom_403": "عدم دسترسی",
+ "title_custom_500": "خطای سرور"
+ },
+ "sidebar": {
+ "dashboard": "داشبورد",
+ "change-password": "تغییر رمز عبور",
+ "edit-profile": "ویرایش پروفایل",
+ "admin": "مدیریت"
+ },
+ "secondary": {
+ },
+ "Authorization": {
+ "typography_your_login_is_valid_and_you_do_not_need_to_login_again": "شما دسترسی لازم به این صفحه را دارید نیاز به ورود مجدد نیست.",
+ "typography_your_access_to_this_page_has_expired_Please_login_again": "دسترسی شما منقضی شده است لطفا دوباره ورود نمایید.",
+ "typography_redirect_to": "درحال رفتن به صفحه",
+ "typography_routing_previuos_page": "صفحه قبل...",
+ "typography_routing_dashbaord_page": "داشبورد..."
+ },
+ "Permission": {
+ "typography_you_dont_have_access": "شما دسترسی لازم برای مشاهده این صفحه را ندارید!",
+ "button_back_dashboard": "بازگشت به صفحه اصلی"
+ },
+ "LoginPage": {
+ "text_field_user_name": "نام کاربری",
+ "text_field_enter_your_username": "نام کاربری خود را وارد کنید",
+ "text_field_password": "رمز عبور",
+ "text_field_enter_your_password": "رمز عبور خود را وارد کنید",
+ "button_submit": "ورود",
+ "link_routing_back_to": "بازگشت به",
+ "link_routing_previuos_page": "صفحه قبل",
+ "link_routing_main_page": "صفحه اصلی",
+ "error_message_required": "اجباری!"
+ },
+ "ErrorPage": {
+ "custom_404": "صفحه مورد نظر یافت نشد | 404",
+ "custom_500": "دسترسی به سامانه امکانپذیر نمی باشد دقایقی دیگر تلاش نمائید | 500",
+ "custom_403": "شما دسترسی به این بخش را ندارید | 403",
+ "link_routing_back_to": "بازگشت به",
+ "link_routing_previuos_page": "صفحه قبل",
+ "link_routing_main_page": "صفحه اصلی"
+ },
+ "Dashboard": {
+ "dashboard_page": "داشبورد",
+ "change_password": "تغییر رمز عبور",
+ "edit_profile": "ویرایش پروفایل"
+ },
+ "MuiDatePicker": {
+ "date_picker_birthday": "تاریخ"
+ },
+ "ChangePassword": {
+ "typography_change_password": "تغییر رمز عبور",
+ "label_current_password": "رمز عبور فعلی",
+ "label_new_password": "رمز عبور جدید",
+ "label_confirm_password": "تکرار رمز عبور جدید",
+ "error_message_required": "اجباری !",
+ "error_message_password_length": "رمز عبور باید حداقل 8 کاراکتر باشد.",
+ "error_message_password_match": "پسورد ها یکسان هستند",
+ "error_message_password_not_match": "پسورد و تکرار رمز عبور باید یکسان باشد"
+ },
+ "SubmitButton": {
+ "button_while_submit": "در حال ثبت",
+ "button_submit": "ثبت"
+ },
+ "UpdateProfile": {
+ "error_invalid_email": "ایمیل نامعتبر است",
+ "typography_edit_profile": "پروفایل",
+ "text_field_email": "ایمیل",
+ "text_field_enter_your_password": "رمز عبور خود را وارد کنید",
+ "text_field_username": "نام کاربری",
+ "text_field_name": "نام",
+ "text_field_phone_number": "شماره تماس",
+ "text_field_position": "سمت",
+ "text_field_province_name": "نام استان",
+ "error_message_required": "اجباری !"
+ },
+ "notifications": {
+ "code": "کد",
+ "error": "خطا",
+ "warning": "خطا",
+ "success": "موفق",
+ "error_static_text": "عملیات شما با خطا مواجه شد",
+ "warning_static_text": "خطا سرور",
+ "success_static_text": "عملیات شما با موفقیت انجام شد",
+ "pending": "در حال انجام..."
+ },
+ "UploadSystem": {
+ "upload_file": "فایل خود را بارگذاری کنید",
+ "optional": " (اختیاری)",
+ "upload_file_unit": "حداکثر 2Mb",
+ "upload_file_format": "فرمت قابل قبول : png,jpg,pdf",
+ "delete": "پاک کردن",
+ "uploadfile_error": "حجم فایل بیشتر از 2 مگابایت می باشد"
+ }
+}
diff --git a/public/locales/fa/datatable.js b/public/locales/fa/datatable.js
new file mode 100644
index 0000000..fd43663
--- /dev/null
+++ b/public/locales/fa/datatable.js
@@ -0,0 +1,92 @@
+export const FA_DATATABLE_LOCALIZATION = {
+ actions: "عملیات",
+ and: "و",
+ cancel: "لغو",
+ changeFilterMode: "تغییر حالت فیلتر",
+ changeSearchMode: "تغییر حالت جستجو",
+ clearFilter: "پاک کردن فیلتر",
+ clearSearch: "پاک کردن جستجو",
+ clearSort: "پاک کردن مرتب سازی",
+ clickToCopy: "کلیک برای کپی",
+ collapse: "جمع شدن",
+ collapseAll: "جمع شدن همه",
+ columnActions: "عملیات ستون",
+ copiedToClipboard: "کپی شد",
+ dropToGroupBy: "رها کردن برای گروه بندی بر اساس {column}",
+ edit: "ویرایش",
+ expand: "باز شدن",
+ expandAll: "باز شدن همه",
+ filterArrIncludes: "شامل",
+ filterArrIncludesAll: "شامل همه",
+ filterArrIncludesSome: "شامل",
+ filterBetween: "میان",
+ filterBetweenInclusive: "میان با احتساب هر دو",
+ filterByColumn: "فیلتر بر اساس {column}",
+ filterContains: "شامل",
+ filterEmpty: "خالی",
+ filterEndsWith: "به پایان میرسد با",
+ filterEquals: "برابر",
+ filterEqualsString: "برابر",
+ filterFuzzy: "نزدیک",
+ filterGreaterThan: "بزرگتر از",
+ filterGreaterThanOrEqualTo: "بزرگتر یا مساوی",
+ filterInNumberRange: "میان",
+ filterIncludesString: "شامل",
+ filterIncludesStringSensitive: "شامل",
+ filterLessThan: "کوچکتر از",
+ filterLessThanOrEqualTo: "کوچکتر یا مساوی",
+ filterMode: "حالت فیلتر: {filterType}",
+ filterNotEmpty: "غیر خالی",
+ filterNotEquals: "نا برابر",
+ filterStartsWith: "شروع میشود با",
+ filterWeakEquals: "برابر",
+ filteringByColumn: "فیلتر بر اساس {column} - {filterType} {filterValue}",
+ goToFirstPage: "رفتن به صفحه اول",
+ goToLastPage: "رفتن به صفحه آخر",
+ goToNextPage: "رفتن به صفحه بعدی",
+ goToPreviousPage: "رفتن به صفحه قبلی",
+ grab: "گرفتن",
+ groupByColumn: "گروه بندی بر اساس {column}",
+ groupedBy: "گروه بندی شده بر اساس",
+ hideAll: "پنهان کردن همه",
+ hideColumn: "پنهان کردن ستون {column}",
+ max: "حداکثر",
+ min: "حداقل",
+ move: "انتقال",
+ noRecordsToDisplay: "هیچ رکوردی برای نمایش وجود ندارد",
+ noResultsFound: "نتیجهای یافت نشد",
+ of: "از",
+ or: "یا",
+ pinToLeft: "چسباندن به سمت چپ",
+ pinToRight: "چسباندن به سمت راست",
+ resetColumnSize: "بازنشانی اندازه ستون",
+ resetOrder: "بازنشانی ترتیب",
+ rowActions: "عملیات ردیف",
+ rowNumber: "#",
+ rowNumbers: "شماره ردیف",
+ rowsPerPage: "ردیف در هر صفحه",
+ save: "ذخیره",
+ search: "جستجو",
+ selectedCountOfRowCountRowsSelected:
+ "{selectedCount} از {rowCount} ردیف انتخاب شده",
+ select: "انتخاب",
+ showAll: "نمایش همه",
+ showAllColumns: "نمایش همه ستونها",
+ showHideColumns: "نمایش/مخفی کردن ستونها",
+ showHideFilters: "نمایش/مخفی کردن فیلترها",
+ showHideSearch: "نمایش/مخفی کردن جستجو",
+ sortByColumnAsc: "مرتب سازی بر اساس {column} صعودی",
+ sortByColumnDesc: "مرتب سازی بر اساس {column} نزولی",
+ sortedByColumnAsc: "مرتب شده بر اساس {column} صعودی",
+ sortedByColumnDesc: "مرتب شده بر اساس {column} نزولی",
+ thenBy: "، سپس بر اساس ",
+ toggleDensity: "تغییر تراکم",
+ toggleFullScreen: "تغییر حالت تمام صفحه",
+ toggleSelectAll: "انتخاب/عدم انتخاب همه",
+ toggleSelectRow: "انتخاب/عدم انتخاب ردیف",
+ toggleVisibility: "تغییر پیدا/پنهان",
+ ungroupByColumn: "لغو گروه بندی بر اساس {column}",
+ unpin: "رها کردن",
+ unpinAll: "رها کردن همه",
+ unsorted: "بدون مرتب سازی",
+};
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 0000000..9246992
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,57 @@
+{
+ "short_name": "CRM Dashboard",
+ "name": "CRM Expert Dashboard",
+ "description": "CRM",
+ "version": "1.0.0",
+ "version_name": "1.0.0 beta",
+ "start_url": "/dashboard",
+ "orientation": "portrait",
+ "display": "standalone",
+ "theme_color": "#084070",
+ "background_color": "#084070",
+ "icons": [
+ {
+ "src": "/icons/maskable_icon.png",
+ "sizes": "any",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/maskable_icon_x48.png",
+ "sizes": "48x48",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/maskable_icon_x72.png",
+ "sizes": "72x72",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/maskable_icon_x96.png",
+ "sizes": "96x96",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/maskable_icon_x128.png",
+ "sizes": "128x128",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/maskable_icon_x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/maskable_icon_x384.png",
+ "sizes": "384x384",
+ "type": "image/png"
+ },
+ {
+ "src": "/icons/maskable_icon_x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "categories": [
+ "government"
+ ]
+}
diff --git a/src/components/dashboard/change-password/index.jsx b/src/components/dashboard/change-password/index.jsx
new file mode 100644
index 0000000..ae74de6
--- /dev/null
+++ b/src/components/dashboard/change-password/index.jsx
@@ -0,0 +1,149 @@
+import CenterLayout from "@/layouts/CenterLayout";
+import DashboardLayouts from "@/layouts/dashboardLayouts";
+import {Button, Container, Paper, Stack, Typography,} from "@mui/material";
+import {Formik} from "formik";
+import * as Yup from "yup";
+import {useTranslations} from "next-intl";
+import PasswordField from "@/core/components/PasswordField";
+import StyledForm from "@/core/components/StyledForm";
+import {SET_USER_PASSWORD} from "@/core/data/apiRoutes";
+import SvgChangePassword from "@/core/components/svgs/SvgChangePassword";
+import useRequest from "@/lib/app/hooks/useRequest";
+
+const DashboardChangePasswordComponent = () => {
+ const t = useTranslations();
+ const requestServer = useRequest({auth: true})
+
+ const handleSubmit = (values, {setSubmitting, resetForm}) => {
+ requestServer(SET_USER_PASSWORD, 'post', {
+ data: {
+ current_password: values.current_password,
+ new_password: values.new_password,
+ new_password_confirmation: values.new_password_confirmation,
+ },
+ }).then((response) => {
+ resetForm();
+ }).catch(() => {
+ }).finally(() => {
+ setSubmitting(false);
+ });
+ };
+ const initialValues = {
+ current_password: "",
+ new_password: "",
+ new_password_confirmation: "",
+ };
+ const validationSchema = Yup.object().shape({
+ current_password: Yup.string().required(
+ t("ChangePassword.error_message_required")
+ ),
+ new_password: Yup.string()
+ .min(8, t("ChangePassword.error_message_password_length"))
+ .required(t("ChangePassword.error_message_required")),
+ new_password_confirmation: Yup.string()
+ .required(t("ChangePassword.error_message_required"))
+ .test(
+ t("ChangePassword.error_message_password_match"),
+ t("ChangePassword.error_message_password_not_match"),
+ function (value) {
+ return this.parent.new_password === value;
+ }
+ ),
+ });
+
+ return (
+
+
+
+
+ {(props) => (
+ {
+ e.preventDefault();
+ props.handleSubmit();
+ }}
+ >
+
+
+
+
+ {t("ChangePassword.typography_change_password")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ );
+};
+export default DashboardChangePasswordComponent;
diff --git a/src/components/dashboard/edit-profile/index.jsx b/src/components/dashboard/edit-profile/index.jsx
new file mode 100644
index 0000000..3018aa3
--- /dev/null
+++ b/src/components/dashboard/edit-profile/index.jsx
@@ -0,0 +1,223 @@
+import StyledForm from "@/core/components/StyledForm";
+import CenterLayout from "@/layouts/CenterLayout";
+import DashboardLayouts from "@/layouts/dashboardLayouts";
+import useUser from "@/lib/app/hooks/useUser";
+import {Box, Container, Grid, Paper, Stack, TextField, Typography,} from "@mui/material";
+import * as Yup from "yup";
+import {Field, Formik} from "formik";
+import {useTranslations} from "next-intl";
+import AvatarUpload from "@/core/components/AvatarUpload";
+import axios from "axios";
+import {UPDATE_AVATAR} from "@/core/data/apiRoutes";
+import useDirection from "@/lib/app/hooks/useDirection";
+import Notifications from "@/core/components/notifications";
+import ImageResizer from "@/core/components/ImageConvertor";
+
+const DashboardEditProfile = () => {
+ const t = useTranslations();
+ const {user, token, getUser, changeUser} = useUser();
+ const {directionApp} = useDirection();
+
+ const editAvatar = async (avatar) => {
+ Notifications(t);
+ try {
+ const formData = new FormData();
+
+ if (avatar != null) {
+ var resizedAvatar;
+ resizedAvatar = await ImageResizer(avatar);
+ formData.append("avatar", resizedAvatar);
+ }
+
+ await axios.post(UPDATE_AVATAR, formData, {
+ headers: {
+ authorization: `Bearer ${token}`,
+ "Content-Type": "multipart/form-data",
+ },
+ });
+ } catch (error) {
+ Notifications(t, error.response);
+ throw error;
+ }
+ };
+ const handleSubmit = (values, {setSubmitting}) => {
+
+ };
+ const initialValues = {
+ expert_avatar: null,
+ username: user.username,
+ name: user.name,
+ email: user.email,
+ province_name: user.province_name,
+ position: user.position,
+ };
+ const validationSchema = Yup.object().shape({});
+
+ return (
+
+
+
+
+ {(props) => (
+ {
+ e.preventDefault();
+ props.handleSubmit();
+ }}
+ >
+
+
+
+ {t("UpdateProfile.typography_edit_profile")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default DashboardEditProfile;
diff --git a/src/components/dashboard/first/index.jsx b/src/components/dashboard/first/index.jsx
new file mode 100644
index 0000000..ee4f80a
--- /dev/null
+++ b/src/components/dashboard/first/index.jsx
@@ -0,0 +1,7 @@
+import DashboardLayouts from "@/layouts/dashboardLayouts";
+
+const DashboardFirstComponent = () => {
+ return ;
+};
+
+export default DashboardFirstComponent;
diff --git a/src/components/errors/403.jsx b/src/components/errors/403.jsx
new file mode 100644
index 0000000..ce91443
--- /dev/null
+++ b/src/components/errors/403.jsx
@@ -0,0 +1,37 @@
+import CenterLayout from "@/layouts/CenterLayout";
+import FullPageLayout from "@/layouts/FullPageLayout";
+import {Button, Typography} from "@mui/material";
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import {useTranslations} from "next-intl";
+import TitlePage from "@/core/components/TitlePage";
+import Svg403 from "@/core/components/svgs/Svg403";
+
+const UnAuthorizedComponent = () => {
+ const t = useTranslations();
+
+ return (
+ <>
+
+
+
+
+
+ {t("ErrorPage.custom_403")}
+
+
+
+
+ >
+ );
+};
+
+export default UnAuthorizedComponent;
diff --git a/src/components/errors/404.jsx b/src/components/errors/404.jsx
new file mode 100644
index 0000000..acfd418
--- /dev/null
+++ b/src/components/errors/404.jsx
@@ -0,0 +1,37 @@
+import CenterLayout from "@/layouts/CenterLayout";
+import FullPageLayout from "@/layouts/FullPageLayout";
+import {Button, Typography} from "@mui/material";
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import {useTranslations} from "next-intl";
+import TitlePage from "@/core/components/TitlePage";
+import Svg404 from "@/core/components/svgs/Svg404";
+
+const NotFoundComponent = () => {
+ const t = useTranslations();
+
+ return (
+ <>
+
+
+
+
+
+ {t("ErrorPage.custom_404")}
+
+
+
+
+ >
+ );
+};
+
+export default NotFoundComponent;
diff --git a/src/components/errors/500.jsx b/src/components/errors/500.jsx
new file mode 100644
index 0000000..10e9d12
--- /dev/null
+++ b/src/components/errors/500.jsx
@@ -0,0 +1,48 @@
+import CenterLayout from "@/layouts/CenterLayout";
+import FullPageLayout from "@/layouts/FullPageLayout";
+import {Box, Button, Container, Stack, Typography} from "@mui/material";
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import {useTranslations} from "next-intl";
+import Image from "next/image";
+import TitlePage from "@/core/components/TitlePage";
+
+const ServerErrorComponent = () => {
+ const t = useTranslations();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ {t("ErrorPage.custom_500")}
+
+
+
+
+
+
+ >
+ );
+};
+
+export default ServerErrorComponent;
diff --git a/src/components/first/index.jsx b/src/components/first/index.jsx
new file mode 100644
index 0000000..fade07c
--- /dev/null
+++ b/src/components/first/index.jsx
@@ -0,0 +1,55 @@
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import CenterLayout from "@/layouts/CenterLayout";
+import FullPageLayout from "@/layouts/FullPageLayout";
+import useUser from "@/lib/app/hooks/useUser";
+import {Button, Stack, Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+import SvgDashboard from "@/core/components/svgs/SvgDashboard";
+
+const FirstComponent = () => {
+ const t = useTranslations();
+ const {isAuth} = useUser();
+
+ return (
+
+
+
+
+ {t("app_name")}
+
+ {isAuth ? (
+
+ ) : (
+
+ )}
+
+
+ v{process.env.NEXT_PUBLIC_API_VERSION}
+
+
+ );
+};
+
+export default FirstComponent;
diff --git a/src/components/login-expert/index.jsx b/src/components/login-expert/index.jsx
new file mode 100644
index 0000000..2eaa3bd
--- /dev/null
+++ b/src/components/login-expert/index.jsx
@@ -0,0 +1,153 @@
+import LinkRouting from "@/core/components/LinkRouting";
+import PasswordField from "@/core/components/PasswordField";
+import StyledForm from "@/core/components/StyledForm";
+import {GET_USER_TOKEN} from "@/core/data/apiRoutes";
+import CenterLayout from "@/layouts/CenterLayout";
+import FullPageLayout from "@/layouts/FullPageLayout";
+import useUser from "@/lib/app/hooks/useUser";
+import LoginIcon from "@mui/icons-material/Login";
+import {Box, Button, Container, Paper, Stack, TextField, Typography,} from "@mui/material";
+import {Field, Formik} from "formik";
+import {useTranslations} from "next-intl";
+import {useSearchParams} from "next/navigation";
+import * as Yup from "yup";
+import useDirection from "@/lib/app/hooks/useDirection";
+import SvgLogin from "@/core/components/svgs/SvgLogin";
+import useRequest from "@/lib/app/hooks/useRequest";
+
+const LoginComponent = () => {
+ const t = useTranslations();
+ const {directionApp} = useDirection(); // should delete because we don't have direction anymore
+ const {setToken} = useUser(); // pass token to set token
+ const requestServer = useRequest()
+
+ // getting url query
+ const searchParams = useSearchParams();
+ const backUrlDecodedPath = searchParams.get("back_url");
+
+ //formik properties
+ const handleSubmit = (values, props) => {
+ requestServer(GET_USER_TOKEN, 'post', {
+ data: {
+ username: values.username,
+ password: values.password,
+ },
+ success: {
+ notification: {show: false}
+ }
+ }).then((response) => {
+ setToken(response.data.token)
+ }).catch(() => {
+ props.setSubmitting(false)
+ })
+ };
+ const initialValues = {
+ username: "",
+ password: "",
+ };
+ const validationSchema = Yup.object().shape({
+ username: Yup.string().required(t("LoginPage.error_message_required")),
+ password: Yup.string().required(t("LoginPage.error_message_required")),
+ });
+
+ return (
+
+
+
+
+
+ {(props) => (
+
+
+
+
+
+ {t("login_expert")}
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ {t("LoginPage.link_routing_back_to")}{" "}
+ {backUrlDecodedPath
+ ? t("LoginPage.link_routing_previuos_page")
+ : t("LoginPage.link_routing_main_page")}
+
+
+
+ );
+};
+
+export default LoginComponent;
diff --git a/src/core/components/AvatarUpload.jsx b/src/core/components/AvatarUpload.jsx
new file mode 100644
index 0000000..03a9cba
--- /dev/null
+++ b/src/core/components/AvatarUpload.jsx
@@ -0,0 +1,128 @@
+import {Avatar, Box, TextField} from "@mui/material";
+import {useState} from "react";
+import DeleteIcon from "@mui/icons-material/Delete";
+import AddIcon from "@mui/icons-material/Add";
+
+const AvatarUpload = ({user, setFieldValue, valueAvatar, changeFlag}) => {
+ const [selectedImage, setSelectedImage] = useState(user.expert_avatar);
+ const [isHovered, setIsHovered] = useState(false);
+
+ const handleImageChange = (event) => {
+ const newImage = event.target?.files?.[0];
+ if (newImage) {
+ setSelectedImage(URL.createObjectURL(newImage));
+ setFieldValue(valueAvatar, newImage);
+ setFieldValue(changeFlag, true);
+ } else {
+ setSelectedImage("");
+ setFieldValue(valueAvatar, null);
+ }
+ };
+
+ const handleDeleteImage = () => {
+ setSelectedImage("");
+ setFieldValue(valueAvatar, null);
+ setFieldValue(changeFlag, true);
+ };
+
+ const handleMouseEnter = () => {
+ setIsHovered(true);
+ };
+
+ const handleMouseLeave = () => {
+ setIsHovered(false);
+ };
+
+ return (
+
+
+
+
+ {isHovered && (
+
+ {selectedImage ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
+
+ );
+};
+
+export default AvatarUpload;
diff --git a/src/core/components/DataTable.jsx b/src/core/components/DataTable.jsx
new file mode 100644
index 0000000..b3b0f26
--- /dev/null
+++ b/src/core/components/DataTable.jsx
@@ -0,0 +1,176 @@
+import {useTranslations} from "next-intl";
+import useLanguage from "@/lib/app/hooks/useLanguage";
+import {useEffect, useMemo, useState} from "react";
+import moment from "moment-jalaali";
+import useSWR from "swr";
+import {Typography} from "@mui/material";
+import MaterialReactTable from "material-react-table";
+import useRequest from "@/lib/app/hooks/useRequest";
+
+function DataTable(props) {
+ const requestServer = useRequest({auth: true})
+ const fetcher = (...args) => {
+ return requestServer(args, 'get', {
+ pending: false,
+ success: {notification: {show: false}}
+ }).then((response) => {
+ setRowCount(response.data.meta.totalRowCount);
+ return response.data.data;
+ }).catch(() => {
+ })
+ };
+ const t = useTranslations();
+ const {languageApp, languageList} = useLanguage();
+ const [columnFilters, setColumnFilters] = useState([]);
+ const [sorting, setSorting] = useState([]);
+ const [pagination, setPagination] = useState({pageIndex: 0, pageSize: 10});
+ const [rowCount, setRowCount] = useState(0);
+ const [columnFilterFns, setColumnFilterFns] = useState(() => {
+ let output = {};
+ const list = props.columns.map((item) =>
+ item.enableColumnFilter ? {[item.id]: item.filterFn} : {[item.id]: ""}
+ );
+ for (var key in list) {
+ var nestedObj = list[key];
+ for (var nestedKey in nestedObj) {
+ output[nestedKey] = nestedObj[nestedKey];
+ }
+ }
+ return output;
+ });
+
+ const [updateTime, setupdateTime] = useState(
+ moment().format("HH:mm | jYYYY/jM/jD")
+ );
+
+ const tableLocalization = useMemo(
+ () =>
+ languageList.find((item) => item.key == languageApp).tableLocalization,
+ [languageApp, languageList]
+ );
+
+ const fetchUrl = useMemo(() => {
+ const url = new URL(props.tableUrl);
+ url.searchParams.set(
+ "start",
+ `${pagination.pageIndex * pagination.pageSize}`
+ );
+ const filters = columnFilters.map((filter) => {
+ let datatype;
+ for (const i in props.columns) {
+ if (props.columns[i].id == filter.id) {
+ datatype = props.columns[i].datatype;
+ }
+ }
+ return {
+ ...filter,
+ fn: columnFilterFns[filter.id],
+ datatype: datatype,
+ };
+ });
+ url.searchParams.set("size", pagination.pageSize);
+ url.searchParams.set("filters", JSON.stringify(filters ?? []));
+ url.searchParams.set("sorting", JSON.stringify(sorting ?? []));
+ return url;
+ }, [
+ props.tableUrl,
+ columnFilters,
+ columnFilterFns,
+ pagination,
+ sorting,
+ props.columns,
+ ]);
+
+ const {data, isValidating, mutate} = useSWR(fetchUrl, fetcher, {
+ revalidateIfStale: false,
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false
+ });
+
+ useEffect(() => {
+ setupdateTime(moment().format("HH:mm | jYYYY/jM/jD"));
+ }, [isValidating, languageApp]);
+
+ return (
+ (
+ <>
+ {props.enableCustomToolbar /* send condition */
+ ? props.CustomToolbar /* send component */
+ : ""}
+ >
+ )}
+ renderBottomToolbarCustomActions={({table}) => (
+ <>
+ {props.enableLastUpdate /* send condition */ ? (
+
+ {t("last_updated_at")}: {updateTime}
+
+ ) : (
+ ""
+ )}
+ >
+ )}
+ state={{
+ isLoading: isValidating,
+ columnFilters,
+ columnFilterFns,
+ pagination,
+ sorting,
+ }}
+ positionActionsColumn={"last"}
+ enableRowActions={props.enableRowActions}
+ renderRowActions={({row}) => }
+ {...props}
+ />
+ );
+}
+
+export default DataTable;
\ No newline at end of file
diff --git a/src/core/components/GlobalHead.jsx b/src/core/components/GlobalHead.jsx
new file mode 100644
index 0000000..5d6186d
--- /dev/null
+++ b/src/core/components/GlobalHead.jsx
@@ -0,0 +1,58 @@
+import Head from "next/head";
+
+const GlobalHead = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default GlobalHead
\ No newline at end of file
diff --git a/src/core/components/ImageConvertor.jsx b/src/core/components/ImageConvertor.jsx
new file mode 100644
index 0000000..9f99bc1
--- /dev/null
+++ b/src/core/components/ImageConvertor.jsx
@@ -0,0 +1,22 @@
+import ImageResize from "image-resize";
+
+const ImageResizer = async (image) => {
+ const imageResize = new ImageResize({
+ quality: 1,
+ format: "jpg",
+ outputType: "base64",
+ width: 400,
+ });
+
+ const get = await imageResize.get(image);
+ const resize = await imageResize.resize(get).then();
+ const output = await imageResize.output(resize).then();
+
+ const avatar = await (await import("image-to-file-converter"))
+ .base64ToFile(output)
+ .then();
+
+ return avatar;
+};
+
+export default ImageResizer;
diff --git a/src/core/components/LinkRouting.jsx b/src/core/components/LinkRouting.jsx
new file mode 100644
index 0000000..f8fac0a
--- /dev/null
+++ b/src/core/components/LinkRouting.jsx
@@ -0,0 +1,116 @@
+import MuiLink from "@mui/material/Link";
+import {styled} from "@mui/material/styles";
+import clsx from "clsx";
+import NextLink from "next/link";
+import {useRouter} from "next/router";
+import * as React from "react";
+
+// Add support for the sx prop for consistency with the other branches.
+const Anchor = styled("a")({});
+
+export const NextLinkComposed = React.forwardRef(function NextLinkComposed(
+ props,
+ ref
+) {
+ const {
+ to,
+ linkAs,
+ replace,
+ scroll,
+ shallow,
+ prefetch,
+ legacyBehavior = true,
+ locale,
+ ...other
+ } = props;
+
+ return (
+
+
+
+ );
+});
+
+// A styled version of the Next.js Link component:
+// https://nextjs.org/docs/api-reference/next/link
+const LinkRouting = React.forwardRef(function Link(props, ref) {
+ const {
+ activeClassName = "active",
+ as,
+ className: classNameProps,
+ href,
+ legacyBehavior,
+ linkAs: linkAsProp,
+ locale,
+ noLinkStyle,
+ prefetch,
+ replace,
+ role, // Link don't have roles.
+ scroll,
+ shallow,
+ ...other
+ } = props;
+
+ const router = useRouter();
+ const pathname = typeof href === "string" ? href : href.pathname;
+ const className = clsx(classNameProps, {
+ [activeClassName]: router.pathname === pathname && activeClassName,
+ });
+
+ const isExternal =
+ typeof href === "string" &&
+ (href.indexOf("http") === 0 || href.indexOf("mailto:") === 0);
+
+ if (isExternal) {
+ if (noLinkStyle) {
+ return ;
+ }
+
+ return ;
+ }
+
+ const linkAs = linkAsProp || as;
+ const nextjsProps = {
+ to: href,
+ linkAs,
+ replace,
+ scroll,
+ shallow,
+ prefetch,
+ legacyBehavior,
+ locale,
+ };
+
+ if (noLinkStyle) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+});
+
+export default LinkRouting;
diff --git a/src/core/components/LoadingHardPage.jsx b/src/core/components/LoadingHardPage.jsx
new file mode 100644
index 0000000..c3b24d6
--- /dev/null
+++ b/src/core/components/LoadingHardPage.jsx
@@ -0,0 +1,42 @@
+import {Backdrop, Box, styled} from "@mui/material";
+import SvgLoading from "@/core/components/svgs/SvgLoading";
+
+const LoadingImage = styled(Box)({
+ "@keyframes load": {
+ "0%": {
+ // opacity: 0,
+ transform: "scale(1)",
+ },
+ "50%": {
+ // opacity: 1,
+ transform: "scale(2)",
+ },
+ "100%": {
+ // opacity: 0,
+ transform: "scale(1)",
+ },
+ },
+ animation: "load 2s infinite",
+});
+
+const LoadingHardPage = ({children, loading}) => {
+ return (
+ <>
+ theme.zIndex.drawer + 1}}
+ open={loading}
+ >
+
+
+
+
+ {children}
+ >
+ );
+};
+
+export default LoadingHardPage;
diff --git a/src/core/components/Messages.jsx b/src/core/components/Messages.jsx
new file mode 100644
index 0000000..8a5cfc9
--- /dev/null
+++ b/src/core/components/Messages.jsx
@@ -0,0 +1,18 @@
+import CenterLayout from "@/layouts/CenterLayout";
+import FullPageLayout from "@/layouts/FullPageLayout";
+import SvgLoading from "@/core/components/svgs/SvgLoading";
+
+const Message = ({text, actions}) => {
+ return (
+
+
+
+ {text}
+ {actions}
+
+
+ );
+};
+
+export default Message;
diff --git a/src/core/components/MuiDatePicker.jsx b/src/core/components/MuiDatePicker.jsx
new file mode 100644
index 0000000..18be3cc
--- /dev/null
+++ b/src/core/components/MuiDatePicker.jsx
@@ -0,0 +1,61 @@
+import {LocalizationProvider, MobileDateTimePicker} from "@mui/x-date-pickers";
+import {AdapterDateFnsJalali} from "@mui/x-date-pickers/AdapterDateFnsJalali";
+import moment from "jalali-moment";
+import {faIR} from "@mui/x-date-pickers/locales";
+import {Box, IconButton} from "@mui/material";
+import ClearIcon from "@mui/icons-material/Clear";
+import {useTranslations} from "next-intl";
+
+export default function MuiDatePicker({column}) {
+ const t = useTranslations();
+ const filterFnValue = column.columnDef._filterFn;
+
+ return (
+
+
+ {
+ const date = new Date(newValue);
+ const formattedDate = moment(date)
+ .locale("en")
+ .format("YYYY-MM-DD HH:mm");
+ column.setFilterValue(formattedDate);
+ }}
+ slotProps={{
+ textField: {
+ placeholder: "تاریخ خود را وارد کنید",
+ helperText: `${t("filter_mode")}: ${t(filterFnValue)}`,
+ sx: {minWidth: "120px"},
+ variant: "standard",
+ },
+ }}
+ value={
+ column.getFilterValue()
+ ? new Date(column.getFilterValue())
+ : null
+ }
+ />
+
+ {
+ column.setFilterValue(null);
+ }}
+ sx={{
+ color: column.getFilterValue()
+ ? "rgba(0, 0, 0, 0.54)"
+ : "#bfbfbf",
+ }}
+ >
+
+
+
+ );
+}
diff --git a/src/core/components/NetworkComponent.jsx b/src/core/components/NetworkComponent.jsx
new file mode 100644
index 0000000..8ebbb68
--- /dev/null
+++ b/src/core/components/NetworkComponent.jsx
@@ -0,0 +1,34 @@
+import useNetwork from "@/lib/app/hooks/useNetwork";
+import {useEffect, useRef} from "react";
+import {toast} from "react-toastify";
+import WifiIcon from '@mui/icons-material/Wifi';
+import WifiOffIcon from '@mui/icons-material/WifiOff';
+import {useTranslations} from "next-intl";
+
+const NetworkComponent = () => {
+ const toastId = useRef(null);
+ const network = useNetwork()
+ const t = useTranslations()
+
+ useEffect(() => {
+ if (network.online) {
+ toast.update(toastId.current, {
+ type: toast.TYPE.SUCCESS,
+ render: t('online_message'),
+ autoClose: 2000,
+ closeButton: true,
+ closeOnClick: true,
+ icon:
+ });
+ return
+ }
+ toast.dismiss()
+ toastId.current = toast.warn(t('offline_message'), {
+ autoClose: false, closeButton: false, closeOnClick: false, icon:
+ })
+ }, [network.online]);
+
+ return ''
+}
+
+export default NetworkComponent
\ No newline at end of file
diff --git a/src/core/components/NoSsrHandler.jsx b/src/core/components/NoSsrHandler.jsx
new file mode 100644
index 0000000..5a4c4e1
--- /dev/null
+++ b/src/core/components/NoSsrHandler.jsx
@@ -0,0 +1,8 @@
+import {NoSsr} from "@mui/material";
+
+const NoSsrHandler = ({isBot, children}) => {
+ if (isBot) return children;
+ return {children};
+};
+
+export default NoSsrHandler;
diff --git a/src/core/components/PasswordField.jsx b/src/core/components/PasswordField.jsx
new file mode 100644
index 0000000..d109b73
--- /dev/null
+++ b/src/core/components/PasswordField.jsx
@@ -0,0 +1,33 @@
+import {useState} from "react";
+import {IconButton, InputAdornment, TextField} from "@mui/material";
+import {Visibility, VisibilityOff} from "@mui/icons-material";
+import {Field} from "formik";
+
+const PasswordField = (props) => {
+ const [showPassword, setShowPassword] = useState(false);
+
+ const handleClickShowPassword = () => {
+ setShowPassword(!showPassword);
+ };
+
+ return (
+
+
+ {showPassword ? : }
+
+
+ ),
+ }}
+ />
+ );
+};
+
+export default PasswordField;
diff --git a/src/core/components/PriceField.jsx b/src/core/components/PriceField.jsx
new file mode 100644
index 0000000..29d8763
--- /dev/null
+++ b/src/core/components/PriceField.jsx
@@ -0,0 +1,23 @@
+import {InputAdornment, TextField, Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+
+const PriceField = (props) => {
+ const t = useTranslations();
+ return (
+
+ {((props.value) / 10).toLocaleString()}
+ {t("ConfirmDialog.toman")}
+
+ ),
+ }}
+ {...props}
+ />
+ )
+}
+export default PriceField
\ No newline at end of file
diff --git a/src/core/components/StyledForm.jsx b/src/core/components/StyledForm.jsx
new file mode 100644
index 0000000..41eeabc
--- /dev/null
+++ b/src/core/components/StyledForm.jsx
@@ -0,0 +1,6 @@
+import {styled} from "@mui/material";
+import {Form} from "formik";
+
+const StyledForm = styled(Form)``;
+
+export default StyledForm;
diff --git a/src/core/components/StyledImage.jsx b/src/core/components/StyledImage.jsx
new file mode 100644
index 0000000..58dd65f
--- /dev/null
+++ b/src/core/components/StyledImage.jsx
@@ -0,0 +1,6 @@
+import {styled} from "@mui/material";
+import Image from "next/image";
+
+const StyledImage = styled(Image)``;
+
+export default StyledImage;
diff --git a/src/core/components/TitlePage.jsx b/src/core/components/TitlePage.jsx
new file mode 100644
index 0000000..77c72ee
--- /dev/null
+++ b/src/core/components/TitlePage.jsx
@@ -0,0 +1,15 @@
+import {useTranslations} from "next-intl";
+import Head from "next/head";
+
+const TitlePage = ({text}) => {
+ const t = useTranslations();
+ return (
+
+
+ {text ? `${t("app_short_name")} | ${t(text)}` : t("app_short_name")}
+
+
+ );
+};
+
+export default TitlePage;
diff --git a/src/core/components/UploadSystem.jsx b/src/core/components/UploadSystem.jsx
new file mode 100644
index 0000000..b40fb46
--- /dev/null
+++ b/src/core/components/UploadSystem.jsx
@@ -0,0 +1,175 @@
+import {Box, Button, Paper, Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+import AddIcon from "@mui/icons-material/Add";
+import DeleteForeverIcon from "@mui/icons-material/DeleteForever";
+import {useRef} from "react";
+
+const UploadSystem = ({
+ selectedImage,
+ setselectedImage,
+ handleUploadChange,
+ fieldname,
+ setFieldValue,
+ imageAlt,
+ imageSize,
+ fileType,
+ fileName,
+ setShowAddIcon,
+ showAddIcon,
+ }) => {
+ const t = useTranslations();
+ const fileInputRef = useRef(null);
+
+ const handleClick = () => {
+ fileInputRef.current.click();
+ };
+
+ const handleDeleteImage = () => {
+ setselectedImage(null);
+ setFieldValue(fieldname, null);
+ setShowAddIcon(true);
+ if (fileInputRef.current) {
+ fileInputRef.current.value = "";
+ }
+ };
+
+ const isDocumentFormat = (fileType) => {
+ const documentFormats = [
+ "application/pdf",
+ "application/msword",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "application/vnd.ms-excel",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ ];
+ return documentFormats.includes(fileType);
+ };
+
+ return (
+
+ {showAddIcon ? (
+ // Show the add icon and "Upload File" text when no image is selected
+ <>
+
+
+
+ {t("UploadSystem.upload_file")}
+
+
+ >
+ ) : (
+ // Show the uploaded content along with the delete button when an image or document is selected
+ <>
+ {fileType && fileType.startsWith("image/") ? (
+
+
+
+ ) : (
+ fileType &&
+ isDocumentFormat(fileType) && (
+
+
+ {fileName}
+
+
+ )
+ )}
+
+ >
+ )}
+
+
+ );
+};
+
+export default UploadSystem;
diff --git a/src/core/components/notifications/ErrorNotification.jsx b/src/core/components/notifications/ErrorNotification.jsx
new file mode 100644
index 0000000..4ff1ab0
--- /dev/null
+++ b/src/core/components/notifications/ErrorNotification.jsx
@@ -0,0 +1,39 @@
+import DangerousIcon from "@mui/icons-material/Dangerous";
+import {Box, Typography} from "@mui/material";
+import {toast} from "react-toastify";
+
+const ErrorNotification = (t, status, message) => {
+ toast(
+ () => (
+ <>
+
+
+
+
+
+ {t("notifications.error")} ({t("notifications.code")}: {status})
+
+
+ {message || t("notifications.error_static_text")}
+
+
+
+
+ >
+ ),
+ {
+ autoClose: false,
+ closeOnClick: false,
+ draggable: false,
+ }
+ );
+};
+
+export default ErrorNotification;
diff --git a/src/core/components/notifications/PendingNotification.jsx b/src/core/components/notifications/PendingNotification.jsx
new file mode 100644
index 0000000..846b847
--- /dev/null
+++ b/src/core/components/notifications/PendingNotification.jsx
@@ -0,0 +1,12 @@
+import {toast} from "react-toastify";
+
+const PendingNotification = (t) => {
+ toast(t("notifications.pending"), {
+ autoClose: false,
+ closeButton: false,
+ closeOnClick: false,
+ draggable: false,
+ });
+};
+
+export default PendingNotification;
diff --git a/src/core/components/notifications/SuccessNotification.jsx b/src/core/components/notifications/SuccessNotification.jsx
new file mode 100644
index 0000000..998deaa
--- /dev/null
+++ b/src/core/components/notifications/SuccessNotification.jsx
@@ -0,0 +1,42 @@
+import BeenhereIcon from "@mui/icons-material/Beenhere";
+import {Box, Typography} from "@mui/material";
+import {toast} from "react-toastify";
+
+const SuccessNotification = (t, status) => {
+ toast(
+ () => (
+ <>
+
+
+
+
+
+ {t("notifications.success")} ({t("notifications.code")}:{" "}
+ {status})
+
+
+ {t("notifications.success_static_text")}
+
+
+
+
+ >
+ ),
+ {
+ autoClose: 3000,
+ hideProgressBar: true,
+ pauseOnHover: true,
+ closeOnClick: false,
+ draggable: true,
+ }
+ );
+};
+
+export default SuccessNotification;
diff --git a/src/core/components/notifications/UploadFileNotification.jsx b/src/core/components/notifications/UploadFileNotification.jsx
new file mode 100644
index 0000000..6556944
--- /dev/null
+++ b/src/core/components/notifications/UploadFileNotification.jsx
@@ -0,0 +1,38 @@
+import DangerousIcon from "@mui/icons-material/Dangerous";
+import {Box, Typography} from "@mui/material";
+import {toast} from "react-toastify";
+
+const UploadFileNotification = (t) => {
+ toast(
+ ({closeToast}) => (
+ <>
+
+
+
+
+
+ {t("UploadSystem.uploadfile_error")}
+
+
+
+
+ >
+ ),
+ {
+ autoClose: 3000,
+ hideProgressBar: true,
+ pauseOnHover: true,
+ closeOnClick: false,
+ draggable: true,
+ }
+ );
+};
+
+export default UploadFileNotification;
diff --git a/src/core/components/notifications/WarningNotification.jsx b/src/core/components/notifications/WarningNotification.jsx
new file mode 100644
index 0000000..8493180
--- /dev/null
+++ b/src/core/components/notifications/WarningNotification.jsx
@@ -0,0 +1,40 @@
+import ReportIcon from "@mui/icons-material/Report";
+import {Box, Typography} from "@mui/material";
+import {toast} from "react-toastify";
+
+const WarningNotification = (t, status) => {
+ toast(
+ () => (
+ <>
+
+
+
+
+
+ {t("notifications.warning")} ({t("notifications.code")}:{" "}
+ {status})
+
+
+ {t("notifications.warning_static_text")}
+
+
+
+
+ >
+ ),
+ {
+ autoClose: false,
+ closeOnClick: false,
+ draggable: false,
+ }
+ );
+};
+
+export default WarningNotification;
diff --git a/src/core/components/notifications/index.jsx b/src/core/components/notifications/index.jsx
new file mode 100644
index 0000000..bc8eb66
--- /dev/null
+++ b/src/core/components/notifications/index.jsx
@@ -0,0 +1,54 @@
+import {toast} from "react-toastify";
+import ErrorNotification from "./ErrorNotification";
+import WarningNotification from "./WarningNotification";
+import SuccessNotification from "./SuccessNotification";
+
+const Notifications = async (t, response) => {
+ const {status, data} = response != undefined ? response : ""
+ toast.dismiss();
+ switch (status) {
+ case 200:
+ SuccessNotification(t, status);
+ break;
+ case 400:
+ ErrorNotification(t, status);
+ break;
+ case 401:
+ ErrorNotification(t, status);
+ break;
+ case 403:
+ ErrorNotification(t, status);
+ break;
+ case 422:
+ ErrorNotification(t, status, data.message);
+ break;
+ case 500:
+ WarningNotification(t, status);
+ break;
+ case 503:
+ WarningNotification(t, status);
+ break;
+ case 504:
+ WarningNotification(t, status);
+ break;
+ default:
+ toast(t("notifications.pending"), {
+ autoClose: false,
+ closeOnClick: false,
+ draggable: false,
+ });
+ break;
+ }
+};
+
+export default Notifications;
+
+/*
+usage document
+
+** for pending use ( Notifications( t, undefined) ) this before your request.
+** for success use ( Notifications( t, response) ) this inside .then() of your request.
+** for Error and Warning use ( Notifications( t, error.response) ) this inside .catche() of your request.
+
+end usage document
+*/
diff --git a/src/core/components/svgs/Svg403.jsx b/src/core/components/svgs/Svg403.jsx
new file mode 100644
index 0000000..720828c
--- /dev/null
+++ b/src/core/components/svgs/Svg403.jsx
@@ -0,0 +1,131 @@
+import {useTheme} from "@mui/material";
+
+const Svg403 = ({width, height}) => {
+ const theme = useTheme()
+ const fillColor = theme.palette.primary.main
+
+ return (
+
+ )
+
+}
+
+export default Svg403
\ No newline at end of file
diff --git a/src/core/components/svgs/Svg404.jsx b/src/core/components/svgs/Svg404.jsx
new file mode 100644
index 0000000..e46ab12
--- /dev/null
+++ b/src/core/components/svgs/Svg404.jsx
@@ -0,0 +1,101 @@
+import {useTheme} from "@mui/material";
+
+const Svg404 = ({width, height}) => {
+ const theme = useTheme()
+ const fillColor = theme.palette.primary.main
+
+ return (
+
+ )
+}
+
+export default Svg404
\ No newline at end of file
diff --git a/src/core/components/svgs/SvgChangePassword.jsx b/src/core/components/svgs/SvgChangePassword.jsx
new file mode 100644
index 0000000..a7ee82f
--- /dev/null
+++ b/src/core/components/svgs/SvgChangePassword.jsx
@@ -0,0 +1,77 @@
+import {useTheme} from "@mui/material";
+
+const SvgChangePassword = ({width, height}) => {
+ const theme = useTheme()
+ const fillColor = theme.palette.primary.main
+
+ return (
+
+ )
+}
+
+export default SvgChangePassword
\ No newline at end of file
diff --git a/src/core/components/svgs/SvgDashboard.jsx b/src/core/components/svgs/SvgDashboard.jsx
new file mode 100644
index 0000000..a5b40e0
--- /dev/null
+++ b/src/core/components/svgs/SvgDashboard.jsx
@@ -0,0 +1,117 @@
+import {useTheme} from "@mui/material";
+
+const SvgDashboard = ({width, height}) => {
+ const theme = useTheme()
+ const fillColor = theme.palette.primary.main
+
+ return (
+
+ )
+}
+
+export default SvgDashboard
\ No newline at end of file
diff --git a/src/core/components/svgs/SvgLoading.jsx b/src/core/components/svgs/SvgLoading.jsx
new file mode 100644
index 0000000..f9cea4a
--- /dev/null
+++ b/src/core/components/svgs/SvgLoading.jsx
@@ -0,0 +1,136 @@
+import {useTheme} from "@mui/material";
+
+const SvgLoading = ({width, height}) => {
+ const theme = useTheme()
+ const fillColor = theme.palette.primary.main
+
+ return (
+
+ )
+}
+
+export default SvgLoading
\ No newline at end of file
diff --git a/src/core/components/svgs/SvgLogin.jsx b/src/core/components/svgs/SvgLogin.jsx
new file mode 100644
index 0000000..643c195
--- /dev/null
+++ b/src/core/components/svgs/SvgLogin.jsx
@@ -0,0 +1,75 @@
+import {useTheme} from "@mui/material";
+
+const SvgLogin = ({width, height}) => {
+ const theme = useTheme()
+ const fillColor = theme.palette.primary.main
+
+ return (
+
+ )
+
+}
+
+export default SvgLogin
\ No newline at end of file
diff --git a/src/core/data/apiRoutes.js b/src/core/data/apiRoutes.js
new file mode 100644
index 0000000..06dd4e9
--- /dev/null
+++ b/src/core/data/apiRoutes.js
@@ -0,0 +1,13 @@
+const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
+
+//login
+export const GET_USER_TOKEN = BASE_URL + "/dashboard/login";
+//end login
+
+//change password
+export const SET_USER_PASSWORD = BASE_URL + "/dashboard/profile/change_password";
+//end change password
+
+//user data
+export const GET_USER_ROUTE = BASE_URL + "/dashboard/profile/info";
+//user data
\ No newline at end of file
diff --git a/src/core/data/headerProfileItems.jsx b/src/core/data/headerProfileItems.jsx
new file mode 100644
index 0000000..577fba3
--- /dev/null
+++ b/src/core/data/headerProfileItems.jsx
@@ -0,0 +1,19 @@
+import BorderColorIcon from "@mui/icons-material/BorderColor";
+import PasswordIcon from "@mui/icons-material/Password";
+
+const headerProfileItems = [
+ {
+ key: 0,
+ name: "header.edit_profile",
+ route: "/dashboard/edit-profile",
+ icon: ,
+ },
+ {
+ key: 1,
+ name: "header.change_password",
+ route: "/dashboard/change-password",
+ icon: ,
+ },
+];
+
+export default headerProfileItems;
diff --git a/src/core/data/sidebarMenu.jsx b/src/core/data/sidebarMenu.jsx
new file mode 100644
index 0000000..ca946a5
--- /dev/null
+++ b/src/core/data/sidebarMenu.jsx
@@ -0,0 +1,16 @@
+import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
+
+const sidebarMenu = [
+ [
+ {
+ key: "sidebar.dashboard",
+ type: "page",
+ route: "/dashboard",
+ icon: ,
+ selected: false,
+ permission: "all",
+ },
+ ],
+];
+
+export default sidebarMenu;
diff --git a/src/core/utils/createEmotionCache.jsx b/src/core/utils/createEmotionCache.jsx
new file mode 100644
index 0000000..413fafe
--- /dev/null
+++ b/src/core/utils/createEmotionCache.jsx
@@ -0,0 +1,38 @@
+import createCache from "@emotion/cache";
+import {prefixer} from "stylis";
+import stylisRTLPlugin from "stylis-plugin-rtl";
+
+const isBrowser = typeof document !== "undefined";
+
+export const createEmotionCacheLtr = () => {
+ let insertionPoint;
+
+ if (isBrowser) {
+ const emotionInsertionPoint = document.querySelector(
+ 'meta[name="emotion-insertion-point"]'
+ );
+ insertionPoint = emotionInsertionPoint ?? undefined;
+ }
+
+ return createCache({
+ key: "mui-style",
+ insertionPoint,
+ });
+};
+
+export const createEmotionCacheRtl = () => {
+ let insertionPoint;
+
+ if (isBrowser) {
+ const emotionInsertionPoint = document.querySelector(
+ 'meta[name="emotion-insertion-point"]'
+ );
+ insertionPoint = emotionInsertionPoint ?? undefined;
+ }
+
+ return createCache({
+ key: "muirtl",
+ stylisPlugins: [prefixer, stylisRTLPlugin],
+ insertionPoint,
+ });
+};
diff --git a/src/core/utils/errorHandler.js b/src/core/utils/errorHandler.js
new file mode 100644
index 0000000..9bd18f1
--- /dev/null
+++ b/src/core/utils/errorHandler.js
@@ -0,0 +1,62 @@
+import ErrorNotification from "@/core/components/notifications/ErrorNotification";
+import WarningNotification from "@/core/components/notifications/WarningNotification";
+import {toast} from "react-toastify";
+
+export const errorSetting = (t, notification) => {
+ if (notification) toast.dismiss();
+}
+
+export const errorRequest = (t, notification) => {
+ if (notification) toast.dismiss();
+}
+
+export const errorResponse = (response, clearToken, t, notification) => {
+ if (notification) toast.dismiss();
+ if (isServerError(response.status)) {
+ errorServer(response, t, notification)
+ } else if (isClientError(response.status)) {
+ errorClient(response, clearToken, t, notification)
+ }
+}
+
+const errorServer = (response, t, notification) => {
+ if (notification) WarningNotification(t, response.status);
+}
+const errorClient = (response, clearToken, t, notification) => {
+ switch (response.status) {
+ case 401:
+ clearToken()
+ if (notification) ErrorNotification(t, response.status)
+ break;
+ case 422:
+ if ('type' in response.data) {
+ errorLogic(response, t, notification)
+ break;
+ }
+ errorValidation(response, t, notification)
+ break;
+ case 429:
+ if (notification) ErrorNotification(t, response.status)
+ break
+ default:
+ if (notification) ErrorNotification(t, response.status)
+ break
+ }
+}
+
+const isServerError = status => status >= 500 && status <= 599;
+const isClientError = status => status >= 400 && status <= 499;
+
+const errorLogic = (response, t, notification) => {
+ if (notification) ErrorNotification(t, response.status)
+}
+const errorValidation = (response, t, notification) => {
+ if (notification) {
+ const errorsMap = Object.keys(response.data.errors)
+ const errorsArray = response.data.errors
+
+ errorsMap.map((item, index) => {
+ ErrorNotification(t, response.status, errorsArray[item][0]);
+ })
+ }
+}
\ No newline at end of file
diff --git a/src/core/utils/succesHandler.js b/src/core/utils/succesHandler.js
new file mode 100644
index 0000000..a61ac85
--- /dev/null
+++ b/src/core/utils/succesHandler.js
@@ -0,0 +1,9 @@
+import SuccessNotification from "@/core/components/notifications/SuccessNotification";
+import {toast} from "react-toastify";
+
+export const successRequest = (response, t, options) => {
+ if (options.notification && options.success.notification.show) {
+ toast.dismiss();
+ SuccessNotification(t, response.status)
+ }
+}
\ No newline at end of file
diff --git a/src/core/utils/theme-rtl.jsx b/src/core/utils/theme-rtl.jsx
new file mode 100644
index 0000000..33a9ae7
--- /dev/null
+++ b/src/core/utils/theme-rtl.jsx
@@ -0,0 +1,14 @@
+import {createTheme} from "@mui/material/styles";
+import theme from "./theme";
+import {faIR} from "@mui/x-date-pickers/locales";
+
+const themeRtl = createTheme({
+ direction: "rtl",
+ typography: {
+ fontFamily: `IRANSansFaNum, sans-serif`,
+ },
+ faIR,
+ ...theme,
+});
+
+export default themeRtl;
diff --git a/src/core/utils/theme.jsx b/src/core/utils/theme.jsx
new file mode 100644
index 0000000..672e8a8
--- /dev/null
+++ b/src/core/utils/theme.jsx
@@ -0,0 +1,29 @@
+import {colord} from "colord";
+
+const theme = {
+ palette: {
+ primary: {
+ main: process.env.NEXT_PUBLIC_PRIMARY_MAIN,
+ contrastText: "#FFFFFF",
+ light: colord(process.env.NEXT_PUBLIC_PRIMARY_MAIN).lighten(0.1).toHex(),
+ dark: colord(process.env.NEXT_PUBLIC_PRIMARY_MAIN).darken(0.1).toHex(),
+ },
+ secondary: {
+ main: process.env.NEXT_PUBLIC_SECONDARY_MAIN,
+ contrastText: "#FFFFFF",
+ light: colord(process.env.NEXT_PUBLIC_SECONDARY_MAIN).lighten(0.1).toHex(),
+ dark: colord(process.env.NEXT_PUBLIC_SECONDARY_MAIN).darken(0.1).toHex(),
+ },
+ },
+ components: {
+ MuiBackdrop: {
+ styleOverrides: {
+ root: {
+ backgroundColor: colord(process.env.NEXT_PUBLIC_PRIMARY_MAIN).darken(0.2).alpha(0.7).toHex(),
+ },
+ },
+ },
+ },
+};
+
+export default theme;
diff --git a/src/layouts/AppLayout.jsx b/src/layouts/AppLayout.jsx
new file mode 100644
index 0000000..4fc5e1d
--- /dev/null
+++ b/src/layouts/AppLayout.jsx
@@ -0,0 +1,56 @@
+import theme from "@/core/utils/theme";
+import useLanguage from "@/lib/app/hooks/useLanguage";
+import useLoading from "@/lib/app/hooks/useLoading";
+import useUser from "@/lib/app/hooks/useUser";
+import NextNProgress from "nextjs-progressbar";
+import {useEffect} from "react";
+import {ToastContainer} from "react-toastify";
+import useDirection from "@/lib/app/hooks/useDirection";
+import "react-toastify/dist/ReactToastify.css";
+import NetworkComponent from "@/core/components/NetworkComponent";
+import GlobalHead from "@/core/components/GlobalHead";
+
+function AppLayout({children, isBot}) {
+ const {languageIsReady} = useLanguage();
+ const {setLoadingPage} = useLoading();
+ const {userChangedLanguage, token, isAuth} = useUser();
+ const {directionApp} = useDirection();
+
+ useEffect(() => {
+ if (languageIsReady) {
+ if (token) {
+ if (isAuth) {
+ if (userChangedLanguage) {
+ setLoadingPage(true);
+ return;
+ }
+ setLoadingPage(false);
+ return;
+ }
+ setLoadingPage(true);
+ return;
+ }
+ setLoadingPage(false);
+ return;
+ }
+ setLoadingPage(true);
+ }, [languageIsReady, token, isAuth, userChangedLanguage]);
+
+ if (!isBot) {
+ if (userChangedLanguage) return;
+ if (!languageIsReady) return;
+ }
+
+ return (<>
+
+
+
+
+ {children}
+ >);
+}
+
+export default AppLayout;
diff --git a/src/layouts/CenterLayout.jsx b/src/layouts/CenterLayout.jsx
new file mode 100644
index 0000000..1e1b8bf
--- /dev/null
+++ b/src/layouts/CenterLayout.jsx
@@ -0,0 +1,18 @@
+import {Fade, Stack} from "@mui/material";
+
+const CenterLayout = (props) => {
+ return (
+
+
+ {props.children}
+
+
+ );
+};
+
+export default CenterLayout;
diff --git a/src/layouts/FullPageLayout.jsx b/src/layouts/FullPageLayout.jsx
new file mode 100644
index 0000000..d6ed61e
--- /dev/null
+++ b/src/layouts/FullPageLayout.jsx
@@ -0,0 +1,21 @@
+import {Stack} from "@mui/material";
+
+const FullPageLayout = (props) => {
+ return (
+
+ {props.children}
+
+ );
+};
+
+export default FullPageLayout;
diff --git a/src/layouts/MuiLayout.jsx b/src/layouts/MuiLayout.jsx
new file mode 100644
index 0000000..6dcd3ed
--- /dev/null
+++ b/src/layouts/MuiLayout.jsx
@@ -0,0 +1,73 @@
+import NoSsrHandler from "@/core/components/NoSsrHandler";
+import {createEmotionCacheRtl} from "@/core/utils/createEmotionCache";
+import themeRtl from "@/core/utils/theme-rtl";
+import {CacheProvider} from "@emotion/react";
+import {GlobalStyles} from "@mui/material";
+import CssBaseline from "@mui/material/CssBaseline";
+import {ThemeProvider} from "@mui/material/styles";
+import Head from "next/head";
+
+const clientSideEmotionCacheRtl = createEmotionCacheRtl();
+
+const MuiLayout = ({children, isBot}) => {
+ const emotionCache = clientSideEmotionCacheRtl;
+ const theme = themeRtl;
+
+ return (
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+
+ );
+};
+
+export default MuiLayout;
diff --git a/src/layouts/ScrollableLayout.jsx b/src/layouts/ScrollableLayout.jsx
new file mode 100644
index 0000000..583edd7
--- /dev/null
+++ b/src/layouts/ScrollableLayout.jsx
@@ -0,0 +1,25 @@
+import {Box, Fade} from "@mui/material";
+
+const ScrollableLayout = (props) => {
+ const overflowY = props?.y || "hidden";
+ const overflowX = props?.x || "hidden";
+
+ return (
+
+
+ {props.children}
+
+
+ );
+};
+
+export default ScrollableLayout;
diff --git a/src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx b/src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx
new file mode 100644
index 0000000..c233be3
--- /dev/null
+++ b/src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx
@@ -0,0 +1,19 @@
+import LinkRouting from "@/core/components/LinkRouting";
+import {Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+
+export default function BreadcrumbItem(props) {
+ const t = useTranslations();
+ const isLast = props.index === props.RouterArray.length - 1;
+ const url = `/${props.RouterArray.slice(0, props.index + 1).join("/")}`;
+
+ return isLast ? (
+
+ {t("sidebar." + props.label)}
+
+ ) : (
+
+ {t("sidebar." + props.label)}
+
+ );
+}
diff --git a/src/layouts/dashboardLayouts/breadcrumbs/index.jsx b/src/layouts/dashboardLayouts/breadcrumbs/index.jsx
new file mode 100644
index 0000000..9ab234e
--- /dev/null
+++ b/src/layouts/dashboardLayouts/breadcrumbs/index.jsx
@@ -0,0 +1,49 @@
+import {useRouter} from "next/router";
+import {Box, Breadcrumbs} from "@mui/material";
+import {useTheme} from "@mui/material/styles";
+import {NavigateBefore, NavigateNext} from "@mui/icons-material";
+import BreadcrumbItem from "./BreadcrumbItem";
+
+const BreadCrumbs = (props) => {
+ const {isVisible} = props;
+ const theme = useTheme();
+ const router = useRouter();
+
+ if (!isVisible) {
+ return null;
+ }
+
+ const {pathname} = router;
+ const RouterArray = pathname.split("/").filter((segment) => segment !== "");
+
+ if (RouterArray.length === 1) {
+ return null;
+ }
+
+ return (
+
+
+ ) : (
+
+ )
+ }
+ aria-label="breadcrumb"
+ >
+ {RouterArray.map((segment, index) => (
+
+ ))}
+
+
+ );
+};
+
+export default BreadCrumbs;
diff --git a/src/layouts/dashboardLayouts/header/ProfileData.jsx b/src/layouts/dashboardLayouts/header/ProfileData.jsx
new file mode 100644
index 0000000..548378b
--- /dev/null
+++ b/src/layouts/dashboardLayouts/header/ProfileData.jsx
@@ -0,0 +1,18 @@
+import {Avatar, Stack, Typography} from "@mui/material";
+import useUser from "@/lib/app/hooks/useUser";
+
+export default function ProfileData() {
+ const {user} = useUser();
+ return (
+
+
+
+ {user.username}
+
+
+ );
+}
diff --git a/src/layouts/dashboardLayouts/header/ProfileMenu.jsx b/src/layouts/dashboardLayouts/header/ProfileMenu.jsx
new file mode 100644
index 0000000..415d501
--- /dev/null
+++ b/src/layouts/dashboardLayouts/header/ProfileMenu.jsx
@@ -0,0 +1,62 @@
+import {Avatar, IconButton, Menu, Tooltip} from "@mui/material";
+import {useState} from "react";
+import ProfileData from "./ProfileData";
+import ProfileOptions from "./ProfileOptions";
+import {useTranslations} from "next-intl";
+import useUser from "@/lib/app/hooks/useUser";
+
+function ProfileMenu() {
+ const t = useTranslations();
+ const [anchorElUser, setAnchorElUser] = useState(null);
+ const {user} = useUser();
+
+ const handleOpenUserMenu = (event) => {
+ setAnchorElUser(event.currentTarget);
+ };
+ const handleCloseUserMenu = () => {
+ setAnchorElUser(null);
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+}
+
+export default ProfileMenu;
diff --git a/src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx b/src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx
new file mode 100644
index 0000000..2a6cf85
--- /dev/null
+++ b/src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx
@@ -0,0 +1,49 @@
+import {Box, Button, MenuItem, Typography} from "@mui/material";
+import MeetingRoomIcon from "@mui/icons-material/MeetingRoom";
+import useUser from "@/lib/app/hooks/useUser";
+import {useTranslations} from "next-intl";
+
+export default function ProfileOptionLogOut({handleCloseUserMenu}) {
+ const t = useTranslations();
+ const {clearToken} = useUser();
+ const handleClickLogOut = () => {
+ handleCloseUserMenu();
+ clearToken();
+ };
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/src/layouts/dashboardLayouts/header/ProfileOptions.jsx b/src/layouts/dashboardLayouts/header/ProfileOptions.jsx
new file mode 100644
index 0000000..6e7bdba
--- /dev/null
+++ b/src/layouts/dashboardLayouts/header/ProfileOptions.jsx
@@ -0,0 +1,49 @@
+import {Box, MenuItem, Typography} from "@mui/material";
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import {useTranslations} from "next-intl";
+import headerProfileItems from "@/core/data/headerProfileItems";
+import ProfileOptionLogOut from "./ProfileOptionLogOut";
+
+export default function ProfileOptions({handleCloseUserMenu}) {
+ const t = useTranslations();
+
+ return (
+ <>
+ {headerProfileItems.map((profile_item) => (
+
+ ))}
+
+ >
+ );
+}
diff --git a/src/layouts/dashboardLayouts/header/index.jsx b/src/layouts/dashboardLayouts/header/index.jsx
new file mode 100644
index 0000000..8760960
--- /dev/null
+++ b/src/layouts/dashboardLayouts/header/index.jsx
@@ -0,0 +1,72 @@
+import MenuIcon from "@mui/icons-material/Menu";
+import {AppBar, Box, Container, CssBaseline, IconButton, Stack, Toolbar, useTheme} from "@mui/material";
+import ProfileMenu from "./ProfileMenu";
+
+function Header({drawerWidth, handleDrawerToggle}) {
+ const theme = useTheme();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default Header;
diff --git a/src/layouts/dashboardLayouts/index.jsx b/src/layouts/dashboardLayouts/index.jsx
new file mode 100644
index 0000000..fae51d2
--- /dev/null
+++ b/src/layouts/dashboardLayouts/index.jsx
@@ -0,0 +1,45 @@
+import {useState} from "react";
+import FullPageLayout from "../FullPageLayout";
+import Header from "./header";
+import Sidebar from "./sidebar";
+import {Toolbar} from "@mui/material";
+import BreadCrumbs from "./breadcrumbs";
+
+const drawerWidth = 240;
+
+const DashboardLayouts = (props) => {
+ const {window} = props;
+ const [mobileOpen, setMobileOpen] = useState(false);
+ const container =
+ window !== undefined ? () => window().document.body : undefined;
+
+ const handleDrawerToggle = () => {
+ setMobileOpen(!mobileOpen);
+ };
+ return (
+
+
+
+
+
+
+
+ {props.children}
+
+
+
+ );
+};
+
+export default DashboardLayouts;
diff --git a/src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx b/src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx
new file mode 100644
index 0000000..f155dce
--- /dev/null
+++ b/src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx
@@ -0,0 +1,27 @@
+import {Divider, Stack, Toolbar, Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+import SidebarList from "./SidebarList";
+import useUser from "@/lib/app/hooks/useUser";
+
+const SidebarDrawer = ({handleDrawerToggle}) => {
+ const {user} = useUser()
+ const t = useTranslations();
+ return (
+ <>
+
+
+
+ {t("app_short_name")}
+
+
+ {user.name} | {user.position}
+
+
+
+
+
+ >
+ );
+};
+
+export default SidebarDrawer;
diff --git a/src/layouts/dashboardLayouts/sidebar/SidebarList.jsx b/src/layouts/dashboardLayouts/sidebar/SidebarList.jsx
new file mode 100644
index 0000000..12d268d
--- /dev/null
+++ b/src/layouts/dashboardLayouts/sidebar/SidebarList.jsx
@@ -0,0 +1,72 @@
+import {Divider, List} from "@mui/material";
+import {Fragment, useEffect, useReducer} from "react";
+import SidebarListItem from "./SidebarListItem";
+import sidebarMenu from "@/core/data/sidebarMenu";
+import {useRouter} from "next/router";
+import useUser from "@/lib/app/hooks/useUser";
+
+function reducer(state, action) {
+ switch (action.type) {
+ case "COLLAPSE_MENU":
+ return state.map((itemsArr) =>
+ itemsArr.map((item) =>
+ action.key == item.key
+ ? {...item, showSubItem: !item.showSubItem}
+ : item
+ )
+ );
+ case "SELECTED":
+ return state.map((itemsArr) =>
+ itemsArr.map((item) => {
+ console.log(item)
+ if (item.type === "page") {
+ if (action.route === item.route)
+ return {...item, selected: true}
+ else
+ return {...item, selected: false}
+ }
+ return {...item}
+ // item.subItem.map((subitem) => {
+ // if (action.route === subitem.route)
+ // return {...item, showSubItem: true, subItem: {...subitem, selected: true}}
+ // else
+ // return {...item, showSubItem: false, subItem: {...subitem, selected: false}}
+ // })
+ }
+ )
+ );
+ default:
+ throw new Error();
+ }
+}
+
+export default function SidebarList({handleDrawerToggle}) {
+ const [itemMenu, dispatch] = useReducer(reducer, sidebarMenu);
+ const {user} = useUser();
+ const router = useRouter();
+
+ useEffect(() => {
+ dispatch({type: "SELECTED", route: router.pathname});
+ }, [router.pathname]);
+
+ return (
+
+ {itemMenu.map((itemArr, index) => (
+
+ {itemArr.map((item) =>
+
+ {(user.permissions.includes(item.permission) || item.permission === "all") &&
+ }
+
+ )}
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx b/src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx
new file mode 100644
index 0000000..bf57028
--- /dev/null
+++ b/src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx
@@ -0,0 +1,84 @@
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import ExpandLess from "@mui/icons-material/ExpandLess";
+import ExpandMore from "@mui/icons-material/ExpandMore";
+import {Badge, IconButton, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography,} from "@mui/material";
+import {useTranslations} from "next-intl";
+import {Fragment} from "react";
+import SidebarListSubItem from "./SidebarListSubItem";
+import useNotification from "@/lib/app/hooks/useNotification";
+
+const SidebarListItem = ({item, dispatch, handleDrawerToggle}) => {
+ const t = useTranslations();
+ const {notification_count} = useNotification()
+
+ return (
+ <>
+
+
+
+ }>
+ {
+ if (item.type == "menu") {
+ dispatch({type: "COLLAPSE_MENU", key: item.key});
+ }
+ handleDrawerToggle();
+ }}
+ sx={{
+ minHeight: 48,
+ }}
+
+ >
+
+ {item.icon}
+
+
+ {t(item.secondary)}
+
+ ) : null
+ }
+ />
+
+
+ {item.type == "menu" &&
+ (item.showSubItem ? : )}
+
+
+ {item.subItem && (
+
+ )}
+ >
+ );
+};
+
+export default SidebarListItem;
diff --git a/src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx b/src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx
new file mode 100644
index 0000000..c9c8a36
--- /dev/null
+++ b/src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx
@@ -0,0 +1,71 @@
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import {
+ Badge,
+ Collapse,
+ IconButton,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ Typography,
+} from "@mui/material";
+import {useTranslations} from "next-intl";
+import useNotification from "@/lib/app/hooks/useNotification";
+
+const SidebarListSubItem = ({item, handleDrawerToggle}) => {
+ const t = useTranslations();
+ const {notification_count} = useNotification()
+
+ return (
+
+
+ {item.subItem.map((subitem, index) => (
+
+
+
+ }>
+
+
+ {subitem.icon}
+
+
+ {t(subitem.secondary)}
+
+ ) : null
+ }/>
+
+
+ ))}
+
+
+ );
+};
+
+export default SidebarListSubItem;
diff --git a/src/layouts/dashboardLayouts/sidebar/index.jsx b/src/layouts/dashboardLayouts/sidebar/index.jsx
new file mode 100644
index 0000000..2498dfd
--- /dev/null
+++ b/src/layouts/dashboardLayouts/sidebar/index.jsx
@@ -0,0 +1,46 @@
+import {Box, Drawer} from "@mui/material";
+import SidebarDrawer from "./SidebarDrawer";
+
+const Sidebar = (props) => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Sidebar;
diff --git a/src/lib/app/contexts/language.jsx b/src/lib/app/contexts/language.jsx
new file mode 100644
index 0000000..e3f41f4
--- /dev/null
+++ b/src/lib/app/contexts/language.jsx
@@ -0,0 +1,99 @@
+import {FA_DATATABLE_LOCALIZATION} from "&/locales/fa/datatable";
+import {useRouter} from "next/router";
+import {createContext, useEffect, useState} from "react";
+import useUser from "../hooks/useUser";
+
+export const LanguageContext = createContext();
+
+export const LanguageProvider = ({children}) => {
+ const router = useRouter();
+ const languageList = [
+ {
+ key: "fa",
+ dir: "rtl",
+ name: "فارسی",
+ fontFamily: `IRANSans, sans-serif`,
+ tableLocalization: FA_DATATABLE_LOCALIZATION,
+ }
+ ];
+ const {user, userChangedLanguage, changeLanguageState} = useUser();
+ const [languageIsReady, setLanguageIsReady] = useState(false);
+ const [languageApp, setLanguageApp] = useState();
+ const [directionApp, setDirectionApp] = useState(
+ process.env.NEXT_PUBLIC_DEFAULT_DIRECTION
+ );
+
+ useEffect(() => {
+ const lang = localStorage.getItem("_language");
+
+ if (!lang && !languageApp) {
+ setLanguageApp(process.env.NEXT_PUBLIC_DEFAULT_LANGUAGE);
+ } else if (lang) {
+ setLanguageApp(lang);
+ }
+ }, []);
+
+ useEffect(() => {
+ if (!languageApp) return;
+
+ const lang = localStorage.getItem("_language");
+
+ if (!lang) {
+ localStorage.setItem("_language", languageApp);
+ } else {
+ if (languageApp != lang) {
+ localStorage.setItem("_language", languageApp);
+ }
+ }
+ }, [languageApp]);
+
+ useEffect(() => {
+ if (!router.isReady) return;
+ if (user.user_language) {
+ if (user.user_language != languageApp) {
+ setLanguageApp(user.user_language);
+ return;
+ }
+ }
+
+ if (languageApp != router.locale) {
+ router.replace(
+ {pathname: router.pathname, query: router.query},
+ router.asPath,
+ {
+ locale: languageApp,
+ }
+ );
+ return;
+ }
+ for (const lang of languageList) {
+ if (languageApp != lang.key) continue;
+ setDirectionApp(lang.dir);
+ document.dir = lang.dir;
+ }
+
+ const timer = setTimeout(() => {
+ changeLanguageState(false);
+ setLanguageIsReady(true);
+ }, 1000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }, [router.locale, router.isReady, userChangedLanguage, languageApp]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/lib/app/contexts/loading.jsx b/src/lib/app/contexts/loading.jsx
new file mode 100644
index 0000000..f5b8a47
--- /dev/null
+++ b/src/lib/app/contexts/loading.jsx
@@ -0,0 +1,14 @@
+import LoadingHardPage from "@/core/components/LoadingHardPage";
+import {createContext, useState} from "react";
+
+export const LoadingContext = createContext();
+
+export const LoadingProvider = ({children}) => {
+ const [loadingPage, setLoadingPage] = useState(false);
+ return (
+
+
+ {children}
+
+ );
+};
diff --git a/src/lib/app/contexts/user.jsx b/src/lib/app/contexts/user.jsx
new file mode 100644
index 0000000..10611b8
--- /dev/null
+++ b/src/lib/app/contexts/user.jsx
@@ -0,0 +1,132 @@
+import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
+import axios from "axios";
+import {createContext, useCallback, useEffect, useReducer} from "react";
+import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler";
+
+const initialUser = {
+ isAuth: false,
+ userChangedLanguage: false,
+ token: null,
+ user: {},
+};
+
+const reducer = (state, action) => {
+ switch (action.type) {
+ case "CLEAR_USER":
+ return {...state, user: {}};
+ case "CHANGE_USER":
+ return {...state, user: action.user};
+ case "CHANGE_USER_LANGUAGE":
+ return {
+ ...state,
+ user: {...state.user, user_language: action.language},
+ };
+ case "CHANGE_AUTH_STATE":
+ return {...state, isAuth: action.isAuth};
+ case "CHANGE_LANGUAGE_STATE":
+ return {...state, userChangedLanguage: action.userChangedLanguage};
+ case "CLEAR_TOKEN":
+ localStorage.removeItem("_token");
+ return {...state, token: null};
+ case "SET_TOKEN":
+ localStorage.setItem("_token", action.token);
+ return {...state, token: action.token};
+ default:
+ return state;
+ }
+};
+
+export const UserContext = createContext();
+
+export const UserProvider = ({children}) => {
+ const [state, dispatch] = useReducer(reducer, initialUser);
+
+ const clearUser = useCallback(() => {
+ dispatch({type: "CLEAR_USER"});
+ }, []);
+
+ const changeUser = useCallback((user) => {
+ dispatch({type: "CHANGE_USER", user});
+ }, []);
+
+ const changeUserLanguage = useCallback(/* use in multi language app */);
+
+ const changeAuthState = useCallback((isAuth) => {
+ dispatch({type: "CHANGE_AUTH_STATE", isAuth});
+ }, []);
+
+ const changeLanguageState = useCallback((userChangedLanguage) => {
+ dispatch({type: "CHANGE_LANGUAGE_STATE", userChangedLanguage});
+ }, []);
+
+ const clearToken = useCallback(() => {
+ dispatch({type: "CLEAR_TOKEN"});
+ }, []);
+
+ const setToken = useCallback((token) => {
+ dispatch({type: "SET_TOKEN", token});
+ }, []);
+
+ const getUser = useCallback(
+ (callback = () => {
+ }) => {
+ axios
+ .get(GET_USER_ROUTE, {
+ headers: {authorization: `Bearer ${state.token}`},
+ })
+ .then(({data}) => {
+ if (typeof callback === "function") callback(data);
+ })
+ .catch(error => {
+ if (error.response) {
+ errorResponse(error.response, clearToken, null, false)
+ } else if (error.request) {
+ errorRequest(null, false)
+ } else {
+ errorSetting(null, false)
+ }
+ })
+ },
+ [state.token]
+ );
+
+ useEffect(() => {
+ const localToken = localStorage.getItem("_token");
+ if (localToken) dispatch({type: "SET_TOKEN", token: localToken});
+ }, []);
+
+ useEffect(() => {
+ if (!state.token) {
+ clearUser();
+ changeAuthState(false);
+ changeLanguageState(false);
+ return;
+ }
+ getUser((data) => {
+ changeUser(data);
+ changeAuthState(true);
+ changeLanguageState(true);
+ });
+ }, [state.token]);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/lib/app/hooks/useDirection.jsx b/src/lib/app/hooks/useDirection.jsx
new file mode 100644
index 0000000..b2e01fe
--- /dev/null
+++ b/src/lib/app/hooks/useDirection.jsx
@@ -0,0 +1,10 @@
+import {useContext} from "react";
+import {LanguageContext} from "../contexts/language";
+
+const useDirection = () => {
+ const {directionApp} = useContext(LanguageContext);
+
+ return {directionApp};
+};
+
+export default useDirection;
diff --git a/src/lib/app/hooks/useLanguage.jsx b/src/lib/app/hooks/useLanguage.jsx
new file mode 100644
index 0000000..d663835
--- /dev/null
+++ b/src/lib/app/hooks/useLanguage.jsx
@@ -0,0 +1,28 @@
+import {useContext} from "react";
+import {LanguageContext} from "../contexts/language";
+import useUser from "./useUser";
+
+const useLanguage = () => {
+ const {isAuth, changeUserLanguage} = useUser();
+ const {
+ languageApp,
+ setLanguageApp,
+ languageIsReady,
+ setLanguageIsReady,
+ languageList,
+ } = useContext(LanguageContext);
+
+ const changeLanguage = (lang) => {
+ if (lang == languageApp) return;
+
+ setLanguageIsReady(false);
+ setLanguageApp(lang);
+ if (isAuth) {
+ changeUserLanguage(lang);
+ }
+ };
+
+ return {languageApp, changeLanguage, languageIsReady, languageList};
+};
+
+export default useLanguage;
diff --git a/src/lib/app/hooks/useLoading.jsx b/src/lib/app/hooks/useLoading.jsx
new file mode 100644
index 0000000..a691bd7
--- /dev/null
+++ b/src/lib/app/hooks/useLoading.jsx
@@ -0,0 +1,10 @@
+import {useContext} from "react";
+import {LoadingContext} from "../contexts/loading";
+
+const useLoading = () => {
+ const {setLoadingPage} = useContext(LoadingContext);
+
+ return {setLoadingPage};
+};
+
+export default useLoading;
diff --git a/src/lib/app/hooks/useNetwork.jsx b/src/lib/app/hooks/useNetwork.jsx
new file mode 100644
index 0000000..84e2950
--- /dev/null
+++ b/src/lib/app/hooks/useNetwork.jsx
@@ -0,0 +1,58 @@
+import {useEffect, useState} from "react";
+
+function getNetworkConnection() {
+ return (
+ navigator.connection ||
+ navigator.mozConnection ||
+ navigator.webkitConnection ||
+ null
+ );
+}
+
+function getNetworkConnectionInfo() {
+ const connection = getNetworkConnection();
+ if (!connection) {
+ return {};
+ }
+ return {
+ rtt: connection.rtt,
+ type: connection.type,
+ saveData: connection.saveData,
+ downLink: connection.downLink,
+ downLinkMax: connection.downLinkMax,
+ effectiveType: connection.effectiveType,
+ };
+}
+
+function useNetwork() {
+ const [state, setState] = useState(() => {
+ return {
+ online: navigator.onLine,
+ };
+ });
+ useEffect(() => {
+ const handleOnline = () => {
+ setState((prevState) => ({
+ ...prevState,
+ online: true,
+ }));
+ };
+ const handleOffline = () => {
+ setState((prevState) => ({
+ ...prevState,
+ online: false,
+ }));
+ };
+
+ window.addEventListener("online", handleOnline);
+ window.addEventListener("offline", handleOffline);
+ return () => {
+ window.removeEventListener("online", handleOnline);
+ window.removeEventListener("offline", handleOffline);
+ };
+ }, []);
+
+ return state;
+}
+
+export default useNetwork;
\ No newline at end of file
diff --git a/src/lib/app/hooks/useNotification.jsx b/src/lib/app/hooks/useNotification.jsx
new file mode 100644
index 0000000..e32f829
--- /dev/null
+++ b/src/lib/app/hooks/useNotification.jsx
@@ -0,0 +1,23 @@
+import useSWR from 'swr'
+import {GET_SIDEBAR_NOTIFICATION} from "@/core/data/apiRoutes";
+import useRequest from "@/lib/app/hooks/useRequest";
+
+const useNotification = () => {
+ const requestServer = useRequest({auth: true, notification: false})
+
+ //swr config
+ const fetcher = (...args) => {
+ return requestServer(args, 'get').then((response) => {
+ return response.data.data;
+ }).catch(() => {
+ })
+ };
+
+ const {data, mutate} = useSWR(GET_SIDEBAR_NOTIFICATION, fetcher)
+ const notification_count = data
+ //swr config
+
+ // render data
+ return {notification_count, update_notification: mutate}
+}
+export default useNotification
\ No newline at end of file
diff --git a/src/lib/app/hooks/useRequest.jsx b/src/lib/app/hooks/useRequest.jsx
new file mode 100644
index 0000000..d361e08
--- /dev/null
+++ b/src/lib/app/hooks/useRequest.jsx
@@ -0,0 +1,66 @@
+import axios from "axios";
+import {successRequest} from "@/core/utils/succesHandler";
+import PendingNotification from "@/core/components/notifications/PendingNotification";
+import {useTranslations} from "next-intl";
+import useUser from "@/lib/app/hooks/useUser";
+import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler";
+import useNetwork from "@/lib/app/hooks/useNetwork";
+
+const defaultOptions = {
+ auth: false, data: {}, requestOptions: {
+ headers: {}
+ }, notification: true, pending: true, success: {
+ notification: {
+ show: true,
+ },
+ }, failed: {
+ notification: {
+ show: true,
+ },
+ },
+}
+const useRequest = (initOptions) => {
+ const network = useNetwork()
+ const t = useTranslations()
+ const {token, clearToken} = useUser()
+ let _options = {...defaultOptions, ...initOptions}
+
+ function requestServer(url = '', method = 'get', options) {
+ _options = {..._options, ...options}
+ if (_options.auth) _options = {
+ ..._options, requestOptions: {
+ ..._options.requestOptions,
+ headers: {..._options.requestOptions.headers, authorization: `Bearer ${token}`}
+ }
+ }
+
+ return new Promise((resolve, reject) => {
+ if (!network.online) {
+ reject()
+ return
+ }
+ if (_options.notification && _options.failed.notification.show && _options.pending) PendingNotification(t)
+ axios({
+ url: url, method: method, data: _options.data, ..._options.requestOptions
+ })
+ .then(response => {
+ successRequest(response, t, _options)
+ resolve(response)
+ })
+ .catch(error => {
+ if (error.response) {
+ errorResponse(error.response, clearToken, t, _options.notification && _options.failed.notification.show)
+ } else if (error.request) {
+ errorRequest(t, _options.notification && _options.failed.notification.show)
+ } else {
+ errorSetting(t, _options.notification && _options.failed.notification.show)
+ }
+ reject(error)
+ })
+ });
+ }
+
+ return requestServer
+}
+
+export default useRequest
\ No newline at end of file
diff --git a/src/lib/app/hooks/useUser.jsx b/src/lib/app/hooks/useUser.jsx
new file mode 100644
index 0000000..b0bbc1a
--- /dev/null
+++ b/src/lib/app/hooks/useUser.jsx
@@ -0,0 +1,34 @@
+import {useContext} from "react";
+import {UserContext} from "../contexts/user";
+
+const useUser = () => {
+ const {
+ isAuth,
+ userChangedLanguage,
+ token,
+ user,
+ getUser,
+ clearUser,
+ changeUser,
+ changeAuthState,
+ changeLanguageState,
+ clearToken,
+ setToken,
+ } = useContext(UserContext);
+
+ return {
+ isAuth,
+ userChangedLanguage,
+ token,
+ user,
+ getUser,
+ clearUser,
+ changeUser,
+ changeAuthState,
+ changeLanguageState,
+ clearToken,
+ setToken,
+ };
+};
+
+export default useUser;
diff --git a/src/middlewares/RolePermission.jsx b/src/middlewares/RolePermission.jsx
new file mode 100644
index 0000000..67fc3f1
--- /dev/null
+++ b/src/middlewares/RolePermission.jsx
@@ -0,0 +1,43 @@
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import Message from "@/core/components/Messages";
+import useUser from "@/lib/app/hooks/useUser";
+import {Button, Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+
+const RolePermissionMiddleware = ({children, requiredPermissions}) => {
+ const {user} = useUser();
+ const t = useTranslations();
+
+ const hasPermission = requiredPermissions.some((permission) =>
+ user?.permissions?.includes(permission)
+ );
+
+ if (!hasPermission) {
+ return (
+
+ {t("Permission.typography_you_dont_have_access")}
+
+ }
+ actions={
+ <>
+
+ >
+ }
+ />
+ );
+ }
+
+ return <>{children}>;
+};
+
+export default RolePermissionMiddleware;
diff --git a/src/middlewares/WithAuth.jsx b/src/middlewares/WithAuth.jsx
new file mode 100644
index 0000000..b20d8b3
--- /dev/null
+++ b/src/middlewares/WithAuth.jsx
@@ -0,0 +1,42 @@
+import {NextLinkComposed} from "@/core/components/LinkRouting";
+import Message from "@/core/components/Messages";
+import useUser from "@/lib/app/hooks/useUser";
+import {Button, Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+import {useRouter} from "next/router";
+
+const WithAuthMiddleware = ({children}) => {
+ const {isAuth} = useUser();
+ const t = useTranslations();
+ const router = useRouter();
+
+ if (!isAuth)
+ return (
+
+ {t(
+ "Authorization.typography_your_access_to_this_page_has_expired_Please_login_again"
+ )}
+
+ }
+ actions={
+ <>
+
+ >
+ }
+ />
+ );
+ return <>{children}>;
+};
+
+export default WithAuthMiddleware;
diff --git a/src/middlewares/WithoutAuth.jsx b/src/middlewares/WithoutAuth.jsx
new file mode 100644
index 0000000..594ffc5
--- /dev/null
+++ b/src/middlewares/WithoutAuth.jsx
@@ -0,0 +1,56 @@
+import Message from "@/core/components/Messages";
+import useUser from "@/lib/app/hooks/useUser";
+import {Stack, Typography} from "@mui/material";
+import {useTranslations} from "next-intl";
+import {useSearchParams} from "next/navigation";
+import {useRouter} from "next/router";
+import {useEffect} from "react";
+
+const WithoutAuthMiddleware = ({children}) => {
+ const {isAuth} = useUser();
+ const t = useTranslations();
+ const router = useRouter();
+
+ // gettin url query
+ const searchParams = useSearchParams();
+ const backUrlDecodedPath = searchParams.get("back_url");
+
+ useEffect(() => {
+ if (!isAuth) return;
+ const timer = setTimeout(() => {
+ router.replace(
+ backUrlDecodedPath
+ ? decodeURIComponent(backUrlDecodedPath)
+ : "/dashboard"
+ );
+ }, 2000);
+
+ return () => {
+ clearTimeout(timer);
+ };
+ }, [isAuth]);
+
+ if (isAuth)
+ return (
+
+
+ {t(
+ "Authorization.typography_your_login_is_valid_and_you_do_not_need_to_login_again"
+ )}
+
+
+ {t("Authorization.typography_redirect_to")}{" "}
+ {backUrlDecodedPath
+ ? t("Authorization.typography_routing_previuos_page")
+ : t("Authorization.typography_routing_dashbaord_page")}
+
+
+ }
+ />
+ );
+ return <>{children}>;
+};
+
+export default WithoutAuthMiddleware;
diff --git a/src/pages/403.jsx b/src/pages/403.jsx
new file mode 100644
index 0000000..15ba915
--- /dev/null
+++ b/src/pages/403.jsx
@@ -0,0 +1,24 @@
+import {useEffect, useState} from "react";
+import {useRouter} from "next/router";
+import {NextIntlProvider} from "next-intl";
+import UnAuthorizedComponent from "@/components/errors/403";
+
+export default function Custom404() {
+ const router = useRouter()
+ const [messages, setMessages] = useState(null)
+ useEffect(() => {
+ const fetch = async () => {
+ const tempMessages = (await import(`&/locales/${router.locale}/app.json`)).default
+ setMessages(tempMessages)
+ }
+ fetch()
+ }, []);
+
+ if (!messages) return
+
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/404.jsx b/src/pages/404.jsx
new file mode 100644
index 0000000..26de038
--- /dev/null
+++ b/src/pages/404.jsx
@@ -0,0 +1,25 @@
+import {useEffect, useState} from "react";
+import {useRouter} from "next/router";
+import {NextIntlProvider} from "next-intl";
+import NotFoundComponent from "@/components/errors/404";
+
+export default function Custom404() {
+ const router = useRouter()
+ const [messages, setMessages] = useState(null)
+ useEffect(() => {
+
+ const fetch = async () => {
+ const tempMessages = (await import(`&/locales/${router.locale}/app.json`)).default
+ setMessages(tempMessages)
+ }
+ fetch()
+ }, []);
+
+ if (!messages) return
+
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/500.jsx b/src/pages/500.jsx
new file mode 100644
index 0000000..4b85bc2
--- /dev/null
+++ b/src/pages/500.jsx
@@ -0,0 +1,24 @@
+import {useEffect, useState} from "react";
+import {useRouter} from "next/router";
+import {NextIntlProvider} from "next-intl";
+import ServerErrorComponent from "@/components/errors/500";
+
+export default function Custom500() {
+ const router = useRouter()
+ const [messages, setMessages] = useState(null)
+ useEffect(() => {
+ const fetch = async () => {
+ const tempMessages = (await import(`&/locales/${router.locale}/app.json`)).default
+ setMessages(tempMessages)
+ }
+ fetch()
+ }, []);
+
+ if (!messages) return
+
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx
new file mode 100644
index 0000000..29ec464
--- /dev/null
+++ b/src/pages/_app.jsx
@@ -0,0 +1,33 @@
+import "&/fontiran.scss";
+import AppLayout from "@/layouts/AppLayout";
+import MuiLayout from "@/layouts/MuiLayout";
+import {LanguageProvider} from "@/lib/app/contexts/language";
+import {LoadingProvider} from "@/lib/app/contexts/loading";
+import {UserProvider} from "@/lib/app/contexts/user";
+import "moment/locale/fa";
+import {NextIntlProvider} from "next-intl";
+import TitlePage from "@/core/components/TitlePage";
+
+const App = ({Component, pageProps}) => {
+
+ return (
+ <>
+
+
+
+
+ {pageProps.messages ? : ''}
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default App;
diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx
new file mode 100644
index 0000000..474e29b
--- /dev/null
+++ b/src/pages/_document.jsx
@@ -0,0 +1,61 @@
+import {createEmotionCacheRtl} from "@/core/utils/createEmotionCache";
+import theme from "@/core/utils/theme";
+import createEmotionServer from "@emotion/server/create-instance";
+import Document, {Head, Html, Main, NextScript} from "next/document";
+
+export default function MyDocument(props) {
+ const {emotionStyleTags} = props;
+
+ return (
+
+
+
+
+
+
+ {emotionStyleTags}
+
+
+
+
+
+
+ );
+}
+
+MyDocument.getInitialProps = async (ctx) => {
+ const originalRenderPage = ctx.renderPage;
+ let cache;
+ switch (ctx.locale) {
+ case "fa":
+ cache = createEmotionCacheRtl();
+ break;
+ }
+
+ const {extractCriticalToChunks} = createEmotionServer(cache);
+
+ ctx.renderPage = () =>
+ originalRenderPage({
+ enhanceApp: (App) =>
+ function EnhanceApp(props) {
+ return ;
+ },
+ });
+
+ const initialProps = await Document.getInitialProps(ctx);
+
+ const emotionStyles = extractCriticalToChunks(initialProps.html);
+ const emotionStyleTags = emotionStyles.styles.map((style) => (
+
+ ));
+
+ return {
+ ...initialProps,
+ emotionStyleTags,
+ };
+};
diff --git a/src/pages/dashboard/change-password/index.jsx b/src/pages/dashboard/change-password/index.jsx
new file mode 100644
index 0000000..670e184
--- /dev/null
+++ b/src/pages/dashboard/change-password/index.jsx
@@ -0,0 +1,22 @@
+import DashboardChangePasswordComponent from "@/components/dashboard/change-password";
+import WithAuthMiddleware from "@/middlewares/WithAuth";
+import {parse} from "next-useragent";
+
+export default function LoanFollowUp() {
+ return (
+
+
+
+ );
+}
+
+export async function getServerSideProps({req, locale}) {
+ const {isBot} = parse(req.headers["user-agent"]);
+ return {
+ props: {
+ messages: (await import(`&/locales/${locale}/app.json`)).default,
+ title: "Dashboard.change_password",
+ isBot,
+ },
+ };
+}
diff --git a/src/pages/dashboard/edit-profile/index.jsx b/src/pages/dashboard/edit-profile/index.jsx
new file mode 100644
index 0000000..9460506
--- /dev/null
+++ b/src/pages/dashboard/edit-profile/index.jsx
@@ -0,0 +1,22 @@
+import DashboardEditProfile from "@/components/dashboard/edit-profile";
+import WithAuthMiddleware from "@/middlewares/WithAuth";
+import {parse} from "next-useragent";
+
+export default function LoanFollowUp() {
+ return (
+
+
+
+ );
+}
+
+export async function getServerSideProps({req, locale}) {
+ const {isBot} = parse(req.headers["user-agent"]);
+ return {
+ props: {
+ messages: (await import(`&/locales/${locale}/app.json`)).default,
+ title: "Dashboard.edit_profile",
+ isBot,
+ },
+ };
+}
diff --git a/src/pages/dashboard/index.jsx b/src/pages/dashboard/index.jsx
new file mode 100644
index 0000000..af824d5
--- /dev/null
+++ b/src/pages/dashboard/index.jsx
@@ -0,0 +1,22 @@
+import DashboardFirstComponent from "@/components/dashboard/first";
+import WithAuthMiddleware from "@/middlewares/WithAuth";
+import {parse} from "next-useragent";
+
+export default function Dashboard() {
+ return (
+
+
+
+ );
+}
+
+export async function getServerSideProps({req, locale}) {
+ const {isBot} = parse(req.headers["user-agent"]);
+ return {
+ props: {
+ messages: (await import(`&/locales/${locale}/app.json`)).default,
+ title: "Dashboard.dashboard_page",
+ isBot,
+ },
+ };
+}
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
new file mode 100644
index 0000000..2e146c6
--- /dev/null
+++ b/src/pages/index.jsx
@@ -0,0 +1,21 @@
+import FirstComponent from "@/components/first";
+import {parse} from "next-useragent";
+
+export default function Home() {
+ return (
+ <>
+
+ >
+ );
+}
+
+export async function getServerSideProps({req, locale}) {
+ const {isBot} = parse(req.headers["user-agent"]);
+ return {
+ props: {
+ messages: (await import(`&/locales/${locale}/app.json`)).default,
+ title: "first_page",
+ isBot,
+ },
+ };
+}
diff --git a/src/pages/login-expert.jsx b/src/pages/login-expert.jsx
new file mode 100644
index 0000000..1cc6abf
--- /dev/null
+++ b/src/pages/login-expert.jsx
@@ -0,0 +1,22 @@
+import LoginComponent from "@/components/login-expert";
+import WithoutAuthMiddleware from "@/middlewares/WithoutAuth";
+import {parse} from "next-useragent";
+
+export default function Login() {
+ return (
+
+
+
+ );
+}
+
+export async function getServerSideProps({req, locale}) {
+ const {isBot} = parse(req.headers["user-agent"]);
+ return {
+ props: {
+ messages: (await import(`&/locales/${locale}/app.json`)).default,
+ title: "Titles.title_login_expert_page",
+ isBot,
+ },
+ };
+}