Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00f805dcd8 | |||
| 1e086c389a | |||
| c136e853c6 | |||
| 54f8781774 | |||
| 1c61fe6846 | |||
| 516da671c6 | |||
| 45a291b529 | |||
| cb6102191e | |||
| 7d491059f8 | |||
| 6b41ae8b5e | |||
| 8be14089c6 | |||
| fa5987bf3e | |||
| ac5818855c | |||
| 8f58f01781 | |||
| cca89e4100 | |||
| d7abcf4278 | |||
| 10331663ce | |||
| 34f5cbd5a8 | |||
| 8ae25d2b38 |
@@ -4,7 +4,7 @@ name: Push nuget and docker image Actions Workflow
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stage
|
||||
- stage-new
|
||||
jobs:
|
||||
Deploy:
|
||||
runs-on: windows
|
||||
|
||||
@@ -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"
|
||||
@@ -11,10 +11,15 @@ public partial class App
|
||||
{
|
||||
[Inject] private ILocalStorageService LocalStorage { get; set; } = default!;
|
||||
[Inject] private AuthService AuthService { get; set; } = default!;
|
||||
[Inject] private AppVersionService AppVersionService { get; set; } = default!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Check app version and clear cache if needed
|
||||
await AppVersionService.CheckVersionAndClearCacheIfNeededAsync();
|
||||
|
||||
// Check for referral code in URL query parameters
|
||||
var uri = Navigation.ToAbsoluteUri(Navigation.Uri);
|
||||
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.City.Protobuf;
|
||||
using FrontOffice.BFF.Configuration.Protobuf.Protos.Configuration;
|
||||
using FrontOffice.BFF.Configuration.Protobuf.Protos.AppVersion;
|
||||
using FrontOffice.Main.Utilities;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection;
|
||||
@@ -61,6 +62,8 @@ public static class ConfigureServices
|
||||
services.AddScoped<ClubConfigurationService>();
|
||||
services.AddScoped<NetworkMembershipService>();
|
||||
services.AddScoped<CommissionService>();
|
||||
// App Version Service for cache invalidation
|
||||
services.AddScoped<AppVersionService>();
|
||||
// Device detection: very light, dependency-free
|
||||
services.AddTransient<IDeviceDetector, DeviceDetector>();
|
||||
// PDF generation (Chromium only)
|
||||
@@ -110,6 +113,7 @@ public static class ConfigureServices
|
||||
services.AddScoped(CreateAuthenticatedClient<NetworkMembershipContract.NetworkMembershipContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<DiscountShopContract.DiscountShopContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<CityContract.CityContractClient>);
|
||||
services.AddScoped(CreateAuthenticatedClient<AppVersionContract.AppVersionContractClient>);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
<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.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.Commission.Protobuf" Version="0.0.2" /> -->
|
||||
<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.NetworkMembership.Protobuf" Version="0.0.2" />-->
|
||||
<!-- <PackageReference Include="Foursat.FrontOffice.BFF.Package.Protobuf" Version="0.0.113" /> -->
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
@if (_membership.IsActive)
|
||||
{
|
||||
<MudAlert Severity="Severity.Success" Variant="Variant.Outlined">
|
||||
<MudText>عضویت شما در باشگاه فعال است! از مزایای باشگاه مشتریان استفاده کنید.</MudText>
|
||||
<MudText>عضویت شما در باشگاه مشتریان فعال است و هم اکنون میتوانید از مزایای باشگاه مشتریان استفاده کنید.</MudText>
|
||||
</MudAlert>
|
||||
|
||||
@if (_membership.DaysRemaining.HasValue)
|
||||
@@ -88,16 +88,16 @@
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Color="Color.Primary" Size="Size.Large" />
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.subtitle1">عضویت در شبکه بازاریابی</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Default">عضویت در شبکه بازاریابی و دریافت پورسانت</MudText>
|
||||
<MudText Typo="Typo.subtitle1">عضویت در باشگاه مشتریان</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Default"> دریافت پاداش وفاداری</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
<MudDivider />
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2" Class="pa-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.GroupAdd" Color="Color.Secondary" Size="Size.Large" />
|
||||
<MudStack Spacing="0">
|
||||
<MudText Typo="Typo.subtitle1">جذب زیرمجموعه</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Default">امکان جذب زیرمجموعه و گسترش شبکه</MudText>
|
||||
<MudText Typo="Typo.subtitle1">معرفی مشتریان جدید</MudText>
|
||||
<MudText Typo="Typo.caption" Color="Color.Default">توسعه باشگاه مشتریان</MudText>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<MudContainer MaxWidth="MaxWidth.Large" Class="py-6">
|
||||
<MudStack Spacing="3">
|
||||
<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>
|
||||
</MudStack>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<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>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Groups" Color="Color.Primary" />
|
||||
</MudStack>
|
||||
@@ -31,7 +31,7 @@
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<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>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronLeft" Color="Color.Info" />
|
||||
</MudStack>
|
||||
@@ -40,7 +40,7 @@
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<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>
|
||||
<MudIcon Icon="@Icons.Material.Filled.ChevronRight" Color="Color.Success" />
|
||||
</MudStack>
|
||||
@@ -49,7 +49,7 @@
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Elevation="2" Class="pa-4 rounded-lg">
|
||||
<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>
|
||||
<MudIcon Icon="@Icons.Material.Filled.AccountTree" Color="Color.Secondary" />
|
||||
</MudStack>
|
||||
@@ -59,10 +59,10 @@
|
||||
|
||||
<!-- تعادل شاخهها -->
|
||||
<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 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"
|
||||
Value="@GetLeftPercentage()"
|
||||
Size="Size.Medium"
|
||||
@@ -70,7 +70,7 @@
|
||||
Style="width: 200px;" />
|
||||
</MudStack>
|
||||
<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"
|
||||
Value="@GetRightPercentage()"
|
||||
Size="Size.Medium"
|
||||
@@ -80,7 +80,7 @@
|
||||
@if (!string.IsNullOrEmpty(_statistics.WeakerLeg))
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
|
||||
شاخه ضعیفتر: <strong>@GetLegText(_statistics.WeakerLeg)</strong>
|
||||
بخش کوچک تر: <strong>@GetLegText(_statistics.WeakerLeg)</strong>
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
@@ -88,12 +88,12 @@
|
||||
|
||||
<!-- نمودار دایرهای -->
|
||||
<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"
|
||||
Width="300px"
|
||||
Height="300px"
|
||||
InputData="@(new double[] { _statistics.LeftLegCount, _statistics.RightLegCount })"
|
||||
InputLabels="@(new[] { "شاخه چپ", "شاخه راست" })"
|
||||
InputLabels="@(new[] { "بخش (A)", "بخش (B)" })"
|
||||
ChartOptions="@_chartOptions" />
|
||||
</MudPaper>
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
StartIcon="@Icons.Material.Filled.AccountTree"
|
||||
Href="@RouteConstants.Profile.Tree"
|
||||
FullWidth="true">
|
||||
مشاهده درخت کامل
|
||||
مشاهده کل توسعه دهندگان
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
@@ -140,7 +140,7 @@
|
||||
else if (_hasError)
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Variant="Variant.Filled">
|
||||
خطا در دریافت آمار شبکه. لطفا دوباره تلاش کنید.
|
||||
خطا در دریافت آمار توسعه دهندگان. لطفا دوباره تلاش کنید.
|
||||
</MudAlert>
|
||||
<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
|
||||
{
|
||||
"left" => "شاخه چپ",
|
||||
"right" => "شاخه راست",
|
||||
"left" => "بخش (A)",
|
||||
"right" => "بخش (B)",
|
||||
_ => leg
|
||||
};
|
||||
|
||||
private string GetPositionText(string position) => position.ToLower() switch
|
||||
{
|
||||
"left" => "چپ",
|
||||
"right" => "راست",
|
||||
"left" => "(A)",
|
||||
"right" => "(B)",
|
||||
_ => position
|
||||
};
|
||||
|
||||
|
||||
@@ -123,23 +123,14 @@ public partial class PackageDetail : IDisposable
|
||||
|
||||
private async Task LoadReviewsAsync()
|
||||
{
|
||||
// TODO: Load reviews from API
|
||||
_reviews = new List<Review>
|
||||
{
|
||||
new() { UserName = "علی احمدی", Rating = 5, Comment = "عالی! کارمزد رو دقیق حساب میکنه و گزارشها کامل هستن.", Date = "۱۴۰۲/۱۰/۰۵" },
|
||||
new() { UserName = "مریم رضایی", Rating = 4, Comment = "رابط کاربری خوبی داره، فقط سرعت بارگذاری میتونه بهتر بشه.", Date = "۱۴۰۲/۰۹/۲۲" },
|
||||
new() { UserName = "حسن کریمی", Rating = 5, Comment = "پشتیبانی فوقالعاده سریع و حرفهای داشتن. پیشنهاد میکنم.", Date = "۱۴۰۲/۰۹/۱۵" }
|
||||
};
|
||||
// TODO: Load reviews from API when endpoint is available
|
||||
_reviews = new List<Review>();
|
||||
}
|
||||
|
||||
private async Task LoadRelatedPackagesAsync()
|
||||
{
|
||||
// TODO: Load related packages from API
|
||||
_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 } }
|
||||
};
|
||||
// TODO: Load related packages from API when endpoint is available
|
||||
_relatedPackages = new List<RelatedPackage>();
|
||||
}
|
||||
|
||||
private async Task PurchasePackage()
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
برای فعالسازی لینک دعوت و دعوت دوستان خود به جمع مشتریان ویژه کارابازار، کافی است ابتدا <strong>پکیج پایه ۵۶ میلیونی</strong> را تهیه کنید و سپس در <strong>باشگاه مشتریان</strong> عضو شوید.
|
||||
</MudText>
|
||||
<MudText Typo="Typo.body2" Align="Align.Center" Class="mud-text-secondary">
|
||||
پس از تکمیل این مراحل، میتوانید از مزایای ویژه عضویت در شبکه فروش بهرهمند شده و با معرفی دوستان، کمیسیون دریافت کنید.
|
||||
از مزایای ویژه عضویت در باشگاه مشتریان کارابازار سلامت بهرهمند شده و با فعالیت در زمینه توسعه فروشگاهها پاداش دریافت کنید.
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
@@ -219,8 +219,8 @@
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Groups" Size="Size.Large" Color="Color.Info" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">شبکه من</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">آمار و درخت شبکه</MudText>
|
||||
<MudText Typo="Typo.subtitle1" Class="mt-2">آمار باشگاه</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mud-text-secondary">آمار جزئیات باشگاه</MudText>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
</MudLink>
|
||||
@@ -230,7 +230,7 @@
|
||||
<MudCard Elevation="1" Class="rounded-lg profile-tile">
|
||||
<MudCardContent Class="d-flex flex-column align-center pa-4">
|
||||
<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>
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
@@ -266,12 +266,12 @@
|
||||
<MudText Typo="Typo.body2" Class="mud-text-secondary">
|
||||
پرداخت آنی از طریق درگاه بانکی
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled"
|
||||
<MudButton Variant="Variant.Filled" Disabled="true"
|
||||
Color="Color.Primary"
|
||||
FullWidth="true"
|
||||
StartIcon="@Icons.Material.Filled.Payment"
|
||||
OnClick="DirectPayment">
|
||||
پرداخت با کارت بانکی
|
||||
پرداخت با کارت بانکی(بزودی)
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@@ -45,7 +45,9 @@ public partial class CheckoutSummary : ComponentBase
|
||||
private async Task LoadWalletBalance()
|
||||
{
|
||||
var walletResult = await WalletService.GetBalancesAsync();
|
||||
walletBalance = walletResult.CreditBalance + walletResult.NetworkBalance;
|
||||
walletBalance = walletResult.CreditBalance
|
||||
// + walletResult.NetworkBalance
|
||||
;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// Fallback to mock data if backend is unavailable
|
||||
// Return empty membership if backend is unavailable
|
||||
return new ClubMembershipDto
|
||||
{
|
||||
UserId = 1,
|
||||
UserId = 0,
|
||||
IsActive = false,
|
||||
Status = "Inactive",
|
||||
Status = string.Empty,
|
||||
DaysRemaining = null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ public class CommissionService
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to mock data if backend is unavailable
|
||||
return GenerateMockWeekDefinitions();
|
||||
// Return empty list if backend is unavailable
|
||||
return new List<WeekDefinitionDto>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +129,14 @@ public class CommissionService
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to mock data if backend is unavailable
|
||||
return GenerateMockPayoutsResponse(weekDefinitionId, status, pageNumber, pageSize);
|
||||
// Return empty response if backend is unavailable
|
||||
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
|
||||
{
|
||||
return CreateMockWeeklyBalance();
|
||||
return CreateEmptyWeeklyBalance();
|
||||
}
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private static WeeklyBalanceDto CreateMockWeeklyBalance()
|
||||
private static WeeklyBalanceDto CreateEmptyWeeklyBalance()
|
||||
{
|
||||
return new WeeklyBalanceDto
|
||||
{
|
||||
WeekDefinitionId = 0,
|
||||
WeekDisplayName = "دادهای یافت نشد",
|
||||
WeekDisplayName = string.Empty,
|
||||
LeftBalance = 0,
|
||||
RightBalance = 0,
|
||||
MinBalance = 0,
|
||||
@@ -198,94 +204,10 @@ public class CommissionService
|
||||
CalculatedCommission = 0,
|
||||
LeftCarryover = 0,
|
||||
RightCarryover = 0,
|
||||
StartDate = DateTime.Now.AddDays(-7),
|
||||
EndDate = DateTime.Now
|
||||
StartDate = DateTime.MinValue,
|
||||
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
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ public class NetworkNodeDto
|
||||
public DateTime? JoinedAt { get; set; }
|
||||
public bool IsClubActive { get; set; }
|
||||
public string? ActivationWeekNumber { get; set; }
|
||||
/// <summary>
|
||||
/// کد معرف کاربر
|
||||
/// </summary>
|
||||
public string? ReferralCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,6 +39,10 @@ public class FlatNetworkNodeDto
|
||||
public bool IsClubActive { get; set; }
|
||||
public string? ActivationWeekNumber { get; set; }
|
||||
public DateTime? JoinedAt { get; set; }
|
||||
/// <summary>
|
||||
/// کد معرف کاربر - فقط برای کاربران فعال در باشگاه نمایش داده شود
|
||||
/// </summary>
|
||||
public string? ReferralCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,7 +80,8 @@ public class NetworkTreeDto
|
||||
IsActive = node.IsActive,
|
||||
IsClubActive = node.IsClubActive,
|
||||
ActivationWeekNumber = node.ActivationWeekNumber,
|
||||
JoinedAt = node.JoinedAt
|
||||
JoinedAt = node.JoinedAt,
|
||||
ReferralCode = node.ReferralCode
|
||||
};
|
||||
|
||||
result.Add(flatNode);
|
||||
|
||||
@@ -36,8 +36,13 @@ public class NetworkMembershipService
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to mock data if backend is unavailable
|
||||
return CreateMockTree();
|
||||
// Return empty tree if backend is unavailable
|
||||
return new NetworkTreeDto
|
||||
{
|
||||
CurrentDepth = 0,
|
||||
TotalMembers = 0,
|
||||
RootNode = null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,21 +107,15 @@ public class NetworkMembershipService
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to mock data if backend is unavailable
|
||||
// Return empty statistics if backend is unavailable
|
||||
return new NetworkStatisticsDto
|
||||
{
|
||||
LeftLegCount = 15,
|
||||
RightLegCount = 12,
|
||||
TotalMembers = 27,
|
||||
TreeDepth = 4,
|
||||
WeakerLeg = "Right",
|
||||
LastMember = new LastMemberDto
|
||||
{
|
||||
UserId = 28,
|
||||
FullName = "محمد رضایی",
|
||||
Position = "Left",
|
||||
JoinedAt = DateTime.Now.AddHours(-2)
|
||||
}
|
||||
LeftLegCount = 0,
|
||||
RightLegCount = 0,
|
||||
TotalMembers = 0,
|
||||
TreeDepth = 0,
|
||||
WeakerLeg = string.Empty,
|
||||
LastMember = null
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -135,85 +134,15 @@ public class NetworkMembershipService
|
||||
Avatar = node.Avatar,
|
||||
Position = node.Position,
|
||||
Level = node.Level,
|
||||
IsActive = node.HasChildren, // Temporary: use HasChildren as active indicator until proto is updated
|
||||
IsClubActive = false, // Will be populated when proto is updated
|
||||
ActivationWeekNumber = null, // Will be populated when proto is updated
|
||||
JoinedAt = null, // Will be populated when proto is updated
|
||||
IsActive = node.IsActive,
|
||||
IsClubActive = node.IsClubActive,
|
||||
ReferralCode = node.ReferralCode,
|
||||
ActivationWeekNumber = node.ActivationWeekDefinitionId?.ToString(),
|
||||
JoinedAt = node.JoinedAt?.ToDateTime(),
|
||||
LeftChild = MapNodeFromProto(node.LeftChild),
|
||||
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
|
||||
}
|
||||
|
||||
@@ -104,12 +104,11 @@ public class PackageService
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get user's package purchase status (Mock for now - needs BFF implementation)
|
||||
/// Get user's package purchase status
|
||||
/// </summary>
|
||||
public Task<UserPackageStatusDto> GetUserPackageStatusAsync(CancellationToken ct = default)
|
||||
{
|
||||
// TODO: Connect to GetUserPackageStatus RPC when available in FrontOffice.BFF
|
||||
// For now, return a mock status
|
||||
return Task.FromResult(new UserPackageStatusDto(
|
||||
HasPurchasedPackage: false,
|
||||
PurchaseMethod: null,
|
||||
|
||||
@@ -32,8 +32,8 @@ public class WalletService
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to mock data if backend is unavailable
|
||||
return new WalletBalances(350_000, 0, 1_250_000);
|
||||
// Return zero balances if backend is unavailable
|
||||
return new WalletBalances(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,15 +62,8 @@ public class WalletService
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback to mock data if backend is unavailable
|
||||
var _transactions = 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();
|
||||
// Return empty list if backend is unavailable
|
||||
return new List<WalletTransaction>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +151,8 @@ public class WalletService
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new WithdrawalSettings(1_000_000);
|
||||
// Return default settings if backend is unavailable
|
||||
return new WithdrawalSettings(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"GwUrl": "https://frontoffice-bff.foursat.afrino.co",
|
||||
// "GwUrl": "https://localhost:34781",
|
||||
"DownloadUrl": "https://dl.afrino.co",
|
||||
"EncryptionSettings": {
|
||||
"Key": "kmcQ3XTmH4mrdh8VHziuscyf8LLYjG//Kyni81nH/0E=",
|
||||
|
||||
@@ -124,6 +124,68 @@
|
||||
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) */
|
||||
.node-avatar {
|
||||
width: 36px;
|
||||
@@ -414,3 +476,73 @@
|
||||
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;
|
||||
}
|
||||
@@ -30,12 +30,15 @@ window.OrgChart = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Debug: log first node to see data structure
|
||||
console.log('OrgChart Data Sample:', data[0]);
|
||||
|
||||
try {
|
||||
this.chart = new d3.OrgChart()
|
||||
.container('#' + containerId)
|
||||
.data(data)
|
||||
.nodeWidth((d) => 130)
|
||||
.nodeHeight((d) => 56)
|
||||
.nodeWidth((d) => 140)
|
||||
.nodeHeight((d) => 70)
|
||||
.childrenMargin((d) => 50)
|
||||
.compactMarginBetween((d) => 15)
|
||||
.compactMarginPair((d) => 15)
|
||||
@@ -59,17 +62,27 @@ window.OrgChart = {
|
||||
const positionClass = data.position === 'Left' ? 'position-left' :
|
||||
data.position === 'Right' ? 'position-right' : 'position-root';
|
||||
const activeClass = data.isActive ? 'active' : 'inactive';
|
||||
const clubActiveClass = data.isClubActive ? 'club-active' : '';
|
||||
|
||||
// Avatar - first letter of name
|
||||
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 `
|
||||
<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-avatar-sm">${firstChar}</div>
|
||||
<div class="node-info">
|
||||
<div class="node-name-sm">${data.fullName || 'بدون نام'}</div>
|
||||
<div class="node-level-sm">L${data.level || 0}${!isRoot ? ' • ' + (data.position === 'Left' ? 'چپ' : 'راست') : ''}</div>
|
||||
${referralCodeHtml}
|
||||
</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
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user