Compare commits

...

19 Commits

Author SHA1 Message Date
masoodafar-web 00f805dcd8 fix: update gateway URL to new front office endpoint
Build and Deploy / build (push) Successful in 2m16s
2025-12-27 01:13:21 +03:30
masoodafar-web 1e086c389a fix: update gateway URL to new production endpoint
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2025-12-26 10:06:39 +03:30
masoodafar-web c136e853c6 fix: update gateway URL to new production endpoint
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2025-12-26 10:06:07 +03:30
masoodafar-web 54f8781774 feat: enhance NetworkMembershipService with updated member properties and add referral code functionality; improve org chart styling and debug logging
Build and Deploy to Production / build-and-deploy (push) Successful in 2m3s
2025-12-26 05:52:17 +03:30
masoodafar-web 1c61fe6846 feat: update NetworkMembership Protobuf version and add referral code functionality with UI enhancements
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2025-12-26 05:51:24 +03:30
masoodafar-web 516da671c6 feat(AppVersion): Add AppVersionService for cache invalidation on version update
Build and Deploy to Production / build-and-deploy (push) Successful in 1m37s
- Add AppVersionService to check version and clear cache if needed
- Update Configuration.Protobuf package to 0.0.4 with AppVersion proto
- Integrate version check in App.razor on app initialization
2025-12-25 18:45:01 +03:30
masoodafar-web 45a291b529 fix: update fallback behavior to return empty responses when backend is unavailable 2025-12-25 17:57:39 +03:30
masoodafar-web cb6102191e fix: update gateway URL to secure production endpoint
Build and Deploy to Production / build-and-deploy (push) Successful in 2m21s
2025-12-25 17:35:32 +03:30
admin 7d491059f8 fix: correct dockerfile path and registry
Build and Deploy to Production / build-and-deploy (push) Successful in 1m29s
2025-12-23 22:34:57 +00:00
admin 6b41ae8b5e fix: correct dockerfile path and registry
Build and Deploy to Production / build-and-deploy (push) Has been cancelled
2025-12-23 22:34:48 +00:00
admin 8be14089c6 feat: add production deployment workflow
Build and Deploy to Production / build-and-deploy (push) Failing after 11s
2025-12-23 22:28:27 +00:00
admin fa5987bf3e fix: update appsettings for production K8s 2025-12-23 22:18:38 +00:00
admin ac5818855c fix: update appsettings for production K8s 2025-12-23 22:18:18 +00:00
masoud 8f58f01781 Update src/FrontOffice.Main/Pages/Store/CheckoutSummary.razor.cs 2025-12-23 04:12:18 -08:00
masoodafar-web cca89e4100 feat: update membership and network terminology to customer club language 2025-12-21 16:51:39 +03:30
masoodafar-web d7abcf4278 feat: update membership and network terminology to customer club language 2025-12-21 16:00:49 +03:30
masoodafar-web 10331663ce feat: disable payment button and add coming soon label 2025-12-20 05:57:53 +03:30
masoodafar-web 34f5cbd5a8 fix: update gateway URL to production environment 2025-12-20 05:23:14 +03:30
masoodafar-web 8ae25d2b38 feat: update deployment branch to stage-new in workflow configuration 2025-12-20 05:05:41 +03:30
21 changed files with 546 additions and 251 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ name: Push nuget and docker image Actions Workflow
on: on:
push: push:
branches: branches:
- stage - stage-new
jobs: jobs:
Deploy: Deploy:
runs-on: windows runs-on: windows
+83
View File
@@ -0,0 +1,83 @@
name: Build and Deploy to Production
on:
push:
branches:
- production
env:
REGISTRY: gitea-svc:3000
EXTERNAL_REGISTRY: git.foursat.afrino.co
IMAGE_NAME: admin/frontoffice
jobs:
build-and-deploy:
runs-on: ubuntu-latest
container:
image: docker:latest
options: --privileged
env:
HTTP_PROXY: http://proxyuser:87zH26nbqT2@46.249.98.211:3128
HTTPS_PROXY: http://proxyuser:87zH26nbqT2@46.249.98.211:3128
NO_PROXY: localhost,127.0.0.1,gitea-svc,45.149.79.127,194.5.195.53,10.0.0.0/8
steps:
- name: Install dependencies
run: |
apk add --no-cache git curl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/
- name: Start Docker daemon
run: |
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'DAEMON'
{
"insecure-registries": ["git.foursat.afrino.co", "gitea-svc:3000"]
}
DAEMON
mkdir -p ~/.docker
cat > ~/.docker/config.json << 'CONF'
{
"proxies": {
"default": {
"httpProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128",
"httpsProxy": "http://proxyuser:87zH26nbqT2@46.249.98.211:3128",
"noProxy": "localhost,127.0.0.1,gitea-svc,194.5.195.53,10.0.0.0/8"
}
}
}
CONF
dockerd &
for i in $(seq 1 30); do docker info >/dev/null 2>&1 && break || sleep 2; done
- name: Checkout code
run: |
git clone --depth 1 --branch production http://gitea-svc:3000/admin/FrontOffice.git .
git log -1 --format="%H %s"
- name: Build Docker Image
run: |
cd src
docker build -f FrontOffice.Main/Dockerfile \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
-t ${{ env.EXTERNAL_REGISTRY }}/${{ env.IMAGE_NAME }}:prod \
--build-arg HTTP_PROXY=http://proxyuser:87zH26nbqT2@46.249.98.211:3128 \
--build-arg HTTPS_PROXY=http://proxyuser:87zH26nbqT2@46.249.98.211:3128 \
.
- name: Push to Registry
run: |
echo "87zH26nbqT" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod
echo "87zH26nbqT" | docker login ${{ env.EXTERNAL_REGISTRY }} -u admin --password-stdin
docker push ${{ env.EXTERNAL_REGISTRY }}/${{ env.IMAGE_NAME }}:prod
- name: Deploy to Production
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_PROD }}" | base64 -d > ~/.kube/config
kubectl rollout restart deployment/frontoffice || echo "Deployment not found"
kubectl rollout status deployment/frontoffice --timeout=5m || echo "Rollout pending"
+5
View File
@@ -11,10 +11,15 @@ public partial class App
{ {
[Inject] private ILocalStorageService LocalStorage { get; set; } = default!; [Inject] private ILocalStorageService LocalStorage { get; set; } = default!;
[Inject] private AuthService AuthService { get; set; } = default!; [Inject] private AuthService AuthService { get; set; } = default!;
[Inject] private AppVersionService AppVersionService { get; set; } = default!;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
// Check app version and clear cache if needed
await AppVersionService.CheckVersionAndClearCacheIfNeededAsync();
// Check for referral code in URL query parameters // Check for referral code in URL query parameters
var uri = Navigation.ToAbsoluteUri(Navigation.Uri); var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
var query = QueryHelpers.ParseQuery(uri.Query); var query = QueryHelpers.ParseQuery(uri.Query);
@@ -20,6 +20,7 @@ using FrontOffice.BFF.NetworkMembership.Protobuf.Protos.NetworkMembership;
using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop; using FrontOffice.BFF.DiscountShop.Protobuf.Protos.DiscountShop;
using FrontOffice.BFF.City.Protobuf; using FrontOffice.BFF.City.Protobuf;
using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration; using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration;
using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
using FrontOffice.Main.Utilities; using FrontOffice.Main.Utilities;
namespace Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.DependencyInjection;
@@ -61,6 +62,8 @@ public static class ConfigureServices
services.AddScoped<ClubConfigurationService>(); services.AddScoped<ClubConfigurationService>();
services.AddScoped<NetworkMembershipService>(); services.AddScoped<NetworkMembershipService>();
services.AddScoped<CommissionService>(); services.AddScoped<CommissionService>();
// App Version Service for cache invalidation
services.AddScoped<AppVersionService>();
// Device detection: very light, dependency-free // Device detection: very light, dependency-free
services.AddTransient<IDeviceDetector, DeviceDetector>(); services.AddTransient<IDeviceDetector, DeviceDetector>();
// PDF generation (Chromium only) // PDF generation (Chromium only)
@@ -110,6 +113,7 @@ public static class ConfigureServices
services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>); services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>);
services.AddScoped(CreateAuthenticatedClient<DiscountShopContract.DiscountShopContractClient>); services.AddScoped(CreateAuthenticatedClient<DiscountShopContract.DiscountShopContractClient>);
services.AddScoped(CreateAuthenticatedClient<CityContract.CityContractClient>); services.AddScoped(CreateAuthenticatedClient<CityContract.CityContractClient>);
services.AddScoped(CreateAuthenticatedClient<AppVersionContract.AppVersionContractClient>);
return services; return services;
} }
+2 -2
View File
@@ -13,11 +13,11 @@
<PackageReference Include="Foursat.FrontOffice.BFF.City.Protobuf" Version="0.0.2" /> <PackageReference Include="Foursat.FrontOffice.BFF.City.Protobuf" Version="0.0.2" />
<PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.4" /> <PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.4" />
<PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.3" /> <PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.3" />
<PackageReference Include="Foursat.FrontOffice.BFF.Configuration.Protobuf" Version="0.0.3" /> <PackageReference Include="Foursat.FrontOffice.BFF.Configuration.Protobuf" Version="0.0.4" />
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.3" /> --> <!-- <PackageReference Include="Foursat.FrontOffice.BFF.ClubMembership.Protobuf" Version="0.0.3" /> -->
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.2" /> --> <!-- <PackageReference Include="Foursat.FrontOffice.BFF.Commission.Protobuf" Version="0.0.2" /> -->
<PackageReference Include="Foursat.FrontOffice.BFF.DiscountShop.Protobuf" Version="0.0.3" /> <PackageReference Include="Foursat.FrontOffice.BFF.DiscountShop.Protobuf" Version="0.0.3" />
<PackageReference Include="Foursat.FrontOffice.BFF.NetworkMembership.Protobuf" Version="0.0.4" /> <PackageReference Include="Foursat.FrontOffice.BFF.NetworkMembership.Protobuf" Version="0.0.5" />
<PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.114" /> <PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.114" />
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.NetworkMembership.Protobuf" Version="0.0.2" />--> <!-- <PackageReference Include="Foursat.FrontOffice.BFF.NetworkMembership.Protobuf" Version="0.0.2" />-->
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.113" /> --> <!-- <PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.113" /> -->
@@ -31,7 +31,7 @@
@if (_membership.IsActive) @if (_membership.IsActive)
{ {
<MudAlert Severity="Severity.Success" Variant="Variant.Outlined"> <MudAlert Severity="Severity.Success" Variant="Variant.Outlined">
<MudText>عضویت شما در باشگاه فعال است! از مزایای باشگاه مشتریان استفاده کنید.</MudText> <MudText>عضویت شما در باشگاه مشتریان فعال است و هم اکنون میتوانید از مزایای باشگاه مشتریان استفاده کنید.</MudText>
</MudAlert> </MudAlert>
@if (_membership.DaysRemaining.HasValue) @if (_membership.DaysRemaining.HasValue)
@@ -88,16 +88,16 @@
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2"> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Primary" Size="Size.Large" /> <MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Primary" Size="Size.Large" />
<MudStack Spacing="0"> <MudStack Spacing="0">
<MudText Typo="Typo.subtitle1">عضویت در شبکه بازاریابی</MudText> <MudText Typo="Typo.subtitle1">عضویت در باشگاه مشتریان</MudText>
<MudText Typo="Typo.caption" Color="Color.Default">عضویت در شبکه بازاریابی و دریافت پورسانت</MudText> <MudText Typo="Typo.caption" Color="Color.Default"> دریافت پاداش وفاداری</MudText>
</MudStack> </MudStack>
</MudStack> </MudStack>
<MudDivider /> <MudDivider />
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2"> <MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
<MudIcon Icon="@Icons.Material.Filled.GroupAdd" Color="Color.Secondary" Size="Size.Large" /> <MudIcon Icon="@Icons.Material.Filled.GroupAdd" Color="Color.Secondary" Size="Size.Large" />
<MudStack Spacing="0"> <MudStack Spacing="0">
<MudText Typo="Typo.subtitle1">جذب زیرمجموعه</MudText> <MudText Typo="Typo.subtitle1">معرفی مشتریان جدید</MudText>
<MudText Typo="Typo.caption" Color="Color.Default">امکان جذب زیرمجموعه و گسترش شبکه</MudText> <MudText Typo="Typo.caption" Color="Color.Default">توسعه باشگاه مشتریان</MudText>
</MudStack> </MudStack>
</MudStack> </MudStack>
</MudStack> </MudStack>
@@ -7,7 +7,7 @@
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6"> <MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
<MudStack Spacing="3"> <MudStack Spacing="3">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center"> <MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.h5">آمار شبکه من</MudText> <MudText Typo="Typo.h5">آمار باشگاه توسعه دهندگان</MudText>
<MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton> <MudButton Variant="Variant.Text" StartIcon="@Icons.Material.Filled.ArrowBack" Href="@RouteConstants.Profile.Index">بازگشت</MudButton>
</MudStack> </MudStack>
@@ -22,7 +22,7 @@
<MudItem xs="12" sm="6" md="3"> <MudItem xs="12" sm="6" md="3">
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudStack Spacing="1"> <MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">کل اعضای شبکه</MudText> <MudText Typo="Typo.subtitle2" Class="mud-text-secondary">اعضای باشگاه توسعه دهندگان</MudText>
<MudText Typo="Typo.h4" Color="Color.Primary">@_statistics.TotalMembers</MudText> <MudText Typo="Typo.h4" Color="Color.Primary">@_statistics.TotalMembers</MudText>
<MudIcon Icon="@Icons.Material.Filled.Groups" Color="Color.Primary" /> <MudIcon Icon="@Icons.Material.Filled.Groups" Color="Color.Primary" />
</MudStack> </MudStack>
@@ -31,7 +31,7 @@
<MudItem xs="12" sm="6" md="3"> <MudItem xs="12" sm="6" md="3">
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudStack Spacing="1"> <MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">شاخه چپ</MudText> <MudText Typo="Typo.subtitle2" Class="mud-text-secondary">بخش (A)</MudText>
<MudText Typo="Typo.h4" Color="Color.Info">@_statistics.LeftLegCount</MudText> <MudText Typo="Typo.h4" Color="Color.Info">@_statistics.LeftLegCount</MudText>
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Color="Color.Info" /> <MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Color="Color.Info" />
</MudStack> </MudStack>
@@ -40,7 +40,7 @@
<MudItem xs="12" sm="6" md="3"> <MudItem xs="12" sm="6" md="3">
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudStack Spacing="1"> <MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">شاخه راست</MudText> <MudText Typo="Typo.subtitle2" Class="mud-text-secondary">بخش (B)</MudText>
<MudText Typo="Typo.h4" Color="Color.Success">@_statistics.RightLegCount</MudText> <MudText Typo="Typo.h4" Color="Color.Success">@_statistics.RightLegCount</MudText>
<MudIcon Icon="@Icons.Material.Filled.ChevronRight" Color="Color.Success" /> <MudIcon Icon="@Icons.Material.Filled.ChevronRight" Color="Color.Success" />
</MudStack> </MudStack>
@@ -49,7 +49,7 @@
<MudItem xs="12" sm="6" md="3"> <MudItem xs="12" sm="6" md="3">
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudStack Spacing="1"> <MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Class="mud-text-secondary">عمق درخت</MudText> <MudText Typo="Typo.subtitle2" Class="mud-text-secondary">فاصله شعب</MudText>
<MudText Typo="Typo.h4" Color="Color.Secondary">@_statistics.TreeDepth</MudText> <MudText Typo="Typo.h4" Color="Color.Secondary">@_statistics.TreeDepth</MudText>
<MudIcon Icon="@Icons.Material.Filled.AccountTree" Color="Color.Secondary" /> <MudIcon Icon="@Icons.Material.Filled.AccountTree" Color="Color.Secondary" />
</MudStack> </MudStack>
@@ -59,10 +59,10 @@
<!-- تعادل شاخه‌ها --> <!-- تعادل شاخه‌ها -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">تعادل شاخه‌ها</MudText> <MudText Typo="Typo.h6" Class="mb-3">آمار جمعی فعالیت</MudText>
<MudStack Spacing="2"> <MudStack Spacing="2">
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center"> <MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body1">شاخه چپ: <strong>@_statistics.LeftLegCount</strong> عضو</MudText> <MudText Typo="Typo.body1">بخش (A): <strong>@_statistics.LeftLegCount</strong> عضو</MudText>
<MudProgressLinear Color="Color.Info" <MudProgressLinear Color="Color.Info"
Value="@GetLeftPercentage()" Value="@GetLeftPercentage()"
Size="Size.Medium" Size="Size.Medium"
@@ -70,7 +70,7 @@
Style="width: 200px;" /> Style="width: 200px;" />
</MudStack> </MudStack>
<MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center"> <MudStack Row="true" Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center">
<MudText Typo="Typo.body1">شاخه راست: <strong>@_statistics.RightLegCount</strong> عضو</MudText> <MudText Typo="Typo.body1">بخش (B): <strong>@_statistics.RightLegCount</strong> عضو</MudText>
<MudProgressLinear Color="Color.Success" <MudProgressLinear Color="Color.Success"
Value="@GetRightPercentage()" Value="@GetRightPercentage()"
Size="Size.Medium" Size="Size.Medium"
@@ -80,7 +80,7 @@
@if (!string.IsNullOrEmpty(_statistics.WeakerLeg)) @if (!string.IsNullOrEmpty(_statistics.WeakerLeg))
{ {
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined"> <MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
شاخه ضعیف‌تر: <strong>@GetLegText(_statistics.WeakerLeg)</strong> بخش کوچک تر: <strong>@GetLegText(_statistics.WeakerLeg)</strong>
</MudAlert> </MudAlert>
} }
</MudStack> </MudStack>
@@ -88,12 +88,12 @@
<!-- نمودار دایره‌ای --> <!-- نمودار دایره‌ای -->
<MudPaper Elevation="2" Class="pa-4 rounded-lg"> <MudPaper Elevation="2" Class="pa-4 rounded-lg">
<MudText Typo="Typo.h6" Class="mb-3">توزیع اعضا</MudText> <MudText Typo="Typo.h6" Class="mb-3">توزیع توسعه دهندگان</MudText>
<MudChart ChartType="ChartType.Donut" <MudChart ChartType="ChartType.Donut"
Width="300px" Width="300px"
Height="300px" Height="300px"
InputData="@(new double[] { _statistics.LeftLegCount, _statistics.RightLegCount })" InputData="@(new double[] { _statistics.LeftLegCount, _statistics.RightLegCount })"
InputLabels="@(new[] { "شاخه چپ", "شاخه راست" })" InputLabels="@(new[] { "بخش (A)", "بخش (B)" })"
ChartOptions="@_chartOptions" /> ChartOptions="@_chartOptions" />
</MudPaper> </MudPaper>
@@ -126,7 +126,7 @@
StartIcon="@Icons.Material.Filled.AccountTree" StartIcon="@Icons.Material.Filled.AccountTree"
Href="@RouteConstants.Profile.Tree" Href="@RouteConstants.Profile.Tree"
FullWidth="true"> FullWidth="true">
مشاهده درخت کامل مشاهده کل توسعه دهندگان
</MudButton> </MudButton>
<MudButton Variant="Variant.Outlined" <MudButton Variant="Variant.Outlined"
Color="Color.Secondary" Color="Color.Secondary"
@@ -140,7 +140,7 @@
else if (_hasError) else if (_hasError)
{ {
<MudAlert Severity="Severity.Error" Variant="Variant.Filled"> <MudAlert Severity="Severity.Error" Variant="Variant.Filled">
خطا در دریافت آمار شبکه. لطفا دوباره تلاش کنید. خطا در دریافت آمار توسعه دهندگان. لطفا دوباره تلاش کنید.
</MudAlert> </MudAlert>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="LoadStatisticsAsync">تلاش مجدد</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="LoadStatisticsAsync">تلاش مجدد</MudButton>
} }
@@ -61,15 +61,15 @@ public partial class NetworkStatisticsPage : ComponentBase
private string GetLegText(string leg) => leg.ToLower() switch private string GetLegText(string leg) => leg.ToLower() switch
{ {
"left" => "شاخه چپ", "left" => "بخش (A)",
"right" => "شاخه راست", "right" => "بخش (B)",
_ => leg _ => leg
}; };
private string GetPositionText(string position) => position.ToLower() switch private string GetPositionText(string position) => position.ToLower() switch
{ {
"left" => "چپ", "left" => "(A)",
"right" => "راست", "right" => "(B)",
_ => position _ => position
}; };
@@ -123,23 +123,14 @@ public partial class PackageDetail : IDisposable
private async Task LoadReviewsAsync() private async Task LoadReviewsAsync()
{ {
// TODO: Load reviews from API // TODO: Load reviews from API when endpoint is available
_reviews = new List<Review> _reviews = new List<Review>();
{
new() { UserName = "علی احمدی", Rating = 5, Comment = "عالی! کارمزد رو دقیق حساب می‌کنه و گزارش‌ها کامل هستن.", Date = "۱۴۰۲/۱۰/۰۵" },
new() { UserName = "مریم رضایی", Rating = 4, Comment = "رابط کاربری خوبی داره، فقط سرعت بارگذاری می‌تونه بهتر بشه.", Date = "۱۴۰۲/۰۹/۲۲" },
new() { UserName = "حسن کریمی", Rating = 5, Comment = "پشتیبانی فوق‌العاده سریع و حرفه‌ای داشتن. پیشنهاد می‌کنم.", Date = "۱۴۰۲/۰۹/۱۵" }
};
} }
private async Task LoadRelatedPackagesAsync() private async Task LoadRelatedPackagesAsync()
{ {
// TODO: Load related packages from API // TODO: Load related packages from API when endpoint is available
_relatedPackages = new List<RelatedPackage> _relatedPackages = new List<RelatedPackage>();
{
new() { Id = "2", Title = "پکیج رشد", ShortDescription = "مناسب برای تیم‌های در حال توسعه", Image = "images/package1.jpg", Pricing = new PricingInfo { FinalPrice = 750000 } },
new() { Id = "3", Title = "پکیج حرفه‌ای", ShortDescription = "برای کسب‌وکارهای بزرگ", Image = "images/package2.jpg", Pricing = new PricingInfo { FinalPrice = 1200000 } }
};
} }
private async Task PurchasePackage() private async Task PurchasePackage()
@@ -77,7 +77,7 @@
برای فعال‌سازی لینک دعوت و دعوت دوستان خود به جمع مشتریان ویژه کارابازار، کافی است ابتدا <strong>پکیج پایه ۵۶ میلیونی</strong> را تهیه کنید و سپس در <strong>باشگاه مشتریان</strong> عضو شوید. برای فعال‌سازی لینک دعوت و دعوت دوستان خود به جمع مشتریان ویژه کارابازار، کافی است ابتدا <strong>پکیج پایه ۵۶ میلیونی</strong> را تهیه کنید و سپس در <strong>باشگاه مشتریان</strong> عضو شوید.
</MudText> </MudText>
<MudText Typo="Typo.body2" Align="Align.Center" Class="mud-text-secondary"> <MudText Typo="Typo.body2" Align="Align.Center" Class="mud-text-secondary">
پس از تکمیل این مراحل، می‌توانید از مزایای ویژه عضویت در شبکه فروش بهره‌مند شده و با معرفی دوستان، کمیسیون دریافت کنید. از مزایای ویژه عضویت در باشگاه مشتریان کارابازار سلامت بهره‌مند شده و با فعالیت در زمینه توسعه فروشگاه‌ها پاداش دریافت کنید.
</MudText> </MudText>
<MudButton Variant="Variant.Filled" <MudButton Variant="Variant.Filled"
Color="Color.Primary" Color="Color.Primary"
@@ -219,8 +219,8 @@
<MudCard Elevation="1" Class="rounded-lg profile-tile"> <MudCard Elevation="1" Class="rounded-lg profile-tile">
<MudCardContent Class="d-flex flex-column align-center pa-4"> <MudCardContent Class="d-flex flex-column align-center pa-4">
<MudIcon Icon="@Icons.Material.Filled.Groups" Size="Size.Large" Color="Color.Info" /> <MudIcon Icon="@Icons.Material.Filled.Groups" Size="Size.Large" Color="Color.Info" />
<MudText Typo="Typo.subtitle1" Class="mt-2">شبکه من</MudText> <MudText Typo="Typo.subtitle1" Class="mt-2">آمار باشگاه</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">آمار و درخت شبکه</MudText> <MudText Typo="Typo.caption" Class="mud-text-secondary">آمار جزئیات باشگاه</MudText>
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
</MudLink> </MudLink>
@@ -230,7 +230,7 @@
<MudCard Elevation="1" Class="rounded-lg profile-tile"> <MudCard Elevation="1" Class="rounded-lg profile-tile">
<MudCardContent Class="d-flex flex-column align-center pa-4"> <MudCardContent Class="d-flex flex-column align-center pa-4">
<MudIcon Icon="@Icons.Material.Filled.Payments" Size="Size.Large" Color="Color.Success" /> <MudIcon Icon="@Icons.Material.Filled.Payments" Size="Size.Large" Color="Color.Success" />
<MudText Typo="Typo.subtitle1" Class="mt-2">کمیسیون</MudText> <MudText Typo="Typo.subtitle1" Class="mt-2">پاداش</MudText>
<MudText Typo="Typo.caption" Class="mud-text-secondary">درآمد و تاریخچه</MudText> <MudText Typo="Typo.caption" Class="mud-text-secondary">درآمد و تاریخچه</MudText>
</MudCardContent> </MudCardContent>
</MudCard> </MudCard>
@@ -266,12 +266,12 @@
<MudText Typo="Typo.body2" Class="mud-text-secondary"> <MudText Typo="Typo.body2" Class="mud-text-secondary">
پرداخت آنی از طریق درگاه بانکی پرداخت آنی از طریق درگاه بانکی
</MudText> </MudText>
<MudButton Variant="Variant.Filled" <MudButton Variant="Variant.Filled" Disabled="true"
Color="Color.Primary" Color="Color.Primary"
FullWidth="true" FullWidth="true"
StartIcon="@Icons.Material.Filled.Payment" StartIcon="@Icons.Material.Filled.Payment"
OnClick="DirectPayment"> OnClick="DirectPayment">
پرداخت با کارت بانکی پرداخت با کارت بانکی(بزودی)
</MudButton> </MudButton>
</MudStack> </MudStack>
</MudPaper> </MudPaper>
@@ -45,7 +45,9 @@ public partial class CheckoutSummary : ComponentBase
private async Task LoadWalletBalance() private async Task LoadWalletBalance()
{ {
var walletResult = await WalletService.GetBalancesAsync(); var walletResult = await WalletService.GetBalancesAsync();
walletBalance = walletResult.CreditBalance + walletResult.NetworkBalance; walletBalance = walletResult.CreditBalance
// + walletResult.NetworkBalance
;
} }
private async Task LoadAddresses() private async Task LoadAddresses()
@@ -0,0 +1,156 @@
using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
using Blazored.LocalStorage;
namespace FrontOffice.Main.Utilities;
/// <summary>
/// سرویس مدیریت نسخه اپلیکیشن و کش
/// وقتی نسخه جدید منتشر بشه، این سرویس متوجه میشه و کش رو پاک می‌کنه
/// </summary>
public class AppVersionService
{
private readonly AppVersionContract.AppVersionContractClient _client;
private readonly ILocalStorageService _localStorage;
private readonly ILogger<AppVersionService> _logger;
private const string APP_NAME = "FrontOffice";
private const string LOCAL_VERSION_KEY = "app_version";
private const string LAST_CHECK_KEY = "app_version_last_check";
public AppVersionService(
AppVersionContract.AppVersionContractClient client,
ILocalStorageService localStorage,
ILogger<AppVersionService> logger)
{
_client = client;
_localStorage = localStorage;
_logger = logger;
}
/// <summary>
/// بررسی نسخه اپلیکیشن و پاک کردن کش در صورت نیاز
/// </summary>
public async Task CheckVersionAndClearCacheIfNeededAsync()
{
try
{
// دریافت نسخه فعلی از localStorage
var localVersion = await _localStorage.GetItemAsStringAsync(LOCAL_VERSION_KEY);
// بررسی نسخه از سرور
var response = await _client.GetAppVersionAsync(new GetAppVersionRequest
{
AppName = APP_NAME,
CurrentClientVersion = localVersion
});
if (!response.Found)
{
_logger.LogWarning("App version not found on server for {AppName}", APP_NAME);
return;
}
var serverVersion = response.CurrentVersion;
// اگر نسخه جدید باشه یا پاک کردن کش لازم باشه
if (localVersion != serverVersion || response.RequiresFullCacheClear)
{
_logger.LogInformation(
"New version detected: {OldVersion} -> {NewVersion}, CacheClear: {CacheClear}",
localVersion ?? "null", serverVersion, response.RequiresFullCacheClear);
// پاک کردن کش
await ClearAllCacheAsync();
// ذخیره نسخه جدید
await _localStorage.SetItemAsStringAsync(LOCAL_VERSION_KEY, serverVersion);
await _localStorage.SetItemAsStringAsync(LAST_CHECK_KEY, DateTime.UtcNow.ToString("O"));
_logger.LogInformation("Cache cleared and version updated to {Version}", serverVersion);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking app version");
}
}
/// <summary>
/// پاک کردن همه کش‌های اپلیکیشن
/// </summary>
private async Task ClearAllCacheAsync()
{
try
{
// لیست کلیدهایی که باید حفظ بشن (مثل توکن و اطلاعات کاربر)
var keysToKeep = new HashSet<string>
{
"access_token",
"refresh_token",
"user_info",
"user_roles"
};
// دریافت همه کلیدها
var allKeys = await _localStorage.KeysAsync();
// پاک کردن کلیدهایی که در لیست حفظ نیستن
foreach (var key in allKeys)
{
if (!keysToKeep.Contains(key) && key != LOCAL_VERSION_KEY)
{
await _localStorage.RemoveItemAsync(key);
}
}
_logger.LogInformation("Local storage cache cleared, {Count} keys removed",
allKeys.Count() - keysToKeep.Count);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error clearing cache");
}
}
/// <summary>
/// دریافت اطلاعات نسخه فعلی
/// </summary>
public async Task<AppVersionInfo?> GetCurrentVersionInfoAsync()
{
try
{
var response = await _client.GetAppVersionAsync(new GetAppVersionRequest
{
AppName = APP_NAME
});
if (!response.Found)
return null;
return new AppVersionInfo
{
CurrentVersion = response.CurrentVersion,
MinRequiredVersion = response.MinRequiredVersion,
RequiresUpdate = response.RequiresUpdate,
UpdateMessage = response.UpdateMessage,
ReleaseNotes = response.ReleaseNotes,
LastUpdated = response.LastUpdated?.ToDateTime()
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting version info");
return null;
}
}
}
public class AppVersionInfo
{
public string CurrentVersion { get; set; } = string.Empty;
public string MinRequiredVersion { get; set; } = string.Empty;
public bool RequiresUpdate { get; set; }
public string? UpdateMessage { get; set; }
public string? ReleaseNotes { get; set; }
public DateTime? LastUpdated { get; set; }
}
@@ -35,12 +35,12 @@ public class ClubMembershipService
} }
catch catch
{ {
// Fallback to mock data if backend is unavailable // Return empty membership if backend is unavailable
return new ClubMembershipDto return new ClubMembershipDto
{ {
UserId = 1, UserId = 0,
IsActive = false, IsActive = false,
Status = "Inactive", Status = string.Empty,
DaysRemaining = null DaysRemaining = null
}; };
} }
@@ -65,8 +65,8 @@ public class CommissionService
} }
catch catch
{ {
// Fallback to mock data if backend is unavailable // Return empty list if backend is unavailable
return GenerateMockWeekDefinitions(); return new List<WeekDefinitionDto>();
} }
} }
@@ -129,8 +129,14 @@ public class CommissionService
} }
catch catch
{ {
// Fallback to mock data if backend is unavailable // Return empty response if backend is unavailable
return GenerateMockPayoutsResponse(weekDefinitionId, status, pageNumber, pageSize); return new CommissionPayoutsResponseDto
{
Payouts = new List<CommissionPayoutDto>(),
TotalCount = 0,
PageNumber = pageNumber,
PageSize = pageSize
};
} }
} }
@@ -175,22 +181,22 @@ public class CommissionService
}; };
} }
return CreateMockWeeklyBalance(); return CreateEmptyWeeklyBalance();
} }
catch catch
{ {
return CreateMockWeeklyBalance(); return CreateEmptyWeeklyBalance();
} }
} }
#region Helper Methods #region Helper Methods
private static WeeklyBalanceDto CreateMockWeeklyBalance() private static WeeklyBalanceDto CreateEmptyWeeklyBalance()
{ {
return new WeeklyBalanceDto return new WeeklyBalanceDto
{ {
WeekDefinitionId = 0, WeekDefinitionId = 0,
WeekDisplayName = "داده‌ای یافت نشد", WeekDisplayName = string.Empty,
LeftBalance = 0, LeftBalance = 0,
RightBalance = 0, RightBalance = 0,
MinBalance = 0, MinBalance = 0,
@@ -198,94 +204,10 @@ public class CommissionService
CalculatedCommission = 0, CalculatedCommission = 0,
LeftCarryover = 0, LeftCarryover = 0,
RightCarryover = 0, RightCarryover = 0,
StartDate = DateTime.Now.AddDays(-7), StartDate = DateTime.MinValue,
EndDate = DateTime.Now EndDate = DateTime.MinValue
}; };
} }
private static CommissionPayoutsResponseDto GenerateMockPayoutsResponse(
long? weekDefinitionId, string? status, int pageNumber, int pageSize)
{
var allPayouts = GenerateMockPayouts(50);
var filtered = allPayouts.AsEnumerable();
if (weekDefinitionId.HasValue)
filtered = filtered.Where(p => p.WeekDefinitionId == weekDefinitionId.Value);
if (!string.IsNullOrEmpty(status))
filtered = filtered.Where(p => p.Status == status);
var total = filtered.Count();
var paged = filtered.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();
return new CommissionPayoutsResponseDto
{
Payouts = paged,
TotalCount = total,
PageNumber = pageNumber,
PageSize = pageSize
};
}
private static List<CommissionPayoutDto> GenerateMockPayouts(int count)
{
var payouts = new List<CommissionPayoutDto>();
var random = new Random();
var statusTexts = new[] { "ایجاد شده", "پرداخت شده", "درخواست برداشت", "برداشت شده", "لغو شده" };
var statusColors = new[] { "Info", "Success", "Warning", "Success", "Error" };
for (int i = 0; i < count; i++)
{
var statusIndex = random.Next(statusTexts.Length);
var weekNum = 50 - i;
var balances = random.Next(5, 20);
var amount = balances * 100_000;
payouts.Add(new CommissionPayoutDto
{
Id = i + 1,
WeekDefinitionId = i + 1,
WeekDisplayName = $"هفته {weekNum} - سال 1404",
BalancesEarned = balances,
TotalAmount = amount,
AmountFormatted = $"{amount:N0} تومان",
Status = statusTexts[statusIndex],
StatusBadgeColor = statusColors[statusIndex],
DatePersian = DateTime.Now.AddDays(-i * 7).ToString("yyyy/MM/dd")
});
}
return payouts;
}
private static List<WeekDefinitionDto> GenerateMockWeekDefinitions()
{
var weeks = new List<WeekDefinitionDto>();
var persianOrdinals = new[] { "یکم", "دوم", "سوم", "چهارم", "پنجم", "ششم", "هفتم", "هشتم", "نهم", "دهم" };
// Generate 10 weeks starting from week 46
for (int i = 0; i < 10; i++)
{
var weekOrder = i + 1;
var startDate = new DateTime(2025, 11, 8).AddDays(i * 7);
weeks.Add(new WeekDefinitionDto
{
Id = i + 1,
WeekOrder = weekOrder,
DisplayName = weekOrder <= 10 ? $"هفته {persianOrdinals[weekOrder - 1]}" : $"هفته {weekOrder}",
StartDate = startDate,
EndDate = startDate.AddDays(6),
GregorianYear = 2025,
PersianYear = 1404,
IsActive = true,
IsCurrentWeek = weekOrder == 6, // Assume week 6 is current
StartDatePersian = $"1404/{8 + i}/17",
EndDatePersian = $"1404/{8 + i}/23"
});
}
return weeks;
}
#endregion #endregion
} }
@@ -17,6 +17,10 @@ public class NetworkNodeDto
public DateTime? JoinedAt { get; set; } public DateTime? JoinedAt { get; set; }
public bool IsClubActive { get; set; } public bool IsClubActive { get; set; }
public string? ActivationWeekNumber { get; set; } public string? ActivationWeekNumber { get; set; }
/// <summary>
/// کد معرف کاربر
/// </summary>
public string? ReferralCode { get; set; }
} }
/// <summary> /// <summary>
@@ -35,6 +39,10 @@ public class FlatNetworkNodeDto
public bool IsClubActive { get; set; } public bool IsClubActive { get; set; }
public string? ActivationWeekNumber { get; set; } public string? ActivationWeekNumber { get; set; }
public DateTime? JoinedAt { get; set; } public DateTime? JoinedAt { get; set; }
/// <summary>
/// کد معرف کاربر - فقط برای کاربران فعال در باشگاه نمایش داده شود
/// </summary>
public string? ReferralCode { get; set; }
} }
/// <summary> /// <summary>
@@ -72,7 +80,8 @@ public class NetworkTreeDto
IsActive = node.IsActive, IsActive = node.IsActive,
IsClubActive = node.IsClubActive, IsClubActive = node.IsClubActive,
ActivationWeekNumber = node.ActivationWeekNumber, ActivationWeekNumber = node.ActivationWeekNumber,
JoinedAt = node.JoinedAt JoinedAt = node.JoinedAt,
ReferralCode = node.ReferralCode
}; };
result.Add(flatNode); result.Add(flatNode);
@@ -36,8 +36,13 @@ public class NetworkMembershipService
} }
catch catch
{ {
// Fallback to mock data if backend is unavailable // Return empty tree if backend is unavailable
return CreateMockTree(); return new NetworkTreeDto
{
CurrentDepth = 0,
TotalMembers = 0,
RootNode = null
};
} }
} }
@@ -102,21 +107,15 @@ public class NetworkMembershipService
} }
catch catch
{ {
// Fallback to mock data if backend is unavailable // Return empty statistics if backend is unavailable
return new NetworkStatisticsDto return new NetworkStatisticsDto
{ {
LeftLegCount = 15, LeftLegCount = 0,
RightLegCount = 12, RightLegCount = 0,
TotalMembers = 27, TotalMembers = 0,
TreeDepth = 4, TreeDepth = 0,
WeakerLeg = "Right", WeakerLeg = string.Empty,
LastMember = new LastMemberDto LastMember = null
{
UserId = 28,
FullName = "محمد رضایی",
Position = "Left",
JoinedAt = DateTime.Now.AddHours(-2)
}
}; };
} }
} }
@@ -135,85 +134,15 @@ public class NetworkMembershipService
Avatar = node.Avatar, Avatar = node.Avatar,
Position = node.Position, Position = node.Position,
Level = node.Level, Level = node.Level,
IsActive = node.HasChildren, // Temporary: use HasChildren as active indicator until proto is updated IsActive = node.IsActive,
IsClubActive = false, // Will be populated when proto is updated IsClubActive = node.IsClubActive,
ActivationWeekNumber = null, // Will be populated when proto is updated ReferralCode = node.ReferralCode,
JoinedAt = null, // Will be populated when proto is updated ActivationWeekNumber = node.ActivationWeekDefinitionId?.ToString(),
JoinedAt = node.JoinedAt?.ToDateTime(),
LeftChild = MapNodeFromProto(node.LeftChild), LeftChild = MapNodeFromProto(node.LeftChild),
RightChild = MapNodeFromProto(node.RightChild) RightChild = MapNodeFromProto(node.RightChild)
}; };
} }
private static NetworkTreeDto CreateMockTree()
{
return new NetworkTreeDto
{
CurrentDepth = 3,
TotalMembers = 7,
RootNode = new NetworkNodeDto
{
UserId = 1,
FullName = "شما",
Mobile = "09121234567",
Position = "Root",
Level = 0,
IsActive = true,
IsClubActive = true,
LeftChild = new NetworkNodeDto
{
UserId = 2,
FullName = "علی محمدی",
Mobile = "09121234568",
Position = "Left",
Level = 1,
IsActive = true,
IsClubActive = true,
JoinedAt = DateTime.Now.AddDays(-30),
LeftChild = new NetworkNodeDto
{
UserId = 4,
FullName = "رضا کریمی",
Mobile = "09121234570",
Position = "Left",
Level = 2,
IsActive = true,
JoinedAt = DateTime.Now.AddDays(-15)
},
RightChild = new NetworkNodeDto
{
UserId = 5,
FullName = "زهرا احمدی",
Mobile = "09121234571",
Position = "Right",
Level = 2,
IsActive = false,
JoinedAt = DateTime.Now.AddDays(-10)
}
},
RightChild = new NetworkNodeDto
{
UserId = 3,
FullName = "فاطمه حسینی",
Mobile = "09121234569",
Position = "Right",
Level = 1,
IsActive = true,
JoinedAt = DateTime.Now.AddDays(-25),
LeftChild = new NetworkNodeDto
{
UserId = 6,
FullName = "محمد نوری",
Mobile = "09121234572",
Position = "Left",
Level = 2,
IsActive = true,
IsClubActive = true,
JoinedAt = DateTime.Now.AddDays(-5)
}
}
}
};
}
#endregion #endregion
} }
@@ -104,12 +104,11 @@ public class PackageService
} }
/// <summary> /// <summary>
/// Get user's package purchase status (Mock for now - needs BFF implementation) /// Get user's package purchase status
/// </summary> /// </summary>
public Task<UserPackageStatusDto> GetUserPackageStatusAsync(CancellationToken ct = default) public Task<UserPackageStatusDto> GetUserPackageStatusAsync(CancellationToken ct = default)
{ {
// TODO: Connect to GetUserPackageStatus RPC when available in FrontOffice.BFF // TODO: Connect to GetUserPackageStatus RPC when available in FrontOffice.BFF
// For now, return a mock status
return Task.FromResult(new UserPackageStatusDto( return Task.FromResult(new UserPackageStatusDto(
HasPurchasedPackage: false, HasPurchasedPackage: false,
PurchaseMethod: null, PurchaseMethod: null,
@@ -32,8 +32,8 @@ public class WalletService
} }
catch catch
{ {
// Fallback to mock data if backend is unavailable // Return zero balances if backend is unavailable
return new WalletBalances(350_000, 0, 1_250_000); return new WalletBalances(0, 0, 0);
} }
} }
@@ -62,15 +62,8 @@ public class WalletService
} }
catch catch
{ {
// Fallback to mock data if backend is unavailable // Return empty list if backend is unavailable
var _transactions = new List<WalletTransaction> return new List<WalletTransaction>();
{
new(DateTime.Now.AddDays(-1).ToString(), 500_000, "درگاه بانکی", "شارژ کیف پول"),
new(DateTime.Now.AddDays(-2).ToString(), 200_000, "شبکه/معرف", "پاداش شبکه"),
new(DateTime.Now.AddDays(-4).ToString(), -120_000, "خرید", "برداشت بابت سفارش #1452"),
new(DateTime.Now.AddDays(-9).ToString(), 900_000, "کیف پول شرکای تجاری", "اعتبار خرید"),
};
return _transactions.OrderByDescending(t => t.Date).ToList();
} }
} }
@@ -158,7 +151,8 @@ public class WalletService
} }
catch catch
{ {
return new WithdrawalSettings(1_000_000); // Return default settings if backend is unavailable
return new WithdrawalSettings(0);
} }
} }
} }
-1
View File
@@ -1,6 +1,5 @@
{ {
"GwUrl": "https://frontoffice-bff.foursat.afrino.co", "GwUrl": "https://frontoffice-bff.foursat.afrino.co",
// "GwUrl": "https://localhost:34781",
"DownloadUrl": "https://dl.afrino.co", "DownloadUrl": "https://dl.afrino.co",
"EncryptionSettings": { "EncryptionSettings": {
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=", "Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
@@ -124,6 +124,68 @@
margin-top: 1px; margin-top: 1px;
} }
/* Referral Code - only for club active members */
.node-referral {
display: flex;
align-items: center;
gap: 3px;
margin-top: 2px;
padding: 2px 5px;
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
font-size: 8px;
}
.node-referral:hover {
background: linear-gradient(135deg, #c8e6c9 0%, #a5d6a7 100%);
transform: scale(1.02);
}
.node-referral .referral-icon {
font-size: 9px;
}
.node-referral .referral-code {
color: #2e7d32;
font-weight: 600;
font-size: 8px;
letter-spacing: 0.5px;
}
/* Club Active indicator */
.org-node-card.club-active {
border-top: 2px solid #4caf50;
}
.org-node-card.club-active .node-avatar-sm {
background: linear-gradient(135deg, #4caf50 0%, #388e3c 100%);
}
/* Toast notification */
.org-chart-toast {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: #323232;
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-family: 'Vazirmatn', 'IRANSans', sans-serif;
z-index: 10000;
opacity: 0;
transition: all 0.3s ease;
direction: rtl;
}
.org-chart-toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* Legacy Avatar (keep for compatibility) */ /* Legacy Avatar (keep for compatibility) */
.node-avatar { .node-avatar {
width: 36px; width: 36px;
@@ -414,3 +476,73 @@
overflow: visible; overflow: visible;
} }
} }
/* Referral Code Style */
.node-referral {
display: flex;
align-items: center;
gap: 3px;
margin-top: 2px;
padding: 2px 4px;
background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
max-width: 100%;
}
.node-referral:hover {
background: linear-gradient(135deg, #ffe0b2 0%, #ffcc80 100%);
transform: scale(1.02);
}
.node-referral .referral-icon {
font-size: 8px;
}
.node-referral .referral-code {
font-size: 8px;
font-weight: 600;
color: #e65100;
font-family: monospace;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Club Active Badge */
.org-node-card.club-active {
border-right: 3px solid #ff9800;
}
.org-node-card.club-active .node-avatar-sm {
background: linear-gradient(135deg, #ff9800 0%, #f57c00 100%);
}
/* Toast Notification */
.org-chart-toast {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: #323232;
color: white;
padding: 12px 24px;
border-radius: 8px;
font-size: 14px;
font-family: 'Vazirmatn', sans-serif;
direction: rtl;
z-index: 10000;
opacity: 0;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.org-chart-toast.show {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
.node-button-div{
justify-content: center;
align-items: center;
}
+73 -3
View File
@@ -30,12 +30,15 @@ window.OrgChart = {
return; return;
} }
// Debug: log first node to see data structure
console.log('OrgChart Data Sample:', data[0]);
try { try {
this.chart = new d3.OrgChart() this.chart = new d3.OrgChart()
.container('#' + containerId) .container('#' + containerId)
.data(data) .data(data)
.nodeWidth((d) => 130) .nodeWidth((d) => 140)
.nodeHeight((d) => 56) .nodeHeight((d) => 70)
.childrenMargin((d) => 50) .childrenMargin((d) => 50)
.compactMarginBetween((d) => 15) .compactMarginBetween((d) => 15)
.compactMarginPair((d) => 15) .compactMarginPair((d) => 15)
@@ -59,17 +62,27 @@ window.OrgChart = {
const positionClass = data.position === 'Left' ? 'position-left' : const positionClass = data.position === 'Left' ? 'position-left' :
data.position === 'Right' ? 'position-right' : 'position-root'; data.position === 'Right' ? 'position-right' : 'position-root';
const activeClass = data.isActive ? 'active' : 'inactive'; const activeClass = data.isActive ? 'active' : 'inactive';
const clubActiveClass = data.isClubActive ? 'club-active' : '';
// Avatar - first letter of name // Avatar - first letter of name
const firstChar = data.fullName ? data.fullName.charAt(0) : '?'; const firstChar = data.fullName ? data.fullName.charAt(0) : '?';
// نمایش کد معرف فقط برای کاربران فعال در باشگاه
const referralCodeHtml = data.isClubActive && data.referralCode
? `<div class="node-referral" onclick="event.stopPropagation(); OrgChart.copyReferralCode('${data.referralCode}');" title="کپی کد معرف">
<span class="referral-icon">📋</span>
<span class="referral-code">${data.referralCode}</span>
</div>`
: '';
return ` return `
<div class="org-node-card ${positionClass} ${activeClass}" data-user-id="${data.id}"> <div class="org-node-card ${positionClass} ${activeClass} ${clubActiveClass}" data-user-id="${data.id}">
<div class="node-header-compact ${isRoot ? 'root' : ''}"> <div class="node-header-compact ${isRoot ? 'root' : ''}">
<div class="node-avatar-sm">${firstChar}</div> <div class="node-avatar-sm">${firstChar}</div>
<div class="node-info"> <div class="node-info">
<div class="node-name-sm">${data.fullName || 'بدون نام'}</div> <div class="node-name-sm">${data.fullName || 'بدون نام'}</div>
<div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div> <div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div>
${referralCodeHtml}
</div> </div>
</div> </div>
</div> </div>
@@ -187,6 +200,63 @@ window.OrgChart = {
} }
}, },
/**
* Copy referral code to clipboard
* @param {string} code - The referral code to copy
*/
copyReferralCode: function(code) {
if (navigator.clipboard) {
navigator.clipboard.writeText(code).then(() => {
// Show toast notification
this.showToast('کد معرف کپی شد: ' + code);
}).catch(err => {
console.error('Failed to copy:', err);
this.fallbackCopy(code);
});
} else {
this.fallbackCopy(code);
}
},
/**
* Fallback copy method for older browsers
*/
fallbackCopy: function(text) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.left = '-999999px';
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand('copy');
this.showToast('کد معرف کپی شد: ' + text);
} catch (err) {
console.error('Fallback copy failed:', err);
}
document.body.removeChild(textArea);
},
/**
* Show toast notification
*/
showToast: function(message) {
// Remove existing toast
const existingToast = document.querySelector('.org-chart-toast');
if (existingToast) existingToast.remove();
const toast = document.createElement('div');
toast.className = 'org-chart-toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('show'), 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 2000);
},
/** /**
* Dispose the chart * Dispose the chart
*/ */