Compare commits
10 Commits
edit-proj-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffa7bac319 | ||
| e0e0a0a183 | |||
| ea662f4430 | |||
| 81647e1f3c | |||
| bf1ee78025 | |||
|
|
66b7d0f46e | ||
|
|
63dcf44125 | ||
| cf1ecdfb4c | |||
| e9ad61b780 | |||
| ad8df3eb93 |
@@ -333,11 +333,11 @@ The `serve` functionality provides a local HTTP server for testing production bu
|
||||
- If there are DB changes: before updating the test server, open browser(s) with
|
||||
current version to test DB migrations.
|
||||
|
||||
- Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run
|
||||
- Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run:
|
||||
`npm install`.
|
||||
|
||||
- Run a build to make sure package-lock version is updated, linting works, etc:
|
||||
`npm install && npm run build:web`
|
||||
- Run a build to make sure linting works, etc:
|
||||
`npm run build:web`
|
||||
|
||||
- Commit everything (since the commit hash is used the app).
|
||||
|
||||
@@ -346,7 +346,7 @@ current version to test DB migrations.
|
||||
|
||||
- Tag with the new version,
|
||||
[online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or
|
||||
`git tag 1.0.2 && git push origin 1.0.2`.
|
||||
`git tag 1.3.13 && git push origin 1.3.13`.
|
||||
|
||||
- For test, build the app:
|
||||
|
||||
|
||||
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
|
||||
## [1.3.13] - 2026.04.05
|
||||
### Added
|
||||
- Ability to select project that the current one fulfills
|
||||
- Separate Terms & Conditions page (required for SMS campaigns)
|
||||
### Fixed
|
||||
- Edits to a 'give' would delete the image
|
||||
|
||||
|
||||
## [1.3.12] - 2026.03.21
|
||||
### Added
|
||||
- Device wake-up for notifications
|
||||
|
||||
23
README.md
23
README.md
@@ -15,10 +15,31 @@ Quick start:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
### Web
|
||||
|
||||
```bash
|
||||
npm run build:web:dev
|
||||
```
|
||||
|
||||
To be able to take action on the platform: go to [the test page](http://localhost:8080/test) and click "Become User 0".
|
||||
Then go to [the test page](http://localhost:8080/test) and click "Become User 0" to take action on the platform.
|
||||
|
||||
### Android
|
||||
|
||||
```bash
|
||||
npm run build:android:test:run
|
||||
```
|
||||
|
||||
Assumes ADB is installed; see [Android Build](BUILDING.md#android-build) for SDK, emulator, and `PATH` setup.
|
||||
|
||||
### iOS
|
||||
|
||||
```bash
|
||||
npm run build:ios:studio
|
||||
```
|
||||
|
||||
Assumes Xcode and Xcode Command Line Tools are installed.
|
||||
|
||||
See [BUILDING.md](BUILDING.md) for comprehensive build instructions for all platforms (Web, Electron, iOS, Android, Docker).
|
||||
|
||||
|
||||
3
ios/.gitignore
vendored
3
ios/.gitignore
vendored
@@ -17,6 +17,7 @@ App/App/config.xml
|
||||
App/App.xcodeproj/xcuserdata/*.xcuserdatad/
|
||||
App/App.xcodeproj/*.xcuserstate
|
||||
|
||||
# Generated Icons from capacitor-assets (also Contents.json which is confusing; see BUILDING.md)
|
||||
# Generated by capacitor-assets at build time (not in repo). Fresh clones lack these
|
||||
# folders; scripts/common.sh ensure_ios_capacitor_asset_directories creates them before generate.
|
||||
App/App/Assets.xcassets/AppIcon.appiconset
|
||||
App/App/Assets.xcassets/Splash.imageset
|
||||
|
||||
827
package-lock.json
generated
827
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "giftopia",
|
||||
"version": "1.3.13-beta",
|
||||
"version": "1.3.14-beta",
|
||||
"description": "Giftopia App",
|
||||
"author": {
|
||||
"name": "Gift Economies Team"
|
||||
@@ -28,7 +28,7 @@
|
||||
"auto-run:electron": "./scripts/auto-run.sh --platform=electron",
|
||||
"build:capacitor": "VITE_GIT_HASH=`git log -1 --pretty=format:%h` vite build --mode capacitor --config vite.config.capacitor.mts",
|
||||
"build:capacitor:sync": "npm run build:capacitor && npx cap sync && node scripts/restore-local-plugins.js",
|
||||
"build:native": "vite build && npx cap sync && node scripts/restore-local-plugins.js && npx capacitor-assets generate",
|
||||
"build:native": "vite build && npx cap sync && node scripts/restore-local-plugins.js && bash -c 'source scripts/common.sh && ensure_ios_capacitor_asset_directories' && npx capacitor-assets generate",
|
||||
"assets:config": "npx tsx scripts/assets-config.ts",
|
||||
"assets:validate": "npx tsx scripts/assets-validator.ts",
|
||||
"assets:validate:android": "./scripts/build-android.sh --assets-only",
|
||||
|
||||
@@ -222,7 +222,8 @@ build_ios_app() {
|
||||
|
||||
if [ "$BUILD_TYPE" = "debug" ]; then
|
||||
build_config="Debug"
|
||||
destination="platform=iOS Simulator,name=iPhone 15 Pro"
|
||||
# Any Simulator — avoids hardcoding a device name (e.g. iPhone 15 Pro) that may not exist in newer Xcode runtimes
|
||||
destination="generic/platform=iOS Simulator"
|
||||
else
|
||||
build_config="Release"
|
||||
destination="platform=iOS,id=auto"
|
||||
@@ -232,15 +233,21 @@ build_ios_app() {
|
||||
|
||||
cd ios/App
|
||||
|
||||
# Build the app
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
# Build the app:
|
||||
# -quiet: skip the huge export VAR dump (compiler warnings still show unless suppressed below).
|
||||
# SWIFT_SUPPRESS_WARNINGS / GCC_WARN_INHIBIT_ALL_WARNINGS: quiet CLI output from Pods + plugins;
|
||||
# build in Xcode for full diagnostics. Real errors still fail the build.
|
||||
xcodebuild -quiet \
|
||||
-workspace App.xcworkspace \
|
||||
-scheme "$scheme" \
|
||||
-configuration "$build_config" \
|
||||
-destination "$destination" \
|
||||
build \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
SWIFT_SUPPRESS_WARNINGS=YES \
|
||||
GCC_WARN_INHIBIT_ALL_WARNINGS=YES
|
||||
|
||||
cd ../..
|
||||
|
||||
@@ -406,6 +413,7 @@ fi
|
||||
# Handle assets-only mode
|
||||
if [ "$ASSETS_ONLY" = true ]; then
|
||||
log_info "Assets-only mode: generating assets"
|
||||
ensure_ios_capacitor_asset_directories
|
||||
safe_execute "Generating assets" "npx capacitor-assets generate --ios" || exit 7
|
||||
log_success "Assets generation completed successfully!"
|
||||
exit 0
|
||||
@@ -555,6 +563,7 @@ safe_execute "Installing CocoaPods dependencies" "run_pod_install_with_workaroun
|
||||
safe_execute "Syncing with Capacitor" "run_cap_sync_with_workaround" || exit 6
|
||||
|
||||
# Step 7: Generate assets
|
||||
ensure_ios_capacitor_asset_directories
|
||||
safe_execute "Generating assets" "npx capacitor-assets generate --ios" || exit 7
|
||||
|
||||
# Step 8: Build iOS app
|
||||
@@ -564,16 +573,19 @@ safe_execute "Building iOS app" "build_ios_app" || exit 5
|
||||
if [ "$BUILD_IPA" = true ]; then
|
||||
log_info "Building IPA package..."
|
||||
cd ios/App
|
||||
xcodebuild -workspace App.xcworkspace \
|
||||
xcodebuild -quiet \
|
||||
-workspace App.xcworkspace \
|
||||
-scheme App \
|
||||
-configuration Release \
|
||||
-archivePath build/App.xcarchive \
|
||||
archive \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
SWIFT_SUPPRESS_WARNINGS=YES \
|
||||
GCC_WARN_INHIBIT_ALL_WARNINGS=YES
|
||||
|
||||
xcodebuild -exportArchive \
|
||||
xcodebuild -quiet -exportArchive \
|
||||
-archivePath build/App.xcarchive \
|
||||
-exportPath build/ \
|
||||
-exportOptionsPlist exportOptions.plist
|
||||
|
||||
@@ -337,6 +337,27 @@ parse_args() {
|
||||
fi
|
||||
}
|
||||
|
||||
# iOS: capacitor-assets writes into AppIcon.appiconset and Splash.imageset under
|
||||
# Assets.xcassets. Those paths are gitignored (generated). On a fresh clone the
|
||||
# folders and Contents.json are missing; the tool opens Contents.json before writing
|
||||
# PNGs, so we create minimal asset-catalog stubs when absent.
|
||||
ensure_ios_capacitor_asset_directories() {
|
||||
local base="ios/App/App/Assets.xcassets"
|
||||
if [ ! -d "$base" ]; then
|
||||
log_warn "Missing $base — cannot prepare iOS asset directories"
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$base/AppIcon.appiconset" "$base/Splash.imageset"
|
||||
local minimal_contents='{"images":[],"info":{"author":"xcode","version":1}}'
|
||||
if [ ! -f "$base/AppIcon.appiconset/Contents.json" ]; then
|
||||
printf '%s\n' "$minimal_contents" > "$base/AppIcon.appiconset/Contents.json"
|
||||
fi
|
||||
if [ ! -f "$base/Splash.imageset/Contents.json" ]; then
|
||||
printf '%s\n' "$minimal_contents" > "$base/Splash.imageset/Contents.json"
|
||||
fi
|
||||
log_debug "Ensured iOS capacitor-assets output directories exist"
|
||||
}
|
||||
|
||||
# Export functions for use in child scripts
|
||||
export -f log_info log_success log_warn log_error log_debug log_step
|
||||
export -f measure_time print_header print_footer
|
||||
@@ -344,4 +365,5 @@ export -f check_command check_directory check_file
|
||||
export -f safe_execute check_venv get_git_hash
|
||||
export -f clean_build_artifacts validate_env_vars
|
||||
export -f setup_build_env setup_app_directories load_env_file print_env_vars
|
||||
export -f print_usage parse_args
|
||||
export -f print_usage parse_args
|
||||
export -f ensure_ios_capacitor_asset_directories
|
||||
@@ -1175,11 +1175,6 @@ export const NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_CONFIRM = {
|
||||
message: "",
|
||||
};
|
||||
|
||||
export const NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR = {
|
||||
title: "Error",
|
||||
message: "There was a problem deleting the image.",
|
||||
};
|
||||
|
||||
export const NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER = {
|
||||
title: "Missing Identifier",
|
||||
message: "You must select an identifier before you can record a give.",
|
||||
|
||||
@@ -136,6 +136,11 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: "help-onboarding",
|
||||
component: () => import("../views/HelpOnboardingView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/help-terms",
|
||||
name: "help-terms",
|
||||
component: () => import("../views/HelpTermsView.vue"),
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
|
||||
@@ -118,11 +118,13 @@ import {
|
||||
CONTACT_CSV_HEADER,
|
||||
CONTACT_IMPORT_CONFIRM_URL_PATH_TIME_SAFARI,
|
||||
generateEndorserJwtUrlForAccount,
|
||||
register,
|
||||
setVisibilityUtil,
|
||||
} from "../libs/endorserServer";
|
||||
import UserNameDialog from "../components/UserNameDialog.vue";
|
||||
import { retrieveAccountMetadata } from "../libs/util";
|
||||
|
||||
import { AxiosError } from "axios";
|
||||
import { Account } from "@/db/tables/accounts";
|
||||
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
|
||||
import {
|
||||
@@ -139,7 +141,10 @@ import {
|
||||
NOTIFY_QR_URL_COPIED,
|
||||
NOTIFY_QR_CODE_HELP,
|
||||
NOTIFY_QR_DID_COPIED,
|
||||
NOTIFY_QR_REGISTRATION_SUBMITTED,
|
||||
NOTIFY_QR_REGISTRATION_ERROR,
|
||||
createQRContactAddedMessage,
|
||||
createQRRegistrationSuccessMessage,
|
||||
QR_TIMEOUT_MEDIUM,
|
||||
QR_TIMEOUT_STANDARD,
|
||||
QR_TIMEOUT_LONG,
|
||||
@@ -204,6 +209,7 @@ export default class ContactQRScanFull extends Vue {
|
||||
activeDid = "";
|
||||
apiServer = "";
|
||||
givenName = "";
|
||||
hideRegisterPromptOnNewContact = false;
|
||||
isRegistered = false;
|
||||
profileImageUrl = "";
|
||||
qrValue = "";
|
||||
@@ -278,6 +284,8 @@ export default class ContactQRScanFull extends Vue {
|
||||
|
||||
this.apiServer = settings.apiServer || "";
|
||||
this.givenName = settings.firstName || "";
|
||||
this.hideRegisterPromptOnNewContact =
|
||||
!!settings.hideRegisterPromptOnNewContact;
|
||||
this.isRegistered = !!settings.isRegistered;
|
||||
this.profileImageUrl = settings.profileImageUrl || "";
|
||||
|
||||
@@ -575,6 +583,34 @@ export default class ContactQRScanFull extends Vue {
|
||||
createQRContactAddedMessage(!!this.activeDid),
|
||||
QR_TIMEOUT_STANDARD,
|
||||
);
|
||||
|
||||
if (
|
||||
this.isRegistered &&
|
||||
!this.hideRegisterPromptOnNewContact &&
|
||||
!contact.registered
|
||||
) {
|
||||
setTimeout(() => {
|
||||
this.$notify(
|
||||
{
|
||||
group: "modal",
|
||||
type: "confirm",
|
||||
title: "Register",
|
||||
text: "Do you want to register them?",
|
||||
onCancel: async (stopAsking?: boolean) => {
|
||||
await this.handleRegistrationPromptResponse(stopAsking);
|
||||
},
|
||||
onNo: async (stopAsking?: boolean) => {
|
||||
await this.handleRegistrationPromptResponse(stopAsking);
|
||||
},
|
||||
onYes: async () => {
|
||||
await this.register(contact);
|
||||
},
|
||||
promptToStopAsking: true,
|
||||
},
|
||||
-1,
|
||||
);
|
||||
}, 500);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error saving contact to database:", {
|
||||
did: contact.did,
|
||||
@@ -585,6 +621,74 @@ export default class ContactQRScanFull extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
async register(contact: Contact) {
|
||||
logger.debug("Submitting contact registration", {
|
||||
did: contact.did,
|
||||
name: contact.name,
|
||||
});
|
||||
|
||||
try {
|
||||
const regResult = await register(
|
||||
this.activeDid,
|
||||
this.apiServer,
|
||||
this.axios,
|
||||
contact,
|
||||
);
|
||||
this.notify.toast("Submitted", NOTIFY_QR_REGISTRATION_SUBMITTED.message);
|
||||
if (regResult.success) {
|
||||
contact.registered = true;
|
||||
await this.$updateContact(contact.did, { registered: true });
|
||||
logger.debug("Contact registration successful", { did: contact.did });
|
||||
|
||||
this.notify.success(
|
||||
createQRRegistrationSuccessMessage(contact.name || ""),
|
||||
QR_TIMEOUT_LONG,
|
||||
);
|
||||
} else {
|
||||
this.notify.error(
|
||||
(regResult.error as string) || NOTIFY_QR_REGISTRATION_ERROR.message,
|
||||
QR_TIMEOUT_LONG,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error registering contact:", {
|
||||
did: contact.did,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
let userMessage = "There was an error.";
|
||||
const serverError = error as AxiosError;
|
||||
if (serverError) {
|
||||
if (
|
||||
serverError.response?.data &&
|
||||
typeof serverError.response.data === "object" &&
|
||||
"message" in serverError.response.data
|
||||
) {
|
||||
userMessage = (serverError.response.data as { message: string })
|
||||
.message;
|
||||
} else if (serverError.message) {
|
||||
userMessage = serverError.message;
|
||||
} else {
|
||||
userMessage = JSON.stringify(serverError.toJSON());
|
||||
}
|
||||
} else {
|
||||
userMessage = error as string;
|
||||
}
|
||||
this.notify.error(userMessage, QR_TIMEOUT_LONG);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleRegistrationPromptResponse(
|
||||
stopAsking?: boolean,
|
||||
): Promise<void> {
|
||||
if (stopAsking) {
|
||||
await this.$saveSettings({
|
||||
hideRegisterPromptOnNewContact: stopAsking,
|
||||
});
|
||||
this.hideRegisterPromptOnNewContact = stopAsking;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue lifecycle hook - component mounted
|
||||
* Sets up event listeners and starts scanning automatically
|
||||
|
||||
@@ -263,7 +263,6 @@ import { showSeedPhraseReminder } from "@/utils/seedPhraseReminder";
|
||||
import {
|
||||
NOTIFY_GIFTED_DETAILS_RETRIEVAL_ERROR,
|
||||
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_CONFIRM,
|
||||
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR,
|
||||
NOTIFY_GIFTED_DETAILS_NO_IDENTIFIER,
|
||||
NOTIFY_GIFT_ERROR_NEGATIVE_AMOUNT,
|
||||
NOTIFY_GIFTED_DETAILS_RECORDING_GIVE,
|
||||
@@ -302,6 +301,7 @@ export default class GiftedDetails extends Vue {
|
||||
giverName = "";
|
||||
hideBackButton = false;
|
||||
imageUrl = "";
|
||||
imageUrlToDelete = "";
|
||||
message = "";
|
||||
offerId = "";
|
||||
prevCredToEdit?: GenericCredWrapper<GiveActionClaim>;
|
||||
@@ -517,7 +517,10 @@ export default class GiftedDetails extends Vue {
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.deleteImage(); // not awaiting, so they'll go back immediately
|
||||
// Only delete freshly uploaded images, not ones from an existing claim
|
||||
if (this.imageUrl && this.imageUrl !== this.prevCredToEdit?.claim?.image) {
|
||||
this.deleteImage(this.imageUrl); // not awaiting, so they'll go back immediately
|
||||
}
|
||||
if (this.destinationPathAfter) {
|
||||
(this.$router as Router).push({ path: this.destinationPathAfter });
|
||||
} else {
|
||||
@@ -526,7 +529,10 @@ export default class GiftedDetails extends Vue {
|
||||
}
|
||||
|
||||
cancelBack() {
|
||||
this.deleteImage(); // not awaiting, so they'll go back immediately
|
||||
// Only delete freshly uploaded images, not ones from an existing claim
|
||||
if (this.imageUrl && this.imageUrl !== this.prevCredToEdit?.claim?.image) {
|
||||
this.deleteImage(this.imageUrl); // not awaiting, so they'll go back immediately
|
||||
}
|
||||
(this.$router as Router).back();
|
||||
}
|
||||
|
||||
@@ -539,13 +545,18 @@ export default class GiftedDetails extends Vue {
|
||||
confirmDeleteImage() {
|
||||
this.notify.confirm(
|
||||
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_CONFIRM.message,
|
||||
this.deleteImage,
|
||||
() => {
|
||||
// Stage the image for deletion on submit rather than deleting immediately,
|
||||
// so that canceling the edit doesn't destroy the referenced image.
|
||||
this.imageUrlToDelete = this.imageUrl;
|
||||
this.imageUrl = "";
|
||||
},
|
||||
TIMEOUTS.LONG,
|
||||
);
|
||||
}
|
||||
|
||||
async deleteImage() {
|
||||
if (!this.imageUrl) {
|
||||
async deleteImage(imageUrl: string) {
|
||||
if (!imageUrl) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -559,38 +570,21 @@ export default class GiftedDetails extends Vue {
|
||||
);
|
||||
}
|
||||
const response = await this.axios.delete(
|
||||
DEFAULT_IMAGE_API_SERVER +
|
||||
"/image/" +
|
||||
encodeURIComponent(this.imageUrl),
|
||||
DEFAULT_IMAGE_API_SERVER + "/image/" + encodeURIComponent(imageUrl),
|
||||
{ headers },
|
||||
);
|
||||
if (response.status === 204) {
|
||||
// don't bother with a notification
|
||||
// (either they'll simply continue or they're canceling and going back)
|
||||
} else {
|
||||
logger.error("Problem deleting image:", response);
|
||||
this.notify.error(
|
||||
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR.message,
|
||||
TIMEOUTS.LONG,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.imageUrl = "";
|
||||
} catch (error) {
|
||||
logger.error("Error deleting image:", error);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((error as any)?.response?.status === 404) {
|
||||
logger.log("Weird: the image was already deleted.", error);
|
||||
|
||||
this.imageUrl = "";
|
||||
|
||||
// it already doesn't exist so we won't say anything to the user
|
||||
logger.log("Image was already deleted:", error);
|
||||
} else {
|
||||
this.notify.error(
|
||||
NOTIFY_GIFTED_DETAILS_DELETE_IMAGE_ERROR.message,
|
||||
TIMEOUTS.LONG,
|
||||
);
|
||||
logger.error("Failed to delete image from server:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -733,6 +727,12 @@ export default class GiftedDetails extends Vue {
|
||||
TIMEOUTS.LONG,
|
||||
);
|
||||
} else {
|
||||
// Delete the old image from storage now that the edit is saved
|
||||
if (this.imageUrlToDelete) {
|
||||
this.deleteImage(this.imageUrlToDelete); // not awaiting
|
||||
this.imageUrlToDelete = "";
|
||||
}
|
||||
|
||||
this.notify.success(
|
||||
NOTIFY_GIFTED_DETAILS_GIFT_RECORDED.message,
|
||||
TIMEOUTS.SHORT,
|
||||
|
||||
144
src/views/HelpTermsView.vue
Normal file
144
src/views/HelpTermsView.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<QuickNav />
|
||||
|
||||
<!-- CONTENT -->
|
||||
<section id="Content" class="p-6 pb-24 max-w-3xl mx-auto">
|
||||
<!-- Sub View Heading -->
|
||||
<div id="SubViewHeading" class="flex gap-4 items-start mb-8">
|
||||
<h1 class="grow text-xl text-center font-semibold leading-tight">
|
||||
Terms & Conditions and Privacy Policies
|
||||
</h1>
|
||||
|
||||
<!-- Back -->
|
||||
<a
|
||||
class="order-first text-lg text-center leading-none p-1"
|
||||
@click="$router.go(-1)"
|
||||
>
|
||||
<font-awesome icon="chevron-left" class="block text-center w-[1em]" />
|
||||
</a>
|
||||
|
||||
<!-- Help button -->
|
||||
<router-link
|
||||
:to="{ name: 'help' }"
|
||||
class="block ms-auto text-sm text-center text-white bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] p-1.5 rounded-full"
|
||||
>
|
||||
<font-awesome icon="question" class="block text-center w-[1em]" />
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- eslint-disable prettier/prettier -->
|
||||
<div>
|
||||
<p style="display:inline; align-items: center">
|
||||
This work is public domain. (If you like rules, reference
|
||||
<a
|
||||
href="http://creativecommons.org/publicdomain/zero/1.0?ref=chooser-v1"
|
||||
target="_blank"
|
||||
rel="license noopener noreferrer"
|
||||
>
|
||||
<span class="text-blue-500 mr-1">CC0 1.0</span>
|
||||
<img
|
||||
src="../assets/help/creative-commons-circle.svg"
|
||||
alt="CC circle"
|
||||
width="20"
|
||||
class="display: inline"
|
||||
/>
|
||||
<img
|
||||
src="../assets/help/creative-commons-zero.svg"
|
||||
alt="CC zero"
|
||||
width="20"
|
||||
style="display: inline"
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
This is offered freely, with the hope that it helps but without any
|
||||
warranty or guarantee. When you share data or even look at information here,
|
||||
you accept the risk that goes with those activities. In other words,
|
||||
if you expect some functionality or you expect some protection, and you
|
||||
feel it is appropriate to force those expectations on the system or its
|
||||
operators or creators, then you are not allowed to use it.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
Here is how your data is used:
|
||||
</p>
|
||||
<ul class="list-disc list-outside ml-4">
|
||||
<li>
|
||||
If sending images, a server stores them. They can be removed by editing
|
||||
each claim and deleting the image.
|
||||
</li>
|
||||
<li>
|
||||
If sending other partner system data (eg. to Trustroots) a public key
|
||||
and message data are stored on a server. Those can be removed via
|
||||
direct personal request (email
|
||||
<a :href="`mailto:${SUPPORT_EMAIL}`" class="text-blue-500">
|
||||
{{ SUPPORT_EMAIL }}
|
||||
</a>).
|
||||
</li>
|
||||
<li>
|
||||
For all other claim data,
|
||||
<a
|
||||
href="https://endorser.ch/privacy-policy"
|
||||
target="_blank"
|
||||
class="text-blue-500"
|
||||
>
|
||||
the Endorser Service has this Privacy Policy.
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="mt-4">
|
||||
<!--
|
||||
This section is for Twilio's A2P Campaign requirements.
|
||||
They say: Ensure it includes the program name, description, message/data rates, message frequency, support contact info, and opt-out instructions (HELP and STOP in bold).
|
||||
They link here for a sample: https://help.twilio.com/articles/223134847-Industry-standards-for-US-Short-Code-Terms-of-Service
|
||||
-->
|
||||
Here are the details for SMS notifications:
|
||||
<ul class="list-disc list-outside ml-4">
|
||||
<li>You may opt to receive SMS messages for two purposes:
|
||||
<ul class="list-disc list-outside ml-4">
|
||||
<li>A daily reminder message</li>
|
||||
<li>A notification of new activity for items that you are watching</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Before enabling these notifications, you must register your phone number and give permission to use it for searches.
|
||||
</li>
|
||||
<li>
|
||||
Once your phone number is registered and linked to your DID, you can enable or disable either kind of SMS message.
|
||||
You can disable these any time with the same toggle.
|
||||
</li>
|
||||
<li>
|
||||
If you lose your credentials, you can register your phone with a different DID.
|
||||
Then you can enable and disable notifications for your phone.
|
||||
</li>
|
||||
<li>
|
||||
Carriers are not liable for delayed or undelivered messages.
|
||||
</li>
|
||||
<li>
|
||||
As always, message and data rates may apply for any messages sent to you from us and to us from you.
|
||||
You will receive at most one of each kind of message per day.
|
||||
If you have any questions about your text plan or data plan, it is best to contact your wireless provider.
|
||||
</li>
|
||||
<li>
|
||||
Our servers will only store your phone number and the type of notifications you have enabled,
|
||||
along with the explicit signed permission to use it for searches.
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<!-- eslint-enable -->
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-facing-decorator";
|
||||
|
||||
import QuickNav from "../components/QuickNav.vue";
|
||||
import { SUPPORT_EMAIL } from "../constants/app";
|
||||
|
||||
@Component({ components: { QuickNav } })
|
||||
export default class HelpTermsView extends Vue {
|
||||
SUPPORT_EMAIL = SUPPORT_EMAIL;
|
||||
}
|
||||
</script>
|
||||
@@ -480,46 +480,14 @@
|
||||
</p>
|
||||
|
||||
<h2 class="text-xl font-semibold">What are the terms & conditions and the privacy policy?</h2>
|
||||
<p style="display:inline; align-items: center">
|
||||
This work is public domain. (If you like rules, reference
|
||||
<a href="http://creativecommons.org/publicdomain/zero/1.0?ref=chooser-v1" target="_blank" rel="license noopener noreferrer">
|
||||
<span class="text-blue-500 mr-1">CC0 1.0</span>
|
||||
<img
|
||||
src="../assets/help/creative-commons-circle.svg"
|
||||
alt="CC circle"
|
||||
width="20"
|
||||
class="display: inline"
|
||||
/>
|
||||
<img
|
||||
src="../assets/help/creative-commons-zero.svg"
|
||||
alt="CC zero"
|
||||
width="20"
|
||||
style="display: inline"
|
||||
/>
|
||||
</a>
|
||||
.) This is offered freely, with the hope that it helps but without any warranty or guarantee;
|
||||
if it helps you then enjoy using it,
|
||||
but if you may try to forcibly collect damages for things you think it should do (or not do)
|
||||
then don't use it.
|
||||
<br />
|
||||
As for data & privacy:
|
||||
<p>
|
||||
<router-link
|
||||
class="text-blue-500"
|
||||
:to="{ name: 'help-terms' }"
|
||||
>
|
||||
Read them here.
|
||||
</router-link>
|
||||
</p>
|
||||
<ul class="list-disc list-outside ml-4">
|
||||
<li>
|
||||
If sending images, a server stores them. They can be removed by editing each claim
|
||||
and deleting the image.
|
||||
</li>
|
||||
<li>
|
||||
If sending other partner system data (eg. to Trustroots) a public key and message
|
||||
data are stored on a server. Those can be removed via direct personal request (via contact below).
|
||||
</li>
|
||||
<li>
|
||||
For all other claim data,
|
||||
<a href="https://endorser.ch/privacy-policy" target="_blank" class="text-blue-500">
|
||||
the Endorser Service has this Privacy Policy.
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2 class="text-xl font-semibold">How can I contribute?</h2>
|
||||
<p>
|
||||
|
||||
@@ -223,7 +223,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
);
|
||||
this.notify.error(
|
||||
timeResult?.error || NOTIFY_BVC_TIME_ERROR.message,
|
||||
TIMEOUTS.LONG,
|
||||
timeResult?.error ? TIMEOUTS.MODAL : TIMEOUTS.LONG,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -251,7 +251,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
);
|
||||
this.notify.error(
|
||||
attendResult?.error || NOTIFY_BVC_ATTENDANCE_ERROR.message,
|
||||
TIMEOUTS.LONG,
|
||||
attendResult?.error ? TIMEOUTS.MODAL : TIMEOUTS.LONG,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -276,7 +276,7 @@ export default class QuickActionBvcBeginView extends Vue {
|
||||
logger.error("[QuickActionBvcBeginView] Error sending claims:", error);
|
||||
this.notify.error(
|
||||
error.userMessage || NOTIFY_BVC_SUBMISSION_ERROR.message,
|
||||
TIMEOUTS.LONG,
|
||||
error.userMessage ? TIMEOUTS.MODAL : TIMEOUTS.LONG,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user