Skip to content

Commit 70dd16b

Browse files
committed
Added many future tools
1 parent 1e368ce commit 70dd16b

18 files changed

+2883
-125
lines changed

TextHub/Components/Layout/UINavBar.razor

Lines changed: 123 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11

22
@inject IJSRuntime JsRuntime
3+
@inject TextHub.Services.Data.ToolDataService ToolDataService
4+
@inject NavigationManager Navigation
5+
@using Microsoft.AspNetCore.Components.Web
36
<nav class="sticky top-0 z-50 bg-background/80 backdrop-blur-md border-b border-border">
47
<div class="container mx-auto px-4 py-3 md:py-4">
58
<div class="flex items-center justify-between">
@@ -9,18 +12,80 @@
912
<div class="hidden md:flex items-center gap-6">
1013
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">Home</NavLink>
1114
<div class="relative">
12-
<button @onclick="() => _isToolsDropdownOpen = !_isToolsDropdownOpen" type="button" class="flex items-center gap-1 font-medium hover:text-primary transition-colors cursor-pointer">
15+
<button @onclick="() => _isToolsDropdownOpen = !_isToolsDropdownOpen" @onclick:stopPropagation="true" type="button" class="flex items-center gap-1 font-medium hover:text-primary transition-colors cursor-pointer">
1316
Tools
1417
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down w-4 h-4 transition-transform @(_isToolsDropdownOpen ? "rotate-180" : "")">
1518
<path d="m6 9 6 6 6-6"></path>
1619
</svg>
1720
</button>
1821
@if (_isToolsDropdownOpen)
1922
{
20-
<div class="absolute top-full mt-2 w-48 dropdown-menu rounded-md py-1">
21-
<a href="uppercase" class="block px-4 py-2 text-sm dropdown-item">Uppercase</a>
22-
<a href="lowercase" class="block px-4 py-2 text-sm dropdown-item">Lowercase</a>
23-
<a href="word-counter" class="block px-4 py-2 text-sm dropdown-item">Word Counter</a>
23+
<div id="tools-dropdown" @onclick:stopPropagation="true" @onclick:preventDefault="true" class="absolute top-full mt-2 w-72 p-2 bg-popover text-popover-foreground border border-border rounded-md shadow-md z-50">
24+
@* Text Case Tools Section *@
25+
<div class="mb-1">
26+
<button @onclick="() => ToggleTextCaseSection()" type="button" class="flex items-center justify-between w-full px-2 py-1.5 text-sm font-semibold text-muted-foreground hover:text-foreground transition-colors rounded hover:bg-muted/50">
27+
Text Case Tools
28+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down w-4 h-4 transition-transform @(_isTextCaseOpen ? "rotate-180" : "")">
29+
<path d="m6 9 6 6 6-6"></path>
30+
</svg>
31+
</button>
32+
@if (_isTextCaseOpen)
33+
{
34+
<div class="ml-2 mt-1">
35+
@foreach (var tool in ToolDataService.GetTextCaseTools().Where(t => !t.IsComingSoon).Take(8))
36+
{
37+
<a @onclick="() => NavigateToTool(tool.Href)" class="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground flex items-center gap-3 cursor-pointer">
38+
@((MarkupString)tool.Icon.Replace("w-6 h-6", "w-4 h-4"))
39+
@tool.Title
40+
</a>
41+
}
42+
</div>
43+
}
44+
</div>
45+
46+
@* Text Analysis Section *@
47+
<div class="mb-1">
48+
<button @onclick="() => ToggleTextAnalysisSection()" type="button" class="flex items-center justify-between w-full px-2 py-1.5 text-sm font-semibold text-muted-foreground hover:text-foreground transition-colors rounded hover:bg-muted/50">
49+
Text Analysis
50+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down w-4 h-4 transition-transform @(_isTextAnalysisOpen ? "rotate-180" : "")">
51+
<path d="m6 9 6 6 6-6"></path>
52+
</svg>
53+
</button>
54+
@if (_isTextAnalysisOpen)
55+
{
56+
<div class="ml-2 mt-1">
57+
@foreach (var tool in ToolDataService.GetTextAnalysisTools().Where(t => !t.IsComingSoon).Take(3))
58+
{
59+
<a @onclick="() => NavigateToTool(tool.Href)" class="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground flex items-center gap-3 cursor-pointer">
60+
@((MarkupString)tool.Icon.Replace("w-6 h-6", "w-4 h-4"))
61+
@tool.Title
62+
</a>
63+
}
64+
</div>
65+
}
66+
</div>
67+
68+
@* Text Formatting Section *@
69+
<div class="mb-1">
70+
<button @onclick="() => ToggleTextFormattingSection()" type="button" class="flex items-center justify-between w-full px-2 py-1.5 text-sm font-semibold text-muted-foreground hover:text-foreground transition-colors rounded hover:bg-muted/50">
71+
Text Formatting
72+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down w-4 h-4 transition-transform @(_isTextFormattingOpen ? "rotate-180" : "")">
73+
<path d="m6 9 6 6 6-6"></path>
74+
</svg>
75+
</button>
76+
@if (_isTextFormattingOpen)
77+
{
78+
<div class="ml-2 mt-1">
79+
@foreach (var tool in ToolDataService.GetTextFormattingTools().Where(t => !t.IsComingSoon).Take(4))
80+
{
81+
<a @onclick="() => NavigateToTool(tool.Href)" class="relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground flex items-center gap-3 cursor-pointer">
82+
@((MarkupString)tool.Icon.Replace("w-6 h-6", "w-4 h-4"))
83+
@tool.Title
84+
</a>
85+
}
86+
</div>
87+
}
88+
</div>
2489
</div>
2590
}
2691
</div>
@@ -70,6 +135,9 @@
70135
{
71136
private bool _isMobileMenuOpen;
72137
private bool _isToolsDropdownOpen;
138+
private bool _isTextCaseOpen = false;
139+
private bool _isTextAnalysisOpen = false;
140+
private bool _isTextFormattingOpen = false;
73141
private bool _isDarkMode = false; // Will be initialized from localStorage
74142
75143
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -78,6 +146,10 @@
78146
{
79147
// Initialize theme from localStorage or system preference
80148
_isDarkMode = await JsRuntime.InvokeAsync<bool>("getTheme");
149+
150+
// Setup click outside handler for dropdown
151+
await JsRuntime.InvokeVoidAsync("setupClickOutside", "tools-dropdown", DotNetObjectReference.Create(this));
152+
81153
StateHasChanged();
82154
}
83155
}
@@ -93,4 +165,50 @@
93165
await JsRuntime.InvokeVoidAsync("toggleTheme", _isDarkMode);
94166
StateHasChanged();
95167
}
168+
169+
[JSInvokable]
170+
public void CloseDropdown()
171+
{
172+
_isToolsDropdownOpen = false;
173+
_isTextCaseOpen = false;
174+
_isTextAnalysisOpen = false;
175+
_isTextFormattingOpen = false;
176+
StateHasChanged();
177+
}
178+
179+
private void NavigateToTool(string href)
180+
{
181+
// Close the dropdown first
182+
CloseDropdown();
183+
184+
// Navigate to the tool
185+
Navigation.NavigateTo(href);
186+
}
187+
188+
private void ToggleTextCaseSection()
189+
{
190+
// Close other sections and toggle this one
191+
_isTextAnalysisOpen = false;
192+
_isTextFormattingOpen = false;
193+
_isTextCaseOpen = !_isTextCaseOpen;
194+
StateHasChanged();
195+
}
196+
197+
private void ToggleTextAnalysisSection()
198+
{
199+
// Close other sections and toggle this one
200+
_isTextCaseOpen = false;
201+
_isTextFormattingOpen = false;
202+
_isTextAnalysisOpen = !_isTextAnalysisOpen;
203+
StateHasChanged();
204+
}
205+
206+
private void ToggleTextFormattingSection()
207+
{
208+
// Close other sections and toggle this one
209+
_isTextCaseOpen = false;
210+
_isTextAnalysisOpen = false;
211+
_isTextFormattingOpen = !_isTextFormattingOpen;
212+
StateHasChanged();
213+
}
96214
}

TextHub/Components/Shared/HomeHeroSection.razor

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
@using TextHub.Services.Data
2+
@inject ToolDataService ToolDataService
3+
14
<section class="relative py-16 md:py-24 px-4 overflow-hidden">
25
<!-- Background decoration -->
36
<div class="absolute inset-0 -z-10">
@@ -14,7 +17,7 @@
1417
<path d="m14.063 8.5 2.937 3-2.937 3"></path>
1518
<path d="M12 17.5v-3a2 2 0 0 0-2-2H7"></path>
1619
</svg>
17-
<span>20+ Free Text Tools</span>
20+
<span>@TotalActiveTools+ Free Text Tools</span>
1821
</div>
1922

2023
<!-- Main heading -->
@@ -51,19 +54,25 @@
5154
</div>
5255

5356
<!-- Stats -->
54-
<div class="grid grid-cols-1 sm:grid-cols-3 gap-8 max-w-2xl mx-auto animate-fade-in" style="animation-delay: 0.8s;">
57+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-8 max-w-2xl mx-auto animate-fade-in" style="animation-delay: 0.8s;">
5558
<div class="text-center">
56-
<div class="text-3xl md:text-4xl font-bold text-primary mb-2">20+</div>
59+
<div class="text-3xl md:text-4xl font-bold text-primary mb-2">@TotalActiveTools+</div>
5760
<div class="text-sm text-muted-foreground">Text Tools</div>
5861
</div>
5962
<div class="text-center">
6063
<div class="text-3xl md:text-4xl font-bold text-primary mb-2">100%</div>
6164
<div class="text-sm text-muted-foreground">Free to Use</div>
6265
</div>
63-
<div class="text-center">
64-
<div class="text-3xl md:text-4xl font-bold text-primary mb-2">0</div>
65-
<div class="text-sm text-muted-foreground">Registration</div>
66-
</div>
66+
6767
</div>
6868
</div>
69-
</section>
69+
</section>
70+
71+
@code {
72+
private int TotalActiveTools { get; set; }
73+
74+
protected override void OnInitialized()
75+
{
76+
TotalActiveTools = ToolDataService.GetTotalActiveToolsCount();
77+
}
78+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
@using TextHub.Models
2+
@using TextHub.Services.Data
3+
@inject ToolDataService ToolDataService
4+
@inject NavigationManager Navigation
5+
6+
<div class="relative max-w-4xl mx-auto mb-16 px-4">
7+
<div class="text-center mb-8">
8+
<h2 class="text-3xl md:text-4xl font-bold text-foreground mb-4">
9+
Find the Perfect Tool
10+
</h2>
11+
<p class="text-lg text-muted-foreground">
12+
Search through our collection of text utilities to find exactly what you need
13+
</p>
14+
</div>
15+
16+
<div class="relative">
17+
<div class="absolute inset-y-0 left-0 pl-6 flex items-center pointer-events-none">
18+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search text-muted-foreground">
19+
<circle cx="11" cy="11" r="8"></circle>
20+
<path d="m21 21-4.35-4.35"></path>
21+
</svg>
22+
</div>
23+
<input
24+
@bind="SearchTerm"
25+
@bind:event="oninput"
26+
type="text"
27+
placeholder="Search for text tools... (e.g., 'uppercase', 'word count', 'format')"
28+
class="w-full pl-16 pr-16 py-5 bg-background/80 backdrop-blur-sm border-2 border-border rounded-2xl text-lg text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-4 focus:ring-primary/20 focus:border-primary transition-all duration-300 search-input shadow-lg"
29+
/>
30+
@if (!string.IsNullOrEmpty(SearchTerm))
31+
{
32+
<button
33+
@onclick="ClearSearch"
34+
class="absolute inset-y-0 right-0 pr-6 flex items-center text-muted-foreground hover:text-foreground transition-colors"
35+
>
36+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x">
37+
<path d="M18 6 6 18"></path>
38+
<path d="M6 6l12 12"></path>
39+
</svg>
40+
</button>
41+
}
42+
</div>
43+
44+
@if (!string.IsNullOrEmpty(SearchTerm))
45+
{
46+
<div class="mt-6 text-center">
47+
<span class="text-sm text-muted-foreground">
48+
Found @FilteredTools.Count() tool@(FilteredTools.Count() == 1 ? "" : "s") for "<span class="text-primary font-medium">@SearchTerm</span>"
49+
</span>
50+
</div>
51+
52+
@if (FilteredTools.Any())
53+
{
54+
<div class="mt-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
55+
@foreach (var tool in FilteredTools.Take(6))
56+
{
57+
<div @onclick="() => NavigateToTool(tool.Href)" class="bg-card p-6 rounded-xl border border-border hover:shadow-card-hover transition-all duration-300 cursor-pointer group hover:scale-105">
58+
<div class="flex items-start gap-4">
59+
<div class="flex-shrink-0 p-3 bg-primary/10 rounded-lg group-hover:bg-primary/20 transition-colors">
60+
@((MarkupString)tool.Icon.Replace("w-6 h-6", "w-6 h-6"))
61+
</div>
62+
<div class="flex-1 min-w-0">
63+
<h3 class="text-lg font-semibold text-foreground group-hover:text-primary transition-colors">
64+
@tool.Title
65+
</h3>
66+
<p class="text-sm text-muted-foreground mt-1 line-clamp-2">
67+
@tool.Description
68+
</p>
69+
@if (tool.IsComingSoon)
70+
{
71+
<span class="inline-block mt-2 px-2 py-1 text-xs font-medium bg-muted text-muted-foreground rounded-full">
72+
Coming Soon
73+
</span>
74+
}
75+
</div>
76+
</div>
77+
</div>
78+
}
79+
</div>
80+
81+
@if (FilteredTools.Count() > 6)
82+
{
83+
<div class="mt-6 text-center">
84+
<button @onclick="ShowAllResults" class="btn-primary">
85+
View All @FilteredTools.Count() Results
86+
</button>
87+
</div>
88+
}
89+
}
90+
else
91+
{
92+
<div class="mt-8 text-center py-12">
93+
<div class="inline-flex items-center justify-center p-4 bg-muted/30 rounded-full mb-4">
94+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-search-x text-muted-foreground">
95+
<path d="M13.5 21c-.83 0-1.5-.67-1.5-1.5v-6c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5v6c0 .83-.67 1.5-1.5 1.5z"></path>
96+
<path d="M10.5 3H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h4.5"></path>
97+
<path d="M2 12h2"></path>
98+
<path d="M5 12h2"></path>
99+
<path d="M8 12h2"></path>
100+
</svg>
101+
</div>
102+
<h3 class="text-lg font-semibold text-foreground mb-2">No tools found</h3>
103+
<p class="text-muted-foreground">Try searching with different keywords or browse our categories below</p>
104+
</div>
105+
}
106+
}
107+
</div>
108+
109+
@code {
110+
private string _searchTerm = string.Empty;
111+
private List<Tool> _allTools = new();
112+
private List<Tool> _filteredTools = new();
113+
114+
public string SearchTerm
115+
{
116+
get => _searchTerm;
117+
set
118+
{
119+
_searchTerm = value;
120+
FilterTools();
121+
}
122+
}
123+
124+
public IEnumerable<Tool> FilteredTools => _filteredTools;
125+
126+
protected override void OnInitialized()
127+
{
128+
// Get all tools from all categories
129+
_allTools.AddRange(ToolDataService.GetTextCaseTools());
130+
_allTools.AddRange(ToolDataService.GetTextAnalysisTools());
131+
_allTools.AddRange(ToolDataService.GetTextFormattingTools());
132+
_allTools.AddRange(ToolDataService.GetTextConversionTools());
133+
_allTools.AddRange(ToolDataService.GetTextSecurityTools());
134+
_allTools.AddRange(ToolDataService.GetTextUtilityTools());
135+
136+
_filteredTools = _allTools;
137+
}
138+
139+
private void FilterTools()
140+
{
141+
if (string.IsNullOrWhiteSpace(_searchTerm))
142+
{
143+
_filteredTools = _allTools;
144+
}
145+
else
146+
{
147+
var searchLower = _searchTerm.ToLowerInvariant();
148+
_filteredTools = _allTools.Where(tool =>
149+
tool.Title.ToLowerInvariant().Contains(searchLower) ||
150+
tool.Description.ToLowerInvariant().Contains(searchLower) ||
151+
tool.Href.ToLowerInvariant().Contains(searchLower)
152+
).ToList();
153+
}
154+
}
155+
156+
private void ClearSearch()
157+
{
158+
SearchTerm = string.Empty;
159+
}
160+
161+
private void NavigateToTool(string href)
162+
{
163+
Navigation.NavigateTo(href);
164+
}
165+
166+
private void ShowAllResults()
167+
{
168+
// For now, just show more results in the current view
169+
// In the future, this could navigate to a dedicated search results page
170+
}
171+
}

0 commit comments

Comments
 (0)