Tenantlı uygulama için Login düzenlemesi

This commit is contained in:
Sedat ÖZTÜRK 2026-06-02 21:47:01 +03:00
parent 67286232da
commit f9c5910813
25 changed files with 226 additions and 131 deletions

View file

@ -19,7 +19,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -14,7 +14,6 @@
</PackageReference> </PackageReference>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>

View file

@ -31,6 +31,8 @@ public class MailTrackingManager : DomainService
/// <returns></returns> /// <returns></returns>
public async Task StartAsync() public async Task StartAsync()
{ {
// https://us-east-1.console.aws.amazon.com/iam/home?region=eu-central-1#/users
// https://eu-central-1.console.aws.amazon.com/ses/home?region=eu-central-1#/identities/system%40sozsoft.com?tabId=authentication
var accessKey = configuration.GetValue<string>(AmazonSesEmailSettingNames.AccessKey); var accessKey = configuration.GetValue<string>(AmazonSesEmailSettingNames.AccessKey);
var accessKeyId = configuration.GetValue<string>(AmazonSesEmailSettingNames.AccessKeyId); var accessKeyId = configuration.GetValue<string>(AmazonSesEmailSettingNames.AccessKeyId);
var region = configuration.GetValue<string>(AmazonSesEmailSettingNames.Region); var region = configuration.GetValue<string>(AmazonSesEmailSettingNames.Region);

View file

@ -30,7 +30,6 @@
<PackageReference Include="Volo.Abp.TextTemplating.Razor" Version="10.0.0" /> <PackageReference Include="Volo.Abp.TextTemplating.Razor" Version="10.0.0" />
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -14,7 +14,6 @@
</PackageReference> </PackageReference>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>

View file

@ -65,6 +65,7 @@ public class AmazonSesEmailSender : EmailSenderBase, ISozsoftEmailSender, ITrans
await BackgroundJobManager.EnqueueAsync( await BackgroundJobManager.EnqueueAsync(
new ErpBackgroundEmailSendingJobArgs new ErpBackgroundEmailSendingJobArgs
{ {
TenantId = CurrentTenant.Id,
To = to, To = to,
Sender = sender, Sender = sender,
Params = @params, Params = @params,

View file

@ -1,5 +1,6 @@
using Volo.Abp.BackgroundJobs; using Volo.Abp.BackgroundJobs;
using Volo.Abp.DependencyInjection; using Volo.Abp.DependencyInjection;
using Volo.Abp.MultiTenancy;
namespace Sozsoft.Sender.Mail; namespace Sozsoft.Sender.Mail;
@ -7,13 +8,19 @@ public class ErpBackgroundEmailSendingJob :
AsyncBackgroundJob<ErpBackgroundEmailSendingJobArgs>, ITransientDependency AsyncBackgroundJob<ErpBackgroundEmailSendingJobArgs>, ITransientDependency
{ {
protected ISozsoftEmailSender EmailSender { get; } protected ISozsoftEmailSender EmailSender { get; }
protected ICurrentTenant CurrentTenant { get; }
public ErpBackgroundEmailSendingJob(ISozsoftEmailSender emailSender) public ErpBackgroundEmailSendingJob(
ISozsoftEmailSender emailSender,
ICurrentTenant currentTenant)
{ {
EmailSender = emailSender; EmailSender = emailSender;
CurrentTenant = currentTenant;
} }
public override async Task ExecuteAsync(ErpBackgroundEmailSendingJobArgs args) public override async Task ExecuteAsync(ErpBackgroundEmailSendingJobArgs args)
{
using (CurrentTenant.Change(args.TenantId))
{ {
//await EmailSender.SendEmailAsync(args.Template, args.To, args.Params, args.Subject); //await EmailSender.SendEmailAsync(args.Template, args.To, args.Params, args.Subject);
await EmailSender.SendEmailAsync( await EmailSender.SendEmailAsync(
@ -24,5 +31,5 @@ public class ErpBackgroundEmailSendingJob :
args.Subject, args.Subject,
args.Attachments); args.Attachments);
} }
}
} }

View file

@ -1,8 +1,12 @@
namespace Sozsoft.Sender.Mail; using Volo.Abp.MultiTenancy;
namespace Sozsoft.Sender.Mail;
[Serializable] [Serializable]
public class ErpBackgroundEmailSendingJobArgs public class ErpBackgroundEmailSendingJobArgs : IMultiTenant
{ {
public Guid? TenantId { get; set; }
public string[] To { get; set; } public string[] To { get; set; }
public KeyValuePair<string, string>? Sender { get; set; } public KeyValuePair<string, string>? Sender { get; set; }
@ -14,6 +18,4 @@ public class ErpBackgroundEmailSendingJobArgs
public Dictionary<string, string>? Attachments { get; set; } public Dictionary<string, string>? Attachments { get; set; }
public string? TextContent { get; set; } public string? TextContent { get; set; }
} }

View file

@ -14,7 +14,6 @@
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -5,10 +5,9 @@ using System.Threading.Tasks;
using Sozsoft.Languages; using Sozsoft.Languages;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories; using Volo.Abp.MultiTenancy;
using Volo.Abp.SettingManagement; using Volo.Abp.SettingManagement;
using Volo.Abp.Settings; using Volo.Abp.Settings;
using SettingDefinition = Sozsoft.Settings.Entities.SettingDefinition;
namespace Sozsoft.Settings; namespace Sozsoft.Settings;
@ -20,17 +19,20 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService
private readonly ISettingDefinitionManager settingDefinitionManager; private readonly ISettingDefinitionManager settingDefinitionManager;
private readonly ISettingManager settingManager; private readonly ISettingManager settingManager;
private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager; private readonly ErpSettingDefinitionManager ErpSettingDefinitionManager;
private readonly ICurrentTenant currentTenant;
public SettingUiAppService( public SettingUiAppService(
ILanguageKeyIntegrationService languageKeyIntegrationService, ILanguageKeyIntegrationService languageKeyIntegrationService,
ISettingDefinitionManager settingDefinitionManager, ISettingDefinitionManager settingDefinitionManager,
ISettingManager settingManager, ISettingManager settingManager,
ErpSettingDefinitionManager ErpSettingDefinitionManager) ErpSettingDefinitionManager ErpSettingDefinitionManager,
ICurrentTenant currentTenant)
{ {
this.languageKeyIntegrationService = languageKeyIntegrationService; this.languageKeyIntegrationService = languageKeyIntegrationService;
this.settingDefinitionManager = settingDefinitionManager; this.settingDefinitionManager = settingDefinitionManager;
this.settingManager = settingManager; this.settingManager = settingManager;
this.ErpSettingDefinitionManager = ErpSettingDefinitionManager; this.ErpSettingDefinitionManager = ErpSettingDefinitionManager;
this.currentTenant = currentTenant;
} }
public virtual async Task<List<MainGroupedSettingDto>> GetListAsync() public virtual async Task<List<MainGroupedSettingDto>> GetListAsync()
@ -95,15 +97,18 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService
{ {
if (setting.Providers.IsNullOrEmpty()) if (setting.Providers.IsNullOrEmpty())
{ {
await settingManager.SetForCurrentUserAsync(setting.Name, value); if (currentTenant.Id.HasValue)
{
await settingManager.SetForCurrentTenantAsync(setting.Name, value);
} }
else else
{ {
if (setting.Providers.Any(p => p == UserSettingValueProvider.ProviderName)) await settingManager.SetGlobalAsync(setting.Name, value);
{
await settingManager.SetForCurrentUserAsync(setting.Name, value);
} }
else if (setting.Providers.Any(p => p == TenantSettingValueProvider.ProviderName)) }
else
{
if (setting.Providers.Any(p => p == TenantSettingValueProvider.ProviderName) && currentTenant.Id.HasValue)
{ {
await settingManager.SetForCurrentTenantAsync(setting.Name, value); await settingManager.SetForCurrentTenantAsync(setting.Name, value);
} }
@ -111,6 +116,10 @@ public class SettingUiAppService : ApplicationService, ISettingUiAppService
{ {
await settingManager.SetGlobalAsync(setting.Name, value); await settingManager.SetGlobalAsync(setting.Name, value);
} }
else if (setting.Providers.Any(p => p == UserSettingValueProvider.ProviderName))
{
await settingManager.SetForCurrentUserAsync(setting.Name, value);
}
} }
} }

View file

@ -14,7 +14,6 @@
</PackageReference> </PackageReference>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>

View file

@ -14,7 +14,6 @@
</PackageReference> </PackageReference>
<PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MailKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" /> <PackageReference Include="MimeKit" Version="4.16.0" PrivateAssets="all" />
<PackageReference Include="Scriban" Version="7.2.0" PrivateAssets="all" />
</ItemGroup> </ItemGroup>

View file

@ -258,7 +258,7 @@
"code": "App.SiteManagement.General.NewMemberNotificationEmails", "code": "App.SiteManagement.General.NewMemberNotificationEmails",
"nameKey": "App.SiteManagement.General.NewMemberNotificationEmails", "nameKey": "App.SiteManagement.General.NewMemberNotificationEmails",
"descriptionKey": "App.SiteManagement.General.NewMemberNotificationEmails.Description", "descriptionKey": "App.SiteManagement.General.NewMemberNotificationEmails.Description",
"defaultValue": "SYSTEM@SOZSOFT.COM", "defaultValue": "system@sozsoft.com",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "G|D", "providers": "G|D",
"isInherited": false, "isInherited": false,
@ -274,7 +274,7 @@
"code": "App.SiteManagement.General.TimedLoginEmails", "code": "App.SiteManagement.General.TimedLoginEmails",
"nameKey": "App.SiteManagement.General.TimedLoginEmails", "nameKey": "App.SiteManagement.General.TimedLoginEmails",
"descriptionKey": "App.SiteManagement.General.TimedLoginEmails.Description", "descriptionKey": "App.SiteManagement.General.TimedLoginEmails.Description",
"defaultValue": "SYSTEM@SOZSOFT.COM", "defaultValue": "system@sozsoft.com",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "G|D", "providers": "G|D",
"isInherited": false, "isInherited": false,
@ -466,7 +466,7 @@
"code": "Abp.Mailing.DefaultFromAddress", "code": "Abp.Mailing.DefaultFromAddress",
"nameKey": "Abp.Mailing.DefaultFromAddress", "nameKey": "Abp.Mailing.DefaultFromAddress",
"descriptionKey": "Abp.Mailing.DefaultFromAddress.Description", "descriptionKey": "Abp.Mailing.DefaultFromAddress.Description",
"defaultValue": "SYSTEM@SOZSOFT.COM", "defaultValue": "system@sozsoft.com",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "T|G|D", "providers": "T|G|D",
"isInherited": false, "isInherited": false,
@ -482,7 +482,7 @@
"code": "Abp.Mailing.Smtp.UserName", "code": "Abp.Mailing.Smtp.UserName",
"nameKey": "Abp.Mailing.Smtp.UserName", "nameKey": "Abp.Mailing.Smtp.UserName",
"descriptionKey": "Abp.Mailing.Smtp.UserName.Description", "descriptionKey": "Abp.Mailing.Smtp.UserName.Description",
"defaultValue": "SYSTEM@SOZSOFT.COM", "defaultValue": "system@sozsoft.com",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "T|G|D", "providers": "T|G|D",
"isInherited": false, "isInherited": false,
@ -610,7 +610,7 @@
"code": "Abp.Mailing.AWS.AccessKey", "code": "Abp.Mailing.AWS.AccessKey",
"nameKey": "Abp.Mailing.AWS.AccessKey", "nameKey": "Abp.Mailing.AWS.AccessKey",
"descriptionKey": "Abp.Mailing.AWS.AccessKey.Description", "descriptionKey": "Abp.Mailing.AWS.AccessKey.Description",
"defaultValue": "aXW8L21rP6dPO6Txj76Be2FCpWRBa25EMrSAVL76", "defaultValue": "SibFBAMiSApvz+NChYmlgZmx25JNbximemIDOFps",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "T|G|D", "providers": "T|G|D",
"isInherited": false, "isInherited": false,
@ -626,7 +626,7 @@
"code": "Abp.Mailing.AWS.AccessKeyId", "code": "Abp.Mailing.AWS.AccessKeyId",
"nameKey": "Abp.Mailing.AWS.AccessKeyId", "nameKey": "Abp.Mailing.AWS.AccessKeyId",
"descriptionKey": "Abp.Mailing.AWS.AccessKeyId.Description", "descriptionKey": "Abp.Mailing.AWS.AccessKeyId.Description",
"defaultValue": "AKIATULUYBLX4IY3S2P1", "defaultValue": "AKIA5OCSDJB5KOQY74NV",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "T|G|D", "providers": "T|G|D",
"isInherited": false, "isInherited": false,
@ -722,7 +722,7 @@
"code": "Abp.Account.Captcha.SiteKey", "code": "Abp.Account.Captcha.SiteKey",
"nameKey": "Abp.Account.Captcha.SiteKey", "nameKey": "Abp.Account.Captcha.SiteKey",
"descriptionKey": "Abp.Account.Captcha.SiteKey.Description", "descriptionKey": "Abp.Account.Captcha.SiteKey.Description",
"defaultValue": "0x4AAAAAAAGadwQME-GSYuJU", "defaultValue": "0x4AAAAAABdEjmiXxcl0j7jp",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "G|D", "providers": "G|D",
"isInherited": false, "isInherited": false,
@ -738,7 +738,7 @@
"code": "Abp.Account.Captcha.SecretKey", "code": "Abp.Account.Captcha.SecretKey",
"nameKey": "Abp.Account.Captcha.SecretKey", "nameKey": "Abp.Account.Captcha.SecretKey",
"descriptionKey": "Abp.Account.Captcha.SecretKey.Description", "descriptionKey": "Abp.Account.Captcha.SecretKey.Description",
"defaultValue": "0x4AAAAAAAGad_f_WP47IcNBs9FTu5DhNX8", "defaultValue": "0x4AAAAAABdEjhw1A8sJZUvQX8-CgqvB3mE",
"isVisibleToClients": false, "isVisibleToClients": false,
"providers": "G|D", "providers": "G|D",
"isInherited": false, "isInherited": false,

View file

@ -26,7 +26,7 @@ public class PlatformBackgroundWorkerTemplateDefinitionProvider : TemplateDefini
foreach (var worker in workers.Where(a => foreach (var worker in workers.Where(a =>
a.IsActive && a.IsActive &&
a.WorkerType == WorkerTypeEnum.MailQueueWorker && a.WorkerType == WorkerTypeEnum.MailQueueWorker &&
a.Options != null && a.Options != string.Empty).ToList()) a.Options != null).ToList().Where(a => !a.Options.IsNullOrWhiteSpace()))
{ {
var Options = JsonSerializer.Deserialize<MailQueueWorkerOptions>(worker.Options); var Options = JsonSerializer.Deserialize<MailQueueWorkerOptions>(worker.Options);

View file

@ -12,7 +12,6 @@ const Captcha = forwardRef((props: CaptchaProps, ref: Ref<TurnstileInstance>) =>
const { className, onError, onExpire, onSuccess } = props const { className, onError, onExpire, onSuccess } = props
return ( return (
<>
<Turnstile <Turnstile
ref={ref} ref={ref}
className={className ?? 'mb-4 mx-auto'} className={className ?? 'mb-4 mx-auto'}
@ -21,7 +20,6 @@ const Captcha = forwardRef((props: CaptchaProps, ref: Ref<TurnstileInstance>) =>
onExpire={onExpire} onExpire={onExpire}
onSuccess={onSuccess} onSuccess={onSuccess}
/> />
</>
) )
}) })
Captcha.displayName = 'Captcha' Captcha.displayName = 'Captcha'

View file

@ -0,0 +1,148 @@
import Input from '@/components/ui/Input'
import { getTenantByNameDetail } from '@/services/tenant.service'
import { useStoreActions, useStoreState } from '@/store'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { defaultDomain, getSubdomain } from '@/utils/subdomain'
import type { CSSProperties } from 'react'
import { useCallback, useEffect, useRef } from 'react'
const hiddenTenantStyle: CSSProperties = {
opacity: 0,
position: 'absolute',
pointerEvents: 'none',
height: 0,
margin: 0,
padding: 0,
border: 'none',
}
const TenantSelector = () => {
const { translate } = useLocalization()
const isMultiTenant = useStoreState((state) => state.abpConfig.config?.multiTenancy.isEnabled)
const tenantName = useStoreState((state) => state.locale.currentTenantName)
const { setTenantName } = useStoreActions((actions) => actions.locale)
const { setTenant } = useStoreActions((actions) => actions.auth.tenant)
const { setWarning } = useStoreActions((actions) => actions.base.messages)
const requestIdRef = useRef(0)
const lastRequestedTenantNameRef = useRef<string>()
const subDomainName = getSubdomain()
const isSubdomainTenant = !!subDomainName && subDomainName !== defaultDomain
const tenantStyle = isSubdomainTenant ? hiddenTenantStyle : undefined
const setWarningTimeout = useCallback(
(message: string) => {
setTimeout(() => {
setWarning(message)
}, 100)
},
[setWarning],
)
const redirectToMainDomain = useCallback(
(name: string) => {
setTenantName(undefined)
const parts = window.location.hostname.split('.')
const mainDomain = parts.length >= 3 ? parts.slice(1).join('.') : window.location.hostname
setWarningTimeout(
`"${name}" kurumuna ait kayıt bulunamadı.\nAna sayfaya yönlendiriliyorsunuz...`,
)
setTimeout(() => {
window.location.href = `${window.location.protocol}//${mainDomain}`
}, 3000)
},
[setTenantName, setWarningTimeout],
)
const fetchDataByName = useCallback(
async (name: string, isSubdomain = false) => {
if (!isSubdomain && name === lastRequestedTenantNameRef.current) {
return
}
lastRequestedTenantNameRef.current = name
const requestId = requestIdRef.current + 1
requestIdRef.current = requestId
if (name) {
try {
const response = await getTenantByNameDetail(name)
if (requestId !== requestIdRef.current) {
return
}
if (response.data) {
setTenant({
tenantId: response.data.id,
tenantName: response.data.name,
menuGroup: response.data.menuGroup,
})
} else {
setTenant(undefined)
if (isSubdomain) redirectToMainDomain(name)
}
} catch {
if (requestId !== requestIdRef.current) {
return
}
setTenant(undefined)
if (isSubdomain) redirectToMainDomain(name)
}
} else {
setTenant(undefined)
}
},
[redirectToMainDomain, setTenant],
)
const handleTenantNameChange = (value: string) => {
setTenantName(value)
}
const handleTenantNameBlur = () => {
if (subDomainName) {
return
}
fetchDataByName(tenantName || '')
}
useEffect(() => {
if (!isMultiTenant) {
setTenant(undefined)
return
}
if (subDomainName) {
setTenantName(subDomainName)
fetchDataByName(subDomainName, true)
}
}, [fetchDataByName, isMultiTenant, setTenant, setTenantName, subDomainName])
if (!isMultiTenant) {
return null
}
return (
<>
<label className="form-label mb-2" style={tenantStyle}>
{translate('::Organization')}
</label>
<div className="mb-4">
<Input
placeholder={translate('::Organization')}
value={tenantName ?? ''}
onChange={(event) => handleTenantNameChange(event.target.value)}
onBlur={handleTenantNameBlur}
style={tenantStyle}
aria-hidden={isSubdomainTenant ? 'true' : 'false'}
autoFocus={!isSubdomainTenant}
/>
</div>
</>
)
}
export default TenantSelector

View file

@ -19,6 +19,7 @@ export { default as SegmentItemOption } from './SegmentItemOption'
export { default as StickyFooter } from './StickyFooter' export { default as StickyFooter } from './StickyFooter'
export { default as SvgIcon } from './SvgIcon' export { default as SvgIcon } from './SvgIcon'
export { default as TableRowSkeleton } from './loaders/TableRowSkeleton' export { default as TableRowSkeleton } from './loaders/TableRowSkeleton'
export { default as TenantSelector } from './TenantSelector'
export { default as TextBlockSkeleton } from './loaders/TextBlockSkeleton' export { default as TextBlockSkeleton } from './loaders/TextBlockSkeleton'
export { default as TextEllipsis } from './TextEllipsis' export { default as TextEllipsis } from './TextEllipsis'
export { default as UsersAvatarGroup } from './UsersAvatarGroup' export { default as UsersAvatarGroup } from './UsersAvatarGroup'

View file

@ -456,13 +456,15 @@ function OrgChartNode({
style={{ cursor: dragging ? 'grabbing' : 'grab' }} style={{ cursor: dragging ? 'grabbing' : 'grab' }}
> >
{/* Header bar */} {/* Header bar */}
<div data-header="" className={`${headerBg} rounded-t-xl px-3 py-2 flex items-center gap-2`}> <div data-header="" className={`${headerBg} rounded-t-xl px-3 py-2 flex items-center gap-2 dark:bg-gray-900 dark:text-gray-100`}>
{mode === 'department' ? ( {mode === 'department' ? (
<FaBuilding className="w-3 h-3 text-white opacity-80 flex-shrink-0" /> <FaBuilding className="w-3 h-3 text-white opacity-80 flex-shrink-0" />
) : ( ) : (
<FaBriefcase className="w-3 h-3 text-white opacity-80 flex-shrink-0" /> <FaBriefcase className="w-3 h-3 text-white opacity-80 flex-shrink-0" />
)} )}
<span data-node-name="" className="text-white font-semibold text-xs truncate flex-1">{node.name}</span> <span data-node-name="" className="text-white font-semibold text-xs truncate flex-1 dark:bg-gray-900 dark:text-gray-100">
{node.name}
</span>
{hasChildren && ( {hasChildren && (
<button <button
data-stop-drag="true" data-stop-drag="true"
@ -514,7 +516,7 @@ function OrgChartNode({
{/* Child count badge */} {/* Child count badge */}
{hasChildren && ( {hasChildren && (
<div className="absolute -bottom-2.5 left-1/2 -translate-x-1/2 bg-white border border-slate-200 rounded-full px-2 py-0.5 text-xs text-slate-500 shadow-sm whitespace-nowrap z-10"> <div className="absolute -bottom-2.5 left-1/2 -translate-x-1/2 bg-white border border-slate-200 rounded-full px-2 py-0.5 text-xs text-slate-500 shadow-sm whitespace-nowrap z-10 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-100">
{node.children.length} {node.children.length}
</div> </div>
)} )}

View file

@ -38,9 +38,9 @@ const LoginHistoryIcon = ({ type }: { type: string }) => {
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
password: Yup.string().required('Password Required'), password: Yup.string().required(),
newPassword: Yup.string() newPassword: Yup.string()
.required('Enter your new password') .required()
.min(6, 'Too Short!') .min(6, 'Too Short!')
.matches( .matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{6,})/, /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{6,})/,

View file

@ -1,5 +1,6 @@
import ActionLink from '@/components/shared/ActionLink' import ActionLink from '@/components/shared/ActionLink'
import Captcha from '@/components/shared/Captcha' import Captcha from '@/components/shared/Captcha'
import TenantSelector from '@/components/shared/TenantSelector'
import Alert from '@/components/ui/Alert' import Alert from '@/components/ui/Alert'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
import { FormContainer, FormItem } from '@/components/ui/Form' import { FormContainer, FormItem } from '@/components/ui/Form'
@ -84,6 +85,7 @@ const ExtendLogin = () => {
{message} {message}
</Alert> </Alert>
)} )}
<TenantSelector />
<Formik <Formik
initialValues={{ initialValues={{
email: userName, email: userName,

View file

@ -1,3 +1,4 @@
import { TenantSelector } from '@/components/shared'
import ActionLink from '@/components/shared/ActionLink' import ActionLink from '@/components/shared/ActionLink'
import Captcha from '@/components/shared/Captcha' import Captcha from '@/components/shared/Captcha'
import Alert from '@/components/ui/Alert' import Alert from '@/components/ui/Alert'
@ -87,6 +88,7 @@ const ForgotPassword = () => {
{message} {message}
</Alert> </Alert>
)} )}
<TenantSelector />
<Formik <Formik
initialValues={{ initialValues={{
email: userName, email: userName,

View file

@ -2,6 +2,7 @@ import { FailedSignInResponse } from '@/proxy/auth/models'
import ActionLink from '@/components/shared/ActionLink' import ActionLink from '@/components/shared/ActionLink'
import Captcha from '@/components/shared/Captcha' import Captcha from '@/components/shared/Captcha'
import PasswordInput from '@/components/shared/PasswordInput' import PasswordInput from '@/components/shared/PasswordInput'
import TenantSelector from '@/components/shared/TenantSelector'
import Alert from '@/components/ui/Alert' import Alert from '@/components/ui/Alert'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
import Checkbox from '@/components/ui/Checkbox' import Checkbox from '@/components/ui/Checkbox'
@ -9,7 +10,6 @@ import { FormContainer, FormItem } from '@/components/ui/Form'
import Input from '@/components/ui/Input' import Input from '@/components/ui/Input'
import PlatformLoginResultType from '@/constants/login.result.enum' import PlatformLoginResultType from '@/constants/login.result.enum'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { getTenantByNameDetail } from '@/services/tenant.service'
import { useStoreActions, useStoreState } from '@/store' import { useStoreActions, useStoreState } from '@/store'
import useAuth from '@/utils/hooks/useAuth' import useAuth from '@/utils/hooks/useAuth'
import { useLocalization } from '@/utils/hooks/useLocalization' import { useLocalization } from '@/utils/hooks/useLocalization'
@ -17,10 +17,9 @@ import useTimeOutMessage from '@/utils/hooks/useTimeOutMessage'
import { TurnstileInstance } from '@marsidev/react-turnstile' import { TurnstileInstance } from '@marsidev/react-turnstile'
import { Field, Form, Formik } from 'formik' import { Field, Form, Formik } from 'formik'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { useEffect, useRef, useState } from 'react' import { useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import * as Yup from 'yup' import * as Yup from 'yup'
import { defaultDomain, getSubdomain } from '@/utils/subdomain'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { APP_NAME } from '@/constants/app.constant' import { APP_NAME } from '@/constants/app.constant'
@ -33,28 +32,23 @@ type SignInFormSchema = {
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
userName: Yup.string().required('Please enter your user name'), userName: Yup.string().required(),
password: Yup.string().required('Please enter your password'), password: Yup.string().required(),
rememberMe: Yup.bool(), rememberMe: Yup.bool(),
twoFactor: Yup.boolean(), twoFactor: Yup.boolean(),
twoFactorCode: Yup.string().when('twoFactor', { twoFactorCode: Yup.string().when('twoFactor', {
is: true, is: true,
then: (schema) => schema.required('Mail adresinize gönderilen doğrulama kodunu giriniz'), then: (schema) => schema.required(),
otherwise: (schema) => schema.notRequired(), otherwise: (schema) => schema.notRequired(),
}), }),
}) })
const Login = () => { const Login = () => {
const navigate = useNavigate() const navigate = useNavigate()
const isMultiTenant = useStoreState((a) => a.abpConfig.config?.multiTenancy.isEnabled)
const { setTenant } = useStoreActions((a) => a.auth.tenant)
const UiVersion = useStoreState((state) => state.locale.currentUiVersion) const UiVersion = useStoreState((state) => state.locale.currentUiVersion)
const { setUiVersion } = useStoreActions((a) => a.locale) const { setUiVersion } = useStoreActions((a) => a.locale)
const tenantName = useStoreState((state) => state.locale.currentTenantName)
const { setTenantName } = useStoreActions((actions) => actions.locale)
const [message, setMessage] = useState('') const [message, setMessage] = useState('')
const [error, setError] = useTimeOutMessage(300000) const [error, setError] = useTimeOutMessage(300000)
const [twoFactor, setTwoFactor] = useState(false) const [twoFactor, setTwoFactor] = useState(false)
@ -93,9 +87,6 @@ const Login = () => {
setError(result.message) setError(result.message)
} else { } else {
setError('') setError('')
//Tenant belirlenmişse
fetchDataByName(tenantName || '')
} }
if (result.status === 'failed') { if (result.status === 'failed') {
@ -165,57 +156,6 @@ const Login = () => {
setSubmitting(false) setSubmitting(false)
} }
const fetchDataByName = async (name: string, isSubdomain = false) => {
if (name) {
try {
const response = await getTenantByNameDetail(name)
if (response.data) {
setTenant({ tenantId: response.data.id, tenantName: response.data.name, menuGroup: response.data.menuGroup });
} else {
setTenant(undefined)
if (isSubdomain) redirectToMainDomain(name)
}
} catch {
setTenant(undefined)
if (isSubdomain) redirectToMainDomain(name)
}
} else {
setTenant(undefined)
}
}
const redirectToMainDomain = (name: string) => {
setTenantName(undefined)
const parts = window.location.hostname.split('.')
const mainDomain = parts.length >= 3 ? parts.slice(1).join('.') : window.location.hostname
setWarningTimeout(`"${name}" kurumuna ait kayıt bulunamadı. Ana sayfaya yönlendiriliyorsunuz...`)
setTimeout(() => {
window.location.href = `${window.location.protocol}//${mainDomain}`
}, 3000)
}
const subDomainName = getSubdomain()
useEffect(() => {
if (subDomainName) {
setTenantName(subDomainName)
fetchDataByName(subDomainName, true)
}
}, [subDomainName])
const tenantStyle: React.CSSProperties | undefined =
subDomainName && subDomainName !== defaultDomain
? {
opacity: 0,
position: 'absolute',
pointerEvents: 'none',
height: 0,
margin: 0,
padding: 0,
border: 'none',
}
: undefined
const findUiVersion = async () => { const findUiVersion = async () => {
try { try {
const res = await fetch(`/version.json?ts=${Date.now()}`) const res = await fetch(`/version.json?ts=${Date.now()}`)
@ -246,22 +186,7 @@ const Login = () => {
<p>{translate('::Abp.Account.WelcomeBack.Message')}</p> <p>{translate('::Abp.Account.WelcomeBack.Message')}</p>
</div> </div>
{isMultiTenant && ( <TenantSelector />
<>
<label className="form-label mb-2" style={tenantStyle}>
{translate('::Organization')}
</label>
<div className="mb-4">
<Input
placeholder={translate('::Organization')}
value={tenantName}
onChange={(e) => setTenantName(e.target.value)}
style={tenantStyle}
aria-hidden={subDomainName && subDomainName !== defaultDomain ? 'true' : 'false'}
/>
</div>
</>
)}
<div> <div>
<Formik <Formik
initialValues={{ initialValues={{
@ -289,7 +214,7 @@ const Login = () => {
name="userName" name="userName"
placeholder={translate('::Abp.Account.EmailAddress')} placeholder={translate('::Abp.Account.EmailAddress')}
component={Input} component={Input}
inputClassName="dark:bg-gray-900 dark:text-gray-100" className="dark:bg-gray-900 dark:text-gray-100"
/> />
</FormItem> </FormItem>
)} )}

View file

@ -1,5 +1,6 @@
import ActionLink from '@/components/shared/ActionLink' import ActionLink from '@/components/shared/ActionLink'
import PasswordInput from '@/components/shared/PasswordInput' import PasswordInput from '@/components/shared/PasswordInput'
import TenantSelector from '@/components/shared/TenantSelector'
import Alert from '@/components/ui/Alert' import Alert from '@/components/ui/Alert'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
import { FormContainer, FormItem } from '@/components/ui/Form' import { FormContainer, FormItem } from '@/components/ui/Form'
@ -83,6 +84,7 @@ const Register = () => {
{error} {error}
</Alert> </Alert>
)} )}
<TenantSelector />
<Formik <Formik
initialValues={{ initialValues={{
email: '', email: '',

View file

@ -21,7 +21,7 @@ type ResetPasswordFormSchema = {
} }
const validationSchema = Yup.object().shape({ const validationSchema = Yup.object().shape({
password: Yup.string().required('Please enter your password'), password: Yup.string().required(),
confirmPassword: Yup.string().oneOf([Yup.ref('password')], 'Your passwords do not match'), confirmPassword: Yup.string().oneOf([Yup.ref('password')], 'Your passwords do not match'),
}) })

View file

@ -2,7 +2,7 @@ import useAccount from '@/utils/hooks/useAccount'
import { Alert, Button, FormContainer, FormItem, Input } from '@/components/ui' import { Alert, Button, FormContainer, FormItem, Input } from '@/components/ui'
import { Field, Form, Formik } from 'formik' import { Field, Form, Formik } from 'formik'
import * as Yup from 'yup' import * as Yup from 'yup'
import { ActionLink } from '@/components/shared' import { ActionLink, TenantSelector } from '@/components/shared'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { store } from '@/store' import { store } from '@/store'
import Captcha from '@/components/shared/Captcha' import Captcha from '@/components/shared/Captcha'
@ -57,6 +57,7 @@ const SendConfirmationCode = () => {
{error} {error}
</Alert> </Alert>
)} )}
<TenantSelector />
<Formik <Formik
initialValues={{ initialValues={{
email: userName, email: userName,