Age gating: birth month/year in registration, under-16 login block, under-18 NSFW block
This commit is contained in:
parent
256592379a
commit
b10595f252
|
|
@ -43,6 +43,8 @@ type RegisterRequest struct {
|
||||||
AcceptPrivacy bool `json:"accept_privacy" binding:"required,eq=true"`
|
AcceptPrivacy bool `json:"accept_privacy" binding:"required,eq=true"`
|
||||||
EmailNewsletter bool `json:"email_newsletter"`
|
EmailNewsletter bool `json:"email_newsletter"`
|
||||||
EmailContact bool `json:"email_contact"`
|
EmailContact bool `json:"email_contact"`
|
||||||
|
BirthMonth int `json:"birth_month" binding:"required,min=1,max=12"`
|
||||||
|
BirthYear int `json:"birth_year" binding:"required,min=1900,max=2025"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginRequest struct {
|
type LoginRequest struct {
|
||||||
|
|
@ -144,6 +146,8 @@ func (h *AuthHandler) Register(c *gin.Context) {
|
||||||
ID: userID,
|
ID: userID,
|
||||||
Handle: &req.Handle,
|
Handle: &req.Handle,
|
||||||
DisplayName: &req.DisplayName,
|
DisplayName: &req.DisplayName,
|
||||||
|
BirthMonth: req.BirthMonth,
|
||||||
|
BirthYear: req.BirthYear,
|
||||||
}
|
}
|
||||||
if err := h.repo.CreateProfile(c.Request.Context(), profile); err != nil {
|
if err := h.repo.CreateProfile(c.Request.Context(), profile); err != nil {
|
||||||
log.Printf("[Auth] Failed to create profile for %s: %v. Rolling back user.", user.ID, err)
|
log.Printf("[Auth] Failed to create profile for %s: %v. Rolling back user.", user.ID, err)
|
||||||
|
|
@ -232,6 +236,25 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Age gate: check if user is under 16
|
||||||
|
var profile *models.Profile
|
||||||
|
profile, _ = h.repo.GetProfileByID(c.Request.Context(), user.ID.String())
|
||||||
|
if profile != nil && profile.BirthYear > 0 {
|
||||||
|
now := time.Now()
|
||||||
|
age := now.Year() - profile.BirthYear
|
||||||
|
if int(now.Month()) < profile.BirthMonth {
|
||||||
|
age--
|
||||||
|
}
|
||||||
|
if age < 16 {
|
||||||
|
log.Printf("[Auth] Login blocked for underage user %s (age %d)", req.Email, age)
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": "You must be at least 16 years old to use Sojorn. Please come back when you're older!",
|
||||||
|
"code": "age_restricted",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if user.Status == models.UserStatusPending {
|
if user.Status == models.UserStatusPending {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Email verification required", "code": "verify_email"})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Email verification required", "code": "verify_email"})
|
||||||
return
|
return
|
||||||
|
|
@ -270,10 +293,13 @@ func (h *AuthHandler) Login(c *gin.Context) {
|
||||||
refreshToken, _ := generateRandomString(32)
|
refreshToken, _ := generateRandomString(32)
|
||||||
_ = h.repo.StoreRefreshToken(c.Request.Context(), user.ID.String(), refreshToken, 30*24*time.Hour)
|
_ = h.repo.StoreRefreshToken(c.Request.Context(), user.ID.String(), refreshToken, 30*24*time.Hour)
|
||||||
|
|
||||||
profile, err := h.repo.GetProfileByID(c.Request.Context(), user.ID.String())
|
// Re-fetch profile if not already loaded from age check
|
||||||
if err != nil {
|
if profile == nil {
|
||||||
log.Printf("[Auth] Failed to get profile for %s: %v", user.ID, err)
|
profile, _ = h.repo.GetProfileByID(c.Request.Context(), user.ID.String())
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch profile", "details": err.Error()})
|
}
|
||||||
|
if profile == nil {
|
||||||
|
log.Printf("[Auth] Failed to get profile for %s", user.ID)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch profile"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -82,6 +83,25 @@ func (h *SettingsHandler) UpdateUserSettings(c *gin.Context) {
|
||||||
}
|
}
|
||||||
us.UserID = userID
|
us.UserID = userID
|
||||||
|
|
||||||
|
// Block NSFW toggle for users under 18
|
||||||
|
if us.NSFWEnabled != nil && *us.NSFWEnabled {
|
||||||
|
profile, err := h.userRepo.GetProfileByID(c.Request.Context(), userID.String())
|
||||||
|
if err == nil && profile != nil && profile.BirthYear > 0 {
|
||||||
|
now := time.Now()
|
||||||
|
age := now.Year() - profile.BirthYear
|
||||||
|
if int(now.Month()) < profile.BirthMonth {
|
||||||
|
age--
|
||||||
|
}
|
||||||
|
if age < 18 {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
|
"error": "You must be at least 18 years old to enable sensitive content. This is required by law in most jurisdictions.",
|
||||||
|
"code": "age_restricted_nsfw",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := h.userRepo.UpdateUserSettings(c.Request.Context(), &us); err != nil {
|
if err := h.userRepo.UpdateUserSettings(c.Request.Context(), &us); err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to update user settings")
|
log.Error().Err(err).Msg("Failed to update user settings")
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update settings", "details": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update settings", "details": err.Error()})
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ type Profile struct {
|
||||||
EncryptedPrivateKey *string `json:"encrypted_private_key" db:"encrypted_private_key"`
|
EncryptedPrivateKey *string `json:"encrypted_private_key" db:"encrypted_private_key"`
|
||||||
HasCompletedOnboarding bool `json:"has_completed_onboarding" db:"has_completed_onboarding"`
|
HasCompletedOnboarding bool `json:"has_completed_onboarding" db:"has_completed_onboarding"`
|
||||||
Role string `json:"role" db:"role"`
|
Role string `json:"role" db:"role"`
|
||||||
|
BirthMonth int `json:"birth_month" db:"birth_month"`
|
||||||
|
BirthYear int `json:"birth_year" db:"birth_year"`
|
||||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ func (r *UserRepository) Pool() *pgxpool.Pool {
|
||||||
|
|
||||||
func (r *UserRepository) CreateProfile(ctx context.Context, profile *models.Profile) error {
|
func (r *UserRepository) CreateProfile(ctx context.Context, profile *models.Profile) error {
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO public.profiles (id, handle, display_name, bio, origin_country)
|
INSERT INTO public.profiles (id, handle, display_name, bio, origin_country, birth_month, birth_year)
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
`
|
`
|
||||||
_, err := r.pool.Exec(ctx, query, profile.ID, profile.Handle, profile.DisplayName, profile.Bio, profile.OriginCountry)
|
_, err := r.pool.Exec(ctx, query, profile.ID, profile.Handle, profile.DisplayName, profile.Bio, profile.OriginCountry, profile.BirthMonth, profile.BirthYear)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create profile: %w", err)
|
return fmt.Errorf("failed to create profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -51,11 +51,11 @@ func (r *UserRepository) CreateProfile(ctx context.Context, profile *models.Prof
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UserRepository) GetProfileByID(ctx context.Context, id string) (*models.Profile, error) {
|
func (r *UserRepository) GetProfileByID(ctx context.Context, id string) (*models.Profile, error) {
|
||||||
query := `SELECT id, handle, display_name, bio, avatar_url, origin_country, has_completed_onboarding, is_official, is_private, role, created_at FROM public.profiles WHERE id = $1::uuid`
|
query := `SELECT id, handle, display_name, bio, avatar_url, origin_country, has_completed_onboarding, is_official, is_private, role, created_at, COALESCE(birth_month, 0), COALESCE(birth_year, 0) FROM public.profiles WHERE id = $1::uuid`
|
||||||
|
|
||||||
var p models.Profile
|
var p models.Profile
|
||||||
err := r.pool.QueryRow(ctx, query, id).Scan(
|
err := r.pool.QueryRow(ctx, query, id).Scan(
|
||||||
&p.ID, &p.Handle, &p.DisplayName, &p.Bio, &p.AvatarURL, &p.OriginCountry, &p.HasCompletedOnboarding, &p.IsOfficial, &p.IsPrivate, &p.Role, &p.CreatedAt,
|
&p.ID, &p.Handle, &p.DisplayName, &p.Bio, &p.AvatarURL, &p.OriginCountry, &p.HasCompletedOnboarding, &p.IsOfficial, &p.IsPrivate, &p.Role, &p.CreatedAt, &p.BirthMonth, &p.BirthYear,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -66,7 +66,8 @@ func (r *UserRepository) GetProfileByID(ctx context.Context, id string) (*models
|
||||||
func (r *UserRepository) GetProfileByHandle(ctx context.Context, handle string) (*models.Profile, error) {
|
func (r *UserRepository) GetProfileByHandle(ctx context.Context, handle string) (*models.Profile, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT id, handle, display_name, bio, avatar_url, origin_country,
|
SELECT id, handle, display_name, bio, avatar_url, origin_country,
|
||||||
has_completed_onboarding, is_official, is_private, role, created_at
|
has_completed_onboarding, is_official, is_private, role, created_at,
|
||||||
|
COALESCE(birth_month, 0), COALESCE(birth_year, 0)
|
||||||
FROM public.profiles
|
FROM public.profiles
|
||||||
WHERE handle = $1
|
WHERE handle = $1
|
||||||
`
|
`
|
||||||
|
|
@ -74,6 +75,7 @@ func (r *UserRepository) GetProfileByHandle(ctx context.Context, handle string)
|
||||||
err := r.pool.QueryRow(ctx, query, handle).Scan(
|
err := r.pool.QueryRow(ctx, query, handle).Scan(
|
||||||
&p.ID, &p.Handle, &p.DisplayName, &p.Bio, &p.AvatarURL, &p.OriginCountry,
|
&p.ID, &p.Handle, &p.DisplayName, &p.Bio, &p.AvatarURL, &p.OriginCountry,
|
||||||
&p.HasCompletedOnboarding, &p.IsOfficial, &p.IsPrivate, &p.Role, &p.CreatedAt,
|
&p.HasCompletedOnboarding, &p.IsOfficial, &p.IsPrivate, &p.Role, &p.CreatedAt,
|
||||||
|
&p.BirthMonth, &p.BirthYear,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ class Profile {
|
||||||
final int? registrationId;
|
final int? registrationId;
|
||||||
final String? encryptedPrivateKey;
|
final String? encryptedPrivateKey;
|
||||||
final bool hasCompletedOnboarding;
|
final bool hasCompletedOnboarding;
|
||||||
|
final int birthMonth;
|
||||||
|
final int birthYear;
|
||||||
|
|
||||||
Profile({
|
Profile({
|
||||||
required this.id,
|
required this.id,
|
||||||
|
|
@ -42,6 +44,8 @@ class Profile {
|
||||||
this.registrationId,
|
this.registrationId,
|
||||||
this.encryptedPrivateKey,
|
this.encryptedPrivateKey,
|
||||||
this.hasCompletedOnboarding = false,
|
this.hasCompletedOnboarding = false,
|
||||||
|
this.birthMonth = 0,
|
||||||
|
this.birthYear = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory Profile.fromJson(Map<String, dynamic> json) {
|
factory Profile.fromJson(Map<String, dynamic> json) {
|
||||||
|
|
@ -81,6 +85,8 @@ class Profile {
|
||||||
registrationId: json['registration_id'] as int?,
|
registrationId: json['registration_id'] as int?,
|
||||||
encryptedPrivateKey: json['encrypted_private_key'] as String?,
|
encryptedPrivateKey: json['encrypted_private_key'] as String?,
|
||||||
hasCompletedOnboarding: json['has_completed_onboarding'] as bool? ?? false,
|
hasCompletedOnboarding: json['has_completed_onboarding'] as bool? ?? false,
|
||||||
|
birthMonth: json['birth_month'] as int? ?? 0,
|
||||||
|
birthYear: json['birth_year'] as int? ?? 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,6 +111,8 @@ class Profile {
|
||||||
'registration_id': registrationId,
|
'registration_id': registrationId,
|
||||||
'encrypted_private_key': encryptedPrivateKey,
|
'encrypted_private_key': encryptedPrivateKey,
|
||||||
'has_completed_onboarding': hasCompletedOnboarding,
|
'has_completed_onboarding': hasCompletedOnboarding,
|
||||||
|
'birth_month': birthMonth,
|
||||||
|
'birth_year': birthYear,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,6 +136,8 @@ class Profile {
|
||||||
bool? isPrivate,
|
bool? isPrivate,
|
||||||
bool? isOfficial,
|
bool? isOfficial,
|
||||||
bool? hasCompletedOnboarding,
|
bool? hasCompletedOnboarding,
|
||||||
|
int? birthMonth,
|
||||||
|
int? birthYear,
|
||||||
}) {
|
}) {
|
||||||
return Profile(
|
return Profile(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
|
|
@ -149,6 +159,8 @@ class Profile {
|
||||||
registrationId: registrationId ?? this.registrationId,
|
registrationId: registrationId ?? this.registrationId,
|
||||||
encryptedPrivateKey: encryptedPrivateKey ?? this.encryptedPrivateKey,
|
encryptedPrivateKey: encryptedPrivateKey ?? this.encryptedPrivateKey,
|
||||||
hasCompletedOnboarding: hasCompletedOnboarding ?? this.hasCompletedOnboarding,
|
hasCompletedOnboarding: hasCompletedOnboarding ?? this.hasCompletedOnboarding,
|
||||||
|
birthMonth: birthMonth ?? this.birthMonth,
|
||||||
|
birthYear: birthYear ?? this.birthYear,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,10 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
|
||||||
|
|
||||||
// Email preferences (single combined option)
|
// Email preferences (single combined option)
|
||||||
bool _emailUpdates = false;
|
bool _emailUpdates = false;
|
||||||
|
|
||||||
|
// Age gate
|
||||||
|
int? _birthMonth;
|
||||||
|
int? _birthYear;
|
||||||
|
|
||||||
// Turnstile site key from environment or default production key
|
// Turnstile site key from environment or default production key
|
||||||
static const String _turnstileSiteKey = String.fromEnvironment(
|
static const String _turnstileSiteKey = String.fromEnvironment(
|
||||||
|
|
@ -61,6 +65,14 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate age gate
|
||||||
|
if (_birthMonth == null || _birthYear == null) {
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = 'Please enter your date of birth';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Validate legal consent
|
// Validate legal consent
|
||||||
if (!_acceptTerms) {
|
if (!_acceptTerms) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|
@ -93,6 +105,8 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
|
||||||
acceptPrivacy: _acceptPrivacy,
|
acceptPrivacy: _acceptPrivacy,
|
||||||
emailNewsletter: _emailUpdates,
|
emailNewsletter: _emailUpdates,
|
||||||
emailContact: _emailUpdates,
|
emailContact: _emailUpdates,
|
||||||
|
birthMonth: _birthMonth!,
|
||||||
|
birthYear: _birthYear!,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|
@ -286,6 +300,63 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: AppTheme.spacingLg),
|
const SizedBox(height: AppTheme.spacingLg),
|
||||||
|
|
||||||
|
// Age Gate - Birth Month & Year
|
||||||
|
Text(
|
||||||
|
'Date of Birth',
|
||||||
|
style: AppTheme.bodyMedium.copyWith(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'You must be at least 16 to use Sojorn. Users under 18 cannot access sensitive content.',
|
||||||
|
style: AppTheme.textTheme.labelSmall?.copyWith(
|
||||||
|
color: AppTheme.navyText.withOpacity(0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: AppTheme.spacingSm),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
// Month dropdown
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: DropdownButtonFormField<int>(
|
||||||
|
value: _birthMonth,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Month',
|
||||||
|
prefixIcon: Icon(Icons.calendar_month),
|
||||||
|
),
|
||||||
|
items: List.generate(12, (i) {
|
||||||
|
final month = i + 1;
|
||||||
|
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||||
|
return DropdownMenuItem(value: month, child: Text(months[i]));
|
||||||
|
}),
|
||||||
|
onChanged: (v) => setState(() => _birthMonth = v),
|
||||||
|
validator: (v) => v == null ? 'Required' : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: AppTheme.spacingSm),
|
||||||
|
// Year dropdown
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: DropdownButtonFormField<int>(
|
||||||
|
value: _birthYear,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Year',
|
||||||
|
),
|
||||||
|
items: List.generate(
|
||||||
|
DateTime.now().year - 1900 + 1,
|
||||||
|
(i) {
|
||||||
|
final year = DateTime.now().year - i;
|
||||||
|
return DropdownMenuItem(value: year, child: Text('$year'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onChanged: (v) => setState(() => _birthYear = v),
|
||||||
|
validator: (v) => v == null ? 'Required' : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: AppTheme.spacingLg),
|
||||||
|
|
||||||
// Turnstile CAPTCHA
|
// Turnstile CAPTCHA
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
|
||||||
|
|
@ -408,6 +408,16 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||||
final userSettings = state.user;
|
final userSettings = state.user;
|
||||||
if (userSettings == null) return const SizedBox.shrink();
|
if (userSettings == null) return const SizedBox.shrink();
|
||||||
|
|
||||||
|
// Calculate age from profile
|
||||||
|
final profile = state.profile;
|
||||||
|
bool isUnder18 = false;
|
||||||
|
if (profile != null && profile.birthYear > 0) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
int age = now.year - profile.birthYear;
|
||||||
|
if (now.month < profile.birthMonth) age--;
|
||||||
|
isUnder18 = age < 18;
|
||||||
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppTheme.cardSurface,
|
color: AppTheme.cardSurface,
|
||||||
|
|
@ -434,14 +444,18 @@ class _ProfileSettingsScreenState extends ConsumerState<ProfileSettingsScreen> {
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Show Sensitive Content (NSFW)'),
|
title: const Text('Show Sensitive Content (NSFW)'),
|
||||||
subtitle: const Text(
|
subtitle: Text(
|
||||||
'Enable to see posts marked as sensitive (violence, mature themes, etc). Disabled by default.',
|
isUnder18
|
||||||
|
? 'You must be at least 18 years old to enable this feature. This is required by law in most jurisdictions.'
|
||||||
|
: 'Enable to see posts marked as sensitive (violence, mature themes, etc). Disabled by default.',
|
||||||
),
|
),
|
||||||
value: userSettings.nsfwEnabled,
|
value: userSettings.nsfwEnabled,
|
||||||
activeColor: Colors.amber.shade700,
|
activeColor: Colors.amber.shade700,
|
||||||
onChanged: (v) => ref.read(settingsProvider.notifier).updateUser(
|
onChanged: isUnder18
|
||||||
userSettings.copyWith(nsfwEnabled: v),
|
? null
|
||||||
),
|
: (v) => ref.read(settingsProvider.notifier).updateUser(
|
||||||
|
userSettings.copyWith(nsfwEnabled: v),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,8 @@ class AuthService {
|
||||||
required bool acceptPrivacy,
|
required bool acceptPrivacy,
|
||||||
bool emailNewsletter = false,
|
bool emailNewsletter = false,
|
||||||
bool emailContact = false,
|
bool emailContact = false,
|
||||||
|
required int birthMonth,
|
||||||
|
required int birthYear,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final uri = Uri.parse('${ApiConfig.baseUrl}/auth/register');
|
final uri = Uri.parse('${ApiConfig.baseUrl}/auth/register');
|
||||||
|
|
@ -279,6 +281,8 @@ class AuthService {
|
||||||
'accept_privacy': acceptPrivacy,
|
'accept_privacy': acceptPrivacy,
|
||||||
'email_newsletter': emailNewsletter,
|
'email_newsletter': emailNewsletter,
|
||||||
'email_contact': emailContact,
|
'email_contact': emailContact,
|
||||||
|
'birth_month': birthMonth,
|
||||||
|
'birth_year': birthYear,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue